From 65707c6cba57ab82bb9c40b247c837b6ac11e2e5 Mon Sep 17 00:00:00 2001 From: rasmus Date: Tue, 28 Jan 2025 00:25:57 +0100 Subject: [PATCH 1/4] add optional time limit --- .../java/io/github/gaming32/bingo/Bingo.java | 2 + .../github/gaming32/bingo/BingoCommand.java | 26 ++++++++ .../gaming32/bingo/client/BingoClient.java | 64 +++++++++++------- .../gaming32/bingo/client/BoardScreen.java | 4 +- .../gaming32/bingo/client/ClientGame.java | 65 ++++++++++++++++--- .../client/ClientPayloadHandlerImpl.java | 17 +++-- .../TeamSelectionSpectatorCategory.java | 8 +-- .../github/gaming32/bingo/game/BingoGame.java | 53 +++++++++++++++ .../bingo/network/ClientPayloadHandler.java | 3 + .../messages/s2c/UpdateEndTimePayload.java | 25 +++++++ .../github/gaming32/bingo/util/BingoUtil.java | 15 +++++ .../resources/assets/bingo/lang/en_us.json | 2 + 12 files changed, 241 insertions(+), 43 deletions(-) create mode 100644 common/src/main/java/io/github/gaming32/bingo/network/messages/s2c/UpdateEndTimePayload.java diff --git a/common/src/main/java/io/github/gaming32/bingo/Bingo.java b/common/src/main/java/io/github/gaming32/bingo/Bingo.java index bd1b7f62..3e698393 100644 --- a/common/src/main/java/io/github/gaming32/bingo/Bingo.java +++ b/common/src/main/java/io/github/gaming32/bingo/Bingo.java @@ -21,6 +21,7 @@ import io.github.gaming32.bingo.network.messages.s2c.RemoveBoardPayload; import io.github.gaming32.bingo.network.messages.s2c.ResyncStatesPayload; import io.github.gaming32.bingo.network.messages.s2c.SyncTeamPayload; +import io.github.gaming32.bingo.network.messages.s2c.UpdateEndTimePayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateProgressPayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateStatePayload; import io.github.gaming32.bingo.platform.BingoPlatform; @@ -179,6 +180,7 @@ private static void registerPayloadHandlers() { registrar.register(PacketFlow.CLIENTBOUND, SyncTeamPayload.TYPE, SyncTeamPayload.CODEC); registrar.register(PacketFlow.CLIENTBOUND, UpdateProgressPayload.TYPE, UpdateProgressPayload.CODEC); registrar.register(PacketFlow.CLIENTBOUND, UpdateStatePayload.TYPE, UpdateStatePayload.CODEC); + registrar.register(PacketFlow.CLIENTBOUND, UpdateEndTimePayload.TYPE, UpdateEndTimePayload.CODEC); }); } diff --git a/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java b/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java index 1a798203..78285c90 100644 --- a/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java +++ b/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java @@ -129,6 +129,10 @@ public class BingoCommand { private static final CommandSwitch SEED = CommandSwitch .argument("--seed", LongArgumentType.longArg()) .build(RandomSupport::generateUniqueSeed); + private static final CommandSwitch TIME = CommandSwitch + .argument("--time", IntegerArgumentType.integer()) + .getter(IntegerArgumentType::getInteger) + .build(() -> 1); private static final CommandSwitch AUTO_FORFEIT_TIME = CommandSwitch .argument("--auto-forfeit-time", TimeArgument.time(0)) .getter(IntegerArgumentType::getInteger) @@ -353,6 +357,23 @@ public Component getDisplayName() { ) ) ) + .then(literal("time") + .requires(source -> source.hasPermission(2)) + .then(literal("set") + .then(argument("time", IntegerArgumentType.integer()) + .executes(ctx -> { + final var game = ctx.getSource().getServer().bingo$getGame(); + if (game == null) { + throw NO_GAME_RUNNING.create(); + } + int time = IntegerArgumentType.getInteger(ctx, "time"); + game.setScheduledEndTime(System.currentTimeMillis() + (long) time * 1000 * 60); + game.updateRemainingTime(ctx.getSource().getServer().getPlayerList()); + return 1; + }) + ) + ) + ) ); { @@ -364,6 +385,7 @@ public Component getDisplayName() { SIZE.addTo(startCommand); SEED.addTo(startCommand); + TIME.addTo(startCommand); AUTO_FORFEIT_TIME.addTo(startCommand); DIFFICULTY.addTo(startCommand); GAMEMODE.addTo(startCommand); @@ -403,6 +425,7 @@ private static int startGame(CommandContext context, int tea final boolean continueAfterWin = CONTINUE_AFTER_WIN.get(context); final boolean includeInactiveTeams = INCLUDE_INACTIVE_TEAMS.get(context); final int autoForfeitTicks = AUTO_FORFEIT_TIME.get(context); + final int time = TIME.get(context); final Set teams = LinkedHashSet.newLinkedHashSet(teamCount); for (int i = 1; i <= teamCount; i++) { @@ -452,6 +475,9 @@ private static int startGame(CommandContext context, int tea Bingo.LOGGER.info("Generated board (seed {}):\n{}", seed, board); final var game = new BingoGame(board, gamemode, requireClient, continueAfterWin, autoForfeitTicks, teams.toArray(PlayerTeam[]::new)); + if (time > 0) { + game.setScheduledEndTime(System.currentTimeMillis() + (long) time * 1000 * 60); + } server.bingo$setGame(game); Bingo.updateCommandTree(playerList); new ArrayList<>(playerList.getPlayers()).forEach(game::addPlayer); diff --git a/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java b/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java index db328bc4..df423bb3 100644 --- a/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java +++ b/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java @@ -16,6 +16,7 @@ import io.github.gaming32.bingo.platform.BingoPlatform; import io.github.gaming32.bingo.platform.event.ClientEvents; import io.github.gaming32.bingo.platform.registrar.KeyMappingBuilder; +import io.github.gaming32.bingo.util.BingoUtil; import io.github.gaming32.bingo.util.ResourceLocations; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -139,7 +140,31 @@ public static void renderBoardOnHud(Minecraft minecraft, GuiGraphics graphics) { final PositionAndScale pos = getBoardPosition(); renderBingo(graphics, minecraft.screen instanceof ChatScreen, pos); - if (CONFIG.isShowScoreCounter() && clientGame.renderMode() == BingoGameMode.RenderMode.ALL_TEAMS) { + final Font font = minecraft.font; + final int scoreX = (int)(pos.x() * pos.scale() + getBoardWidth() * pos.scale() / 2); + int scoreY; + if (CONFIG.getBoardCorner().isOnBottom) { + scoreY = (int)((pos.y() - BOARD_OFFSET) * pos.scale() - font.lineHeight); + } else { + scoreY = (int)(pos.y() * pos.scale() + (getBoardHeight() + BOARD_OFFSET) * pos.scale()); + } + final int shift = CONFIG.getBoardCorner().isOnBottom ? -12 : 12; + + if (clientGame.getScheduledEndTime() > 0) { + long remainingTime = clientGame.getScheduledEndTime() - System.currentTimeMillis(); + String formatedRemainingTime = " - " + BingoUtil.formatRemainingTime(remainingTime); + int color = 0xffffffff; + if (remainingTime < 30 * 60 * 1000) + color = 0xffffaf00; + if (remainingTime < 5 * 60 * 1000) + color = 0xffff0000; + final MutableComponent remainingTimeLabel = Component.translatable("bingo.remaining_time"); + graphics.drawString(font, remainingTimeLabel, scoreX - font.width(remainingTimeLabel), scoreY, color); + graphics.drawString(font, Component.literal(formatedRemainingTime), scoreX, scoreY, color); + scoreY += shift; + } + + if (CONFIG.isShowScoreCounter() && clientGame.getRenderMode() == BingoGameMode.RenderMode.ALL_TEAMS) { class TeamValue { final BingoBoard.Teams team; int score; @@ -149,13 +174,13 @@ class TeamValue { } } - final TeamValue[] teams = new TeamValue[clientGame.teams().length]; + final TeamValue[] teams = new TeamValue[clientGame.getTeams().length]; for (int i = 0; i < teams.length; i++) { teams[i] = new TeamValue(BingoBoard.Teams.fromOne(i)); } int totalScore = 0; - for (final BingoBoard.Teams state : clientGame.states()) { + for (final BingoBoard.Teams state : clientGame.getStates()) { if (state.any()) { totalScore++; teams[state.getFirstIndex()].score++; @@ -164,18 +189,9 @@ class TeamValue { Arrays.sort(teams, Comparator.comparing(v -> -v.score)); // Sort in reverse - final Font font = minecraft.font; - final int scoreX = (int)(pos.x() * pos.scale() + getBoardWidth() * pos.scale() / 2); - int scoreY; - if (CONFIG.getBoardCorner().isOnBottom) { - scoreY = (int)((pos.y() - BOARD_OFFSET) * pos.scale() - font.lineHeight); - } else { - scoreY = (int)(pos.y() * pos.scale() + (getBoardHeight() + BOARD_OFFSET) * pos.scale()); - } - final int shift = CONFIG.getBoardCorner().isOnBottom ? -12 : 12; for (final TeamValue teamValue : teams) { if (teamValue.score == 0) break; - final PlayerTeam team = clientGame.teams()[teamValue.team.getFirstIndex()]; + final PlayerTeam team = clientGame.getTeams()[teamValue.team.getFirstIndex()]; final MutableComponent leftText = getDisplayName(team).copy(); final MutableComponent rightText = Component.literal(" - " + teamValue.score); if (team.getColor() != ChatFormatting.RESET) { @@ -188,18 +204,18 @@ class TeamValue { } final MutableComponent leftText = Component.translatable("bingo.unclaimed"); - final MutableComponent rightText = Component.literal(" - " + (clientGame.states().length - totalScore)); + final MutableComponent rightText = Component.literal(" - " + (clientGame.getStates().length - totalScore)); graphics.drawString(font, leftText, scoreX - font.width(leftText), scoreY, 0xffffffff); graphics.drawString(font, rightText, scoreX, scoreY, 0xffffffff); } } public static int getBoardWidth() { - return 14 + 18 * clientGame.size(); + return 14 + 18 * clientGame.getSize(); } public static int getBoardHeight() { - return 24 + 18 * clientGame.size(); + return 24 + 18 * clientGame.getSize(); } public static void renderBingo(GuiGraphics graphics, boolean mouseHover, PositionAndScale pos) { @@ -213,13 +229,13 @@ public static void renderBingo(GuiGraphics graphics, boolean mouseHover, Positio graphics.pose().scale(pos.scale(), pos.scale(), 1); graphics.pose().translate(pos.x(), pos.y(), 0); - final BingoMousePos mousePos = mouseHover ? BingoMousePos.getPos(minecraft, clientGame.size(), pos) : null; + final BingoMousePos mousePos = mouseHover ? BingoMousePos.getPos(minecraft, clientGame.getSize(), pos) : null; graphics.blitSprite( RenderType::guiTextured, BOARD_TEXTURE, 0, 0, - 7 + 18 * clientGame.size() + 7, - 17 + 18 * clientGame.size() + 7 + 7 + 18 * clientGame.getSize() + 7, + 17 + 18 * clientGame.getSize() + 7 ); renderBoardTitle(graphics, minecraft.font); @@ -232,8 +248,8 @@ public static void renderBingo(GuiGraphics graphics, boolean mouseHover, Positio } final boolean spectator = minecraft.player != null && minecraft.player.isSpectator(); - for (int sx = 0; sx < clientGame.size(); sx++) { - for (int sy = 0; sy < clientGame.size(); sy++) { + for (int sx = 0; sx < clientGame.getSize(); sx++) { + for (int sy = 0; sy < clientGame.getSize(); sy++) { final var goal = clientGame.getGoal(sx, sy); final int slotX = sx * 18 + 8; final int slotY = sy * 18 + 18; @@ -244,14 +260,14 @@ public static void renderBingo(GuiGraphics graphics, boolean mouseHover, Positio final BingoBoard.Teams state = clientGame.getState(sx, sy); boolean isGoalCompleted = state.and(clientTeam); - final Integer color = switch (clientGame.renderMode()) { + final Integer color = switch (clientGame.getRenderMode()) { case FANCY -> isGoalCompleted ? Integer.valueOf(0x55ff55) : goal.specialType().incompleteColor; case ALL_TEAMS -> { if (!state.any()) { yield null; } final BingoBoard.Teams team = isGoalCompleted ? clientTeam : state; - final Integer maybeColor = clientGame.teams()[team.getFirstIndex()].getColor().getColor(); + final Integer maybeColor = clientGame.getTeams()[team.getFirstIndex()].getColor().getColor(); yield maybeColor != null ? maybeColor : 0x55ff55; } }; @@ -338,7 +354,7 @@ public static boolean detectClickOrPress(InputConstants.Key key, PositionAndScal return false; } - final BingoMousePos mousePos = BingoMousePos.getPos(Minecraft.getInstance(), clientGame.size(), boardPos); + final BingoMousePos mousePos = BingoMousePos.getPos(Minecraft.getInstance(), clientGame.getSize(), boardPos); if (!mousePos.hasSlotPos()) { return false; } diff --git a/common/src/main/java/io/github/gaming32/bingo/client/BoardScreen.java b/common/src/main/java/io/github/gaming32/bingo/client/BoardScreen.java index 89d3d53d..108c04d9 100644 --- a/common/src/main/java/io/github/gaming32/bingo/client/BoardScreen.java +++ b/common/src/main/java/io/github/gaming32/bingo/client/BoardScreen.java @@ -41,7 +41,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi BingoClient.renderBingo(graphics, true, pos); assert minecraft != null; if (minecraft.player != null && minecraft.player.isSpectator()) { - final PlayerTeam team = BingoClient.clientGame.teams()[BingoClient.clientTeam.getFirstIndex()]; + final PlayerTeam team = BingoClient.clientGame.getTeams()[BingoClient.clientTeam.getFirstIndex()]; final Integer color = team.getColor().getColor(); graphics.drawCenteredString( font, @@ -105,7 +105,7 @@ private void updateButtonVisibility() { private void switchTeam(int dir) { final int currentIndex = BingoClient.clientTeam.getFirstIndex(); - final int newIndex = Math.floorMod(currentIndex + dir, BingoClient.clientGame.teams().length); + final int newIndex = Math.floorMod(currentIndex + dir, BingoClient.clientGame.getTeams().length); BingoClient.clientTeam = BingoBoard.Teams.fromOne(newIndex); } } diff --git a/common/src/main/java/io/github/gaming32/bingo/client/ClientGame.java b/common/src/main/java/io/github/gaming32/bingo/client/ClientGame.java index d65a47ce..01577cd5 100644 --- a/common/src/main/java/io/github/gaming32/bingo/client/ClientGame.java +++ b/common/src/main/java/io/github/gaming32/bingo/client/ClientGame.java @@ -7,14 +7,31 @@ import net.minecraft.world.scores.PlayerTeam; import org.jetbrains.annotations.Nullable; -public record ClientGame( - int size, - BingoBoard.Teams[] states, - ActiveGoal[] goals, - PlayerTeam[] teams, - BingoGameMode.RenderMode renderMode, - @Nullable GoalProgress[] progress -) { +public final class ClientGame { + private final int size; + private final BingoBoard.Teams[] states; + private final ActiveGoal[] goals; + private final PlayerTeam[] teams; + private final BingoGameMode.RenderMode renderMode; + private final GoalProgress @Nullable [] progress; + private long scheduledEndTime = -1; + + public ClientGame( + int size, + BingoBoard.Teams[] states, + ActiveGoal[] goals, + PlayerTeam[] teams, + BingoGameMode.RenderMode renderMode, + @Nullable GoalProgress[] progress + ) { + this.size = size; + this.states = states; + this.goals = goals; + this.teams = teams; + this.renderMode = renderMode; + this.progress = progress; + } + public BingoBoard.Teams getState(int x, int y) { return states[getIndex(x, y)]; } @@ -23,11 +40,43 @@ public ActiveGoal getGoal(int x, int y) { return goals[getIndex(x, y)]; } + public GoalProgress[] getProgress() { + return progress; + } + @Nullable public GoalProgress getProgress(int x, int y) { return progress[getIndex(x, y)]; } + public int getSize() { + return size; + } + + public BingoBoard.Teams[] getStates() { + return states; + } + + public ActiveGoal[] getGoals() { + return goals; + } + + public PlayerTeam[] getTeams() { + return teams; + } + + public BingoGameMode.RenderMode getRenderMode() { + return renderMode; + } + + public void setScheduledEndTime(long endTime) { + this.scheduledEndTime = endTime; + } + + public long getScheduledEndTime() { + return scheduledEndTime; + } + private int getIndex(int x, int y) { return y * size + x; } diff --git a/common/src/main/java/io/github/gaming32/bingo/client/ClientPayloadHandlerImpl.java b/common/src/main/java/io/github/gaming32/bingo/client/ClientPayloadHandlerImpl.java index 53a444f1..c78aabc2 100644 --- a/common/src/main/java/io/github/gaming32/bingo/client/ClientPayloadHandlerImpl.java +++ b/common/src/main/java/io/github/gaming32/bingo/client/ClientPayloadHandlerImpl.java @@ -6,6 +6,7 @@ import io.github.gaming32.bingo.network.messages.s2c.InitBoardPayload; import io.github.gaming32.bingo.network.messages.s2c.ResyncStatesPayload; import io.github.gaming32.bingo.network.messages.s2c.SyncTeamPayload; +import io.github.gaming32.bingo.network.messages.s2c.UpdateEndTimePayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateProgressPayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateStatePayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -50,8 +51,8 @@ public void handleResyncStates(ResyncStatesPayload payload) { if (!checkGamePresent(payload)) return; System.arraycopy( payload.states(), 0, - BingoClient.clientGame.states(), 0, - BingoClient.clientGame.size() * BingoClient.clientGame.size() + BingoClient.clientGame.getStates(), 0, + BingoClient.clientGame.getSize() * BingoClient.clientGame.getSize() ); } @@ -63,18 +64,24 @@ public void handleSyncTeam(SyncTeamPayload payload) { @Override public void handleUpdateProgress(UpdateProgressPayload payload) { if (!checkGamePresent(payload)) return; - BingoClient.clientGame.progress()[payload.index()] = new GoalProgress(payload.progress(), payload.maxProgress()); + BingoClient.clientGame.getProgress()[payload.index()] = new GoalProgress(payload.progress(), payload.maxProgress()); } @Override public void handleUpdateState(UpdateStatePayload payload) { if (!checkGamePresent(payload)) return; final int index = payload.index(); - if (index < 0 || index >= BingoClient.clientGame.size() * BingoClient.clientGame.size()) { + if (index < 0 || index >= BingoClient.clientGame.getSize() * BingoClient.clientGame.getSize()) { Bingo.LOGGER.warn("Invalid {} payload: invalid board index {}", UpdateStatePayload.TYPE, index); return; } - BingoClient.clientGame.states()[index] = payload.newState(); + BingoClient.clientGame.getStates()[index] = payload.newState(); + } + + @Override + public void handleUpdateEndTime(UpdateEndTimePayload payload) { + if (!checkGamePresent(payload)) return; + BingoClient.clientGame.setScheduledEndTime(payload.endTime()); } private static boolean checkGamePresent(CustomPacketPayload payload) { diff --git a/common/src/main/java/io/github/gaming32/bingo/client/TeamSelectionSpectatorCategory.java b/common/src/main/java/io/github/gaming32/bingo/client/TeamSelectionSpectatorCategory.java index 71ca1d42..b9943b7b 100644 --- a/common/src/main/java/io/github/gaming32/bingo/client/TeamSelectionSpectatorCategory.java +++ b/common/src/main/java/io/github/gaming32/bingo/client/TeamSelectionSpectatorCategory.java @@ -45,7 +45,7 @@ private static List createTeamEntries(@Nullable ClientGame ga return List.of(); } final MutableInt teamId = new MutableInt(); - return Arrays.stream(game.teams()) + return Arrays.stream(game.getTeams()) .map(team -> TeamSelectionItem.create(game, team, BingoBoard.Teams.fromOne(teamId.getAndIncrement()))) .filter(Objects::nonNull) .toList(); @@ -107,9 +107,9 @@ public static SpectatorMenuItem create(ClientGame game, PlayerTeam team, BingoBo if (onlinePlayers.isEmpty()) { boolean hasAnyGoals = false; - for (int i = 0; i < game.states().length; i++) { - boolean hasGoal = game.states()[i].and(teamId); - if (game.goals()[i].specialType() == BingoTag.SpecialType.NEVER) { + for (int i = 0; i < game.getStates().length; i++) { + boolean hasGoal = game.getStates()[i].and(teamId); + if (game.getGoals()[i].specialType() == BingoTag.SpecialType.NEVER) { hasGoal = !hasGoal; } if (hasGoal) { diff --git a/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java b/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java index c3c27bc8..6007d37b 100644 --- a/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java +++ b/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java @@ -13,6 +13,7 @@ import io.github.gaming32.bingo.network.messages.s2c.RemoveBoardPayload; import io.github.gaming32.bingo.network.messages.s2c.ResyncStatesPayload; import io.github.gaming32.bingo.network.messages.s2c.SyncTeamPayload; +import io.github.gaming32.bingo.network.messages.s2c.UpdateEndTimePayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateProgressPayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateStatePayload; import io.github.gaming32.bingo.triggers.progress.ProgressibleTrigger; @@ -41,12 +42,14 @@ import net.minecraft.network.chat.ComponentUtils; import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerBossEvent; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.PlayerList; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stat; import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.BossEvent; import net.minecraft.world.entity.player.Player; import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Scoreboard; @@ -83,10 +86,12 @@ public class BingoGame { private final Map> goalAchievedCount = new HashMap<>(); private final Map> queuedGoals = new HashMap<>(); private final Map>> baseStats = new HashMap<>(); + private final ServerBossEvent vanillaRemainingTime = new ServerBossEvent(Bingo.translatable("bingo.remaining_time"), BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS); private final OptionalLong[] lastActiveTimes; private BingoBoard.Teams remainingTeams; private BingoBoard.Teams winningTeams = BingoBoard.Teams.NONE; private BingoBoard.Teams finishedTeams = BingoBoard.Teams.NONE; + private long scheduledEndTime = -1; public BingoGame(BingoBoard board, BingoGameMode gameMode, boolean requireClient, boolean continueAfterWin, int autoForfeitTicks, PlayerTeam... teams) { this.board = board; @@ -116,6 +121,14 @@ public boolean shouldContinueAfterWin() { return continueAfterWin; } + public void setScheduledEndTime(long endTime) { + this.scheduledEndTime = endTime; + } + + public long getScheduledEndTime() { + return scheduledEndTime; + } + /** * @apiNote {@code player} does not need to be in a team. In fact, they should be added even if they aren't! */ @@ -151,6 +164,14 @@ public void addPlayer(ServerPlayer player) { } }); } + + if (scheduledEndTime > 0) { + if (Bingo.isInstalledOnClient(player)) { + new UpdateEndTimePayload(scheduledEndTime).sendTo(player); + } else { + vanillaRemainingTime.addPlayer(player); + } + } } public void syncAdvancementsTo(ServerPlayer player) { @@ -166,6 +187,30 @@ public void syncAdvancementsTo(ServerPlayer player) { public void removePlayer(ServerPlayer player) { unregisterListeners(player, true); + vanillaRemainingTime.removePlayer(player); + } + + public void updateRemainingTime(PlayerList playerList) { + for (ServerPlayer player : playerList.getPlayers()) { + if (Bingo.isInstalledOnClient(player)) { + new UpdateEndTimePayload(scheduledEndTime).sendTo(player); + } + } + updateVanillaRemainingTime(); + } + + public void updateVanillaRemainingTime() { + if (vanillaRemainingTime.getPlayers().isEmpty()) + return; + long remainingTime = getScheduledEndTime() - System.currentTimeMillis(); + String formatedRemainingTime = BingoUtil.formatRemainingTime(remainingTime); + BossEvent.BossBarColor color = BossEvent.BossBarColor.WHITE; + if (remainingTime < 30 * 60 * 1000) + color = BossEvent.BossBarColor.PURPLE; + if (remainingTime < 5 * 60 * 1000) + color = BossEvent.BossBarColor.RED; + vanillaRemainingTime.setName(Bingo.translatable("bingo.remaining_time_with_value", formatedRemainingTime)); + vanillaRemainingTime.setColor(color); } public BingoBoard.Teams[] obfuscateTeam(BingoBoard.Teams playerTeam, Player player) { @@ -266,6 +311,14 @@ public void tick(MinecraftServer server) { } } } + + if (server.getTickCount() % 20 == 0 && getScheduledEndTime() > 0) { + if (System.currentTimeMillis() > getScheduledEndTime()) { + endGame(server.getPlayerList()); + } else { + updateVanillaRemainingTime(); + } + } } public boolean forfeit(PlayerList playerList, BingoBoard.Teams team) { diff --git a/common/src/main/java/io/github/gaming32/bingo/network/ClientPayloadHandler.java b/common/src/main/java/io/github/gaming32/bingo/network/ClientPayloadHandler.java index 6dcd3e15..2024ccdf 100644 --- a/common/src/main/java/io/github/gaming32/bingo/network/ClientPayloadHandler.java +++ b/common/src/main/java/io/github/gaming32/bingo/network/ClientPayloadHandler.java @@ -3,6 +3,7 @@ import io.github.gaming32.bingo.network.messages.s2c.InitBoardPayload; import io.github.gaming32.bingo.network.messages.s2c.ResyncStatesPayload; import io.github.gaming32.bingo.network.messages.s2c.SyncTeamPayload; +import io.github.gaming32.bingo.network.messages.s2c.UpdateEndTimePayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateProgressPayload; import io.github.gaming32.bingo.network.messages.s2c.UpdateStatePayload; import io.github.gaming32.bingo.platform.BingoPlatform; @@ -21,6 +22,8 @@ public interface ClientPayloadHandler { void handleUpdateState(UpdateStatePayload payload); + void handleUpdateEndTime(UpdateEndTimePayload payload); + static ClientPayloadHandler get() { if (Holder.instance == null) { if (BingoPlatform.platform.isClient()) { diff --git a/common/src/main/java/io/github/gaming32/bingo/network/messages/s2c/UpdateEndTimePayload.java b/common/src/main/java/io/github/gaming32/bingo/network/messages/s2c/UpdateEndTimePayload.java new file mode 100644 index 00000000..63fff326 --- /dev/null +++ b/common/src/main/java/io/github/gaming32/bingo/network/messages/s2c/UpdateEndTimePayload.java @@ -0,0 +1,25 @@ +package io.github.gaming32.bingo.network.messages.s2c; + +import io.github.gaming32.bingo.network.AbstractCustomPayload; +import io.github.gaming32.bingo.network.BingoNetworking; +import io.github.gaming32.bingo.network.ClientPayloadHandler; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.NotNull; + +public record UpdateEndTimePayload(long endTime) implements AbstractCustomPayload { + public static final Type TYPE = AbstractCustomPayload.type("update_end_time"); + public static final StreamCodec CODEC = ByteBufCodecs.VAR_LONG.map(UpdateEndTimePayload::new, UpdateEndTimePayload::endTime); + + @NotNull + @Override + public Type type() { + return TYPE; + } + + @Override + public void handle(BingoNetworking.Context context) { + ClientPayloadHandler.get().handleUpdateEndTime(this); + } +} diff --git a/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java b/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java index ca146b72..882635d7 100644 --- a/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java +++ b/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java @@ -216,6 +216,21 @@ public static Either getDisplayName(PlayerTeam team, Playe return Either.right(team.getDisplayName()); } + public static String formatRemainingTime(long remainingTime) { + if (remainingTime < 0) + remainingTime = 0; + int hours = (int) (remainingTime / 1000 / 60 / 60); + int minutes = (int) (remainingTime / 1000 / 60 % 60); + int seconds = (int) (remainingTime / 1000 % 60); + StringBuilder sb = new StringBuilder(); + if (hours > 0) { + sb.append(hours); + sb.append(":"); + } + sb.append(String.format("%02d:%02d", minutes, seconds)); + return sb.toString(); + } + public static Either mapEither(Either either, Function mapper) { return either.mapBoth(mapper, mapper); } diff --git a/common/src/main/resources/assets/bingo/lang/en_us.json b/common/src/main/resources/assets/bingo/lang/en_us.json index b495292f..add8e6f0 100644 --- a/common/src/main/resources/assets/bingo/lang/en_us.json +++ b/common/src/main/resources/assets/bingo/lang/en_us.json @@ -308,6 +308,8 @@ "bingo.key.category": "Bingo", "bingo.sixteen_bang": "%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, and %s!", "bingo.unclaimed": "Unclaimed", + "bingo.remaining_time": "Remaining time", + "bingo.remaining_time_with_value": "Remaining time: %s", "bingo.three": "%s, %s, and %s", "bingo.four": "%s, %s, %s, and %s", "bingo.spawner": "%s %s", From 42c550695a0984e9ad15e556163588fe3b44cd9f Mon Sep 17 00:00:00 2001 From: rasmus Date: Tue, 28 Jan 2025 23:06:07 +0100 Subject: [PATCH 2/4] time limit defaults to -1 --- common/src/main/java/io/github/gaming32/bingo/BingoCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java b/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java index 78285c90..9fe78602 100644 --- a/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java +++ b/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java @@ -132,7 +132,7 @@ public class BingoCommand { private static final CommandSwitch TIME = CommandSwitch .argument("--time", IntegerArgumentType.integer()) .getter(IntegerArgumentType::getInteger) - .build(() -> 1); + .build(() -> -1); private static final CommandSwitch AUTO_FORFEIT_TIME = CommandSwitch .argument("--auto-forfeit-time", TimeArgument.time(0)) .getter(IntegerArgumentType::getInteger) From b65446f7c726067a3d0abe6ea49437b019de7d86 Mon Sep 17 00:00:00 2001 From: rasmus Date: Wed, 5 Feb 2025 21:46:35 +0100 Subject: [PATCH 3/4] use time-limit uses in-game time --- .../github/gaming32/bingo/BingoCommand.java | 22 ++++++++-------- .../gaming32/bingo/client/BingoClient.java | 12 ++++++--- .../github/gaming32/bingo/game/BingoGame.java | 26 +++++++++++-------- .../github/gaming32/bingo/util/BingoUtil.java | 12 ++++----- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java b/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java index 9fe78602..1b341d54 100644 --- a/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java +++ b/common/src/main/java/io/github/gaming32/bingo/BingoCommand.java @@ -129,8 +129,8 @@ public class BingoCommand { private static final CommandSwitch SEED = CommandSwitch .argument("--seed", LongArgumentType.longArg()) .build(RandomSupport::generateUniqueSeed); - private static final CommandSwitch TIME = CommandSwitch - .argument("--time", IntegerArgumentType.integer()) + private static final CommandSwitch TIME_LIMIT = CommandSwitch + .argument("--time-limit", TimeArgument.time(-1)) .getter(IntegerArgumentType::getInteger) .build(() -> -1); private static final CommandSwitch AUTO_FORFEIT_TIME = CommandSwitch @@ -357,18 +357,18 @@ public Component getDisplayName() { ) ) ) - .then(literal("time") + .then(literal("time-limit") .requires(source -> source.hasPermission(2)) .then(literal("set") - .then(argument("time", IntegerArgumentType.integer()) + .then(argument("time-limit", TimeArgument.time()) .executes(ctx -> { final var game = ctx.getSource().getServer().bingo$getGame(); if (game == null) { throw NO_GAME_RUNNING.create(); } - int time = IntegerArgumentType.getInteger(ctx, "time"); - game.setScheduledEndTime(System.currentTimeMillis() + (long) time * 1000 * 60); - game.updateRemainingTime(ctx.getSource().getServer().getPlayerList()); + int time = IntegerArgumentType.getInteger(ctx, "time-limit"); + game.setScheduledEndTime(ctx.getSource().getServer().overworld().getGameTime() + time); + game.updateRemainingTime(ctx.getSource().getServer()); return 1; }) ) @@ -385,7 +385,7 @@ public Component getDisplayName() { SIZE.addTo(startCommand); SEED.addTo(startCommand); - TIME.addTo(startCommand); + TIME_LIMIT.addTo(startCommand); AUTO_FORFEIT_TIME.addTo(startCommand); DIFFICULTY.addTo(startCommand); GAMEMODE.addTo(startCommand); @@ -425,7 +425,7 @@ private static int startGame(CommandContext context, int tea final boolean continueAfterWin = CONTINUE_AFTER_WIN.get(context); final boolean includeInactiveTeams = INCLUDE_INACTIVE_TEAMS.get(context); final int autoForfeitTicks = AUTO_FORFEIT_TIME.get(context); - final int time = TIME.get(context); + final int timeLimitTicks = TIME_LIMIT.get(context); final Set teams = LinkedHashSet.newLinkedHashSet(teamCount); for (int i = 1; i <= teamCount; i++) { @@ -475,8 +475,8 @@ private static int startGame(CommandContext context, int tea Bingo.LOGGER.info("Generated board (seed {}):\n{}", seed, board); final var game = new BingoGame(board, gamemode, requireClient, continueAfterWin, autoForfeitTicks, teams.toArray(PlayerTeam[]::new)); - if (time > 0) { - game.setScheduledEndTime(System.currentTimeMillis() + (long) time * 1000 * 60); + if (timeLimitTicks > 0) { + game.setScheduledEndTime(context.getSource().getServer().overworld().getGameTime() + timeLimitTicks); } server.bingo$setGame(game); Bingo.updateCommandTree(playerList); diff --git a/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java b/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java index df423bb3..f982f37e 100644 --- a/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java +++ b/common/src/main/java/io/github/gaming32/bingo/client/BingoClient.java @@ -151,12 +151,16 @@ public static void renderBoardOnHud(Minecraft minecraft, GuiGraphics graphics) { final int shift = CONFIG.getBoardCorner().isOnBottom ? -12 : 12; if (clientGame.getScheduledEndTime() > 0) { - long remainingTime = clientGame.getScheduledEndTime() - System.currentTimeMillis(); - String formatedRemainingTime = " - " + BingoUtil.formatRemainingTime(remainingTime); + long serverTime = 0; + if (minecraft.level instanceof ClientLevel clientLevel) { + serverTime = clientLevel.getGameTime(); + } + long remainingTimeTicks = clientGame.getScheduledEndTime() - serverTime; + String formatedRemainingTime = " - " + BingoUtil.formatRemainingTime(remainingTimeTicks); int color = 0xffffffff; - if (remainingTime < 30 * 60 * 1000) + if (remainingTimeTicks < 30 * 60 * 20) color = 0xffffaf00; - if (remainingTime < 5 * 60 * 1000) + if (remainingTimeTicks < 5 * 60 * 20) color = 0xffff0000; final MutableComponent remainingTimeLabel = Component.translatable("bingo.remaining_time"); graphics.drawString(font, remainingTimeLabel, scoreX - font.width(remainingTimeLabel), scoreY, color); diff --git a/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java b/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java index 6007d37b..5d882cdd 100644 --- a/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java +++ b/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java @@ -190,24 +190,24 @@ public void removePlayer(ServerPlayer player) { vanillaRemainingTime.removePlayer(player); } - public void updateRemainingTime(PlayerList playerList) { - for (ServerPlayer player : playerList.getPlayers()) { + public void updateRemainingTime(MinecraftServer server) { + for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (Bingo.isInstalledOnClient(player)) { new UpdateEndTimePayload(scheduledEndTime).sendTo(player); } } - updateVanillaRemainingTime(); + updateVanillaRemainingTime(server); } - public void updateVanillaRemainingTime() { + public void updateVanillaRemainingTime(MinecraftServer server) { if (vanillaRemainingTime.getPlayers().isEmpty()) return; - long remainingTime = getScheduledEndTime() - System.currentTimeMillis(); - String formatedRemainingTime = BingoUtil.formatRemainingTime(remainingTime); + long remainingTimeTicks = getScheduledEndTime() - server.overworld().getGameTime(); + String formatedRemainingTime = BingoUtil.formatRemainingTime(remainingTimeTicks); BossEvent.BossBarColor color = BossEvent.BossBarColor.WHITE; - if (remainingTime < 30 * 60 * 1000) + if (remainingTimeTicks < 30 * 60 * 20) color = BossEvent.BossBarColor.PURPLE; - if (remainingTime < 5 * 60 * 1000) + if (remainingTimeTicks < 5 * 60 * 20) color = BossEvent.BossBarColor.RED; vanillaRemainingTime.setName(Bingo.translatable("bingo.remaining_time_with_value", formatedRemainingTime)); vanillaRemainingTime.setColor(color); @@ -282,6 +282,10 @@ public void endGame(PlayerList playerList) { playerList.getServer().bingo$setGame(null); new ResyncStatesPayload(board.getStates()).sendTo(playerList.getPlayers()); + if (getScheduledEndTime() > 0) { + setScheduledEndTime(-1); + updateRemainingTime(playerList.getServer()); + } Bingo.updateCommandTree(playerList); } @@ -312,11 +316,11 @@ public void tick(MinecraftServer server) { } } - if (server.getTickCount() % 20 == 0 && getScheduledEndTime() > 0) { - if (System.currentTimeMillis() > getScheduledEndTime()) { + if (getScheduledEndTime() > 0 && server.getTickCount() % 20 == 0) { + if (server.overworld().getGameTime() > getScheduledEndTime()) { endGame(server.getPlayerList()); } else { - updateVanillaRemainingTime(); + updateVanillaRemainingTime(server); } } } diff --git a/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java b/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java index 882635d7..51a2443e 100644 --- a/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java +++ b/common/src/main/java/io/github/gaming32/bingo/util/BingoUtil.java @@ -216,12 +216,12 @@ public static Either getDisplayName(PlayerTeam team, Playe return Either.right(team.getDisplayName()); } - public static String formatRemainingTime(long remainingTime) { - if (remainingTime < 0) - remainingTime = 0; - int hours = (int) (remainingTime / 1000 / 60 / 60); - int minutes = (int) (remainingTime / 1000 / 60 % 60); - int seconds = (int) (remainingTime / 1000 % 60); + public static String formatRemainingTime(long remainingTimeTicks) { + if (remainingTimeTicks < 0) + remainingTimeTicks = 0; + int hours = (int) (remainingTimeTicks / 20 / 60 / 60); + int minutes = (int) (remainingTimeTicks / 20 / 60 % 60); + int seconds = (int) (remainingTimeTicks / 20 % 60); StringBuilder sb = new StringBuilder(); if (hours > 0) { sb.append(hours); From 3f88330c533241e9a35cac2c6841c039e26ef55f Mon Sep 17 00:00:00 2001 From: rasmus Date: Fri, 7 Feb 2025 17:58:39 +0100 Subject: [PATCH 4/4] remove remaining time for vanilla clients after game ends --- .../io/github/gaming32/bingo/game/BingoGame.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java b/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java index 5d882cdd..c970204a 100644 --- a/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java +++ b/common/src/main/java/io/github/gaming32/bingo/game/BingoGame.java @@ -200,15 +200,21 @@ public void updateRemainingTime(MinecraftServer server) { } public void updateVanillaRemainingTime(MinecraftServer server) { - if (vanillaRemainingTime.getPlayers().isEmpty()) + if (vanillaRemainingTime.getPlayers().isEmpty()) { return; + } long remainingTimeTicks = getScheduledEndTime() - server.overworld().getGameTime(); String formatedRemainingTime = BingoUtil.formatRemainingTime(remainingTimeTicks); + if (remainingTimeTicks <= 0) { + vanillaRemainingTime.removeAllPlayers(); + return; + } BossEvent.BossBarColor color = BossEvent.BossBarColor.WHITE; - if (remainingTimeTicks < 30 * 60 * 20) - color = BossEvent.BossBarColor.PURPLE; - if (remainingTimeTicks < 5 * 60 * 20) + if (remainingTimeTicks < 5 * 60 * 20) { color = BossEvent.BossBarColor.RED; + } else if (remainingTimeTicks < 30 * 60 * 20) { + color = BossEvent.BossBarColor.PURPLE; + } vanillaRemainingTime.setName(Bingo.translatable("bingo.remaining_time_with_value", formatedRemainingTime)); vanillaRemainingTime.setColor(color); }