diff --git a/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java b/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java index 49d4e56f7..4eb7ccca2 100644 --- a/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java +++ b/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java @@ -27,6 +27,7 @@ import noppes.npcs.constants.ScriptContext; import noppes.npcs.controllers.data.ForgeDataScript; import noppes.npcs.controllers.data.IScriptHandler; import noppes.npcs.controllers.data.IScriptUnit; +import noppes.npcs.controllers.data.IScriptHandlerPacket; import noppes.npcs.scripted.item.ScriptCustomItem; import org.lwjgl.opengl.Display; @@ -49,7 +50,6 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb protected Map textAreas = new HashMap<>(); protected GuiScreen parent; - public boolean singleContainer = false; /** If true, uses scroll-based tab selection instead of top buttons */ protected boolean useScrollTabs = false; @@ -97,6 +97,12 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb gui.parent = parent; gui.singleContainer = true; gui.displayGuiScreen(gui); + + // Initialize hooks from handler + gui.hookList = new ArrayList<>(handler.getHooks()); + + + Minecraft.getMinecraft().displayGuiScreen(gui); return gui; } @@ -113,6 +119,9 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb this.guiTop += 10; int yoffset = (int) ((double) this.ySize * 0.02D); + if (hookList.isEmpty()) + hookList = new ArrayList<>(handler.getHooks()); + // ==================== TAB BUTTONS ==================== // Skip standard tab creation for GUIs that use custom toggle/scroll tabs if (!useSettingsToggle) { @@ -123,7 +132,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb int topXoffset = 0; int topYoffset = 0; - if(!singleContainer) { + boolean isSingle = handler.isSingleContainer(); + if (!isSingle) { for (int ta = 0; ta < this.handler.getScripts().size(); ++ta) { if (ta % 20 == 0 && ta > 0) { topYoffset -= 20; @@ -137,7 +147,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb } } - if (singleContainer && getFirst() != null) + if (isSingle && handler.getSingleScript() != null) this.addTopButton( top = new GuiMenuTopButton(1, top.xPosition + top.width + topXoffset, top.yPosition + topYoffset, "Script")); @@ -233,10 +243,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb // Set the scripting language for proper syntax highlighting // Use the container's language if available, otherwise fall back to handler's language String language = (container != null) ? container.getLanguage() : this.handler.getLanguage(); - activeArea.setLanguage(language); - - // Set the script context for context-aware hook autocomplete - activeArea.setScriptContext(getScriptContext()); + activeArea.setLanguage(language); // Set the script context for context-aware hook autocomplete activeArea.setScriptContext(getScriptContext()); @@ -320,6 +327,16 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb return this; } + /** + * Get the script context for this GUI. + * Override in subclasses to return a different context. + * + * @return The script context (default: GLOBAL, or handler's context if available) + */ + protected ScriptContext getScriptContext() { + return handler.getContext(); + } + // ==================== RENDERING ==================== @Override @@ -418,10 +435,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb this.openLink("http://www.minecraftforge.net/forum/index.php/board,122.0.html"); } if (i == 10) { - if (singleContainer) - setHandlerUnit(null); - else - this.handler.getScripts().remove(this.activeTab - 1); + handler.removeScriptUnit(this.activeTab - 1); this.activeTab = 0; } if (i == 101) { @@ -482,11 +496,6 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb return super.getTextField(id); } - public IScriptUnit getFirst() { - if (this.handler.getScripts().isEmpty()) - return null; - return this.handler.getScripts().get(0); - } @Override protected void actionPerformed(GuiButton guibutton) { if (guibutton.id >= 0 && guibutton.id < scriptLimit) { @@ -501,14 +510,15 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb } if (guibutton.id == scriptLimit) { - if (singleContainer) { - if (getFirst() == null) - setHandlerUnit(new ScriptContainer(handler)); - else + if (handler.isSingleContainer()) { + if (handler.getSingleScript() == null) { + handler.addScriptUnit(new ScriptContainer(handler)); + } else { this.setScript(); + } this.activeTab = 1; } else { - this.handler.getScripts().add(new ScriptContainer(this.handler)); + handler.addScriptUnit(new ScriptContainer(this.handler)); this.activeTab = this.handler.getScripts().size(); } this.initGui(); @@ -567,10 +577,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb IScriptUnit container = getCurrentContainer(); if (container == null) { container = new ScriptContainer(this.handler); - if (singleContainer) - setHandlerUnit(container); - else - this.handler.getScripts().add(container); + handler.addScriptUnit(container); } String language = container != null ? container.getLanguage() : this.handler.getLanguage(); @@ -600,12 +607,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb } } - if (singleContainer) { - setHandlerUnit(newUnit); - } else { - // Replace the script unit in the list - handler.getScripts().set(idx, newUnit); - } + handler.replaceScriptUnit(idx, newUnit); // Clear the cached text area so it recreates with new language textAreas.remove(idx); @@ -621,10 +623,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb if (container == null) { container = new ScriptContainer(this.handler); - if (singleContainer) - setHandlerUnit(container); - else - this.handler.getScripts().add(container); + handler.addScriptUnit(container); } if (container != null) { diff --git a/src/main/java/noppes/npcs/controllers/data/IScriptHandler.java b/src/main/java/noppes/npcs/controllers/data/IScriptHandler.java index 69d17beb2..cc47eb1c8 100644 --- a/src/main/java/noppes/npcs/controllers/data/IScriptHandler.java +++ b/src/main/java/noppes/npcs/controllers/data/IScriptHandler.java @@ -3,7 +3,10 @@ package noppes.npcs.controllers.data; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.Event; import noppes.npcs.constants.EnumScriptType; +import noppes.npcs.constants.ScriptContext; +import noppes.npcs.controllers.ScriptHookController; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -74,4 +77,89 @@ public interface IScriptHandler { default boolean supportsJanino() { return createJaninoScriptUnit() != null; } + + /** + * Get the script context for autocomplete and type resolution. + * + * @return The script context (default: GLOBAL) + */ + default ScriptContext getContext() { + return ScriptContext.GLOBAL; + } + + /** + * Get the hook context identifier for this handler. + * Used by ScriptHookController to look up available hooks. + * + * @return The context string (e.g., IScriptHookHandler.CONTEXT_EFFECT), or empty if none + */ + default String getHookContext() { + return ""; + } + + /** + * Get the list of available hooks for this handler. + * Uses getHookContext() to look up hooks from ScriptHookController. + * + * @return List of hook names, or empty list if no context + */ + default List getHooks() { + String context = getHookContext(); + if (context == null || context.isEmpty()) + return Collections.emptyList(); + + return ScriptHookController.Instance.getAllHooks(context); + } + + /** + * Check if this handler manages only a single script container. + * Single-container handlers use simplified UI (no numbered tabs). + * + * @return true if this handler uses a single container + */ + default boolean isSingleContainer() { + return false; + } + + /** + * Get the single script unit for single-container handlers. + * Convenience method that returns the first script or null. + * + * @return The single script unit, or null if none exists + */ + default IScriptUnit getSingleScript() { + List scripts = getScripts(); + return (scripts != null && !scripts.isEmpty()) ? scripts.get(0) : null; + } + + /** + * Add a script unit to this handler. + * For single-container handlers, replaces the current unit. + */ + default void addScriptUnit(IScriptUnit unit) { + if (unit != null) + getScripts().add(unit); + } + + /** + * Replace a script unit at the given index. + * For single-container handlers, replaces the current unit. + */ + default void replaceScriptUnit(int index, IScriptUnit unit) { + List scripts = getScripts(); + if (index < 0 || index >= scripts.size()) + scripts.add(unit); + else + scripts.set(index, unit); + } + + /** + * Remove a script unit at the given index. + * For single-container handlers, clears the unit. + */ + default void removeScriptUnit(int index) { + List scripts = getScripts(); + if (index >= 0 && index < scripts.size()) + scripts.remove(index); + } } diff --git a/src/main/java/noppes/npcs/controllers/data/ScriptHandler.java b/src/main/java/noppes/npcs/controllers/data/ScriptHandler.java index f5ea44383..def6ca7cf 100644 --- a/src/main/java/noppes/npcs/controllers/data/ScriptHandler.java +++ b/src/main/java/noppes/npcs/controllers/data/ScriptHandler.java @@ -5,24 +5,36 @@ import noppes.npcs.config.ConfigScript; import noppes.npcs.controllers.ScriptController; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public abstract class ScriptHandler implements IScriptHandler { - public IScriptUnit container; + public List scripts = new ArrayList<>(); protected String scriptLanguage = "ECMAScript"; protected boolean enabled = false; + protected long lastInited = -1; protected boolean canRunScripts() { - return enabled && ScriptController.HasStart && ConfigScript.ScriptingEnabled && container != null; + return enabled && ScriptController.HasStart && ConfigScript.ScriptingEnabled && scripts != null && !scripts.isEmpty(); } @Override public void callScript(String hookName, Event event) { - if (!canRunScripts()) { + if (!canRunScripts()) return; + + if (ScriptController.Instance.lastLoaded > lastInited) { + lastInited = ScriptController.Instance.lastLoaded; + for (IScriptUnit script : scripts) { + if (script != null) + script.setErrored(false); + } + } + + for (IScriptUnit script : scripts) { + if (script == null || script.hasErrored() || !script.hasCode()) + continue; + script.run(hookName, event); } - container.run(hookName, event); } @Override @@ -45,20 +57,11 @@ public abstract class ScriptHandler implements IScriptHandler { this.scriptLanguage = language; } - @Override public void setScripts(List list) { - if (list == null || list.isEmpty()) { - container = null; - return; - } - container = list.get(0); + this.scripts = list; } - @Override public List getScripts() { - if (container == null) { - return new ArrayList<>(); - } - return Collections.singletonList(container); + return this.scripts; } } diff --git a/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java b/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java new file mode 100644 index 000000000..a047b41b6 --- /dev/null +++ b/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java @@ -0,0 +1,153 @@ +package noppes.npcs.controllers.data; + +import cpw.mods.fml.common.eventhandler.Event; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.Constants; +import noppes.npcs.config.ConfigScript; +import noppes.npcs.controllers.ScriptController; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Base class for script handlers that manage a single script container. + * Provides shared NBT/ByteBuf serialization and packet handling. + * + * Subclasses should: + * - Store their parent object (e.g., CustomEffect, LinkedItem) + * - Override getHookContext() to return the appropriate context string + * - Override getContext() if needed (default is GLOBAL) + * - Override requestData() and sendSavePacket() for packet communication + */ +public abstract class SingleScriptHandler extends ScriptHandler { + public IScriptUnit container; + + @Override + public boolean isSingleContainer() { + return true; + } + + protected boolean canRunScripts() { + return enabled && ScriptController.HasStart && ConfigScript.ScriptingEnabled && container != null; + } + + @Override + public void callScript(String hookName, Event event) { + if (!canRunScripts()) { + return; + } + container.run(hookName, event); + } + + @Override + public void setScripts(List list) { + if (list == null || list.isEmpty()) { + container = null; + this.scripts = new ArrayList<>(); + } else { + container = list.get(0); + this.scripts = new ArrayList<>(1); + this.scripts.add(container); + } + } + + @Override + public List getScripts() { + if(container == null) + return new ArrayList<>(); + + return Collections.singletonList(container); + } + + public void addScriptUnit(IScriptUnit unit) { + this.container = unit; + this.scripts.clear(); + if (unit != null) + this.scripts.add(unit); + } + + public void replaceScriptUnit(int index, IScriptUnit unit) { + this.container = unit; + this.scripts.clear(); + if (unit != null) + this.scripts.add(unit); + } + + public void removeScriptUnit(int index) { + this.container = null; + this.scripts.clear(); + } + + /** + * Write script data to NBT. + * Uses standard keys: ScriptLanguage, ScriptEnabled, ScriptContent + */ + public NBTTagCompound writeToNBT(NBTTagCompound compound) { + compound.setString("ScriptLanguage", scriptLanguage); + compound.setBoolean("ScriptEnabled", enabled); + + if (container != null) { + compound.setTag("ScriptContent", container.writeToNBT(new NBTTagCompound())); + } + return compound; + } + + /** + * Read script data from NBT. + * Uses standard keys: ScriptLanguage, ScriptEnabled, ScriptContent + */ + public void readFromNBT(NBTTagCompound compound) { + scriptLanguage = compound.getString("ScriptLanguage"); + enabled = compound.getBoolean("ScriptEnabled"); + + if (compound.hasKey("ScriptContent", Constants.NBT.TAG_COMPOUND)) { + container = IScriptUnit.createFromNBT(compound.getCompoundTag("ScriptContent"), this); + } + } + + /** + * Save script data from a ByteBuf packet. + * Handles the tab/totalScripts protocol used by script packets. + * + * Protocol: + * - tab == 0: Script content NBT follows + * - tab != 0 (typically -1): Script metadata (language, enabled) follows + */ + public void saveScript(ByteBuf buffer) throws IOException { + int tab = buffer.readInt(); + int totalScripts = buffer.readInt(); + + if (totalScripts == 0) { + this.container = null; + } + + if (tab == 0) { + // Script content + NBTTagCompound tabCompound = ByteBufUtils.readNBT(buffer); + this.container = IScriptUnit.createFromNBT(tabCompound, this); + } else { + // Metadata (language, enabled) + NBTTagCompound compound = ByteBufUtils.readNBT(buffer); + this.setLanguage(compound.getString("ScriptLanguage")); + normalizeLanguage(); + this.setEnabled(compound.getBoolean("ScriptEnabled")); + } + } + + /** + * Normalize the language to a valid one if the current language is not available. + */ + protected void normalizeLanguage() { + if (!ScriptController.Instance.languages.containsKey(this.getLanguage())) { + if (!ScriptController.Instance.languages.isEmpty()) { + this.setLanguage((String) ScriptController.Instance.languages.keySet().toArray()[0]); + } else { + this.setLanguage("ECMAScript"); + } + } + } +}