From e2039260fc80c732b1b5c5cd433bb6f70dc3ba51 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 17 Mar 2026 18:55:42 +0100 Subject: refactor(): use ticks for timer --- src/main/java/world/anhgelus/manhunt/Manhunt.java | 70 ++++++++++----------- .../anhgelus/manhunt/mixins/WorldTimerAccess.java | 44 +++++++++++++ .../world/anhgelus/manhunt/timer/TickTask.java | 72 ++++++++++++++++++++++ .../world/anhgelus/manhunt/timer/TimerAccess.java | 64 +++++++++++++++++++ src/main/resources/fabric.mod.json | 2 +- src/main/resources/manhunt.mixins.json | 3 +- 6 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 src/main/java/world/anhgelus/manhunt/mixins/WorldTimerAccess.java create mode 100644 src/main/java/world/anhgelus/manhunt/timer/TickTask.java create mode 100644 src/main/java/world/anhgelus/manhunt/timer/TimerAccess.java (limited to 'src') diff --git a/src/main/java/world/anhgelus/manhunt/Manhunt.java b/src/main/java/world/anhgelus/manhunt/Manhunt.java index dc14731..66b8fb4 100644 --- a/src/main/java/world/anhgelus/manhunt/Manhunt.java +++ b/src/main/java/world/anhgelus/manhunt/Manhunt.java @@ -8,7 +8,6 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.minecraft.command.EntitySelector; import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.component.DataComponentTypes; @@ -34,6 +33,8 @@ import net.minecraft.util.math.GlobalPos; import net.minecraft.world.GameMode; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import world.anhgelus.manhunt.timer.TickTask; +import world.anhgelus.manhunt.timer.TimerAccess; import java.util.*; @@ -47,15 +48,12 @@ public class Manhunt implements ModInitializer { private final Set speedrunners = new HashSet<>(); private final Map trackedMap = new HashMap<>(); - private final Timer timer = new Timer(); private State state = State.OFF; @Override public void onInitialize() { LOGGER.info("Initializing Manhunt"); MidnightConfig.init(MOD_ID, Config.class); - LOGGER.info(Config.secondsBeforeRelease); - LOGGER.info(Config.updateCompassEach); final LiteralArgumentBuilder command = literal("manhunt"); @@ -100,11 +98,14 @@ public class Manhunt implements ModInitializer { final LiteralArgumentBuilder start = literal("start"); start.requires(CommandManager.requirePermissionLevel(CommandManager.GAMEMASTERS_CHECK)); start.executes(context -> { + final var source = context.getSource(); if (state == State.ON) { - context.getSource().sendFeedback(() -> Text.literal("Cannot start a manhunt if one is already started!"), false); + source.sendFeedback(() -> Text.literal("Cannot start a manhunt if one is already started!"), false); return Command.SINGLE_SUCCESS; } - final PlayerManager pm = context.getSource().getServer().getPlayerManager(); + final var server = source.getServer(); + final var timer = TimerAccess.getTimerFromOverworld(server); + final PlayerManager pm = server.getPlayerManager(); for (final ServerPlayerEntity player : pm.getPlayerList()) { player.setHealth(player.getMaxHealth()); player.setExperienceLevel(0); @@ -142,26 +143,24 @@ public class Manhunt implements ModInitializer { } LOGGER.info("Added modifiers to {}", hunter.getDisplayName()); } - timer.schedule(new TimerTask() { - @Override - public void run() { - LOGGER.info("Removing modifier to hunters"); - for (final UUID uuid : hunters) { - final var hunter = pm.getPlayer(uuid); - assert hunter != null; - var attr = hunter.getAttributeInstance(EntityAttributes.MOVEMENT_SPEED); - if (attr != null) { - attr.removeModifier(Identifier.of("manhunt.speed")); - } - attr = hunter.getAttributeInstance(EntityAttributes.GRAVITY); - if (attr != null) { - attr.removeModifier(Identifier.of("manhunt.gravity")); - } + timer.dds_runTask(new TickTask(() -> { + LOGGER.info("Removing modifier to hunters"); + for (final UUID uuid : hunters) { + final var hunter = pm.getPlayer(uuid); + assert hunter != null; + var attr = hunter.getAttributeInstance(EntityAttributes.MOVEMENT_SPEED); + if (attr != null) { + attr.removeModifier(Identifier.of("manhunt.speed")); + } + attr = hunter.getAttributeInstance(EntityAttributes.GRAVITY); + if (attr != null) { + attr.removeModifier(Identifier.of("manhunt.gravity")); } } - }, Config.secondsBeforeRelease * 1000L); + pm.broadcast(Text.of("Hunters released!"), false); + }, Config.secondsBeforeRelease * 20L)); setTimer(pm); - context.getSource().sendFeedback(() -> Text.literal("Game started!"), true); + source.sendFeedback(() -> Text.literal("Game started!"), true); return Command.SINGLE_SUCCESS; }); @@ -199,7 +198,7 @@ public class Manhunt implements ModInitializer { speedrunners.remove(player.getUuid()); } state = State.OFF; - timer.cancel(); + TimerAccess.getTimerFromOverworld(server).dds_cancel(); }); ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> { @@ -207,25 +206,18 @@ public class Manhunt implements ModInitializer { EntityType.PIGLIN.spawn(world, entity.getBlockPos(), SpawnReason.MOB_SUMMONED); entity.discard(); }); - - ServerLifecycleEvents.SERVER_STOPPED.register((server) -> { - timer.cancel(); - }); } private void setTimer(PlayerManager pm) { - timer.schedule(new TimerTask() { - @Override - public void run() { - for (final UUID uuid : hunters) { - final ServerPlayerEntity hunter = pm.getPlayer(uuid); - if (hunter == null) continue; - final ServerPlayerEntity tracked = pm.getPlayer(trackedMap.get(uuid)); - if (tracked == null) continue; - updateCompass(hunter, tracked); - } + TimerAccess.getTimerFromOverworld(pm.getServer()).dds_runTask(new TickTask(() -> { + for (final UUID uuid : hunters) { + final ServerPlayerEntity hunter = pm.getPlayer(uuid); + if (hunter == null) continue; + final ServerPlayerEntity tracked = pm.getPlayer(trackedMap.get(uuid)); + if (tracked == null) continue; + updateCompass(hunter, tracked); } - }, Config.secondsBeforeRelease * 1000L, Config.updateCompassEach * 1000L); + }, Config.secondsBeforeRelease * 20L, Config.updateCompassEach * 20L)); } private void updateCompass(ServerPlayerEntity player, ServerPlayerEntity tracked) { diff --git a/src/main/java/world/anhgelus/manhunt/mixins/WorldTimerAccess.java b/src/main/java/world/anhgelus/manhunt/mixins/WorldTimerAccess.java new file mode 100644 index 0000000..6919b3b --- /dev/null +++ b/src/main/java/world/anhgelus/manhunt/mixins/WorldTimerAccess.java @@ -0,0 +1,44 @@ +package world.anhgelus.manhunt.mixins; + +import net.minecraft.server.world.ServerWorld; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import world.anhgelus.manhunt.timer.TimerAccess; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; + +@Mixin(ServerWorld.class) +public class WorldTimerAccess implements TimerAccess { + @Unique + private final List tasks = new ArrayList<>(); + + @Unique + private final List tasksToAdd = new ArrayList<>(); + + @Inject(method = "tick", at = @At("TAIL")) + private void onTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) { + tasks.stream().filter(TickTask::isRunning).forEach(TickTask::tick); + tasks.addAll(tasksToAdd); + tasksToAdd.clear(); + } + + @Override + public void dds_runTask(TimerAccess.TickTask task) { + tasksToAdd.add(task); + } + + @Override + public void dds_cancel() { + tasks.stream().filter(TickTask::isRunning).forEach(TickTask::cancel); + } + + @Override + public List dds_getTasks() { + return tasks.stream().filter(TickTask::isRunning).toList(); + } +} \ No newline at end of file diff --git a/src/main/java/world/anhgelus/manhunt/timer/TickTask.java b/src/main/java/world/anhgelus/manhunt/timer/TickTask.java new file mode 100644 index 0000000..5449166 --- /dev/null +++ b/src/main/java/world/anhgelus/manhunt/timer/TickTask.java @@ -0,0 +1,72 @@ +package world.anhgelus.manhunt.timer; + +/** + * Represents a complete task called each tick + */ +public class TickTask implements TimerAccess.TickTask { + public final long ticksDelay; + public final long ticksRepeat; + public final boolean repeating; + public final TimerAccess.Task task; + private boolean cancelled = false; + private long currentTicking; + + /** + * Create a new repeating TickTask + * + * @param task Task to run after the delay or the repeat time + * @param ticksDelay Delay before the first task's run + * @param ticksRepeat Repeat each tick (if the repeat is 0, it will repeat each tick, if it is below 0, it will not repeat) + * @throws IllegalArgumentException if ticksDelay is below 0 + */ + public TickTask(TimerAccess.Task task, long ticksDelay, long ticksRepeat) { + if (ticksDelay < 0) throw new IllegalArgumentException("Ticks delay must be non-negative"); + this.ticksDelay = ticksDelay; + this.ticksRepeat = ticksRepeat; + this.task = task; + repeating = ticksRepeat >= 0; + currentTicking = ticksDelay; + } + + /** + * Create a new delayed TickTask + * + * @param task Task to run after the delay or the repeat time + * @param ticksDelay Delay before the first task's run + * @throws IllegalArgumentException if ticksDelay or if ticksRepeat is below 0 + */ + public TickTask(TimerAccess.Task task, long ticksDelay) { + if (ticksDelay < 0) throw new IllegalArgumentException("Ticks delay must be non-negative"); + this.ticksDelay = ticksDelay; + this.ticksRepeat = -1; + this.task = task; + repeating = false; + currentTicking = ticksDelay; + } + + public void tick() { + if (--currentTicking > 0) return; + task.run(); + if (repeating) { + currentTicking = ticksRepeat; + } else { + cancel(); + } + } + + public long cancel() { + if (cancelled) throw new IllegalStateException("Task already cancelled"); + cancelled = true; + return currentTicking; + } + + public boolean isRunning() { + return !cancelled; + } + + @Override + public long getTickingBeforeRun() { + if (cancelled) return -1; + return currentTicking; + } +} \ No newline at end of file diff --git a/src/main/java/world/anhgelus/manhunt/timer/TimerAccess.java b/src/main/java/world/anhgelus/manhunt/timer/TimerAccess.java new file mode 100644 index 0000000..f9ed08e --- /dev/null +++ b/src/main/java/world/anhgelus/manhunt/timer/TimerAccess.java @@ -0,0 +1,64 @@ +package world.anhgelus.manhunt.timer; + +import net.minecraft.server.MinecraftServer; + +import java.util.List; + +public interface TimerAccess { + /** + * Get the timer linked to the overworld + * + * @param server Current server + * @return TimerAccess linked to the overworld + */ + static TimerAccess getTimerFromOverworld(MinecraftServer server) { + final var timer = (TimerAccess) server.getOverworld(); + if (timer == null) + throw new NullPointerException("Impossible to get TimerAccess from the overworld (it is null)"); + return timer; + } + + /** + * Run a task (called each tick ticked) + * + * @param task Task to run + */ + void dds_runTask(TimerAccess.TickTask task); + + void dds_cancel(); + + /** + * @return All non-cancelled tasks + */ + List dds_getTasks(); + + interface TickTask { + /** + * Tick the task + */ + void tick(); + + /** + * Cancel the task + * + * @return the remaining ticks before the run of the Task + * @throws IllegalStateException if the task is already cancelled + */ + long cancel(); + + boolean isRunning(); + + /** + * @return the number of ticks before run of the task (if the task is cancelled, returns -1) + */ + long getTickingBeforeRun(); + } + + /** + * Represents a task to run after ticking + */ + @FunctionalInterface + interface Task { + void run(); + } +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d218be5..abbd844 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -14,7 +14,7 @@ }, "license": "AGPL-3.0", "icon": "assets/manhunt/icon.png", - "environment": "server", + "environment": "*", "entrypoints": { "main": [ "world.anhgelus.manhunt.Manhunt" diff --git a/src/main/resources/manhunt.mixins.json b/src/main/resources/manhunt.mixins.json index 994072c..6613627 100644 --- a/src/main/resources/manhunt.mixins.json +++ b/src/main/resources/manhunt.mixins.json @@ -3,7 +3,8 @@ "package": "world.anhgelus.manhunt.mixins", "compatibilityLevel": "JAVA_21", "mixins": [ - "Tracker" + "Tracker", + "WorldTimerAccess" ], "injectors": { "defaultRequire": 1 -- cgit v1.2.3