From 332b6552cf86613aaa79b59f91df644e9a5214b2 Mon Sep 17 00:00:00 2001 From: fretuks Date: Sat, 23 May 2026 19:37:58 +0200 Subject: [PATCH] add "Help Up" feature for knocked players, improve execution handling, and update mod version to 1.6.0 --- gradle.properties | 2 +- .../net/fretux/knockedback/CarryManager.java | 4 +- .../knockedback/ExecutionProgressPacket.java | 30 ++- .../net/fretux/knockedback/HelpUpHandler.java | 211 ++++++++++++++++++ .../knockedback/HelpUpKnockedPacket.java | 36 +++ .../fretux/knockedback/KnockedManager.java | 15 +- .../knockedback/KnockedbackAdvancements.java | 98 ++++++++ .../fretux/knockedback/MobKillHandler.java | 2 +- .../fretux/knockedback/NetworkHandler.java | 3 +- .../knockedback/PlayerExecutionHandler.java | 6 +- .../client/ClientExecutionState.java | 66 +++++- .../knockedback/client/ClientKeyHandler.java | 29 ++- .../client/ExecutionProgressOverlay.java | 43 +++- .../net/fretux/knockedback/config/Config.java | 15 ++ .../assets/knockedback/lang/de_de.json | 60 ++++- .../assets/knockedback/lang/en_us.json | 43 +++- .../advancements/be_helped_up.json | 22 ++ .../advancements/carry_player.json | 22 ++ .../advancements/carry_player_10.json | 25 +++ .../advancements/carry_player_50.json | 25 +++ .../advancements/execute_player.json | 25 +++ .../advancements/execute_player_10.json | 25 +++ .../advancements/execute_player_100.json | 25 +++ .../knockedback/advancements/help_up.json | 22 ++ .../knockedback/advancements/help_up_10.json | 25 +++ .../knockedback/advancements/help_up_50.json | 25 +++ .../knockedback/advancements/knocked.json | 22 ++ .../knockedback/advancements/knocked_10.json | 25 +++ .../knockedback/advancements/knocked_50.json | 25 +++ .../data/knockedback/advancements/root.json | 22 ++ 30 files changed, 956 insertions(+), 42 deletions(-) create mode 100644 src/main/java/net/fretux/knockedback/HelpUpHandler.java create mode 100644 src/main/java/net/fretux/knockedback/HelpUpKnockedPacket.java create mode 100644 src/main/java/net/fretux/knockedback/KnockedbackAdvancements.java create mode 100644 src/main/resources/data/knockedback/advancements/be_helped_up.json create mode 100644 src/main/resources/data/knockedback/advancements/carry_player.json create mode 100644 src/main/resources/data/knockedback/advancements/carry_player_10.json create mode 100644 src/main/resources/data/knockedback/advancements/carry_player_50.json create mode 100644 src/main/resources/data/knockedback/advancements/execute_player.json create mode 100644 src/main/resources/data/knockedback/advancements/execute_player_10.json create mode 100644 src/main/resources/data/knockedback/advancements/execute_player_100.json create mode 100644 src/main/resources/data/knockedback/advancements/help_up.json create mode 100644 src/main/resources/data/knockedback/advancements/help_up_10.json create mode 100644 src/main/resources/data/knockedback/advancements/help_up_50.json create mode 100644 src/main/resources/data/knockedback/advancements/knocked.json create mode 100644 src/main/resources/data/knockedback/advancements/knocked_10.json create mode 100644 src/main/resources/data/knockedback/advancements/knocked_50.json create mode 100644 src/main/resources/data/knockedback/advancements/root.json diff --git a/gradle.properties b/gradle.properties index 5fd05ad..e173ae3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ mod_name=Knockedback # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=1.5.3 +mod_version=1.6.0 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/fretux/knockedback/CarryManager.java b/src/main/java/net/fretux/knockedback/CarryManager.java index 2482513..d32db78 100644 --- a/src/main/java/net/fretux/knockedback/CarryManager.java +++ b/src/main/java/net/fretux/knockedback/CarryManager.java @@ -43,7 +43,8 @@ public class CarryManager { .filter(p -> KnockedManager.isKnocked(p) && !p.getUUID().equals(carrier.getUUID()) && !isBeingCarried(p.getUUID()) - && !PlayerExecutionHandler.isBeingPlayerExecuted(p.getUUID())) + && !PlayerExecutionHandler.isBeingPlayerExecuted(p.getUUID()) + && !HelpUpHandler.isBeingHelped(p.getUUID())) .findFirst() .orElse(null); } @@ -56,6 +57,7 @@ public class CarryManager { if (knocked instanceof ServerPlayer spKnocked) { spKnocked.connection.send(mountPacket); } + KnockedbackAdvancements.recordCarry(carrier); carrier.sendSystemMessage(Component.translatable("message.knockedback.picked_up", knocked.getDisplayName())); knocked.sendSystemMessage(Component.translatable("message.knockedback.being_carried", carrier.getDisplayName())); } diff --git a/src/main/java/net/fretux/knockedback/ExecutionProgressPacket.java b/src/main/java/net/fretux/knockedback/ExecutionProgressPacket.java index 2ac70c6..0f05a32 100644 --- a/src/main/java/net/fretux/knockedback/ExecutionProgressPacket.java +++ b/src/main/java/net/fretux/knockedback/ExecutionProgressPacket.java @@ -8,17 +8,40 @@ import java.util.UUID; import java.util.function.Supplier; public class ExecutionProgressPacket { + public enum Action { + NONE, + EXECUTION, + HELP_UP + } + private final int timeLeft; + private final int totalTicks; + private final Action action; + private final String targetName; @Nullable private final UUID executorId; public ExecutionProgressPacket(int timeLeft, @Nullable UUID executorId) { + this(timeLeft, PlayerExecutionHandler.getExecutionTime(), timeLeft > 0 ? Action.EXECUTION : Action.NONE, "", executorId); + } + + public ExecutionProgressPacket(int timeLeft, int totalTicks, Action action, @Nullable UUID executorId) { + this(timeLeft, totalTicks, action, "", executorId); + } + + public ExecutionProgressPacket(int timeLeft, int totalTicks, Action action, String targetName, @Nullable UUID executorId) { this.timeLeft = timeLeft; + this.totalTicks = totalTicks; + this.action = timeLeft > 0 ? action : Action.NONE; + this.targetName = timeLeft > 0 ? targetName : ""; this.executorId = executorId; } public static void encode(ExecutionProgressPacket msg, FriendlyByteBuf buf) { buf.writeInt(msg.timeLeft); + buf.writeInt(msg.totalTicks); + buf.writeEnum(msg.action); + buf.writeUtf(msg.targetName); buf.writeBoolean(msg.executorId != null); if (msg.executorId != null) { buf.writeUUID(msg.executorId); @@ -27,13 +50,16 @@ public class ExecutionProgressPacket { public static ExecutionProgressPacket decode(FriendlyByteBuf buf) { int timeLeft = buf.readInt(); + int totalTicks = buf.readInt(); + Action action = buf.readEnum(Action.class); + String targetName = buf.readUtf(); UUID executorId = buf.readBoolean() ? buf.readUUID() : null; - return new ExecutionProgressPacket(timeLeft, executorId); + return new ExecutionProgressPacket(timeLeft, totalTicks, action, targetName, executorId); } public static void handle(ExecutionProgressPacket msg, Supplier ctx) { ctx.get().enqueueWork(() -> { - net.fretux.knockedback.client.ClientExecutionState.setExecution(msg.timeLeft, msg.executorId); + net.fretux.knockedback.client.ClientExecutionState.setProgress(msg.timeLeft, msg.totalTicks, msg.action, msg.targetName, msg.executorId); }); ctx.get().setPacketHandled(true); } diff --git a/src/main/java/net/fretux/knockedback/HelpUpHandler.java b/src/main/java/net/fretux/knockedback/HelpUpHandler.java new file mode 100644 index 0000000..b94b20a --- /dev/null +++ b/src/main/java/net/fretux/knockedback/HelpUpHandler.java @@ -0,0 +1,211 @@ +package net.fretux.knockedback; + +import net.fretux.knockedback.config.Config; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.AABB; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.living.LivingHurtEvent; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.network.PacketDistributor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Mod.EventBusSubscriber(modid = KnockedBack.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class HelpUpHandler { + public static final double HELP_UP_RANGE = 2.0; + + private static final Map helpUpAttempts = new HashMap<>(); + + public static int getHelpUpTime() { + return Config.COMMON.helpUpTime.get(); + } + + public static boolean isBeingHelped(UUID knockedId) { + return helpUpAttempts.containsKey(knockedId); + } + + public static boolean isHelping(UUID helperId) { + return helpUpAttempts.values().stream().anyMatch(attempt -> attempt.helperId.equals(helperId)); + } + + public static void startHelpUp(ServerPlayer helper) { + if (!Config.COMMON.helpUpEnabled.get()) return; + if (KnockedManager.isKnocked(helper)) { + helper.sendSystemMessage(Component.translatable("message.knockedback.cannot_help_while_knocked")); + return; + } + if (PlayerExecutionHandler.isExecuting(helper.getUUID())) return; + if (isHelping(helper.getUUID())) return; + Player target = findNearestKnocked(helper); + if (target == null) return; + UUID knockedId = target.getUUID(); + if (CarryManager.isBeingCarried(knockedId)) { + helper.sendSystemMessage(Component.translatable("message.knockedback.cannot_help_carried_player")); + return; + } + if (helpUpAttempts.containsKey(knockedId)) return; + helpUpAttempts.put(knockedId, new HelpUpAttempt(helper.getUUID())); + helper.sendSystemMessage(Component.translatable("message.knockedback.help_up_started", target.getDisplayName())); + target.sendSystemMessage(Component.translatable("message.knockedback.being_helped", helper.getDisplayName())); + } + + public static void cancelHelpUp(UUID knockedId) { + HelpUpAttempt attempt = helpUpAttempts.remove(knockedId); + if (attempt == null) return; + ServerPlayer knocked = getPlayerByUuid(knockedId); + if (knocked != null) { + knocked.sendSystemMessage(Component.translatable("message.knockedback.help_up_interrupted")); + clearProgress(knocked); + } + ServerPlayer helper = getPlayerByUuid(attempt.helperId); + if (helper != null) { + helper.sendSystemMessage(Component.translatable("message.knockedback.help_up_interrupted_helper")); + clearProgress(helper); + } + } + + public static void cancelHelpUpByHelper(UUID helperId) { + List toCancel = helpUpAttempts.entrySet().stream() + .filter(entry -> entry.getValue().helperId.equals(helperId)) + .map(Map.Entry::getKey) + .toList(); + for (UUID knockedId : toCancel) { + cancelHelpUp(knockedId); + } + } + + @SubscribeEvent + public static void onPlayerTick(TickEvent.PlayerTickEvent event) { + if (event.side.isServer() && event.phase == TickEvent.Phase.END && event.player instanceof ServerPlayer sp && isHelping(sp.getUUID())) { + sp.setDeltaMovement(0, 0, 0); + sp.setSprinting(false); + } + } + + @SubscribeEvent + public static void onAttackEntity(AttackEntityEvent event) { + if (event.getEntity() instanceof ServerPlayer sp && isHelping(sp.getUUID())) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onLivingHurt(LivingHurtEvent event) { + if (!(event.getEntity() instanceof ServerPlayer hurtPlayer)) return; + cancelHelpUpByHelper(hurtPlayer.getUUID()); + } + + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END || !event.side.isServer()) return; + Map updated = new HashMap<>(); + List toCancel = new ArrayList<>(); + List toComplete = new ArrayList<>(); + for (Map.Entry entry : helpUpAttempts.entrySet()) { + UUID knockedId = entry.getKey(); + HelpUpAttempt attempt = entry.getValue(); + ServerPlayer knocked = getPlayerByUuid(knockedId); + ServerPlayer helper = getPlayerByUuid(attempt.helperId); + if (knocked == null || helper == null || !KnockedManager.isKnocked(knocked)) { + toCancel.add(knockedId); + continue; + } + if (CarryManager.isBeingCarried(knockedId) + || PlayerExecutionHandler.isBeingPlayerExecuted(knockedId) + || MobKillHandler.isBeingMobExecuted(knockedId) + || helper.distanceTo(knocked) > HELP_UP_RANGE) { + toCancel.add(knockedId); + continue; + } + attempt.timeLeft--; + sendProgress(helper, knocked, attempt.timeLeft); + sendProgress(knocked, knocked, attempt.timeLeft); + if (attempt.timeLeft <= 0) { + toComplete.add(knockedId); + } else { + updated.put(knockedId, attempt); + } + } + for (UUID knockedId : toCancel) { + cancelHelpUp(knockedId); + } + for (UUID knockedId : toComplete) { + HelpUpAttempt attempt = helpUpAttempts.get(knockedId); + if (attempt == null) continue; + ServerPlayer knocked = getPlayerByUuid(knockedId); + ServerPlayer helper = getPlayerByUuid(attempt.helperId); + if (knocked == null || helper == null) { + cancelHelpUp(knockedId); + continue; + } + completeHelpUp(helper, knocked); + } + helpUpAttempts.clear(); + helpUpAttempts.putAll(updated); + } + + private static void completeHelpUp(ServerPlayer helper, ServerPlayer knocked) { + helpUpAttempts.remove(knocked.getUUID()); + KnockedManager.removeKnockedState(knocked); + knocked.heal(Config.COMMON.helpUpHealAmount.get().floatValue()); + clearProgress(helper); + clearProgress(knocked); + KnockedbackAdvancements.recordHelpUp(helper); + KnockedbackAdvancements.award(knocked, KnockedbackAdvancements.BE_HELPED_UP); + helper.sendSystemMessage(Component.translatable("message.knockedback.you_helped_up", knocked.getDisplayName())); + knocked.sendSystemMessage(Component.translatable("message.knockedback.you_were_helped_up", helper.getDisplayName())); + } + + private static void sendProgress(ServerPlayer player, ServerPlayer target, int timeLeft) { + NetworkHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new ExecutionProgressPacket(timeLeft, getHelpUpTime(), ExecutionProgressPacket.Action.HELP_UP, target.getDisplayName().getString(), null) + ); + } + + private static void clearProgress(ServerPlayer player) { + NetworkHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new ExecutionProgressPacket(0, 1, ExecutionProgressPacket.Action.NONE, null) + ); + } + + private static Player findNearestKnocked(ServerPlayer helper) { + AABB box = helper.getBoundingBox().inflate(HELP_UP_RANGE); + return helper + .getCommandSenderWorld() + .getEntitiesOfClass(Player.class, box).stream() + .filter(player -> KnockedManager.isKnocked(player) + && !player.getUUID().equals(helper.getUUID()) + && !CarryManager.isBeingCarried(player.getUUID()) + && !PlayerExecutionHandler.isBeingPlayerExecuted(player.getUUID()) + && !MobKillHandler.isBeingMobExecuted(player.getUUID()) + && !isBeingHelped(player.getUUID())) + .findFirst() + .orElse(null); + } + + private static ServerPlayer getPlayerByUuid(UUID uuid) { + MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); + return NetworkHandlerHelper.getPlayerByUuid(server, uuid); + } + + private static class HelpUpAttempt { + private final UUID helperId; + private int timeLeft; + + private HelpUpAttempt(UUID helperId) { + this.helperId = helperId; + this.timeLeft = getHelpUpTime(); + } + } +} diff --git a/src/main/java/net/fretux/knockedback/HelpUpKnockedPacket.java b/src/main/java/net/fretux/knockedback/HelpUpKnockedPacket.java new file mode 100644 index 0000000..bccf5cb --- /dev/null +++ b/src/main/java/net/fretux/knockedback/HelpUpKnockedPacket.java @@ -0,0 +1,36 @@ +package net.fretux.knockedback; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class HelpUpKnockedPacket { + private final boolean helping; + + public HelpUpKnockedPacket(boolean helping) { + this.helping = helping; + } + + public static void encode(HelpUpKnockedPacket msg, FriendlyByteBuf buf) { + buf.writeBoolean(msg.helping); + } + + public static HelpUpKnockedPacket decode(FriendlyByteBuf buf) { + return new HelpUpKnockedPacket(buf.readBoolean()); + } + + public static void handle(HelpUpKnockedPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer helper = ctx.get().getSender(); + if (helper == null) return; + if (msg.helping) { + HelpUpHandler.startHelpUp(helper); + } else { + HelpUpHandler.cancelHelpUpByHelper(helper.getUUID()); + } + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/net/fretux/knockedback/KnockedManager.java b/src/main/java/net/fretux/knockedback/KnockedManager.java index adc9349..e9f6a99 100644 --- a/src/main/java/net/fretux/knockedback/KnockedManager.java +++ b/src/main/java/net/fretux/knockedback/KnockedManager.java @@ -43,6 +43,9 @@ public class KnockedManager { knockedEntities.put(entity.getUUID(), getKnockedDuration()); entity.setHealth(1.0F); applyKnockedEffects((Player) entity); + if (entity instanceof ServerPlayer sp) { + KnockedbackAdvancements.recordKnocked(sp); + } } } @@ -69,6 +72,7 @@ public class KnockedManager { knockedEntities.remove(entity.getUUID()); setGripped(entity, false); MobKillHandler.clearKillAttempt(entity.getUUID()); + HelpUpHandler.cancelHelpUp(entity.getUUID()); entity.setPose(Pose.STANDING); if (entity instanceof ServerPlayer sp) { NetworkHandler.CHANNEL.send( @@ -118,7 +122,8 @@ public class KnockedManager { continue; } if (MobKillHandler.isBeingMobExecuted(playerId) - || PlayerExecutionHandler.isBeingPlayerExecuted(playerId)) { + || PlayerExecutionHandler.isBeingPlayerExecuted(playerId) + || HelpUpHandler.isBeingHelped(playerId)) { ServerPlayer p = NetworkHandlerHelper.getPlayerByUuid(server, playerId); if (p != null) { applyKnockedEffects(p); @@ -206,6 +211,8 @@ public class KnockedManager { knockedEntities.remove(playerId); grippedEntities.remove(playerId); MobKillHandler.clearKillAttempt(playerId); + HelpUpHandler.cancelHelpUp(playerId); + HelpUpHandler.cancelHelpUpByHelper(playerId); NetworkHandler.CHANNEL.send( PacketDistributor.PLAYER.with(() -> sp), new KnockedTimePacket(0) @@ -220,11 +227,17 @@ public class KnockedManager { ); } + @SubscribeEvent + public static void onPlayerClone(PlayerEvent.Clone event) { + KnockedbackAdvancements.copyCounts(event.getOriginal(), event.getEntity()); + } + @SubscribeEvent public static void onPlayerLoggedOut(PlayerLoggedOutEvent event) { if (!(event.getEntity() instanceof ServerPlayer sp)) { return; } + HelpUpHandler.cancelHelpUpByHelper(sp.getUUID()); if (!isKnocked(sp)) { return; } diff --git a/src/main/java/net/fretux/knockedback/KnockedbackAdvancements.java b/src/main/java/net/fretux/knockedback/KnockedbackAdvancements.java new file mode 100644 index 0000000..4d5b536 --- /dev/null +++ b/src/main/java/net/fretux/knockedback/KnockedbackAdvancements.java @@ -0,0 +1,98 @@ +package net.fretux.knockedback; + +import net.minecraft.advancements.Advancement; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; + +public class KnockedbackAdvancements { + private static final String CRITERION = "triggered"; + private static final String DATA_KEY = KnockedBack.MOD_ID + ".advancement_counts"; + + public static final ResourceLocation KNOCKED = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "knocked"); + public static final ResourceLocation KNOCKED_10 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "knocked_10"); + public static final ResourceLocation KNOCKED_50 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "knocked_50"); + public static final ResourceLocation EXECUTE_PLAYER = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "execute_player"); + public static final ResourceLocation EXECUTE_PLAYER_10 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "execute_player_10"); + public static final ResourceLocation EXECUTE_PLAYER_100 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "execute_player_100"); + public static final ResourceLocation HELP_UP = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "help_up"); + public static final ResourceLocation HELP_UP_10 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "help_up_10"); + public static final ResourceLocation HELP_UP_50 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "help_up_50"); + public static final ResourceLocation BE_HELPED_UP = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "be_helped_up"); + public static final ResourceLocation CARRY_PLAYER = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "carry_player"); + public static final ResourceLocation CARRY_PLAYER_10 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "carry_player_10"); + public static final ResourceLocation CARRY_PLAYER_50 = + ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "carry_player_50"); + + public static void award(ServerPlayer player, ResourceLocation advancementId) { + MinecraftServer server = player.getServer(); + if (server == null) return; + Advancement advancement = server.getAdvancements().getAdvancement(advancementId); + if (advancement == null) return; + player.getAdvancements().award(advancement, CRITERION); + } + + public static void recordKnocked(ServerPlayer player) { + award(player, KNOCKED); + int count = increment(player, "knocked"); + awardAt(player, count, 10, KNOCKED_10); + awardAt(player, count, 50, KNOCKED_50); + } + + public static void recordExecution(ServerPlayer player) { + award(player, EXECUTE_PLAYER); + int count = increment(player, "executions"); + awardAt(player, count, 10, EXECUTE_PLAYER_10); + awardAt(player, count, 100, EXECUTE_PLAYER_100); + } + + public static void recordHelpUp(ServerPlayer player) { + award(player, HELP_UP); + int count = increment(player, "help_ups"); + awardAt(player, count, 10, HELP_UP_10); + awardAt(player, count, 50, HELP_UP_50); + } + + public static void recordCarry(ServerPlayer player) { + award(player, CARRY_PLAYER); + int count = increment(player, "carries"); + awardAt(player, count, 10, CARRY_PLAYER_10); + awardAt(player, count, 50, CARRY_PLAYER_50); + } + + private static void awardAt(ServerPlayer player, int count, int threshold, ResourceLocation advancementId) { + if (count >= threshold) { + award(player, advancementId); + } + } + + private static int increment(ServerPlayer player, String key) { + CompoundTag persistentData = player.getPersistentData(); + CompoundTag counts = persistentData.getCompound(DATA_KEY); + int count = counts.getInt(key) + 1; + counts.putInt(key, count); + persistentData.put(DATA_KEY, counts); + return count; + } + + public static void copyCounts(Player original, Player replacement) { + CompoundTag originalData = original.getPersistentData(); + if (originalData.contains(DATA_KEY)) { + replacement.getPersistentData().put(DATA_KEY, originalData.getCompound(DATA_KEY).copy()); + } + } +} diff --git a/src/main/java/net/fretux/knockedback/MobKillHandler.java b/src/main/java/net/fretux/knockedback/MobKillHandler.java index 0ee68d7..3c4b79d 100644 --- a/src/main/java/net/fretux/knockedback/MobKillHandler.java +++ b/src/main/java/net/fretux/knockedback/MobKillHandler.java @@ -132,7 +132,7 @@ public class MobKillHandler { if (knocked instanceof ServerPlayer sp) { NetworkHandler.CHANNEL.send( PacketDistributor.PLAYER.with(() -> sp), - new ExecutionProgressPacket(attempt.timeLeft, null) + new ExecutionProgressPacket(attempt.timeLeft, getExecutionTime(), ExecutionProgressPacket.Action.EXECUTION, knocked.getDisplayName().getString(), null) ); } if (attempt.timeLeft <= 0) { diff --git a/src/main/java/net/fretux/knockedback/NetworkHandler.java b/src/main/java/net/fretux/knockedback/NetworkHandler.java index 9f33c2b..dbee844 100644 --- a/src/main/java/net/fretux/knockedback/NetworkHandler.java +++ b/src/main/java/net/fretux/knockedback/NetworkHandler.java @@ -5,7 +5,7 @@ import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; public class NetworkHandler { - public static final String PROTOCOL_VERSION = "1"; + public static final String PROTOCOL_VERSION = "4"; public static SimpleChannel CHANNEL; public static void register() { CHANNEL = NetworkRegistry.newSimpleChannel(ResourceLocation.fromNamespaceAndPath(KnockedBack.MOD_ID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals); @@ -15,5 +15,6 @@ public class NetworkHandler { CHANNEL.registerMessage(id++, ExecutionProgressPacket.class, ExecutionProgressPacket::encode, ExecutionProgressPacket::decode, ExecutionProgressPacket::handle); CHANNEL.registerMessage(id++, CarryTogglePacket.class, CarryTogglePacket::encode, CarryTogglePacket::decode, CarryTogglePacket::handle); CHANNEL.registerMessage(id++, GrippedStatePacket.class, GrippedStatePacket::encode, GrippedStatePacket::decode, GrippedStatePacket::handle); + CHANNEL.registerMessage(id++, HelpUpKnockedPacket.class, HelpUpKnockedPacket::encode, HelpUpKnockedPacket::decode, HelpUpKnockedPacket::handle); } } diff --git a/src/main/java/net/fretux/knockedback/PlayerExecutionHandler.java b/src/main/java/net/fretux/knockedback/PlayerExecutionHandler.java index 269d934..c877349 100644 --- a/src/main/java/net/fretux/knockedback/PlayerExecutionHandler.java +++ b/src/main/java/net/fretux/knockedback/PlayerExecutionHandler.java @@ -76,6 +76,7 @@ public class PlayerExecutionHandler { executor.sendSystemMessage(Component.translatable("message.knockedback.cannot_execute_carried_player")); return; } + if (HelpUpHandler.isBeingHelped(knockedId)) return; if (executionAttempts.containsKey(knockedId)) return; executionAttempts.put(knockedId, new PlayerExecutionAttempt(executor.getUUID())); executor.sendSystemMessage(Component.translatable("message.knockedback.execution_started")); @@ -139,6 +140,7 @@ public class PlayerExecutionHandler { new ExecutionProgressPacket(0, null) ); } + KnockedbackAdvancements.recordExecution(executor); executor.sendSystemMessage(Component.translatable("message.knockedback.you_executed", knockedPlayer.getDisplayName())); if (knockedPlayer instanceof ServerPlayer serverPlayer) { serverPlayer.sendSystemMessage(Component.translatable("message.knockedback.you_were_executed", executor.getDisplayName())); @@ -168,11 +170,11 @@ public class PlayerExecutionHandler { attempt.timeLeft--; NetworkHandler.CHANNEL.send( PacketDistributor.PLAYER.with(() -> executor), - new ExecutionProgressPacket(attempt.timeLeft, attempt.executorUuid) + new ExecutionProgressPacket(attempt.timeLeft, getExecutionTime(), ExecutionProgressPacket.Action.EXECUTION, knocked.getDisplayName().getString(), attempt.executorUuid) ); NetworkHandler.CHANNEL.send( PacketDistributor.PLAYER.with(() -> knocked), - new ExecutionProgressPacket(attempt.timeLeft, attempt.executorUuid) + new ExecutionProgressPacket(attempt.timeLeft, getExecutionTime(), ExecutionProgressPacket.Action.EXECUTION, knocked.getDisplayName().getString(), attempt.executorUuid) ); if (attempt.timeLeft <= 0) { toExecute.add(knockedId); diff --git a/src/main/java/net/fretux/knockedback/client/ClientExecutionState.java b/src/main/java/net/fretux/knockedback/client/ClientExecutionState.java index 3ca5c3e..b4956c4 100644 --- a/src/main/java/net/fretux/knockedback/client/ClientExecutionState.java +++ b/src/main/java/net/fretux/knockedback/client/ClientExecutionState.java @@ -1,27 +1,79 @@ package net.fretux.knockedback.client; +import net.fretux.knockedback.ExecutionProgressPacket; + import javax.annotation.Nullable; import java.util.UUID; public class ClientExecutionState { private static int timeLeft = 0; + private static int totalTicks = 1; + private static ExecutionProgressPacket.Action action = ExecutionProgressPacket.Action.NONE; + private static String targetName = ""; @Nullable private static UUID executorId; public static void setExecution(int ticks, @Nullable UUID executor) { - timeLeft = ticks; - executorId = ticks > 0 ? executor : null; + setProgress(ticks, net.fretux.knockedback.PlayerExecutionHandler.getExecutionTime(), ExecutionProgressPacket.Action.EXECUTION, "", executor); + } + + public static void setProgress(int ticks, int total, ExecutionProgressPacket.Action progressAction, @Nullable UUID actor) { + setProgress(ticks, total, progressAction, "", actor); + } + + public static void setProgress(int ticks, int total, ExecutionProgressPacket.Action progressAction, String target, @Nullable UUID actor) { + timeLeft = Math.max(0, ticks); + totalTicks = Math.max(1, total); + action = timeLeft > 0 ? progressAction : ExecutionProgressPacket.Action.NONE; + targetName = timeLeft > 0 ? target : ""; + executorId = timeLeft > 0 ? actor : null; + } + + public static int getTotalTicks() { + return totalTicks; + } + + public static ExecutionProgressPacket.Action getAction() { + return action; + } + + public static String getTargetName() { + return targetName; + } + + public static boolean isHelpUp() { + return isActive() && action == ExecutionProgressPacket.Action.HELP_UP; + } + + public static boolean isExecution() { + return isActive() && action == ExecutionProgressPacket.Action.EXECUTION; + } + + public static boolean isActive() { + return timeLeft > 0; + } + + public static boolean isExecuting() { + return isExecution(); + } + + public static boolean hasProgress() { + return isActive(); + } + + public static void clear() { + timeLeft = 0; + totalTicks = 1; + action = ExecutionProgressPacket.Action.NONE; + targetName = ""; + executorId = null; } public static int getTimeLeft() { return timeLeft; } - public static boolean isExecuting() { - return timeLeft > 0; - } - public static boolean isExecutor(UUID playerId) { - return timeLeft > 0 && executorId != null && executorId.equals(playerId); + return isExecution() && executorId != null && executorId.equals(playerId); } } diff --git a/src/main/java/net/fretux/knockedback/client/ClientKeyHandler.java b/src/main/java/net/fretux/knockedback/client/ClientKeyHandler.java index fb9f6bf..74bdf81 100644 --- a/src/main/java/net/fretux/knockedback/client/ClientKeyHandler.java +++ b/src/main/java/net/fretux/knockedback/client/ClientKeyHandler.java @@ -2,10 +2,10 @@ package net.fretux.knockedback.client; import net.fretux.knockedback.CarryTogglePacket; import net.fretux.knockedback.ExecuteKnockedPacket; +import net.fretux.knockedback.HelpUpKnockedPacket; import net.fretux.knockedback.NetworkHandler; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RegisterKeyMappingsEvent; import net.minecraftforge.event.TickEvent; @@ -27,6 +27,14 @@ public class ClientKeyHandler { "key.categories.knockedback" ); + public static final KeyMapping HELP_UP_KEY = new KeyMapping( + "key.knockedback.help_up", + GLFW.GLFW_KEY_H, + "key.categories.knockedback" + ); + + private static boolean wasHelping; + @Mod.EventBusSubscriber(modid = "knockedback", value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public static class KeyMappingRegistration { @SubscribeEvent @@ -37,21 +45,32 @@ public class ClientKeyHandler { public static void registerCarryKey(RegisterKeyMappingsEvent e) { e.register(CARRY_KEY); } + @SubscribeEvent + public static void registerHelpUpKey(RegisterKeyMappingsEvent e) { + e.register(HELP_UP_KEY); + } } @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { - if (Minecraft.getInstance().screen != null) return; + Minecraft mc = Minecraft.getInstance(); + if (event.phase == TickEvent.Phase.END) { + boolean isHelping = mc.player != null && mc.screen == null && HELP_UP_KEY.isDown(); + if (isHelping != wasHelping) { + NetworkHandler.CHANNEL.sendToServer(new HelpUpKnockedPacket(isHelping)); + wasHelping = isHelping; + } + } + if (mc.screen != null) return; if (EXECUTE_KEY.consumeClick()) { - if (Minecraft.getInstance().player != null) { + if (mc.player != null) { NetworkHandler.CHANNEL.sendToServer(new ExecuteKnockedPacket()); } } if (event.phase == TickEvent.Phase.END && CARRY_KEY.consumeClick()) { - Minecraft mc = Minecraft.getInstance(); if (mc.player != null && mc.screen == null) { NetworkHandler.CHANNEL.sendToServer(new CarryTogglePacket()); } } } -} \ No newline at end of file +} diff --git a/src/main/java/net/fretux/knockedback/client/ExecutionProgressOverlay.java b/src/main/java/net/fretux/knockedback/client/ExecutionProgressOverlay.java index 47bb9c5..e410df2 100644 --- a/src/main/java/net/fretux/knockedback/client/ExecutionProgressOverlay.java +++ b/src/main/java/net/fretux/knockedback/client/ExecutionProgressOverlay.java @@ -3,6 +3,7 @@ package net.fretux.knockedback.client; import net.fretux.knockedback.KnockedBack; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.resources.language.I18n; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderGuiOverlayEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -10,22 +11,46 @@ import net.minecraftforge.fml.common.Mod; @Mod.EventBusSubscriber(modid = KnockedBack.MOD_ID, value = Dist.CLIENT) public class ExecutionProgressOverlay { - private static final int BAR_WIDTH = 100; - private static final int BAR_HEIGHT = 8; + private static final int BAR_WIDTH = 132; + private static final int BAR_HEIGHT = 9; @SubscribeEvent public static void onRender(RenderGuiOverlayEvent.Post event) { - if (!ClientExecutionState.isExecuting()) return; + if (!ClientExecutionState.hasProgress()) return; Minecraft mc = Minecraft.getInstance(); GuiGraphics gui = event.getGuiGraphics(); int sw = mc.getWindow().getGuiScaledWidth(); int sh = mc.getWindow().getGuiScaledHeight(); int x = (sw - BAR_WIDTH) / 2; - int y = (sh - BAR_HEIGHT) / 2; - int totalTicks = net.fretux.knockedback.PlayerExecutionHandler.getExecutionTime(); - float progress = 1.0f - (ClientExecutionState.getTimeLeft() / (float) totalTicks); + int y = (sh / 2) + 24; + float progress = 1.0f - (ClientExecutionState.getTimeLeft() / (float) ClientExecutionState.getTotalTicks()); + progress = Math.max(0.0f, Math.min(1.0f, progress)); int fillWidth = (int) (BAR_WIDTH * progress); - gui.fill(x, y, x + BAR_WIDTH, y + BAR_HEIGHT, 0x80000000); - gui.fill(x, y, x + fillWidth, y + BAR_HEIGHT, 0x80FFFFFF); + int fillColor = ClientExecutionState.isHelpUp() ? 0xFF55D987 : 0xFFE05252; + int fillHighlight = ClientExecutionState.isHelpUp() ? 0xFF8EF0B0 : 0xFFFF8A78; + String targetName = ClientExecutionState.getTargetName(); + String label = targetName.isBlank() + ? I18n.get(ClientExecutionState.isHelpUp() + ? "overlay.knockedback.help_up_progress" + : "overlay.knockedback.execution_progress") + : I18n.get(ClientExecutionState.isHelpUp() + ? "overlay.knockedback.helping_up_target" + : "overlay.knockedback.executing_target", targetName); + String percent = Math.round(progress * 100.0f) + "%"; + int maxLabelWidth = BAR_WIDTH - mc.font.width(percent) - 6; + if (mc.font.width(label) > maxLabelWidth) { + label = mc.font.plainSubstrByWidth(label, Math.max(0, maxLabelWidth - 3)) + "..."; + } + int labelY = y - 12; + + gui.drawString(mc.font, label, x, labelY, 0xFFFFFFFF, true); + gui.drawString(mc.font, percent, x + BAR_WIDTH - mc.font.width(percent), labelY, 0xFFE8E8E8, true); + gui.fill(x - 2, y - 2, x + BAR_WIDTH + 2, y + BAR_HEIGHT + 2, 0xCC000000); + gui.fill(x - 1, y - 1, x + BAR_WIDTH + 1, y + BAR_HEIGHT + 1, 0x99FFFFFF); + gui.fill(x, y, x + BAR_WIDTH, y + BAR_HEIGHT, 0xDD101010); + if (fillWidth > 0) { + gui.fill(x, y, x + fillWidth, y + BAR_HEIGHT, fillColor); + gui.fill(x, y, x + fillWidth, y + 2, fillHighlight); + } } -} \ No newline at end of file +} diff --git a/src/main/java/net/fretux/knockedback/config/Config.java b/src/main/java/net/fretux/knockedback/config/Config.java index d0ae1a2..3236027 100644 --- a/src/main/java/net/fretux/knockedback/config/Config.java +++ b/src/main/java/net/fretux/knockedback/config/Config.java @@ -17,6 +17,9 @@ public class Config { public static class Common { public final ForgeConfigSpec.IntValue knockedDuration; public final ForgeConfigSpec.IntValue executionTime; + public final ForgeConfigSpec.IntValue helpUpTime; + public final ForgeConfigSpec.DoubleValue helpUpHealAmount; + public final ForgeConfigSpec.BooleanValue helpUpEnabled; public final ForgeConfigSpec.BooleanValue mobExecutionEnabled; public final ForgeConfigSpec.BooleanValue totemPreventsKnockdown; public final ForgeConfigSpec.ConfigValue> mobExecutionAllowlist; @@ -34,6 +37,18 @@ public class Config { .comment("How long (in ticks) it takes to execute a knocked player. Default: 60 (3 seconds).") .defineInRange("executionTime", 60, 20, 200); + helpUpEnabled = builder + .comment("If true, players can help knocked players recover by holding the help-up key. Default: true.") + .define("helpUpEnabled", true); + + helpUpTime = builder + .comment("How long (in ticks) it takes to help up a knocked player. Default: 60 (3 seconds).") + .defineInRange("helpUpTime", 60, 20, 200); + + helpUpHealAmount = builder + .comment("How much health a helped-up player receives after recovery. Default: 4.0 (2 hearts).") + .defineInRange("helpUpHealAmount", 4.0D, 1.0D, 20.0D); + mobExecutionEnabled = builder .comment("If true, mobs can execute knocked players. Default: true.") .define("mobExecutionEnabled", true); diff --git a/src/main/resources/assets/knockedback/lang/de_de.json b/src/main/resources/assets/knockedback/lang/de_de.json index 1c8ee8a..06d7429 100644 --- a/src/main/resources/assets/knockedback/lang/de_de.json +++ b/src/main/resources/assets/knockedback/lang/de_de.json @@ -1,23 +1,61 @@ { "key.knockedback.carry": "Tragen", "key.knockedback.execute": "Hinrichten", + "key.knockedback.help_up": "Aufhelfen", "key.categories.knockedback": "Knockedback-Steuerung", - - "message.knockedback.cannot_carry_while_knocked": "Du kannst niemanden tragen, während du betäubt bist!", - "message.knockedback.cannot_execute_while_knocked": "Du kannst niemanden hinrichten, während du betäubt bist!", + "message.knockedback.cannot_carry_while_knocked": "Du kannst niemanden tragen, w\u00e4hrend du bet\u00e4ubt bist!", + "message.knockedback.cannot_execute_while_knocked": "Du kannst niemanden hinrichten, w\u00e4hrend du bet\u00e4ubt bist!", "message.knockedback.cannot_execute_carried_player": "Du kannst keinen Spieler hinrichten, der getragen wird!", - + "message.knockedback.cannot_help_while_knocked": "Du kannst niemandem aufhelfen, w\u00e4hrend du bet\u00e4ubt bist!", + "message.knockedback.cannot_help_carried_player": "Du kannst keinem Spieler aufhelfen, der getragen wird!", "message.knockedback.execution_started": "Hinrichtung gestartet...", "message.knockedback.being_executed": "Du wirst hingerichtet!", "message.knockedback.execution_interrupted": "Hinrichtung abgebrochen!", "message.knockedback.execution_interrupted_executor": "Deine Hinrichtung wurde abgebrochen!", - "message.knockedback.picked_up": "%1$s wurde Aufgehoben", - "message.knockedback.being_carried": "Du wirst von %1$s getragen", - "message.knockedback.dropped": "%1$s wurde Abgesetzt", - "message.knockedback.been_dropped": "Du wurdest von %1$s abgesetzt", - "message.knockedback.you_executed": "Du hast %1$s hingerichtet!", + "message.knockedback.help_up_started": "Du hilfst %1$s auf...", + "message.knockedback.being_helped": "%1$s hilft dir auf!", + "message.knockedback.help_up_interrupted": "Aufhelfen abgebrochen!", + "message.knockedback.help_up_interrupted_helper": "Dein Aufhelfen wurde abgebrochen!", + "message.knockedback.you_helped_up": "Du hast %1$s aufgeholfen!", + "message.knockedback.you_were_helped_up": "Dir wurde von %1$s aufgeholfen!", + "message.knockedback.picked_up": "%1$s wurde aufgehoben", + "message.knockedback.being_carried": "Du wirst von %1$s getragen", + "message.knockedback.dropped": "%1$s wurde abgesetzt", + "message.knockedback.been_dropped": "Du wurdest von %1$s abgesetzt", + "message.knockedback.you_executed": "Du hast %1$s hingerichtet!", "message.knockedback.you_were_executed": "Du wurdest von %1$s hingerichtet!", + "overlay.knockedback.execution_progress": "Hinrichten", + "overlay.knockedback.help_up_progress": "Aufhelfen", + "overlay.knockedback.executing_target": "%s hinrichten...", + "overlay.knockedback.helping_up_target": "%s aufhelfen...", "overlay.knockedback.recovery_time": "Zeit bis Erholung: %s s", - "death.attack.knockedback_execution": "%1$s wurde gnadenlos hingerichtet!" + "death.attack.knockedback_execution": "%1$s wurde gnadenlos hingerichtet!", + "advancements.knockedback.root.title": "Knocked Back", + "advancements.knockedback.root.description": "\u00dcberlebe, rette andere oder beende den Kampf, w\u00e4hrend der Tod wartet.", + "advancements.knockedback.knocked.title": "Noch nicht tot", + "advancements.knockedback.knocked.description": "Werde bet\u00e4ubt.", + "advancements.knockedback.knocked_10.title": "Hart im Nehmen", + "advancements.knockedback.knocked_10.description": "Werde 10-mal bet\u00e4ubt.", + "advancements.knockedback.knocked_50.title": "Bleibt nicht liegen", + "advancements.knockedback.knocked_50.description": "Werde 50-mal bet\u00e4ubt.", + "advancements.knockedback.execute_player.title": "Gnadensto\u00df", + "advancements.knockedback.execute_player.description": "Richte einen bet\u00e4ubten Spieler hin.", + "advancements.knockedback.execute_player_10.title": "Keine offenen Enden", + "advancements.knockedback.execute_player_10.description": "Richte 10 bet\u00e4ubte Spieler hin.", + "advancements.knockedback.execute_player_100.title": "Gnadenlos", + "advancements.knockedback.execute_player_100.description": "Richte 100 bet\u00e4ubte Spieler hin.", + "advancements.knockedback.help_up.title": "Feldsanit\u00e4ter", + "advancements.knockedback.help_up.description": "Hilf einem bet\u00e4ubten Spieler wieder auf die Beine.", + "advancements.knockedback.help_up_10.title": "Kampfsanit\u00e4ter", + "advancements.knockedback.help_up_10.description": "Hilf 10 bet\u00e4ubten Spielern auf.", + "advancements.knockedback.help_up_50.title": "Schutzengel", + "advancements.knockedback.help_up_50.description": "Hilf 50 bet\u00e4ubten Spielern auf.", + "advancements.knockedback.be_helped_up.title": "Zweiter Atem", + "advancements.knockedback.be_helped_up.description": "Lass dir von einem anderen Spieler aufhelfen.", + "advancements.knockedback.carry_player.title": "Niemand bleibt zur\u00fcck", + "advancements.knockedback.carry_player.description": "Trage einen bet\u00e4ubten Spieler aus der Gefahr.", + "advancements.knockedback.carry_player_10.title": "Evakuierungsteam", + "advancements.knockedback.carry_player_10.description": "Trage 10 bet\u00e4ubte Spieler.", + "advancements.knockedback.carry_player_50.title": "Bergungsexperte", + "advancements.knockedback.carry_player_50.description": "Trage 50 bet\u00e4ubte Spieler." } - diff --git a/src/main/resources/assets/knockedback/lang/en_us.json b/src/main/resources/assets/knockedback/lang/en_us.json index 4c4b367..aec7882 100644 --- a/src/main/resources/assets/knockedback/lang/en_us.json +++ b/src/main/resources/assets/knockedback/lang/en_us.json @@ -1,10 +1,13 @@ { "key.knockedback.carry": "Carry", "key.knockedback.execute": "Execute", + "key.knockedback.help_up": "Help Up", "key.categories.knockedback": "Knockedback Controls", "message.knockedback.cannot_carry_while_knocked": "You cannot carry others while you are knocked!", "message.knockedback.cannot_execute_while_knocked": "You cannot execute others while you are knocked!", "message.knockedback.cannot_execute_carried_player": "You cannot execute a player who is being carried!", + "message.knockedback.cannot_help_while_knocked": "You cannot help others while you are knocked!", + "message.knockedback.cannot_help_carried_player": "You cannot help up a player who is being carried!", "message.knockedback.picked_up": "Picked up %s", "message.knockedback.being_carried": "You are being carried by %s", "message.knockedback.dropped": "Dropped %s", @@ -13,8 +16,46 @@ "message.knockedback.being_executed": "You're being executed!", "message.knockedback.execution_interrupted": "Execution interrupted!", "message.knockedback.execution_interrupted_executor": "Your execution was interrupted!", + "message.knockedback.help_up_started": "Helping up %s...", + "message.knockedback.being_helped": "%s is helping you up!", + "message.knockedback.help_up_interrupted": "Help interrupted!", + "message.knockedback.help_up_interrupted_helper": "Your help-up was interrupted!", + "message.knockedback.you_helped_up": "You helped up %s!", + "message.knockedback.you_were_helped_up": "You were helped up by %s!", "message.knockedback.you_executed": "You executed %s!", "message.knockedback.you_were_executed": "You were executed by %s!", + "overlay.knockedback.execution_progress": "Executing", + "overlay.knockedback.help_up_progress": "Helping up", + "overlay.knockedback.executing_target": "Executing %s...", + "overlay.knockedback.helping_up_target": "Helping up %s...", "overlay.knockedback.recovery_time": "Time until recovery: %s s", - "death.attack.knockedback_execution": "%1$s was executed mercilessly." + "death.attack.knockedback_execution": "%1$s was executed mercilessly.", + "advancements.knockedback.root.title": "Knocked Back", + "advancements.knockedback.root.description": "Survive, rescue, or finish the fight while death is waiting.", + "advancements.knockedback.knocked.title": "Not Dead Yet", + "advancements.knockedback.knocked.description": "Enter the knocked state.", + "advancements.knockedback.knocked_10.title": "Built Different", + "advancements.knockedback.knocked_10.description": "Enter the knocked state 10 times.", + "advancements.knockedback.knocked_50.title": "Refuses to Stay Down", + "advancements.knockedback.knocked_50.description": "Enter the knocked state 50 times.", + "advancements.knockedback.execute_player.title": "Coup de Grace", + "advancements.knockedback.execute_player.description": "Execute a knocked player.", + "advancements.knockedback.execute_player_10.title": "No Loose Ends", + "advancements.knockedback.execute_player_10.description": "Execute 10 knocked players.", + "advancements.knockedback.execute_player_100.title": "Merciless", + "advancements.knockedback.execute_player_100.description": "Execute 100 knocked players.", + "advancements.knockedback.help_up.title": "Field Medic", + "advancements.knockedback.help_up.description": "Help a knocked player back onto their feet.", + "advancements.knockedback.help_up_10.title": "Combat Medic", + "advancements.knockedback.help_up_10.description": "Help up 10 knocked players.", + "advancements.knockedback.help_up_50.title": "Guardian Angel", + "advancements.knockedback.help_up_50.description": "Help up 50 knocked players.", + "advancements.knockedback.be_helped_up.title": "Second Wind", + "advancements.knockedback.be_helped_up.description": "Get helped up by another player.", + "advancements.knockedback.carry_player.title": "No One Left Behind", + "advancements.knockedback.carry_player.description": "Carry a knocked player out of danger.", + "advancements.knockedback.carry_player_10.title": "Evac Team", + "advancements.knockedback.carry_player_10.description": "Carry 10 knocked players.", + "advancements.knockedback.carry_player_50.title": "Extraction Expert", + "advancements.knockedback.carry_player_50.description": "Carry 50 knocked players." } diff --git a/src/main/resources/data/knockedback/advancements/be_helped_up.json b/src/main/resources/data/knockedback/advancements/be_helped_up.json new file mode 100644 index 0000000..f6f7968 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/be_helped_up.json @@ -0,0 +1,22 @@ +{ + "parent": "knockedback:knocked", + "display": { + "icon": { + "item": "minecraft:honey_bottle" + }, + "title": { + "translate": "advancements.knockedback.be_helped_up.title" + }, + "description": { + "translate": "advancements.knockedback.be_helped_up.description" + }, + "frame": "task", + "show_toast": true, + "announce_to_chat": false + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + } +} diff --git a/src/main/resources/data/knockedback/advancements/carry_player.json b/src/main/resources/data/knockedback/advancements/carry_player.json new file mode 100644 index 0000000..175b1d4 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/carry_player.json @@ -0,0 +1,22 @@ +{ + "parent": "knockedback:knocked", + "display": { + "icon": { + "item": "minecraft:lead" + }, + "title": { + "translate": "advancements.knockedback.carry_player.title" + }, + "description": { + "translate": "advancements.knockedback.carry_player.description" + }, + "frame": "task", + "show_toast": true, + "announce_to_chat": false + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + } +} diff --git a/src/main/resources/data/knockedback/advancements/carry_player_10.json b/src/main/resources/data/knockedback/advancements/carry_player_10.json new file mode 100644 index 0000000..d8a5201 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/carry_player_10.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:carry_player", + "display": { + "icon": { + "item": "minecraft:saddle" + }, + "title": { + "translate": "advancements.knockedback.carry_player_10.title" + }, + "description": { + "translate": "advancements.knockedback.carry_player_10.description" + }, + "frame": "goal", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 50 + } +} diff --git a/src/main/resources/data/knockedback/advancements/carry_player_50.json b/src/main/resources/data/knockedback/advancements/carry_player_50.json new file mode 100644 index 0000000..921bd47 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/carry_player_50.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:carry_player_10", + "display": { + "icon": { + "item": "minecraft:elytra" + }, + "title": { + "translate": "advancements.knockedback.carry_player_50.title" + }, + "description": { + "translate": "advancements.knockedback.carry_player_50.description" + }, + "frame": "challenge", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 150 + } +} diff --git a/src/main/resources/data/knockedback/advancements/execute_player.json b/src/main/resources/data/knockedback/advancements/execute_player.json new file mode 100644 index 0000000..3648ce5 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/execute_player.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:knocked", + "display": { + "icon": { + "item": "minecraft:iron_sword" + }, + "title": { + "translate": "advancements.knockedback.execute_player.title" + }, + "description": { + "translate": "advancements.knockedback.execute_player.description" + }, + "frame": "challenge", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 25 + } +} diff --git a/src/main/resources/data/knockedback/advancements/execute_player_10.json b/src/main/resources/data/knockedback/advancements/execute_player_10.json new file mode 100644 index 0000000..c4e8c04 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/execute_player_10.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:execute_player", + "display": { + "icon": { + "item": "minecraft:diamond_sword" + }, + "title": { + "translate": "advancements.knockedback.execute_player_10.title" + }, + "description": { + "translate": "advancements.knockedback.execute_player_10.description" + }, + "frame": "goal", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 50 + } +} diff --git a/src/main/resources/data/knockedback/advancements/execute_player_100.json b/src/main/resources/data/knockedback/advancements/execute_player_100.json new file mode 100644 index 0000000..c12cdad --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/execute_player_100.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:execute_player_10", + "display": { + "icon": { + "item": "minecraft:netherite_sword" + }, + "title": { + "translate": "advancements.knockedback.execute_player_100.title" + }, + "description": { + "translate": "advancements.knockedback.execute_player_100.description" + }, + "frame": "challenge", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 250 + } +} diff --git a/src/main/resources/data/knockedback/advancements/help_up.json b/src/main/resources/data/knockedback/advancements/help_up.json new file mode 100644 index 0000000..707668a --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/help_up.json @@ -0,0 +1,22 @@ +{ + "parent": "knockedback:knocked", + "display": { + "icon": { + "item": "minecraft:golden_apple" + }, + "title": { + "translate": "advancements.knockedback.help_up.title" + }, + "description": { + "translate": "advancements.knockedback.help_up.description" + }, + "frame": "goal", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + } +} diff --git a/src/main/resources/data/knockedback/advancements/help_up_10.json b/src/main/resources/data/knockedback/advancements/help_up_10.json new file mode 100644 index 0000000..fe56f85 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/help_up_10.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:help_up", + "display": { + "icon": { + "item": "minecraft:glistering_melon_slice" + }, + "title": { + "translate": "advancements.knockedback.help_up_10.title" + }, + "description": { + "translate": "advancements.knockedback.help_up_10.description" + }, + "frame": "goal", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 50 + } +} diff --git a/src/main/resources/data/knockedback/advancements/help_up_50.json b/src/main/resources/data/knockedback/advancements/help_up_50.json new file mode 100644 index 0000000..7abc345 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/help_up_50.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:help_up_10", + "display": { + "icon": { + "item": "minecraft:enchanted_golden_apple" + }, + "title": { + "translate": "advancements.knockedback.help_up_50.title" + }, + "description": { + "translate": "advancements.knockedback.help_up_50.description" + }, + "frame": "challenge", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 150 + } +} diff --git a/src/main/resources/data/knockedback/advancements/knocked.json b/src/main/resources/data/knockedback/advancements/knocked.json new file mode 100644 index 0000000..b38f633 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/knocked.json @@ -0,0 +1,22 @@ +{ + "parent": "knockedback:root", + "display": { + "icon": { + "item": "minecraft:redstone" + }, + "title": { + "translate": "advancements.knockedback.knocked.title" + }, + "description": { + "translate": "advancements.knockedback.knocked.description" + }, + "frame": "task", + "show_toast": true, + "announce_to_chat": false + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + } +} diff --git a/src/main/resources/data/knockedback/advancements/knocked_10.json b/src/main/resources/data/knockedback/advancements/knocked_10.json new file mode 100644 index 0000000..e38e842 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/knocked_10.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:knocked", + "display": { + "icon": { + "item": "minecraft:shield" + }, + "title": { + "translate": "advancements.knockedback.knocked_10.title" + }, + "description": { + "translate": "advancements.knockedback.knocked_10.description" + }, + "frame": "goal", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 50 + } +} diff --git a/src/main/resources/data/knockedback/advancements/knocked_50.json b/src/main/resources/data/knockedback/advancements/knocked_50.json new file mode 100644 index 0000000..ae9f497 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/knocked_50.json @@ -0,0 +1,25 @@ +{ + "parent": "knockedback:knocked_10", + "display": { + "icon": { + "item": "minecraft:netherite_chestplate" + }, + "title": { + "translate": "advancements.knockedback.knocked_50.title" + }, + "description": { + "translate": "advancements.knockedback.knocked_50.description" + }, + "frame": "challenge", + "show_toast": true, + "announce_to_chat": true + }, + "criteria": { + "triggered": { + "trigger": "minecraft:impossible" + } + }, + "rewards": { + "experience": 150 + } +} diff --git a/src/main/resources/data/knockedback/advancements/root.json b/src/main/resources/data/knockedback/advancements/root.json new file mode 100644 index 0000000..2552912 --- /dev/null +++ b/src/main/resources/data/knockedback/advancements/root.json @@ -0,0 +1,22 @@ +{ + "display": { + "icon": { + "item": "minecraft:totem_of_undying" + }, + "title": { + "translate": "advancements.knockedback.root.title" + }, + "description": { + "translate": "advancements.knockedback.root.description" + }, + "background": "minecraft:textures/gui/advancements/backgrounds/adventure.png", + "frame": "task", + "show_toast": false, + "announce_to_chat": false + }, + "criteria": { + "triggered": { + "trigger": "minecraft:tick" + } + } +}