From c2875cbed82d4aed8278d80aa2ebb416f2d26884 Mon Sep 17 00:00:00 2001 From: sisby-folk Date: Tue, 23 Jul 2024 15:37:28 +1000 Subject: [PATCH] open/close voting mechanics, vote reminder --- gradle.properties | 2 +- .../java/net/modfest/ballotbox/BallotBox.java | 53 +++++++++++++++++-- .../modfest/ballotbox/BallotBoxCommands.java | 33 ++++++------ .../modfest/ballotbox/BallotBoxConfig.java | 2 + .../ballotbox/BallotBoxNetworking.java | 14 +++-- .../client/BallotBoxClientNetworking.java | 14 +++-- .../ballotbox/client/BallotBoxKeybinds.java | 13 +++-- .../ballotbox/client/NotBallotBoxClient.java | 20 +++++++ .../ballotbox/client/VotingScreen.java | 2 +- .../mixin/client/GameMenuScreenMixin.java | 37 ++++++++++--- ...eScreenPacket.java => OpenVoteScreen.java} | 6 +-- .../modfest/ballotbox/packet/S2CGameJoin.java | 22 ++++++++ 12 files changed, 176 insertions(+), 42 deletions(-) rename src/main/java/net/modfest/ballotbox/packet/{OpenVoteScreenPacket.java => OpenVoteScreen.java} (63%) create mode 100644 src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java diff --git a/gradle.properties b/gradle.properties index 6e9e64d..8974085 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.gradle.configureondemand=true # Enable advanced multi-module optimizations (share tiny-remaper instance between projects) fabric.loom.multiProjectOptimisation=true # Mod Properties -baseVersion = 0.3.1 +baseVersion = 0.4.0 defaultBranch = 1.21 branch = 1.21 diff --git a/src/main/java/net/modfest/ballotbox/BallotBox.java b/src/main/java/net/modfest/ballotbox/BallotBox.java index d293bc0..f9e959d 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBox.java +++ b/src/main/java/net/modfest/ballotbox/BallotBox.java @@ -4,22 +4,62 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.MinecraftServer; import net.minecraft.world.World; +import net.modfest.ballotbox.data.VotingCategory; +import net.modfest.ballotbox.data.VotingSelections; +import net.modfest.ballotbox.packet.S2CGameJoin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.concurrent.TimeUnit; + public class BallotBox implements ModInitializer { public static final String ID = "ballotbox"; public static final Logger LOGGER = LoggerFactory.getLogger(ID); public static final BallotBoxConfig CONFIG = BallotBoxConfig.createToml(FabricLoader.getInstance().getConfigDir(), "", ID, BallotBoxConfig.class); public static final String STATE_KEY = "ballotbox_ballots"; public static BallotState STATE = null; + public static Instant closingTime = null; + + public static String relativeTime(Instant then) { + Instant now = Instant.now(); + long offset = now.toEpochMilli() - then.toEpochMilli(); + long days = TimeUnit.MILLISECONDS.toDays(Math.abs(offset)); + if (days > 0) return (offset > 0 ? "%s days ago" : "in %s days").formatted(days); + long hours = TimeUnit.MILLISECONDS.toHours(Math.abs(offset)); + if (hours > 0) return (offset > 0 ? "%s hours ago" : "in %s hours").formatted(days); + long minutes = TimeUnit.MILLISECONDS.toMinutes(Math.abs(offset)); + if (minutes > 0) return (offset > 0 ? "%s minutes ago" : "in %s minutes").formatted(days); + return (offset > 0 ? "%s seconds ago" : "in %s seconds").formatted(TimeUnit.MILLISECONDS.toSeconds(offset)); + } + public static boolean isEnabled(MinecraftServer server) { + return !server.isSingleplayer(); + } + + public static boolean isOpen() { + return closingTime == null || closingTime.isAfter(Instant.now()); + } + + public static Instant parseClosingTime(String value) { + try { + if (!value.isEmpty()) return LocalDateTime.parse(value).toInstant(ZoneOffset.UTC); + } catch (DateTimeException e) { + LOGGER.error("Failed to parse configured closing time '{}', ignoring...", value, e); + } + return null; + } @Override public void onInitialize() { - LOGGER.info("[BallotBox] Initialized!"); + closingTime = parseClosingTime(CONFIG.closingTime.value()); BallotBoxNetworking.init(); CommandRegistrationCallback.EVENT.register(BallotBoxCommands::register); ServerWorldEvents.LOAD.register(((server, world) -> { @@ -28,12 +68,19 @@ public void onInitialize() { } })); ServerLifecycleEvents.SERVER_STARTED.register((server -> { - if (server.isSingleplayer()) return; + if (!isEnabled(server)) return; BallotBoxPlatformClient.init(server.getResourceManager()); })); ServerLifecycleEvents.END_DATA_PACK_RELOAD.register(((server, resourceManager, success) -> { - if (server.isSingleplayer()) return; + if (!isEnabled(server)) return; BallotBoxPlatformClient.init(resourceManager); })); + ServerPlayConnectionEvents.JOIN.register(((handler, sender, server) -> { + VotingSelections selections = STATE.selections().get(handler.getPlayer().getUuid()); + int totalVotes = BallotBoxPlatformClient.categories.values().stream().mapToInt(VotingCategory::limit).sum(); + int remainingVotes = totalVotes - (selections == null ? 0 : selections.votes().size()); + sender.sendPacket(new S2CGameJoin(CONFIG.closingTime.value(), remainingVotes)); + })); + LOGGER.info("[BallotBox] Initialized!"); } } \ No newline at end of file diff --git a/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java b/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java index f600634..a2441c8 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java +++ b/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java @@ -5,7 +5,6 @@ import com.google.common.collect.Multisets; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.server.command.CommandManager; @@ -15,7 +14,7 @@ import net.minecraft.util.Formatting; import net.modfest.ballotbox.data.VotingCategory; import net.modfest.ballotbox.data.VotingOption; -import net.modfest.ballotbox.packet.OpenVoteScreenPacket; +import net.modfest.ballotbox.packet.OpenVoteScreen; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -27,14 +26,7 @@ public interface BallotBoxCommandExecutor { } public static int execute(CommandContext context, String arg, BallotBoxCommandExecutor executor) { - ServerPlayerEntity player; - try { - player = context.getSource().getPlayerOrThrow(); - } catch (CommandSyntaxException e) { - BallotBox.LOGGER.error("[BallotBox] Commands cannot be invoked by a non-player"); - return 0; - } - + ServerPlayerEntity player = context.getSource().getPlayer(); try { return executor.execute(player, arg != null ? context.getArgument(arg, String.class) : null, t -> context.getSource().sendFeedback(() -> t, false)); } catch (Exception e) { @@ -47,23 +39,24 @@ public static int execute(CommandContext context, String ar public static void register(CommandDispatcher dispatcher, CommandRegistryAccess context, CommandManager.RegistrationEnvironment environment) { dispatcher.register( CommandManager.literal("vote") - .executes(c -> execute(c, null, BallotBoxCommands::vote)) + .executes(c -> execute(c, null, (p, a1, f) -> BallotBoxCommands.vote(p, f))) ); dispatcher.register( CommandManager.literal("votes") .requires(s -> s.hasPermissionLevel(4)) - .executes(c -> execute(c, null, BallotBoxCommands::votes)) + .executes(c -> execute(c, null, (p, a1, f) -> BallotBoxCommands.votes(f))) ); } - private static int votes(ServerPlayerEntity player, String ignored, Consumer feedback) { + private static int votes(Consumer feedback) { Map> votes = new ConcurrentHashMap<>(); BallotBox.STATE.selections().forEach((uuid, selections) -> selections.votes().forEach((category, option) -> { if (BallotBoxPlatformClient.categories.containsKey(category) && BallotBoxPlatformClient.options.containsKey(option)) { votes.computeIfAbsent(BallotBoxPlatformClient.categories.get(category), k -> HashMultiset.create()).add(BallotBoxPlatformClient.options.get(option)); } })); - feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("%d players have submitted %d votes so far!".formatted(BallotBox.STATE.selections().size(), votes.values().stream().mapToInt(Multiset::size).sum())).formatted(Formatting.AQUA))); + if (BallotBox.closingTime != null) feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal((BallotBox.isOpen() ? "Voting closes %s." : "Voting closed %s.").formatted(BallotBox.relativeTime(BallotBox.closingTime))).formatted(Formatting.AQUA))); + feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("%d players have submitted %d votes!".formatted(BallotBox.STATE.selections().size(), votes.values().stream().mapToInt(Multiset::size).sum())).formatted(Formatting.AQUA))); votes.forEach((category, options) -> { feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("--- Top %d for %s ---".formatted(BallotBox.CONFIG.awardLimit.value(), category.name())).formatted(Formatting.LIGHT_PURPLE))); int i = 0; @@ -77,8 +70,16 @@ private static int votes(ServerPlayerEntity player, String ignored, Consumer feedback) { - ServerPlayNetworking.send(player, new OpenVoteScreenPacket()); + private static int vote(ServerPlayerEntity player, Consumer feedback) { + if (player == null) { + feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Vote cannot be invoked by a non-player").formatted(Formatting.RED))); + return 0; + } + if (!BallotBox.isOpen()) { + feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Voting is unavailable! Voting closed %s.".formatted(BallotBox.relativeTime(BallotBox.closingTime))).formatted(Formatting.RED))); + return 0; + } + ServerPlayNetworking.send(player, new OpenVoteScreen()); BallotBoxNetworking.sendVoteScreenData(player); return 1; } diff --git a/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java b/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java index f536bdc..f262701 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java +++ b/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java @@ -15,4 +15,6 @@ public class BallotBoxConfig extends ReflectiveConfig { public final TrackedValue bug_url = value("https://discord.gg/gn543Ee"); @Comment("The number of top results to show when displaying voting results") public final TrackedValue awardLimit = value(8); + @Comment("The closing date, as an ISO local date time - or an empty string for none") + public final TrackedValue closingTime = value("2024-07-28T12:00:00"); } diff --git a/src/main/java/net/modfest/ballotbox/BallotBoxNetworking.java b/src/main/java/net/modfest/ballotbox/BallotBoxNetworking.java index 4ed64a5..6e9de2d 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBoxNetworking.java +++ b/src/main/java/net/modfest/ballotbox/BallotBoxNetworking.java @@ -7,7 +7,8 @@ import net.minecraft.util.Formatting; import net.modfest.ballotbox.packet.C2SUpdateVote; import net.modfest.ballotbox.data.VotingCategory; -import net.modfest.ballotbox.packet.OpenVoteScreenPacket; +import net.modfest.ballotbox.packet.OpenVoteScreen; +import net.modfest.ballotbox.packet.S2CGameJoin; import net.modfest.ballotbox.packet.S2CVoteScreenData; import java.util.ArrayList; @@ -15,22 +16,25 @@ public class BallotBoxNetworking { public static void init() { PayloadTypeRegistry.playC2S().register(C2SUpdateVote.ID, C2SUpdateVote.CODEC); - PayloadTypeRegistry.playC2S().register(OpenVoteScreenPacket.ID, OpenVoteScreenPacket.CODEC); - PayloadTypeRegistry.playS2C().register(OpenVoteScreenPacket.ID, OpenVoteScreenPacket.CODEC); + PayloadTypeRegistry.playC2S().register(OpenVoteScreen.ID, OpenVoteScreen.CODEC); + PayloadTypeRegistry.playS2C().register(S2CGameJoin.ID, S2CGameJoin.CODEC); + PayloadTypeRegistry.playS2C().register(OpenVoteScreen.ID, OpenVoteScreen.CODEC); PayloadTypeRegistry.playS2C().register(S2CVoteScreenData.ID, S2CVoteScreenData.CODEC); ServerPlayNetworking.registerGlobalReceiver(C2SUpdateVote.ID, BallotBoxNetworking::handleUpdateVote); - ServerPlayNetworking.registerGlobalReceiver(OpenVoteScreenPacket.ID, BallotBoxNetworking::handleOpenVoteScreen); + ServerPlayNetworking.registerGlobalReceiver(OpenVoteScreen.ID, BallotBoxNetworking::handleOpenVoteScreen); } public static void sendVoteScreenData(ServerPlayerEntity player) { + if (!BallotBox.isOpen()) return; BallotBoxPlatformClient.getSelections(player.getUuid()).thenAccept(selections -> ServerPlayNetworking.send(player, new S2CVoteScreenData(new ArrayList<>(BallotBoxPlatformClient.categories.values()), new ArrayList<>(BallotBoxPlatformClient.options.values()), selections))); } - private static void handleOpenVoteScreen(OpenVoteScreenPacket packet, ServerPlayNetworking.Context context) { + private static void handleOpenVoteScreen(OpenVoteScreen packet, ServerPlayNetworking.Context context) { sendVoteScreenData(context.player()); } private static void handleUpdateVote(C2SUpdateVote packet, ServerPlayNetworking.Context context) { + if (!BallotBox.isOpen()) return; BallotBoxPlatformClient.putSelections(context.player().getUuid(), packet.selections()).thenAccept(success -> { if (success) { context.player().sendMessage(Text.literal("[BallotBox] ").formatted(Formatting.AQUA).append(Text.literal("Votes Saved! You assigned %s/%s votes over %s/%s categories.".formatted(packet.selections().votes().size(), BallotBoxPlatformClient.categories.values().stream().mapToInt(VotingCategory::limit).sum(), packet.selections().votes().keySet().size(), BallotBoxPlatformClient.categories.size())).formatted(Formatting.GREEN)), true); diff --git a/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java b/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java index 9ab81c8..d07b701 100644 --- a/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java +++ b/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java @@ -2,16 +2,24 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.minecraft.client.MinecraftClient; -import net.modfest.ballotbox.packet.OpenVoteScreenPacket; +import net.modfest.ballotbox.BallotBox; +import net.modfest.ballotbox.packet.OpenVoteScreen; +import net.modfest.ballotbox.packet.S2CGameJoin; import net.modfest.ballotbox.packet.S2CVoteScreenData; public class BallotBoxClientNetworking { public static void init() { + ClientPlayNetworking.registerGlobalReceiver(S2CGameJoin.ID, BallotBoxClientNetworking::handleGameJoin); ClientPlayNetworking.registerGlobalReceiver(S2CVoteScreenData.ID, BallotBoxClientNetworking::handleVoteScreenData); - ClientPlayNetworking.registerGlobalReceiver(OpenVoteScreenPacket.ID, BallotBoxClientNetworking::handleOpenVoteScreen); + ClientPlayNetworking.registerGlobalReceiver(OpenVoteScreen.ID, BallotBoxClientNetworking::handleOpenVoteScreen); } - private static void handleOpenVoteScreen(OpenVoteScreenPacket packet, ClientPlayNetworking.Context context) { + private static void handleGameJoin(S2CGameJoin packet, ClientPlayNetworking.Context context) { + NotBallotBoxClient.closingTime = BallotBox.parseClosingTime(packet.closingTime()); + NotBallotBoxClient.remainingVotes = packet.remainingVotes(); + } + + private static void handleOpenVoteScreen(OpenVoteScreen packet, ClientPlayNetworking.Context context) { MinecraftClient.getInstance().setScreen(new VotingScreen()); } diff --git a/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java b/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java index 2ca91d1..3540128 100644 --- a/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java +++ b/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java @@ -6,7 +6,10 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; -import net.modfest.ballotbox.packet.OpenVoteScreenPacket; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.modfest.ballotbox.BallotBox; +import net.modfest.ballotbox.packet.OpenVoteScreen; public class BallotBoxKeybinds { public static final KeyBinding OPEN_VOTING_SCREEN = new KeyBinding("key.ballotbox.open", InputUtil.Type.KEYSYM, InputUtil.GLFW_KEY_APOSTROPHE, "key.ballotbox.category"); @@ -17,10 +20,12 @@ public static void init() { } private static void tick(MinecraftClient client) { - while (OPEN_VOTING_SCREEN.wasPressed()) { - if (client.currentScreen == null) { + while (OPEN_VOTING_SCREEN.wasPressed() && NotBallotBoxClient.isEnabled(client)) { + if (!NotBallotBoxClient.isOpen()) { + client.inGameHud.setOverlayMessage(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Voting is unavailable! Voting closed %s.".formatted(BallotBox.relativeTime(NotBallotBoxClient.closingTime))).formatted(Formatting.RED)), false); + } else if (client.currentScreen == null) { client.setScreen(new VotingScreen()); - ClientPlayNetworking.send(new OpenVoteScreenPacket()); + ClientPlayNetworking.send(new OpenVoteScreen()); } } } diff --git a/src/main/java/net/modfest/ballotbox/client/NotBallotBoxClient.java b/src/main/java/net/modfest/ballotbox/client/NotBallotBoxClient.java index 14411df..c6df7db 100644 --- a/src/main/java/net/modfest/ballotbox/client/NotBallotBoxClient.java +++ b/src/main/java/net/modfest/ballotbox/client/NotBallotBoxClient.java @@ -1,16 +1,36 @@ package net.modfest.ballotbox.client; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.MinecraftClient; import net.modfest.ballotbox.BallotBox; +import net.modfest.ballotbox.packet.OpenVoteScreen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.Instant; + public class NotBallotBoxClient implements ClientModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger("%s-client".formatted(BallotBox.ID)); + public static Instant closingTime = null; + public static int remainingVotes = 0; + + public static boolean isEnabled(MinecraftClient client) { + return !client.isIntegratedServerRunning() && ClientPlayNetworking.canSend(OpenVoteScreen.ID); + } + + public static boolean isOpen() { + return closingTime == null || closingTime.isAfter(Instant.now()); + } @Override public void onInitializeClient() { LOGGER.info("[BallotBox Client] Initialized!"); + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + remainingVotes = 0; + closingTime = null; + }); BallotBoxClientNetworking.init(); BallotBoxKeybinds.init(); } diff --git a/src/main/java/net/modfest/ballotbox/client/VotingScreen.java b/src/main/java/net/modfest/ballotbox/client/VotingScreen.java index d010479..fc29f96 100644 --- a/src/main/java/net/modfest/ballotbox/client/VotingScreen.java +++ b/src/main/java/net/modfest/ballotbox/client/VotingScreen.java @@ -35,7 +35,6 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -137,6 +136,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { @Override public void removed() { if (!previousSelections.equals(selections)) { + NotBallotBoxClient.remainingVotes = categories.stream().mapToInt(VotingCategory::limit).sum() - selections.size(); ClientPlayNetworking.send(new C2SUpdateVote(new VotingSelections(selections))); } super.removed(); diff --git a/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java b/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java index 1397d74..78e101f 100644 --- a/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java +++ b/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java @@ -4,32 +4,57 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ConfirmLinkScreen; import net.minecraft.client.gui.screen.GameMenuScreen; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.GridWidget; import net.minecraft.client.gui.widget.Widget; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.modfest.ballotbox.BallotBox; +import net.modfest.ballotbox.client.NotBallotBoxClient; import net.modfest.ballotbox.client.VotingScreen; -import net.modfest.ballotbox.packet.OpenVoteScreenPacket; +import net.modfest.ballotbox.packet.OpenVoteScreen; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GameMenuScreen.class) public class GameMenuScreenMixin { + private static ButtonWidget ballotbox$voteButton = null; + @WrapOperation(method = "addFeedbackAndBugsButtons", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/GridWidget$Adder;add(Lnet/minecraft/client/gui/widget/Widget;)Lnet/minecraft/client/gui/widget/Widget;", ordinal = 0)) private static Widget replaceSendFeedback(GridWidget.Adder instance, Widget widget, Operation original, Screen parentScreen, GridWidget.Adder gridAdder) { - if (MinecraftClient.getInstance().isIntegratedServerRunning() || !ClientPlayNetworking.canSend(OpenVoteScreenPacket.ID)) return original.call(instance, widget); - return gridAdder.add(ButtonWidget.builder(Text.of("Submission Voting"), b -> { + if (!NotBallotBoxClient.isEnabled(MinecraftClient.getInstance())) return original.call(instance, widget); + ballotbox$voteButton = ButtonWidget.builder(Text.of("Submission Voting"), b -> { MinecraftClient.getInstance().setScreen(new VotingScreen()); - ClientPlayNetworking.send(new OpenVoteScreenPacket()); - }).width(98).build()); + ClientPlayNetworking.send(new OpenVoteScreen()); + }).width(98).tooltip(NotBallotBoxClient.isOpen() ? null : Tooltip.of(Text.literal("Closed %s.".formatted(BallotBox.relativeTime(NotBallotBoxClient.closingTime))).formatted(Formatting.GRAY))).build(); + ballotbox$voteButton.active = NotBallotBoxClient.isOpen(); + return gridAdder.add(ballotbox$voteButton); } + @WrapOperation(method = "addFeedbackAndBugsButtons", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/GridWidget$Adder;add(Lnet/minecraft/client/gui/widget/Widget;)Lnet/minecraft/client/gui/widget/Widget;", ordinal = 1)) private static Widget replaceReportBugs(GridWidget.Adder instance, Widget widget, Operation original, Screen parentScreen, GridWidget.Adder gridAdder) { - if (MinecraftClient.getInstance().isIntegratedServerRunning() || !ClientPlayNetworking.canSend(OpenVoteScreenPacket.ID)) return original.call(instance, widget); + if (!NotBallotBoxClient.isEnabled(MinecraftClient.getInstance())) return original.call(instance, widget); return gridAdder.add(ButtonWidget.builder(Text.of(BallotBox.CONFIG.bug_text.value()), ConfirmLinkScreen.opening(parentScreen, BallotBox.CONFIG.bug_url.value())).width(98).build()); } + + @Inject(method = "render", at = @At("TAIL")) + private void addReminder(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (ballotbox$voteButton == null) return; + ballotbox$voteButton.active = NotBallotBoxClient.isOpen(); + if (NotBallotBoxClient.isOpen() && NotBallotBoxClient.remainingVotes > 0) { + Text remainingText = Text.literal("%s vote%s available!".formatted(NotBallotBoxClient.remainingVotes, NotBallotBoxClient.remainingVotes > 1 ? "s" : "")).formatted(Formatting.GREEN); + context.drawText(MinecraftClient.getInstance().textRenderer, remainingText, ballotbox$voteButton.getX() - MinecraftClient.getInstance().textRenderer.getWidth(remainingText) - 2, ballotbox$voteButton.getY() + 2, 0xFFFFFFFF, true); + if (NotBallotBoxClient.closingTime != null) { + Text timeText = Text.literal("Closes %s.".formatted(BallotBox.relativeTime(NotBallotBoxClient.closingTime))).formatted(Formatting.YELLOW); + context.drawText(MinecraftClient.getInstance().textRenderer, timeText, ballotbox$voteButton.getX() - MinecraftClient.getInstance().textRenderer.getWidth(timeText) - 2, ballotbox$voteButton.getY() + 10, 0xFFFFFFFF, true); + } + } + } } diff --git a/src/main/java/net/modfest/ballotbox/packet/OpenVoteScreenPacket.java b/src/main/java/net/modfest/ballotbox/packet/OpenVoteScreen.java similarity index 63% rename from src/main/java/net/modfest/ballotbox/packet/OpenVoteScreenPacket.java rename to src/main/java/net/modfest/ballotbox/packet/OpenVoteScreen.java index 8ae0124..8ee500b 100644 --- a/src/main/java/net/modfest/ballotbox/packet/OpenVoteScreenPacket.java +++ b/src/main/java/net/modfest/ballotbox/packet/OpenVoteScreen.java @@ -6,9 +6,9 @@ import net.minecraft.util.Identifier; import net.modfest.ballotbox.BallotBox; -public record OpenVoteScreenPacket() implements CustomPayload { - public static final Id ID = new Id<>(Identifier.of(BallotBox.ID, "open_vote_screen")); - public static final PacketCodec CODEC = PacketCodec.unit(new OpenVoteScreenPacket()); +public record OpenVoteScreen() implements CustomPayload { + public static final Id ID = new Id<>(Identifier.of(BallotBox.ID, "open_vote_screen")); + public static final PacketCodec CODEC = PacketCodec.unit(new OpenVoteScreen()); @Override public Id getId() { diff --git a/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java b/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java new file mode 100644 index 0000000..2561d25 --- /dev/null +++ b/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java @@ -0,0 +1,22 @@ +package net.modfest.ballotbox.packet; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; +import net.modfest.ballotbox.BallotBox; + +public record S2CGameJoin(String closingTime, int remainingVotes) implements CustomPayload { + public static final Id ID = new Id<>(Identifier.of(BallotBox.ID, "game_join")); + public static final PacketCodec CODEC = PacketCodec.tuple( + PacketCodecs.STRING, S2CGameJoin::closingTime, + PacketCodecs.INTEGER, S2CGameJoin::remainingVotes, + S2CGameJoin::new + ); + + @Override + public Id getId() { + return ID; + } +}