diff --git a/src/main/java/io/github/restioson/siege/entity/SiegeKitStandEntity.java b/src/main/java/io/github/restioson/siege/entity/SiegeKitStandEntity.java index 46e64c2..6d1164a 100644 --- a/src/main/java/io/github/restioson/siege/entity/SiegeKitStandEntity.java +++ b/src/main/java/io/github/restioson/siege/entity/SiegeKitStandEntity.java @@ -24,42 +24,40 @@ public final class SiegeKitStandEntity extends ArmorStandEntity { @Nullable private final GameTeam team; - private final SiegeKit type; + private final SiegeKit kit; private final SiegeActive game; public SiegeKitStandEntity(World world, SiegeActive game, SiegeKitStandLocation stand) { super(EntityType.ARMOR_STAND, world); - this.type = stand.type(); - this.team = stand.team(); + this.kit = stand.type(); this.controllingFlag = stand.flag(); + this.team = this.controllingFlag != null ? this.controllingFlag.team : stand.team(); this.game = game; this.setPose(EntityPose.CROUCHING); this.updatePositionAndAngles(stand.pos().x, stand.pos().y, stand.pos().z, stand.yaw(), 0); - this.setCustomName(this.type.name); + this.setCustomName(this.kit.name()); this.setInvulnerable(true); this.setCustomNameVisible(true); this.setShowArms(true); - this.type.equipArmourStand(this); + this.kit.equipArmourStand(this); } public GameTeam getTeam() { - if (this.controllingFlag != null) { - return this.controllingFlag.team; - } else { - return this.team; - } + return this.controllingFlag != null ? this.controllingFlag.team : this.team; } public void onControllingFlagCaptured() { - this.type.equipArmourStand(this); + this.kit.equipArmourStand(this); } @Override - public ActionResult interactAt(PlayerEntity player, Vec3d hitPos, Hand hand) { - SiegePlayer participant = this.game.participant((ServerPlayerEntity) player); - if (participant == null || ((ServerPlayerEntity) player).interactionManager.getGameMode() != GameMode.SURVIVAL) { + public ActionResult interactAt(PlayerEntity playerEntity, Vec3d hitPos, Hand hand) { + var player = (ServerPlayerEntity) playerEntity; + SiegePlayer participant = this.game.participant(player); + + if (participant == null || player.interactionManager.getGameMode() != GameMode.SURVIVAL) { return ActionResult.FAIL; } @@ -67,8 +65,9 @@ public ActionResult interactAt(PlayerEntity player, Vec3d hitPos, Hand hand) { return ActionResult.FAIL; } - participant.kit = this.type; - this.type.equipPlayer((ServerPlayerEntity) player, participant, this.game.world, this.game.config); + participant.kit.returnResources(player, participant); + participant.kit = this.kit; + this.kit.equipPlayer(player, participant, this.game.config, player.getWorld().getTime()); return ActionResult.SUCCESS; } @@ -78,7 +77,7 @@ public boolean isImmobile() { } @Override - public void takeKnockback(double strength, double x, double z) { - super.takeKnockback(strength, x, z); + public boolean isMarker() { + return true; } } 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 a9339e3..977071d 100644 --- a/src/main/java/io/github/restioson/siege/game/SiegeKit.java +++ b/src/main/java/io/github/restioson/siege/game/SiegeKit.java @@ -3,64 +3,241 @@ import io.github.restioson.siege.entity.SiegeKitStandEntity; import io.github.restioson.siege.game.active.SiegePersonalResource; import io.github.restioson.siege.game.active.SiegePlayer; +import net.minecraft.enchantment.EnchantmentLevelEntry; import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffect; import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.effect.StatusEffects; -import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Equipment; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.plasmid.game.common.team.GameTeam; import xyz.nucleoid.plasmid.util.ItemStackBuilder; -public enum SiegeKit { - ARCHER(Text.literal("Archer Kit")), - SOLDIER(Text.literal("Soldier Kit")), - CONSTRUCTOR(Text.literal("Constructor Kit")), - SHIELD_BEARER(Text.literal("Shield Bearer Kit")), - DEMOLITIONER(Text.literal("Demolitioner Kit")); +import java.util.List; +import java.util.stream.Stream; - public static int ARROWS = 16; - public static int PLANKS = 12; - public static int STEAK = 10; - public static int TNT = 1; +public record SiegeKit(Text name, List equipment, List resources, + List statusEffects) { + public static final SiegeKit SOLDIER = new SiegeKit( + Text.translatable("game.siege.kit.kits.soldier"), + List.of( + new KitEquipment(Items.LEATHER_HELMET), + new KitEquipment(Items.DIAMOND_CHESTPLATE), + new KitEquipment(Items.LEATHER_LEGGINGS), + new KitEquipment(Items.IRON_BOOTS), + new KitEquipment(Items.IRON_SWORD, EquipmentSlot.MAINHAND), + new KitEquipment(Items.STONE_AXE, EquipmentSlot.OFFHAND) + ), + List.of(), + List.of() + ); + public static final SiegeKit SHIELD_BEARER = new SiegeKit( + Text.translatable("game.siege.kit.kits.shield_bearer"), + List.of( + new KitEquipment(Items.LEATHER_HELMET), + 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.SHIELD, EquipmentSlot.OFFHAND) + ), + List.of(), + List.of(new KitStatusEffect(StatusEffects.RESISTANCE)) + ); + public static final SiegeKit ARCHER = new SiegeKit( + Text.translatable("game.siege.kit.kits.archer"), + 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.OFFHAND), + new KitEquipment( + Items.BOW, + List.of(new EnchantmentLevelEntry(Enchantments.PUNCH, 1)), + EquipmentSlot.MAINHAND + ) + ), + List.of( + new KitResource( + Text.translatable("game.siege.kit.items.arrows"), + Items.ARROW, + SiegePersonalResource.WOOD, + 16 + ) + ), + List.of(new KitStatusEffect(StatusEffects.SPEED)) + ); + public static final SiegeKit CONSTRUCTOR = new SiegeKit( + Text.translatable("game.siege.kit.kits.constructor"), + 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.WOODEN_AXE, EquipmentSlot.MAINHAND), + new KitEquipment(Items.WOODEN_SWORD) + ), + List.of( + new KitResource( + Text.translatable("game.siege.kit.items.wood"), + Items.CHERRY_PLANKS, + Items.BIRCH_PLANKS, + SiegePersonalResource.WOOD, + EquipmentSlot.OFFHAND, + 12 + ) + ), + List.of() + ); + public static final SiegeKit DEMOLITIONER = new SiegeKit( + Text.translatable("game.siege.kit.kits.demolitioner"), + 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) + ), + List.of( + new KitResource( + Text.translatable("game.siege.kit.items.tnt"), + Items.TNT, + SiegePersonalResource.TNT, + EquipmentSlot.OFFHAND, + 2 + ) + ), + List.of(new KitStatusEffect(StatusEffects.GLOWING)) + ); - public final Text name; - - SiegeKit(Text name) { + public SiegeKit(Text name, List equipment, List resources, List statusEffects) { this.name = name; + this.equipment = equipment; + this.resources = Stream.concat(resources.stream(), Stream.of(KitResource.STEAK)).toList(); + this.statusEffects = statusEffects; + } + + private static Text restockMessage(List restockResults, long time, boolean isEquip) { + var text = Text.empty(); + + if (restockResults.stream().allMatch(RestockResult::success)) { + if (isEquip) { + return Text.empty(); + } + + text.append(Text.literal("Successfully restocked ").formatted(Formatting.DARK_GREEN)); + } else if (restockResults.stream().noneMatch(RestockResult::success)) { + text.append(Text.literal("Failed to restock ").formatted(Formatting.RED)); + } else { + text.append(Text.literal("Partially restocked ").formatted(Formatting.YELLOW)); + } + + boolean first = true; + var iter = restockResults.iterator(); + while (iter.hasNext()) { + var result = iter.next(); + if (!first) { + if (!iter.hasNext()) { + text.append(Text.literal(" and ")); + } else { + text.append(Text.literal(", ")); + } + } + first = false; + + var colour = result.success ? Formatting.DARK_GREEN : Formatting.RED; + + var restockingIn = result.current != 0 || result.resource == null ? "" : String.format(" (more in %ss)", result.resource.getNextRefreshSecs(time)); + + text.append(result.name.copy().formatted(colour)); + + if (!result.success) { + text.append(Text.literal(restockingIn).formatted(colour)); + } else if (result.max != 0) { + text.append(Text.literal(String.format(" (%d/%d left)", result.current, result.max)).formatted(colour)); + } + } + + return text; } public void equipArmourStand(SiegeKitStandEntity stand) { - switch (this) { - case ARCHER -> { - this.giveArcherEquipment(stand, stand.getTeam()); - stand.equipStack(EquipmentSlot.OFFHAND, new ItemStack(Items.WOODEN_SWORD)); - stand.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + var team = stand.getTeam(); + + for (var item : this.equipment) { + EquipmentSlot slot; + if (item.armourStandSlot != null) { + slot = item.armourStandSlot; + } else if (item.item instanceof Equipment equipmentItem) { + slot = equipmentItem.getSlotType(); + } else { + continue; } - case SOLDIER -> { - this.giveSoldierEquipment(stand, stand.getTeam()); - stand.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); - stand.equipStack(EquipmentSlot.OFFHAND, new ItemStack(Items.STONE_AXE)); + + stand.equipStack(slot, item.buildItemStack(team)); + } + + for (var item : this.resources) { + if (item.equipmentSlot != null) { + stand.equipStack(item.equipmentSlot, item.itemStackBuilder(team).build()); } - case CONSTRUCTOR -> { - this.giveConstructorEquipment(stand, stand.getTeam()); - stand.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.WOODEN_AXE)); + } + } + + public void returnResources(ServerPlayerEntity player, SiegePlayer participant) { + var inventory = player.getInventory(); + + for (var invList : List.of(inventory.main, inventory.offHand)) { + for (var stack : invList) { + for (var resource : this.resources) { + if (resource.resource == null) { + continue; + } + + if (resource.itemForTeam(participant.team) == stack.getItem()) { + participant.incrementResource(resource.resource, stack.getCount()); + break; + } + } } - case SHIELD_BEARER -> { - this.giveShieldEquipment(stand, stand.getTeam()); - stand.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD)); + } + + inventory.clear(); + } + + public void equipPlayer(ServerPlayerEntity player, SiegePlayer participant, SiegeConfig config, long time) { + var inventory = player.getInventory(); + var team = participant.team; + + for (var item : this.equipment) { + var stack = item.buildItemStack(team); + if (item.item instanceof Equipment equipmentItem) { + player.equipStack(equipmentItem.getSlotType(), stack); + } else { + inventory.offerOrDrop(stack); } - case DEMOLITIONER -> this.giveDemolitionerEquipment(stand, stand.getTeam()); + } + + var result = this.restock(player, participant, config, time, true); + if (!result.getSiblings().isEmpty()) { + player.sendMessage(result.copy().formatted(Formatting.BOLD), true); + } + + player.clearStatusEffects(); + for (var statusEffect : this.statusEffects) { + player.addStatusEffect(statusEffect.instance()); } } + // TODO teleporting GUI does not work private void maybeGiveEnderPearl(ServerPlayerEntity player, SiegePlayer participant, SiegeConfig config) { if (config.defenderEnderPearl() && participant.team == SiegeTeams.DEFENDERS && player.getInventory().count(Items.ENDER_PEARL) == 0) { player.getInventory().insertStack( @@ -75,160 +252,118 @@ private void maybeGiveEnderPearl(ServerPlayerEntity player, SiegePlayer particip } } - public @Nullable String restock(ServerPlayerEntity player, SiegePlayer participant, ServerWorld world, SiegeConfig config) { - var inventory = player.getInventory(); - - switch (participant.kit) { - case ARCHER -> { - int arrowsRequired = SiegeKit.ARROWS - inventory.count(Items.ARROW); - int arrowsToGive = participant.tryDecrementResource(SiegePersonalResource.WOOD, arrowsRequired); - inventory.offerOrDrop(ItemStackBuilder.of(Items.ARROW).setCount(arrowsToGive).build()); - if (arrowsRequired != 0 && arrowsToGive == 0) { - return "You have no more arrows right now!"; - } - } - case CONSTRUCTOR -> { - Item planks = SiegeTeams.planksForTeam(participant.team.key()); - int planksRequired = SiegeKit.PLANKS - inventory.count(planks); - int planksToGive = participant.tryDecrementResource(SiegePersonalResource.WOOD, planksRequired); - if (planksRequired == SiegeKit.PLANKS) { - player.equipStack(EquipmentSlot.OFFHAND, ItemStackBuilder.of(planks).setCount(planksToGive).build()); - } else { - inventory.offerOrDrop(ItemStackBuilder.of(planks).setCount(planksToGive).build()); - } - } - case DEMOLITIONER -> { - int tntRequired = SiegeKit.TNT - inventory.count(Items.TNT); - int tntToGive = participant.tryDecrementResource(SiegePersonalResource.TNT, tntRequired); - inventory.offerOrDrop(ItemStackBuilder.of(Items.TNT).setCount(tntToGive).build()); - if (tntRequired != 0 && tntToGive == 0) { - return "You have no TNT right now!"; - } - } - default -> { - } - } - - int steakRequired = SiegeKit.STEAK - inventory.count(Items.COOKED_BEEF); - inventory.offerOrDrop(ItemStackBuilder.of(Items.COOKED_BEEF).setCount(steakRequired).build()); + public Text restock(ServerPlayerEntity player, SiegePlayer participant, SiegeConfig config, long time) { + return this.restock(player, participant, config, time, false); + } + private Text restock(ServerPlayerEntity player, SiegePlayer participant, SiegeConfig config, long time, boolean isEquip) { this.maybeGiveEnderPearl(player, participant, config); - return null; + var results = this.resources + .stream() + .map(resource -> resource.restock(player, participant)) + .toList(); + + return restockMessage(results, time, isEquip); } - public void equipPlayer(ServerPlayerEntity player, SiegePlayer participant, ServerWorld world, SiegeConfig config) { - var inventory = player.getInventory(); - int wood = inventory.count(Items.ARROW) + inventory.count(SiegeTeams.planksForTeam(SiegeTeams.ATTACKERS.key())) - + inventory.count(SiegeTeams.planksForTeam(SiegeTeams.DEFENDERS.key())); - int tnt = inventory.count(Items.TNT); - participant.incrementResource(SiegePersonalResource.WOOD, wood); - participant.incrementResource(SiegePersonalResource.TNT, tnt); + public record KitEquipment(Item item, List enchantments, + @Nullable EquipmentSlot armourStandSlot) { + public KitEquipment(Item item) { + this(item, List.of(), null); + } - inventory.clear(); - player.clearStatusEffects(); - GameTeam team = participant.team; + public KitEquipment(Item item, EquipmentSlot armourStandSlot) { + this(item, List.of(), armourStandSlot); + } - switch (this) { - case ARCHER -> { - this.giveArcherKit(player, participant); - player.addStatusEffect(new StatusEffectInstance(StatusEffects.SPEED, -1, 0, false, false, true)); - } - case SOLDIER -> this.giveSoldierKit(player, team); - case CONSTRUCTOR -> this.giveConstructorKit(player, participant); - case SHIELD_BEARER -> { - this.giveShieldKit(player, team); - player.addStatusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE, -1, 0, false, false, true)); - } - case DEMOLITIONER -> { - this.giveDemolitionerKit(player, team); - player.addStatusEffect(new StatusEffectInstance(StatusEffects.GLOWING, -1, 0, false, false, true)); + public ItemStack buildItemStack(GameTeam team) { + var builder = ItemStackBuilder + .of(this.item) + .setCount(1) + .setUnbreakable() + .setDyeColor(team.config().dyeColor().getRgb()); + + for (var enchantment : this.enchantments) { + builder.addEnchantment(enchantment.enchantment, enchantment.level); } + + return builder.build(); } + } - inventory.insertStack(ItemStackBuilder.of(Items.COOKED_BEEF).setCount(STEAK).build()); + /** + * A restockable item in a kit (e.g. arrows) + */ + public record KitResource(Text name, Item attackerItem, Item defenderItem, @Nullable SiegePersonalResource resource, + @Nullable EquipmentSlot equipmentSlot, int max) { + public static final KitResource STEAK = new KitResource(Items.COOKED_BEEF.getName(), Items.COOKED_BEEF, null, null, 10); - this.maybeGiveEnderPearl(player, participant, config); - this.restock(player, participant, world, config); - } + public KitResource(Text name, Item item, @Nullable SiegePersonalResource resource, EquipmentSlot equipmentSlot, int max) { + this(name, item, item, resource, equipmentSlot, max); + } - // give{x}Equipment is for armour stands and players + public KitResource(Text name, Item item, @Nullable SiegePersonalResource resource, int max) { + this(name, item, item, resource, null, max); + } - private void giveArcherEquipment(LivingEntity entity, GameTeam team) { - entity.equipStack(EquipmentSlot.HEAD, this.dyedArmor(team, Items.LEATHER_HELMET)); - entity.equipStack(EquipmentSlot.CHEST, this.dyedArmor(team, Items.LEATHER_CHESTPLATE)); - entity.equipStack(EquipmentSlot.LEGS, this.dyedArmor(team, Items.LEATHER_LEGGINGS)); - entity.equipStack(EquipmentSlot.FEET, this.dyedArmor(team, Items.LEATHER_BOOTS)); - } + public ItemStackBuilder itemStackBuilder(GameTeam team) { + return ItemStackBuilder + .of(this.itemForTeam(team)) + .setUnbreakable() + .setDyeColor(team.config().dyeColor().getRgb()); + } - private void giveSoldierEquipment(LivingEntity entity, GameTeam team) { - entity.equipStack(EquipmentSlot.HEAD, this.dyedArmor(team, Items.LEATHER_HELMET)); - entity.equipStack(EquipmentSlot.CHEST, ItemStackBuilder.of(Items.DIAMOND_CHESTPLATE).setUnbreakable().build()); - entity.equipStack(EquipmentSlot.LEGS, this.dyedArmor(team, Items.LEATHER_LEGGINGS)); - entity.equipStack(EquipmentSlot.FEET, ItemStackBuilder.of(Items.IRON_BOOTS).setUnbreakable().build()); - } + public Item itemForTeam(GameTeam team) { + return team == SiegeTeams.DEFENDERS ? this.defenderItem : this.attackerItem; + } - private void giveConstructorEquipment(LivingEntity entity, GameTeam team) { - entity.equipStack(EquipmentSlot.HEAD, this.dyedArmor(team, Items.LEATHER_HELMET)); - entity.equipStack(EquipmentSlot.CHEST, ItemStackBuilder.of(Items.IRON_CHESTPLATE).setUnbreakable().build()); - entity.equipStack(EquipmentSlot.LEGS, this.dyedArmor(team, Items.LEATHER_LEGGINGS)); - entity.equipStack(EquipmentSlot.FEET, this.dyedArmor(team, Items.LEATHER_BOOTS)); - entity.equipStack(EquipmentSlot.OFFHAND, new ItemStack(SiegeTeams.planksForTeam(team.key()))); - } + RestockResult restock(ServerPlayerEntity player, SiegePlayer participant) { + var inventory = player.getInventory(); + var team = participant.team; + var item = this.itemForTeam(team); - private void giveShieldEquipment(LivingEntity entity, GameTeam team) { - entity.equipStack(EquipmentSlot.HEAD, this.dyedArmor(team, Items.LEATHER_HELMET)); - entity.equipStack(EquipmentSlot.CHEST, ItemStackBuilder.of(Items.IRON_CHESTPLATE).setUnbreakable().build()); - entity.equipStack(EquipmentSlot.LEGS, ItemStackBuilder.of(Items.IRON_LEGGINGS).setUnbreakable().build()); - entity.equipStack(EquipmentSlot.FEET, this.dyedArmor(team, Items.LEATHER_BOOTS)); - entity.equipStack(EquipmentSlot.OFFHAND, ItemStackBuilder.of(Items.SHIELD).setUnbreakable().build()); - } + int required = this.max - inventory.count(item); + int toGive = this.resource != null ? participant.tryDecrementResource(this.resource, required) : required; - private void giveDemolitionerEquipment(LivingEntity entity, GameTeam team) { - entity.equipStack(EquipmentSlot.HEAD, this.dyedArmor(team, Items.LEATHER_HELMET)); - entity.equipStack(EquipmentSlot.CHEST, this.dyedArmor(team, Items.LEATHER_CHESTPLATE)); - entity.equipStack(EquipmentSlot.LEGS, this.dyedArmor(team, Items.LEATHER_LEGGINGS)); - entity.equipStack(EquipmentSlot.FEET, this.dyedArmor(team, Items.LEATHER_BOOTS)); - entity.equipStack(EquipmentSlot.MAINHAND, ItemStackBuilder.of(Items.TNT).setUnbreakable().build()); - } + var stack = this.itemStackBuilder(team).setCount(toGive).build(); - private ItemStack dyedArmor(GameTeam team, Item leatherHelmet) { - return ItemStackBuilder.of(leatherHelmet).setDyeColor(team.config().dyeColor().getRgb()).setUnbreakable().build(); - } + if (this.equipmentSlot != null && required == this.max) { + player.equipStack(this.equipmentSlot, stack); + } else { + inventory.offerOrDrop(stack); + } - // give{x}Kit is for players only - - private void giveArcherKit(PlayerEntity player, SiegePlayer participant) { - this.giveArcherEquipment(player, participant.team); - player.getInventory().insertStack(ItemStackBuilder.of(Items.WOODEN_SWORD).setUnbreakable().build()); - player.getInventory().insertStack( - ItemStackBuilder.of(Items.BOW) - .addEnchantment(Enchantments.PUNCH, 1) - .setUnbreakable() - .build() - ); + if (this.resource != null) { + return new RestockResult(participant.getResourceAmount(this.resource), this.resource.max, required == 0 || toGive > 0, this.resource, this.name); + } else { + return new RestockResult(0, 0, true, null, this.name); + } + } } - private void giveSoldierKit(PlayerEntity player, GameTeam team) { - this.giveSoldierEquipment(player, team); - player.getInventory().insertStack(ItemStackBuilder.of(Items.IRON_SWORD).setUnbreakable().build()); - player.getInventory().insertStack(ItemStackBuilder.of(Items.STONE_AXE).setUnbreakable().build()); + /** + * The result of trying to restock a kit. + * + * @param current The current amount of {@link SiegePersonalResource} that the player has + * @param max The max amount of {@link SiegePersonalResource} that the player can have + * @param success Whether the restocking was successful + * @param resource The {@link SiegePersonalResource} that the player tried to restock + * @param name The name of the item that they tried to restock. This isn't the same as the + * {@link SiegePersonalResource}'s name, because arrows and wood both restock from the same pool, for + * example. + */ + private record RestockResult(int current, int max, boolean success, SiegePersonalResource resource, Text name) { } - private void giveConstructorKit(PlayerEntity player, SiegePlayer participant) { - this.giveConstructorEquipment(player, participant.team); - player.getInventory().insertStack(ItemStackBuilder.of(Items.WOODEN_SWORD).setUnbreakable().build()); - player.getInventory().insertStack(ItemStackBuilder.of(Items.WOODEN_AXE).setUnbreakable().build()); - player.equipStack(EquipmentSlot.OFFHAND, new ItemStack(Items.AIR)); - } + public record KitStatusEffect(StatusEffect statusEffect, int amplifier) { + public KitStatusEffect(StatusEffect statusEffect) { + this(statusEffect, 0); + } - private void giveShieldKit(PlayerEntity player, GameTeam team) { - this.giveShieldEquipment(player, team); - player.getInventory().insertStack(ItemStackBuilder.of(Items.STONE_SWORD).setUnbreakable().build()); + public StatusEffectInstance instance() { + return new StatusEffectInstance(this.statusEffect, -1, this.amplifier, false, false, true); + } } - private void giveDemolitionerKit(PlayerEntity player, GameTeam team) { - this.giveDemolitionerEquipment(player, team); - player.equipStack(EquipmentSlot.MAINHAND, new ItemStack(Items.AIR)); - } } diff --git a/src/main/java/io/github/restioson/siege/game/SiegeWaiting.java b/src/main/java/io/github/restioson/siege/game/SiegeWaiting.java index dec8189..d8ac313 100644 --- a/src/main/java/io/github/restioson/siege/game/SiegeWaiting.java +++ b/src/main/java/io/github/restioson/siege/game/SiegeWaiting.java @@ -42,8 +42,7 @@ private SiegeWaiting(ServerWorld world, GameSpace gameSpace, SiegeMap map, Siege public static GameOpenProcedure open(GameOpenContext context) { var config = context.config(); - SiegeMapLoader generator = new SiegeMapLoader(config.map()); - SiegeMap map = generator.create(context.server()); + SiegeMap map = SiegeMapLoader.load(context.server(), config.map()); RuntimeWorldConfig worldConfig = new RuntimeWorldConfig() .setGenerator(map.asGenerator(context.server())) diff --git a/src/main/java/io/github/restioson/siege/game/active/CapturingState.java b/src/main/java/io/github/restioson/siege/game/active/CapturingState.java index eca1370..02b0b95 100644 --- a/src/main/java/io/github/restioson/siege/game/active/CapturingState.java +++ b/src/main/java/io/github/restioson/siege/game/active/CapturingState.java @@ -5,6 +5,7 @@ public enum CapturingState { CAPTURING(Text.literal("Capturing..").formatted(Formatting.GOLD)), + RECAPTURE_DISABLED(Text.literal("Recapture is disabled for this game!").formatted(Formatting.RED)), CONTESTED(Text.literal("Contested!").formatted(Formatting.GRAY)), SECURING(Text.literal("Securing..").formatted(Formatting.AQUA)), PREREQUISITE_REQUIRED(Text.literal("Cannot capture yet!").formatted(Formatting.RED)); 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 5813373..dbbc08f 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 @@ -2,7 +2,6 @@ import com.google.common.collect.Multimap; import eu.pb4.sgui.api.gui.SimpleGui; -import io.github.restioson.siege.entity.SiegeKitStandEntity; import io.github.restioson.siege.game.SiegeConfig; import io.github.restioson.siege.game.SiegeKit; import io.github.restioson.siege.game.SiegeSpawnLogic; @@ -75,7 +74,7 @@ public class SiegeActive { public final ServerWorld world; public final GameSpace gameSpace; - final SiegeMap map; + private static final int RESPAWN_DELAY_TICKS = 5 * 20; final SiegeTeams teams; @@ -87,8 +86,8 @@ public class SiegeActive { final SiegeCaptureLogic captureLogic; final SiegeGateLogic gateLogic; - - public static int TNT_GATE_DAMAGE = 15; + public static int TNT_GATE_DAMAGE = 10; + public SiegeMap map; private SiegeActive(ServerWorld world, GameActivity activity, SiegeMap map, SiegeConfig config, GlobalWidgets widgets, Multimap players) { this.world = world; @@ -151,14 +150,7 @@ public static void open(ServerWorld world, GameSpace gameSpace, SiegeMap map, Si activity.listen(PlayerAttackEntityEvent.EVENT, active::onAttackEntity); activity.listen(ProjectileHitEvent.ENTITY, active::onProjectileHitEntity); - for (SiegeKitStandLocation stand : active.map.kitStands) { - SiegeKitStandEntity standEntity = new SiegeKitStandEntity(world, active, stand); - world.spawnEntity(standEntity); - - if (standEntity.controllingFlag != null) { - standEntity.controllingFlag.kitStands.add(standEntity); - } - } + active.map.spawnKitStands(active); }); } @@ -169,6 +161,11 @@ private void onExplosion(Explosion explosion, boolean particles) { if (!gate.bashedOpen && gate.health > 0 && gate.portcullis.contains(pos)) { gate.health = Math.max(0, gate.health - TNT_GATE_DAMAGE); gate.timeOfLastBash = this.world.getTime(); + + if (explosion.getCausingEntity() instanceof ServerPlayerEntity player) { + gate.broadcastHealth(player, this, this.world); + } + break gate; } } @@ -240,6 +237,12 @@ private void onOpen() { }); } + if (SiegeMapLoader.loadRemote()) { + var hint = Text.literal("[Siege] Loaded map from build server").formatted(Formatting.AQUA); + this.gameSpace.getPlayers().sendMessage(hint); + } + + this.stageManager.onOpen(this.world.getTime(), this.config); } @@ -327,25 +330,21 @@ private ActionResult onUseBlock(ServerPlayerEntity player, Hand hand, BlockHitRe pos.offset(hitResult.getSide()); BlockState state = this.world.getBlockState(pos); if (state.getBlock() instanceof EnderChestBlock) { - String error = participant.kit.restock(player, participant, this.world, this.config); - - if (error == null) { - player.sendMessage(Text.literal("Items restocked!").formatted(Formatting.DARK_GREEN, Formatting.BOLD), true); - } else { - player.sendMessage(Text.literal(error).formatted(Formatting.RED, Formatting.BOLD), true); - } + MutableText result = participant.kit.restock(player, participant, this.config, this.world.getTime()).copy(); + player.sendMessage(result.formatted(Formatting.BOLD), true); return ActionResult.FAIL; } else if (state.getBlock() instanceof DoorBlock) { return ActionResult.PASS; } else if (state.getBlock() instanceof BlockWithEntity) { return ActionResult.FAIL; - } else if (inHand == Items.STONE_AXE || inHand == Items.IRON_SWORD) { + } else if (SiegeGateLogic.canUseToBash(inHand)) { if (this.gateLogic.maybeBash(pos, player, participant, this.world.getTime()) == ActionResult.FAIL) { return ActionResult.FAIL; } } - if (inHand == Items.WOODEN_AXE || inHand == Items.STONE_AXE) { + // Disable log stripping + if (inHand instanceof AxeItem) { return ActionResult.FAIL; } @@ -387,7 +386,7 @@ private ActionResult onPlayerDamage(ServerPlayerEntity player, DamageSource sour SiegePlayer participant = this.participant(player); long time = this.world.getTime(); - if (participant != null && this.world.getTime() < participant.timeOfSpawn + 5 * 20 && !participant.attackedThisLife) { + if (participant != null && this.world.getTime() < participant.timeOfSpawn + RESPAWN_DELAY_TICKS && !participant.attackedThisLife) { return ActionResult.FAIL; } @@ -491,8 +490,9 @@ private void completeParticipantSpawn(ServerPlayerEntity player) { SiegePlayer participant = this.participant(player); assert participant != null; // spawnParticipant should only be spawned on a participant - participant.timeOfSpawn = this.world.getTime(); - participant.kit.equipPlayer(player, participant, this.world, this.config); + var time = this.world.getTime(); + participant.timeOfSpawn = time; + participant.kit.equipPlayer(player, participant, this.config, time); } private SiegeSpawnResult getSpawnFor(ServerPlayerEntity player, long time) { @@ -539,10 +539,7 @@ private void tick() { this.sidebar.update(time); this.tickWarpingPlayers(); - - if (time % (20 * 2) == 0) { - this.tickResources(time); - } + this.tickResources(time); } this.tickDead(this.world, time); @@ -582,11 +579,11 @@ private void tickWarpingPlayers() { } private void tickResources(long time) { - for (SiegePlayer player : this.participants.values()) { - player.incrementResource(SiegePersonalResource.WOOD, 1); - - if (time % (60 * 20) == 0) { - player.incrementResource(SiegePersonalResource.TNT, 1); + for (SiegePersonalResource resource : SiegePersonalResource.values()) { + if (time % (resource.refreshSecs * 20L) == 0) { + for (SiegePlayer player : this.participants.values()) { + player.incrementResource(resource, 1); + } } } } @@ -604,7 +601,7 @@ private void tickDead(ServerWorld world, long time) { p.sendMessage(text, true); } - if (time - state.timeOfDeath > 5 * 20) { + if (time - state.timeOfDeath > RESPAWN_DELAY_TICKS) { this.spawnParticipant(p, null); } } @@ -684,18 +681,10 @@ private void broadcastWin(GameTeam winningTeam) { Formatting colour = Formatting.GOLD; - mostKills.ifPresent(p -> { - players.sendMessage(Text.literal(String.format("Most kills - %s with %d", p.name, (int) p.score)).formatted(colour)); - }); - highestKd.ifPresent(p -> { - players.sendMessage(Text.literal(String.format("Highest KD - %s with %.2f", p.name, p.score)).formatted(colour)); - }); - mostCaptures.ifPresent(p -> { - players.sendMessage(Text.literal(String.format("Most captures - %s with %d", p.name, (int) p.score)).formatted(colour)); - }); - mostSecures.ifPresent(p -> { - players.sendMessage(Text.literal(String.format("Most secures - %s with %d", p.name, (int) p.score)).formatted(colour)); - }); + mostKills.ifPresent(p -> players.sendMessage(Text.literal(String.format("Most kills - %s with %d", p.name, (int) p.score)).formatted(colour))); + highestKd.ifPresent(p -> players.sendMessage(Text.literal(String.format("Highest KD - %s with %.2f", p.name, p.score)).formatted(colour))); + mostCaptures.ifPresent(p -> players.sendMessage(Text.literal(String.format("Most captures - %s with %d", p.name, (int) p.score)).formatted(colour))); + mostSecures.ifPresent(p -> players.sendMessage(Text.literal(String.format("Most secures - %s with %d", p.name, (int) p.score)).formatted(colour))); int attacker_kills = 0; int defender_kills = 0; 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 1d1b9ab..7cab4f5 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 @@ -8,7 +8,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -import net.minecraft.block.*; import net.minecraft.entity.EntityType; import net.minecraft.entity.LightningEntity; import net.minecraft.screen.ScreenTexts; @@ -18,10 +17,8 @@ import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameMode; -import xyz.nucleoid.map_templates.BlockBounds; import xyz.nucleoid.plasmid.game.GameSpace; import xyz.nucleoid.plasmid.game.common.team.GameTeam; import xyz.nucleoid.plasmid.util.PlayerRef; @@ -93,17 +90,19 @@ private void tickCaptureFlag(ServerWorld world, SiegeFlag flag, int interval) { playersPresent.addAll(defendersPresent); boolean recapture = this.game.config.recapture(); + + boolean attackersAtFlag = !attackersPresent.isEmpty(); boolean defendersAtFlag = !defendersPresent.isEmpty(); - boolean defendersActuallyCapturing = defendersAtFlag && recapture; - boolean attackersCapturing = !attackersPresent.isEmpty(); - boolean contested = defendersAtFlag && attackersCapturing && !(flag.team == SiegeTeams.ATTACKERS && recapture); - boolean capturing = defendersActuallyCapturing || attackersCapturing; + boolean defendersCapturing = defendersAtFlag && recapture && flag.team != SiegeTeams.DEFENDERS; + boolean attackersCapturing = attackersAtFlag && flag.team != SiegeTeams.ATTACKERS; + boolean tryingToCapture = defendersCapturing || attackersCapturing; + boolean contested = tryingToCapture && defendersAtFlag && attackersAtFlag; CapturingState capturingState = null; GameTeam captureTeam = flag.team; List capturingPlayers = ImmutableList.of(); - if (capturing) { + if (tryingToCapture) { if (!contested) { if (defendersAtFlag) { captureTeam = SiegeTeams.DEFENDERS; @@ -113,7 +112,7 @@ private void tickCaptureFlag(ServerWorld world, SiegeFlag flag, int interval) { capturingPlayers = attackersPresent; } - capturingState = captureTeam != flag.team ? CapturingState.CAPTURING : null; + capturingState = CapturingState.CAPTURING; } else { capturingState = CapturingState.CONTESTED; } @@ -123,7 +122,9 @@ private void tickCaptureFlag(ServerWorld world, SiegeFlag flag, int interval) { } } - if (capturingState != null && !flag.isReadyForCapture()) { + if (defendersAtFlag && flag.team != SiegeTeams.DEFENDERS) { + capturingState = CapturingState.RECAPTURE_DISABLED; + } else if (capturingState != null && !flag.isReadyForCapture()) { capturingState = CapturingState.PREREQUISITE_REQUIRED; } @@ -158,65 +159,7 @@ private void tickCapturing(SiegeFlag flag, int interval, GameTeam captureTeam, L } this.broadcastCaptured(flag, captureTeam); - - ServerWorld world = this.game.world; - - for (BlockBounds blockBounds : flag.flagIndicatorBlocks) { - for (BlockPos blockPos : blockBounds) { - BlockState blockState = world.getBlockState(blockPos); - Block block = blockState.getBlock(); - if (block == Blocks.BLUE_WOOL || block == Blocks.RED_WOOL) { - Block wool; - - if (captureTeam == SiegeTeams.DEFENDERS) { - wool = Blocks.BLUE_WOOL; - } else { - wool = Blocks.RED_WOOL; - } - - world.setBlockState(blockPos, wool.getDefaultState()); - } - - if (block == Blocks.BLUE_WALL_BANNER || block == Blocks.RED_WALL_BANNER) { - Block banner; - - if (captureTeam == SiegeTeams.DEFENDERS) { - banner = Blocks.BLUE_WALL_BANNER; - } else { - banner = Blocks.RED_WALL_BANNER; - } - - BlockState newBlockState = banner.getDefaultState().with(WallBannerBlock.FACING, blockState.get(WallBannerBlock.FACING)); - world.setBlockState(blockPos, newBlockState); - } - - if (block == Blocks.BLUE_BANNER || block == Blocks.RED_BANNER) { - Block banner; - - if (captureTeam == SiegeTeams.DEFENDERS) { - banner = Blocks.BLUE_BANNER; - } else { - banner = Blocks.RED_BANNER; - } - - BlockState newBlockState = banner.getDefaultState().with(BannerBlock.ROTATION, blockState.get(BannerBlock.ROTATION)); - world.setBlockState(blockPos, newBlockState); - } - - if (block == Blocks.BLUE_CONCRETE || block == Blocks.RED_CONCRETE) { - Block concrete; - - if (captureTeam == SiegeTeams.DEFENDERS) { - concrete = Blocks.BLUE_CONCRETE; - } else { - concrete = Blocks.RED_CONCRETE; - } - - BlockState newBlockState = concrete.getDefaultState(); - world.setBlockState(blockPos, newBlockState); - } - } - } + flag.setTeamBlocks(this.game.world, captureTeam); for (ServerPlayerEntity player : capturingPlayers) { player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.NEUTRAL, 1.0F, 1.0F); @@ -242,12 +185,16 @@ private void tickSecuring(SiegeFlag flag, int interval, List } private void broadcastStartCapture(SiegeFlag flag, GameTeam captureTeam) { + var capture = captureTeam == SiegeTeams.ATTACKERS ? "captured" : "recaptured"; + this.gameSpace.getPlayers().sendMessage( Text.literal("The ") .append(Text.literal(flag.name).formatted(Formatting.YELLOW)) .append(ScreenTexts.SPACE) .append(flag.presentTobe()) - .append(" being captured by the ") + .append(" being ") + .append(capture) + .append(" by the ") .append(captureTeam.config().name()) .append("...") .formatted(Formatting.BOLD) 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 b70f306..9433f19 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 @@ -7,9 +7,10 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import net.minecraft.SharedConstants; import net.minecraft.block.Blocks; +import net.minecraft.item.AxeItem; import net.minecraft.item.Item; import net.minecraft.item.ItemUsageContext; -import net.minecraft.item.Items; +import net.minecraft.item.SwordItem; import net.minecraft.screen.ScreenTexts; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; @@ -25,6 +26,8 @@ import xyz.nucleoid.plasmid.game.common.team.GameTeam; import xyz.nucleoid.plasmid.util.PlayerRef; +import java.util.List; + public class SiegeGateLogic { private final SiegeActive game; @@ -58,31 +61,44 @@ public ActionResult maybeBraceGate(BlockPos pos, ServerPlayerEntity player, Item return ActionResult.PASS; } + public static boolean canUseToBash(Item item) { + return item instanceof SwordItem || item instanceof AxeItem; + } + public ActionResult maybeBash(BlockPos pos, ServerPlayerEntity player, SiegePlayer participant, long time) { Item mainHandItem = player.getInventory().getMainHandStack().getItem(); - boolean holdingBashWeapon = mainHandItem == Items.IRON_SWORD || mainHandItem == Items.STONE_AXE; boolean rightKit = participant.kit == SiegeKit.SHIELD_BEARER || participant.kit == SiegeKit.SOLDIER; for (SiegeGate gate : this.game.map.gates) { if (!gate.bashedOpen && gate.health > 0 && gate.portcullis.contains(pos)) { + var cooldownMgr = player.getItemCooldownManager(); + if (participant.team == gate.flag.team) { player.sendMessage(Text.literal("You cannot bash your own gate!").formatted(Formatting.RED), true); return ActionResult.FAIL; } else if (!rightKit) { player.sendMessage(Text.literal("Only soldiers and shieldbearers can bash!").formatted(Formatting.RED), true); return ActionResult.FAIL; - } else if (!holdingBashWeapon) { + } else if (!canUseToBash(mainHandItem)) { player.sendMessage(Text.literal("You can only bash with a sword or axe!").formatted(Formatting.RED), true); return ActionResult.FAIL; } else if (!player.isSprinting()) { player.sendMessage(Text.literal("You must be sprinting to bash!").formatted(Formatting.RED), true); return ActionResult.FAIL; - } else if (player.getItemCooldownManager().isCoolingDown(mainHandItem)) { + } else if (cooldownMgr.isCoolingDown(mainHandItem)) { return ActionResult.FAIL; } - player.getItemCooldownManager().set(Items.IRON_SWORD, SharedConstants.TICKS_PER_SECOND); - player.getItemCooldownManager().set(Items.STONE_AXE, SharedConstants.TICKS_PER_SECOND); + var inventory = player.getInventory(); + for (var invList : List.of(inventory.main, inventory.offHand)) { + for (var stack : invList) { + var item = stack.getItem(); + if (canUseToBash(item)) { + cooldownMgr.set(item, SharedConstants.TICKS_PER_SECOND); + } + } + } + ServerWorld world = this.game.world; world.createExplosion(null, pos.getX(), pos.getY(), pos.getZ(), 0.0f, World.ExplosionSourceType.NONE); gate.health -= 1; 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 5d75ad3..d3b9f75 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 @@ -1,12 +1,18 @@ package io.github.restioson.siege.game.active; public enum SiegePersonalResource { - WOOD(64), - TNT(2); + WOOD(64, 2), + TNT(4, 30); public final int max; + public final int refreshSecs; - SiegePersonalResource(int max) { + SiegePersonalResource(int max, int refreshSecs) { this.max = max; + this.refreshSecs = refreshSecs; + } + + public int getNextRefreshSecs(long time) { + return this.refreshSecs - (int) ((time % (this.refreshSecs * 20L)) / 20); } } 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 6a33fef..a4c522d 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 @@ -43,11 +43,15 @@ public ServerPlayerEntity attacker(long time, ServerWorld world) { public void incrementResource(SiegePersonalResource resource, int amount) { int newAmount = this.resources.addTo(resource, amount); - if (newAmount > resource.max) { + if (newAmount >= resource.max) { this.resources.replace(resource, resource.max); } } + public int getResourceAmount(SiegePersonalResource resource) { + return this.resources.getInt(resource); + } + // Try decrement an amount, returning how much was actually decremented public int tryDecrementResource(SiegePersonalResource resource, int amount) { int newAmount = this.resources.addTo(resource, -amount) - amount; // addTo returns previous value 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 7da88be..87e8685 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 @@ -68,7 +68,7 @@ public void update(long time) { Text line; if (capturing || percent > 0) { line = Text.literal("(" + percent + "%) ").append(flagName); - } else if (flag.gate != null && time - flag.gate.timeOfLastBash < 5 * 20) { + } else if (flag.gateUnderAttack(time)) { line = Text.literal("(!) ").append(flagName); } else { line = flagName; diff --git a/src/main/java/io/github/restioson/siege/game/map/GateSlider.java b/src/main/java/io/github/restioson/siege/game/map/GateSlider.java index 4402946..d974f41 100644 --- a/src/main/java/io/github/restioson/siege/game/map/GateSlider.java +++ b/src/main/java/io/github/restioson/siege/game/map/GateSlider.java @@ -2,6 +2,8 @@ import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.block.FenceBlock; +import net.minecraft.block.HorizontalConnectingBlock; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -82,6 +84,24 @@ public void set(ServerWorld world, int offset) { Slice slice = this.getSlice(world, y - offset); slice.applyAt(world, min, y); } + + for (BlockPos pos : this.bounds) { + var state = world.getBlockState(pos); + if (state.getBlock() instanceof FenceBlock fence) { + for (var entry : HorizontalConnectingBlock.FACING_PROPERTIES.entrySet()) { + var dir = entry.getKey(); + var prop = entry.getValue(); + var neighbourPos = pos.offset(dir, 1); + var neighbourBlock = world.getBlockState(neighbourPos); + + boolean neighbourSideConnectable = neighbourBlock.isSideSolidFullSquare(world, neighbourPos, dir.getOpposite()); + boolean connects = fence.canConnect(neighbourBlock, neighbourSideConnectable, dir.getOpposite()); + + state = state.with(prop, connects); + world.setBlockState(pos, state); + } + } + } } public int getMaxOffset() { 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 5e79de9..fbf0312 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 @@ -5,11 +5,14 @@ import io.github.restioson.siege.game.active.CapturingState; import io.github.restioson.siege.game.active.SiegeCaptureLogic; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.minecraft.block.*; import net.minecraft.entity.boss.BossBar; import net.minecraft.entity.boss.ServerBossBar; import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.map_templates.BlockBounds; import xyz.nucleoid.plasmid.game.common.team.GameTeam; @@ -36,8 +39,7 @@ public final class SiegeFlag { @Nullable public SiegeSpawn defenderRespawn; - @Nullable - public SiegeGate gate; + public List gates = new ArrayList<>(); public GameTeam team; @@ -172,6 +174,78 @@ public void closeCaptureBar() { public boolean isFrontLine(long time) { CapturingState state = this.capturingState; return (state != null && state.hasAlert() && state != CapturingState.SECURING) - || (this.gate != null && time - this.gate.timeOfLastBash < 5 * 20); + || (this.gateUnderAttack(time)); + } + + public boolean gateUnderAttack(long time) { + long lastBash = this.gates + .stream() + .map(gate -> gate.timeOfLastBash) + .max(Long::compareTo) + .stream() + .findAny() + .orElse(0L); + + return time - lastBash < 5 * 20; + } + + public void setTeamBlocks(ServerWorld world, GameTeam captureTeam) { + for (BlockBounds blockBounds : this.flagIndicatorBlocks) { + for (BlockPos blockPos : blockBounds) { + BlockState blockState = world.getBlockState(blockPos); + Block block = blockState.getBlock(); + if (block == Blocks.BLUE_WOOL || block == Blocks.RED_WOOL) { + Block wool; + + if (captureTeam == SiegeTeams.DEFENDERS) { + wool = Blocks.BLUE_WOOL; + } else { + wool = Blocks.RED_WOOL; + } + + world.setBlockState(blockPos, wool.getDefaultState()); + } + + if (block == Blocks.BLUE_WALL_BANNER || block == Blocks.RED_WALL_BANNER) { + Block banner; + + if (captureTeam == SiegeTeams.DEFENDERS) { + banner = Blocks.BLUE_WALL_BANNER; + } else { + banner = Blocks.RED_WALL_BANNER; + } + + BlockState newBlockState = banner.getDefaultState().with(WallBannerBlock.FACING, blockState.get(WallBannerBlock.FACING)); + world.setBlockState(blockPos, newBlockState); + } + + if (block == Blocks.BLUE_BANNER || block == Blocks.RED_BANNER) { + Block banner; + + if (captureTeam == SiegeTeams.DEFENDERS) { + banner = Blocks.BLUE_BANNER; + } else { + banner = Blocks.RED_BANNER; + } + + BlockState newBlockState = banner.getDefaultState().with(BannerBlock.ROTATION, blockState.get(BannerBlock.ROTATION)); + world.setBlockState(blockPos, newBlockState); + } + + if (block == Blocks.BLUE_CONCRETE || block == Blocks.RED_CONCRETE) { + Block concrete; + + if (captureTeam == SiegeTeams.DEFENDERS) { + concrete = Blocks.BLUE_CONCRETE; + } else { + concrete = Blocks.RED_CONCRETE; + } + + BlockState newBlockState = concrete.getDefaultState(); + world.setBlockState(blockPos, newBlockState); + } + } + + } } } diff --git a/src/main/java/io/github/restioson/siege/game/map/SiegeGate.java b/src/main/java/io/github/restioson/siege/game/map/SiegeGate.java index 0dd41de..a01426c 100644 --- a/src/main/java/io/github/restioson/siege/game/map/SiegeGate.java +++ b/src/main/java/io/github/restioson/siege/game/map/SiegeGate.java @@ -10,6 +10,7 @@ import xyz.nucleoid.plasmid.util.PlayerRef; public class SiegeGate { + public final String id; public final SiegeFlag flag; public final BlockBounds gateOpen; public final BlockBounds portcullis; @@ -20,13 +21,14 @@ public class SiegeGate { public int health; public int repairedHealthThreshold; public int maxHealth; - private int openSlide; + public int openSlide; public long timeOfLastBash; @Nullable public BlockBounds brace; - public SiegeGate(SiegeFlag flag, BlockBounds gateOpen, BlockBounds portcullis, @Nullable BlockBounds brace, int retractHeight, int repairedHealthThreshold, int maxHealth) { + public SiegeGate(String id, SiegeFlag flag, BlockBounds gateOpen, BlockBounds portcullis, @Nullable BlockBounds brace, int retractHeight, int repairedHealthThreshold, int maxHealth) { + this.id = id; this.flag = flag; this.gateOpen = gateOpen; this.portcullis = portcullis; diff --git a/src/main/java/io/github/restioson/siege/game/map/SiegeKitStandLocation.java b/src/main/java/io/github/restioson/siege/game/map/SiegeKitStandLocation.java index 6082c60..229a464 100644 --- a/src/main/java/io/github/restioson/siege/game/map/SiegeKitStandLocation.java +++ b/src/main/java/io/github/restioson/siege/game/map/SiegeKitStandLocation.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.Nullable; import xyz.nucleoid.plasmid.game.common.team.GameTeam; -public final record SiegeKitStandLocation( +public record SiegeKitStandLocation( @Nullable GameTeam team, @Nullable SiegeFlag flag, Vec3d pos, diff --git a/src/main/java/io/github/restioson/siege/game/map/SiegeMap.java b/src/main/java/io/github/restioson/siege/game/map/SiegeMap.java index f853eaa..1849191 100644 --- a/src/main/java/io/github/restioson/siege/game/map/SiegeMap.java +++ b/src/main/java/io/github/restioson/siege/game/map/SiegeMap.java @@ -1,9 +1,14 @@ package io.github.restioson.siege.game.map; +import io.github.restioson.siege.Siege; +import io.github.restioson.siege.entity.SiegeKitStandEntity; +import io.github.restioson.siege.game.SiegeSpawnLogic; import io.github.restioson.siege.game.SiegeTeams; +import io.github.restioson.siege.game.active.SiegeActive; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.server.MinecraftServer; +import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import net.minecraft.world.gen.chunk.ChunkGenerator; import xyz.nucleoid.map_templates.BlockBounds; @@ -15,11 +20,10 @@ import java.util.List; public class SiegeMap { + private final SiegeMapConfig config; private final MapTemplate template; public final List flags = new ArrayList<>(); public final List kitStands = new ArrayList<>(); - public final int attackerSpawnAngle; - public final BlockBounds bounds; public SiegeSpawn waitingSpawn = null; public List noBuildRegions = new ArrayList<>(); public List gates = new ArrayList<>(); @@ -29,10 +33,9 @@ public class SiegeMap { private final LongSet protectedBlocks = new LongOpenHashSet(); - public SiegeMap(MapTemplate template, int attackerSpawnAngle) { + public SiegeMap(SiegeMapConfig config, MapTemplate template) { + this.config = config; this.template = template; - this.attackerSpawnAngle = attackerSpawnAngle; - this.bounds = template.getBounds(); this.time = 1000; } @@ -63,4 +66,15 @@ public boolean isProtectedBlock(long pos) { public ChunkGenerator asGenerator(MinecraftServer server) { return new TemplateChunkGenerator(server, this.template); } + + public void spawnKitStands(SiegeActive active) { + for (SiegeKitStandLocation stand : this.kitStands) { + SiegeKitStandEntity standEntity = new SiegeKitStandEntity(active.world, active, stand); + active.world.spawnEntity(standEntity); + + if (standEntity.controllingFlag != null) { + standEntity.controllingFlag.kitStands.add(standEntity); + } + } + } } diff --git a/src/main/java/io/github/restioson/siege/game/map/SiegeMapConfig.java b/src/main/java/io/github/restioson/siege/game/map/SiegeMapConfig.java index fe014e1..f4edce3 100644 --- a/src/main/java/io/github/restioson/siege/game/map/SiegeMapConfig.java +++ b/src/main/java/io/github/restioson/siege/game/map/SiegeMapConfig.java @@ -4,7 +4,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.util.Identifier; -public final record SiegeMapConfig( +public record SiegeMapConfig( Identifier templateId, int attackerSpawnAngle ) { 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 4a199d6..9d68025 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 @@ -5,10 +5,10 @@ import io.github.restioson.siege.game.SiegeKit; import io.github.restioson.siege.game.SiegeTeams; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.fabricmc.fabric.api.util.NbtType; -import net.minecraft.block.BlockState; +import net.minecraft.block.*; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; import net.minecraft.registry.Registries; import net.minecraft.registry.RegistryKey; @@ -18,6 +18,9 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.world.biome.BiomeKeys; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.map_templates.*; import xyz.nucleoid.plasmid.game.GameOpenException; @@ -30,62 +33,85 @@ import java.util.stream.Collectors; public class SiegeMapLoader { - private final SiegeMapConfig config; + @Nullable + static final CloseableHttpClient httpClient = loadRemote() ? HttpClients.createDefault() : null; - public SiegeMapLoader(SiegeMapConfig config) { - this.config = config; + public static boolean loadRemote() { + return System.getenv().getOrDefault("SIEGE_LOAD_MAPS_FROM_BUILD", "false").equals("true"); } - public SiegeMap create(MinecraftServer server) throws GameOpenException { + public static SiegeMap load(MinecraftServer server, SiegeMapConfig config) throws GameOpenException { + MapTemplate template; try { - MapTemplate template = MapTemplateSerializer.loadFromResource(server, this.config.templateId()); - MapTemplateMetadata metadata = template.getMetadata(); + if (loadRemote()) { + Siege.LOGGER.info("Loading map from build server"); + + assert httpClient != null; - SiegeMap map = new SiegeMap(template, this.config.attackerSpawnAngle()); + var id = config.templateId(); + var uri = String.format( + "https://build.nucleoid.xyz/nucleoid_creator_tools/export/%s/map_templates/%s.nbt", + id.getNamespace(), + id.getPath() + ); - NbtCompound mapData = metadata.getData(); - String biomeId = mapData.getString("biome"); - if (!Strings.isNullOrEmpty(biomeId)) { - template.setBiome(RegistryKey.of(RegistryKeys.BIOME, new Identifier(biomeId))); + var response = httpClient.execute(new HttpGet(uri)); + template = MapTemplateSerializer.loadFrom(response.getEntity().getContent()); } else { - template.setBiome(BiomeKeys.PLAINS); + Siege.LOGGER.info("Loading map from resources"); + template = MapTemplateSerializer.loadFromResource(server, config.templateId()); } + } catch (IOException e) { + throw new GameOpenException(Text.literal(String.format("Failed to load map template %s", config.templateId())), e); + } - if (mapData.contains("time")) { - map.time = mapData.getLong("time"); - } + MapTemplateMetadata metadata = template.getMetadata(); - TemplateRegion waitingSpawn = metadata.getFirstRegion("waiting_spawn"); - if (waitingSpawn == null) { - throw new GameOpenException(Text.literal("waiting_spawn region required but not found")); - } + SiegeMap map = new SiegeMap(config, template); - map.setWaitingSpawn(new SiegeSpawn(waitingSpawn.getBounds(), waitingSpawn.getData().getFloat("yaw"))); + NbtCompound mapData = metadata.getData(); + String biomeId = mapData.getString("biome"); + if (!Strings.isNullOrEmpty(biomeId)) { + template.setBiome(RegistryKey.of(RegistryKeys.BIOME, new Identifier(biomeId))); + } else { + template.setBiome(BiomeKeys.PLAINS); + } - this.addFlagsToMap(map, metadata); - map.kitStands.addAll(this.collectKitStands(map.flags, template)); + if (mapData.contains("time")) { + map.time = mapData.getLong("time"); + } - for (BlockPos pos : template.getBounds()) { - BlockState state = template.getBlockState(pos); - if (!state.isAir()) { - map.addProtectedBlock(pos.asLong()); - } - } + TemplateRegion waitingSpawn = metadata.getFirstRegion("waiting_spawn"); + if (waitingSpawn == null) { + throw new GameOpenException(Text.literal("waiting_spawn region required but not found")); + } - return map; - } catch (IOException e) { - throw new GameOpenException(Text.literal("Failed to load map"), e); + map.setWaitingSpawn(new SiegeSpawn(waitingSpawn.getBounds(), waitingSpawn.getData().getFloat("yaw"))); + + addFlagsToMap(map, metadata); + map.kitStands.addAll(collectKitStands(map.flags, template)); + + for (BlockPos pos : template.getBounds()) { + BlockState state = template.getBlockState(pos); + Block block = state.getBlock(); + + boolean destructible = block instanceof PaneBlock || block instanceof VineBlock || block instanceof PlantBlock; + if (!state.isAir() && !destructible) { + map.addProtectedBlock(pos.asLong()); + } } + + return map; } - private List collectKitStands(List flags, MapTemplate template) { + private static List collectKitStands(List flags, MapTemplate template) { return template.getMetadata() .getRegions("kit_stand") .map(region -> { NbtCompound data = region.getData(); GameTeam team = null; if (data.contains("team")) { - team = this.parseTeam(data); + team = parseTeam(data); } SiegeFlag flag = null; @@ -98,14 +124,14 @@ private List collectKitStands(List flags, MapT } } - SiegeKit type = this.parseKitStandType(data); + SiegeKit type = parseKitStandType(data); - return new SiegeKitStandLocation(team, flag, region.getBounds().center(), type, data.getFloat("yaw")); + return new SiegeKitStandLocation(team, flag, region.getBounds().centerBottom(), type, data.getFloat("yaw")); }) .collect(Collectors.toList()); } - private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { + private static void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { Map flags = new Object2ObjectOpenHashMap<>(); metadata.getRegions("flag").forEach(region -> { @@ -113,7 +139,7 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { NbtCompound data = region.getData(); String id = data.getString("id"); String name = data.getString("name"); - GameTeam team = this.parseTeam(data); + GameTeam team = parseTeam(data); SiegeFlag flag = new SiegeFlag(id, name, team, bounds); if (data.contains("capturable") && !data.getBoolean("capturable")) { @@ -144,7 +170,7 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { return; } - NbtList prerequisiteFlagsList = data.getList("prerequisite_flags", NbtType.STRING); + NbtList prerequisiteFlagsList = data.getList("prerequisite_flags", NbtElement.STRING_TYPE); for (int i = 0; i < prerequisiteFlagsList.size(); i++) { String prerequisiteId = prerequisiteFlagsList.getString(i); @@ -157,7 +183,7 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { flag.prerequisiteFlags.add(prerequisite); } - NbtList recapturePrerequisites = data.getList("recapture_prerequisites", NbtType.STRING); + NbtList recapturePrerequisites = data.getList("recapture_prerequisites", NbtElement.STRING_TYPE); for (int i = 0; i < recapturePrerequisites.size(); i++) { String prerequisiteId = recapturePrerequisites.getString(i); @@ -184,7 +210,7 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { float yaw = data.getFloat("yaw"); SiegeSpawn respawn = new SiegeSpawn(region.getBounds(), yaw); - GameTeam team = this.parseOptionalTeam(data); + GameTeam team = parseOptionalTeam(data); if (team == SiegeTeams.DEFENDERS) { flag.defenderRespawn = respawn; } else if (team == SiegeTeams.ATTACKERS) { @@ -201,7 +227,8 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { } } } else { - Siege.LOGGER.warn("Respawn attached to missing flag: {}", flagId); + // TODO: i should fix this + Siege.LOGGER.warn("Skipping respawn as flag is missing: {}", flagId); } }); @@ -211,18 +238,27 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { .map(region -> { NbtCompound data = region.getData(); - String id = data.getString("id"); - SiegeFlag flag = flags.get(id); + String gateId = data.getString("id"); + String flagIdRaw = data.getString("flag"); + final String flagId = flagIdRaw.isEmpty() ? gateId : flagIdRaw; + + SiegeFlag flag = flags.get(flagId); if (flag == null) { - throw new GameOpenException(Text.literal("Gate missing flag with id '" + id + "'!")); + var text = Text.literal(String.format("Gate (id '%s') missing flag with id '%s'!", gateId, flagId)); + + if (flagIdRaw.isEmpty()) { + text = text.append(Text.literal("\nNote: flag id was implicitly defined as the gate id, as `flag` was missing in data.")); + } + + throw new GameOpenException(text); } TemplateRegion portcullisRegion = metadata.getRegions("portcullis") - .filter(r -> id.equalsIgnoreCase(r.getData().getString("id"))) + .filter(r -> gateId.equalsIgnoreCase(r.getData().getString("id"))) .findFirst() .orElseThrow(() -> { - Siege.LOGGER.error("Gate \"{}\" missing portcullis!", id); - return new GameOpenException(Text.literal("Gate missing portcullis!")); + Siege.LOGGER.error("Gate \"{}\" missing portcullis!", gateId); + return new GameOpenException(Text.literal(String.format("Gate (id '%s') missing portcullis!", gateId))); }); NbtCompound portcullisData = portcullisRegion.getData(); @@ -241,13 +277,13 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { } BlockBounds brace = metadata.getRegions("gate_brace") - .filter(r -> id.equalsIgnoreCase(r.getData().getString("id"))) + .filter(r -> gateId.equalsIgnoreCase(r.getData().getString("id"))) .map(TemplateRegion::getBounds) .findFirst() .orElse(null); - SiegeGate gate = new SiegeGate(flag, region.getBounds(), portcullisRegion.getBounds(), brace, retractHeight, repairHealthThreshold, maxHealth); - flag.gate = gate; + SiegeGate gate = new SiegeGate(gateId, flag, region.getBounds(), portcullisRegion.getBounds(), brace, retractHeight, repairHealthThreshold, maxHealth); + flag.gates.add(gate); return gate; }) .collect(Collectors.toList()); @@ -261,8 +297,8 @@ private void addFlagsToMap(SiegeMap map, MapTemplateMetadata metadata) { } } - private GameTeam parseTeam(NbtCompound data) { - String teamName = data.getString("team"); + private static GameTeam parseTeam(NbtCompound data) { + String teamName = data.getString("team").toLowerCase(); GameTeam team = SiegeTeams.byKey(teamName); if (team == null) { Siege.LOGGER.error("Unknown team \"{}\"", teamName); @@ -272,12 +308,12 @@ private GameTeam parseTeam(NbtCompound data) { } @Nullable - private GameTeam parseOptionalTeam(NbtCompound data) { + private static GameTeam parseOptionalTeam(NbtCompound data) { String teamName = data.getString("team"); return SiegeTeams.byKey(teamName); } - private SiegeKit parseKitStandType(NbtCompound data) { + private static SiegeKit parseKitStandType(NbtCompound data) { String kitName = data.getString("type"); return switch (kitName) { case "bow" -> SiegeKit.ARCHER; diff --git a/src/main/resources/data/siege/games/canal.json b/src/main/resources/data/siege/games/canal.json index 4259a85..80f2f5b 100644 --- a/src/main/resources/data/siege/games/canal.json +++ b/src/main/resources/data/siege/games/canal.json @@ -1,8 +1,14 @@ { "type": "siege:siege", "icon": "minecraft:iron_sword", - "short_name": "gameType.siege.siege", - "description": ["game.siege.desc"], + "short_name": { + "translate": "gameType.siege.siege" + }, + "description": [ + { + "translate": "game.siege.siege.desc" + } + ], "map": { "id": "siege:canal", "attacker_spawn_angle": 180 diff --git a/src/main/resources/data/siege/games/canal_recapture.json b/src/main/resources/data/siege/games/canal_recapture.json index 9188ffb..f2efe7e 100644 --- a/src/main/resources/data/siege/games/canal_recapture.json +++ b/src/main/resources/data/siege/games/canal_recapture.json @@ -1,8 +1,17 @@ { "type": "siege:siege", "icon": "minecraft:iron_sword", - "short_name": "gameType.siege.siege", - "description": ["game.siege.desc", "game.siege.recapture.desc"], + "short_name": { + "translate": "gameType.siege.siege" + }, + "description": [ + { + "translate": "game.siege.siege.desc" + }, + { + "translate": "game.siege.recapture.desc" + } + ], "map": { "id": "siege:canal", "attacker_spawn_angle": 180 diff --git a/src/main/resources/data/siege/games/villagers_vs_pillagers_recapture_enderpearl.json b/src/main/resources/data/siege/games/villagers_vs_pillagers_recapture_enderpearl.json index 590ae74..2266d1c 100644 --- a/src/main/resources/data/siege/games/villagers_vs_pillagers_recapture_enderpearl.json +++ b/src/main/resources/data/siege/games/villagers_vs_pillagers_recapture_enderpearl.json @@ -1,7 +1,6 @@ { "type": "siege:siege", "icon": "minecraft:iron_sword", - "icon": "minecraft:iron_sword", "short_name": { "translate": "gameType.siege.siege" }, diff --git a/src/main/resources/data/siege/lang/en_us.json b/src/main/resources/data/siege/lang/en_us.json index a97ba46..7ff7452 100644 --- a/src/main/resources/data/siege/lang/en_us.json +++ b/src/main/resources/data/siege/lang/en_us.json @@ -14,5 +14,13 @@ "game.siege.recapture.desc.3": "points.", "game.siege.enderpearl.desc.1": "In this mode, defenders have", "game.siege.enderpearl.desc.2": "ender pearls that allow them", - "game.siege.enderpearl.desc.3": "to teleport to points under siege." + "game.siege.enderpearl.desc.3": "to teleport to points under siege.", + "game.siege.kit.kits.archer": "Archer Kit", + "game.siege.kit.kits.soldier": "Soldier Kit", + "game.siege.kit.kits.shield_bearer": "Shieldbearer Kit", + "game.siege.kit.kits.constructor": "Constructor Kit", + "game.siege.kit.kits.demolitioner": "Demolitioner Kit", + "game.siege.kit.items.arrows": "arrows", + "game.siege.kit.items.tnt": "TNT", + "game.siege.kit.items.wood": "wood" } diff --git a/src/main/resources/data/siege/map_templates/canal.nbt b/src/main/resources/data/siege/map_templates/canal.nbt index 0928047..4bb1157 100644 Binary files a/src/main/resources/data/siege/map_templates/canal.nbt and b/src/main/resources/data/siege/map_templates/canal.nbt differ diff --git a/src/main/resources/data/siege/map_templates/jungle.nbt b/src/main/resources/data/siege/map_templates/jungle.nbt index 1479886..9d2f385 100644 Binary files a/src/main/resources/data/siege/map_templates/jungle.nbt and b/src/main/resources/data/siege/map_templates/jungle.nbt differ diff --git a/src/main/resources/siege.accesswidener b/src/main/resources/siege.accesswidener index 4744b14..72b6de7 100644 --- a/src/main/resources/siege.accesswidener +++ b/src/main/resources/siege.accesswidener @@ -1,3 +1,3 @@ -accessWidener v1 named +accessWidener v2 named -accessible method net/minecraft/entity/decoration/ArmorStandEntity setShowArms (Z)V +accessible field net/minecraft/block/HorizontalConnectingBlock FACING_PROPERTIES Ljava/util/Map;