From 3071651ab689a499ee149f73e53b8873401cc51c Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Thu, 1 Jan 2026 16:32:48 +0100 Subject: [PATCH] feat: Mobs following player logic --- .../java/cz/jzitnik/config/Debugging.java | 4 +- .../events/handlers/FullRoomDrawHandler.java | 15 +- .../handlers/RoomChangeEventHandler.java | 25 ++-- .../cz/jzitnik/game/mobs/HittableMob.java | 6 + src/main/java/cz/jzitnik/game/mobs/Mob.java | 12 +- .../cz/jzitnik/game/mobs/tasks/AStarAlg.java | 133 ++++++++++++++++++ .../tasks/BlindMobFollowingPlayerTask.java | 60 ++++++++ .../mobs/tasks/MobFollowingPlayerTask.java | 99 +++++++++++++ .../cz/jzitnik/game/setup/mobs/Zombie.java | 5 +- .../java/cz/jzitnik/game/utils/RoomCords.java | 11 +- .../java/cz/jzitnik/states/SoundState.java | 1 + .../cz/jzitnik/threads/MicrophoneThread.java | 1 + .../cz/jzitnik/utils/roomtasks/RoomTask.java | 10 +- .../utils/roomtasks/RoomTaskScheduler.java | 23 ++- 14 files changed, 378 insertions(+), 27 deletions(-) create mode 100644 src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java create mode 100644 src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java create mode 100644 src/main/java/cz/jzitnik/game/mobs/tasks/MobFollowingPlayerTask.java diff --git a/src/main/java/cz/jzitnik/config/Debugging.java b/src/main/java/cz/jzitnik/config/Debugging.java index 1f46f8b..8ae4227 100644 --- a/src/main/java/cz/jzitnik/config/Debugging.java +++ b/src/main/java/cz/jzitnik/config/Debugging.java @@ -6,6 +6,6 @@ import lombok.Getter; @Getter @Config public class Debugging { - private final boolean renderColliders = false; - private final boolean renderPlayerCollider = false; + private final boolean renderColliders = true; + private final boolean renderPlayerCollider = true; } diff --git a/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java b/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java index 5dd5be3..3a792dc 100644 --- a/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java @@ -28,31 +28,28 @@ import lombok.extern.slf4j.Slf4j; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; @Slf4j @EventHandler(FullRoomDraw.class) public class FullRoomDrawHandler extends AbstractEventHandler { + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @InjectState private GameState gameState; - @InjectState private ScreenBuffer screenBuffer; - @InjectDependency private ResourceManager resourceManager; - @InjectState private TerminalState terminalState; - @InjectDependency private EventManager eventManager; - @InjectState private RenderState renderState; - @InjectConfig private Debugging debugging; - @InjectDependency private RoomTaskScheduler roomTaskScheduler; @@ -90,7 +87,9 @@ public class FullRoomDrawHandler extends AbstractEventHandler { if (renderState.isFirstRender() || event.isFullRerender()) { eventManager.emitEvent(RerenderScreen.full(terminalSize)); renderState.setFirstRender(false); - roomTaskScheduler.setupNewSchedulers(currentRoom); + scheduler.schedule(() -> { + roomTaskScheduler.setupNewSchedulers(currentRoom); + }, 200, TimeUnit.MILLISECONDS); } else { eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new))); } diff --git a/src/main/java/cz/jzitnik/events/handlers/RoomChangeEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/RoomChangeEventHandler.java index 6fac605..a74f241 100644 --- a/src/main/java/cz/jzitnik/events/handlers/RoomChangeEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/RoomChangeEventHandler.java @@ -14,22 +14,25 @@ import cz.jzitnik.utils.events.EventManager; import cz.jzitnik.utils.roomtasks.RoomTaskScheduler; import lombok.extern.slf4j.Slf4j; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + @Slf4j @EventHandler(RoomChangeEvent.class) public class RoomChangeEventHandler extends AbstractEventHandler { + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + @InjectState + private GameState gameState; + @InjectDependency + private EventManager eventManager; + @InjectDependency + private RoomTaskScheduler roomTaskScheduler; + public RoomChangeEventHandler(DependencyManager dm) { super(dm); } - @InjectState - private GameState gameState; - - @InjectDependency - private EventManager eventManager; - - @InjectDependency - private RoomTaskScheduler roomTaskScheduler; - @Override public void handle(RoomChangeEvent event) { RoomCords playerCords = gameState.getPlayer().getPlayerCords(); @@ -53,7 +56,9 @@ public class RoomChangeEventHandler extends AbstractEventHandler { + roomTaskScheduler.setupNewSchedulers(newRoom); + }, 200, TimeUnit.MILLISECONDS); eventManager.emitEvent(new FullRoomDraw()); } } diff --git a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java index 0cc4cf2..8c74fb0 100644 --- a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java +++ b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java @@ -18,6 +18,7 @@ import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.RerenderUtils; import cz.jzitnik.utils.events.EventManager; import cz.jzitnik.utils.roomtasks.RoomTask; +import cz.jzitnik.utils.roomtasks.RoomTaskScheduler; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -49,6 +50,8 @@ public abstract class HittableMob extends Mob { private ScreenBuffer screenBuffer; @InjectConfig private Debugging debugging; + @InjectDependency + private RoomTaskScheduler roomTaskScheduler; public HittableMob(BufferedImage texture, RoomTask task, RoomCords cords, int initialHealth) { super(texture, task, cords); @@ -66,6 +69,9 @@ public abstract class HittableMob extends Mob { if (health <= 0) { onKilled(); + if (task != null) { + roomTaskScheduler.stopTask(task); + } gameState.getCurrentRoom().getMobs().remove(this); rerender(); return; diff --git a/src/main/java/cz/jzitnik/game/mobs/Mob.java b/src/main/java/cz/jzitnik/game/mobs/Mob.java index e9c184f..d31bf3d 100644 --- a/src/main/java/cz/jzitnik/game/mobs/Mob.java +++ b/src/main/java/cz/jzitnik/game/mobs/Mob.java @@ -5,18 +5,24 @@ import cz.jzitnik.game.utils.RoomCords; import cz.jzitnik.game.utils.Selectable; import cz.jzitnik.utils.roomtasks.RoomTask; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.Setter; import java.awt.image.BufferedImage; @Getter -@RequiredArgsConstructor public abstract class Mob implements Renderable, Selectable { protected final BufferedImage texture; - private final RoomTask task; + + @Setter + protected RoomTask task; protected final RoomCords cords; + public Mob(BufferedImage texture, RoomTask task, RoomCords cords) { + this.texture = texture; + this.task = task; + this.cords = cords; + } + @Setter private boolean selected = false; } diff --git a/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java b/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java new file mode 100644 index 0000000..394e800 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java @@ -0,0 +1,133 @@ +package cz.jzitnik.game.mobs.tasks; + +import cz.jzitnik.game.GameRoomPart; +import cz.jzitnik.game.utils.RoomCords; + +import java.util.*; + +public class AStarAlg { + + // Boundaries matching your Player switch statement + // Player: if (x <= 30) return -> means 30 is the edge, valid area is > 30? + // User Update: "he can be on 30". So valid range is [30, 155] + private static final int MIN_X = 30; + private static final int MAX_X = 155; + private static final int MIN_Y = 10; + private static final int MAX_Y = 113; + + public static List findPath(RoomCords start, RoomCords target, List colliders) { + PriorityQueue openSet = new PriorityQueue<>(Comparator.comparingInt(n -> n.f)); + Set closedSet = new HashSet<>(); + + // We use Chebyshev distance for the heuristic (best for 8-way movement) + Node startNode = new Node(start.getX(), start.getY(), 0, getHeuristic(start, target), null); + openSet.add(startNode); + + while (!openSet.isEmpty()) { + Node current = openSet.poll(); + + // Reached target? + if (current.x == target.getX() && current.y == target.getY()) { + return reconstructPath(current); + } + + String key = current.x + "," + current.y; + if (closedSet.contains(key)) continue; + closedSet.add(key); + + for (Node neighbor : getNeighbors(current, target)) { + String neighborKey = neighbor.x + "," + neighbor.y; + if (closedSet.contains(neighborKey)) continue; + + // Check collisions and boundaries + if (!isValidPosition(neighbor.x, neighbor.y, colliders)) { + // EDGE CASE FIX: + // If the Player (target) is standing exactly on a wall/edge that the Mob considers invalid, + // A* will usually fail. + // We add a check: if this neighbor IS the target, we allow it. + // This lets the mob walk right up to the player's face even if they are hugging the wall. + if (neighbor.x != target.getX() || neighbor.y != target.getY()) { + continue; + } + } + + int newG = current.g + 1; + neighbor.g = newG; + neighbor.f = newG + neighbor.h; + neighbor.parent = current; + + openSet.add(neighbor); + } + } + return new ArrayList<>(); + } + + private static List getNeighbors(Node current, RoomCords target) { + List neighbors = new ArrayList<>(); + + // Added Diagonal Directions + int[][] directions = { + {0, 1}, {0, -1}, {1, 0}, {-1, 0}, // Up, Down, Right, Left + {1, 1}, {1, -1}, {-1, 1}, {-1, -1} // Diagonals + }; + + for (int[] dir : directions) { + int newX = current.x + dir[0]; + int newY = current.y + dir[1]; + int h = getHeuristic(new RoomCords(newX, newY), target); + neighbors.add(new Node(newX, newY, 0, h, null)); + } + return neighbors; + } + + private static boolean isValidPosition(int x, int y, List colliders) { + // Updated checks to be inclusive so 30 is valid. + // Valid X: 30 to 155 + if (x < MIN_X || x > MAX_X) return false; + + // Valid Y: 10 to 110 + if (y < MIN_Y || y > MAX_Y) return false; + + // Check Colliders + RoomCords temp = new RoomCords(x, y); + for (GameRoomPart part : colliders) { + if (part.isWithin(temp)) { + return false; + } + } + return true; + } + + private static List reconstructPath(Node endNode) { + List path = new ArrayList<>(); + Node current = endNode; + while (current != null) { + path.add(new RoomCords(current.x, current.y)); + current = current.parent; + } + Collections.reverse(path); + return path; + } + + // Changed to Chebyshev Distance for better diagonal estimation + private static int getHeuristic(RoomCords a, RoomCords b) { + int dx = Math.abs(a.getX() - b.getX()); + int dy = Math.abs(a.getY() - b.getY()); + return Math.max(dx, dy); + } + + private static class Node { + int x, y; + int g, h, f; + Node parent; + + public Node(int x, int y, int g, int h, Node parent) { + this.x = x; + this.y = y; + this.g = g; + this.h = h; + this.f = g + h; + this.parent = parent; + } + } +} diff --git a/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java b/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java new file mode 100644 index 0000000..a8fe416 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java @@ -0,0 +1,60 @@ +package cz.jzitnik.game.mobs.tasks; + +import cz.jzitnik.annotations.injectors.InjectConfig; +import cz.jzitnik.annotations.injectors.InjectDependency; +import cz.jzitnik.annotations.injectors.InjectState; +import cz.jzitnik.config.Debugging; +import cz.jzitnik.game.GameState; +import cz.jzitnik.game.ResourceManager; +import cz.jzitnik.game.mobs.Mob; +import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.states.ScreenBuffer; +import cz.jzitnik.states.SoundState; +import cz.jzitnik.states.TerminalState; +import cz.jzitnik.utils.events.EventManager; +import cz.jzitnik.utils.roomtasks.RoomTask; +import lombok.RequiredArgsConstructor; + +import java.util.concurrent.TimeUnit; + +public class BlindMobFollowingPlayerTask extends RoomTask { + public BlindMobFollowingPlayerTask(Mob mob, int speed, int updateRate) { + super(new Task(mob, speed), updateRate, TimeUnit.MILLISECONDS); + } + + @RequiredArgsConstructor + private static class Task implements Runnable { + private final Mob mob; + private final int speed; + private RoomCords playerCords; + @InjectState + private GameState gameState; + + @InjectDependency + private EventManager eventManager; + + @InjectDependency + private ResourceManager resourceManager; + + @InjectState + private TerminalState terminalState; + + @InjectState + private ScreenBuffer screenBuffer; + + @InjectConfig + private Debugging debugging; + + @InjectState + private SoundState soundState; + + @Override + public void run() { + if (playerCords == null || (soundState.isMicrophoneSetup() && soundState.getSoundVolume() > 2f)) { + playerCords = gameState.getPlayer().getPlayerCords().clone(); + } + + MobFollowingPlayerTask.Task.moveMob(playerCords, mob, gameState, speed, resourceManager, terminalState, screenBuffer, debugging, eventManager); + } + } +} diff --git a/src/main/java/cz/jzitnik/game/mobs/tasks/MobFollowingPlayerTask.java b/src/main/java/cz/jzitnik/game/mobs/tasks/MobFollowingPlayerTask.java new file mode 100644 index 0000000..74205cc --- /dev/null +++ b/src/main/java/cz/jzitnik/game/mobs/tasks/MobFollowingPlayerTask.java @@ -0,0 +1,99 @@ +package cz.jzitnik.game.mobs.tasks; + +import com.googlecode.lanterna.TerminalPosition; +import cz.jzitnik.annotations.injectors.InjectConfig; +import cz.jzitnik.annotations.injectors.InjectDependency; +import cz.jzitnik.annotations.injectors.InjectState; +import cz.jzitnik.config.Debugging; +import cz.jzitnik.events.RerenderScreen; +import cz.jzitnik.game.GameRoomPart; +import cz.jzitnik.game.GameState; +import cz.jzitnik.game.Player; +import cz.jzitnik.game.ResourceManager; +import cz.jzitnik.game.mobs.Mob; +import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.states.ScreenBuffer; +import cz.jzitnik.states.TerminalState; +import cz.jzitnik.utils.RerenderUtils; +import cz.jzitnik.utils.events.EventManager; +import cz.jzitnik.utils.roomtasks.RoomTask; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class MobFollowingPlayerTask extends RoomTask { + public MobFollowingPlayerTask(Mob mob, int speed, int updateRate) { + super(new Task(mob, speed), updateRate, TimeUnit.MILLISECONDS); + } + + @RequiredArgsConstructor + static class Task implements Runnable { + private final Mob mob; + private final int speed; + + @InjectState + private GameState gameState; + + @InjectDependency + private EventManager eventManager; + + @InjectDependency + private ResourceManager resourceManager; + + @InjectState + private TerminalState terminalState; + + @InjectState + private ScreenBuffer screenBuffer; + + @InjectConfig + private Debugging debugging; + + protected static void moveMob(RoomCords playerCords, Mob mob, GameState gameState, int speed, ResourceManager resourceManager, TerminalState terminalState, ScreenBuffer screenBuffer, Debugging debugging, EventManager eventManager) { + RoomCords mobCords = mob.getCords(); + List solidParts = gameState.getCurrentRoom().getColliders(); + List path = AStarAlg.findPath(mobCords, playerCords, solidParts); + + if (path.size() > 1) { + int targetIndex = Math.min(speed, path.size() - 1); + + RoomCords newCords = path.get(targetIndex); + + mob.getCords().updateCords(newCords.getX(), newCords.getY()); + + int forStartX = Math.min(mobCords.getX(), newCords.getX()); + int forStartY = Math.min(mobCords.getY(), newCords.getY()); + int forEndX = Math.max(mobCords.getX(), newCords.getX()) + mob.getTexture().getWidth() + 1; + int forEndY = Math.max(mobCords.getY(), newCords.getY()) + mob.getTexture().getHeight() + 1; + + BufferedImage room = resourceManager.getResource(gameState.getCurrentRoom().getTexture()); + var start = RerenderUtils.getStart(room, terminalState.getTerminalScreen().getTerminalSize()); + int startX = start.getX(); + int startY = start.getY(); + + Player player = gameState.getPlayer(); + BufferedImage playerTexture = RerenderUtils.getPlayer(resourceManager, player); + + RerenderUtils.rerenderPart(forStartX, forEndX, forStartY, forEndY, startX, startY, gameState.getCurrentRoom(), room, player, playerTexture, screenBuffer, resourceManager, debugging); + + eventManager.emitEvent(new RerenderScreen( + new RerenderScreen.ScreenPart( + new TerminalPosition(forStartX + startX, forStartY + startY), + new TerminalPosition(forEndX + 1 + startX, forEndY + startY) + ) + )); + } else { + log.debug("Mob is effectively at the target or trapped."); + } + } + + @Override + public void run() { + moveMob(gameState.getPlayer().getPlayerCords(), mob, gameState, speed, resourceManager, terminalState, screenBuffer, debugging, eventManager); + } + } +} diff --git a/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java b/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java index 7dbab49..079c704 100644 --- a/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java +++ b/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java @@ -2,6 +2,7 @@ package cz.jzitnik.game.setup.mobs; import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.mobs.HittableMob; +import cz.jzitnik.game.mobs.tasks.MobFollowingPlayerTask; import cz.jzitnik.game.utils.RoomCords; import lombok.extern.slf4j.Slf4j; @@ -9,7 +10,9 @@ import lombok.extern.slf4j.Slf4j; public class Zombie extends HittableMob { public Zombie(ResourceManager resourceManager, RoomCords cords) { - super(resourceManager.getResource(ResourceManager.Resource.CHEST), null, cords, 10); + super(resourceManager.getResource(ResourceManager.Resource.PLAYER_FRONT), null, cords, 10); + //setTask(new BlindMobFollowingPlayerTask(this, 1, 100)); + setTask(new MobFollowingPlayerTask(this, 1, 100)); } @Override diff --git a/src/main/java/cz/jzitnik/game/utils/RoomCords.java b/src/main/java/cz/jzitnik/game/utils/RoomCords.java index a085ea9..90a3537 100644 --- a/src/main/java/cz/jzitnik/game/utils/RoomCords.java +++ b/src/main/java/cz/jzitnik/game/utils/RoomCords.java @@ -10,7 +10,7 @@ import java.util.List; @Slf4j @ToString @Getter -public class RoomCords { +public class RoomCords implements Cloneable { private int x; private int y; @@ -29,4 +29,13 @@ public class RoomCords { } updateCords(x, y); } + + @Override + public RoomCords clone() { + try { + return (RoomCords) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } } diff --git a/src/main/java/cz/jzitnik/states/SoundState.java b/src/main/java/cz/jzitnik/states/SoundState.java index bc90706..ebc8b47 100644 --- a/src/main/java/cz/jzitnik/states/SoundState.java +++ b/src/main/java/cz/jzitnik/states/SoundState.java @@ -7,4 +7,5 @@ import lombok.Data; @State public class SoundState { private double soundVolume; + private boolean microphoneSetup = false; } diff --git a/src/main/java/cz/jzitnik/threads/MicrophoneThread.java b/src/main/java/cz/jzitnik/threads/MicrophoneThread.java index 369de70..163a91b 100644 --- a/src/main/java/cz/jzitnik/threads/MicrophoneThread.java +++ b/src/main/java/cz/jzitnik/threads/MicrophoneThread.java @@ -41,6 +41,7 @@ public class MicrophoneThread extends ShutdownableThread { if (bytesRead > 0) { double volume = calculateRMS(buffer, bytesRead); soundState.setSoundVolume(volume); + soundState.setMicrophoneSetup(true); } } } catch (LineUnavailableException e) { diff --git a/src/main/java/cz/jzitnik/utils/roomtasks/RoomTask.java b/src/main/java/cz/jzitnik/utils/roomtasks/RoomTask.java index fac4e2d..4af0f0f 100644 --- a/src/main/java/cz/jzitnik/utils/roomtasks/RoomTask.java +++ b/src/main/java/cz/jzitnik/utils/roomtasks/RoomTask.java @@ -1,6 +1,14 @@ package cz.jzitnik.utils.roomtasks; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + import java.util.concurrent.TimeUnit; -public record RoomTask(Runnable task, long rate, TimeUnit rateUnit) { +@RequiredArgsConstructor +@Getter +public class RoomTask { + private final Runnable task; + private final long rate; + private final TimeUnit rateUnit; } diff --git a/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java b/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java index fff033c..5836383 100644 --- a/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java +++ b/src/main/java/cz/jzitnik/utils/roomtasks/RoomTaskScheduler.java @@ -3,13 +3,17 @@ package cz.jzitnik.utils.roomtasks; import cz.jzitnik.annotations.Dependency; import cz.jzitnik.annotations.PostInit; import cz.jzitnik.annotations.injectors.InjectConfig; +import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.config.ThreadPoolConfig; import cz.jzitnik.game.GameRoom; import cz.jzitnik.game.mobs.Mob; +import cz.jzitnik.utils.DependencyManager; import lombok.extern.slf4j.Slf4j; +import java.util.HashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @Slf4j @@ -18,6 +22,10 @@ public class RoomTaskScheduler { ScheduledExecutorService scheduler; @InjectConfig private ThreadPoolConfig threadPoolConfig; + @InjectDependency + private DependencyManager dependencyManager; + + private HashMap> tasks = new HashMap<>(); private boolean firstRun = true; @@ -46,6 +54,16 @@ public class RoomTaskScheduler { shutdownAll(); } + public void stopTask(RoomTask task) { + if (!tasks.containsKey(task)) { + return; + } + + ScheduledFuture future = tasks.get(task); + future.cancel(true); + tasks.remove(task); + } + public void setupNewSchedulers(GameRoom currentRoom) { if (!firstRun) { shutdownAll(); @@ -54,8 +72,11 @@ public class RoomTaskScheduler { for (Mob mob : currentRoom.getMobs()) { RoomTask task = mob.getTask(); + if (task != null) { - scheduler.scheduleAtFixedRate(task.task(), 0, task.rate(), task.rateUnit()); + dependencyManager.inject(task.getTask()); + ScheduledFuture future = scheduler.scheduleAtFixedRate(task.getTask(), 0, task.getRate(), task.getRateUnit()); + tasks.put(task, future); } }