Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
Kam committed 2026-03-03 22:32:38 -05:00
commit 9e68943b7e
14 files changed
+229 -45

No files matched your search

@@ -220,6 +220,7 @@ public class AbilityController implements IAbilityHandler {
public void load() {
customAbilities.clear();
customAbilitiesById.clear();
abilityScriptHandlers.clear();
File dir = getDir();
File[] files = dir.exists() ? dir.listFiles() : null;
@@ -470,6 +471,7 @@ public class AbilityController implements IAbilityHandler {
private void loadChainedAbilities() {
chainedAbilities.clear();
chainedAbilitiesById.clear();
chainedAbilityScriptHandlers.clear();
File dir = getChainedDir();
File[] files = dir.exists() ? dir.listFiles() : null;
@@ -45,6 +45,7 @@ import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.util.IAbilityConfigCallback;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.ScriptContainer;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.controllers.data.Frame;
import noppes.npcs.controllers.data.AbilityScript;
@@ -237,6 +238,9 @@ public abstract class Ability implements IAbility, IAbilityAction {
// Burst execution state
protected transient int burstIndex = 0;
protected transient List<Entity> burstEntities = new ArrayList<>();
// Per-instance script (isolated per execution, not saved to NBT)
protected transient AbilityScript instanceScript;
protected transient float damageMultiplier = 1.0f; // Ability-internal damage scaling (e.g., Slam height)
// ═══════════════════════════════════════════════════════════════════
@@ -1335,6 +1339,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
telegraphInstances.clear();
previewMode = false;
previewEntityHandler = null;
instanceScript = null;
}
/**
@@ -1647,10 +1652,14 @@ public abstract class Ability implements IAbility, IAbilityAction {
}
// Script handler data (matching CustomEffect.readFromNBT pattern)
// Only create a new handler if one doesn't already exist in the global map,
// to avoid clobbering a live handler's script state during deepCopy/resolve.
if (nbt.hasKey("ScriptData", 10)) {
AbilityScript handler = new AbilityScript(this.id);
handler.readFromNBT(nbt.getCompoundTag("ScriptData"));
setScriptHandler(handler);
if (getScriptHandler() == null) {
AbilityScript handler = new AbilityScript(this.id);
handler.readFromNBT(nbt.getCompoundTag("ScriptData"));
setScriptHandler(handler);
}
}
}
@@ -1753,7 +1762,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
}
public Ability deepCopy() {
return AbilityController.Instance.fromNBT(this.writeNBT(true));
return AbilityController.Instance.fromNBT(this.writeNBT(false));
}
@Override
@@ -2734,4 +2743,25 @@ public abstract class Ability implements IAbility, IAbilityAction {
}
return handler;
}
/**
* Get or create a per-instance script handler for this ability execution.
* Shares the template's engine but creates isolated Bindings for variable state.
* Returns null if no script is configured on the template.
*/
public AbilityScript getOrCreateInstanceScript() {
if (instanceScript != null) return instanceScript;
AbilityScript template = getScriptHandler();
if (template == null || !template.getEnabled() || template.container == null) return null;
instanceScript = new AbilityScript(this.id);
instanceScript.setLanguage(template.getLanguage());
instanceScript.setEnabled(true);
ScriptContainer clone = ((ScriptContainer) template.container).createInstanceScope(instanceScript);
instanceScript.addScriptUnit(clone);
return instanceScript;
}
}
@@ -10,6 +10,7 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.api.ability.IChainedAbility;
import noppes.npcs.controllers.ScriptContainer;
import kamkeel.npcs.util.FileNameHelper;
import java.util.ArrayList;
@@ -75,6 +76,9 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
*/
private NBTTagCompound customData = new NBTTagCompound();
// Per-instance script (isolated per execution, not saved to NBT)
private transient ChainedAbilityScript instanceScript;
public ChainedAbility() {
}
@@ -371,7 +375,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
*/
public ChainedAbility deepCopy() {
ChainedAbility copy = new ChainedAbility();
copy.readNBT(this.writeNBT(true));
copy.readNBT(this.writeNBT(false));
return copy;
}
@@ -468,9 +472,11 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
// Script handler data (matching CustomEffect.readFromNBT pattern)
if (nbt.hasKey("ScriptData", 10)) {
ChainedAbilityScript handler = new ChainedAbilityScript(this.id);
handler.readFromNBT(nbt.getCompoundTag("ScriptData"));
setScriptHandler(handler);
if (getScriptHandler() == null) {
ChainedAbilityScript handler = new ChainedAbilityScript(this.id);
handler.readFromNBT(nbt.getCompoundTag("ScriptData"));
setScriptHandler(handler);
}
}
}
@@ -494,4 +500,32 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
}
return handler;
}
/**
* Get or create a per-instance script handler for this chain execution.
* Shares the template's engine but creates isolated Bindings for variable state.
* Returns null if no script is configured on the template.
*/
public ChainedAbilityScript getOrCreateInstanceScript() {
if (instanceScript != null) return instanceScript;
ChainedAbilityScript template = getScriptHandler();
if (template == null || !template.getEnabled() || template.container == null) return null;
instanceScript = new ChainedAbilityScript(this.id);
instanceScript.setLanguage(template.getLanguage());
instanceScript.setEnabled(true);
ScriptContainer clone = ((ScriptContainer) template.container).createInstanceScope(instanceScript);
instanceScript.addScriptUnit(clone);
return instanceScript;
}
/**
* Clear the per-instance script handler (call on chain completion/reset).
*/
public void clearInstanceScript() {
this.instanceScript = null;
}
}
@@ -88,9 +88,17 @@ public final class AbilityScriptPacket extends AbstractPacket {
Ability ability = AbilityController.Instance.resolveAbility(id);
if (ability == null) {
// Ability not yet saved — send default empty script data so the GUI is usable
// Ability not yet saved — store handler in global map so scripts persist
// until the ability is saved via CustomAbilitySavePacket
AbilityScript data = AbilityController.Instance.abilityScriptHandlers.get(id);
if (data == null) {
data = new AbilityScript(id);
AbilityController.Instance.abilityScriptHandlers.put(id, data);
}
if (requestedAction == Action.GET) {
PacketUtil.getScripts((IScriptHandler) new AbilityScript(id), (EntityPlayerMP) player);
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
} else {
data.saveScript(in, player);
}
return;
}
@@ -88,9 +88,17 @@ public final class ChainedAbilityScriptPacket extends AbstractPacket {
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(id);
if (chain == null) {
// Chain not yet saved — send default empty script data so the GUI is usable
// Chain not yet saved — store handler in global map so scripts persist
// until the chain is saved via ChainedAbilitySavePacket
ChainedAbilityScript data = AbilityController.Instance.chainedAbilityScriptHandlers.get(id);
if (data == null) {
data = new ChainedAbilityScript(id);
AbilityController.Instance.chainedAbilityScriptHandlers.put(id, data);
}
if (requestedAction == Action.GET) {
PacketUtil.getScripts((IScriptHandler) new ChainedAbilityScript(id), (EntityPlayerMP) player);
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
} else {
data.saveScript(in, player);
}
return;
}
@@ -1079,6 +1079,7 @@ public abstract class AbstractDataAbilities {
rollChainCooldown(currentChain);
interruptCooldownRolled = true;
fireChainInterruptEvent(currentChain, chainEntryIndex, getTarget(), source, damage);
currentChain.clearInstanceScript();
currentChain = null;
chainEntryIndex = -1;
chainDelayRemaining = -1;
@@ -1138,6 +1139,7 @@ public abstract class AbstractDataAbilities {
// Roll cooldown
if (currentChain != null) {
rollChainCooldown(currentChain);
currentChain.clearInstanceScript();
currentChain = null;
chainEntryIndex = -1;
chainDelayRemaining = -1;
@@ -1336,6 +1338,7 @@ public abstract class AbstractDataAbilities {
if (currentChain != null) {
rollChainCooldown(currentChain);
fireChainCompleteEvent(currentChain, chainEntryIndex, getTarget());
currentChain.clearInstanceScript();
}
currentChain = null;
chainEntryIndex = -1;
@@ -299,6 +299,7 @@ public class DataAbilities extends AbstractDataAbilities {
if (positionLocked) releaseLockedPosition();
}
// Clear chain and concurrent state
if (currentChain != null) currentChain.clearInstanceScript();
currentChain = null;
chainEntryIndex = -1;
chainDelayRemaining = -1;
@@ -588,6 +589,7 @@ public class DataAbilities extends AbstractDataAbilities {
lastTarget = null;
}
// Also clear chain state
if (currentChain != null) currentChain.clearInstanceScript();
currentChain = null;
chainEntryIndex = -1;
chainDelayRemaining = -1;
+4 -2
View File
@@ -887,7 +887,8 @@ public class EventHooks {
IAbilityEvent abilityEvent = (IAbilityEvent) event;
// 1. Internal ability script (fires for ALL — both NPC and Player)
AbilityScript abScript = ability.getScriptHandler();
// Use per-instance script so each execution has isolated variable state
AbilityScript abScript = ability.getOrCreateInstanceScript();
if (abScript != null) {
abScript.callScript(abilityEvent.getHookName(), event);
}
@@ -916,7 +917,8 @@ public class EventHooks {
IChainEvent chainEvent = (IChainEvent) event;
// 1. Internal chain script (fires first)
ChainedAbilityScript chainScript = chain.getScriptHandler();
// Use per-instance script so each execution has isolated variable state
ChainedAbilityScript chainScript = chain.getOrCreateInstanceScript();
if (chainScript != null) {
chainScript.callScript(chainEvent.getHookName(), event);
}
@@ -794,11 +794,10 @@ public class NoppesUtilServer {
HashMap<String, Integer> map = new HashMap<String, Integer>();
AbilityController controller = AbilityController.Instance;
if (controller != null) {
int index = 0;
for (String name : controller.getAbilityNames()) {
Ability ability = controller.getAbility(name);
if (ability != null) {
map.put(ability.getName(), index++);
map.put(ability.getName(), ability.getAllowedBy().ordinal());
}
}
}
@@ -809,11 +808,10 @@ public class NoppesUtilServer {
HashMap<String, Integer> map = new HashMap<String, Integer>();
AbilityController controller = AbilityController.Instance;
if (controller != null) {
int index = 0;
for (String name : controller.getCustomAbilityNames()) {
Ability ability = controller.getCustomAbility(name);
if (ability == null || !ability.getAllowedBy().allowsNpc()) continue;
map.put(name, index++);
if (ability == null) continue;
map.put(name, ability.getAllowedBy().ordinal());
}
}
sendScrollData(player, map, EnumScrollData.CUSTOM_ABILITIES);
@@ -2,6 +2,7 @@ package noppes.npcs.client.gui.advanced;
import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.enums.UserType;
import kamkeel.npcs.network.PacketClient;
import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket;
@@ -21,6 +22,7 @@ import noppes.npcs.constants.EnumScrollData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
@@ -98,14 +100,18 @@ public class SubGuiAbilityLoad extends SubGuiInterface implements ICustomScrollL
}
private List<String> getFilteredList() {
if (search.isEmpty()) {
return new ArrayList<>(rawData.keySet());
}
List<String> list = new ArrayList<>();
for (String name : rawData.keySet()) {
if (name.toLowerCase().contains(search.toLowerCase())) {
list.add(name);
for (Map.Entry<String, Integer> entry : rawData.entrySet()) {
String name = entry.getKey();
// Only show NPC-valid abilities (NPC_ONLY or BOTH)
UserType ut = UserType.fromOrdinal(entry.getValue());
if (!ut.allowsNpc()) continue;
if (!search.isEmpty() && !name.toLowerCase().contains(search.toLowerCase())) {
continue;
}
list.add(name);
}
return list;
}
@@ -1,5 +1,6 @@
package noppes.npcs.client.gui.advanced;
import kamkeel.npcs.controllers.data.ability.enums.UserType;
import kamkeel.npcs.network.PacketClient;
import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket;
import net.minecraft.client.gui.GuiButton;
@@ -15,6 +16,7 @@ import noppes.npcs.constants.EnumScrollData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
@@ -93,14 +95,18 @@ public class SubGuiAbilitySelect extends SubGuiInterface implements ICustomScrol
merged.putAll(builtInData);
}
if (search.isEmpty()) {
return new ArrayList<>(merged.keySet());
}
List<String> list = new ArrayList<>();
for (String name : merged.keySet()) {
if (name.toLowerCase().contains(search.toLowerCase())) {
list.add(name);
for (Map.Entry<String, Integer> entry : merged.entrySet()) {
String name = entry.getKey();
// Only show NPC-valid abilities (NPC_ONLY or BOTH)
UserType ut = UserType.fromOrdinal(entry.getValue());
if (!ut.allowsNpc()) continue;
if (!search.isEmpty() && !name.toLowerCase().contains(search.toLowerCase())) {
continue;
}
list.add(name);
}
return list;
}
@@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.AbilityController;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
import kamkeel.npcs.controllers.data.ability.data.ChainedAbility;
import kamkeel.npcs.controllers.data.ability.enums.UserType;
import kamkeel.npcs.network.PacketClient;
import java.util.UUID;
@@ -48,6 +49,7 @@ import noppes.npcs.entity.EntityNPCInterface;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
@@ -72,6 +74,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
private static final int BTN_PREVIEW_PLAY = 91;
private static final int BTN_PREVIEW_PAUSE = 92;
private static final int BTN_PREVIEW_STOP = 93;
private static final int BTN_USER_TYPE_FILTER = 11;
// ── TextField / Label IDs ─────────────────────────────────────────────────
private static final int TF_SEARCH = 55;
@@ -99,6 +102,11 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
private boolean showingChained = false;
private boolean currentIsBuiltIn = false;
// ==================== USER TYPE FILTER ====================
// 0 = Both, 1 = NPC Only, 2 = Player Only
private int userTypeFilter = 0;
private static final String[] USER_TYPE_FILTER_LABELS = {"gui.both", "gui.npcs", "gui.players"};
// ==================== CHAINED DATA ====================
private HashMap<String, Integer> chainedData = new HashMap<>();
private ChainedAbility selectedChain = null;
@@ -176,6 +184,11 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
// Search bar
addTextField(new GuiNpcTextField(TF_SEARCH, this, fontRendererObj, guiLeft + 220, guiTop + 192, 143, 20, search));
// User type filter button (only for Custom and Built-in views)
if (!showingChained) {
addButton(new GuiNpcButton(BTN_USER_TYPE_FILTER, guiLeft + 368, guiTop + 192, 45, 20, USER_TYPE_FILTER_LABELS[userTypeFilter]));
}
// Check if we have something to preview
if (showingChained) {
if (selectedChain == null || selected == null || selectedChain.getEntries().isEmpty()) {
@@ -213,14 +226,23 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
private List<String> getSearchList() {
HashMap<String, Integer> data = getCurrentData();
if (search.isEmpty()) {
return new ArrayList<>(data.keySet());
}
List<String> list = new ArrayList<>();
for (String name : data.keySet()) {
if (name.toLowerCase().contains(search.toLowerCase())) {
list.add(name);
for (Map.Entry<String, Integer> entry : data.entrySet()) {
String name = entry.getKey();
// Apply search filter
if (!search.isEmpty() && !name.toLowerCase().contains(search.toLowerCase())) {
continue;
}
// Apply UserType filter (only for Custom and Built-in views)
if (!showingChained && userTypeFilter != 0) {
UserType ut = UserType.fromOrdinal(entry.getValue());
if (userTypeFilter == 1 && !ut.allowsNpc()) continue;
if (userTypeFilter == 2 && !ut.allowsPlayer()) continue;
}
list.add(name);
}
return list;
}
@@ -278,6 +300,16 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
int id = guibutton.id;
// User type filter: Both → NPC → Player → Both
if (id == BTN_USER_TYPE_FILTER) {
userTypeFilter = (userTypeFilter + 1) % 3;
if (scroll != null) {
scroll.setList(getSearchList());
}
initGui();
return;
}
// Toggle: Custom → Built-in → Chained → Custom
if (id == BTN_TOGGLE_VIEW) {
if (!showingBuiltIn && !showingChained) {
@@ -11,6 +11,7 @@ import noppes.npcs.controllers.data.IScriptHandler;
import noppes.npcs.controllers.data.IScriptUnit;
import noppes.npcs.scripted.NpcAPI;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
@@ -52,10 +53,39 @@ public class ScriptContainer implements IScriptUnit {
*/
private String language = null;
/**
* Per-instance bindings for variable isolation. When non-null, this container
* is an instance scope sharing a template's engine. All eval/lookup uses these
* bindings instead of the engine's default ENGINE_SCOPE.
*/
private Bindings instanceBindings = null;
public ScriptContainer(IScriptHandler handler) {
this.handler = handler;
}
/**
* Create a lightweight instance scope that shares this container's engine
* but has its own isolated Bindings for variable state.
* The template's engine is initialized if needed.
*
* @param instanceHandler the script handler for the instance
*/
public ScriptContainer createInstanceScope(IScriptHandler instanceHandler) {
if (this.engine == null) {
this.setEngine(this.getLanguage());
}
ScriptContainer instance = new ScriptContainer(instanceHandler);
instance.script = this.script;
instance.scripts = new ArrayList<>(this.scripts);
instance.console = this.console;
instance.language = this.language;
instance.engine = this.engine;
instance.currentScriptLanguage = this.currentScriptLanguage;
instance.instanceBindings = this.engine.createBindings();
return instance;
}
public void readFromNBT(NBTTagCompound compound) {
String prevScript = this.script;
this.script = compound.getString("Script");
@@ -205,20 +235,33 @@ public class ScriptContainer implements IScriptUnit {
engine.getContext().setWriter(pw);
engine.getContext().setErrorWriter(pw);
engine.put("API", NpcAPI.Instance());
HashMap<String, Object> engineEntries = new HashMap<>(NpcAPI.engineObjects);
for (Map.Entry<String, Object> objectEntry : engineEntries.entrySet()) {
engine.put(objectEntry.getKey(), objectEntry.getValue());
// Populate bindings with API objects
if (instanceBindings != null) {
instanceBindings.put("API", NpcAPI.Instance());
HashMap<String, Object> engineEntries = new HashMap<>(NpcAPI.engineObjects);
for (Map.Entry<String, Object> objectEntry : engineEntries.entrySet()) {
instanceBindings.put(objectEntry.getKey(), objectEntry.getValue());
}
} else {
engine.put("API", NpcAPI.Instance());
HashMap<String, Object> engineEntries = new HashMap<>(NpcAPI.engineObjects);
for (Map.Entry<String, Object> objectEntry : engineEntries.entrySet()) {
engine.put(objectEntry.getKey(), objectEntry.getValue());
}
}
try {
if (!evaluated) {
this.cachedFunctions.clear();
engine.eval(getFullCode());
if (instanceBindings != null) {
engine.eval(getFullCode(), instanceBindings);
} else {
engine.eval(getFullCode());
}
evaluated = true;
}
if (engine.getFactory().getLanguageName().equals("lua")) {
Object ob = engine.get(type);
Object ob = instanceBindings != null ? instanceBindings.get(type) : engine.get(type);
if (ob != null) {
if (luaCoerce == null) {
luaCoerce = Class.forName("org.luaj.vm2.lib.jse.CoerceJavaToLua").getMethod("coerce", Object.class);
@@ -230,8 +273,14 @@ public class ScriptContainer implements IScriptUnit {
}
} else {
if (!this.cachedFunctions.containsKey(type)) {
ScriptObjectMirror global = (ScriptObjectMirror) engine.getBindings(ScriptContext.ENGINE_SCOPE);
ScriptObjectMirror func = (ScriptObjectMirror) global.get(type);
ScriptObjectMirror func;
if (instanceBindings != null) {
Object val = instanceBindings.get(type);
func = val instanceof ScriptObjectMirror ? (ScriptObjectMirror) val : null;
} else {
ScriptObjectMirror global = (ScriptObjectMirror) engine.getBindings(ScriptContext.ENGINE_SCOPE);
func = (ScriptObjectMirror) global.get(type);
}
this.cachedFunctions.put(type, func);
}
ScriptObjectMirror func = this.cachedFunctions.get(type);
@@ -280,6 +329,8 @@ public class ScriptContainer implements IScriptUnit {
public void setEngine(String scriptLanguage) {
if (!Objects.equals(scriptLanguage, this.currentScriptLanguage)) {
// Instance scopes share the template's engine — never create a new one
if (instanceBindings != null) return;
this.currentScriptLanguage = scriptLanguage;
if (ConfigScript.ScriptingECMA6 && scriptLanguage.equals("ECMAScript")) {
System.setProperty("nashorn.args", "--language=es6");
@@ -696,6 +696,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
}
// Clear chain and concurrent state
if (currentChain != null) currentChain.clearInstanceScript();
currentChain = null;
chainEntryIndex = -1;
chainDelayRemaining = -1;
@@ -778,6 +779,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
}
// Clear chain and concurrent state
if (currentChain != null) currentChain.clearInstanceScript();
currentChain = null;
chainEntryIndex = -1;
chainDelayRemaining = -1;