diff --git a/src/api b/src/api index bbbea878f..65c81997b 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit bbbea878fe30ea033112b0c5f1604417d4197447 +Subproject commit 65c81997b9d552d39ed1005975f9e9824eebccf6 diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 8f8ffbb2f..26b638ac6 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -496,7 +496,7 @@ public class ScriptPlayerEventHandler { EntityPlayer player = (EntityPlayer) event.entityLiving; PlayerData playerData = PlayerData.get(player); - playerData.actionManager.clear(); + // playerData.actionManager.clear(); } if (event.source.getEntity() instanceof EntityPlayerMP) { diff --git a/src/main/java/noppes/npcs/ServerTickHandler.java b/src/main/java/noppes/npcs/ServerTickHandler.java index c73874df5..ecfdce50a 100644 --- a/src/main/java/noppes/npcs/ServerTickHandler.java +++ b/src/main/java/noppes/npcs/ServerTickHandler.java @@ -11,13 +11,22 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.WorldServer; import noppes.npcs.client.AnalyticsTracking; import noppes.npcs.controllers.data.PlayerData; +import noppes.npcs.scripted.NpcAPI; import java.net.InetAddress; import java.net.UnknownHostException; public class ServerTickHandler { + @SubscribeEvent - public void onServerTick(TickEvent.WorldTickEvent event) { + public void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase == Phase.END) { + NpcAPI.actionManager.tick(MinecraftServer.getServer().getTickCounter()); + } + } + + @SubscribeEvent + public void onWorldTick(TickEvent.WorldTickEvent event) { if (event.phase == Phase.START) { NPCSpawning.findChunksForSpawning((WorldServer) event.world); } diff --git a/src/main/java/noppes/npcs/controllers/data/action/Action.java b/src/main/java/noppes/npcs/controllers/data/action/Action.java index 9bf4e745e..39affcd26 100644 --- a/src/main/java/noppes/npcs/controllers/data/action/Action.java +++ b/src/main/java/noppes/npcs/controllers/data/action/Action.java @@ -1,11 +1,13 @@ package noppes.npcs.controllers.data.action; import noppes.npcs.api.handler.data.IAction; +import noppes.npcs.scripted.CustomNPCsException; import noppes.npcs.scripted.ScriptedActionManager; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Supplier; public class Action implements IAction { protected final ScriptedActionManager manager; @@ -16,8 +18,10 @@ public class Action implements IAction { protected int maxDuration = -1; protected int updateEveryXTick = 5; protected Consumer task; - private boolean done; - private final Map dataStore = new HashMap<>(); + protected boolean done; + protected final Map dataStore = new HashMap<>(); + protected boolean isThreaded; + protected ActionThread threaded; public Action(ScriptedActionManager manager, String name) { this.manager = manager; @@ -68,13 +72,17 @@ public class Action implements IAction { return; } if (ticksExisted % updateEveryXTick == 0 && task != null) { - try { - task.accept(this); - count++; - } catch (Throwable t) { - System.err.println("Scripted action '" + name + "' threw an exception:"); - t.printStackTrace(); - markDone(); + if (isThreaded) + threaded.run(); + else { + try { + task.accept(this); + count++; + } catch (Throwable t) { + System.err.println("Scripted action '" + name + "' threw an exception:"); + t.printStackTrace(); + markDone(); + } } } duration++; @@ -107,7 +115,7 @@ public class Action implements IAction { @Override public IAction setMaxDuration(int x) { - this.maxDuration = x; + this.maxDuration = Math.max(-1, x); return this; } @@ -121,6 +129,11 @@ public class Action implements IAction { return done; } + public void kill() { + if (threaded != null) + threaded.stop(); + } + @Override public Object getData(String key) { return dataStore.get(key); @@ -150,7 +163,7 @@ public class Action implements IAction { @Override public IAction setUpdateEveryXTick(int x) { - this.updateEveryXTick = x; + this.updateEveryXTick = Math.max(1, x); return this; } @@ -161,7 +174,67 @@ public class Action implements IAction { @Override public IAction pauseFor(int ticks) { - this.startAfterTicks = ticks; + if (isThreaded) + threaded.pauseFor(ticks); + else + this.startAfterTicks = ticks; + + return this; + } + + @Override + public IAction pauseFor(long millis) { + if (isThreaded) { + threaded.pauseFor(millis); + return this; + } + + return pauseFor((int) (millis / 50)); + } + + @Override + public void pause() { + if (isThreaded) + threaded.pause(); + else + throw new CustomNPCsException("Must threadify() IAction before pausing!"); + } + + @Override + public void pauseUntil(Supplier until) { + if (isThreaded) + threaded.pauseUntil(until); + else + throw new CustomNPCsException("Must threadify() IAction before pausing!"); + } + + @Override + public void resume() { + if (isThreaded) + threaded.resume(); + } + + @Override + public boolean isPaused() { + if (isThreaded) + return threaded.isPaused(); + + return startAfterTicks > 0; + } + + @Override + public IAction threadify() { + if (!isThreaded) { + isThreaded = true; + threaded = new ActionThread(this); + } + + return this; + } + + @Override + public IAction start() { + manager.start(); return this; } @@ -191,6 +264,32 @@ public class Action implements IAction { return null; } + @Override + public IAction parallel(IAction after) { + return manager.scheduleParallelAction(after); + } + + @Override + public IAction parallel(Consumer task) { + return manager.scheduleParallelAction(manager.create(task)); + } + + @Override + public IAction parallel(int delay, Consumer task) { + return manager.scheduleParallelAction(manager.create(delay, task)); + } + + @Override + public IAction parallel(String name, Consumer task) { + return manager.scheduleParallelAction(manager.create(name, task)); + } + + @Override + public IAction parallel(String name, int startAfterTicks, Consumer task) { + return manager.scheduleParallelAction(manager.create(name, startAfterTicks, task)); + } + + @Override public IAction after(IAction after) { int idx = manager.getIndex(this); diff --git a/src/main/java/noppes/npcs/controllers/data/action/ActionThread.java b/src/main/java/noppes/npcs/controllers/data/action/ActionThread.java new file mode 100644 index 000000000..e00607e76 --- /dev/null +++ b/src/main/java/noppes/npcs/controllers/data/action/ActionThread.java @@ -0,0 +1,119 @@ +package noppes.npcs.controllers.data.action; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +public class ActionThread { + private final Action action; + + private final Object lock = new Object(); + private Thread thread; + ExecutorService executor; + private Future currentExecution; + private volatile boolean threadPaused, threadSleeping; + + private Supplier pauseUntil; + + public ActionThread(Action action) { + this.action = action; + + executor = Executors.newSingleThreadExecutor(r -> { + thread = new Thread(r); + thread.setName(String.format("ActionThread (%s)", action.getName())); + return thread; + }); + } + + public void stop() { + if (isThreadRunning()) + currentExecution.cancel(true); + + executor.shutdown(); + } + + public void pauseFor(long millis) { + if (isPaused() || !inActionThread()) + return; + + try { + threadSleeping = true; + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + threadSleeping = false; + } + } + + public void pauseFor(int ticks) { + pauseFor(ticks * 50L); + } + + public void pause() { + if (!inActionThread() || isPaused()) + return; + + threadPaused = true; + synchronized (lock) { + while (threadPaused) { + try { + lock.wait(); // Wait until notified + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void pauseUntil(Supplier until) { + this.pauseUntil = until; + pause(); + } + + public void resume() { + synchronized (lock) { + if (threadPaused) { + threadPaused = false; + lock.notify(); + } + + if (threadSleeping && currentExecution != null) + currentExecution.cancel(true); + } + } + + public boolean inActionThread() { + return Thread.currentThread() == thread; + } + + public boolean isPaused() { + return threadPaused || threadSleeping; + } + + public boolean isThreadRunning() { + return currentExecution != null && !currentExecution.isDone(); + } + + + protected void run() { + if (threadPaused && pauseUntil != null && pauseUntil.get()) + resume(); + + if (isThreadRunning()) + return; + + currentExecution = executor.submit(() -> { + try { + action.task.accept(action); + action.count++; + } catch (Throwable t) { + System.err.println("Scripted threaded action '" + action.name + "' threw an exception:"); + t.printStackTrace(); + action.markDone(); + } + }); + + } +} diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index c79354347..6ff6961f0 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -1168,7 +1168,7 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti setHealth(getMaxHealth()); dataWatcher.updateObject(14, 0); // animation Normal dataWatcher.updateObject(15, 0); - actionManager.clear(); + // actionManager.clear(); combatHandler.reset(); this.setAttackTarget(null); this.setRevengeTarget(null); diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index 54d86963d..a514f8508 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -79,6 +79,7 @@ public class NpcAPI extends AbstractNpcAPI { private static final CacheHashMap> scriptItemCache = new CacheHashMap<>(60 * 1000); public static final HashMap engineObjects = new HashMap<>(); public static final EventBus EVENT_BUS = new EventBus(); + public static final ScriptedActionManager actionManager = new ScriptedActionManager(); private static AbstractNpcAPI instance = null; private final static String API_USER_AGENT = "CNPC+API"; @@ -559,6 +560,10 @@ public class NpcAPI extends AbstractNpcAPI { return container instanceof ContainerNpcInterface ? ContainerNpcInterface.getOrCreateIContainer((ContainerNpcInterface) container) : new ScriptContainer(container); } + public IActionManager getActionManager() { + return actionManager; + } + private void checkWorld() { if (CustomNpcs.getServer() == null || CustomNpcs.getServer().isServerStopped()) { throw new CustomNPCsException("No world is loaded right now"); diff --git a/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java b/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java index 9d62fffac..cf9f95bc0 100644 --- a/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java +++ b/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java @@ -40,8 +40,8 @@ public class ScriptedActionManager implements IActionManager { } @Override - public IAction create(String name, int maxDuration, int startAfterTicks, Consumer task) { - return new Action(this, name, maxDuration, startAfterTicks, task); + public IAction create(String name, int maxDuration, int delay, Consumer task) { + return new Action(this, name, maxDuration, delay, task); } @Override @@ -106,8 +106,27 @@ public class ScriptedActionManager implements IActionManager { } @Override - public IAction scheduleAction(String name, int maxDuration, int startAfterTicks, Consumer task) { - return scheduleAction(create(name, maxDuration, startAfterTicks, task)); + public IAction scheduleAction(Consumer task) { + return scheduleAction(create(task)); + } + + @Override + public IAction scheduleAction(int delay, Consumer task) { + return scheduleAction(create(delay, task)); + } + + @Override + public IAction scheduleAction(String name, Consumer task) { + return scheduleAction(create(name, task)); + } + @Override + public IAction scheduleAction(String name, int delay, Consumer task) { + return scheduleAction(create(name, delay, task)); + } + + @Override + public IAction scheduleAction(String name, int maxDuration, int delay, Consumer task) { + return scheduleAction(create(name, maxDuration, delay, task)); } @Override @@ -141,11 +160,51 @@ public class ScriptedActionManager implements IActionManager { return actionQueue.peekFirst(); } + @Override + public IAction getAction(String name) { + for (IAction action : actionQueue) { + if (action.getName().equals(name)) + return action; + } + + for (IAction action : parallelActions) { + if (action.getName().equals(name)) + return action; + } + + for (IAction action : conditionalActions) { + if (action.getName().equals(name)) + return action; + } + + return null; + } + @Override public Queue getActionQueue() { return actionQueue; } + @Override + public boolean hasAction(String name) { + for (IAction act : actionQueue) + if (act.getName().equals(name)) + return true; + + return false; + } + @Override + public boolean hasAny(String name) { + if (hasAction(name)) + return true; + else if (hasParallel(name)) + return true; + else if (hasConditional(name)) + return true; + + return false; + } + @Override public void clear() { actionQueue.clear(); @@ -213,11 +272,33 @@ public class ScriptedActionManager implements IActionManager { return new ArrayList<>(conditionalActions); } + @Override + public boolean hasConditional(String name) { + for (IAction act : conditionalActions) + if (act.getName().equals(name)) + return true; + + return false; + } + @Override public IAction scheduleParallelAction(IAction action) { parallelActions.add(action); return action; } + @Override + public IAction scheduleParallelAction(String name, Consumer task) { + return scheduleParallelAction(create(name, task)); + } + @Override + public IAction scheduleParallelAction(String name, int delay, Consumer task) { + return scheduleParallelAction(create(name, delay, task)); + } + + @Override + public IAction scheduleParallelAction(String name, int maxDuration, int delay, Consumer task) { + return scheduleParallelAction(create(name, maxDuration, delay, task)); + } /** @@ -233,7 +314,10 @@ public class ScriptedActionManager implements IActionManager { if (current instanceof Action) { Action cab = (Action) current; cab.tick(ticksExisted); - if (cab.isDone()) actionQueue.pollFirst(); + if (cab.isDone()) { + cab.kill(); + actionQueue.pollFirst(); + } } // ─── Parallel (all) ─────────────────────────────────────── @@ -241,7 +325,10 @@ public class ScriptedActionManager implements IActionManager { while (pit.hasNext()) { Action a = (Action) pit.next(); a.tick(ticksExisted); - if (a.isDone()) pit.remove(); + if (a.isDone()) { + a.kill(); + pit.remove(); + } } // ─── Conditionals ───────────────────────────────────────── @@ -249,7 +336,10 @@ public class ScriptedActionManager implements IActionManager { while (cit.hasNext()) { ConditionalAction con = (ConditionalAction) cit.next(); con.tick(ticksExisted); - if (con.isDone()) cit.remove(); + if (con.isDone()) { + con.kill(); + cit.remove(); + } } } @@ -262,4 +352,18 @@ public class ScriptedActionManager implements IActionManager { public IActionChain parallelChain() { return new ParallelActionChain(this); } + + @Override + public Queue getParallelActions() { + return parallelActions; + } + + @Override + public boolean hasParallel(String name) { + for (IAction act : parallelActions) + if (act.getName().equals(name)) + return true; + + return false; + } } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java index fb3d3d9ab..93b7aa6e7 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptPlayer.java @@ -27,6 +27,7 @@ import noppes.npcs.api.*; import noppes.npcs.api.entity.IEntity; import noppes.npcs.api.entity.IPlayer; import noppes.npcs.api.gui.ICustomGui; +import noppes.npcs.api.handler.IActionManager; import noppes.npcs.api.handler.IOverlayHandler; import noppes.npcs.api.handler.data.*; import noppes.npcs.api.item.IItemStack; @@ -846,4 +847,9 @@ public class ScriptPlayer extends ScriptLivingBase public IPlayerAttributes getAttributes() { return AttributeController.getTracker(this.player); } + + @Override + public IActionManager getActionManager() { + return data.actionManager; + } }