diff --git a/src/main/java/io/github/restioson/siege/game/SiegeKit.java b/src/main/java/io/github/restioson/siege/game/SiegeKit.java index c962db2..2d3ad08 100644 --- a/src/main/java/io/github/restioson/siege/game/SiegeKit.java +++ b/src/main/java/io/github/restioson/siege/game/SiegeKit.java @@ -5,7 +5,6 @@ import io.github.restioson.siege.game.active.SiegePlayer; import io.github.restioson.siege.item.SiegeHorn; import net.minecraft.enchantment.EnchantmentLevelEntry; -import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.effect.StatusEffect; import net.minecraft.entity.effect.StatusEffectInstance; @@ -39,7 +38,14 @@ public final class SiegeKit { new KitEquipment(Items.IRON_SWORD, EquipmentSlot.MAINHAND), new KitEquipment(Items.STONE_AXE, EquipmentSlot.OFFHAND) ), - List.of(), + List.of( + new KitResource( + Items.GOLDEN_APPLE.getName(), + Items.GOLDEN_APPLE, + SiegePersonalResource.GAPPLE, + 2 + ) + ), List.of() ); public static final SiegeKit SHIELD_BEARER = new SiegeKit( @@ -50,7 +56,8 @@ public final class SiegeKit { new KitEquipment(Items.IRON_CHESTPLATE), new KitEquipment(Items.IRON_LEGGINGS), new KitEquipment(Items.LEATHER_BOOTS), - new KitEquipment(Items.STONE_SWORD, EquipmentSlot.MAINHAND), + new KitEquipment(Items.IRON_SWORD, EquipmentSlot.MAINHAND), + new KitEquipment(Items.WOODEN_AXE, EquipmentSlot.MAINHAND), new KitEquipable() { @Override public EquipmentSlot getArmorStandSlot() { @@ -80,57 +87,40 @@ public ItemStack buildItemStack(GameTeam team) { new KitEquipment(Items.LEATHER_CHESTPLATE), new KitEquipment(Items.LEATHER_LEGGINGS), new KitEquipment(Items.LEATHER_BOOTS), - new KitEquipment(Items.WOODEN_SWORD, EquipmentSlot.OFFHAND), - new KitEquipment( - Items.BOW, - Items.BOW, - List.of( - new EnchantmentLevelEntry(Enchantments.POWER, 1) - ), - EquipmentSlot.MAINHAND, - null - ) + new KitEquipment(Items.STONE_SWORD, EquipmentSlot.OFFHAND), + new KitEquipment(Items.BOW, EquipmentSlot.MAINHAND), + new KitEquipment(Items.CROSSBOW) ), List.of(new KitResource( Text.translatable("game.siege.kit.items.arrows"), Items.ARROW, SiegePersonalResource.ARROWS, - 24 + 32 )), List.of(kitEffect(StatusEffects.SPEED)) ); - public static final SiegeKit CONSTRUCTOR = new SiegeKit( - "constructor", - Items.OAK_PLANKS, + public static final SiegeKit ENGINEER = new SiegeKit( + "engineer", + Items.IRON_SHOVEL, List.of( new KitEquipment(Items.LEATHER_HELMET), new KitEquipment(Items.IRON_CHESTPLATE), new KitEquipment(Items.LEATHER_LEGGINGS), new KitEquipment(Items.LEATHER_BOOTS), - new KitEquipment(Items.STONE_SWORD, EquipmentSlot.MAINHAND), + new KitEquipment(Items.STONE_SWORD), new KitEquipment(Items.WOODEN_AXE) ), - List.of(KitResource.PLANKS), - List.of() - ); - public static final SiegeKit DEMOLITIONER = new SiegeKit( - "demolitioner", - Items.TNT, List.of( - new KitEquipment(Items.LEATHER_HELMET), - new KitEquipment(Items.LEATHER_CHESTPLATE), - new KitEquipment(Items.LEATHER_LEGGINGS), - new KitEquipment(Items.LEATHER_BOOTS), - new KitEquipment(Items.WOODEN_SWORD, EquipmentSlot.MAINHAND) + KitResource.PLANKS, + new KitResource( + Text.translatable("game.siege.kit.items.tnt"), + Items.TNT, + SiegePersonalResource.TNT, + EquipmentSlot.MAINHAND, + 2 + ) ), - List.of(new KitResource( - Text.translatable("game.siege.kit.items.tnt"), - Items.TNT, - SiegePersonalResource.TNT, - EquipmentSlot.OFFHAND, - 2 - )), - List.of(kitEffect(StatusEffects.GLOWING)) + List.of() ); public static final SiegeKit CAPTAIN = new SiegeKit( "captain", @@ -230,6 +220,10 @@ private static Text restockMessage(List restockResults, long time return text; } + public static ItemStack kitSelectItemStack() { + return KitEquipment.KIT_SELECT.buildItemStack(null); + } + public void equipArmourStand(SiegeKitStandEntity stand) { var team = stand.getTeam(); @@ -406,7 +400,7 @@ private record KitEquipment(Item attackerItem, Item defenderItem, List kitSelections; private final TeamSelectionLobby teamSelection; private SiegeWaiting(ServerWorld world, GameSpace gameSpace, SiegeMap map, SiegeConfig config, TeamSelectionLobby teamSelection) { @@ -38,6 +53,7 @@ private SiegeWaiting(ServerWorld world, GameSpace gameSpace, SiegeMap map, Siege this.map = map; this.config = config; this.teamSelection = teamSelection; + this.kitSelections = new Object2ObjectOpenHashMap<>(); } public static GameOpenProcedure open(GameOpenContext context) { @@ -58,20 +74,49 @@ public static GameOpenProcedure open(GameOpenContext context) { activity.listen(GameActivityEvents.REQUEST_START, waiting::requestStart); activity.listen(GamePlayerEvents.OFFER, waiting::offerPlayer); activity.listen(PlayerDeathEvent.EVENT, waiting::onPlayerDeath); + activity.listen(ItemUseEvent.EVENT, waiting::onUseItem); + activity.listen(GamePlayerEvents.ADD, waiting::onAddPlayer); }); } + private void onAddPlayer(ServerPlayerEntity player) { + player.getInventory().offerOrDrop(SiegeKit.kitSelectItemStack()); + } + + private TypedActionResult onUseItem(ServerPlayerEntity player, Hand hand) { + var stack = player.getStackInHand(hand); + + if (stack.getItem() == SiegeKit.KIT_SELECT_ITEM) { + var ref = PlayerRef.of(player); + SimpleGui ui = WarpSelectionUi.createKitSelect(player, this.kitSelections.get(ref), selectedKit -> { + this.kitSelections.put(ref, selectedKit); + var msg = Text.translatable("game.siege.kit.selected") + .append(" ") + .append(selectedKit.getName()) + .formatted(Formatting.GREEN); + player.sendMessage((msg), true); + player.playSound(SoundEvents.ITEM_ARMOR_EQUIP_GENERIC, SoundCategory.NEUTRAL, 1.0F, 1.0F); + }); + + ui.open(); + } + + return TypedActionResult.fail(stack); + } + private GameResult requestStart() { Multimap players = HashMultimap.create(); this.teamSelection.allocate(this.gameSpace.getPlayers(), players::put); - SiegeActive.open(this.world, this.gameSpace, this.map, this.config, players); + SiegeActive.open(this.world, this.gameSpace, this.map, this.config, players, this.kitSelections); return GameResult.ok(); } private PlayerOfferResult offerPlayer(PlayerOffer offer) { - return SiegeSpawnLogic.acceptPlayer(offer, this.world, this.map.waitingSpawn, GameMode.ADVENTURE); + var res = SiegeSpawnLogic.acceptPlayer(offer, this.world, this.map.waitingSpawn, GameMode.ADVENTURE); + offer.player().getInventory().offerOrDrop(SiegeKit.kitSelectItemStack()); + return res; } private ActionResult onPlayerDeath(ServerPlayerEntity player, DamageSource source) { diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegeActive.java b/src/main/java/io/github/restioson/siege/game/active/SiegeActive.java index 971aab8..49e062d 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegeActive.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegeActive.java @@ -13,7 +13,10 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.minecraft.block.*; +import net.minecraft.block.BlockState; +import net.minecraft.block.BlockWithEntity; +import net.minecraft.block.DoorBlock; +import net.minecraft.block.EnderChestBlock; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; @@ -86,22 +89,22 @@ public class SiegeActive { public final Object2ObjectMap participants; public final Map warpingPlayers; - private long timeLimitSecs; final SiegeStageManager stageManager; - final SiegeSidebar sidebar; - final SiegeCaptureLogic captureLogic; final SiegeGateLogic gateLogic; - public static int TNT_GATE_DAMAGE = 10; public SiegeMap map; + public static int TNT_GATE_DAMAGE = 10; + private final static List PLANKS = List.of( SiegeKit.KitResource.PLANKS.attackerItem(), SiegeKit.KitResource.PLANKS.defenderItem() ); - private SiegeActive(ServerWorld world, GameActivity activity, SiegeMap map, SiegeConfig config, GlobalWidgets widgets, Multimap players) { + private SiegeActive(ServerWorld world, GameActivity activity, SiegeMap map, SiegeConfig config, + GlobalWidgets widgets, Multimap players, + Map kitSelections) { this.world = world; this.gameSpace = activity.getGameSpace(); this.config = config; @@ -113,25 +116,26 @@ private SiegeActive(ServerWorld world, GameActivity activity, SiegeMap map, Sieg for (GameTeamKey key : players.keySet()) { for (ServerPlayerEntity player : players.get(key)) { - this.participants.put(PlayerRef.of(player), new SiegePlayer(player.getRandom(), SiegeTeams.byKey(key))); + var ref = PlayerRef.of(player); + this.participants.put(ref, new SiegePlayer(SiegeTeams.byKey(key), kitSelections.get(ref))); this.teams.addPlayer(player, key); } } - this.timeLimitSecs = config.timeLimitMins() * 60L; this.stageManager = new SiegeStageManager(this); - this.sidebar = new SiegeSidebar(this, widgets); this.captureLogic = new SiegeCaptureLogic(this); this.gateLogic = new SiegeGateLogic(this); } - public static void open(ServerWorld world, GameSpace gameSpace, SiegeMap map, SiegeConfig config, Multimap players) { + public static void open(ServerWorld world, GameSpace gameSpace, SiegeMap map, SiegeConfig config, + Multimap players, + Map kitSelections) { gameSpace.setActivity(activity -> { GlobalWidgets widgets = GlobalWidgets.addTo(activity); - SiegeActive active = new SiegeActive(world, activity, map, config, widgets, players); + SiegeActive active = new SiegeActive(world, activity, map, config, widgets, players, kitSelections); activity.deny(GameRuleType.CRAFTING); activity.deny(GameRuleType.PORTALS); @@ -317,7 +321,6 @@ private void onOpen() { this.gameSpace.getPlayers().sendMessage(hint); } - this.stageManager.onOpen(this.world.getTime()); } @@ -325,6 +328,8 @@ private void onClose() { for (SiegeFlag flag : this.map.flags) { flag.closeCaptureBar(); } + + this.stageManager.closeTimerBar(); } private PlayerOfferResult offerPlayer(PlayerOffer offer) { @@ -340,7 +345,7 @@ private PlayerOfferResult offerPlayer(PlayerOffer offer) { private void allocateParticipant(ServerPlayerEntity player) { GameTeamKey smallestTeam = this.teams.getSmallestTeam(); - SiegePlayer participant = new SiegePlayer(player.getRandom(), SiegeTeams.byKey(smallestTeam)); + SiegePlayer participant = new SiegePlayer(SiegeTeams.byKey(smallestTeam), null); this.participants.put(PlayerRef.of(player), participant); this.teams.addPlayer(player, smallestTeam); } @@ -368,9 +373,7 @@ private ActionResult onPlaceBlock( return ActionResult.FAIL; } - Block block = blockState.getBlock(); - - if (participant.kit == SiegeKit.CONSTRUCTOR && block != Blocks.TNT) { + if (participant.kit == SiegeKit.ENGINEER) { // TNT may be placed anyway for (BlockBounds noBuildRegion : this.map.noBuildRegions) { if (noBuildRegion.contains(blockPos)) { @@ -383,8 +386,6 @@ private ActionResult onPlaceBlock( } return this.gateLogic.maybeBraceGate(blockPos, participant, player, ctx, this.world.getTime()); - } else if (participant.kit == SiegeKit.DEMOLITIONER && block == Blocks.TNT) { - return ActionResult.PASS; } else { return ActionResult.FAIL; } @@ -468,7 +469,7 @@ private TypedActionResult onUseItem(ServerPlayerEntity player, Hand h return new TypedActionResult<>(ActionResult.FAIL, stack); } else if (item == SiegeKit.KIT_SELECT_ITEM) { - SimpleGui ui = WarpSelectionUi.createKitWarp(player, participant, selectedKit -> { + SimpleGui ui = WarpSelectionUi.createKitSelect(player, participant.kit, selectedKit -> { long time = player.getWorld().getTime(); var spawn = this.getSpawnFor(player, time); @@ -607,6 +608,7 @@ private void spawnParticipant(ServerPlayerEntity player, @Nullable SiegeSpawn sp spawn = this.getSpawnFor(player, this.world.getTime()).spawn; } + this.stageManager.timerBar.addPlayer(player); SiegeSpawnLogic.resetPlayer(player, GameMode.SURVIVAL); SiegeSpawnLogic.spawnPlayer(player, spawn, this.world); @@ -654,19 +656,19 @@ private void tick() { long time = this.world.getTime(); SiegeStageManager.TickResult result = this.stageManager.tick(time); - if (result != SiegeStageManager.TickResult.CONTINUE_TICK) { + if (!result.continueGame()) { switch (result) { case ATTACKERS_WIN -> this.broadcastWin(SiegeTeams.ATTACKERS); case DEFENDERS_WIN -> this.broadcastWin(SiegeTeams.DEFENDERS); case GAME_CLOSED -> this.gameSpace.close(GameCloseReason.FINISHED); } + return; } if (time % 20 == 0) { this.captureLogic.tick(this.world, 20); this.gateLogic.tick(); - this.sidebar.update(time); this.tickResources(time); } @@ -749,14 +751,6 @@ private void tickDead(ServerWorld world, long time) { } } - public void addTime(long time) { - this.timeLimitSecs += time; - } - - public long timeLimitSecs() { - return this.timeLimitSecs; - } - private void broadcastWin(GameTeam winningTeam) { for (Map.Entry entry : this.participants.entrySet()) { entry.getKey().ifOnline(this.gameSpace.getServer(), player -> { diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegeCaptureLogic.java b/src/main/java/io/github/restioson/siege/game/active/SiegeCaptureLogic.java index 340711c..159cc01 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegeCaptureLogic.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegeCaptureLogic.java @@ -147,7 +147,7 @@ private void tickCapturing(SiegeFlag flag, int interval, GameTeam captureTeam, this.broadcastCaptured(flag, captureTeam); flag.setTeamBlocks(this.game.world, captureTeam); - this.game.addTime(this.game.config.capturingGiveTimeSecs()); + this.game.stageManager.addTime(this.game.config.capturingGiveTimeSecs()); for (ServerPlayerEntity player : capturingPlayers) { player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, SoundCategory.NEUTRAL, 1.0F, 1.0F); diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegeGateLogic.java b/src/main/java/io/github/restioson/siege/game/active/SiegeGateLogic.java index 65a4563..1839444 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegeGateLogic.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegeGateLogic.java @@ -211,7 +211,7 @@ public void tickGate(SiegeGate gate) { if (participant.team == gate.flag.team) { ownerTeamPresent.add(player); - if (participant.kit == SiegeKit.CONSTRUCTOR) { + if (participant.kit == SiegeKit.ENGINEER) { ownerTeamPresent.add(player); } @@ -229,7 +229,7 @@ public void tickGate(SiegeGate gate) { if (gate.underAttack(time) || gate.health != gate.maxHealth) { if (time - participant.timeOfLastBrace > 5 * 20) { - var kit = participant.kit == SiegeKit.CONSTRUCTOR ? "constructor" : "general"; + var kit = participant.kit == SiegeKit.ENGINEER ? "engineer" : "general"; if (gate.bashedOpen) { var key = String.format("game.siege.gate.repair_hint.%s", kit); player.sendMessage( @@ -247,12 +247,12 @@ public void tickGate(SiegeGate gate) { } } - if (!gate.underAttack(time)) { - enemyTeamPresent.sendActionBar(Text.translatable("game.siege.gate.bash_hint").formatted(Formatting.GOLD)); - } - if (gate.bashedOpen) { + enemyTeamPresent.sendActionBar(Text.translatable("game.siege.gate.capture_hint") + .formatted(Formatting.GOLD)); return; + } else if (!gate.underAttack(time)) { + enemyTeamPresent.sendActionBar(Text.translatable("game.siege.gate.bash_hint").formatted(Formatting.GOLD)); } boolean shouldOpen = !ownerTeamPresent.isEmpty() && enemyTeamPresent.isEmpty(); diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegePersonalResource.java b/src/main/java/io/github/restioson/siege/game/active/SiegePersonalResource.java index 364738a..29f80d5 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegePersonalResource.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegePersonalResource.java @@ -4,7 +4,8 @@ public enum SiegePersonalResource { WOOD(64, 2), ARROWS(64, 2), TNT(4, 30), - FLARES(16, 4); + FLARES(16, 4), + GAPPLE(4, 10); public final int max; public final int refreshSecs; diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegePlayer.java b/src/main/java/io/github/restioson/siege/game/active/SiegePlayer.java index b2f9486..546ca06 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegePlayer.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegePlayer.java @@ -4,7 +4,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.math.random.Random; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.plasmid.game.common.team.GameTeam; @@ -27,9 +26,9 @@ public class SiegePlayer { public int captures; public int secures; - public SiegePlayer(Random random, GameTeam team) { + public SiegePlayer(GameTeam team, @Nullable SiegeKit kit) { this.team = team; - this.kit = SiegeKit.KITS.get(random.nextInt(SiegeKit.KITS.size())); + this.kit = kit != null ? kit : SiegeKit.SOLDIER; for (var resource : SiegePersonalResource.values()) { this.resources.put(resource, resource.max); diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegeSidebar.java b/src/main/java/io/github/restioson/siege/game/active/SiegeSidebar.java index 837ffcb..4a72c6a 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegeSidebar.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegeSidebar.java @@ -43,62 +43,16 @@ private static int getSortIndex(SiegeFlag flag, long time) { return -1; } - private static Text getTimeLeft(long ticksUntilEnd) { - long secondsUntilEnd = ticksUntilEnd / 20; - - long minutes = secondsUntilEnd / 60; - long seconds = secondsUntilEnd % 60; - - return Text.literal(String.format("%02d:%02d", minutes, seconds)).formatted(Formatting.AQUA, Formatting.BOLD); - } - public void update(long time) { this.widget.set(content -> { - content.add(ScreenTexts.EMPTY); - - long ticksUntilEnd = this.game.stageManager.finishTime() - time; - content.add( - Text.literal("Time left ").formatted(Formatting.GOLD, Formatting.BOLD), - getTimeLeft(ticksUntilEnd) - ); - - Text giveTimeRight; if (this.config.capturingGiveTimeSecs() > 0) { - giveTimeRight = Text.translatable( - "game.siege.quick.sidebar.right.enabled", - this.config.giveTimeFormatted() - ).formatted(Formatting.AQUA); - } else { - giveTimeRight = Text.translatable("game.siege.quick.sidebar.right.disabled") - .formatted(Formatting.DARK_RED); - } - - content.add(Text.translatable("game.siege.quick.sidebar").formatted(Formatting.GOLD), giveTimeRight); - - String enderPearlKey; - Formatting enderPearlColor; - if (this.config.hasEnderPearl(SiegeTeams.ATTACKERS) && this.config.hasEnderPearl(SiegeTeams.DEFENDERS)) { - enderPearlKey = "both"; - enderPearlColor = Formatting.GREEN; - } else if (this.config.hasEnderPearl(SiegeTeams.ATTACKERS)) { - enderPearlKey = "attacker_only"; - enderPearlColor = SiegeTeams.ATTACKERS.config().chatFormatting(); - } else if (this.config.hasEnderPearl(SiegeTeams.DEFENDERS)) { - enderPearlKey = "defender_only"; - enderPearlColor = SiegeTeams.DEFENDERS.config().chatFormatting(); - } else { - enderPearlKey = "neither"; - enderPearlColor = Formatting.DARK_RED; + content.add( + Text.translatable("game.siege.quick.sidebar").formatted(Formatting.GOLD), + Text.literal(this.config.giveTimeFormatted()).formatted(Formatting.AQUA) + ); + content.add(ScreenTexts.EMPTY); } - content.add( - Text.translatable("game.siege.enderpearl.sidebar").formatted(Formatting.GOLD), - Text.translatable(String.format("game.siege.enderpearl.sidebar.%s", enderPearlKey)) - .formatted(enderPearlColor) - ); - - content.add(ScreenTexts.EMPTY); - List flags = new ArrayList<>(this.game.map.flags); flags.sort(Comparator.comparingInt(flag -> getSortIndex(flag, time))); @@ -134,7 +88,7 @@ public void update(long time) { int percent = (int) Math.floor(flag.captureFraction() * 100); Text line; - boolean underAttack = flag.capturingState != null && flag.capturingState.isUnderAttack(); + boolean underAttack = flag.capturingState != null && flag.isFlagUnderAttack(); if (underAttack || percent > 0) { line = Text.literal("(" + percent + "%) ").append(flagName); } else if (flag.gateUnderAttack(time)) { diff --git a/src/main/java/io/github/restioson/siege/game/active/SiegeStageManager.java b/src/main/java/io/github/restioson/siege/game/active/SiegeStageManager.java index 3aa29f4..2483af4 100644 --- a/src/main/java/io/github/restioson/siege/game/active/SiegeStageManager.java +++ b/src/main/java/io/github/restioson/siege/game/active/SiegeStageManager.java @@ -2,7 +2,11 @@ import io.github.restioson.siege.game.SiegeTeams; import io.github.restioson.siege.game.map.SiegeFlag; +import net.minecraft.entity.boss.BossBar; +import net.minecraft.entity.boss.ServerBossBar; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.world.GameMode; import xyz.nucleoid.plasmid.game.common.team.GameTeam; @@ -10,8 +14,15 @@ public class SiegeStageManager { private final SiegeActive game; private final boolean singlePlayer; + public final ServerBossBar timerBar = new ServerBossBar( + Text.translatable("game.siege.timer.time_left"), + BossBar.Color.BLUE, + BossBar.Style.PROGRESS + ); private long closeTime = -1; public long startTime = -1; + private long maxPotentialTime = -1; + private long finishTime = -1; SiegeStageManager(SiegeActive game) { this.game = game; @@ -20,10 +31,8 @@ public class SiegeStageManager { public void onOpen(long time) { this.startTime = time; - } - - public long finishTime() { - return this.startTime + (this.game.timeLimitSecs() * 20); + this.maxPotentialTime = this.game.config.timeLimitMins() * 60L * 20L; + this.finishTime = this.startTime + this.maxPotentialTime; } public TickResult tick(long time) { @@ -36,6 +45,12 @@ public TickResult tick(long time) { return this.tickClosing(time); } + if (this.testOvertime(time)) { + this.timerBar.setName(Text.translatable("game.siege.timer.overtime").formatted(Formatting.RED)); + this.timerBar.setPercent(1.0f); + return TickResult.OVERTIME; + } + if (this.testDefendersWin(time)) { this.triggerFinish(time); return TickResult.DEFENDERS_WIN; @@ -56,14 +71,32 @@ public TickResult tick(long time) { } } - var timeToFinish = this.finishTime() - time; + var timeToFinish = this.finishTime - time; if (timeToFinish == 20 * 60) { SiegeDialogueLogic.broadcastTimeRunningOut(this.game); } + long ticksTillEnd = this.finishTime - time; + long secondsUntilEnd = ticksTillEnd / 20; + long minutes = secondsUntilEnd / 60; + long seconds = secondsUntilEnd % 60; + var timerBarText = Text.translatable("game.siege.timer.time_left") + .append(" ") + .append( + Text.literal(String.format("%02d:%02d", minutes, seconds)) + .formatted(Formatting.AQUA) + ); + + this.timerBar.setName(timerBarText); + this.timerBar.setPercent((float) ticksTillEnd / this.maxPotentialTime); + return TickResult.CONTINUE_TICK; } + private boolean testOvertime(long time) { + return time >= this.finishTime && this.game.map.flags.stream().noneMatch(SiegeFlag::isFlagUnderAttack); + } + private TickResult tickClosing(long time) { if (time >= this.closeTime) { return TickResult.GAME_CLOSED; @@ -72,6 +105,8 @@ private TickResult tickClosing(long time) { } private void triggerFinish(long time) { + this.closeTimerBar(); + for (ServerPlayerEntity player : this.game.gameSpace.getPlayers()) { player.changeGameMode(GameMode.SPECTATOR); } @@ -80,7 +115,7 @@ private void triggerFinish(long time) { } private boolean testDefendersWin(long time) { - return time >= this.finishTime(); + return time >= this.finishTime; } private boolean testAttackersWin() { @@ -102,11 +137,31 @@ private GameTeam getRemainingTeam() { return SiegeTeams.DEFENDERS; } + public void addTime(int secs) { + long timeNow = this.game.world.getTime(); + if (timeNow > this.finishTime) { + this.finishTime = timeNow; + } + + this.maxPotentialTime += secs * 20L; + this.finishTime += secs * 20L; + } + + public void closeTimerBar() { + this.timerBar.setVisible(false); + this.timerBar.clearPlayers(); + } + public enum TickResult { CONTINUE_TICK, TICK_FINISHED, ATTACKERS_WIN, DEFENDERS_WIN, - GAME_CLOSED, + OVERTIME, + GAME_CLOSED; + + public boolean continueGame() { + return this == CONTINUE_TICK || this == OVERTIME; + } } } diff --git a/src/main/java/io/github/restioson/siege/game/active/WarpSelectionUi.java b/src/main/java/io/github/restioson/siege/game/active/WarpSelectionUi.java index 571799e..cb74c36 100644 --- a/src/main/java/io/github/restioson/siege/game/active/WarpSelectionUi.java +++ b/src/main/java/io/github/restioson/siege/game/active/WarpSelectionUi.java @@ -10,6 +10,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.MutableText; import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; import xyz.nucleoid.plasmid.game.common.team.GameTeam; import xyz.nucleoid.plasmid.shop.ShopEntry; import xyz.nucleoid.plasmid.util.ColoredBlocks; @@ -31,19 +32,19 @@ public static WarpSelectionUi createFlagWarp(ServerPlayerEntity player, SiegeMap return new WarpSelectionUi(player, selectors, Text.translatable("game.siege.warp.flag")); } - public static WarpSelectionUi createKitWarp(ServerPlayerEntity player, SiegePlayer participant, - Consumer select) { - var selectors = kitSelectors(participant, select); + public static WarpSelectionUi createKitSelect(ServerPlayerEntity player, @Nullable SiegeKit selectedKit, + Consumer select) { + var selectors = kitSelectors(selectedKit, select); return new WarpSelectionUi(player, selectors, Text.translatable("game.siege.warp.kit")); } - private static List kitSelectors(SiegePlayer participant, Consumer select) { + private static List kitSelectors(@Nullable SiegeKit selectedKit, Consumer select) { List selectors = new ArrayList<>(); for (SiegeKit kit : SiegeKit.KITS) { ItemStack icon = kit.icon.getDefaultStack(); - if (participant.kit == kit) { + if (selectedKit == kit) { icon.addEnchantment(null, 0); } diff --git a/src/main/java/io/github/restioson/siege/game/active/capturing/SimpleCapturingState.java b/src/main/java/io/github/restioson/siege/game/active/capturing/SimpleCapturingState.java index e3beca9..e3567ad 100644 --- a/src/main/java/io/github/restioson/siege/game/active/capturing/SimpleCapturingState.java +++ b/src/main/java/io/github/restioson/siege/game/active/capturing/SimpleCapturingState.java @@ -35,11 +35,12 @@ public boolean isUnderAttack() { } @Override - public @NotNull BossBar.Color getCaptureBarColorForTeam(GameTeam team) { - return switch (this.blink) { - case OWNING_TEAM_TO_GREY -> BossBar.Color.WHITE; - case OWNING_TEAM_TO_CAPTURING -> team == SiegeTeams.ATTACKERS ? BossBar.Color.RED : BossBar.Color.BLUE; - case NO_BLINK -> BossBar.Color.RED; + public @NotNull BossBar.Color getCaptureBarColorForTeam(GameTeam flagOwner) { + return switch (this) { + case CAPTURING -> flagOwner == SiegeTeams.DEFENDERS ? BossBar.Color.RED : BossBar.Color.BLUE; + case CONTESTED -> BossBar.Color.WHITE; + case SECURING -> BossBar.Color.GREEN; + case RECAPTURE_DISABLED -> BossBar.Color.YELLOW; }; } diff --git a/src/main/java/io/github/restioson/siege/game/map/SiegeFlag.java b/src/main/java/io/github/restioson/siege/game/map/SiegeFlag.java index 9c73fab..e4440a5 100644 --- a/src/main/java/io/github/restioson/siege/game/map/SiegeFlag.java +++ b/src/main/java/io/github/restioson/siege/game/map/SiegeFlag.java @@ -164,7 +164,7 @@ public void updateCaptureBar() { this.captureBar.setVisible(true); this.captureBar.setName(this.capturingState.getTitle()); this.captureBar.setPercent(this.captureFraction()); - this.captureBar.setColor(this.capturingState.getCaptureBarColorForTeam(this.team)); + this.captureBar.setColor(this.capturingState.getCaptureBarColorForTeam(SiegeTeams.opposite(this.team))); } else { this.captureBar.setVisible(false); } @@ -259,4 +259,11 @@ public void playSound(ServerWorld world, SoundEvent event, float pitch) { var centre = this.bounds.center(); world.playSound(null, centre.x, centre.y, centre.z, event, SoundCategory.NEUTRAL, 2.0f, pitch); } + + /** + * Whether this _flag_ is under attack (not including gate) + */ + public boolean isFlagUnderAttack() { + return this.capturingState.isUnderAttack(); + } } diff --git a/src/main/java/io/github/restioson/siege/game/map/SiegeMapLoader.java b/src/main/java/io/github/restioson/siege/game/map/SiegeMapLoader.java index baf4d91..b9adbbe 100644 --- a/src/main/java/io/github/restioson/siege/game/map/SiegeMapLoader.java +++ b/src/main/java/io/github/restioson/siege/game/map/SiegeMapLoader.java @@ -228,6 +228,16 @@ private static void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { } }); + for (var flag : map.flags) { + if (flag.attackerRespawn == null) { + throw new GameOpenException(Text.literal("Flag %s missing respawn for attackers!".formatted(flag.name))); + } + + if (flag.defenderRespawn == null) { + throw new GameOpenException(Text.literal("Flag %s missing respawn for defenders!".formatted(flag.name))); + } + } + map.noBuildRegions = metadata.getRegionBounds("no_build").collect(Collectors.toList()); map.gates = metadata.getRegions("gate_open") @@ -323,9 +333,9 @@ private static SiegeKit parseKitStandType(NbtCompound data) { case "bow" -> SiegeKit.ARCHER; case "sword" -> SiegeKit.SOLDIER; case "shield" -> SiegeKit.SHIELD_BEARER; - case "builder" -> SiegeKit.CONSTRUCTOR; - case "demolitioner" -> SiegeKit.DEMOLITIONER; + case "builder" -> SiegeKit.ENGINEER; case "captain" -> SiegeKit.CAPTAIN; + case "demolitioner" -> SiegeKit.ENGINEER; // TODO HACK: remove later default -> { Siege.LOGGER.error("Unknown kit \"" + kitName + "\""); throw new GameOpenException(Text.literal("unknown kit")); diff --git a/src/main/resources/data/siege/games/canal_quick.json b/src/main/resources/data/siege/games/canal_quick.json index 723c312..67b2a50 100644 --- a/src/main/resources/data/siege/games/canal_quick.json +++ b/src/main/resources/data/siege/games/canal_quick.json @@ -5,9 +5,6 @@ "translate": "gameType.siege.siege" }, "description": [ - { - "translate": "game.siege.siege.desc" - }, { "translate": "game.siege.siege.desc" }, @@ -25,5 +22,5 @@ "threshold": 4 }, "capturing_give_time_secs": 120, - "time_limit_mins": 5 + "time_limit_mins": 6 } diff --git a/src/main/resources/data/siege/games/jungle_quick.json b/src/main/resources/data/siege/games/jungle_quick.json index db1e321..559a9d4 100644 --- a/src/main/resources/data/siege/games/jungle_quick.json +++ b/src/main/resources/data/siege/games/jungle_quick.json @@ -31,5 +31,5 @@ "threshold": 4 }, "capturing_give_time_secs": 120, - "time_limit_mins": 5 + "time_limit_mins": 6 } diff --git a/src/main/resources/data/siege/games/vvp_quick.json b/src/main/resources/data/siege/games/vvp_quick.json index f575652..1c296f1 100644 --- a/src/main/resources/data/siege/games/vvp_quick.json +++ b/src/main/resources/data/siege/games/vvp_quick.json @@ -32,6 +32,6 @@ }, "defender_ender_pearl": true, "attacker_ender_pearl": true, - "capturing_give_time_secs": 90, - "time_limit_mins": 5 + "capturing_give_time_secs": 120, + "time_limit_mins": 6 } diff --git a/src/main/resources/data/siege/lang/en_us.json b/src/main/resources/data/siege/lang/en_us.json index 4c14720..7754408 100644 --- a/src/main/resources/data/siege/lang/en_us.json +++ b/src/main/resources/data/siege/lang/en_us.json @@ -1,6 +1,8 @@ { "gameType.siege.siege": "Siege", "item.siege.captains_horn": "Captain's Horn", + "game.siege.timer.time_left": "Time left:", + "game.siege.timer.overtime": "Overtime!", "game.siege.canal": "Siege: Canal", "game.siege.canal_recapture": "Siege: Canal (with Recapture)", @@ -13,8 +15,6 @@ "game.siege.siege.desc": "A castle siege game.", "game.siege.quick.sidebar": "Capture adds", - "game.siege.quick.sidebar.right.enabled": "%s", - "game.siege.quick.sidebar.right.disabled": "No", "game.siege.quick.desc.1": "Quick mode - capturing flags", "game.siege.quick.desc.2": "adds time for the attackers!", "game.siege.recapture.desc.1": "Recapture enabled - defenders can", @@ -39,18 +39,14 @@ "game.siege.kit.kits.shield_bearer": "Shieldbearer Kit", "game.siege.kit.kits.shield_bearer.desc.1": "Be tough and protect yourself", "game.siege.kit.kits.shield_bearer.desc.2": "with a sturdy shield!", - - "game.siege.kit.kits.constructor": "Constructor Kit", - "game.siege.kit.kits.constructor.desc.1": "Build walls and repair gates", - "game.siege.kit.kits.constructor.desc.2": "to protect your team!", - - "game.siege.kit.kits.demolitioner": "Demolitioner Kit", - "game.siege.kit.kits.demolitioner.desc.1": "Destroy enemy fortifications", - "game.siege.kit.kits.demolitioner.desc.2": "and do massive explosive damage!", + "game.siege.kit.kits.engineer": "Engineer Kit", + "game.siege.kit.kits.engineer.desc.1": "Build walls, repair gates,", + "game.siege.kit.kits.engineer.desc.2": "or blast them down as a siege engineer!", "game.siege.kit.kits.captain": "Captain Kit", "game.siege.kit.kits.captain.desc.1": "Inspire and buff your team's", "game.siege.kit.kits.captain.desc.2": "speed and damage with your horn!", + "game.siege.kit.selected": "Chosen to play as", "game.siege.kit.cooldown": "Please wait before switching kit again", "game.siege.kit.not_your_stand": "This is not your team's kit stand", "game.siege.kit.items.arrows": "arrows", @@ -72,11 +68,12 @@ "game.siege.flag.prerequisite_required.and": "and", "game.siege.gate.under_attack": "The %s is under attack!", "game.siege.gate.contested": "There are enemies outside the gate!", - "game.siege.gate.brace_hint.general": "Gate health: %s/%s. Brace it as a constructor by placing wood nearby!", - "game.siege.gate.brace_hint.constructor": "Gate health: %s/%s. Brace it by placing wood nearby!", - "game.siege.gate.repair_hint.general": "%s more blocks to repair gate. Repair it as a constructor by placing wood nearby!", - "game.siege.gate.repair_hint.constructor": "%s more blocks to repair gate. Repair it by placing wood nearby!", - "game.siege.gate.bash_hint": "Bash down the gate as a soldier, shieldbearer, or demolitioner!", + "game.siege.gate.brace_hint.general": "Gate health: %s/%s. Brace it as an engineer by placing wood nearby!", + "game.siege.gate.brace_hint.engineer": "Gate health: %s/%s. Brace it by placing wood nearby!", + "game.siege.gate.repair_hint.general": "%s more blocks to repair gate. Repair it as an engineer by placing wood nearby!", + "game.siege.gate.repair_hint.engineer": "%s more blocks to repair gate. Repair it by placing wood nearby!", + "game.siege.gate.bash_hint": "Bash down the gate as a soldier, shieldbearer, or engineer!", + "game.siege.gate.capture_hint": "Capture the flag to control the gate!", "game.siege.team.attackers": "Attackers", "game.siege.team.defenders": "Defenders", "game.siege.character.attackers": "Captain Gorok", diff --git a/src/main/resources/data/siege/map_templates/villagers_vs_pillagers.nbt b/src/main/resources/data/siege/map_templates/villagers_vs_pillagers.nbt index 84e1b73..d75dfab 100644 Binary files a/src/main/resources/data/siege/map_templates/villagers_vs_pillagers.nbt and b/src/main/resources/data/siege/map_templates/villagers_vs_pillagers.nbt differ