Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved supports for in-app/game notifications and toasts #587

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/client/java/minicraft/core/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import minicraft.saveload.Load;
import minicraft.saveload.Version;
import minicraft.screen.Display;
import minicraft.screen.AppToast;
import minicraft.screen.GameToast;
import minicraft.screen.ResourcePackDisplay;
import minicraft.screen.TitleDisplay;
import minicraft.util.Logging;
Expand All @@ -30,7 +32,9 @@ protected Game() {
public static InputHandler input; // Input used in Game, Player, and just about all the *Menu classes.
public static Player player;

public static List<String> notifications = new ArrayList<>();
public static List<String> inGameNotifications = new ArrayList<>();
public static ArrayDeque<AppToast> inAppToasts = new ArrayDeque<>();
public static ArrayDeque<GameToast> inGameToasts = new ArrayDeque<>(); // Canvas size is limited, so handled one by one

public static int MAX_FPS;

Expand Down
151 changes: 146 additions & 5 deletions src/client/java/minicraft/core/Renderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@
import minicraft.item.ToolType;
import minicraft.item.WateringCanItem;
import minicraft.level.Level;
import minicraft.screen.GameToast;
import minicraft.screen.LoadingDisplay;
import minicraft.screen.Menu;
import minicraft.screen.AppToast;
import minicraft.screen.QuestsDisplay;
import minicraft.screen.RelPos;
import minicraft.screen.TutorialDisplayHandler;
import minicraft.screen.entry.ListEntry;
import minicraft.screen.entry.StringEntry;
import minicraft.util.Quest;
import minicraft.util.Quest.QuestSeries;
import org.intellij.lang.annotations.MagicConstant;

import javax.imageio.ImageIO;

Expand Down Expand Up @@ -106,6 +109,7 @@ public static void initScreen() {
hudSheet = new LinkedSprite(SpriteType.Gui, "hud");

canvas.createBufferStrategy(3);
appStatusBar.initialize();
}


Expand All @@ -123,6 +127,13 @@ public static void render() {
if (currentDisplay != null) // Renders menu, if present.
currentDisplay.render(screen);

appStatusBar.render();

AppToast toast;
if ((toast = inAppToasts.peek()) != null) {
toast.render(screen);
}

if (!canvas.hasFocus())
renderFocusNagger(); // Calls the renderFocusNagger() method, which creates the "Click to Focus" message.

Expand Down Expand Up @@ -183,6 +194,131 @@ public static void render() {
}
}

public static final AppStatusBar appStatusBar = new AppStatusBar();

public static class AppStatusBar {
private static final int DURATION_ON_UPDATE = 90; // 1.5s

public final AppStatusElement HARDWARE_ACCELERATION_STATUS = new HardwareAccelerationElementStatus();

private AppStatusBar() {}

private int duration = 120; // Shows for 2 seconds initially.

private void render() {
if (duration == 0) return;
MinicraftImage sheet = spriteLinker.getSheet(SpriteType.Gui, "app_status_bar"); // Obtains sheet.

// Background
for (int x = 0; x < 12; ++x) {
for (int y = 0; y < 2; ++y) {
screen.render(Screen.w - 16 * 8 + x * 8, y * 8, x, y, 0, sheet);
}
}

// Hardware Acceleration Status (width = 16)
HARDWARE_ACCELERATION_STATUS.render(Screen.w - 16 * 8 + 5, 2, sheet);
}

void tick() {
if (duration > 0)
duration--;
HARDWARE_ACCELERATION_STATUS.tick();
}

void show(int duration) {
this.duration = Math.max(this.duration, duration);
}

private void onStatusUpdate() {
show(DURATION_ON_UPDATE);
}

private void initialize() {
HARDWARE_ACCELERATION_STATUS.initialize();
}

public abstract class AppStatusElement {
// width == 16 - size * 2
protected final int size; // 0: largest, 1: smaller, etc. (gradually)

private AppStatusElement(int size) {
this.size = size;
}

private static final int BLINK_PERIOD = 10; // 6 Hz

private int durationUpdated = 0;
private boolean blinking = false;
private int blinkTick = 0;

protected void render(int x, int y, MinicraftImage sheet) {
if (durationUpdated > 0) {
if (blinking) {
screen.render(x, y, 10, 3 + size, 0, sheet);
}
}
}

protected void tick() {
if (durationUpdated > 0) {
durationUpdated--;
if (blinkTick == 0) {
blinkTick = BLINK_PERIOD;
blinking = !blinking;
} else blinkTick--;
}
}

protected void updateStatus() {
durationUpdated = DURATION_ON_UPDATE;
blinking = false;
blinkTick = 0;
onStatusUpdate();
}

public abstract void updateStatus(int status);

protected void initialize() {}
}

public class HardwareAccelerationElementStatus extends AppStatusElement {
public final int ACCELERATION_ON = 0;
public final int ACCELERATION_OFF = 1;

@MagicConstant(intValues = {ACCELERATION_ON, ACCELERATION_OFF})
private int status = ACCELERATION_ON;

private HardwareAccelerationElementStatus() {
super(0);
}

@Override
protected void initialize() {
status = Boolean.parseBoolean(System.getProperty("sun.java2d.opengl")) ?
ACCELERATION_ON : ACCELERATION_OFF;
}

@Override
protected void render(int x, int y, MinicraftImage sheet) {
super.render(x, y, sheet);
if (status == ACCELERATION_ON) {
for (int xx = 0; xx < 2; ++xx)
screen.render(x + xx * 8, y, xx, 4, 0, sheet);
} else {
for (int xx = 0; xx < 2; ++xx)
screen.render(x + xx * 8, y, 2 + xx, 4, 0, sheet);
}
}

@Override
public void updateStatus(int status) {
super.updateStatus();
this.status = status;
}
}
}


private static void renderLevel() {
Level level = levels[currentLevel];
Expand Down Expand Up @@ -268,18 +404,18 @@ private static void renderGui() {
// NOTIFICATIONS

Updater.updateNoteTick = false;
if (permStatus.size() == 0 && notifications.size() > 0) {
if (permStatus.size() == 0 && inGameNotifications.size() > 0) {
Updater.updateNoteTick = true;
if (notifications.size() > 3) { // Only show 3 notifs max at one time; erase old notifs.
notifications = notifications.subList(notifications.size() - 3, notifications.size());
if (inGameNotifications.size() > 3) { // Only show 3 notifs max at one time; erase old notifs.
inGameNotifications = inGameNotifications.subList(inGameNotifications.size() - 3, inGameNotifications.size());
}

if (Updater.notetick > 180) { // Display time per notification.
notifications.remove(0);
inGameNotifications.remove(0);
Updater.notetick = 0;
}
List<String> print = new ArrayList<>();
for (String n : notifications) {
for (String n : inGameNotifications) {
for (String l : Font.getLines(n, Screen.w, Screen.h, 0))
print.add(l);
}
Expand Down Expand Up @@ -422,6 +558,11 @@ private static void renderGui() {
TutorialDisplayHandler.render(screen);
renderQuestsDisplay();
renderDebugInfo();

GameToast toast;
if ((toast = inGameToasts.peek()) != null) {
toast.render(screen);
}
}

public static void renderBossbar(int length, String title) {
Expand Down
27 changes: 26 additions & 1 deletion src/client/java/minicraft/core/Updater.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import minicraft.saveload.Save;
import minicraft.screen.Display;
import minicraft.screen.EndGameDisplay;
import minicraft.screen.GameToast;
import minicraft.screen.LevelTransitionDisplay;
import minicraft.screen.AppToast;
import minicraft.screen.PlayerDeathDisplay;
import minicraft.screen.TutorialDisplayHandler;
import minicraft.screen.WorldSelectDisplay;
Expand Down Expand Up @@ -177,7 +179,20 @@ public static void tick() {
scoreTime--;
}

Renderer.appStatusBar.tick();
if (input.getMappedKey("9").isDown())
Renderer.appStatusBar.show(1);
if (updateNoteTick) notetick++;
AppToast appToast;
if ((appToast = inAppToasts.peek()) != null) {
boolean refresh = true;
if (appToast.isExpired()) {
inAppToasts.pop(); // Removes
refresh = (appToast = inAppToasts.peek()) != null; // Tries getting new
}

if (refresh) appToast.tick();
}

// This is the general action statement thing! Regulates menus, mostly.
if (!Renderer.canvas.hasFocus()) {
Expand Down Expand Up @@ -213,6 +228,16 @@ public static void tick() {
}

player.tick(); // Ticks the player when there's no menu.
GameToast gameToast;
if ((gameToast = inGameToasts.peek()) != null) {
boolean refresh = true;
if (gameToast.isExpired()) {
inGameToasts.pop(); // Removes
refresh = (gameToast = inGameToasts.peek()) != null; // Tries getting new
}

if (refresh) gameToast.tick();
}

if (level != null) {
level.tick(true);
Expand Down Expand Up @@ -337,7 +362,7 @@ public static void notifyAll(String msg) {

public static void notifyAll(String msg, int notetick) {
msg = Localization.getLocalized(msg);
notifications.add(msg);
inGameNotifications.add(msg);
Updater.notetick = notetick;
}
}
2 changes: 1 addition & 1 deletion src/client/java/minicraft/core/World.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public static void resetGame(boolean keepPlayer) {
playerDeadTime = 0;
currentLevel = 3;
Updater.asTick = 0;
Updater.notifications.clear();
Updater.inGameNotifications.clear();

// Adds a new player
if (keepPlayer) {
Expand Down
2 changes: 1 addition & 1 deletion src/client/java/minicraft/entity/furniture/Bed.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static boolean checkCanSleep(Player player) {
// It is too early to sleep; display how much time is remaining.
int sec = (int) Math.ceil((Updater.sleepStartTime - Updater.tickCount) * 1.0 / Updater.normSpeed); // gets the seconds until sleeping is allowed. // normSpeed is in tiks/sec.
String note = Localization.getLocalized("minicraft.notification.cannot_sleep", sec / 60, sec % 60);
Game.notifications.add(note); // Add the notification displaying the time remaining in minutes and seconds.
Game.inGameNotifications.add(note); // Add the notification displaying the time remaining in minutes and seconds.

return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/client/java/minicraft/entity/furniture/DeathChest.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ public void touchedBy(Entity other) {

int returned = playerInv.add(i);
if (returned < total) {
Game.notifications.add("Your inventory is full!");
Game.inGameNotifications.add("Your inventory is full!");
return;
}

inventory.removeItem(i);
}

remove();
Game.notifications.add(Localization.getLocalized("minicraft.notification.death_chest_retrieved"));
Game.inGameNotifications.add(Localization.getLocalized("minicraft.notification.death_chest_retrieved"));
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/client/java/minicraft/entity/furniture/KnightStatue.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public KnightStatue(int health) {
public boolean interact(Player player, Item heldItem, Direction attackDir) {
if (!ObsidianKnight.active) {
if (touches == 0) { // Touched the first time.
Game.notifications.add(Localization.getLocalized("minicraft.notifications.statue_tapped"));
Game.inGameNotifications.add(Localization.getLocalized("minicraft.notifications.statue_tapped"));
touches++;
} else if (touches == 1) { // Touched the second time.
Game.notifications.add(Localization.getLocalized("minicraft.notifications.statue_touched"));
Game.inGameNotifications.add(Localization.getLocalized("minicraft.notifications.statue_touched"));
touches++;
} else { // Touched the third time.
// Awoken notifications is in Boss class
Expand All @@ -37,7 +37,7 @@ public boolean interact(Player player, Item heldItem, Direction attackDir) {

return true;
} else { // The boss is active.
Game.notifications.add(Localization.getLocalized("minicraft.notification.boss_limit"));
Game.inGameNotifications.add(Localization.getLocalized("minicraft.notification.boss_limit"));
return false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/client/java/minicraft/entity/mob/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ private void goFishing() {
}
if (itemData.startsWith(";")) {
// For secret messages :=)
Game.notifications.add(itemData.substring(1));
Game.inGameNotifications.add(itemData.substring(1));
} else {
if (Items.get(itemData).equals(Items.get("Raw Fish"))) {
AchievementsDisplay.setAchievement("minicraft.achievement.fish", true);
Expand Down
1 change: 1 addition & 0 deletions src/client/java/minicraft/gfx/Color.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class Color {
public static final int YELLOW = Color.get(1, 255, 255, 0);
public static final int MAGENTA = Color.get(1, 255, 0, 255);
public static final int CYAN = Color.get(1, 90, 204, 204);
public static final int GOLD = Color.get(1, 255, 193, 37);

public static final char COLOR_CHAR = '\u00A7';

Expand Down
2 changes: 1 addition & 1 deletion src/client/java/minicraft/item/FishingRodItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public boolean canAttack() {
@Override
public boolean isDepleted() {
if (random.nextInt(100) > 120 - uses + level * 6) { // Breaking is random, the lower the level, and the more times you use it, the higher the chance
Game.notifications.add("Your Fishing rod broke.");
Game.inGameNotifications.add("Your Fishing rod broke.");
return true;
}
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/client/java/minicraft/item/PotionType.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public boolean toggleEffect(Player player, boolean addEffect) {

if (playerDepth == 0) {
// player is in overworld
Game.notifications.add("You can't escape from here!");
Game.inGameNotifications.add("You can't escape from here!");
return false;
}

Expand Down
Loading
Loading