diff --git a/modules/map-core/src/main/java/net/hollowcube/mapmaker/map/MapPlayer.java b/modules/map-core/src/main/java/net/hollowcube/mapmaker/map/MapPlayer.java index b1a11ae34..02e603fee 100644 --- a/modules/map-core/src/main/java/net/hollowcube/mapmaker/map/MapPlayer.java +++ b/modules/map-core/src/main/java/net/hollowcube/mapmaker/map/MapPlayer.java @@ -133,7 +133,7 @@ public abstract class MapPlayer extends CommandHandlingPlayer implements MiscFun private final IntList ownedEntities = new IntArrayList(); - private final Object2IntMap cooldownGroups = new Object2IntArrayMap<>(); + private final Object2IntMap cooldowns = new Object2IntArrayMap<>(); private Function visibilityFunc = null; // entity id -> visibility ordinal @@ -227,6 +227,7 @@ public void update(long time) { super.update(time); physicsTick(); + cooldownTick(); riptideTick(); fallTick(); weatherTick(); @@ -417,15 +418,49 @@ public boolean tryUseItem(@NotNull ItemStack itemStack) { if (useCooldown == null || useCooldown.cooldownGroup() == null) return true; - int cooldownEnd = cooldownGroups.getInt(useCooldown.cooldownGroup()); - if (cooldownEnd > getAliveTicks()) return false; // Still in cooldown + int cooldown = cooldowns.getInt(useCooldown.cooldownGroup()); + if (cooldown > 0) return false; // Still in cooldown int cooldownTicks = (int) (useCooldown.seconds() * 20); - cooldownGroups.put(useCooldown.cooldownGroup(), (int) (getAliveTicks() + cooldownTicks)); + cooldowns.put(useCooldown.cooldownGroup(), cooldownTicks); sendPacket(new SetCooldownPacket(useCooldown.cooldownGroup(), cooldownTicks)); return true; } + private void cooldownTick() { + // Tick cooldown + var iterator = cooldowns.object2IntEntrySet().iterator(); + while (iterator.hasNext()) { + Object2IntMap.Entry cooldown = iterator.next(); + int newCooldown = cooldown.getIntValue() - 1; + if (newCooldown <= 0) { + iterator.remove(); + sendPacket(new SetCooldownPacket(cooldown.getKey(), 0)); // Make sure the client is synced + } else { + cooldown.setValue(newCooldown); + } + } + } + + public void setCooldowns(Map newCooldowns) { + + for (Object2IntMap.Entry cooldown : cooldowns.object2IntEntrySet()) { + if (!newCooldowns.containsKey(cooldown.getKey())) + sendPacket(new SetCooldownPacket(cooldown.getKey(), 0)); // Reset the cooldown for the client + } + + for (Map.Entry cooldown : newCooldowns.entrySet()) { + sendPacket(new SetCooldownPacket(cooldown.getKey(), cooldown.getValue())); + } + + cooldowns.clear(); + cooldowns.putAll(newCooldowns); + } + + public Map getCooldowns() { + return Map.copyOf(cooldowns); + } + //endregion //region EXT: Per player visibility diff --git a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/PlayState.java b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/PlayState.java index c78bf9c18..cc8e933e6 100644 --- a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/PlayState.java +++ b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/PlayState.java @@ -37,11 +37,15 @@ public static Attachment attachment(Key key, Codec codec, Function> GHOST_BLOCKS_CODEC = StructCodec.struct( "ghostBlocks", ExtraCodecs.LONG_STRING.mapValue(ExtraCodecs.BLOCK_STATE_STRING).optional(Map.of()), Function.identity(), it -> it); + private static final StructCodec> COOLDOWN_GROUPS_CODEC = StructCodec.struct( + "cooldownGroups", Codec.STRING.mapValue(Codec.INT).optional(Map.of()), Function.identity(), + it -> it); public static Codec CODEC = net.minestom.server.codec.Codec.Recursive(codec -> StructCodec.struct( "lastState", codec.optional(), PlayState::lastState, "history", net.minestom.server.codec.Codec.STRING.list().optional(List.of()), PlayState::history, "pos", ExtraCodecs.POS.optional(), PlayState::pos, StructCodec.INLINE, GHOST_BLOCKS_CODEC.optional(), PlayState::ghostBlocks, + StructCodec.INLINE, COOLDOWN_GROUPS_CODEC.optional(), PlayState::cooldownGroups, StructCodec.INLINE, new ActionDataCodec(Integer.MAX_VALUE), PlayState::actionData, PlayState::new)); @@ -54,21 +58,24 @@ StructCodec.INLINE, new ActionDataCodec(Integer.MAX_VALUE), PlayState::actionDat private final List history; private @Nullable Pos pos; private Map ghostBlocks; + private Map cooldownGroups; private Map, Object> actionData; public PlayState() { - this(null, null, null, null, null); + this(null, null, null, null, null, null); } public PlayState( @Nullable PlayState lastState, @Nullable List statusEffects, @Nullable Pos pos, @Nullable Map ghostBlocks, + @Nullable Map cooldownGroups, @Nullable Map, Object> actionData ) { this.lastState = lastState; this.history = new ArrayList<>(Objects.requireNonNullElse(statusEffects, List.of())); this.pos = pos; this.ghostBlocks = new HashMap<>(Objects.requireNonNullElse(ghostBlocks, Map.of())); + this.cooldownGroups = new HashMap<>(Objects.requireNonNullElse(cooldownGroups, Map.of())); this.actionData = new HashMap<>(Objects.requireNonNullElse(actionData, Map.of())); } @@ -127,6 +134,14 @@ public void setGhostBlocks(Map blockMap) { this.ghostBlocks = new HashMap<>(blockMap); } + public Map cooldownGroups() { + return cooldownGroups; + } + + public void setCooldownGroups(Map cooldownGroups) { + this.cooldownGroups = new HashMap<>(cooldownGroups); + } + public void setLastState(@Nullable PlayState lastState) { this.lastState = lastState == null ? null : lastState.copy(); } @@ -149,7 +164,7 @@ public PlayState copy() { //noinspection unchecked newActionData.put(entry.getKey(), ((Attachment) entry.getKey()).copyValue(entry.getValue())); } - return new PlayState(lastState, history, pos, new HashMap<>(ghostBlocks), newActionData); + return new PlayState(lastState, history, pos, new HashMap<>(ghostBlocks), new HashMap<>(cooldownGroups), newActionData); } public void setFrom(PlayState other) { diff --git a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/ParkourState.java b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/ParkourState.java index 1ae825e05..7411f5223 100644 --- a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/ParkourState.java +++ b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/ParkourState.java @@ -149,6 +149,10 @@ default void resetPlayer(ParkourMapWorld world, Player player, @Nullable Parkour ((MapPlayer) player).removeOwnedEntities(); ((MapPlayer) player).resetTouchingState(); + if (playState.lastState() != null) { + ((MapPlayer) player).setCooldowns(playState.lastState().cooldownGroups()); + } + // The following is deinit logic which should not happen when switching from play to play (aka checkpoint reset). if (nextState instanceof AnyPlaying) return; diff --git a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/TempEffectApplicator.java b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/TempEffectApplicator.java index 76007085a..498677ffd 100644 --- a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/TempEffectApplicator.java +++ b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/TempEffectApplicator.java @@ -112,6 +112,7 @@ public static void applyCheckpoint(ParkourMapWorld world, ActionTriggerData data newHistory, respawnPosition, Map.copyOf(playState.ghostBlocks()), + Map.copyOf(playState.cooldownGroups()), Map.copyOf(playState.actionData()) )); diff --git a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/action/LegacyActionStateManager.java b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/action/LegacyActionStateManager.java index f2a8130e8..e2bbcc2ba 100644 --- a/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/action/LegacyActionStateManager.java +++ b/modules/map-runtime/src/main/java/net/hollowcube/mapmaker/runtime/parkour/action/LegacyActionStateManager.java @@ -1,6 +1,7 @@ package net.hollowcube.mapmaker.runtime.parkour.action; import net.hollowcube.common.util.OpUtils; +import net.hollowcube.mapmaker.map.MapPlayer; import net.hollowcube.mapmaker.map.block.ghost.GhostBlockHolder; import net.hollowcube.mapmaker.map.entity.potion.PotionEffectList; import net.hollowcube.mapmaker.runtime.PlayState; @@ -142,6 +143,9 @@ private static void handleUpdateStateFromPlayer(ParkourMapPlayerStateUpdateEvent var ghostBlocks = GhostBlockHolder.forPlayerOptional(player); playState.setGhostBlocks(ghostBlocks == null ? Map.of() : ghostBlocks.save()); + var cooldowns = ((MapPlayer) player).getCooldowns(); + playState.setCooldownGroups(cooldowns); + var items = playState.get(Attachments.HOTBAR_ITEMS, HotbarItems.EMPTY); playState.set(Attachments.HOTBAR_ITEMS, new HotbarItems( OpUtils.map(items.item0(), item -> updateItemStack(player, item, 3)),