From 6514b217b91ebfc1319a7a192eeb9491705b6551 Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 17 Feb 2026 23:08:12 -0500 Subject: [PATCH 01/12] Abilities are now Stored by Name not UUID. Refactor Built In and Toggleable Abilities --- src/api | 2 +- .../kamkeel/npcs/command/AbilityCommand.java | 26 ++-- .../controllers/data/ability/Ability.java | 134 ++++++++++++++-- .../data/ability/AbilityController.java | 143 +++++++++++++----- .../data/ability/BuiltInAbility.java | 61 -------- .../controllers/data/ability/ToggleEntry.java | 27 ++++ .../noppes/npcs/AbstractDataAbilities.java | 137 ++++++++++++++++- src/main/java/noppes/npcs/DataAbilities.java | 22 +++ .../gui/global/GuiNpcManageAbilities.java | 9 +- .../controllers/data/PlayerAbilityData.java | 25 +++ .../scripted/wrapper/ScriptDataAbilities.java | 4 +- .../wrapper/ScriptPlayerAbilityData.java | 2 +- 12 files changed, 465 insertions(+), 127 deletions(-) delete mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java diff --git a/src/api b/src/api index 22bce4b2a..010849fe8 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 22bce4b2acc9cf9a120a7fdddbb62cc4e567c796 +Subproject commit 010849fe8fb67c0359d7ebdb7bcd5c3c957fe3db diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 10df3cdbe..3f90f9332 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -117,7 +117,8 @@ public class AbilityCommand extends CommandKamkeelBase { return; } - sendResult(sender, "Ability: \u00A7b" + ability.getName()); + sendResult(sender, "Ability: \u00A7b" + ability.getDisplayName()); + sendResult(sender, " Name: \u00A77" + ability.getName()); sendResult(sender, " Type: \u00A7d" + ability.getTypeId()); sendResult(sender, " Cooldown: \u00A7e" + ability.getCooldownTicks() + " ticks"); sendResult(sender, " Wind Up: \u00A7e" + ability.getWindUpTicks() + " ticks"); @@ -188,12 +189,12 @@ public class AbilityCommand extends CommandKamkeelBase { return; } - // Use the ability's canonical ID for storage (registry key for built-in, name for custom) + // Use the ability's canonical ID for storage (registry key for built-in, UUID for custom) String canonicalKey = ability.getId(); if (canonicalKey == null || canonicalKey.isEmpty()) { canonicalKey = abilityKey; // Fallback to user-provided key } - String displayName = ability.getName() != null ? ability.getName() : canonicalKey; + String displayName = ability.getDisplayName(); // Check if ability allows players if (!ability.getAllowedBy().allowsPlayer()) { @@ -232,13 +233,13 @@ public class AbilityCommand extends CommandKamkeelBase { return; } - // Resolve ability to get canonical key + // Resolve ability to get canonical key (registry key for built-in, UUID for custom) Ability ability = AbilityController.Instance.resolveAbility(abilityKey); String canonicalKey = abilityKey; String displayName = abilityKey; if (ability != null) { canonicalKey = ability.getId() != null ? ability.getId() : abilityKey; - displayName = ability.getName() != null ? ability.getName() : canonicalKey; + displayName = ability.getDisplayName(); } // Get player data and lock @@ -284,7 +285,7 @@ public class AbilityCommand extends CommandKamkeelBase { for (int i = 0; i < abilityKeys.length; i++) { String key = abilityKeys[i]; Ability ability = AbilityController.Instance.resolveAbility(key); - String displayName = ability != null && ability.getName() != null ? ability.getName() : key; + String displayName = ability != null ? ability.getDisplayName() : key; String prefix = (i == selected) ? "\u00A7e> " : " "; // Show display name and key if different if (!displayName.equals(key)) { @@ -306,12 +307,17 @@ public class AbilityCommand extends CommandKamkeelBase { /** * Get list of player-usable ability names (for tab completion). - * Uses registry keys for built-in abilities and display names for custom abilities. + * Resolves UUIDs to names so tab completion shows human-readable names. * Used by CommandKamkeel for usage token. */ public static List getPlayerAbilityNames() { - List keys = new ArrayList<>(AbilityController.Instance.getPlayerAbilityKeys()); - Collections.sort(keys); - return keys; + Set keys = AbilityController.Instance.getPlayerAbilityKeys(); + List names = new ArrayList<>(); + for (String key : keys) { + Ability a = AbilityController.Instance.resolveAbility(key); + names.add(a != null ? a.getName() : key); + } + Collections.sort(names); + return names; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 891558b96..14047b0cf 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -44,9 +44,10 @@ public abstract class Ability implements IAbility { // CONFIGURATION (saved to NBT) // ═══════════════════════════════════════════════════════════════════ - protected String id; - protected String name; - protected String typeId; // e.g., "ability.cnpc.slam" (also used as lang key) + protected String id = ""; // UUID for custom, registry key for built-in + protected String name = ""; // Unique file key / identifier + protected String displayName = ""; // Cosmetic name (falls back to name when empty) + protected String typeId = ""; // e.g., "ability.cnpc.slam" (also used as lang key) // Selection protected int weight = 10; @@ -102,6 +103,23 @@ public abstract class Ability implements IAbility { // Cooldown override protected boolean ignoreCooldown = false; + // ═══════════════════════════════════════════════════════════════════ + // BUILT-IN (set in constructor via configureAsBuiltIn, NOT persisted) + // ═══════════════════════════════════════════════════════════════════ + + protected transient boolean builtIn = false; + protected transient String registryKey; + + // ═══════════════════════════════════════════════════════════════════ + // TOGGLE CONFIGURATION (saved to NBT for custom abilities) + // ═══════════════════════════════════════════════════════════════════ + + /** Whether this ability is a toggle (ON/OFF) rather than a phased execution */ + protected boolean toggleable = false; + + /** Whether the toggled-ON state ticks each game tick (e.g., Kaioken drains ki per tick) */ + protected boolean hasActiveToggle = false; + // Configurable potion effects protected List effects = new ArrayList<>(); @@ -205,6 +223,31 @@ public abstract class Ability implements IAbility { return false; } + // ═══════════════════════════════════════════════════════════════════ + // TOGGLE CALLBACKS (optional overrides for toggleable abilities) + // ═══════════════════════════════════════════════════════════════════ + + /** + * Called when this toggle ability is turned ON for an entity. + * Override to apply effects (e.g., set a DBC flag, apply a buff). + */ + public void onToggleOn(EntityLivingBase caster) {} + + /** + * Called when this toggle ability is turned OFF for an entity. + * Override to remove effects (e.g., clear a DBC flag, remove a buff). + */ + public void onToggleOff(EntityLivingBase caster) {} + + /** + * Called every tick while this toggle is active (only if hasActiveToggle is true). + * Override for per-tick effects (e.g., ki drain for Kaioken). + * @return true to keep the toggle active, false to auto-deactivate + */ + public boolean onToggleTick(EntityLivingBase caster, int tickCount) { + return true; + } + /** * Whether this ability is ready for auto-completion during burst overlap mode. * Override to delay auto-completion until all staggered projectiles have been fired. @@ -483,6 +526,8 @@ public abstract class Ability implements IAbility { // ── General tab ────────────────────────────────────────────── defs.add(FieldDef.stringField("gui.name", this::getName, this::setName) .tab("General")); + defs.add(FieldDef.stringField("gui.displayName", this::getRawDisplayName, this::setDisplayName) + .tab("General")); defs.add(FieldDef.row( FieldDef.intField("ability.weight", this::getWeight, this::setWeight).range(1, 1000), FieldDef.boolField("gui.enabled", this::isEnabled, this::setEnabled) @@ -1119,6 +1164,7 @@ public abstract class Ability implements IAbility { NBTTagCompound nbt = new NBTTagCompound(); nbt.setString("id", id); nbt.setString("name", name); + nbt.setString("displayName", displayName); nbt.setString("typeId", typeId); nbt.setInteger("weight", weight); nbt.setBoolean("enabled", enabled); @@ -1150,6 +1196,10 @@ public abstract class Ability implements IAbility { nbt.setInteger("allowedBy", allowedBy.ordinal()); nbt.setBoolean("ignoreCooldown", ignoreCooldown); + // Toggle + nbt.setBoolean("toggleable", toggleable); + nbt.setBoolean("hasActiveToggle", hasActiveToggle); + // Burst nbt.setBoolean("burstEnabled", burstEnabled); nbt.setInteger("burstAmount", burstAmount); @@ -1169,10 +1219,12 @@ public abstract class Ability implements IAbility { } nbt.setTag("effects", effectList); - // Type-specific - NBTTagCompound typeNBT = new NBTTagCompound(); - writeTypeNBT(typeNBT); - nbt.setTag("typeData", typeNBT); + // Type-specific (skip for built-in abilities with fixed configs) + if (!builtIn) { + NBTTagCompound typeNBT = new NBTTagCompound(); + writeTypeNBT(typeNBT); + nbt.setTag("typeData", typeNBT); + } return nbt; } @@ -1180,6 +1232,7 @@ public abstract class Ability implements IAbility { public void readNBT(NBTTagCompound nbt) { id = nbt.getString("id"); name = nbt.getString("name"); + displayName = nbt.getString("displayName"); typeId = nbt.getString("typeId"); weight = nbt.getInteger("weight"); enabled = nbt.getBoolean("enabled"); @@ -1215,6 +1268,10 @@ public abstract class Ability implements IAbility { allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy")); ignoreCooldown = nbt.getBoolean("ignoreCooldown"); + // Toggle + toggleable = nbt.getBoolean("toggleable"); + hasActiveToggle = nbt.getBoolean("hasActiveToggle"); + // Burst burstEnabled = nbt.getBoolean("burstEnabled"); burstAmount = nbt.getInteger("burstAmount"); @@ -1242,7 +1299,10 @@ public abstract class Ability implements IAbility { } } - readTypeNBT(nbt.getCompoundTag("typeData")); + // Type-specific (skip for built-in abilities with fixed configs) + if (!builtIn) { + readTypeNBT(nbt.getCompoundTag("typeData")); + } } // ═══════════════════════════════════════════════════════════════════ @@ -1265,12 +1325,52 @@ public abstract class Ability implements IAbility { this.name = name; } + /** + * Get the display name for this ability. + * Returns displayName if set, otherwise falls back to name. + */ + public String getDisplayName() { + return (displayName != null && !displayName.isEmpty()) ? displayName : name; + } + + /** + * Get the raw display name (may be empty). + */ + public String getRawDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName != null ? displayName : ""; + } + public String getTypeId() { return typeId; } public boolean isBuiltIn() { - return false; + return builtIn; + } + + /** + * Get the registry key for built-in abilities. + * Returns null for custom abilities. + */ + public String getRegistryKey() { + return registryKey; + } + + /** + * Configure this ability as a built-in ability with fixed config. + * Sets builtIn flag, assigns registry key, id, name, and derives typeId. + * Call this in the constructor of any ability that should be non-customizable. + */ + protected void configureAsBuiltIn(String registryKey) { + this.builtIn = true; + this.registryKey = registryKey; + this.id = registryKey; + this.name = registryKey; + this.typeId = "ability." + registryKey.replace(':', '.'); } public Ability deepCopy() { @@ -1727,6 +1827,22 @@ public abstract class Ability implements IAbility { this.ignoreCooldown = ignoreCooldown; } + public boolean isToggleable() { + return toggleable; + } + + public void setToggleable(boolean toggleable) { + this.toggleable = toggleable; + } + + public boolean hasActiveToggle() { + return hasActiveToggle; + } + + public void setHasActiveToggle(boolean hasActiveToggle) { + this.hasActiveToggle = hasActiveToggle; + } + public List getEffects() { return effects; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index 406d66c12..d26f9e0cd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -33,7 +33,8 @@ public class AbilityController implements IAbilityHandler { // ── Core Registries ────────────────────────────────────────────────────── private final Map> abilityTypes = new LinkedHashMap<>(); private final Map builtAbilities = new LinkedHashMap<>(); - private final Map customAbilities = new LinkedHashMap<>(); + private final Map customAbilities = new LinkedHashMap<>(); // name → Ability + private final Map customAbilitiesById = new LinkedHashMap<>(); // UUID → Ability // ── Extension Points ───────────────────────────────────────────────────── private final Map> externalVariants = new LinkedHashMap<>(); @@ -162,6 +163,7 @@ public class AbilityController implements IAbilityHandler { public void load() { customAbilities.clear(); + customAbilitiesById.clear(); int migrated = 0; File dir = getDir(); @@ -177,6 +179,13 @@ public class AbilityController implements IAbilityHandler { Ability ability = fromNBT(nbt); if (ability == null) continue; + // Skip built-in abilities that were erroneously saved as custom files + if (ability.isBuiltIn()) { + file.delete(); + LogWriter.info("Removed stale built-in ability file: " + filename); + continue; + } + // Legacy migration: UUID-named files get renamed to name-based if (UUID_PATTERN.matcher(key).matches()) { String name = ability.getName(); @@ -202,8 +211,24 @@ public class AbilityController implements IAbilityHandler { migrated++; } - ability.setId(key); + // Ensure ability has a UUID; generate one for legacy abilities + String uuid = ability.getId(); + if (uuid == null || uuid.isEmpty() || uuid.equals(key)) { + // Legacy: id was the name or empty — generate a real UUID + uuid = UUID.randomUUID().toString(); + ability.setId(uuid); + // Re-save file with the new UUID + try { + NBTJsonUtil.SaveFile(file, ability.writeNBT()); + } catch (Exception ex) { + LogWriter.error("Failed to save UUID for ability: " + key, ex); + } + migrated++; + } + + ability.setName(key); customAbilities.put(key, ability); + customAbilitiesById.put(uuid, ability); } catch (Exception e) { LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e); } @@ -212,7 +237,7 @@ public class AbilityController implements IAbilityHandler { customAbilityRevision++; if (migrated > 0) { - LogWriter.info("Migrated " + migrated + " UUID-named abilities to name-based files"); + LogWriter.info("Migrated " + migrated + " abilities (UUID-named files or missing UUIDs)"); } LogWriter.info("Loaded " + customAbilities.size() + " custom abilities"); } @@ -223,42 +248,50 @@ public class AbilityController implements IAbilityHandler { public boolean saveCustomAbility(Ability ability) { if (ability == null) return false; + if (ability.isBuiltIn()) return false; String name = ability.getName(); if (name == null || name.isEmpty()) return false; - // Ensure unique name for new abilities (no existing id) - String oldName = ability.getId(); - boolean isNew = oldName == null || oldName.isEmpty(); + String uuid = ability.getId(); + boolean isNew = (uuid == null || uuid.isEmpty()); + if (isNew) { + // Generate UUID for new ability + uuid = UUID.randomUUID().toString(); + ability.setId(uuid); + + // Ensure unique name while (hasCustomAbilityName(name)) { name = name + "_"; } ability.setName(name); + } else { + // Existing ability — check for rename via UUID lookup + Ability existing = customAbilitiesById.get(uuid); + if (existing != null) { + String oldName = existing.getName(); + if (!oldName.equals(name)) { + // Name changed! Ensure new name is unique (excluding self) + String testName = name; + while (customAbilities.containsKey(testName) && !testName.equals(oldName)) { + testName = testName + "_"; + } + if (!testName.equals(name)) { + name = testName; + ability.setName(name); + } + + // Delete old file and remove old map entry + File oldFile = new File(getDir(), oldName + ".json"); + if (oldFile.exists()) { + oldFile.delete(); + } + customAbilities.remove(oldName); + } + } } - // Handle rename: old id (previous name) differs from current name - if (!isNew && !oldName.equals(name)) { - // Check uniqueness of new name (excluding the entry being renamed) - String testName = name; - while (customAbilities.containsKey(testName) && !testName.equals(oldName)) { - testName = testName + "_"; - } - if (!testName.equals(name)) { - name = testName; - ability.setName(name); - } - - // Delete old file and remove old map entry - File oldFile = new File(getDir(), oldName + ".json"); - if (oldFile.exists()) { - oldFile.delete(); - } - customAbilities.remove(oldName); - } - - ability.setId(name); - File dir = getDir(); File fileNew = new File(dir, name + ".json_new"); File fileCurrent = new File(dir, name + ".json"); @@ -276,8 +309,9 @@ public class AbilityController implements IAbilityHandler { } customAbilities.put(name, ability); + customAbilitiesById.put(uuid, ability); customAbilityRevision++; - LogWriter.info("Saved custom ability: " + name); + LogWriter.info("Saved custom ability: " + name + " [" + uuid + "]"); SyncController.syncAllCustomAbilities(); return true; } catch (Exception e) { @@ -292,6 +326,12 @@ public class AbilityController implements IAbilityHandler { Ability removed = customAbilities.remove(name); if (removed == null) return false; + // Also remove from UUID index + String uuid = removed.getId(); + if (uuid != null && !uuid.isEmpty()) { + customAbilitiesById.remove(uuid); + } + File dir = getDir(); File file = new File(dir, name + ".json"); if (file.exists()) { @@ -318,7 +358,14 @@ public class AbilityController implements IAbilityHandler { public void setCustomAbilities(Map synced) { customAbilities.clear(); + customAbilitiesById.clear(); customAbilities.putAll(synced); + for (Ability ability : synced.values()) { + String uuid = ability.getId(); + if (uuid != null && !uuid.isEmpty()) { + customAbilitiesById.put(uuid, ability); + } + } customAbilityRevision++; } @@ -341,6 +388,10 @@ public class AbilityController implements IAbilityHandler { public Ability resolveAbility(String key) { if (key == null || key.isEmpty()) return null; + // Custom: UUID lookup (most common for persistent references) + Ability byUuid = customAbilitiesById.get(key); + if (byUuid != null) return byUuid.deepCopy(); + // Built-in: exact name Ability builtIn = builtAbilities.get(key); if (builtIn != null) return builtIn.deepCopy(); @@ -359,7 +410,7 @@ public class AbilityController implements IAbilityHandler { } } - // Custom: exact name + // Custom: exact name (fallback for legacy references) Ability custom = customAbilities.get(key); if (custom != null) return custom.deepCopy(); @@ -381,11 +432,11 @@ public class AbilityController implements IAbilityHandler { keys.add(id); } } - // Custom abilities keyed by name + // Custom abilities keyed by UUID (stable references) for (Ability ability : customAbilities.values()) { - String name = ability.getName(); - if (name != null && !name.isEmpty()) { - keys.add(name); + String id = ability.getId(); + if (id != null && !id.isEmpty()) { + keys.add(id); } } return keys; @@ -401,12 +452,12 @@ public class AbilityController implements IAbilityHandler { } } } - // Custom abilities keyed by name + // Custom abilities keyed by UUID (stable references) for (Ability ability : customAbilities.values()) { if (ability.getAllowedBy().allowsPlayer()) { - String name = ability.getName(); - if (name != null && !name.isEmpty()) { - keys.add(name); + String id = ability.getId(); + if (id != null && !id.isEmpty()) { + keys.add(id); } } } @@ -414,7 +465,16 @@ public class AbilityController implements IAbilityHandler { } public boolean hasAbility(String key) { - return builtAbilities.containsKey(key) || customAbilities.containsKey(key); + return builtAbilities.containsKey(key) || customAbilities.containsKey(key) + || customAbilitiesById.containsKey(key); + } + + public Ability getCustomAbilityByUUID(String uuid) { + return customAbilitiesById.get(uuid); + } + + public Ability getCustomAbilityByName(String name) { + return customAbilities.get(name); } /** @@ -424,6 +484,9 @@ public class AbilityController implements IAbilityHandler { public boolean canResolveAbility(String key) { if (key == null || key.isEmpty()) return false; + // Custom: UUID lookup (most common for persistent references) + if (customAbilitiesById.containsKey(key)) return true; + // Built-in: exact name if (builtAbilities.containsKey(key)) return true; @@ -437,7 +500,7 @@ public class AbilityController implements IAbilityHandler { if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) return true; } - // Custom: exact name + // Custom: exact name (fallback for legacy references) if (customAbilities.containsKey(key)) return true; // Custom: case-insensitive name diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java deleted file mode 100644 index 36344e9e5..000000000 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java +++ /dev/null @@ -1,61 +0,0 @@ -package kamkeel.npcs.controllers.data.ability; - -import net.minecraft.nbt.NBTTagCompound; - -/** - * Abstract base class for built-in (non-customizable) abilities. - * Built-in abilities are registered by key, have fixed configurations, - * and cannot be modified through the preset GUI system. - *

- * Examples: simple utility abilities like Jump, Sprint, etc. - * Third-party mods can extend this class to create their own built-in abilities. - */ -public abstract class BuiltInAbility extends Ability { - - /** - * The registry key used to look up this ability (e.g., "cnpc:jump", "dbc:ki_blast"). - */ - private final String registryKey; - - public BuiltInAbility(String registryKey) { - this.registryKey = registryKey; - this.id = registryKey; - this.name = registryKey; - // Convert "namespace:name" to "ability.namespace.name" for GUI filter compatibility - this.typeId = "ability." + registryKey.replace(':', '.'); - } - - @Override - public boolean isBuiltIn() { - return true; - } - - /** - * Get the registry key for this built-in ability. - */ - public String getRegistryKey() { - return registryKey; - } - - /** - * Built-in abilities do not write type-specific NBT (they have fixed configs). - */ - @Override - public void writeTypeNBT(NBTTagCompound nbt) { - // No-op: built-in abilities have fixed configurations - } - - /** - * Built-in abilities do not read type-specific NBT. - */ - @Override - public void readTypeNBT(NBTTagCompound nbt) { - // No-op: built-in abilities have fixed configurations - } - - /** - * Get the duration of the active phase in ticks. - * Built-in abilities must define their own active duration. - */ - public abstract int getActiveDurationTicks(); -} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java new file mode 100644 index 000000000..e08350601 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java @@ -0,0 +1,27 @@ +package kamkeel.npcs.controllers.data.ability; + +/** + * Tracks the state of a single active toggle for an entity. + * Stored in AbstractDataAbilities.activeToggles map. + */ +public class ToggleEntry { + private final Ability ability; + private int tickCount; + + public ToggleEntry(Ability ability) { + this.ability = ability; + this.tickCount = 0; + } + + public Ability getAbility() { + return ability; + } + + public int getTickCount() { + return tickCount; + } + + public void incrementTick() { + tickCount++; + } +} diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 774e9661e..9084c7a75 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -3,13 +3,14 @@ package noppes.npcs; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.ToggleEntry; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; -import java.util.List; +import java.util.*; /** * Shared base class for NPC and Player ability data management. @@ -168,6 +169,140 @@ public abstract class AbstractDataAbilities { return currentAbility; } + // ═══════════════════════════════════════════════════════════════════ + // TOGGLE STATE (active toggles, independent of currentAbility) + // ═══════════════════════════════════════════════════════════════════ + + /** Currently active toggles. Key = ability key, Value = toggle entry with tick counter. */ + protected Map activeToggles = new LinkedHashMap<>(); + + /** + * Toggle an ability ON or OFF. If currently on, turns off; if off, turns on. + * @param key The ability key (e.g., "npcdbc:ki_fist") + * @return true if the toggle is now ON, false if OFF + */ + public boolean toggleAbility(String key) { + if (activeToggles.containsKey(key)) { + deactivateToggle(key); + return false; + } else { + return activateToggle(key); + } + } + + /** + * Set a toggle to a specific state. + * @param key The ability key + * @param on true to activate, false to deactivate + */ + public void setAbilityToggled(String key, boolean on) { + if (on && !activeToggles.containsKey(key)) { + activateToggle(key); + } else if (!on && activeToggles.containsKey(key)) { + deactivateToggle(key); + } + } + + /** + * Check if a toggle ability is currently active. + */ + public boolean isAbilityToggled(String key) { + return activeToggles.containsKey(key); + } + + /** + * Get all active toggle keys. + */ + public Set getActiveToggleKeys() { + return new LinkedHashSet<>(activeToggles.keySet()); + } + + private boolean activateToggle(String key) { + Ability ability = AbilityController.Instance != null + ? AbilityController.Instance.resolveAbility(key) : null; + if (ability == null || !ability.isToggleable()) return false; + + ToggleEntry entry = new ToggleEntry(ability); + activeToggles.put(key, entry); + ability.onToggleOn(getEntity()); + onToggleStateChanged(key, true); + return true; + } + + private void deactivateToggle(String key) { + ToggleEntry entry = activeToggles.remove(key); + if (entry != null) { + entry.getAbility().onToggleOff(getEntity()); + onToggleStateChanged(key, false); + } + } + + /** + * Tick all active toggles. Call from tick() each game tick. + * Toggles with hasActiveToggle=true get their onToggleTick() called. + * If onToggleTick returns false, the toggle is auto-deactivated. + */ + protected void tickActiveToggles() { + if (activeToggles.isEmpty()) return; + + EntityLivingBase entity = getEntity(); + List toRemove = null; + + for (Map.Entry mapEntry : activeToggles.entrySet()) { + ToggleEntry entry = mapEntry.getValue(); + entry.incrementTick(); + + if (entry.getAbility().hasActiveToggle()) { + if (!entry.getAbility().onToggleTick(entity, entry.getTickCount())) { + if (toRemove == null) toRemove = new ArrayList<>(); + toRemove.add(mapEntry.getKey()); + } + } + } + + if (toRemove != null) { + for (String key : toRemove) { + ToggleEntry entry = activeToggles.remove(key); + if (entry != null) { + entry.getAbility().onToggleOff(entity); + onToggleStateChanged(key, false); + } + } + } + } + + /** Hook for subclass to react to toggle state changes (sync packets, script events). */ + protected void onToggleStateChanged(String key, boolean active) {} + + /** + * Clear all active toggles (e.g., on death/reset). + * Calls onToggleOff for each active toggle. + */ + protected void clearActiveToggles() { + if (activeToggles.isEmpty()) return; + EntityLivingBase entity = getEntity(); + for (ToggleEntry entry : activeToggles.values()) { + entry.getAbility().onToggleOff(entity); + } + activeToggles.clear(); + } + + /** + * Add a toggle entry directly without calling onToggleOn. + * Used for client-side sync and NBT restoration. + */ + public void setToggleEntryDirect(String key, boolean active) { + if (active) { + Ability ability = AbilityController.Instance != null + ? AbilityController.Instance.resolveAbility(key) : null; + if (ability != null && ability.isToggleable()) { + activeToggles.put(key, new ToggleEntry(ability)); + } + } else { + activeToggles.remove(key); + } + } + // ═══════════════════════════════════════════════════════════════════ // COOLDOWN MANAGEMENT // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index cb1e5104d..b303a8c52 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -11,6 +11,7 @@ import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; import net.minecraft.util.DamageSource; import noppes.npcs.controllers.data.Animation; import noppes.npcs.entity.EntityNPCInterface; @@ -297,6 +298,9 @@ public class DataAbilities extends AbstractDataAbilities { if (positionLocked) releaseLockedPosition(); } + // Tick active toggles (independent of currentAbility) + tickActiveToggles(); + // Tick current ability if executing (shared logic from base class) if (currentAbility != null && currentAbility.isExecuting()) { tickCurrentAbility(); @@ -664,6 +668,7 @@ public class DataAbilities extends AbstractDataAbilities { */ public void reset() { stopCurrentAbility(); + clearActiveToggles(); // Roll cooldown so NPC doesn't immediately attack after reset rollCooldownOnReset(); @@ -983,6 +988,13 @@ public class DataAbilities extends AbstractDataAbilities { } compound.setTag("Abilities", abilityList); + // Active toggles + NBTTagList toggleList = new NBTTagList(); + for (String key : activeToggles.keySet()) { + toggleList.appendTag(new NBTTagString(key)); + } + compound.setTag("ActiveToggles", toggleList); + return compound; } @@ -1000,5 +1012,15 @@ public class DataAbilities extends AbstractDataAbilities { abilitySlots.add(slot); } } + + // Active toggles - restore state directly (no onToggleOn callback during load) + activeToggles.clear(); + if (compound.hasKey("ActiveToggles")) { + NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 8); // 8 = TAG_STRING + for (int i = 0; i < toggleNbt.tagCount(); i++) { + String key = toggleNbt.getStringTagAt(i); + setToggleEntryDirect(key, true); + } + } } } diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index d016e7c77..9378e606b 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -474,9 +474,12 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface private boolean hasDuplicateName(Ability ability) { String name = ability.getName(); if (name == null || name.isEmpty()) return false; - String oldName = ability.getId(); - // A duplicate exists if the name is already in use and it's not the same ability being edited - return customData.containsKey(name) && !name.equals(oldName); + if (!customData.containsKey(name)) return false; + // Name exists — check if it belongs to a different ability (by UUID) + String uuid = ability.getId(); + if (uuid == null || uuid.isEmpty()) return true; // New ability with conflicting name + Ability existing = AbilityController.Instance.getCustomAbilityByName(name); + return existing == null || !uuid.equals(existing.getId()); } @Override diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index f5ffb7028..93215daae 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -263,6 +263,9 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA return; } + // Tick active toggles (independent of currentAbility) + tickActiveToggles(); + if (currentAbility != null && currentAbility.isExecuting()) { tickCurrentAbility(); @@ -385,6 +388,11 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA } } + @Override + protected void onToggleStateChanged(String key, boolean active) { + syncToClient(); + } + // ═══════════════════════════════════════════════════════════════════ // SELECTION // ═══════════════════════════════════════════════════════════════════ @@ -750,6 +758,13 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA compound.setTag("PlayerAbilities", list); compound.setInteger("PlayerAbilitySelected", selectedIndex); compound.setBoolean("AbilityAnimating", playingAbilityAnimation); + + // Active toggles + NBTTagList toggleList = new NBTTagList(); + for (String key : activeToggles.keySet()) { + toggleList.appendTag(new NBTTagString(key)); + } + compound.setTag("ActiveToggles", toggleList); } public void readFromNBT(NBTTagCompound compound) { @@ -772,6 +787,16 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA selectedIndex = Math.max(0, unlockedAbilities.size() - 1); } playingAbilityAnimation = compound.getBoolean("AbilityAnimating"); + + // Active toggles - restore state directly (no onToggleOn callback during load) + activeToggles.clear(); + if (compound.hasKey("ActiveToggles")) { + NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 8); // 8 = TAG_STRING + for (int i = 0; i < toggleNbt.tagCount(); i++) { + String key = toggleNbt.getStringTagAt(i); + setToggleEntryDirect(key, true); + } + } } /** diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java index e27f6e075..b356b55c6 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java @@ -61,7 +61,9 @@ public class ScriptDataAbilities implements IDataAbilities { if (!ability.getAllowedBy().allowsNpc()) { return; // Ability is PLAYER_ONLY } - data.addAbilityReference(key); + // Store by UUID/registry key for stable reference + String canonicalKey = ability.getId() != null ? ability.getId() : key; + data.addAbilityReference(canonicalKey); } @Override diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java index c1de83878..42ed3372e 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java @@ -36,7 +36,7 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { if (!ability.getAllowedBy().allowsPlayer()) { return; // Ability is NPC-only } - // Use canonical key (registry key for built-in, name for custom) + // Use canonical key (registry key for built-in, UUID for custom) String canonicalKey = ability.getId() != null ? ability.getId() : key; data.unlockAbility(canonicalKey); } From 2f814c1903fe0cd803aba4c9362003fd4fea7a05 Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 17 Feb 2026 23:22:03 -0500 Subject: [PATCH 02/12] IAbilityAction interface, AbilityAction unified slot, and bug fixes - Added IAbilityAction interface (internal + API) for unified Ability/ChainedAbility handling - Created AbilityAction (replaces AbilitySlot) with 3 modes: INLINE_ABILITY, ABILITY_REFERENCE, CHAIN_REFERENCE - Unified DataAbilities storage from dual lists to single List - Added ChainedAbility, ChainedAbilityEntry, ChainedAbilityController - Updated PlayerAbilityData with resolveActionKey() for chain: prefix resolution - Added resolveAction/canResolveAction to AbilityController - Fixed unsafe cast in DataAbilities.onDamage() - Fixed NPE in GuiNPCAbilities for chain reference slots - Fixed NPC chain interrupt double cooldown - Fixed removeAbility() incorrectly removing chain references Co-Authored-By: Claude Opus 4.6 --- .../kamkeel/npcs/command/AbilityCommand.java | 4 +- .../npcs/controllers/SyncController.java | 43 ++- .../controllers/data/ability/Ability.java | 12 +- .../data/ability/AbilityAction.java | 247 +++++++++++++ .../data/ability/AbilityController.java | 45 +++ .../controllers/data/ability/AbilitySlot.java | 3 + .../data/ability/ChainedAbility.java | 347 ++++++++++++++++++ .../ability/ChainedAbilityController.java | 207 +++++++++++ .../data/ability/ChainedAbilityEntry.java | 63 ++++ .../data/ability/IAbilityAction.java | 50 +++ .../npcs/network/enums/EnumSyncType.java | 3 +- .../noppes/npcs/AbstractDataAbilities.java | 205 ++++++++++- src/main/java/noppes/npcs/CustomNpcs.java | 2 + src/main/java/noppes/npcs/DataAbilities.java | 254 ++++++++----- .../client/gui/advanced/GuiNPCAbilities.java | 60 +-- .../controllers/data/PlayerAbilityData.java | 65 +++- .../scripted/wrapper/ScriptDataAbilities.java | 16 +- 17 files changed, 1482 insertions(+), 144 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 10df3cdbe..2e91c2962 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -2,6 +2,7 @@ package kamkeel.npcs.command; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayerMP; @@ -77,7 +78,8 @@ public class AbilityCommand extends CommandKamkeelBase { ) public void reload(ICommandSender sender, String[] args) { AbilityController.Instance.load(); - sendResult(sender, "Abilities reloaded from disk."); + ChainedAbilityController.Instance.load(); + sendResult(sender, "Abilities and chained abilities reloaded from disk."); } @SubCommand( diff --git a/src/main/java/kamkeel/npcs/controllers/SyncController.java b/src/main/java/kamkeel/npcs/controllers/SyncController.java index b6010ad86..6789e1227 100644 --- a/src/main/java/kamkeel/npcs/controllers/SyncController.java +++ b/src/main/java/kamkeel/npcs/controllers/SyncController.java @@ -41,6 +41,8 @@ import noppes.npcs.controllers.data.RecipeAnvil; import noppes.npcs.controllers.data.RecipeCarpentry; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; import java.io.IOException; import java.util.EnumMap; @@ -76,7 +78,8 @@ public class SyncController { EnumSyncType.CUSTOM_EFFECTS, EnumSyncType.MAGIC, EnumSyncType.MAGIC_CYCLE, - EnumSyncType.CUSTOM_ABILITY + EnumSyncType.CUSTOM_ABILITY, + EnumSyncType.CHAINED_ABILITY }; public static void load() { @@ -93,6 +96,7 @@ public class SyncController { registerCache(EnumSyncType.MAGIC, SyncController::magicsNBT); registerCache(EnumSyncType.MAGIC_CYCLE, SyncController::magicCyclesNBT); registerCache(EnumSyncType.CUSTOM_ABILITY, SyncController::customAbilitiesNBT); + registerCache(EnumSyncType.CHAINED_ABILITY, SyncController::chainedAbilitiesNBT); } public static void syncPlayer(EntityPlayerMP player) { @@ -349,6 +353,27 @@ public class SyncController { updateAllPlayerRevisions(EnumSyncType.CUSTOM_ABILITY, payload.getRevision()); } + public static NBTTagCompound chainedAbilitiesNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Map.Entry entry : ChainedAbilityController.Instance.getChainedAbilities().entrySet()) { + NBTTagCompound chainNBT = entry.getValue().writeNBT(); + chainNBT.setString("ChainedAbilityId", entry.getKey()); + list.appendTag(chainNBT); + } + compound.setTag("Data", list); + return compound; + } + + public static void syncAllChainedAbilities() { + CachedSyncPayload payload = rebuildNow(EnumSyncType.CHAINED_ABILITY); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.CHAINED_ABILITY, payload)); + updateAllPlayerRevisions(EnumSyncType.CHAINED_ABILITY, payload.getRevision()); + } + public static void syncPlayerData(EntityPlayerMP player, boolean update) { PlayerData data = PlayerData.get(player); if (data != null) { @@ -636,6 +661,20 @@ public class SyncController { AbilityController.Instance.setCustomAbilities(sync); break; } + case CHAINED_ABILITY: { + NBTTagList list = fullCompound.getTagList("Data", 10); + LinkedHashMap sync = new LinkedHashMap<>(); + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound nbt = list.getCompoundTagAt(i); + String id = nbt.getString("ChainedAbilityId"); + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(nbt); + chain.setName(id); + sync.put(id, chain); + } + ChainedAbilityController.Instance.setChainedAbilities(sync); + break; + } } ClientCacheHandler.updateClientRevision(enumSyncType, revision); @@ -886,6 +925,8 @@ public class SyncController { return EnumSet.of(EnumSyncType.CUSTOM_EFFECTS); case CUSTOM_ABILITY: return EnumSet.of(EnumSyncType.CUSTOM_ABILITY); + case CHAINED_ABILITY: + return EnumSet.of(EnumSyncType.CHAINED_ABILITY); default: return EnumSet.noneOf(EnumSyncType.class); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 891558b96..624bea8ae 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -38,7 +38,7 @@ import java.util.List; import net.minecraft.entity.Entity; -public abstract class Ability implements IAbility { +public abstract class Ability implements IAbility, IAbilityAction { // ═══════════════════════════════════════════════════════════════════ // CONFIGURATION (saved to NBT) @@ -1277,6 +1277,16 @@ public abstract class Ability implements IAbility { return AbilityController.Instance.fromNBT(this.writeNBT()); } + @Override + public boolean isChain() { + return false; + } + + @Override + public IAbilityAction deepCopyAction() { + return deepCopy(); + } + public boolean isNpcInlineEdit() { return npcInlineEdit; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java new file mode 100644 index 000000000..6d721ff81 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -0,0 +1,247 @@ +package kamkeel.npcs.controllers.data.ability; + +import net.minecraft.nbt.NBTTagCompound; + +/** + * Unified slot for storing any combat action: inline ability, ability reference, or chain reference. + * Replaces {@link AbilitySlot} with support for all {@link IAbilityAction} types. + *

+ * References are lazy-resolved via their respective controllers and cached until + * the controller revision changes. + */ +public class AbilityAction { + + public enum SlotType { + INLINE_ABILITY, + ABILITY_REFERENCE, + CHAIN_REFERENCE + } + + private SlotType slotType; + private Ability inlineAbility; + private String referenceId; + + /** Per-slot enabled override for reference slots (null = use master's default). */ + private Boolean enabledOverride; + + // Cache + private transient IAbilityAction cachedAction; + private transient int cachedRevision = -1; + + private AbilityAction() {} + + // ═══════════════════════════════════════════════════════════════════ + // FACTORY METHODS + // ═══════════════════════════════════════════════════════════════════ + + public static AbilityAction inline(Ability ability) { + AbilityAction slot = new AbilityAction(); + slot.slotType = SlotType.INLINE_ABILITY; + slot.inlineAbility = ability; + return slot; + } + + public static AbilityAction abilityReference(String referenceId) { + AbilityAction slot = new AbilityAction(); + slot.slotType = SlotType.ABILITY_REFERENCE; + slot.referenceId = referenceId; + return slot; + } + + public static AbilityAction chainReference(String referenceId) { + AbilityAction slot = new AbilityAction(); + slot.slotType = SlotType.CHAIN_REFERENCE; + slot.referenceId = referenceId; + return slot; + } + + // ═══════════════════════════════════════════════════════════════════ + // QUERIES + // ═══════════════════════════════════════════════════════════════════ + + public SlotType getSlotType() { + return slotType; + } + + public boolean isReference() { + return slotType != SlotType.INLINE_ABILITY; + } + + public boolean isChainReference() { + return slotType == SlotType.CHAIN_REFERENCE; + } + + public boolean isAbilityReference() { + return slotType == SlotType.ABILITY_REFERENCE; + } + + public String getReferenceId() { + return referenceId; + } + + // ═══════════════════════════════════════════════════════════════════ + // RESOLUTION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Resolve the action. Inline returns directly; references resolve + * from the appropriate controller with version-based caching. + */ + public IAbilityAction getAction() { + switch (slotType) { + case INLINE_ABILITY: + return inlineAbility; + + case ABILITY_REFERENCE: { + AbilityController ctrl = AbilityController.Instance; + if (ctrl == null) return null; + int rev = ctrl.getCustomAbilityRevision(); + if (cachedAction != null && cachedRevision == rev) { + return cachedAction; + } + Ability resolved = ctrl.resolveAbility(referenceId); + if (resolved != null && enabledOverride != null) { + resolved.setEnabled(enabledOverride); + } + cachedAction = resolved; + cachedRevision = rev; + return cachedAction; + } + + case CHAIN_REFERENCE: { + ChainedAbilityController ctrl = ChainedAbilityController.Instance; + if (ctrl == null) return null; + int rev = ctrl.getRevision(); + if (cachedAction != null && cachedRevision == rev) { + return cachedAction; + } + ChainedAbility resolved = ctrl.resolve(referenceId); + if (resolved != null && enabledOverride != null) { + resolved.setEnabled(enabledOverride); + } + cachedAction = resolved; + cachedRevision = rev; + return cachedAction; + } + + default: + return null; + } + } + + /** + * Get as Ability (returns null if this is a chain slot or resolution fails). + */ + public Ability getAbility() { + IAbilityAction action = getAction(); + return (action instanceof Ability) ? (Ability) action : null; + } + + /** + * Get as ChainedAbility (returns null if this is an ability slot or resolution fails). + */ + public ChainedAbility getChainedAbility() { + IAbilityAction action = getAction(); + return (action instanceof ChainedAbility) ? (ChainedAbility) action : null; + } + + // ═══════════════════════════════════════════════════════════════════ + // ENABLED OVERRIDE + // ═══════════════════════════════════════════════════════════════════ + + public void setEnabled(boolean enabled) { + if (slotType == SlotType.INLINE_ABILITY) { + if (inlineAbility != null) { + inlineAbility.setEnabled(enabled); + } + } else { + this.enabledOverride = enabled; + if (cachedAction != null) { + cachedAction.setEnabled(enabled); + } + } + } + + // ═══════════════════════════════════════════════════════════════════ + // INLINE CONVERSION (ability references only) + // ═══════════════════════════════════════════════════════════════════ + + public Ability getInlineAbility() { + return inlineAbility; + } + + /** + * Convert this reference slot to an inline slot by copying the resolved ability. + * Only works for ability references (chains cannot be inlined). + */ + public boolean convertToInline() { + if (slotType != SlotType.ABILITY_REFERENCE) return false; + + Ability resolved = getAbility(); + if (resolved == null) return false; + + inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT()); + slotType = SlotType.INLINE_ABILITY; + referenceId = null; + cachedAction = null; + cachedRevision = -1; + return true; + } + + // ═══════════════════════════════════════════════════════════════════ + // NBT + // ═══════════════════════════════════════════════════════════════════ + + public NBTTagCompound writeNBT() { + switch (slotType) { + case CHAIN_REFERENCE: { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setString("ChainReference", referenceId); + if (enabledOverride != null) { + nbt.setBoolean("RefEnabled", enabledOverride); + } + return nbt; + } + + case ABILITY_REFERENCE: { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setString("Reference", referenceId); + if (enabledOverride != null) { + nbt.setBoolean("RefEnabled", enabledOverride); + } + return nbt; + } + + case INLINE_ABILITY: + default: + return inlineAbility != null ? inlineAbility.writeNBT() : new NBTTagCompound(); + } + } + + public static AbilityAction fromNBT(NBTTagCompound nbt) { + if (nbt == null) return null; + + // Chain reference (new format) + if (nbt.hasKey("ChainReference")) { + AbilityAction slot = chainReference(nbt.getString("ChainReference")); + if (nbt.hasKey("RefEnabled")) { + slot.enabledOverride = nbt.getBoolean("RefEnabled"); + } + return slot; + } + + // Ability reference (existing format) + if (nbt.hasKey("Reference")) { + AbilityAction slot = abilityReference(nbt.getString("Reference")); + if (nbt.hasKey("RefEnabled")) { + slot.enabledOverride = nbt.getBoolean("RefEnabled"); + } + return slot; + } + + // Inline ability (existing format) + Ability ability = AbilityController.Instance.fromNBT(nbt); + if (ability == null) return null; + return inline(ability); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index 406d66c12..23a718430 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -448,6 +448,36 @@ public class AbilityController implements IAbilityHandler { return false; } + // ═══════════════════════════════════════════════════════════════════════════ + // UNIFIED ACTION RESOLUTION + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Resolve any action key to an IAbilityAction. + * Checks abilities first, then chained abilities. + */ + public IAbilityAction resolveAction(String key) { + if (key == null || key.isEmpty()) return null; + + Ability ability = resolveAbility(key); + if (ability != null) return ability; + + if (ChainedAbilityController.Instance != null) { + return ChainedAbilityController.Instance.resolve(key); + } + + return null; + } + + /** + * Check if a key can be resolved as any action (ability or chain). + */ + public boolean canResolveAction(String key) { + if (canResolveAbility(key)) return true; + return ChainedAbilityController.Instance != null + && ChainedAbilityController.Instance.canResolve(key); + } + // ═══════════════════════════════════════════════════════════════════════════ // EXTENSION POINTS // ═══════════════════════════════════════════════════════════════════════════ @@ -621,4 +651,19 @@ public class AbilityController implements IAbilityHandler { public boolean deleteCustomAbilityByName(String name) { return deleteCustomAbility(name); } + + @Override + public String[] getChainedAbilityNames() { + return ChainedAbilityController.Instance.getNames().toArray(new String[0]); + } + + @Override + public boolean hasChainedAbilityName(String name) { + return ChainedAbilityController.Instance.hasName(name); + } + + @Override + public boolean deleteChainedAbilityByName(String name) { + return ChainedAbilityController.Instance.delete(name); + } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java index ab8446114..212cd7d16 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java @@ -8,7 +8,10 @@ import net.minecraft.nbt.NBTTagCompound; *

* References are lazy-resolved via {@link AbilityController#resolveAbility(String)} * and cached until the controller version changes. + * + * @deprecated Use {@link AbilityAction} instead, which supports abilities and chained abilities. */ +@Deprecated public class AbilitySlot { private Ability inlineAbility; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java new file mode 100644 index 000000000..1ba1a204e --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java @@ -0,0 +1,347 @@ +package kamkeel.npcs.controllers.data.ability; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.api.ability.IChainedAbility; + +import java.util.ArrayList; +import java.util.List; + +/** + * A chained ability is an ordered sequence of ability references that execute + * one after another. It has its own conditions, cooldown, weight, and per-entry + * delay configuration. Individual ability conditions are skipped during chain execution. + *

+ * All entries must be ability references (by name/key). Inline abilities are not supported. + * When saving, all references are validated against {@link AbilityController}. + */ +public class ChainedAbility implements IChainedAbility, IAbilityAction { + + // ═══════════════════════════════════════════════════════════════════ + // CONFIGURATION (saved to NBT) + // ═══════════════════════════════════════════════════════════════════ + + private String name = ""; + + private boolean enabled = true; + + /** Weight for NPC weighted random selection. */ + private int weight = 10; + + /** Whether each ability in the chain uses its own windup phase. */ + private boolean windUpAll = true; + + /** Chain-level cooldown in ticks (applied after entire chain completes). */ + private int cooldownTicks = 100; + + /** Minimum range for NPC eligibility check. */ + private float minRange = 0; + + /** Maximum range for NPC eligibility check. */ + private float maxRange = 20; + + /** Which entity types can use this chained ability. */ + private UserType allowedBy = UserType.BOTH; + + /** Chain-level conditions (individual ability conditions are ignored). */ + private List conditions = new ArrayList<>(); + + /** Ordered list of ability entries to execute. */ + private List entries = new ArrayList<>(); + + public ChainedAbility() {} + + public ChainedAbility(String name) { + this.name = name != null ? name : ""; + } + + // ═══════════════════════════════════════════════════════════════════ + // GETTERS / SETTERS + // ═══════════════════════════════════════════════════════════════════ + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name != null ? name : ""; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = Math.max(1, weight); + } + + public boolean isWindUpAll() { + return windUpAll; + } + + public void setWindUpAll(boolean windUpAll) { + this.windUpAll = windUpAll; + } + + public int getCooldownTicks() { + return cooldownTicks; + } + + public void setCooldownTicks(int cooldownTicks) { + this.cooldownTicks = Math.max(0, cooldownTicks); + } + + public float getMinRange() { + return minRange; + } + + public void setMinRange(float minRange) { + this.minRange = Math.max(0, minRange); + } + + public float getMaxRange() { + return maxRange; + } + + public void setMaxRange(float maxRange) { + this.maxRange = Math.max(0, maxRange); + } + + public UserType getAllowedBy() { + return allowedBy; + } + + public void setAllowedBy(UserType allowedBy) { + this.allowedBy = allowedBy != null ? allowedBy : UserType.BOTH; + } + + public List getConditions() { + return conditions; + } + + public void setConditions(List conditions) { + this.conditions = conditions != null ? conditions : new ArrayList<>(); + } + + public List getEntries() { + return entries; + } + + public void setEntries(List entries) { + this.entries = entries != null ? entries : new ArrayList<>(); + } + + // ═══════════════════════════════════════════════════════════════════ + // IChainedAbility INTERFACE + // ═══════════════════════════════════════════════════════════════════ + + @Override + public int getEntryCount() { + return entries.size(); + } + + @Override + public String getEntryReference(int index) { + if (index < 0 || index >= entries.size()) return null; + return entries.get(index).getAbilityReference(); + } + + @Override + public int getEntryDelay(int index) { + if (index < 0 || index >= entries.size()) return 0; + return entries.get(index).getDelayTicks(); + } + + // ═══════════════════════════════════════════════════════════════════ + // ENTRY MANAGEMENT + // ═══════════════════════════════════════════════════════════════════ + + public void addEntry(ChainedAbilityEntry entry) { + if (entry != null) { + entries.add(entry); + } + } + + public void addEntry(String abilityReference, int delayTicks) { + entries.add(new ChainedAbilityEntry(abilityReference, delayTicks)); + } + + public void removeEntry(int index) { + if (index >= 0 && index < entries.size()) { + entries.remove(index); + } + } + + public void moveEntryUp(int index) { + if (index > 0 && index < entries.size()) { + ChainedAbilityEntry entry = entries.remove(index); + entries.add(index - 1, entry); + } + } + + public void moveEntryDown(int index) { + if (index >= 0 && index < entries.size() - 1) { + ChainedAbilityEntry entry = entries.remove(index); + entries.add(index + 1, entry); + } + } + + // ═══════════════════════════════════════════════════════════════════ + // CONDITIONS + // ═══════════════════════════════════════════════════════════════════ + + /** + * Check all chain-level conditions against the caster and target. + */ + public boolean checkConditions(EntityLivingBase caster, EntityLivingBase target) { + for (Condition c : conditions) { + if (!c.check(caster, target)) { + return false; + } + } + return true; + } + + /** + * Check conditions for player use (skip target-requiring conditions). + */ + public boolean checkConditionsForPlayer(EntityLivingBase caster) { + for (Condition c : conditions) { + if (c.requiresTarget()) continue; + if (!c.check(caster, null)) { + return false; + } + } + return true; + } + + // ═══════════════════════════════════════════════════════════════════ + // VALIDATION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Validate this chained ability. Returns a list of error messages. + * An empty list means valid. + */ + public List validate() { + List errors = new ArrayList<>(); + + if (name == null || name.isEmpty()) { + errors.add("Name is required"); + } + + if (entries.isEmpty()) { + errors.add("At least one ability entry is required"); + } + + AbilityController controller = AbilityController.Instance; + if (controller != null) { + for (int i = 0; i < entries.size(); i++) { + ChainedAbilityEntry entry = entries.get(i); + String ref = entry.getAbilityReference(); + if (ref == null || ref.isEmpty()) { + errors.add("Entry " + (i + 1) + ": ability reference is empty"); + } else if (!controller.canResolveAbility(ref)) { + errors.add("Entry " + (i + 1) + ": cannot resolve ability '" + ref + "'"); + } + } + } + + return errors; + } + + // ═══════════════════════════════════════════════════════════════════ + // DEEP COPY + // ═══════════════════════════════════════════════════════════════════ + + /** + * Create a deep copy via NBT round-trip. + */ + public ChainedAbility deepCopy() { + ChainedAbility copy = new ChainedAbility(); + copy.readNBT(this.writeNBT()); + return copy; + } + + @Override + public boolean isChain() { + return true; + } + + @Override + public IAbilityAction deepCopyAction() { + return deepCopy(); + } + + // ═══════════════════════════════════════════════════════════════════ + // NBT SERIALIZATION + // ═══════════════════════════════════════════════════════════════════ + + public NBTTagCompound writeNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setString("Name", name); + nbt.setBoolean("Enabled", enabled); + nbt.setInteger("Weight", weight); + nbt.setBoolean("WindUpAll", windUpAll); + nbt.setInteger("CooldownTicks", cooldownTicks); + nbt.setFloat("MinRange", minRange); + nbt.setFloat("MaxRange", maxRange); + nbt.setInteger("AllowedBy", allowedBy.ordinal()); + + // Conditions + NBTTagList condList = new NBTTagList(); + for (Condition c : conditions) { + condList.appendTag(c.writeNBT()); + } + nbt.setTag("Conditions", condList); + + // Entries + NBTTagList entryList = new NBTTagList(); + for (ChainedAbilityEntry entry : entries) { + entryList.appendTag(entry.writeNBT()); + } + nbt.setTag("Entries", entryList); + + return nbt; + } + + public void readNBT(NBTTagCompound nbt) { + name = nbt.getString("Name"); + enabled = !nbt.hasKey("Enabled") || nbt.getBoolean("Enabled"); + weight = nbt.hasKey("Weight") ? nbt.getInteger("Weight") : 10; + windUpAll = !nbt.hasKey("WindUpAll") || nbt.getBoolean("WindUpAll"); + cooldownTicks = nbt.hasKey("CooldownTicks") ? nbt.getInteger("CooldownTicks") : 100; + minRange = nbt.getFloat("MinRange"); + maxRange = nbt.hasKey("MaxRange") ? nbt.getFloat("MaxRange") : 20; + allowedBy = UserType.fromOrdinal(nbt.getInteger("AllowedBy")); + + // Conditions + conditions.clear(); + NBTTagList condList = nbt.getTagList("Conditions", 10); + for (int i = 0; i < condList.tagCount(); i++) { + Condition c = Condition.fromNBT(condList.getCompoundTagAt(i)); + if (c != null) { + conditions.add(c); + } + } + + // Entries + entries.clear(); + NBTTagList entryList = nbt.getTagList("Entries", 10); + for (int i = 0; i < entryList.tagCount(); i++) { + ChainedAbilityEntry entry = ChainedAbilityEntry.fromNBT(entryList.getCompoundTagAt(i)); + if (entry != null) { + entries.add(entry); + } + } + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java new file mode 100644 index 000000000..05819dea4 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java @@ -0,0 +1,207 @@ +package kamkeel.npcs.controllers.data.ability; + +import kamkeel.npcs.controllers.SyncController; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.CustomNpcs; +import noppes.npcs.LogWriter; +import noppes.npcs.util.NBTJsonUtil; + +import java.io.File; +import java.util.*; + +/** + * Global registry and storage for {@link ChainedAbility} definitions. + * Follows the same patterns as {@link AbilityController} for custom abilities: + * JSON file storage, revision tracking for cache invalidation, and client sync. + */ +public class ChainedAbilityController { + + public static ChainedAbilityController Instance = new ChainedAbilityController(); + + private final Map chainedAbilities = new LinkedHashMap<>(); + + /** Incremented on any change; used for cache invalidation. */ + private int revision = 0; + + // ═══════════════════════════════════════════════════════════════════ + // LOAD / SAVE + // ═══════════════════════════════════════════════════════════════════ + + /** + * Load all chained abilities from disk. + */ + public void load() { + chainedAbilities.clear(); + + File dir = getDir(); + File[] files = dir.exists() ? dir.listFiles() : null; + if (files != null) { + for (File file : files) { + if (!file.isFile() || !file.getName().endsWith(".json")) continue; + try { + String filename = file.getName(); + String key = filename.substring(0, filename.length() - 5); + + NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(nbt); + + // Use filename as authoritative key + chain.setName(key); + chainedAbilities.put(key, chain); + } catch (Exception e) { + LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e); + } + } + } + + revision++; + LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities"); + } + + /** + * Save a chained ability to disk. Handles renames and uniqueness. + */ + public boolean save(ChainedAbility chain) { + if (chain == null) return false; + + String name = chain.getName(); + if (name == null || name.isEmpty()) return false; + + File dir = getDir(); + File fileNew = new File(dir, name + ".json_new"); + File fileCurrent = new File(dir, name + ".json"); + + try { + NBTTagCompound nbt = chain.writeNBT(); + NBTJsonUtil.SaveFile(fileNew, nbt); + + if (fileCurrent.exists()) { + fileCurrent.delete(); + } + fileNew.renameTo(fileCurrent); + if (fileNew.exists()) { + fileNew.delete(); + } + + chainedAbilities.put(name, chain); + revision++; + LogWriter.info("Saved chained ability: " + name); + SyncController.syncAllChainedAbilities(); + return true; + } catch (Exception e) { + LogWriter.error("Error saving chained ability: " + name, e); + return false; + } + } + + /** + * Delete a chained ability by name. + */ + public boolean delete(String name) { + if (name == null || name.isEmpty()) return false; + + ChainedAbility removed = chainedAbilities.remove(name); + if (removed == null) return false; + + File dir = getDir(); + File file = new File(dir, name + ".json"); + if (file.exists()) { + file.delete(); + } + + revision++; + LogWriter.info("Deleted chained ability: " + name); + SyncController.syncAllChainedAbilities(); + return true; + } + + // ═══════════════════════════════════════════════════════════════════ + // RESOLUTION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Resolve a chained ability by name, returning a deep copy. + * Tries exact match first, then case-insensitive. + */ + public ChainedAbility resolve(String name) { + if (name == null || name.isEmpty()) return null; + + // Exact match + ChainedAbility chain = chainedAbilities.get(name); + if (chain != null) return chain.deepCopy(); + + // Case-insensitive match + for (Map.Entry entry : chainedAbilities.entrySet()) { + if (entry.getKey().equalsIgnoreCase(name)) { + return entry.getValue().deepCopy(); + } + } + + return null; + } + + /** + * Check if a name can be resolved without creating a deep copy. + */ + public boolean canResolve(String name) { + if (name == null || name.isEmpty()) return false; + + if (chainedAbilities.containsKey(name)) return true; + + for (String key : chainedAbilities.keySet()) { + if (key.equalsIgnoreCase(name)) return true; + } + + return false; + } + + // ═══════════════════════════════════════════════════════════════════ + // QUERIES + // ═══════════════════════════════════════════════════════════════════ + + public ChainedAbility get(String name) { + return chainedAbilities.get(name); + } + + public Set getNames() { + return new LinkedHashSet<>(chainedAbilities.keySet()); + } + + public Map getChainedAbilities() { + return chainedAbilities; + } + + public boolean hasName(String name) { + return chainedAbilities.containsKey(name); + } + + public int getRevision() { + return revision; + } + + // ═══════════════════════════════════════════════════════════════════ + // CLIENT SYNC + // ═══════════════════════════════════════════════════════════════════ + + /** + * Set chained abilities from sync payload (client-side). + */ + public void setChainedAbilities(Map synced) { + chainedAbilities.clear(); + chainedAbilities.putAll(synced); + revision++; + } + + // ═══════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════ + + private File getDir() { + File dir = new File(CustomNpcs.getWorldSaveDirectory(), "chained_abilities"); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java new file mode 100644 index 000000000..ace05d69d --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java @@ -0,0 +1,63 @@ +package kamkeel.npcs.controllers.data.ability; + +import net.minecraft.nbt.NBTTagCompound; + +/** + * A single entry in a {@link ChainedAbility}, representing one ability + * to execute in sequence along with its delay configuration. + */ +public class ChainedAbilityEntry { + + /** Reference key (built-in name or custom ability name). */ + private String abilityReference = ""; + + /** Delay in ticks BEFORE this ability starts (after previous entry completes). */ + private int delayTicks = 0; + + public ChainedAbilityEntry() {} + + public ChainedAbilityEntry(String abilityReference, int delayTicks) { + this.abilityReference = abilityReference != null ? abilityReference : ""; + this.delayTicks = Math.max(0, delayTicks); + } + + // ═══════════════════════════════════════════════════════════════════ + // GETTERS / SETTERS + // ═══════════════════════════════════════════════════════════════════ + + public String getAbilityReference() { + return abilityReference; + } + + public void setAbilityReference(String abilityReference) { + this.abilityReference = abilityReference != null ? abilityReference : ""; + } + + public int getDelayTicks() { + return delayTicks; + } + + public void setDelayTicks(int delayTicks) { + this.delayTicks = Math.max(0, delayTicks); + } + + // ═══════════════════════════════════════════════════════════════════ + // NBT + // ═══════════════════════════════════════════════════════════════════ + + public NBTTagCompound writeNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setString("Reference", abilityReference); + nbt.setInteger("Delay", delayTicks); + return nbt; + } + + public static ChainedAbilityEntry fromNBT(NBTTagCompound nbt) { + if (nbt == null) return null; + + ChainedAbilityEntry entry = new ChainedAbilityEntry(); + entry.abilityReference = nbt.getString("Reference"); + entry.delayTicks = Math.max(0, nbt.getInteger("Delay")); + return entry; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java new file mode 100644 index 000000000..a1f4ac777 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java @@ -0,0 +1,50 @@ +package kamkeel.npcs.controllers.data.ability; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; + +import java.util.List; + +/** + * Common interface for all combat actions that can be assigned to NPCs or players. + * Implemented by {@link Ability} (individual ability) and {@link ChainedAbility} (sequence). + *

+ * This enables unified storage via {@link AbilityAction}, unified weighted random selection, + * and unified eligibility checking without instanceof or separate codepaths. + */ +public interface IAbilityAction { + + String getName(); + + boolean isEnabled(); + + void setEnabled(boolean enabled); + + int getWeight(); + + int getCooldownTicks(); + + float getMinRange(); + + float getMaxRange(); + + UserType getAllowedBy(); + + List getConditions(); + + boolean checkConditions(EntityLivingBase caster, EntityLivingBase target); + + boolean checkConditionsForPlayer(EntityLivingBase caster); + + /** + * Whether this action is a chained ability (sequence of abilities) vs an individual ability. + */ + boolean isChain(); + + /** + * Create a deep copy of this action. + */ + IAbilityAction deepCopyAction(); + + NBTTagCompound writeNBT(); +} diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumSyncType.java b/src/main/java/kamkeel/npcs/network/enums/EnumSyncType.java index 1ac0ec828..ee6cf352f 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumSyncType.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumSyncType.java @@ -13,5 +13,6 @@ public enum EnumSyncType { CARPENTRY_RECIPES, ANVIL_RECIPES, CUSTOM_EFFECTS, - CUSTOM_ABILITY + CUSTOM_ABILITY, + CHAINED_ABILITY } diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 774e9661e..11ff57104 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -3,6 +3,8 @@ package noppes.npcs; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; @@ -45,6 +47,14 @@ public abstract class AbstractDataAbilities { protected double lockedPosY = 0; protected double lockedPosZ = 0; + /** Chained ability execution state */ + protected ChainedAbility currentChain; + protected int chainEntryIndex = -1; + protected int chainDelayRemaining = -1; + + /** Set when interrupt already rolled cooldown (prevents double cooldown on NPC dazed completion). */ + protected boolean interruptCooldownRolled = false; + // ═══════════════════════════════════════════════════════════════════ // ABSTRACT METHODS - Subclasses must implement // ═══════════════════════════════════════════════════════════════════ @@ -108,6 +118,12 @@ public abstract class AbstractDataAbilities { /** Additional completion logic (NPC clears lastTarget, Player clears key/target and syncs). */ protected abstract void onAbilityComplete(); + /** + * Roll cooldown after a chained ability completes. + * NPC: random(min, max) + chain.cooldownTicks. Player: chain.cooldownTicks only. + */ + protected abstract void rollChainCooldown(ChainedAbility chain); + // -- Hooks (optional overrides) -- @@ -120,6 +136,9 @@ public abstract class AbstractDataAbilities { /** Additional lock releases in BURST_DELAY. NPC releases hitScan. */ protected void onBurstDelayReleaseLocks() {} + /** Hook for retargeting during chain execution when target dies. NPC overrides, Player returns null. */ + protected EntityLivingBase retargetForChain() { return null; } + // ═══════════════════════════════════════════════════════════════════ // SHARED ANIMATION METHODS // ═══════════════════════════════════════════════════════════════════ @@ -168,6 +187,16 @@ public abstract class AbstractDataAbilities { return currentAbility; } + /** Check if a chained ability is currently executing. */ + public boolean isExecutingChain() { + return currentChain != null; + } + + /** Get the currently executing chained ability. */ + public ChainedAbility getCurrentChain() { + return currentChain; + } + // ═══════════════════════════════════════════════════════════════════ // COOLDOWN MANAGEMENT // ═══════════════════════════════════════════════════════════════════ @@ -242,6 +271,24 @@ public abstract class AbstractDataAbilities { * Subclass-specific behavior is injected via abstract methods and hooks. */ protected void tickCurrentAbility() { + // Handle chain delay between entries + if (chainDelayRemaining > 0) { + chainDelayRemaining--; + if (chainDelayRemaining <= 0) { + EntityLivingBase chainTarget = getTarget(); + // Check if target is dead and retarget + if (currentChain != null && chainTarget != null && chainTarget.isDead) { + chainTarget = retargetForChain(); + if (chainTarget == null) { + completeChain(); + return; + } + } + startChainEntry(chainTarget); + } + return; + } + EntityLivingBase entity = getEntity(); EntityLivingBase target = getTarget(); AbilityPhase oldPhase = currentAbility.getPhase(); @@ -381,6 +428,7 @@ public abstract class AbstractDataAbilities { /** * Handle ability completion. Called when ability phase becomes IDLE. + * If a chain is active, advances to the next entry instead of completing. */ protected void handleAbilityCompletion(EntityLivingBase target) { if (currentAbility == null) return; @@ -400,10 +448,44 @@ public abstract class AbstractDataAbilities { // Release locks releaseRotationControl(); releaseLockedPosition(); - - // Subclass-specific completion (cooldown, cleanup, sync) - rollCooldown(currentAbility); stopAbilityAnimation(); + + // Chain mode: advance to next entry instead of completing + if (currentChain != null) { + chainEntryIndex++; + if (chainEntryIndex < currentChain.getEntries().size()) { + ChainedAbilityEntry nextEntry = currentChain.getEntries().get(chainEntryIndex); + + // Check if target died and retarget + if (target != null && target.isDead) { + target = retargetForChain(); + if (target == null) { + completeChain(); + return; + } + } + + int delay = nextEntry.getDelayTicks(); + if (delay > 0) { + chainDelayRemaining = delay; + currentAbility = null; + } else { + startChainEntry(target); + } + return; + } + // All entries complete + completeChain(); + return; + } + + // Normal (non-chain) completion + if (interruptCooldownRolled) { + // Cooldown was already rolled during interrupt (e.g., chain interrupted, NPC ticked through DAZED) + interruptCooldownRolled = false; + } else { + rollCooldown(currentAbility); + } onAbilityComplete(); } @@ -465,6 +547,15 @@ public abstract class AbstractDataAbilities { releaseRotationControl(); releaseLockedPosition(); + // If chain was active, roll chain cooldown and clear chain state + if (currentChain != null) { + rollChainCooldown(currentChain); + interruptCooldownRolled = true; + currentChain = null; + chainEntryIndex = -1; + chainDelayRemaining = -1; + } + // Let subclass handle cleanup onInterruptComplete(); } @@ -475,4 +566,112 @@ public abstract class AbstractDataAbilities { * NPC: keeps currentAbility (ticks through DAZED). Player: clears immediately. */ protected void onInterruptComplete() {} + + // ═══════════════════════════════════════════════════════════════════ + // CHAINED ABILITY EXECUTION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Start executing a chained ability. Sets up chain state and starts the first entry. + * + * @param chain The chained ability to execute (should be a deep copy) + * @param target The initial target + * @return true if the chain was started + */ + protected boolean startChain(ChainedAbility chain, EntityLivingBase target) { + if (chain == null || chain.getEntries().isEmpty()) return false; + + currentChain = chain; + chainEntryIndex = 0; + chainDelayRemaining = -1; + + // Check if first entry has a delay + ChainedAbilityEntry firstEntry = chain.getEntries().get(0); + int delay = firstEntry.getDelayTicks(); + if (delay > 0) { + chainDelayRemaining = delay; + return true; + } + + return startChainEntry(target); + } + + /** + * Start the current chain entry's ability. + */ + protected boolean startChainEntry(EntityLivingBase target) { + if (currentChain == null || chainEntryIndex < 0 || chainEntryIndex >= currentChain.getEntries().size()) { + completeChain(); + return false; + } + + ChainedAbilityEntry entry = currentChain.getEntries().get(chainEntryIndex); + Ability ability = AbilityController.Instance.resolveAbility(entry.getAbilityReference()); + if (ability == null) { + // Broken reference - complete chain + completeChain(); + return false; + } + + // Validate the resolved ability's UserType matches the chain's allowed context. + // The chain's own UserType was already checked at selection time, but individual + // abilities may have stricter restrictions (e.g., PLAYER_ONLY in an NPC chain). + if (!ability.getAllowedBy().allowsNpc() && getEntity() instanceof noppes.npcs.entity.EntityNPCInterface) { + // Skip this entry - ability doesn't allow NPCs + completeChain(); + return false; + } + if (!ability.getAllowedBy().allowsPlayer() && getEntity() instanceof net.minecraft.entity.player.EntityPlayer) { + // Skip this entry - ability doesn't allow players + completeChain(); + return false; + } + + // If windUpAll=false, force windup to 0 (skip individual windups) + if (!currentChain.isWindUpAll()) { + ability.setWindUpTicks(0); + } + + // Start the ability (skip its own conditions - chain conditions were already checked) + currentAbility = ability; + ability.start(target); + + if (ability.getPhase() == AbilityPhase.ACTIVE) { + // Windup was 0 - capture locks for immediate active phase, then execute + if (ability.isRotationLockedDuringActive()) { + captureLockedRotation(); + } + if (ability.isMovementLockedDuringActive() && !ability.hasAbilityMovement()) { + captureLockedPosition(); + } + executeImmediate(ability, target); + } else { + // Normal windup flow + if (ability.isRotationLockedDuringWindup()) { + captureLockedRotation(); + } + if (ability.isMovementLockedDuringWindup() && !ability.hasAbilityMovement()) { + captureLockedPosition(); + } + spawnTelegraph(ability, target); + playAbilitySound(ability.getWindUpSound()); + playAbilityAnimation(ability.getWindUpAnimation()); + } + + return true; + } + + /** + * Complete the chain execution. Rolls chain cooldown and clears all chain state. + */ + protected void completeChain() { + if (currentChain != null) { + rollChainCooldown(currentChain); + } + currentChain = null; + chainEntryIndex = -1; + chainDelayRemaining = -1; + currentAbility = null; + onAbilityComplete(); + } } diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 892c3648f..e1dcf489b 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -26,6 +26,7 @@ import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.TelegraphController; import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; import kamkeel.npcs.controllers.data.profile.CNPCData; import kamkeel.npcs.developer.Developer; import kamkeel.npcs.network.PacketHandler; @@ -355,6 +356,7 @@ public class CustomNpcs { new SpawnController(); new LinkedNpcController(); AbilityController.Instance.load(); + ChainedAbilityController.Instance.load(); TelegraphController.init(); LinkedItemController.getInstance().load(); diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index cb1e5104d..52a086cce 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -3,7 +3,10 @@ package noppes.npcs; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.AbilitySlot; +import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; +import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; @@ -35,9 +38,9 @@ public class DataAbilities extends AbstractDataAbilities { // ═══════════════════════════════════════════════════════════════════ /** - * List of ability slots this NPC can use (inline or reference). + * Unified list of action slots (abilities and chained abilities). */ - private List abilitySlots = new ArrayList<>(); + private List actionSlots = new ArrayList<>(); /** * Whether the ability system is enabled for this NPC @@ -207,6 +210,20 @@ public class DataAbilities extends AbstractDataAbilities { cooldownEndTime += ability.getCooldownTicks(); } + @Override + protected void rollChainCooldown(ChainedAbility chain) { + int baseCooldown = minCooldown; + if (maxCooldown > minCooldown) { + baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1); + } + cooldownEndTime = npc.worldObj.getTotalWorldTime() + baseCooldown + chain.getCooldownTicks(); + } + + @Override + protected EntityLivingBase retargetForChain() { + return npc.getAttackTarget(); + } + @Override protected void onAbilityComplete() { currentAbility = null; @@ -288,17 +305,21 @@ public class DataAbilities extends AbstractDataAbilities { if (rotationLocked || hitScanActive) releaseRotationControl(); if (positionLocked) releaseLockedPosition(); } + // Clear chain state + currentChain = null; + chainEntryIndex = -1; + chainDelayRemaining = -1; return; } // Safety: release orphaned locks if no ability is actively executing - if (currentAbility == null || !currentAbility.isExecuting()) { + if ((currentAbility == null || !currentAbility.isExecuting()) && chainDelayRemaining <= 0) { if (rotationLocked || hitScanActive) releaseRotationControl(); if (positionLocked) releaseLockedPosition(); } - // Tick current ability if executing (shared logic from base class) - if (currentAbility != null && currentAbility.isExecuting()) { + // Tick current ability if executing, or tick chain delay between entries + if (chainDelayRemaining > 0 || (currentAbility != null && currentAbility.isExecuting())) { tickCurrentAbility(); } } @@ -329,7 +350,7 @@ public class DataAbilities extends AbstractDataAbilities { // ═══════════════════════════════════════════════════════════════════ /** - * Try to select and start an ability for the given target. + * Try to select and start an ability or chained ability for the given target. * Called by CombatHandler when NPC is in combat and ready for an ability. * * @param target The combat target @@ -340,24 +361,61 @@ public class DataAbilities extends AbstractDataAbilities { return false; } - Ability selected = selectAbility(target); - if (selected == null) { + // Build eligible pool from unified action slots + List eligible = new ArrayList<>(); + List weights = new ArrayList<>(); + int totalWeight = 0; + + for (AbilityAction slot : actionSlots) { + IAbilityAction action = slot.getAction(); + if (action != null && isActionEligible(action, target)) { + eligible.add(action); + weights.add(action.getWeight()); + totalWeight += action.getWeight(); + } + } + + if (eligible.isEmpty() || totalWeight <= 0) { return false; } - return startAbility(selected, target); + // Weighted random selection + int roll = random.nextInt(totalWeight); + int cumulative = 0; + IAbilityAction selected = null; + for (int i = 0; i < eligible.size(); i++) { + cumulative += weights.get(i); + if (roll < cumulative) { + selected = eligible.get(i); + break; + } + } + if (selected == null) { + selected = eligible.get(eligible.size() - 1); + } + + // Dispatch + lastTarget = target; + if (selected.isChain()) { + return startChain((ChainedAbility) selected, target); + } else { + return startAbility((Ability) selected, target); + } } /** * Check if an ability can be selected right now. */ public boolean canSelectAbility() { - if (!enabled || abilitySlots.isEmpty()) { + if (!enabled || actionSlots.isEmpty()) { return false; } if (currentAbility != null && currentAbility.isExecuting()) { return false; } + if (isExecutingChain()) { + return false; + } // Check if still on cooldown if (npc.worldObj.getTotalWorldTime() < cooldownEndTime) { return false; @@ -366,65 +424,36 @@ public class DataAbilities extends AbstractDataAbilities { } /** - * Select an ability using weighted random from eligible abilities. + * Check if an action (ability or chain) is eligible for use. */ - private Ability selectAbility(EntityLivingBase target) { - List eligible = new ArrayList<>(); - int totalWeight = 0; - - for (Ability ability : getAbilities()) { - if (isAbilityEligible(ability, target)) { - eligible.add(ability); - totalWeight += ability.getWeight(); - } + private boolean isActionEligible(IAbilityAction action, EntityLivingBase target) { + if (!action.getAllowedBy().allowsNpc()) { + return false; } - - if (eligible.isEmpty() || totalWeight <= 0) { - return null; - } - - // Weighted random selection - int roll = random.nextInt(totalWeight); - int cumulative = 0; - for (Ability ability : eligible) { - cumulative += ability.getWeight(); - if (roll < cumulative) { - return ability; - } - } - - return eligible.get(eligible.size() - 1); - } - - /** - * Check if a specific ability is eligible for use. - */ - private boolean isAbilityEligible(Ability ability, EntityLivingBase target) { - // Check UserType allows NPCs - if (!ability.getAllowedBy().allowsNpc()) { + if (!action.isEnabled()) { return false; } - // Check enabled - if (!ability.isEnabled()) { + // Ability-specific: check if already executing + if (!action.isChain() && ((Ability) action).isExecuting()) { return false; } - // Check if already executing - if (ability.isExecuting()) { + // Chain-specific: must have entries + if (action.isChain() && ((ChainedAbility) action).getEntries().isEmpty()) { return false; } - // Check range + // Range check if (target != null) { float distance = npc.getDistanceToEntity(target); - if (distance < ability.getMinRange() || distance > ability.getMaxRange()) { + if (distance < action.getMinRange() || distance > action.getMaxRange()) { return false; } } - // Check conditions - if (!ability.checkConditions(npc, target)) { + // Conditions + if (!action.checkConditions(npc, target)) { return false; } @@ -496,7 +525,9 @@ public class DataAbilities extends AbstractDataAbilities { return false; } - currentAbility.onDamageTaken(npc, (EntityLivingBase) source.getEntity(), source, amount); + net.minecraft.entity.Entity sourceEntity = source.getEntity(); + EntityLivingBase attacker = sourceEntity instanceof EntityLivingBase ? (EntityLivingBase) sourceEntity : null; + currentAbility.onDamageTaken(npc, attacker, source, amount); if (currentAbility.canInterrupt(source)) { interruptCurrentAbility(source, amount); @@ -568,6 +599,10 @@ public class DataAbilities extends AbstractDataAbilities { currentAbility = null; lastTarget = null; } + // Also clear chain state + currentChain = null; + chainEntryIndex = -1; + chainDelayRemaining = -1; } /** @@ -687,15 +722,20 @@ public class DataAbilities extends AbstractDataAbilities { } // ═══════════════════════════════════════════════════════════════════ - // ABILITY LIST MANAGEMENT + // ACTION SLOT MANAGEMENT // ═══════════════════════════════════════════════════════════════════ + /** Get the unified action slot list. */ + public List getAbilityActions() { + return actionSlots; + } + /** - * Get resolved abilities from all slots, filtering out broken references. + * Get resolved abilities from all ability slots, filtering out broken references and chains. */ public List getAbilities() { List resolved = new ArrayList<>(); - for (AbilitySlot slot : abilitySlots) { + for (AbilityAction slot : actionSlots) { Ability a = slot.getAbility(); if (a != null) { resolved.add(a); @@ -704,39 +744,54 @@ public class DataAbilities extends AbstractDataAbilities { return resolved; } - /** Get the raw slot list (for GUI access). */ - public List getAbilitySlots() { - return abilitySlots; - } - /** Add an inline ability. */ public void addAbility(Ability ability) { - abilitySlots.add(AbilitySlot.inline(ability)); + actionSlots.add(AbilityAction.inline(ability)); } /** Add a reference ability by key (built-in name or custom ability name). */ public void addAbilityReference(String key) { - abilitySlots.add(AbilitySlot.reference(key)); + actionSlots.add(AbilityAction.abilityReference(key)); } - public void removeAbility(int index) { - if (index >= 0 && index < abilitySlots.size()) { - abilitySlots.remove(index); + /** Add a chained ability reference by name. */ + public void addChainReference(String name) { + if (name != null && !name.isEmpty()) { + for (AbilityAction slot : actionSlots) { + if (slot.isChainReference() && name.equals(slot.getReferenceId())) { + return; // Prevent duplicates + } + } + actionSlots.add(AbilityAction.chainReference(name)); } } + /** Remove an action slot by index. */ + public void removeAction(int index) { + if (index >= 0 && index < actionSlots.size()) { + actionSlots.remove(index); + } + } + + public void removeAbility(int index) { + removeAction(index); + } + public void removeAbility(String id) { - abilitySlots.removeIf(slot -> { - if (slot.isReference()) { + actionSlots.removeIf(slot -> { + if (slot.isAbilityReference()) { return slot.getReferenceId().equals(id); } + if (slot.isChainReference()) { + return false; // Don't remove chain references via removeAbility + } Ability a = slot.getAbility(); return a != null && a.getId().equals(id); }); } public Ability getAbility(String id) { - for (AbilitySlot slot : abilitySlots) { + for (AbilityAction slot : actionSlots) { Ability a = slot.getAbility(); if (a != null && a.getId().equals(id)) { return a; @@ -747,22 +802,22 @@ public class DataAbilities extends AbstractDataAbilities { /** Check if a slot at a given index is a reference. */ public boolean isSlotReference(int index) { - if (index < 0 || index >= abilitySlots.size()) return false; - return abilitySlots.get(index).isReference(); + if (index < 0 || index >= actionSlots.size()) return false; + return actionSlots.get(index).isReference(); } - /** Convert a reference slot to inline. Returns false if resolution fails. */ + /** Convert a reference slot to inline. Returns false if resolution fails or is a chain. */ public boolean convertToInline(int index) { - if (index < 0 || index >= abilitySlots.size()) return false; - return abilitySlots.get(index).convertToInline(); + if (index < 0 || index >= actionSlots.size()) return false; + return actionSlots.get(index).convertToInline(); } public void clearAbilities() { - abilitySlots.clear(); + actionSlots.clear(); } public boolean isEmpty() { - return abilitySlots.isEmpty(); + return actionSlots.isEmpty(); } // ═══════════════════════════════════════════════════════════════════ @@ -977,11 +1032,11 @@ public class DataAbilities extends AbstractDataAbilities { compound.setInteger("AbilityMinCooldown", minCooldown); compound.setInteger("AbilityMaxCooldown", maxCooldown); - NBTTagList abilityList = new NBTTagList(); - for (AbilitySlot slot : abilitySlots) { - abilityList.appendTag(slot.writeNBT()); + NBTTagList actionList = new NBTTagList(); + for (AbilityAction slot : actionSlots) { + actionList.appendTag(slot.writeNBT()); } - compound.setTag("Abilities", abilityList); + compound.setTag("AbilityActions", actionList); return compound; } @@ -991,13 +1046,36 @@ public class DataAbilities extends AbstractDataAbilities { minCooldown = compound.getInteger("AbilityMinCooldown"); maxCooldown = compound.getInteger("AbilityMaxCooldown"); - abilitySlots.clear(); - NBTTagList abilityList = compound.getTagList("Abilities", 10); - for (int i = 0; i < abilityList.tagCount(); i++) { - NBTTagCompound abilityNBT = abilityList.getCompoundTagAt(i); - AbilitySlot slot = AbilitySlot.fromNBT(abilityNBT); - if (slot != null) { - abilitySlots.add(slot); + actionSlots.clear(); + + if (compound.hasKey("AbilityActions")) { + // New unified format + NBTTagList actionList = compound.getTagList("AbilityActions", 10); + for (int i = 0; i < actionList.tagCount(); i++) { + AbilityAction slot = AbilityAction.fromNBT(actionList.getCompoundTagAt(i)); + if (slot != null) { + actionSlots.add(slot); + } + } + } else { + // Legacy migration: read old separate lists + if (compound.hasKey("Abilities")) { + NBTTagList abilityList = compound.getTagList("Abilities", 10); + for (int i = 0; i < abilityList.tagCount(); i++) { + AbilityAction slot = AbilityAction.fromNBT(abilityList.getCompoundTagAt(i)); + if (slot != null) { + actionSlots.add(slot); + } + } + } + if (compound.hasKey("ChainedAbilities")) { + NBTTagList chainList = compound.getTagList("ChainedAbilities", 8); + for (int i = 0; i < chainList.tagCount(); i++) { + String ref = chainList.getStringTagAt(i); + if (ref != null && !ref.isEmpty()) { + actionSlots.add(AbilityAction.chainReference(ref)); + } + } } } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index 2e89d3d49..4afa9dec4 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -2,7 +2,7 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; -import kamkeel.npcs.controllers.data.ability.AbilitySlot; +import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket; @@ -48,8 +48,8 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Display name to typeId mapping for the available types scroll private final HashMap displayNameToTypeId = new HashMap<>(); - // NPC's current ability slots - private final List npcSlots = new ArrayList<>(); + // NPC's current action slots + private final List npcSlots = new ArrayList<>(); // Settings private boolean abilitiesEnabled = false; @@ -163,9 +163,11 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // On/Off toggle for selected ability (under Load button) if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { Ability selectedAbility = npcSlots.get(selectedAbilityIndex).getAbility(); - GuiNpcButton toggleBtn = new GuiNpcButton(77, centerX, y + 112, 40, 20, - new String[]{"gui.off", "gui.on"}, selectedAbility.isEnabled() ? 1 : 0); - addButton(toggleBtn); + if (selectedAbility != null) { + GuiNpcButton toggleBtn = new GuiNpcButton(77, centerX, y + 112, 40, 20, + new String[]{"gui.off", "gui.on"}, selectedAbility.isEnabled() ? 1 : 0); + addButton(toggleBtn); + } } // Check if selected ability is built-in (non-editable) @@ -220,7 +222,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC variants.get(0).apply(newAbility); } newAbility.setId(UUID.randomUUID().toString()); - npcSlots.add(AbilitySlot.inline(newAbility)); + npcSlots.add(AbilityAction.inline(newAbility)); selectedAbilityIndex = npcSlots.size() - 1; updateNpcAbilitiesList(); selectAbilityByIndex(selectedAbilityIndex); @@ -247,7 +249,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Edit ability if (id == 72) { if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilitySlot slot = npcSlots.get(selectedAbilityIndex); + AbilityAction slot = npcSlots.get(selectedAbilityIndex); if (slot.isReference()) { // Reference slot - ask clone-and-modify vs modify-parent setSubGui(new SubGuiAbilityEditMode()); @@ -265,7 +267,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Move up (carrot left = earlier in list) if (id == 73) { if (selectedAbilityIndex > 0) { - AbilitySlot temp = npcSlots.get(selectedAbilityIndex); + AbilityAction temp = npcSlots.get(selectedAbilityIndex); npcSlots.set(selectedAbilityIndex, npcSlots.get(selectedAbilityIndex - 1)); npcSlots.set(selectedAbilityIndex - 1, temp); selectedAbilityIndex--; @@ -280,7 +282,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Move down (carrot right = later in list) if (id == 74) { if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size() - 1) { - AbilitySlot temp = npcSlots.get(selectedAbilityIndex); + AbilityAction temp = npcSlots.get(selectedAbilityIndex); npcSlots.set(selectedAbilityIndex, npcSlots.get(selectedAbilityIndex + 1)); npcSlots.set(selectedAbilityIndex + 1, temp); selectedAbilityIndex++; @@ -317,7 +319,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Toggle selected ability on/off if (id == 77) { if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilitySlot slot = npcSlots.get(selectedAbilityIndex); + AbilityAction slot = npcSlots.get(selectedAbilityIndex); Ability ability = slot.getAbility(); if (ability != null) { boolean enabled = ((GuiNpcButton) guibutton).getValue() == 1; @@ -397,7 +399,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Only process NPC ability data, not saved ability preset data // SubGuiAbilityLoad also implements IGuiData and may receive responses // that get forwarded here - ignore those by checking for expected keys - if (!compound.hasKey("Abilities")) { + if (!compound.hasKey("AbilityActions") && !compound.hasKey("Abilities")) { return; } @@ -411,10 +413,12 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC } npcSlots.clear(); - NBTTagList abilityList = compound.getTagList("Abilities", 10); - for (int i = 0; i < abilityList.tagCount(); i++) { - NBTTagCompound abilityNBT = abilityList.getCompoundTagAt(i); - AbilitySlot slot = AbilitySlot.fromNBT(abilityNBT); + // Support both new unified format and legacy format + String tagName = compound.hasKey("AbilityActions") ? "AbilityActions" : "Abilities"; + NBTTagList actionList = compound.getTagList(tagName, 10); + for (int i = 0; i < actionList.tagCount(); i++) { + NBTTagCompound slotNBT = actionList.getCompoundTagAt(i); + AbilityAction slot = AbilityAction.fromNBT(slotNBT); if (slot != null) { npcSlots.add(slot); } @@ -526,7 +530,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC * Broken: "[●] 1. > [Missing: id...]" in red */ private String getAbilityListEntry(int index) { - AbilitySlot slot = npcSlots.get(index); + AbilityAction slot = npcSlots.get(index); Ability ability = slot.getAbility(); if (ability == null) { @@ -579,11 +583,11 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC compound.setInteger("AbilityMinCooldown", minCooldown); compound.setInteger("AbilityMaxCooldown", maxCooldown); - NBTTagList abilityList = new NBTTagList(); - for (AbilitySlot slot : npcSlots) { - abilityList.appendTag(slot.writeNBT()); + NBTTagList actionList = new NBTTagList(); + for (AbilityAction slot : npcSlots) { + actionList.appendTag(slot.writeNBT()); } - compound.setTag("Abilities", abilityList); + compound.setTag("AbilityActions", actionList); PacketClient.sendClient(new AbilitiesNpcSavePacket(compound)); } @@ -595,7 +599,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC public void loadAbility(Ability loadedAbility) { if (loadedAbility != null) { loadedAbility.setId(UUID.randomUUID().toString()); - npcSlots.add(AbilitySlot.inline(loadedAbility)); + npcSlots.add(AbilityAction.inline(loadedAbility)); selectedAbilityIndex = npcSlots.size() - 1; updateNpcAbilitiesList(); selectAbilityByIndex(selectedAbilityIndex); @@ -610,7 +614,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC */ public void loadAbilityReference(String referenceId) { if (referenceId != null && !referenceId.isEmpty()) { - npcSlots.add(AbilitySlot.reference(referenceId)); + npcSlots.add(AbilityAction.abilityReference(referenceId)); selectedAbilityIndex = npcSlots.size() - 1; updateNpcAbilitiesList(); selectAbilityByIndex(selectedAbilityIndex); @@ -652,7 +656,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { if (scroll == npcAbilitiesScroll && selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilitySlot slot = npcSlots.get(selectedAbilityIndex); + AbilityAction slot = npcSlots.get(selectedAbilityIndex); Ability ability = slot.getAbility(); if (ability != null && ability.isBuiltIn()) return; if (slot.isReference()) { @@ -697,7 +701,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC if (newAbility != null) { variantGui.getVariants().get(idx).apply(newAbility); newAbility.setId(UUID.randomUUID().toString()); - npcSlots.add(AbilitySlot.inline(newAbility)); + npcSlots.add(AbilityAction.inline(newAbility)); selectedAbilityIndex = npcSlots.size() - 1; updateNpcAbilitiesList(); selectAbilityByIndex(selectedAbilityIndex); @@ -711,7 +715,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC if (mode < 0) return; // cancelled if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilitySlot slot = npcSlots.get(selectedAbilityIndex); + AbilityAction slot = npcSlots.get(selectedAbilityIndex); if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { // Convert reference to inline, then edit if (slot.convertToInline()) { @@ -743,13 +747,13 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC */ public void onAbilitySaved(Ability ability) { if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilitySlot slot = npcSlots.get(selectedAbilityIndex); + AbilityAction slot = npcSlots.get(selectedAbilityIndex); if (slot.isReference()) { // For reference slots, save back to controller PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT())); } else { // For inline slots, replace with the updated ability - npcSlots.set(selectedAbilityIndex, AbilitySlot.inline(ability)); + npcSlots.set(selectedAbilityIndex, AbilityAction.inline(ability)); } updateNpcAbilitiesList(); save(); diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index f5ffb7028..cecad2494 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -3,6 +3,9 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; +import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; @@ -229,6 +232,11 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA } } + @Override + protected void rollChainCooldown(ChainedAbility chain) { + cooldownEndTime = getWorldTime() + chain.getCooldownTicks(); + } + @Override protected void onAbilityComplete() { currentAbility = null; @@ -263,7 +271,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA return; } - if (currentAbility != null && currentAbility.isExecuting()) { + if (chainDelayRemaining > 0 || (currentAbility != null && currentAbility.isExecuting())) { tickCurrentAbility(); // Apply rotation and position locks after ability tick @@ -302,11 +310,28 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA return activateAbility(player, key); } + /** Prefix used to identify chained ability keys in the unlocked list. */ + public static final String CHAIN_PREFIX = "chain:"; + /** - * Activate a specific ability by key. + * Resolve an action key to an IAbilityAction. + * Handles "chain:" prefix for chained abilities, otherwise resolves from AbilityController. + */ + private IAbilityAction resolveActionKey(String key) { + if (key.startsWith(CHAIN_PREFIX)) { + if (ChainedAbilityController.Instance == null) return null; + return ChainedAbilityController.Instance.resolve(key.substring(CHAIN_PREFIX.length())); + } + if (AbilityController.Instance == null) return null; + return AbilityController.Instance.resolveAbility(key); + } + + /** + * Activate a specific ability by key. Keys prefixed with "chain:" are + * treated as chained abilities and resolved from {@link ChainedAbilityController}. * * @param player The player - * @param key The ability key (built-in or preset name) + * @param key The ability key (built-in or preset name), or "chain:name" for chains * @return true if the ability was started */ public boolean activateAbility(EntityPlayer player, String key) { @@ -314,20 +339,32 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Can't activate if already executing if (currentAbility != null && currentAbility.isExecuting()) return false; + if (isExecutingChain()) return false; - // Resolve ability from controller - if (AbilityController.Instance == null) return false; - Ability ability = AbilityController.Instance.resolveAbility(key); - if (ability == null) return false; + // Resolve via unified resolver + IAbilityAction action = resolveActionKey(key); + if (action == null) return false; // Check user type allows player - if (!ability.getAllowedBy().allowsPlayer()) return false; - - // Check universal cooldown - if (!ability.isIgnoreCooldown() && isOnCooldown()) return false; + if (!action.getAllowedBy().allowsPlayer()) return false; // Check conditions (skip target-requiring ones) - if (!ability.checkConditionsForPlayer(player)) return false; + if (!action.checkConditionsForPlayer(player)) return false; + + // Dispatch: chain vs ability + if (action.isChain()) { + if (isOnCooldown()) return false; + + currentAbilityKey = key; + currentTarget = null; + return startChain((ChainedAbility) action, null); + } + + // Regular ability activation + Ability ability = (Ability) action; + + // Check universal cooldown (abilities can optionally ignore it) + if (!ability.isIgnoreCooldown() && isOnCooldown()) return false; // Fire extender start hook (e.g., resource cost checks) if (!AbilityController.Instance.fireOnAbilityStart(ability, player, null)) { @@ -777,6 +814,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA /** * Remove any unlocked ability keys that can no longer be resolved. * Called during load to clean up references to deleted abilities. + * Supports both regular ability keys and "chain:" prefixed chained ability keys. */ private void validateUnlockedAbilities() { if (AbilityController.Instance == null) return; @@ -784,7 +822,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA Iterator it = unlockedAbilities.iterator(); while (it.hasNext()) { String key = it.next(); - if (!AbilityController.Instance.canResolveAbility(key)) { + IAbilityAction resolved = resolveActionKey(key); + if (resolved == null) { it.remove(); LogWriter.info("Removed invalid ability reference from player data: " + key); } diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java index e27f6e075..909b8ba17 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java @@ -2,7 +2,7 @@ package noppes.npcs.scripted.wrapper; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; -import kamkeel.npcs.controllers.data.ability.AbilitySlot; +import kamkeel.npcs.controllers.data.ability.AbilityAction; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.DataAbilities; import noppes.npcs.api.ability.IAbility; @@ -81,12 +81,12 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public boolean isAbilityReference(String abilityId) { - List slots = data.getAbilitySlots(); + List slots = data.getAbilityActions(); for (int i = 0; i < slots.size(); i++) { - kamkeel.npcs.controllers.data.ability.AbilitySlot slot = slots.get(i); - if (slot.isReference()) { + AbilityAction slot = slots.get(i); + if (slot.isAbilityReference()) { if (slot.getReferenceId().equals(abilityId)) return true; - } else { + } else if (!slot.isReference()) { Ability a = slot.getAbility(); if (a != null && a.getId().equals(abilityId)) return false; } @@ -96,10 +96,10 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public boolean convertToInline(String abilityId) { - List slots = data.getAbilitySlots(); + List slots = data.getAbilityActions(); for (int i = 0; i < slots.size(); i++) { - kamkeel.npcs.controllers.data.ability.AbilitySlot slot = slots.get(i); - if (slot.isReference() && slot.getReferenceId().equals(abilityId)) { + AbilityAction slot = slots.get(i); + if (slot.isAbilityReference() && slot.getReferenceId().equals(abilityId)) { return data.convertToInline(i); } } From 0512588a7a2fa4a8814002d78c23ff0440a36876 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 04:02:51 -0500 Subject: [PATCH 03/12] Add Chained Abilities --- src/api | 2 +- .../kamkeel/npcs/command/AbilityCommand.java | 163 +++- .../kamkeel/npcs/command/CommandKamkeel.java | 4 + .../{data/ability => }/AbilityController.java | 264 ++++++- .../npcs/controllers/SyncController.java | 12 +- .../controllers/data/ability/Ability.java | 37 +- .../data/ability/AbilityAction.java | 97 ++- .../controllers/data/ability/AbilitySlot.java | 1 + .../data/ability/ChainedAbility.java | 34 +- .../ability/ChainedAbilityController.java | 319 -------- .../data/ability/ChainedAbilityEntry.java | 140 +++- .../npcs/entity/EntityEnergyProjectile.java | 11 +- .../kamkeel/npcs/network/PacketHandler.java | 8 + .../npcs/network/enums/EnumRequestPacket.java | 4 + .../ability/BuiltInAbilityGetPacket.java | 2 +- .../ability/ChainedAbilitiesGetPacket.java | 51 ++ .../ability/ChainedAbilityGetPacket.java | 68 ++ .../ability/ChainedAbilityRemovePacket.java | 69 ++ .../ability/ChainedAbilitySavePacket.java | 79 ++ .../ability/CustomAbilityGetPacket.java | 2 +- .../ability/CustomAbilityRemovePacket.java | 2 +- .../ability/CustomAbilitySavePacket.java | 2 +- src/main/java/kamkeel/npcs/util/Register.java | 5 +- .../noppes/npcs/AbstractDataAbilities.java | 19 +- src/main/java/noppes/npcs/CustomNpcs.java | 6 +- src/main/java/noppes/npcs/DataAbilities.java | 3 +- .../java/noppes/npcs/NoppesUtilServer.java | 13 +- .../noppes/npcs/ScriptPlayerEventHandler.java | 2 +- .../noppes/npcs/client/ClientTickHandler.java | 2 +- .../client/gui/advanced/GuiNPCAbilities.java | 725 +++++++++++++----- .../gui/advanced/SubGuiAbilityEditMode.java | 14 +- .../gui/advanced/SubGuiAbilityLoad.java | 2 +- .../gui/advanced/SubGuiAbilityLoadMode.java | 14 +- .../advanced/SubGuiAbilitySaveConfirm.java | 6 + .../gui/advanced/SubGuiAbilitySelect.java | 162 ++++ .../gui/advanced/SubGuiAbilityTypeSelect.java | 2 +- .../gui/advanced/SubGuiChainSelect.java | 92 +++ .../advanced/SubGuiChainedAbilityConfig.java | 607 +++++++++++++++ .../advanced/SubGuiChainedEntrySource.java | 58 ++ .../gui/advanced/SubGuiLoadTypeChoice.java | 47 ++ .../gui/advanced/SubGuiNpcSlotPicker.java | 113 +++ .../gui/advanced/SubGuiSimpleChoice.java | 23 + .../gui/global/GuiNpcManageAbilities.java | 381 ++++++--- .../util/IChainedAbilityConfigCallback.java | 10 + .../noppes/npcs/constants/EnumScrollData.java | 3 +- .../controllers/data/PlayerAbilityData.java | 11 +- .../java/noppes/npcs/scripted/NpcAPI.java | 4 +- .../scripted/entity/ScriptEnergySweeper.java | 9 + .../scripted/wrapper/ScriptDataAbilities.java | 13 +- .../wrapper/ScriptPlayerAbilityData.java | 20 +- .../assets/customnpcs/lang/en_US.lang | 18 + 51 files changed, 2955 insertions(+), 800 deletions(-) rename src/main/java/kamkeel/npcs/controllers/{data/ability => }/AbilityController.java (76%) delete mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java create mode 100644 src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitiesGetPacket.java create mode 100644 src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java create mode 100644 src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityRemovePacket.java create mode 100644 src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySelect.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainSelect.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedEntrySource.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiLoadTypeChoice.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiNpcSlotPicker.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/SubGuiSimpleChoice.java create mode 100644 src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java create mode 100644 src/main/java/noppes/npcs/scripted/entity/ScriptEnergySweeper.java diff --git a/src/api b/src/api index 010849fe8..28c0ca840 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 010849fe8fb67c0359d7ebdb7bcd5c3c957fe3db +Subproject commit 28c0ca840f3d726c07b47aafa29d97b51a69a0cd diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 3c7a0e728..5c5a69f88 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -1,13 +1,14 @@ package kamkeel.npcs.command; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayerMP; import noppes.npcs.NoppesUtilServer; import noppes.npcs.controllers.PlayerDataController; +import noppes.npcs.controllers.data.PlayerAbilityData; import noppes.npcs.controllers.data.PlayerData; import java.util.ArrayList; @@ -78,7 +79,6 @@ public class AbilityCommand extends CommandKamkeelBase { ) public void reload(ICommandSender sender, String[] args) { AbilityController.Instance.load(); - ChainedAbilityController.Instance.load(); sendResult(sender, "Abilities and chained abilities reloaded from disk."); } @@ -112,7 +112,7 @@ public class AbilityCommand extends CommandKamkeelBase { return; } - String key = args[0]; + String key = joinArgs(args, 0); Ability ability = AbilityController.Instance.resolveAbility(key); if (ability == null) { sendError(sender, "No ability found for: " + key); @@ -165,26 +165,24 @@ public class AbilityCommand extends CommandKamkeelBase { } @SubCommand( - desc = "Unlock an ability for a player", + desc = "Give an ability to a player", usage = " " ) - public void unlock(ICommandSender sender, String[] args) { + public void give(ICommandSender sender, String[] args) { if (args.length < 2) { - sendError(sender, "Usage: /kam ability unlock "); + sendError(sender, "Usage: /kam ability give "); return; } String playerName = args[0]; String abilityKey = joinArgs(args, 1); - // Find player EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName); if (player == null) { sendError(sender, "Player not found: " + playerName); return; } - // Resolve ability Ability ability = AbilityController.Instance.resolveAbility(abilityKey); if (ability == null) { sendError(sender, "Ability not found: " + abilityKey); @@ -194,17 +192,15 @@ public class AbilityCommand extends CommandKamkeelBase { // Use the ability's canonical ID for storage (registry key for built-in, UUID for custom) String canonicalKey = ability.getId(); if (canonicalKey == null || canonicalKey.isEmpty()) { - canonicalKey = abilityKey; // Fallback to user-provided key + canonicalKey = abilityKey; } String displayName = ability.getDisplayName(); - // Check if ability allows players if (!ability.getAllowedBy().allowsPlayer()) { sendError(sender, "Ability '\u00A7b" + displayName + "\u00A7c' is NPC-only and cannot be given to players."); return; } - // Get player data and unlock PlayerData data = PlayerDataController.Instance.getPlayerData(player); if (data.abilityData.hasUnlockedAbility(canonicalKey)) { sendError(sender, "Player already has ability: " + displayName); @@ -212,30 +208,28 @@ public class AbilityCommand extends CommandKamkeelBase { } data.abilityData.unlockAbility(canonicalKey); - sendResult(sender, "Unlocked ability '\u00A7b" + displayName + "\u00A77' for player \u00A7a" + playerName); + sendResult(sender, "Gave ability '\u00A7b" + displayName + "\u00A77' to player \u00A7a" + playerName); } @SubCommand( - desc = "Lock (revoke) an ability from a player", + desc = "Remove an ability from a player", usage = " " ) - public void lock(ICommandSender sender, String[] args) { + public void remove(ICommandSender sender, String[] args) { if (args.length < 2) { - sendError(sender, "Usage: /kam ability lock "); + sendError(sender, "Usage: /kam ability remove "); return; } String playerName = args[0]; String abilityKey = joinArgs(args, 1); - // Find player EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName); if (player == null) { sendError(sender, "Player not found: " + playerName); return; } - // Resolve ability to get canonical key (registry key for built-in, UUID for custom) Ability ability = AbilityController.Instance.resolveAbility(abilityKey); String canonicalKey = abilityKey; String displayName = abilityKey; @@ -244,7 +238,6 @@ public class AbilityCommand extends CommandKamkeelBase { displayName = ability.getDisplayName(); } - // Get player data and lock PlayerData data = PlayerDataController.Instance.getPlayerData(player); if (!data.abilityData.hasUnlockedAbility(canonicalKey)) { sendError(sender, "Player doesn't have ability: " + displayName); @@ -252,7 +245,98 @@ public class AbilityCommand extends CommandKamkeelBase { } data.abilityData.lockAbility(canonicalKey); - sendResult(sender, "Locked ability '\u00A7b" + displayName + "\u00A77' from player \u00A7a" + playerName); + sendResult(sender, "Removed ability '\u00A7b" + displayName + "\u00A77' from player \u00A7a" + playerName); + } + + @SubCommand( + desc = "Give a chained ability to a player", + usage = " " + ) + public void giveChain(ICommandSender sender, String[] args) { + if (args.length < 2) { + sendError(sender, "Usage: /kam ability giveChain "); + return; + } + + String playerName = args[0]; + String chainKey = joinArgs(args, 1); + + EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName); + if (player == null) { + sendError(sender, "Player not found: " + playerName); + return; + } + + ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainKey); + if (chain == null) { + sendError(sender, "Chained ability not found: " + chainKey); + return; + } + + if (!chain.getAllowedBy().allowsPlayer()) { + sendError(sender, "Chained ability '\u00A7b" + chain.getDisplayName() + "\u00A7c' is NPC-only and cannot be given to players."); + return; + } + + String chainId = chain.getId(); + if (chainId == null || chainId.isEmpty()) { + sendError(sender, "Chained ability has no ID: " + chain.getDisplayName()); + return; + } + + // Store by UUID: chain: + String storageKey = PlayerAbilityData.CHAIN_PREFIX + chainId; + PlayerData data = PlayerDataController.Instance.getPlayerData(player); + if (data.abilityData.hasUnlockedAbility(storageKey)) { + sendError(sender, "Player already has chained ability: " + chain.getDisplayName()); + return; + } + + data.abilityData.unlockAbility(storageKey); + sendResult(sender, "Gave chained ability '\u00A7b" + chain.getDisplayName() + "\u00A77' to player \u00A7a" + playerName); + } + + @SubCommand( + desc = "Remove a chained ability from a player", + usage = " " + ) + public void removeChain(ICommandSender sender, String[] args) { + if (args.length < 2) { + sendError(sender, "Usage: /kam ability removeChain "); + return; + } + + String playerName = args[0]; + String chainKey = joinArgs(args, 1); + + EntityPlayerMP player = (EntityPlayerMP) NoppesUtilServer.getPlayerByName(playerName); + if (player == null) { + sendError(sender, "Player not found: " + playerName); + return; + } + + ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainKey); + if (chain == null) { + sendError(sender, "Chained ability not found: " + chainKey); + return; + } + + String chainId = chain.getId(); + if (chainId == null || chainId.isEmpty()) { + sendError(sender, "Chained ability has no ID: " + chain.getDisplayName()); + return; + } + + // Storage uses UUID: chain: + String storageKey = PlayerAbilityData.CHAIN_PREFIX + chainId; + PlayerData data = PlayerDataController.Instance.getPlayerData(player); + if (!data.abilityData.hasUnlockedAbility(storageKey)) { + sendError(sender, "Player doesn't have chained ability: " + chain.getDisplayName()); + return; + } + + data.abilityData.lockAbility(storageKey); + sendResult(sender, "Removed chained ability '\u00A7b" + chain.getDisplayName() + "\u00A77' from player \u00A7a" + playerName); } @SubCommand( @@ -286,14 +370,23 @@ public class AbilityCommand extends CommandKamkeelBase { int selected = data.abilityData.getSelectedIndex(); for (int i = 0; i < abilityKeys.length; i++) { String key = abilityKeys[i]; - Ability ability = AbilityController.Instance.resolveAbility(key); - String displayName = ability != null ? ability.getDisplayName() : key; String prefix = (i == selected) ? "\u00A7e> " : " "; - // Show display name and key if different - if (!displayName.equals(key)) { - sendResult(sender, prefix + "\u00A7b" + displayName + " \u00A78[" + key + "]"); + + if (key.startsWith(PlayerAbilityData.CHAIN_PREFIX)) { + // Chained ability + String chainName = key.substring(PlayerAbilityData.CHAIN_PREFIX.length()); + ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainName); + String displayName = chain != null ? chain.getDisplayName() : chainName; + sendResult(sender, prefix + "\u00A7d[Chain] \u00A7b" + displayName); } else { - sendResult(sender, prefix + "\u00A7b" + displayName); + // Regular ability + Ability ability = AbilityController.Instance.resolveAbility(key); + String displayName = ability != null ? ability.getDisplayName() : key; + if (!displayName.equals(key)) { + sendResult(sender, prefix + "\u00A7b" + displayName + " \u00A78[" + key + "]"); + } else { + sendResult(sender, prefix + "\u00A7b" + displayName); + } } } } @@ -310,7 +403,7 @@ public class AbilityCommand extends CommandKamkeelBase { /** * Get list of player-usable ability names (for tab completion). * Resolves UUIDs to names so tab completion shows human-readable names. - * Used by CommandKamkeel for usage token. + * Used by CommandKamkeel for {@code } usage token. */ public static List getPlayerAbilityNames() { Set keys = AbilityController.Instance.getPlayerAbilityKeys(); @@ -322,4 +415,20 @@ public class AbilityCommand extends CommandKamkeelBase { Collections.sort(names); return names; } + + /** + * Get list of player-usable chained ability names (for tab completion). + * Used by CommandKamkeel for {@code } usage token. + */ + public static List getPlayerChainNames() { + List names = new ArrayList<>(); + for (String chainName : AbilityController.Instance.getChainedAbilityNamesSet()) { + ChainedAbility chain = AbilityController.Instance.getChainedAbility(chainName); + if (chain != null && chain.getAllowedBy().allowsPlayer()) { + names.add(chainName); + } + } + Collections.sort(names); + return names; + } } diff --git a/src/main/java/kamkeel/npcs/command/CommandKamkeel.java b/src/main/java/kamkeel/npcs/command/CommandKamkeel.java index 91356c825..0f0e0dbbf 100644 --- a/src/main/java/kamkeel/npcs/command/CommandKamkeel.java +++ b/src/main/java/kamkeel/npcs/command/CommandKamkeel.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import static kamkeel.npcs.command.AbilityCommand.getPlayerAbilityNames; +import static kamkeel.npcs.command.AbilityCommand.getPlayerChainNames; import static kamkeel.npcs.command.EffectCommand.getSortedEffectNames; public class CommandKamkeel extends CommandBase { @@ -139,6 +140,9 @@ public class CommandKamkeel extends CommandBase { } else if (usage.equals("")) { List keys = getPlayerAbilityNames(); return CommandBase.getListOfStringsMatchingLastWord(args, keys.toArray(new String[keys.size()])); + } else if (usage.equals("")) { + List keys = getPlayerChainNames(); + return CommandBase.getListOfStringsMatchingLastWord(args, keys.toArray(new String[keys.size()])); } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java similarity index 76% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java rename to src/main/java/kamkeel/npcs/controllers/AbilityController.java index 2af31f73a..13101f599 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -1,7 +1,14 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.IAbilityExtender; +import kamkeel.npcs.controllers.data.ability.IAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.UserType; import kamkeel.npcs.controllers.data.ability.type.*; -import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDisc; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome; @@ -47,6 +54,11 @@ public class AbilityController implements IAbilityHandler { Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", Pattern.CASE_INSENSITIVE); + // ── Chained Abilities ────────────────────────────────────────────────── + private final Map chainedAbilities = new LinkedHashMap<>(); // name → ChainedAbility + private final Map chainedAbilitiesById = new LinkedHashMap<>(); // UUID → ChainedAbility + private int chainedAbilityRevision = 0; + // ── Derived State ──────────────────────────────────────────────────────── private final Set builtInTypeIds = new HashSet<>(); @@ -240,6 +252,8 @@ public class AbilityController implements IAbilityHandler { LogWriter.info("Migrated " + migrated + " abilities (UUID-named files or missing UUIDs)"); } LogWriter.info("Loaded " + customAbilities.size() + " custom abilities"); + + loadChainedAbilities(); } private boolean nameFileExists(File dir, String name) { @@ -381,6 +395,229 @@ public class AbilityController implements IAbilityHandler { return dir; } + // ═══════════════════════════════════════════════════════════════════════════ + // CHAINED ABILITIES + // ═══════════════════════════════════════════════════════════════════════════ + + private void loadChainedAbilities() { + chainedAbilities.clear(); + chainedAbilitiesById.clear(); + + File dir = getChainedDir(); + File[] files = dir.exists() ? dir.listFiles() : null; + if (files != null) { + for (File file : files) { + if (!file.isFile() || !file.getName().endsWith(".json")) continue; + try { + String filename = file.getName(); + String key = filename.substring(0, filename.length() - 5); + + NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(nbt); + + chain.setName(key); + chainedAbilities.put(key, chain); + + String uuid = chain.getId(); + if (uuid != null && !uuid.isEmpty()) { + chainedAbilitiesById.put(uuid, chain); + } + } catch (Exception e) { + LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e); + } + } + } + + chainedAbilityRevision++; + LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities"); + } + + public boolean saveChainedAbility(ChainedAbility chain) { + if (chain == null) return false; + + String name = chain.getName(); + if (name == null || name.isEmpty()) return false; + + String uuid = chain.getId(); + boolean isNew = (uuid == null || uuid.isEmpty()); + + if (isNew) { + uuid = UUID.randomUUID().toString(); + chain.setId(uuid); + + while (hasChainedAbilityName(name)) { + name = name + "_"; + } + chain.setName(name); + } else { + ChainedAbility existing = chainedAbilitiesById.get(uuid); + if (existing != null) { + String oldName = existing.getName(); + if (!oldName.equals(name)) { + String testName = name; + while (chainedAbilities.containsKey(testName) && !testName.equals(oldName)) { + testName = testName + "_"; + } + if (!testName.equals(name)) { + name = testName; + chain.setName(name); + } + + File oldFile = new File(getChainedDir(), oldName + ".json"); + if (oldFile.exists()) { + oldFile.delete(); + } + chainedAbilities.remove(oldName); + } + } + } + + File dir = getChainedDir(); + File fileNew = new File(dir, name + ".json_new"); + File fileCurrent = new File(dir, name + ".json"); + + try { + NBTTagCompound nbt = chain.writeNBT(); + NBTJsonUtil.SaveFile(fileNew, nbt); + + if (fileCurrent.exists()) { + fileCurrent.delete(); + } + fileNew.renameTo(fileCurrent); + if (fileNew.exists()) { + fileNew.delete(); + } + + chainedAbilities.put(name, chain); + chainedAbilitiesById.put(uuid, chain); + chainedAbilityRevision++; + LogWriter.info("Saved chained ability: " + name + " [" + uuid + "]"); + SyncController.syncAllChainedAbilities(); + return true; + } catch (Exception e) { + LogWriter.error("Error saving chained ability: " + name, e); + return false; + } + } + + public boolean deleteChainedAbility(String name) { + if (name == null || name.isEmpty()) return false; + + ChainedAbility removed = chainedAbilities.remove(name); + if (removed == null) return false; + + String uuid = removed.getId(); + if (uuid != null && !uuid.isEmpty()) { + chainedAbilitiesById.remove(uuid); + } + + File dir = getChainedDir(); + File file = new File(dir, name + ".json"); + if (file.exists()) { + file.delete(); + } + + chainedAbilityRevision++; + LogWriter.info("Deleted chained ability: " + name); + SyncController.syncAllChainedAbilities(); + return true; + } + + public ChainedAbility resolveChainedAbility(String key) { + if (key == null || key.isEmpty()) return null; + + ChainedAbility byId = chainedAbilitiesById.get(key); + if (byId != null) return byId.deepCopy(); + + ChainedAbility chain = chainedAbilities.get(key); + if (chain != null) return chain.deepCopy(); + + for (Map.Entry entry : chainedAbilities.entrySet()) { + if (entry.getKey().equalsIgnoreCase(key)) { + return entry.getValue().deepCopy(); + } + } + + return null; + } + + public boolean canResolveChainedAbility(String key) { + if (key == null || key.isEmpty()) return false; + if (chainedAbilitiesById.containsKey(key)) return true; + if (chainedAbilities.containsKey(key)) return true; + for (String name : chainedAbilities.keySet()) { + if (name.equalsIgnoreCase(key)) return true; + } + return false; + } + + @Override + public ChainedAbility getChainedAbility(String name) { + ChainedAbility chain = chainedAbilities.get(name); + return chain != null ? chain.deepCopy() : null; + } + + public ChainedAbility getChainedAbilityByUUID(String uuid) { + return chainedAbilitiesById.get(uuid); + } + + public Set getChainedAbilityNamesSet() { + return new LinkedHashSet<>(chainedAbilities.keySet()); + } + + public Map getChainedAbilities() { + return chainedAbilities; + } + + @Override + public String[] getChainedAbilityNames() { + return chainedAbilities.keySet().toArray(new String[0]); + } + + @Override + public boolean hasChainedAbilityName(String name) { + return chainedAbilities.containsKey(name); + } + + @Override + public boolean deleteChainedAbilityByName(String name) { + return deleteChainedAbility(name); + } + + @Override + public boolean saveChainedAbility(noppes.npcs.api.ability.IChainedAbility chain) { + if (chain instanceof ChainedAbility) { + return saveChainedAbility((ChainedAbility) chain); + } + return false; + } + + public int getChainedAbilityRevision() { + return chainedAbilityRevision; + } + + public void setChainedAbilities(Map synced) { + chainedAbilities.clear(); + chainedAbilitiesById.clear(); + chainedAbilities.putAll(synced); + for (ChainedAbility chain : synced.values()) { + String uuid = chain.getId(); + if (uuid != null && !uuid.isEmpty()) { + chainedAbilitiesById.put(uuid, chain); + } + } + chainedAbilityRevision++; + } + + private File getChainedDir() { + File dir = new File(CustomNpcs.getWorldSaveDirectory(), "abilities" + File.separator + "chained"); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + // ═══════════════════════════════════════════════════════════════════════════ // RESOLUTION // ═══════════════════════════════════════════════════════════════════════════ @@ -525,11 +762,7 @@ public class AbilityController implements IAbilityHandler { Ability ability = resolveAbility(key); if (ability != null) return ability; - if (ChainedAbilityController.Instance != null) { - return ChainedAbilityController.Instance.resolve(key); - } - - return null; + return resolveChainedAbility(key); } /** @@ -537,8 +770,7 @@ public class AbilityController implements IAbilityHandler { */ public boolean canResolveAction(String key) { if (canResolveAbility(key)) return true; - return ChainedAbilityController.Instance != null - && ChainedAbilityController.Instance.canResolve(key); + return canResolveChainedAbility(key); } // ═══════════════════════════════════════════════════════════════════════════ @@ -715,18 +947,4 @@ public class AbilityController implements IAbilityHandler { return deleteCustomAbility(name); } - @Override - public String[] getChainedAbilityNames() { - return ChainedAbilityController.Instance.getNames().toArray(new String[0]); - } - - @Override - public boolean hasChainedAbilityName(String name) { - return ChainedAbilityController.Instance.hasName(name); - } - - @Override - public boolean deleteChainedAbilityByName(String name) { - return ChainedAbilityController.Instance.delete(name); - } } diff --git a/src/main/java/kamkeel/npcs/controllers/SyncController.java b/src/main/java/kamkeel/npcs/controllers/SyncController.java index 6789e1227..014568af3 100644 --- a/src/main/java/kamkeel/npcs/controllers/SyncController.java +++ b/src/main/java/kamkeel/npcs/controllers/SyncController.java @@ -40,9 +40,7 @@ import noppes.npcs.controllers.data.QuestCategory; import noppes.npcs.controllers.data.RecipeAnvil; import noppes.npcs.controllers.data.RecipeCarpentry; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; import java.io.IOException; import java.util.EnumMap; @@ -356,7 +354,7 @@ public class SyncController { public static NBTTagCompound chainedAbilitiesNBT() { NBTTagList list = new NBTTagList(); NBTTagCompound compound = new NBTTagCompound(); - for (Map.Entry entry : ChainedAbilityController.Instance.getChainedAbilities().entrySet()) { + for (Map.Entry entry : AbilityController.Instance.getChainedAbilities().entrySet()) { NBTTagCompound chainNBT = entry.getValue().writeNBT(); chainNBT.setString("ChainedAbilityId", entry.getKey()); list.appendTag(chainNBT); @@ -651,11 +649,11 @@ public class SyncController { LinkedHashMap sync = new LinkedHashMap<>(); for (int i = 0; i < list.tagCount(); i++) { NBTTagCompound nbt = list.getCompoundTagAt(i); - String uuid = nbt.getString("CustomAbilityId"); + String name = nbt.getString("CustomAbilityId"); Ability ability = AbilityController.Instance.fromNBT(nbt); if (ability != null) { - ability.setId(uuid); - sync.put(uuid, ability); + ability.setName(name); + sync.put(name, ability); } } AbilityController.Instance.setCustomAbilities(sync); @@ -672,7 +670,7 @@ public class SyncController { chain.setName(id); sync.put(id, chain); } - ChainedAbilityController.Instance.setChainedAbilities(sync); + AbilityController.Instance.setChainedAbilities(sync); break; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 854766529..54529b7f3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -2,6 +2,7 @@ package kamkeel.npcs.controllers.data.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -1219,12 +1220,10 @@ public abstract class Ability implements IAbility, IAbilityAction { } nbt.setTag("effects", effectList); - // Type-specific (skip for built-in abilities with fixed configs) - if (!builtIn) { - NBTTagCompound typeNBT = new NBTTagCompound(); - writeTypeNBT(typeNBT); - nbt.setTag("typeData", typeNBT); - } + // Type-specific + NBTTagCompound typeNBT = new NBTTagCompound(); + writeTypeNBT(typeNBT); + nbt.setTag("typeData", typeNBT); return nbt; } @@ -1234,16 +1233,20 @@ public abstract class Ability implements IAbility, IAbilityAction { name = nbt.getString("name"); displayName = nbt.getString("displayName"); typeId = nbt.getString("typeId"); - weight = nbt.getInteger("weight"); - enabled = nbt.getBoolean("enabled"); - targetingMode = TargetingMode.valueOf(nbt.getString("targetingMode")); + weight = nbt.hasKey("weight") ? nbt.getInteger("weight") : 10; + enabled = nbt.hasKey("enabled") ? nbt.getBoolean("enabled") : true; + try { + targetingMode = TargetingMode.valueOf(nbt.getString("targetingMode")); + } catch (Exception e) { + targetingMode = TargetingMode.AGGRO_TARGET; + } minRange = nbt.getFloat("minRange"); maxRange = nbt.getFloat("maxRange"); cooldownTicks = nbt.getInteger("cooldown"); windUpTicks = nbt.getInteger("windUp"); - syncWindupWithAnimation = nbt.getBoolean("syncWindup"); - dazedTicks = nbt.getInteger("recovery"); - interruptible = nbt.getBoolean("interruptible"); + syncWindupWithAnimation = nbt.hasKey("syncWindup") ? nbt.getBoolean("syncWindup") : true; + dazedTicks = nbt.hasKey("recovery") ? nbt.getInteger("recovery") : 80; + interruptible = nbt.hasKey("interruptible") ? nbt.getBoolean("interruptible") : true; lockMovement = LockMovementType.fromOrdinal(nbt.getInteger("lockMovement")); rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode")); rotationPhase = LockMovementType.fromOrdinal(nbt.getInteger("rotationPhase")); @@ -1257,7 +1260,7 @@ public abstract class Ability implements IAbility, IAbilityAction { windUpAnimationName = nbt.getString("windUpAnimationName"); activeAnimationName = nbt.getString("activeAnimationName"); dazedAnimationName = nbt.getString("dazedAnimationName"); - showTelegraph = nbt.getBoolean("showTelegraph"); + showTelegraph = nbt.hasKey("showTelegraph") ? nbt.getBoolean("showTelegraph") : true; try { telegraphType = TelegraphType.valueOf(nbt.getString("telegraphType")); } catch (Exception e) { @@ -1299,8 +1302,8 @@ public abstract class Ability implements IAbility, IAbilityAction { } } - // Type-specific (skip for built-in abilities with fixed configs) - if (!builtIn) { + // Type-specific + if (nbt.hasKey("typeData")) { readTypeNBT(nbt.getCompoundTag("typeData")); } } @@ -1328,9 +1331,11 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * Get the display name for this ability. * Returns displayName if set, otherwise falls back to name. + * Converts & color codes to § for rendering. */ public String getDisplayName() { - return (displayName != null && !displayName.isEmpty()) ? displayName : name; + String result = (displayName != null && !displayName.isEmpty()) ? displayName : name; + return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : ""; } /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java index 6d721ff81..e42c52980 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -1,5 +1,6 @@ package kamkeel.npcs.controllers.data.ability; +import kamkeel.npcs.controllers.AbilityController; import net.minecraft.nbt.NBTTagCompound; /** @@ -14,11 +15,13 @@ public class AbilityAction { public enum SlotType { INLINE_ABILITY, ABILITY_REFERENCE, - CHAIN_REFERENCE + CHAIN_REFERENCE, + INLINE_CHAIN } private SlotType slotType; private Ability inlineAbility; + private ChainedAbility inlineChain; private String referenceId; /** Per-slot enabled override for reference slots (null = use master's default). */ @@ -55,6 +58,13 @@ public class AbilityAction { return slot; } + public static AbilityAction inlineChain(ChainedAbility chain) { + AbilityAction slot = new AbilityAction(); + slot.slotType = SlotType.INLINE_CHAIN; + slot.inlineChain = chain; + return slot; + } + // ═══════════════════════════════════════════════════════════════════ // QUERIES // ═══════════════════════════════════════════════════════════════════ @@ -64,13 +74,21 @@ public class AbilityAction { } public boolean isReference() { - return slotType != SlotType.INLINE_ABILITY; + return slotType == SlotType.ABILITY_REFERENCE || slotType == SlotType.CHAIN_REFERENCE; } public boolean isChainReference() { return slotType == SlotType.CHAIN_REFERENCE; } + public boolean isInlineChain() { + return slotType == SlotType.INLINE_CHAIN; + } + + public boolean isChain() { + return slotType == SlotType.CHAIN_REFERENCE || slotType == SlotType.INLINE_CHAIN; + } + public boolean isAbilityReference() { return slotType == SlotType.ABILITY_REFERENCE; } @@ -92,6 +110,9 @@ public class AbilityAction { case INLINE_ABILITY: return inlineAbility; + case INLINE_CHAIN: + return inlineChain; + case ABILITY_REFERENCE: { AbilityController ctrl = AbilityController.Instance; if (ctrl == null) return null; @@ -109,13 +130,13 @@ public class AbilityAction { } case CHAIN_REFERENCE: { - ChainedAbilityController ctrl = ChainedAbilityController.Instance; + AbilityController ctrl = AbilityController.Instance; if (ctrl == null) return null; - int rev = ctrl.getRevision(); + int rev = ctrl.getChainedAbilityRevision(); if (cachedAction != null && cachedRevision == rev) { return cachedAction; } - ChainedAbility resolved = ctrl.resolve(referenceId); + ChainedAbility resolved = ctrl.resolveChainedAbility(referenceId); if (resolved != null && enabledOverride != null) { resolved.setEnabled(enabledOverride); } @@ -154,6 +175,10 @@ public class AbilityAction { if (inlineAbility != null) { inlineAbility.setEnabled(enabled); } + } else if (slotType == SlotType.INLINE_CHAIN) { + if (inlineChain != null) { + inlineChain.setEnabled(enabled); + } } else { this.enabledOverride = enabled; if (cachedAction != null) { @@ -170,22 +195,41 @@ public class AbilityAction { return inlineAbility; } + public ChainedAbility getInlineChain() { + return inlineChain; + } + /** - * Convert this reference slot to an inline slot by copying the resolved ability. - * Only works for ability references (chains cannot be inlined). + * Convert this reference slot to an inline slot by copying the resolved data. + * Works for ability references (→ INLINE_ABILITY) and chain references (→ INLINE_CHAIN). + * Chain entries are kept as-is (not auto-converted to inline). */ public boolean convertToInline() { - if (slotType != SlotType.ABILITY_REFERENCE) return false; + if (slotType == SlotType.ABILITY_REFERENCE) { + Ability resolved = getAbility(); + if (resolved == null) return false; - Ability resolved = getAbility(); - if (resolved == null) return false; + inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT()); + slotType = SlotType.INLINE_ABILITY; + referenceId = null; + cachedAction = null; + cachedRevision = -1; + return true; + } - inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT()); - slotType = SlotType.INLINE_ABILITY; - referenceId = null; - cachedAction = null; - cachedRevision = -1; - return true; + if (slotType == SlotType.CHAIN_REFERENCE) { + ChainedAbility resolved = getChainedAbility(); + if (resolved == null) return false; + + inlineChain = resolved.deepCopy(); + slotType = SlotType.INLINE_CHAIN; + referenceId = null; + cachedAction = null; + cachedRevision = -1; + return true; + } + + return false; } // ═══════════════════════════════════════════════════════════════════ @@ -194,6 +238,14 @@ public class AbilityAction { public NBTTagCompound writeNBT() { switch (slotType) { + case INLINE_CHAIN: { + NBTTagCompound nbt = new NBTTagCompound(); + if (inlineChain != null) { + nbt.setTag("InlineChain", inlineChain.writeNBT()); + } + return nbt; + } + case CHAIN_REFERENCE: { NBTTagCompound nbt = new NBTTagCompound(); nbt.setString("ChainReference", referenceId); @@ -221,7 +273,14 @@ public class AbilityAction { public static AbilityAction fromNBT(NBTTagCompound nbt) { if (nbt == null) return null; - // Chain reference (new format) + // Inline chain (new format) + if (nbt.hasKey("InlineChain")) { + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(nbt.getCompoundTag("InlineChain")); + return inlineChain(chain); + } + + // Chain reference if (nbt.hasKey("ChainReference")) { AbilityAction slot = chainReference(nbt.getString("ChainReference")); if (nbt.hasKey("RefEnabled")) { @@ -230,7 +289,7 @@ public class AbilityAction { return slot; } - // Ability reference (existing format) + // Ability reference if (nbt.hasKey("Reference")) { AbilityAction slot = abilityReference(nbt.getString("Reference")); if (nbt.hasKey("RefEnabled")) { @@ -239,7 +298,7 @@ public class AbilityAction { return slot; } - // Inline ability (existing format) + // Inline ability Ability ability = AbilityController.Instance.fromNBT(nbt); if (ability == null) return null; return inline(ability); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java index 212cd7d16..07295aaf7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java @@ -1,5 +1,6 @@ package kamkeel.npcs.controllers.data.ability; +import kamkeel.npcs.controllers.AbilityController; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java index dd45de17d..17b4f89d9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java @@ -1,5 +1,6 @@ package kamkeel.npcs.controllers.data.ability; +import kamkeel.npcs.controllers.AbilityController; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; @@ -81,9 +82,11 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { /** * Get the display name for this chained ability. * Returns displayName if set, otherwise falls back to name. + * Converts & color codes to § for rendering. */ public String getDisplayName() { - return (displayName != null && !displayName.isEmpty()) ? displayName : name; + String result = (displayName != null && !displayName.isEmpty()) ? displayName : name; + return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : ""; } public String getRawDisplayName() { @@ -178,7 +181,18 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { @Override public String getEntryReference(int index) { if (index < 0 || index >= entries.size()) return null; - return entries.get(index).getAbilityReference(); + ChainedAbilityEntry entry = entries.get(index); + if (entry.isInline()) { + Ability a = entry.getInlineAbility(); + return a != null ? a.getName() : ""; + } + return entry.getAbilityReference(); + } + + @Override + public boolean isEntryInline(int index) { + if (index < 0 || index >= entries.size()) return false; + return entries.get(index).isInline(); } @Override @@ -273,11 +287,17 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { if (controller != null) { for (int i = 0; i < entries.size(); i++) { ChainedAbilityEntry entry = entries.get(i); - String ref = entry.getAbilityReference(); - if (ref == null || ref.isEmpty()) { - errors.add("Entry " + (i + 1) + ": ability reference is empty"); - } else if (!controller.canResolveAbility(ref)) { - errors.add("Entry " + (i + 1) + ": cannot resolve ability '" + ref + "'"); + if (entry.isInline()) { + if (entry.getInlineAbility() == null) { + errors.add("Entry " + (i + 1) + ": inline ability is null"); + } + } else { + String ref = entry.getAbilityReference(); + if (ref == null || ref.isEmpty()) { + errors.add("Entry " + (i + 1) + ": ability reference is empty"); + } else if (!controller.canResolveAbility(ref)) { + errors.add("Entry " + (i + 1) + ": cannot resolve ability '" + ref + "'"); + } } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java deleted file mode 100644 index 775ea0d7e..000000000 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityController.java +++ /dev/null @@ -1,319 +0,0 @@ -package kamkeel.npcs.controllers.data.ability; - -import kamkeel.npcs.controllers.SyncController; -import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.CustomNpcs; -import noppes.npcs.LogWriter; -import noppes.npcs.util.NBTJsonUtil; - -import java.io.File; -import java.util.*; - -/** - * Global registry and storage for {@link ChainedAbility} definitions. - * Follows the same patterns as {@link AbilityController} for custom abilities: - * JSON file storage in {@code abilities/chained/}, dual-index by name and UUID, - * revision tracking for cache invalidation, and client sync. - */ -public class ChainedAbilityController { - - public static ChainedAbilityController Instance = new ChainedAbilityController(); - - private final Map chainedAbilities = new LinkedHashMap<>(); // name → ChainedAbility - private final Map chainedAbilitiesById = new LinkedHashMap<>(); // UUID → ChainedAbility - - /** Incremented on any change; used for cache invalidation. */ - private int revision = 0; - - // ═══════════════════════════════════════════════════════════════════ - // LOAD / SAVE - // ═══════════════════════════════════════════════════════════════════ - - /** - * Load all chained abilities from disk. - */ - public void load() { - chainedAbilities.clear(); - chainedAbilitiesById.clear(); - int migrated = 0; - - File dir = getDir(); - - // Legacy migration: move files from old chained_abilities/ directory - File legacyDir = new File(CustomNpcs.getWorldSaveDirectory(), "chained_abilities"); - if (legacyDir.exists() && legacyDir.isDirectory()) { - File[] legacyFiles = legacyDir.listFiles(); - if (legacyFiles != null) { - for (File legacyFile : legacyFiles) { - if (!legacyFile.isFile() || !legacyFile.getName().endsWith(".json")) continue; - File dest = new File(dir, legacyFile.getName()); - if (!dest.exists()) { - if (legacyFile.renameTo(dest)) { - LogWriter.info("Migrated chained ability file: chained_abilities/" + legacyFile.getName() + " -> abilities/chained/" + legacyFile.getName()); - migrated++; - } - } - } - // Remove legacy directory if empty - if (legacyDir.list() != null && legacyDir.list().length == 0) { - legacyDir.delete(); - } - } - } - - File[] files = dir.exists() ? dir.listFiles() : null; - if (files != null) { - for (File file : files) { - if (!file.isFile() || !file.getName().endsWith(".json")) continue; - try { - String filename = file.getName(); - String key = filename.substring(0, filename.length() - 5); - - NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); - ChainedAbility chain = new ChainedAbility(); - chain.readNBT(nbt); - - // Ensure chain has a UUID; generate one for legacy entries - String uuid = chain.getId(); - if (uuid == null || uuid.isEmpty() || uuid.equals(key)) { - uuid = UUID.randomUUID().toString(); - chain.setId(uuid); - // Re-save file with the new UUID - try { - NBTJsonUtil.SaveFile(file, chain.writeNBT()); - } catch (Exception ex) { - LogWriter.error("Failed to save UUID for chained ability: " + key, ex); - } - migrated++; - } - - // Use filename as authoritative name key - chain.setName(key); - chainedAbilities.put(key, chain); - chainedAbilitiesById.put(uuid, chain); - } catch (Exception e) { - LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e); - } - } - } - - revision++; - if (migrated > 0) { - LogWriter.info("Migrated " + migrated + " chained abilities (legacy dir or missing UUIDs)"); - } - LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities"); - } - - /** - * Save a chained ability to disk. Handles UUID assignment, renames, and uniqueness. - */ - public boolean save(ChainedAbility chain) { - if (chain == null) return false; - - String name = chain.getName(); - if (name == null || name.isEmpty()) return false; - - String uuid = chain.getId(); - boolean isNew = (uuid == null || uuid.isEmpty()); - - if (isNew) { - // Generate UUID for new chain - uuid = UUID.randomUUID().toString(); - chain.setId(uuid); - - // Ensure unique name - while (hasName(name)) { - name = name + "_"; - } - chain.setName(name); - } else { - // Existing chain — check for rename via UUID lookup - ChainedAbility existing = chainedAbilitiesById.get(uuid); - if (existing != null) { - String oldName = existing.getName(); - if (!oldName.equals(name)) { - // Name changed! Ensure new name is unique (excluding self) - String testName = name; - while (chainedAbilities.containsKey(testName) && !testName.equals(oldName)) { - testName = testName + "_"; - } - if (!testName.equals(name)) { - name = testName; - chain.setName(name); - } - - // Delete old file and remove old map entry - File oldFile = new File(getDir(), oldName + ".json"); - if (oldFile.exists()) { - oldFile.delete(); - } - chainedAbilities.remove(oldName); - } - } - } - - File dir = getDir(); - File fileNew = new File(dir, name + ".json_new"); - File fileCurrent = new File(dir, name + ".json"); - - try { - NBTTagCompound nbt = chain.writeNBT(); - NBTJsonUtil.SaveFile(fileNew, nbt); - - if (fileCurrent.exists()) { - fileCurrent.delete(); - } - fileNew.renameTo(fileCurrent); - if (fileNew.exists()) { - fileNew.delete(); - } - - chainedAbilities.put(name, chain); - chainedAbilitiesById.put(uuid, chain); - revision++; - LogWriter.info("Saved chained ability: " + name + " [" + uuid + "]"); - SyncController.syncAllChainedAbilities(); - return true; - } catch (Exception e) { - LogWriter.error("Error saving chained ability: " + name, e); - return false; - } - } - - /** - * Delete a chained ability by name. - */ - public boolean delete(String name) { - if (name == null || name.isEmpty()) return false; - - ChainedAbility removed = chainedAbilities.remove(name); - if (removed == null) return false; - - // Also remove from UUID index - String uuid = removed.getId(); - if (uuid != null && !uuid.isEmpty()) { - chainedAbilitiesById.remove(uuid); - } - - File dir = getDir(); - File file = new File(dir, name + ".json"); - if (file.exists()) { - file.delete(); - } - - revision++; - LogWriter.info("Deleted chained ability: " + name); - SyncController.syncAllChainedAbilities(); - return true; - } - - // ═══════════════════════════════════════════════════════════════════ - // RESOLUTION - // ═══════════════════════════════════════════════════════════════════ - - /** - * Resolve a chained ability by key, returning a deep copy. - * Checks UUID first, then exact name, then case-insensitive name. - */ - public ChainedAbility resolve(String key) { - if (key == null || key.isEmpty()) return null; - - // UUID lookup (most common for persistent references) - ChainedAbility byId = chainedAbilitiesById.get(key); - if (byId != null) return byId.deepCopy(); - - // Exact name match - ChainedAbility chain = chainedAbilities.get(key); - if (chain != null) return chain.deepCopy(); - - // Case-insensitive name match - for (Map.Entry entry : chainedAbilities.entrySet()) { - if (entry.getKey().equalsIgnoreCase(key)) { - return entry.getValue().deepCopy(); - } - } - - return null; - } - - /** - * Check if a key can be resolved without creating a deep copy. - */ - public boolean canResolve(String key) { - if (key == null || key.isEmpty()) return false; - - if (chainedAbilitiesById.containsKey(key)) return true; - - if (chainedAbilities.containsKey(key)) return true; - - for (String name : chainedAbilities.keySet()) { - if (name.equalsIgnoreCase(key)) return true; - } - - return false; - } - - // ═══════════════════════════════════════════════════════════════════ - // QUERIES - // ═══════════════════════════════════════════════════════════════════ - - public ChainedAbility get(String name) { - return chainedAbilities.get(name); - } - - public ChainedAbility getByUUID(String uuid) { - return chainedAbilitiesById.get(uuid); - } - - public Set getNames() { - return new LinkedHashSet<>(chainedAbilities.keySet()); - } - - public Map getChainedAbilities() { - return chainedAbilities; - } - - public boolean hasName(String name) { - return chainedAbilities.containsKey(name); - } - - public boolean hasKey(String key) { - return chainedAbilities.containsKey(key) || chainedAbilitiesById.containsKey(key); - } - - public int getRevision() { - return revision; - } - - // ═══════════════════════════════════════════════════════════════════ - // CLIENT SYNC - // ═══════════════════════════════════════════════════════════════════ - - /** - * Set chained abilities from sync payload (client-side). - */ - public void setChainedAbilities(Map synced) { - chainedAbilities.clear(); - chainedAbilitiesById.clear(); - chainedAbilities.putAll(synced); - for (ChainedAbility chain : synced.values()) { - String uuid = chain.getId(); - if (uuid != null && !uuid.isEmpty()) { - chainedAbilitiesById.put(uuid, chain); - } - } - revision++; - } - - // ═══════════════════════════════════════════════════════════════════ - // STORAGE - // ═══════════════════════════════════════════════════════════════════ - - private File getDir() { - File dir = new File(CustomNpcs.getWorldSaveDirectory(), "abilities" + File.separator + "chained"); - if (!dir.exists()) { - dir.mkdirs(); - } - return dir; - } -} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java index ace05d69d..887d85571 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java @@ -1,26 +1,74 @@ package kamkeel.npcs.controllers.data.ability; +import kamkeel.npcs.controllers.AbilityController; import net.minecraft.nbt.NBTTagCompound; /** * A single entry in a {@link ChainedAbility}, representing one ability * to execute in sequence along with its delay configuration. + *

+ * Entries can be either a reference to a registered ability (by name/key) + * or an inline ability that is owned directly by this entry. */ public class ChainedAbilityEntry { - /** Reference key (built-in name or custom ability name). */ + public enum EntryType { REFERENCE, INLINE } + + private EntryType entryType = EntryType.REFERENCE; + + /** Reference key (built-in name or custom ability name). Used when entryType == REFERENCE. */ private String abilityReference = ""; + /** Inline ability data. Used when entryType == INLINE. */ + private Ability inlineAbility = null; + /** Delay in ticks BEFORE this ability starts (after previous entry completes). */ private int delayTicks = 0; public ChainedAbilityEntry() {} public ChainedAbilityEntry(String abilityReference, int delayTicks) { + this.entryType = EntryType.REFERENCE; this.abilityReference = abilityReference != null ? abilityReference : ""; this.delayTicks = Math.max(0, delayTicks); } + // ═══════════════════════════════════════════════════════════════════ + // FACTORY METHODS + // ═══════════════════════════════════════════════════════════════════ + + public static ChainedAbilityEntry reference(String ref, int delay) { + ChainedAbilityEntry entry = new ChainedAbilityEntry(); + entry.entryType = EntryType.REFERENCE; + entry.abilityReference = ref != null ? ref : ""; + entry.delayTicks = Math.max(0, delay); + return entry; + } + + public static ChainedAbilityEntry inline(Ability ability, int delay) { + ChainedAbilityEntry entry = new ChainedAbilityEntry(); + entry.entryType = EntryType.INLINE; + entry.inlineAbility = ability; + entry.delayTicks = Math.max(0, delay); + return entry; + } + + // ═══════════════════════════════════════════════════════════════════ + // TYPE QUERIES + // ═══════════════════════════════════════════════════════════════════ + + public EntryType getEntryType() { + return entryType; + } + + public boolean isInline() { + return entryType == EntryType.INLINE; + } + + public boolean isReference() { + return entryType == EntryType.REFERENCE; + } + // ═══════════════════════════════════════════════════════════════════ // GETTERS / SETTERS // ═══════════════════════════════════════════════════════════════════ @@ -33,6 +81,14 @@ public class ChainedAbilityEntry { this.abilityReference = abilityReference != null ? abilityReference : ""; } + public Ability getInlineAbility() { + return inlineAbility; + } + + public void setInlineAbility(Ability ability) { + this.inlineAbility = ability; + } + public int getDelayTicks() { return delayTicks; } @@ -42,13 +98,77 @@ public class ChainedAbilityEntry { } // ═══════════════════════════════════════════════════════════════════ - // NBT + // RESOLUTION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Resolve this entry to an Ability instance. + * Inline entries return their ability directly. + * Reference entries resolve via AbilityController. + */ + public Ability resolve() { + if (entryType == EntryType.INLINE) { + return inlineAbility; + } + AbilityController ctrl = AbilityController.Instance; + if (ctrl == null) return null; + return ctrl.resolveAbility(abilityReference); + } + + // ═══════════════════════════════════════════════════════════════════ + // CONVERSION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Convert a reference entry to inline by cloning the resolved ability. + * Returns false if already inline or the reference cannot be resolved. + */ + public boolean convertToInline() { + if (entryType != EntryType.REFERENCE) return false; + + AbilityController ctrl = AbilityController.Instance; + if (ctrl == null) return false; + + Ability resolved = ctrl.resolveAbility(abilityReference); + if (resolved == null) return false; + + inlineAbility = ctrl.fromNBT(resolved.writeNBT()); + entryType = EntryType.INLINE; + abilityReference = ""; + return true; + } + + // ═══════════════════════════════════════════════════════════════════ + // DEEP COPY + // ═══════════════════════════════════════════════════════════════════ + + /** + * Create a deep copy of this entry. Inline abilities are copied via NBT round-trip. + */ + public ChainedAbilityEntry deepCopy() { + ChainedAbilityEntry copy = new ChainedAbilityEntry(); + copy.entryType = this.entryType; + copy.abilityReference = this.abilityReference; + copy.delayTicks = this.delayTicks; + if (this.inlineAbility != null) { + copy.inlineAbility = AbilityController.Instance.fromNBT(this.inlineAbility.writeNBT()); + } + return copy; + } + + // ═══════════════════════════════════════════════════════════════════ + // NBT (backwards compatible) // ═══════════════════════════════════════════════════════════════════ public NBTTagCompound writeNBT() { NBTTagCompound nbt = new NBTTagCompound(); - nbt.setString("Reference", abilityReference); nbt.setInteger("Delay", delayTicks); + + if (entryType == EntryType.INLINE && inlineAbility != null) { + nbt.setTag("InlineAbility", inlineAbility.writeNBT()); + } else { + nbt.setString("Reference", abilityReference); + } return nbt; } @@ -56,8 +176,20 @@ public class ChainedAbilityEntry { if (nbt == null) return null; ChainedAbilityEntry entry = new ChainedAbilityEntry(); - entry.abilityReference = nbt.getString("Reference"); entry.delayTicks = Math.max(0, nbt.getInteger("Delay")); + + // Inline entry (new format) + if (nbt.hasKey("InlineAbility")) { + NBTTagCompound abilityNBT = nbt.getCompoundTag("InlineAbility"); + entry.entryType = EntryType.INLINE; + entry.inlineAbility = AbilityController.Instance != null + ? AbilityController.Instance.fromNBT(abilityNBT) : null; + } else { + // Reference entry (old + new format) + entry.entryType = EntryType.REFERENCE; + entry.abilityReference = nbt.getString("Reference"); + } + return entry; } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 8f58fada7..b4e63bace 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -2,7 +2,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.data.*; @@ -56,6 +56,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { refs.add(new WeakReference<>(projectile)); } + public static void clearAllProjectiles() { + activeProjectiles.clear(); + } + public static List getActiveProjectiles(int ownerEntityId) { List> refs = activeProjectiles.get(ownerEntityId); if (refs == null) return Collections.emptyList(); @@ -455,8 +459,9 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { Math.pow(target.posZ - posZ, 2) ); - if (dist <= getExplosionRadius()) { - float falloff = 1.0f - (float) (dist / getExplosionRadius()) * getExplosionDamageFalloff(); + float radius = getExplosionRadius(); + if (radius > 0 && dist <= radius) { + float falloff = 1.0f - (float) (dist / radius) * getExplosionDamageFalloff(); applyDamage(target, getDamage() * falloff, getKnockback() * falloff); } } diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index dee4a3a94..eca69684d 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -98,6 +98,10 @@ import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket; import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilitiesGetPacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilityGetPacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilityRemovePacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilitySavePacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket; import kamkeel.npcs.network.packets.request.animation.AnimationGetPacket; import kamkeel.npcs.network.packets.request.animation.AnimationRemovePacket; @@ -519,6 +523,10 @@ public class PacketHandler { REQUEST_PACKET.registerPacket(new CustomAbilityRemovePacket()); REQUEST_PACKET.registerPacket(new CustomAbilitySavePacket()); REQUEST_PACKET.registerPacket(new BuiltInAbilityGetPacket()); + REQUEST_PACKET.registerPacket(new ChainedAbilitiesGetPacket()); + REQUEST_PACKET.registerPacket(new ChainedAbilityGetPacket()); + REQUEST_PACKET.registerPacket(new ChainedAbilitySavePacket()); + REQUEST_PACKET.registerPacket(new ChainedAbilityRemovePacket()); // Other Packets REQUEST_PACKET.registerPacket(new IsGuiOpenInform()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java index 25ba2bba6..e0f3451a1 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java @@ -206,6 +206,10 @@ public enum EnumRequestPacket { CustomAbilityRemove, CustomAbilitySave, BuiltInAbilityGet, + ChainedAbilitiesGet, + ChainedAbilityGet, + ChainedAbilitySave, + ChainedAbilityRemove, Hammer, RecipeScript, diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java index 6e565f3de..14f22d983 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitiesGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitiesGetPacket.java new file mode 100644 index 000000000..97ccfa52d --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitiesGetPacket.java @@ -0,0 +1,51 @@ +package kamkeel.npcs.network.packets.request.ability; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import noppes.npcs.NoppesUtilServer; + +import java.io.IOException; + +/** + * Request packet to get list of all chained ability names. + */ +public final class ChainedAbilitiesGetPacket extends AbstractPacket { + public static String packetName = "Request|ChainedAbilitiesGet"; + + public ChainedAbilitiesGetPacket() { + } + + @Override + public Enum getType() { + return EnumRequestPacket.ChainedAbilitiesGet; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player)) + return; + + NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java new file mode 100644 index 000000000..9772bc4ad --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java @@ -0,0 +1,68 @@ +package kamkeel.npcs.network.packets.request.ability; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; + +import java.io.IOException; + +/** + * Request packet to get a specific chained ability by name. + */ +public final class ChainedAbilityGetPacket extends AbstractPacket { + public static String packetName = "Request|ChainedAbilityGet"; + + private String name; + + public ChainedAbilityGetPacket() { + } + + public ChainedAbilityGetPacket(String name) { + this.name = name; + } + + @Override + public Enum getType() { + return EnumRequestPacket.ChainedAbilityGet; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, name); + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + + if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player)) + return; + + String chainName = ByteBufUtils.readString(in); + ChainedAbility chain = AbilityController.Instance.getChainedAbility(chainName); + if (chain != null) { + NBTTagCompound compound = chain.writeNBT(); + GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); + } + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityRemovePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityRemovePacket.java new file mode 100644 index 000000000..b373d6331 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityRemovePacket.java @@ -0,0 +1,69 @@ +package kamkeel.npcs.network.packets.request.ability; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.NoppesUtilServer; + +import java.io.IOException; + +/** + * Request packet to remove a chained ability by name. + */ +public final class ChainedAbilityRemovePacket extends AbstractPacket { + public static String packetName = "Request|ChainedAbilityRemove"; + + private String name; + + public ChainedAbilityRemovePacket() { + } + + public ChainedAbilityRemovePacket(String name) { + this.name = name; + } + + @Override + public Enum getType() { + return EnumRequestPacket.ChainedAbilityRemove; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @Override + public CustomNpcsPermissions.Permission getPermission() { + return CustomNpcsPermissions.GLOBAL_ABILITY; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, name); + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + + if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player)) + return; + + String chainName = ByteBufUtils.readString(in); + AbilityController.Instance.deleteChainedAbility(chainName); + NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java new file mode 100644 index 000000000..aaaf8d6a9 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java @@ -0,0 +1,79 @@ +package kamkeel.npcs.network.packets.request.ability; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.NoppesUtilServer; + +import java.io.IOException; + +/** + * Request packet to save a chained ability. + */ +public final class ChainedAbilitySavePacket extends AbstractPacket { + public static String packetName = "Request|ChainedAbilitySave"; + + private NBTTagCompound chainNBT; + + public ChainedAbilitySavePacket() { + } + + public ChainedAbilitySavePacket(NBTTagCompound chainNBT) { + this.chainNBT = chainNBT; + } + + @Override + public Enum getType() { + return EnumRequestPacket.ChainedAbilitySave; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @Override + public CustomNpcsPermissions.Permission getPermission() { + return CustomNpcsPermissions.GLOBAL_ABILITY; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeNBT(out, chainNBT); + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + + if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player)) + return; + + NBTTagCompound compound = ByteBufUtils.readNBT(in); + if (compound == null) return; + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(compound); + + if (chain.getName() != null && !chain.getName().isEmpty()) { + AbilityController.Instance.saveChainedAbility(chain); + NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player); + GuiDataPacket.sendGuiData((EntityPlayerMP) player, chain.writeNBT()); + } + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java index a4757a20c..e53d53d49 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityRemovePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityRemovePacket.java index 487d5f346..ae65c374f 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityRemovePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityRemovePacket.java @@ -3,7 +3,7 @@ package kamkeel.npcs.network.packets.request.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java index f42ae5ada..b1662e246 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java index bd2ef983c..4a0d2bb78 100644 --- a/src/main/java/kamkeel/npcs/util/Register.java +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -1,7 +1,7 @@ package kamkeel.npcs.util; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import noppes.npcs.LogWriter; import noppes.npcs.controllers.AnimationController; @@ -68,8 +68,7 @@ public class Register { } public AbilityVariant registerVariant(String typeId, String variantName, String group, Consumer configurator) { - registerVariant(typeId, variantName, () -> new AbilityVariant(variantName, group, configurator)); - return new AbilityVariant(variantName, group, configurator); + return registerVariant(typeId, variantName, () -> new AbilityVariant(variantName, group, configurator)); } public AbilityVariant registerVariant(String typeId, String factoryName, Supplier factory) { diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 89c0b4f51..341ed4e08 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -1,7 +1,7 @@ package noppes.npcs; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; @@ -352,6 +352,11 @@ public abstract class AbstractDataAbilities { cooldownEndTime = 0; } + /** Set the cooldown end time directly. */ + public void setCooldownEndTime(long endTime) { + cooldownEndTime = endTime; + } + // ═══════════════════════════════════════════════════════════════════ // POSITION LOCKING // ═══════════════════════════════════════════════════════════════════ @@ -500,7 +505,10 @@ public abstract class AbstractDataAbilities { // Fire execute event (cancelable) if (fireExecuteEvent(currentAbility, target)) { - return; // Cancelled + // Script cancelled — abort cleanly instead of leaving stuck in ACTIVE + currentAbility.interrupt(); + handleAbilityCompletion(target); + return; } // Call onExecute @@ -638,7 +646,10 @@ public abstract class AbstractDataAbilities { // Fire execute event (cancelable) if (fireExecuteEvent(ability, target)) { - return; // Cancelled + // Script cancelled — abort cleanly instead of leaving stuck in ACTIVE + ability.interrupt(); + handleAbilityCompletion(target); + return; } // Call onExecute @@ -741,7 +752,7 @@ public abstract class AbstractDataAbilities { } ChainedAbilityEntry entry = currentChain.getEntries().get(chainEntryIndex); - Ability ability = AbilityController.Instance.resolveAbility(entry.getAbilityReference()); + Ability ability = entry.resolve(); if (ability == null) { // Broken reference - complete chain completeChain(); diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index e1dcf489b..b063c790d 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -25,8 +25,7 @@ import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.TelegraphController; -import kamkeel.npcs.controllers.data.ability.AbilityController; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.profile.CNPCData; import kamkeel.npcs.developer.Developer; import kamkeel.npcs.network.PacketHandler; @@ -356,7 +355,6 @@ public class CustomNpcs { new SpawnController(); new LinkedNpcController(); AbilityController.Instance.load(); - ChainedAbilityController.Instance.load(); TelegraphController.init(); LinkedItemController.getInstance().load(); @@ -423,6 +421,8 @@ public class CustomNpcs { AuctionController.Instance.save(); } + kamkeel.npcs.entity.EntityEnergyProjectile.clearAllProjectiles(); + if (FMLCommonHandler.instance().getSide().isClient()) clientJaninoCompiler = null; globalJaninoCompiler = null; diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index af623b741..29e4822b1 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -1,11 +1,10 @@ package noppes.npcs; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; diff --git a/src/main/java/noppes/npcs/NoppesUtilServer.java b/src/main/java/noppes/npcs/NoppesUtilServer.java index bbb19c97d..521c0cb02 100644 --- a/src/main/java/noppes/npcs/NoppesUtilServer.java +++ b/src/main/java/noppes/npcs/NoppesUtilServer.java @@ -1,9 +1,9 @@ package noppes.npcs; import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.data.ParticlePacket; import kamkeel.npcs.network.packets.data.PlayerDataInfoPacket; @@ -807,7 +807,7 @@ public class NoppesUtilServer { public static void sendCustomAbilitiesData(EntityPlayerMP player) { HashMap map = new HashMap(); - kamkeel.npcs.controllers.data.ability.AbilityController controller = kamkeel.npcs.controllers.data.ability.AbilityController.Instance; + AbilityController controller = AbilityController.Instance; if (controller != null) { int index = 0; for (String name : controller.getCustomAbilityNames()) { @@ -819,6 +819,15 @@ public class NoppesUtilServer { sendScrollData(player, map, EnumScrollData.CUSTOM_ABILITIES); } + public static void sendChainedAbilitiesData(EntityPlayerMP player) { + HashMap map = new HashMap(); + int index = 0; + for (String name : AbilityController.Instance.getChainedAbilityNamesSet()) { + map.put(name, index++); + } + sendScrollData(player, map, EnumScrollData.CHAINED_ABILITIES); + } + public static DialogOption setNpcDialog(int slot, int dialogId, EntityPlayer player) throws IOException { return setNpcDialog(slot, dialogId, getEditingNpc(player)); } diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index c0405c193..a0c2ad0cc 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -9,7 +9,7 @@ import kamkeel.npcs.addon.DBCAddon; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.util.AttributeAttackUtil; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index d993762ee..05af02800 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -6,7 +6,7 @@ import cpw.mods.fml.common.gameevent.InputEvent; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import kamkeel.npcs.client.renderer.lightning.LightningBolt; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.player.CheckPlayerValue; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index 4afa9dec4..8e0db5e71 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -1,9 +1,11 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket; import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket; @@ -21,6 +23,7 @@ import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.IAbilityConfigCallback; +import noppes.npcs.client.gui.util.IChainedAbilityConfigCallback; import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IGuiData; import noppes.npcs.client.gui.util.IScrollData; @@ -36,35 +39,76 @@ import java.util.*; * GUI for managing NPC abilities. * Shows list of assigned abilities with add/remove/edit controls. */ -public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, ICustomScrollListener, IGuiData, ITextfieldListener, ISubGuiListener, IAbilityConfigCallback { +public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, ICustomScrollListener, IGuiData, ITextfieldListener, ISubGuiListener, IAbilityConfigCallback, IChainedAbilityConfigCallback { + // ── Button IDs ──────────────────────────────────────────────────────────── + private static final int BTN_SCROLL_TYPE = 50; + private static final int BTN_ADD_ABILITY = 70; + private static final int BTN_REMOVE = 71; + private static final int BTN_EDIT = 72; + private static final int BTN_MOVE_UP = 73; + private static final int BTN_MOVE_DOWN = 74; + private static final int BTN_LOAD = 75; + private static final int BTN_SAVE_PRESET = 76; + private static final int BTN_TOGGLE_SLOT = 77; + private static final int BTN_ADD_CHAIN = 78; + private static final int BTN_ENABLED = 100; + + // ── Scroll IDs ──────────────────────────────────────────────────────────── + private static final int SCROLL_TYPES = 0; + private static final int SCROLL_NPC = 1; + + // ── TextField / Label IDs ───────────────────────────────────────────────── + private static final int TF_MIN_COOLDOWN = 101; + private static final int TF_MAX_COOLDOWN = 102; + private static final int TF_SEARCH = 4; + private static final int LBL_TYPES = 1; + private static final int LBL_NPC = 2; + private static final int LBL_MIN_COOLDOWN = 101; + private static final int LBL_MAX_COOLDOWN = 102; + + // ── Scroll lists ────────────────────────────────────────────────────────── private GuiCustomScroll availableTypesScroll; private GuiCustomScroll npcAbilitiesScroll; - // All available ability types (typeId -> index) + // ── Available type data ─────────────────────────────────────────────────── private final HashMap allAbilityTypes = new HashMap<>(); private final HashMap filteredAbilityTypes = new HashMap<>(); - - // Display name to typeId mapping for the available types scroll private final HashMap displayNameToTypeId = new HashMap<>(); - // NPC's current action slots + // ── NPC slot data ───────────────────────────────────────────────────────── private final List npcSlots = new ArrayList<>(); + private final List rowMapping = new ArrayList<>(); // [slotIdx, entryIdx], entryIdx=-1 = header/standalone - // Settings + // ── NPC settings ────────────────────────────────────────────────────────── private boolean abilitiesEnabled = false; private int minCooldown = 20; private int maxCooldown = 60; + // ── Selection state ─────────────────────────────────────────────────────── private String search = ""; private int selectedAbilityIndex = -1; + private int selectedSlotIndex = -1; + private int selectedEntryIndex = -1; - // Existing preset names for duplicate checking + // ── Pending: chain editing ──────────────────────────────────────────────── + private ChainedAbility pendingChain = null; + + // ── Pending: inline chain-entry editing ────────────────────────────────── + private boolean editingChainEntry = false; + private int editChainSlotIdx = -1; + private int editChainEntryIdx = -1; + private boolean editingChainEntryParent = false; + + // ── Pending: save-as-preset ─────────────────────────────────────────────── + private int pendingSaveSlotIdx = -1; + private int pendingSaveEntryIdx = -1; // -1 for standalone ability private final Set existingPresetNames = new HashSet<>(); - // Pending variant selection + // ── Pending: variant selection ──────────────────────────────────────────── private String pendingTypeId = null; + // ── Static view state (persists across GUI opens) ───────────────────────── public static int modIndex = 0; public static ScrollType scrollType = ScrollType.CNPC; @@ -103,17 +147,17 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC int y = guiTop + 10; - addButton(new GuiNpcButton(50, guiLeft + 5, y, 140, 20, scrollType.toString())); + addButton(new GuiNpcButton(BTN_SCROLL_TYPE, guiLeft + 5, y, 140, 20, scrollType.toString())); // Cooldown range: Min Cooldown and Max Cooldown (spaced apart more) - addLabel(new GuiNpcLabel(101, "ability.minCooldown", guiLeft + 210, y + 5)); - GuiNpcTextField minField = new GuiNpcTextField(101, this, fontRendererObj, guiLeft + 260, y, 40, 20, "" + minCooldown); + addLabel(new GuiNpcLabel(LBL_MIN_COOLDOWN, "ability.minCooldown", guiLeft + 210, y + 5)); + GuiNpcTextField minField = new GuiNpcTextField(TF_MIN_COOLDOWN, this, fontRendererObj, guiLeft + 260, y, 40, 20, "" + minCooldown); minField.setIntegersOnly(); minField.setMinMaxDefault(0, 10000, 20); addTextField(minField); - addLabel(new GuiNpcLabel(102, "ability.maxCooldown", guiLeft + 320, y + 5)); - GuiNpcTextField maxField = new GuiNpcTextField(102, this, fontRendererObj, guiLeft + 370, y, 40, 20, "" + maxCooldown); + addLabel(new GuiNpcLabel(LBL_MAX_COOLDOWN, "ability.maxCooldown", guiLeft + 320, y + 5)); + GuiNpcTextField maxField = new GuiNpcTextField(TF_MAX_COOLDOWN, this, fontRendererObj, guiLeft + 370, y, 40, 20, "" + maxCooldown); maxField.setIntegersOnly(); maxField.setMinMaxDefault(0, 10000, 60); addTextField(maxField); @@ -121,13 +165,13 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC y += 28; // Enabled checkbox - addButton(new GuiNpcButton(100, guiLeft + 334, y + 145, 76, 20, new String[]{"gui.disabled", "gui.enabled"}, abilitiesEnabled ? 1 : 0)); - getButton(100).packedFGColour = abilitiesEnabled ? 0x00FF00 : 0xFF0000; + addButton(new GuiNpcButton(BTN_ENABLED, guiLeft + 334, y + 145, 76, 20, new String[]{"gui.disabled", "gui.enabled"}, abilitiesEnabled ? 1 : 0)); + getButton(BTN_ENABLED).packedFGColour = abilitiesEnabled ? 0x00FF00 : 0xFF0000; // Left scroll: available ability types - addLabel(new GuiNpcLabel(1, "ability.availableTypes", guiLeft + 5, y)); + addLabel(new GuiNpcLabel(LBL_TYPES, "ability.availableTypes", guiLeft + 5, y)); if (availableTypesScroll == null) { - availableTypesScroll = new GuiCustomScroll(this, 0); + availableTypesScroll = new GuiCustomScroll(this, SCROLL_TYPES); availableTypesScroll.setSize(140, 130); } availableTypesScroll.guiLeft = guiLeft + 5; @@ -136,12 +180,12 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC addScroll(availableTypesScroll); // Search bar for types - addTextField(new GuiNpcTextField(4, this, fontRendererObj, guiLeft + 5, y + 145, 140, 18, search)); + addTextField(new GuiNpcTextField(TF_SEARCH, this, fontRendererObj, guiLeft + 5, y + 145, 140, 18, search)); // Right scroll: NPC's abilities (widened to use available space) - addLabel(new GuiNpcLabel(2, "ability.npcAbilities", guiLeft + 210, y)); + addLabel(new GuiNpcLabel(LBL_NPC, "ability.npcAbilities", guiLeft + 210, y)); if (npcAbilitiesScroll == null) { - npcAbilitiesScroll = new GuiCustomScroll(this, 1); + npcAbilitiesScroll = new GuiCustomScroll(this, SCROLL_NPC); npcAbilitiesScroll.setSize(200, 130); } else { npcAbilitiesScroll.setSize(200, 130); @@ -153,43 +197,70 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Center buttons: Add/Remove int centerX = guiLeft + 158; - addButton(new GuiNpcButton(70, centerX, y + 15, 40, 20, ">>>")); // Add - addButton(new GuiNpcButton(71, centerX, y + 37, 40, 20, "<<<")); // Remove - getButton(71).setEnabled(selectedAbilityIndex >= 0); + addButton(new GuiNpcButton(BTN_ADD_ABILITY, centerX, y + 15, 40, 20, ">>>")); // Add + addButton(new GuiNpcButton(BTN_REMOVE, centerX, y + 37, 40, 20, "<<<")); // Remove + getButton(BTN_REMOVE).setEnabled(selectedAbilityIndex >= 0); // Load button (under the add/remove arrows) - addButton(new GuiNpcButton(75, centerX, y + 60, 40, 20, "gui.load")); + addButton(new GuiNpcButton(BTN_LOAD, centerX, y + 60, 40, 20, "gui.load")); - // On/Off toggle for selected ability (under Load button) - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - Ability selectedAbility = npcSlots.get(selectedAbilityIndex).getAbility(); - if (selectedAbility != null) { - GuiNpcButton toggleBtn = new GuiNpcButton(77, centerX, y + 112, 40, 20, - new String[]{"gui.off", "gui.on"}, selectedAbility.isEnabled() ? 1 : 0); - addButton(toggleBtn); + // On/Off toggle for selected slot (under Load button) — chains and abilities + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() && selectedEntryIndex == -1) { + AbilityAction selectedSlot = npcSlots.get(selectedSlotIndex); + boolean isEnabled = false; + if (selectedSlot.isChain()) { + ChainedAbility chain = selectedSlot.isInlineChain() ? selectedSlot.getInlineChain() : selectedSlot.getChainedAbility(); + isEnabled = chain != null && chain.isEnabled(); + } else { + Ability ability = selectedSlot.getAbility(); + isEnabled = ability != null && ability.isEnabled(); + } + GuiNpcButton toggleBtn = new GuiNpcButton(BTN_TOGGLE_SLOT, centerX, y + 112, 40, 20, + new String[]{"gui.off", "gui.on"}, isEnabled ? 1 : 0); + addButton(toggleBtn); + } + + // Check if selected slot is editable + boolean selectedIsBuiltIn = false; + boolean selectedIsChainEntry = selectedEntryIndex >= 0; + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() && !selectedIsChainEntry) { + AbilityAction sel = npcSlots.get(selectedSlotIndex); + if (!sel.isChain()) { + Ability a = sel.getAbility(); + if (a != null && a.isBuiltIn()) selectedIsBuiltIn = true; } } - // Check if selected ability is built-in (non-editable) - boolean selectedIsBuiltIn = false; - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - Ability sel = npcSlots.get(selectedAbilityIndex).getAbility(); - if (sel != null && sel.isBuiltIn()) selectedIsBuiltIn = true; - } - // Right side buttons: Edit and Up/Down carrots with Save button - addButton(new GuiNpcButton(72, guiLeft + 210, y + 145, 55, 20, "gui.edit")); - getButton(72).setEnabled(selectedAbilityIndex >= 0 && !selectedIsBuiltIn); + addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 210, y + 145, 55, 20, "gui.edit")); + getButton(BTN_EDIT).setEnabled(selectedSlotIndex >= 0 && !selectedIsBuiltIn); // Up/Down carrot buttons - addButton(new GuiNpcButton(73, guiLeft + 270, y + 145, 20, 20, "<")); - addButton(new GuiNpcButton(74, guiLeft + 292, y + 145, 20, 20, ">")); - getButton(73).setEnabled(selectedAbilityIndex > 0); - getButton(74).setEnabled(selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size() - 1); + addButton(new GuiNpcButton(BTN_MOVE_UP, guiLeft + 270, y + 145, 20, 20, "<")); + addButton(new GuiNpcButton(BTN_MOVE_DOWN, guiLeft + 292, y + 145, 20, 20, ">")); + getButton(BTN_MOVE_UP).setEnabled(selectedSlotIndex > 0 && selectedEntryIndex == -1); + getButton(BTN_MOVE_DOWN).setEnabled(selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() - 1 && selectedEntryIndex == -1); - // Save button (to the right of carrots, only enabled when ability is selected) - addButton(new GuiNpcButton(76, centerX, y + 82, 40, 20, "gui.save")); - getButton(76).setEnabled(selectedAbilityIndex >= 0 && !selectedIsBuiltIn); + // Save button — enabled for inline abilities (standalone or chain entry), disabled for references/chains + addButton(new GuiNpcButton(BTN_SAVE_PRESET, centerX, y + 82, 40, 20, "gui.save")); + boolean canSave = false; + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() && !selectedIsBuiltIn) { + AbilityAction saveSlot = npcSlots.get(selectedSlotIndex); + if (selectedEntryIndex >= 0) { + // Chain entry: only inline entries can be saved + ChainedAbility saveChain = saveSlot.isInlineChain() ? saveSlot.getInlineChain() : null; + if (saveChain != null && selectedEntryIndex < saveChain.getEntries().size()) { + canSave = saveChain.getEntries().get(selectedEntryIndex).isInline(); + } + } else if (!saveSlot.isChain() && !saveSlot.isReference()) { + // Standalone inline ability + canSave = true; + } + } + getButton(BTN_SAVE_PRESET).setEnabled(canSave); + + // Add Chain button + addButton(new GuiNpcButton(BTN_ADD_CHAIN, centerX, y + 135, 40, 20, "ability.addChain")); } @Override @@ -197,7 +268,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC int id = guibutton.id; // Enabled toggle - if (id == 100) { + if (id == BTN_ENABLED) { abilitiesEnabled = ((GuiNpcButton) guibutton).getValue() == 1; initGui(); save(); @@ -205,7 +276,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC } // Add ability - if (id == 70) { + if (id == BTN_ADD_ABILITY) { if (availableTypesScroll.hasSelected()) { String displayName = availableTypesScroll.getSelected(); String typeId = displayNameToTypeId.get(displayName); @@ -234,11 +305,23 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC return; } - // Remove ability - if (id == 71) { - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - npcSlots.remove(selectedAbilityIndex); + // Remove slot or chain entry + if (id == BTN_REMOVE) { + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { + if (selectedEntryIndex >= 0) { + // Remove entry from chain — not the whole slot + AbilityAction slot = npcSlots.get(selectedSlotIndex); + ChainedAbility chain = slot.isInlineChain() ? slot.getInlineChain() : null; + if (chain != null && selectedEntryIndex < chain.getEntries().size()) { + chain.removeEntry(selectedEntryIndex); + } + } else { + npcSlots.remove(selectedSlotIndex); + } + selectedSlotIndex = -1; + selectedEntryIndex = -1; selectedAbilityIndex = -1; + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); updateNpcAbilitiesList(); initGui(); save(); @@ -246,12 +329,45 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC return; } - // Edit ability - if (id == 72) { - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilityAction slot = npcSlots.get(selectedAbilityIndex); - if (slot.isReference()) { - // Reference slot - ask clone-and-modify vs modify-parent + // Edit ability or chain + if (id == BTN_EDIT) { + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { + AbilityAction slot = npcSlots.get(selectedSlotIndex); + + if (slot.isChain() && selectedEntryIndex == -1) { + // Chain header selected + if (slot.isChainReference()) { + // Reference chain — offer Clone & Modify / Edit Parent + setSubGui(new SubGuiAbilityEditMode()); + } else { + // Inline chain — open chain config directly + ChainedAbility chain = slot.getInlineChain(); + if (chain != null) { + pendingChain = chain; + setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); + } + } + } else if (slot.isChain() && selectedEntryIndex >= 0) { + // Chain entry selected + ChainedAbility chain = slot.isInlineChain() ? slot.getInlineChain() : slot.getChainedAbility(); + if (chain != null && selectedEntryIndex < chain.getEntries().size()) { + ChainedAbilityEntry entry = chain.getEntries().get(selectedEntryIndex); + if (entry.isInline() && slot.isInlineChain()) { + // Inline entry in inline chain — edit directly + Ability a = entry.getInlineAbility(); + if (a != null) { + editingChainEntry = true; + editChainSlotIdx = selectedSlotIndex; + editChainEntryIdx = selectedEntryIndex; + a.setNpcInlineEdit(true); + setSubGui(a.createConfigGui(this)); + } + } else { + // Reference entry or entry in reference chain — Clone & Modify / Edit Parent + setSubGui(new SubGuiAbilityEditMode()); + } + } + } else if (slot.isReference()) { setSubGui(new SubGuiAbilityEditMode()); } else { Ability ability = slot.getAbility(); @@ -265,14 +381,14 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC } // Move up (carrot left = earlier in list) - if (id == 73) { - if (selectedAbilityIndex > 0) { - AbilityAction temp = npcSlots.get(selectedAbilityIndex); - npcSlots.set(selectedAbilityIndex, npcSlots.get(selectedAbilityIndex - 1)); - npcSlots.set(selectedAbilityIndex - 1, temp); - selectedAbilityIndex--; + if (id == BTN_MOVE_UP) { + if (selectedSlotIndex > 0 && selectedEntryIndex == -1) { + AbilityAction temp = npcSlots.get(selectedSlotIndex); + npcSlots.set(selectedSlotIndex, npcSlots.get(selectedSlotIndex - 1)); + npcSlots.set(selectedSlotIndex - 1, temp); + selectedSlotIndex--; + selectedAbilityIndex = selectedSlotIndex; updateNpcAbilitiesList(); - selectAbilityByIndex(selectedAbilityIndex); initGui(); save(); } @@ -280,30 +396,53 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC } // Move down (carrot right = later in list) - if (id == 74) { - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size() - 1) { - AbilityAction temp = npcSlots.get(selectedAbilityIndex); - npcSlots.set(selectedAbilityIndex, npcSlots.get(selectedAbilityIndex + 1)); - npcSlots.set(selectedAbilityIndex + 1, temp); - selectedAbilityIndex++; + if (id == BTN_MOVE_DOWN) { + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() - 1 && selectedEntryIndex == -1) { + AbilityAction temp = npcSlots.get(selectedSlotIndex); + npcSlots.set(selectedSlotIndex, npcSlots.get(selectedSlotIndex + 1)); + npcSlots.set(selectedSlotIndex + 1, temp); + selectedSlotIndex++; + selectedAbilityIndex = selectedSlotIndex; updateNpcAbilitiesList(); - selectAbilityByIndex(selectedAbilityIndex); initGui(); save(); } return; } - // Load ability (opens load dialog to add ability directly to NPC) - if (id == 75) { - setSubGui(new SubGuiAbilityLoad(this)); + // Load ability or chain (opens type choice first) + if (id == BTN_LOAD) { + setSubGui(new SubGuiLoadTypeChoice()); return; } - // Save selected ability as custom preset - if (id == 76) { - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - Ability abilityToSave = npcSlots.get(selectedAbilityIndex).getAbility(); + // Save selected ability as custom preset (converts inline → reference) + if (id == BTN_SAVE_PRESET) { + Ability abilityToSave = null; + pendingSaveSlotIdx = -1; + pendingSaveEntryIdx = -1; + + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { + AbilityAction saveSlot = npcSlots.get(selectedSlotIndex); + + if (selectedEntryIndex >= 0) { + // Saving a chain entry's inline ability + ChainedAbility chain = saveSlot.isInlineChain() ? saveSlot.getInlineChain() : null; + if (chain != null && selectedEntryIndex < chain.getEntries().size()) { + ChainedAbilityEntry entry = chain.getEntries().get(selectedEntryIndex); + if (entry.isInline()) { + abilityToSave = entry.getInlineAbility(); + pendingSaveSlotIdx = selectedSlotIndex; + pendingSaveEntryIdx = selectedEntryIndex; + } + } + } else if (!saveSlot.isChain() && !saveSlot.isReference()) { + // Saving a standalone inline ability + abilityToSave = saveSlot.getAbility(); + pendingSaveSlotIdx = selectedSlotIndex; + pendingSaveEntryIdx = -1; + } + if (abilityToSave != null) { if (abilityToSave.getName() == null || abilityToSave.getName().isEmpty()) { abilityToSave.setNpcInlineEdit(true); @@ -316,24 +455,30 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC return; } - // Toggle selected ability on/off - if (id == 77) { - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilityAction slot = npcSlots.get(selectedAbilityIndex); - Ability ability = slot.getAbility(); - if (ability != null) { - boolean enabled = ((GuiNpcButton) guibutton).getValue() == 1; - slot.setEnabled(enabled); - updateNpcAbilitiesList(); - selectAbilityByIndex(selectedAbilityIndex); - initGui(); - save(); - } + // Toggle selected slot on/off + if (id == BTN_TOGGLE_SLOT) { + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { + AbilityAction slot = npcSlots.get(selectedSlotIndex); + boolean enabled = ((GuiNpcButton) guibutton).getValue() == 1; + slot.setEnabled(enabled); + updateNpcAbilitiesList(); + initGui(); + save(); } return; } - if (id == 50) { + // Add Chain button + if (id == BTN_ADD_CHAIN) { + ChainedAbility newChain = new ChainedAbility(); + newChain.setId(UUID.randomUUID().toString()); + newChain.setName("NewChain"); + pendingChain = newChain; + setSubGui(new SubGuiChainedAbilityConfig(newChain, this, true, npcSlots)); + return; + } + + if (id == BTN_SCROLL_TYPE) { if (scrollType != ScrollType.MODDED) { ScrollType[] values = ScrollType.values(); ScrollType next = values[(scrollType.ordinal() + 1) % values.length]; @@ -438,10 +583,10 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void keyTyped(char c, int i) { super.keyTyped(c, i); - if (getTextField(4) != null && getTextField(4).isFocused()) { - if (search.equals(getTextField(4).getText())) + if (getTextField(TF_SEARCH) != null && getTextField(TF_SEARCH).isFocused()) { + if (search.equals(getTextField(TF_SEARCH).getText())) return; - search = getTextField(4).getText().toLowerCase(); + search = getTextField(TF_SEARCH).getText().toLowerCase(); availableTypesScroll.setList(getFilteredTypeList()); availableTypesScroll.resetScroll(); } @@ -492,31 +637,34 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC private void updateNpcAbilitiesList() { List list = new ArrayList<>(); + rowMapping.clear(); + for (int i = 0; i < npcSlots.size(); i++) { - list.add(getAbilityListEntry(i)); - } + AbilityAction slot = npcSlots.get(i); - // Sort: enabled first, then numerically by slot index - Collections.sort(list, new Comparator() { - @Override - public int compare(String a, String b) { - boolean aEnabled = a.startsWith("\u00A7a"); - boolean bEnabled = b.startsWith("\u00A7a"); - if (aEnabled != bEnabled) return aEnabled ? -1 : 1; - return extractIndex(a) - extractIndex(b); - } + if (slot.isChain()) { + // Chain header row + list.add(getChainHeaderEntry(i, slot)); + rowMapping.add(new int[]{i, -1}); - private int extractIndex(String entry) { - String stripped = entry.replaceAll("\u00A7.", "").trim(); - int dotIndex = stripped.indexOf("."); - if (dotIndex > 0) { - try { - return Integer.parseInt(stripped.substring(0, dotIndex).replaceAll("[^0-9]", "")); - } catch (NumberFormatException e) { } + // Chain entry rows + ChainedAbility chain = slot.getChainedAbility(); + if (chain == null && slot.isInlineChain()) { + chain = slot.getInlineChain(); } - return Integer.MAX_VALUE; + if (chain != null) { + for (int e = 0; e < chain.getEntries().size(); e++) { + ChainedAbilityEntry entry = chain.getEntries().get(e); + list.add(getChainEntryDisplay(entry, e)); + rowMapping.add(new int[]{i, e}); + } + } + } else { + // Regular ability row + list.add(getAbilityListEntry(i)); + rowMapping.add(new int[]{i, -1}); } - }); + } if (npcAbilitiesScroll != null) { npcAbilitiesScroll.setUnsortedList(list); @@ -534,7 +682,6 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC Ability ability = slot.getAbility(); if (ability == null) { - // Broken reference String refId = slot.getReferenceId(); String shortId = refId != null && refId.length() > 8 ? refId.substring(0, 8) + "..." : refId; return "\u00A7c\u25CF\u00A7r " + (index + 1) + ". \u00A7c> [Missing: " + shortId + "]\u00A7r"; @@ -560,6 +707,33 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC return colorPrefix + (index + 1) + ". " + nameDisplay; } + private String getChainHeaderEntry(int index, AbilityAction slot) { + ChainedAbility chain = slot.isInlineChain() ? slot.getInlineChain() : slot.getChainedAbility(); + String chainName = chain != null ? chain.getDisplayName() : "???"; + boolean enabled = chain != null && chain.isEnabled(); + String colorPrefix = enabled ? "\u00A7a\u25CF\u00A7r " : "\u00A7c\u25CF\u00A7r "; + if (slot.isChainReference()) { + return colorPrefix + (index + 1) + ". \u00A7e> [Chain] " + chainName + "\u00A7r"; + } + return colorPrefix + (index + 1) + ". \u00A7d[Chain] \u00A7r" + chainName; + } + + private String getChainEntryDisplay(ChainedAbilityEntry entry, int entryIndex) { + if (entry.isInline()) { + Ability a = entry.getInlineAbility(); + String name = a != null ? a.getDisplayName() : "???"; + return " \u00A78--- \u00A7r" + name; + } else { + String name = entry.getAbilityReference(); + if (name == null || name.isEmpty()) name = "???"; + Ability resolved = entry.resolve(); + if (resolved != null) { + name = resolved.getDisplayName(); + } + return " \u00A78--- \u00A7e> " + name + "\u00A7r"; + } + } + private void selectAbilityByIndex(int index) { if (index >= 0 && index < npcSlots.size()) { String entry = getAbilityListEntry(index); @@ -601,6 +775,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC loadedAbility.setId(UUID.randomUUID().toString()); npcSlots.add(AbilityAction.inline(loadedAbility)); selectedAbilityIndex = npcSlots.size() - 1; + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); updateNpcAbilitiesList(); selectAbilityByIndex(selectedAbilityIndex); initGui(); @@ -616,6 +791,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC if (referenceId != null && !referenceId.isEmpty()) { npcSlots.add(AbilityAction.abilityReference(referenceId)); selectedAbilityIndex = npcSlots.size() - 1; + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); updateNpcAbilitiesList(); selectAbilityByIndex(selectedAbilityIndex); initGui(); @@ -633,21 +809,16 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { if (guiCustomScroll == npcAbilitiesScroll) { - String selected = npcAbilitiesScroll.getSelected(); - if (selected != null) { - // Parse index from "[●] 1. Name" format - strip color codes first - String stripped = selected.replaceAll("\u00A7.", "").trim(); - // Now format is "● 1. Name" - find the number after the circle - int dotIndex = stripped.indexOf("."); - if (dotIndex > 0) { - try { - // Extract just the number portion (skip "● " prefix) - String numPart = stripped.substring(0, dotIndex).replaceAll("[^0-9]", ""); - selectedAbilityIndex = Integer.parseInt(numPart) - 1; - } catch (NumberFormatException e) { - selectedAbilityIndex = -1; - } - } + int scrollIndex = npcAbilitiesScroll.selected; + if (scrollIndex >= 0 && scrollIndex < rowMapping.size()) { + int[] mapping = rowMapping.get(scrollIndex); + selectedSlotIndex = mapping[0]; + selectedEntryIndex = mapping[1]; + selectedAbilityIndex = selectedSlotIndex; + } else { + selectedSlotIndex = -1; + selectedEntryIndex = -1; + selectedAbilityIndex = -1; } initGui(); } @@ -655,24 +826,15 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { - if (scroll == npcAbilitiesScroll && selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilityAction slot = npcSlots.get(selectedAbilityIndex); - Ability ability = slot.getAbility(); - if (ability != null && ability.isBuiltIn()) return; - if (slot.isReference()) { - setSubGui(new SubGuiAbilityEditMode()); - } else { - if (ability != null) { - ability.setNpcInlineEdit(true); - setSubGui(ability.createConfigGui(this)); - } - } + if (scroll == npcAbilitiesScroll && selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { + // Double-click mirrors Edit button logic + buttonEvent(new GuiNpcButton(BTN_EDIT, 0, 0, 0, 0, "")); } } @Override public void unFocused(GuiNpcTextField textField) { - if (textField.id == 101) { + if (textField.id == TF_MIN_COOLDOWN) { minCooldown = textField.getInteger(); // Ensure min <= max if (minCooldown > maxCooldown) { @@ -680,7 +842,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC initGui(); } save(); - } else if (textField.id == 102) { + } else if (textField.id == TF_MAX_COOLDOWN) { maxCooldown = textField.getInteger(); // Ensure min <= max if (maxCooldown < minCooldown) { @@ -693,70 +855,263 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void subGuiClosed(SubGuiInterface subgui) { - if (subgui instanceof SubGuiAbilityVariantSelect) { - SubGuiAbilityVariantSelect variantGui = (SubGuiAbilityVariantSelect) subgui; - int idx = variantGui.getSelectedIndex(); - if (idx >= 0 && pendingTypeId != null) { - Ability newAbility = AbilityController.Instance.create(pendingTypeId); - if (newAbility != null) { - variantGui.getVariants().get(idx).apply(newAbility); - newAbility.setId(UUID.randomUUID().toString()); - npcSlots.add(AbilityAction.inline(newAbility)); - selectedAbilityIndex = npcSlots.size() - 1; + if (subgui instanceof SubGuiAbilityVariantSelect) { handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui); initGui(); return; } + if (subgui instanceof SubGuiChainedAbilityConfig) { handleChainConfigClosed(); initGui(); return; } + if (subgui instanceof SubGuiAbilityEditMode) { handleEditModeClosed((SubGuiAbilityEditMode) subgui); return; } + if (subgui instanceof SubGuiAbilitySaveConfirm) { handleSaveConfirmClosed((SubGuiAbilitySaveConfirm) subgui); initGui(); return; } + if (subgui instanceof SubGuiLoadTypeChoice) { handleLoadTypeChoiceClosed((SubGuiLoadTypeChoice) subgui); return; } + if (subgui instanceof SubGuiChainSelect) { handleChainSelectClosed((SubGuiChainSelect) subgui); return; } + if (subgui instanceof SubGuiAbilityConfig) { handleAbilityConfigClosed(); return; } + } + + private void handleVariantSelectClosed(SubGuiAbilityVariantSelect gui) { + int idx = gui.getSelectedIndex(); + if (idx >= 0 && pendingTypeId != null) { + Ability newAbility = AbilityController.Instance.create(pendingTypeId); + if (newAbility != null) { + gui.getVariants().get(idx).apply(newAbility); + newAbility.setId(UUID.randomUUID().toString()); + npcSlots.add(AbilityAction.inline(newAbility)); + selectedAbilityIndex = npcSlots.size() - 1; + selectedSlotIndex = selectedAbilityIndex; + selectedEntryIndex = -1; + updateNpcAbilitiesList(); + save(); + } + } + pendingTypeId = null; + } + + private void handleChainConfigClosed() { + if (pendingChain != null) { + boolean found = false; + for (int i = 0; i < npcSlots.size(); i++) { + AbilityAction slot = npcSlots.get(i); + if (slot.isInlineChain() && slot.getInlineChain() == pendingChain) { + found = true; + break; + } + } + if (!found) { + npcSlots.add(AbilityAction.inlineChain(pendingChain)); + selectedSlotIndex = npcSlots.size() - 1; + selectedEntryIndex = -1; + selectedAbilityIndex = selectedSlotIndex; + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); + } + pendingChain = null; + } + updateNpcAbilitiesList(); + save(); + } + + private void handleEditModeClosed(SubGuiAbilityEditMode gui) { + int mode = gui.getResult(); + if (mode < 0) return; // cancelled + + if (selectedSlotIndex < 0 || selectedSlotIndex >= npcSlots.size()) return; + AbilityAction slot = npcSlots.get(selectedSlotIndex); + + if (slot.isChain() && selectedEntryIndex == -1) { + // Chain header (must be CHAIN_REFERENCE) + if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { + if (slot.convertToInline()) { + ChainedAbility chain = slot.getInlineChain(); + if (chain != null) { + pendingChain = chain; + setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); + } updateNpcAbilitiesList(); - selectAbilityByIndex(selectedAbilityIndex); save(); } + } else if (mode == SubGuiAbilityEditMode.MODE_MODIFY_PARENT) { + ChainedAbility chain = slot.getChainedAbility(); + if (chain != null) { + pendingChain = chain; + setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); + } } - pendingTypeId = null; - initGui(); - } else if (subgui instanceof SubGuiAbilityEditMode) { - int mode = ((SubGuiAbilityEditMode) subgui).getResult(); - if (mode < 0) return; // cancelled - - if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { - AbilityAction slot = npcSlots.get(selectedAbilityIndex); - if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { - // Convert reference to inline, then edit - if (slot.convertToInline()) { - Ability ability = slot.getAbility(); - if (ability != null) { - ability.setId(UUID.randomUUID().toString()); - ability.setNpcInlineEdit(true); - setSubGui(ability.createConfigGui(this)); + } else if (slot.isChain() && selectedEntryIndex >= 0) { + // Chain entry reference + if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { + if (slot.isChainReference()) { + slot.convertToInline(); + } + ChainedAbility chain = slot.getInlineChain(); + if (chain != null && selectedEntryIndex < chain.getEntries().size()) { + ChainedAbilityEntry entry = chain.getEntries().get(selectedEntryIndex); + if (entry.convertToInline()) { + Ability a = entry.getInlineAbility(); + if (a != null) { + editingChainEntry = true; + editChainSlotIdx = selectedSlotIndex; + editChainEntryIdx = selectedEntryIndex; + a.setNpcInlineEdit(true); + setSubGui(a.createConfigGui(this)); } - updateNpcAbilitiesList(); - save(); } - } else if (mode == SubGuiAbilityEditMode.MODE_MODIFY_PARENT) { - // Edit the resolved ability directly (changes propagate to controller) - Ability ability = slot.getAbility(); - if (ability != null) { - setSubGui(ability.createConfigGui(this)); + } + updateNpcAbilitiesList(); + save(); + } else if (mode == SubGuiAbilityEditMode.MODE_MODIFY_PARENT) { + ChainedAbility chain = slot.isInlineChain() ? slot.getInlineChain() : slot.getChainedAbility(); + if (chain != null && selectedEntryIndex < chain.getEntries().size()) { + ChainedAbilityEntry entry = chain.getEntries().get(selectedEntryIndex); + Ability resolved = entry.resolve(); + if (resolved != null) { + editingChainEntryParent = true; + setSubGui(resolved.createConfigGui(this)); } } } - } else if (subgui instanceof SubGuiAbilityConfig) { + } else if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { + // Standalone ability reference → convert to inline + if (slot.convertToInline()) { + Ability ability = slot.getAbility(); + if (ability != null) { + ability.setId(UUID.randomUUID().toString()); + ability.setNpcInlineEdit(true); + setSubGui(ability.createConfigGui(this)); + } + updateNpcAbilitiesList(); + save(); + } + } else if (mode == SubGuiAbilityEditMode.MODE_MODIFY_PARENT) { + Ability ability = slot.getAbility(); + if (ability != null) { + setSubGui(ability.createConfigGui(this)); + } + } + } + + private void handleSaveConfirmClosed(SubGuiAbilitySaveConfirm gui) { + if (gui.wasSaved() && pendingSaveSlotIdx >= 0 && pendingSaveSlotIdx < npcSlots.size()) { + if (pendingSaveEntryIdx >= 0) { + // Chain entry: convert inline entry to reference + AbilityAction slot = npcSlots.get(pendingSaveSlotIdx); + ChainedAbility chain = slot.isInlineChain() ? slot.getInlineChain() : null; + if (chain != null && pendingSaveEntryIdx < chain.getEntries().size()) { + ChainedAbilityEntry entry = chain.getEntries().get(pendingSaveEntryIdx); + Ability a = entry.getInlineAbility(); + if (a != null && a.getName() != null && !a.getName().isEmpty()) { + chain.getEntries().set(pendingSaveEntryIdx, + ChainedAbilityEntry.reference(a.getName(), entry.getDelayTicks())); + } + } + } else { + // Standalone: convert inline slot to reference + AbilityAction slot = npcSlots.get(pendingSaveSlotIdx); + Ability a = slot.getAbility(); + if (a != null && a.getName() != null && !a.getName().isEmpty()) { + npcSlots.set(pendingSaveSlotIdx, AbilityAction.abilityReference(a.getName())); + } + } + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); updateNpcAbilitiesList(); save(); } + pendingSaveSlotIdx = -1; + pendingSaveEntryIdx = -1; + } + + private void handleLoadTypeChoiceClosed(SubGuiLoadTypeChoice gui) { + int result = gui.getResult(); + if (result == SubGuiLoadTypeChoice.RESULT_ABILITY) { + setSubGui(new SubGuiAbilityLoad(this)); + } else if (result == SubGuiLoadTypeChoice.RESULT_CHAIN) { + setSubGui(new SubGuiChainSelect()); + } + } + + private void handleChainSelectClosed(SubGuiChainSelect gui) { + String chainName = gui.getSelectedName(); + if (chainName != null) { + loadChainReference(chainName); + } + } + + private void handleAbilityConfigClosed() { + editingChainEntry = false; + editChainSlotIdx = -1; + editChainEntryIdx = -1; + editingChainEntryParent = false; + updateNpcAbilitiesList(); + save(); } /** * Called from SubGuiAbilityConfig when ability is saved. */ public void onAbilitySaved(Ability ability) { + // Check if we were editing a global parent ability via "Modify Parent" on a chain entry + if (editingChainEntryParent) { + PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT())); + editingChainEntryParent = false; + return; + } + + // Check if we were editing an ability inside a chain entry + if (editingChainEntry && editChainSlotIdx >= 0 && editChainSlotIdx < npcSlots.size()) { + AbilityAction slot = npcSlots.get(editChainSlotIdx); + ChainedAbility chain = slot.isInlineChain() ? slot.getInlineChain() : null; + if (chain != null && editChainEntryIdx >= 0 && editChainEntryIdx < chain.getEntries().size()) { + // Update the inline ability on the entry — it's already the same object reference, + // but refresh the list and save to persist + } + editingChainEntry = false; + editChainSlotIdx = -1; + editChainEntryIdx = -1; + updateNpcAbilitiesList(); + save(); + return; + } + if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) { AbilityAction slot = npcSlots.get(selectedAbilityIndex); if (slot.isReference()) { - // For reference slots, save back to controller PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT())); } else { - // For inline slots, replace with the updated ability npcSlots.set(selectedAbilityIndex, AbilityAction.inline(ability)); } updateNpcAbilitiesList(); save(); } } + + @Override + public void onChainedAbilitySaved(ChainedAbility chain) { + // Handled in subGuiClosed — pendingChain tracks the chain being edited + } + + /** + * Called from SubGuiAbilityLoad when a chain reference is loaded. + */ + public void loadChainReference(String referenceId) { + if (referenceId != null && !referenceId.isEmpty()) { + npcSlots.add(AbilityAction.chainReference(referenceId)); + selectedSlotIndex = npcSlots.size() - 1; + selectedEntryIndex = -1; + selectedAbilityIndex = selectedSlotIndex; + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); + updateNpcAbilitiesList(); + initGui(); + save(); + } + } + + /** + * Called from SubGuiAbilityLoad to load a chain as inline. + */ + public void loadChainInline(ChainedAbility chain) { + if (chain != null) { + npcSlots.add(AbilityAction.inlineChain(chain)); + selectedSlotIndex = npcSlots.size() - 1; + selectedEntryIndex = -1; + selectedAbilityIndex = selectedSlotIndex; + if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); + updateNpcAbilitiesList(); + initGui(); + save(); + } + } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityEditMode.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityEditMode.java index 4d21f12ca..9e3856a14 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityEditMode.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityEditMode.java @@ -3,18 +3,15 @@ package noppes.npcs.client.gui.advanced; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; -import noppes.npcs.client.gui.util.SubGuiInterface; /** * Dialog asking the user whether to clone-and-modify (convert to inline) or modify the parent ability. */ -public class SubGuiAbilityEditMode extends SubGuiInterface { +public class SubGuiAbilityEditMode extends SubGuiSimpleChoice { public static final int MODE_CLONE_MODIFY = 0; public static final int MODE_MODIFY_PARENT = 1; - private int result = -1; - public SubGuiAbilityEditMode() { setBackground("menubg.png"); xSize = 220; @@ -39,18 +36,13 @@ public class SubGuiAbilityEditMode extends SubGuiInterface { @Override public void buttonEvent(GuiButton guibutton) { if (guibutton.id == 0) { - result = MODE_CLONE_MODIFY; + setResult(MODE_CLONE_MODIFY); close(); } else if (guibutton.id == 1) { - result = MODE_MODIFY_PARENT; + setResult(MODE_MODIFY_PARENT); close(); } else if (guibutton.id == 2) { - result = -1; close(); } } - - public int getResult() { - return result; - } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java index 905faa1f4..595f5a773 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java @@ -1,7 +1,7 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoadMode.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoadMode.java index 28a7bd0bb..5995c0e9e 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoadMode.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoadMode.java @@ -3,18 +3,15 @@ package noppes.npcs.client.gui.advanced; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; -import noppes.npcs.client.gui.util.SubGuiInterface; /** * Dialog asking the user whether to Clone (inline copy) or Reference the loaded ability. */ -public class SubGuiAbilityLoadMode extends SubGuiInterface { +public class SubGuiAbilityLoadMode extends SubGuiSimpleChoice { public static final int MODE_CLONE = 0; public static final int MODE_REFERENCE = 1; - private int result = -1; - public SubGuiAbilityLoadMode() { setBackground("menubg.png"); xSize = 200; @@ -39,18 +36,13 @@ public class SubGuiAbilityLoadMode extends SubGuiInterface { @Override public void buttonEvent(GuiButton guibutton) { if (guibutton.id == 0) { - result = MODE_CLONE; + setResult(MODE_CLONE); close(); } else if (guibutton.id == 1) { - result = MODE_REFERENCE; + setResult(MODE_REFERENCE); close(); } else if (guibutton.id == 2) { - result = -1; close(); } } - - public int getResult() { - return result; - } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java index b8a4e0e66..57a363e1f 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java @@ -21,6 +21,7 @@ public class SubGuiAbilitySaveConfirm extends SubGuiInterface implements ISubGui private final Ability ability; private final IAbilityConfigCallback callback; private final Set existingNames; + private boolean saved = false; public SubGuiAbilitySaveConfirm(Ability ability) { this(ability, null, null); @@ -68,6 +69,7 @@ public class SubGuiAbilitySaveConfirm extends SubGuiInterface implements ISubGui } private void doSave() { + saved = true; PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT())); if (callback != null) { callback.onAbilitySaved(ability); @@ -75,6 +77,10 @@ public class SubGuiAbilitySaveConfirm extends SubGuiInterface implements ISubGui close(); } + public boolean wasSaved() { + return saved; + } + @Override public void subGuiClosed(SubGuiInterface subgui) { if (subgui instanceof SubGuiDuplicateNameConfirm) { diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySelect.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySelect.java new file mode 100644 index 000000000..a1d235d51 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySelect.java @@ -0,0 +1,162 @@ +package noppes.npcs.client.gui.advanced; + +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.ICustomScrollListener; +import noppes.npcs.client.gui.util.IScrollData; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.constants.EnumScrollData; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Vector; + +/** + * Simple ability name picker. Shows all abilities (custom + built-in) in a + * single scroll list with search. Returns the selected ability name. + */ +public class SubGuiAbilitySelect extends SubGuiInterface implements ICustomScrollListener, IScrollData { + + private GuiCustomScroll scroll; + private HashMap customData = new HashMap<>(); + private HashMap builtInData = new HashMap<>(); + private String selectedName = null; + private String search = ""; + + public SubGuiAbilitySelect() { + setBackground("menubg.png"); + xSize = 220; + ySize = 216; + + PacketClient.sendClient(new CustomAbilitiesGetPacket()); + } + + @Override + public void initGui() { + super.initGui(); + + int y = guiTop + 5; + + addLabel(new GuiNpcLabel(0, "ability.select", guiLeft + 10, y)); + y += 14; + + if (scroll == null) { + scroll = new GuiCustomScroll(this, 0); + scroll.setSize(200, 140); + } + scroll.guiLeft = guiLeft + 10; + scroll.guiTop = y; + scroll.setList(getFilteredList()); + if (selectedName != null) { + scroll.setSelected(selectedName); + } + addScroll(scroll); + + y += 143; + + addTextField(new GuiNpcTextField(10, this, fontRendererObj, guiLeft + 10, y, 200, 20, search)); + + y += 24; + + addButton(new GuiNpcButton(0, guiLeft + 10, y, 95, 20, "gui.select")); + addButton(new GuiNpcButton(2, guiLeft + 115, y, 95, 20, "gui.cancel")); + + getButton(0).setEnabled(selectedName != null); + } + + private List getFilteredList() { + HashMap merged = new HashMap<>(); + merged.putAll(customData); + merged.putAll(builtInData); + + if (search.isEmpty()) { + return new ArrayList<>(merged.keySet()); + } + List list = new ArrayList<>(); + for (String name : merged.keySet()) { + if (name.toLowerCase().contains(search.toLowerCase())) { + list.add(name); + } + } + return list; + } + + @Override + public void buttonEvent(GuiButton guibutton) { + int id = guibutton.id; + + if (id == 0 && selectedName != null) { + close(); + } else if (id == 2) { + selectedName = null; + close(); + } + } + + @Override + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + if (getTextField(10) != null && getTextField(10).isFocused()) { + String newSearch = getTextField(10).getText(); + if (!search.equals(newSearch)) { + search = newSearch; + scroll.resetScroll(); + scroll.setList(getFilteredList()); + } + } + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) { + if (scroll.id == 0) { + selectedName = scroll.getSelected(); + initGui(); + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { + if (scroll.id == 0 && selection != null && !selection.isEmpty()) { + selectedName = selection; + close(); + } + } + + @Override + public void setData(Vector list, HashMap data, EnumScrollData type) { + if (type == EnumScrollData.CUSTOM_ABILITIES) { + customData = data; + } else if (type == EnumScrollData.BUILTIN_ABILITIES) { + builtInData = data; + } + if (scroll != null) { + scroll.setList(getFilteredList()); + if (selectedName != null) { + scroll.setSelected(selectedName); + } + } + initGui(); + } + + @Override + public void setSelected(String selected) { + this.selectedName = selected; + if (scroll != null) { + scroll.setSelected(selected); + } + } + + /** + * Get the selected ability name after the dialog closes. + * Returns null if cancelled. + */ + public String getSelectedName() { + return selectedName; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java index 5d810862f..99d1fff54 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityTypeSelect.java @@ -1,6 +1,6 @@ package noppes.npcs.client.gui.advanced; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.resources.I18n; import noppes.npcs.client.gui.util.GuiCustomScroll; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainSelect.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainSelect.java new file mode 100644 index 000000000..f2d858551 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainSelect.java @@ -0,0 +1,92 @@ +package noppes.npcs.client.gui.advanced; + +import kamkeel.npcs.controllers.AbilityController; +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.ICustomScrollListener; +import noppes.npcs.client.gui.util.SubGuiInterface; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Selector for chained ability presets. Lists chain names from synced data. + * Returns the selected chain name via {@link #getSelectedName()}. + */ +public class SubGuiChainSelect extends SubGuiInterface implements ICustomScrollListener { + + private GuiCustomScroll scroll; + private String selectedName = null; + + public SubGuiChainSelect() { + setBackground("menubg.png"); + xSize = 220; + ySize = 200; + } + + @Override + public void initGui() { + super.initGui(); + + int y = guiTop + 5; + addLabel(new GuiNpcLabel(0, "ability.loadChain", guiLeft + 10, y)); + y += 14; + + List names = getChainNames(); + + if (scroll == null) { + scroll = new GuiCustomScroll(this, 0); + scroll.setSize(200, 130); + } + scroll.guiLeft = guiLeft + 10; + scroll.guiTop = y; + scroll.setList(names); + if (selectedName != null) { + scroll.setSelected(selectedName); + } + addScroll(scroll); + + y += 133; + addButton(new GuiNpcButton(0, guiLeft + 10, y, 95, 20, "gui.select")); + addButton(new GuiNpcButton(1, guiLeft + 115, y, 95, 20, "gui.cancel")); + getButton(0).setEnabled(selectedName != null); + } + + private List getChainNames() { + Set names = AbilityController.Instance.getChainedAbilityNamesSet(); + return new ArrayList<>(names); + } + + @Override + public void buttonEvent(GuiButton guibutton) { + if (guibutton.id == 0 && selectedName != null) { + close(); + } else if (guibutton.id == 1) { + selectedName = null; + close(); + } + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 0) { + selectedName = guiCustomScroll.getSelected(); + initGui(); + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 0 && selection != null && !selection.isEmpty()) { + selectedName = selection; + close(); + } + } + + public String getSelectedName() { + return selectedName; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java new file mode 100644 index 000000000..83a091a87 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -0,0 +1,607 @@ +package noppes.npcs.client.gui.advanced; + +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.Condition; +import kamkeel.npcs.controllers.data.ability.UserType; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.client.gui.builder.GuiFieldBuilder; +import noppes.npcs.client.gui.util.GuiMenuTopButton; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.GuiScrollWindow; +import noppes.npcs.client.gui.util.IAbilityConfigCallback; +import noppes.npcs.client.gui.util.IChainedAbilityConfigCallback; +import noppes.npcs.client.gui.util.ISubGuiListener; +import noppes.npcs.client.gui.util.ITextfieldListener; +import noppes.npcs.client.gui.util.SubGuiInterface; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * SubGui for editing a {@link ChainedAbility}. + * Three tabs: General, Entries, Conditions. + */ +public class SubGuiChainedAbilityConfig extends SubGuiInterface implements ITextfieldListener, ISubGuiListener, IAbilityConfigCallback { + + // ── Declarative field ID ranges ────────────────────────────────────────── + private static final int DECLARATIVE_ID_START = 1000; + private static final int CLEAR_ID_START = 2000; + private static final int LABEL_ID_START = 3000; + + // ── Tab constants ───────────────────────────────────────────────────────── + private static final int TAB_GENERAL = 0; + private static final int TAB_ENTRIES = 1; + private static final int TAB_CONDITIONS = 2; + + // ── Tab button IDs ──────────────────────────────────────────────────────── + private static final int BTN_TAB_GENERAL = 90; + private static final int BTN_TAB_ENTRIES = 91; + private static final int BTN_TAB_CONDITIONS = 92; + private static final int BTN_CLOSE = -1000; + + // ── Entries tab: per-entry ID encoding ───────────────────────────────────── + // Actual ID = ENTRY_BASE + i * ENTRY_STRIDE + offset + // offset 0 = name/select button, 1 = delay text field, 2 = up, 3 = down, 4 = delete + private static final int ENTRY_BASE = 100; + private static final int ENTRY_END = 200; + private static final int ENTRY_STRIDE = 10; + private static final int BTN_ADD_ENTRY = 200; + + // ── Conditions tab: per-condition ID encoding ───────────────────────────── + // Actual ID = COND_BASE + i * COND_STRIDE + offset + // offset 0 = name/click, 1 = edit, 2 = delete + private static final int COND_BASE = 50; + private static final int COND_END = 80; + private static final int COND_STRIDE = 10; + private static final int BTN_ADD_COND = 80; + + // ── Layout constants ────────────────────────────────────────────────────── + private static final int L_LABEL_X = 5; + private static final int ROW_H = 24; + private static final int MAX_ENTRIES = 10; + + private final ChainedAbility chain; + private final IChainedAbilityConfigCallback callback; + private final boolean npcContext; + private final List npcSlots; + private int activeTab = TAB_GENERAL; + + private List fieldDefs; + private List entries; + private List conditions; + + private GuiFieldBuilder builder; + private float[] tabScrollY = new float[3]; + + // Entry editing + private int editingEntryIndex = -1; + + // Condition editing + private int editingConditionIndex = -1; + + // Save only on explicit X button, not ESC + private boolean saveOnClose = false; + + // Track consumed NPC slot indices for deferred removal on save + private final List consumedSlotIndices = new ArrayList<>(); + + public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback) { + this(chain, callback, false, null); + } + + public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback, + boolean npcContext, List npcSlots) { + this.chain = chain; + this.callback = callback; + this.npcContext = npcContext; + this.npcSlots = npcSlots; + this.entries = new ArrayList<>(chain.getEntries()); + this.conditions = new ArrayList<>(chain.getConditions()); + + buildFieldDefs(); + + setBackground("menubg.png", 217); + xSize = 356; + ySize = 200; + } + + private void buildFieldDefs() { + fieldDefs = new ArrayList<>(); + + fieldDefs.add(FieldDef.stringField("gui.name", chain::getName, chain::setName).tab("General")); + fieldDefs.add(FieldDef.stringField("gui.displayName", chain::getRawDisplayName, chain::setDisplayName).tab("General")); + fieldDefs.add(FieldDef.boolField("gui.enabled", chain::isEnabled, chain::setEnabled).tab("General")); + fieldDefs.add(FieldDef.intField("ability.weight", chain::getWeight, chain::setWeight).range(1, 100).tab("General")); + fieldDefs.add(FieldDef.boolField("ability.windUpAll", chain::isWindUpAll, chain::setWindUpAll).tab("General")); + fieldDefs.add(FieldDef.intField("ability.cooldown", chain::getCooldownTicks, chain::setCooldownTicks).range(0, 12000).tab("General")); + fieldDefs.add(FieldDef.row( + FieldDef.floatField("ability.minRange", chain::getMinRange, chain::setMinRange).range(0, 64), + FieldDef.floatField("ability.maxRange", chain::getMaxRange, chain::setMaxRange).range(0, 64) + ).tab("General")); + fieldDefs.add(FieldDef.enumField("ability.allowedBy", UserType.class, chain::getAllowedBy, chain::setAllowedBy).tab("General")); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // INIT GUI + // ═══════════════════════════════════════════════════════════════════════════ + + @Override + public void initGui() { + // Save scroll position + GuiScrollWindow oldSw = getScrollableGui(0); + if (oldSw != null && activeTab < tabScrollY.length) { + tabScrollY[activeTab] = oldSw.nextScrollY; + } + + super.initGui(); + + // Tab buttons + GuiMenuTopButton generalTab = new GuiMenuTopButton(BTN_TAB_GENERAL, guiLeft + 4, guiTop - 17, "menu.general"); + generalTab.active = (activeTab == TAB_GENERAL); + addTopButton(generalTab); + + GuiMenuTopButton entriesTab = new GuiMenuTopButton(BTN_TAB_ENTRIES, generalTab, "ability.entries"); + entriesTab.active = (activeTab == TAB_ENTRIES); + addTopButton(entriesTab); + + GuiMenuTopButton conditionsTab = new GuiMenuTopButton(BTN_TAB_CONDITIONS, entriesTab, "ability.conditions"); + conditionsTab.active = (activeTab == TAB_CONDITIONS); + addTopButton(conditionsTab); + + GuiMenuTopButton closeBtn = new GuiMenuTopButton(BTN_CLOSE, guiLeft + xSize - 22, guiTop - 17, "X"); + addTopButton(closeBtn); + + // Scroll window dimensions + int swX = guiLeft + 4; + int swY = guiTop + 5; + int swW = xSize - 8; + int swH = ySize - 10; + + if (activeTab == TAB_GENERAL) { + buildGeneralTab(swX, swY, swW, swH); + } else if (activeTab == TAB_ENTRIES) { + buildEntriesTab(swX, swY, swW, swH); + } else if (activeTab == TAB_CONDITIONS) { + buildConditionsTab(swX, swY, swW, swH); + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // GENERAL TAB + // ═══════════════════════════════════════════════════════════════════════════ + + private void buildGeneralTab(int swX, int swY, int swW, int swH) { + List tabFields = new ArrayList<>(); + for (FieldDef def : fieldDefs) { + if ("General".equals(def.getTab()) && def.isVisible()) { + tabFields.add(def); + } + } + + builder = new GuiFieldBuilder(this, fontRendererObj); + builder.startIds(DECLARATIVE_ID_START, CLEAR_ID_START, LABEL_ID_START); + builder.startY(5); + + GuiScrollWindow sw = builder.buildScrollWindow(tabFields, swX, swY, swW, swH); + restoreScroll(sw); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // ENTRIES TAB + // ═══════════════════════════════════════════════════════════════════════════ + + private void buildEntriesTab(int swX, int swY, int swW, int swH) { + GuiScrollWindow sw = new GuiScrollWindow(this, swX, swY, swW, swH, 0); + sw.backgroundColor = 0x88000000; + addScrollableGui(0, sw); + + int y = 5; + int labelCounter = 0; + + for (int i = 0; i < entries.size() && i < MAX_ENTRIES; i++) { + ChainedAbilityEntry entry = entries.get(i); + + // Index label + sw.addLabel(new GuiNpcLabel(labelCounter++, (i + 1) + ".", L_LABEL_X, y + 5, 0xFFFFFF)); + + // Ability reference/inline button + String btnLabel; + if (entry.isInline()) { + Ability a = entry.getInlineAbility(); + btnLabel = a != null ? a.getDisplayName() : "\u00A7cEmpty"; + } else { + String refName = entry.getAbilityReference(); + if (refName == null || refName.isEmpty()) { + btnLabel = StatCollector.translateToLocal("gui.select") + "..."; + } else { + Ability resolved = entry.resolve(); + btnLabel = "\u00A7e> " + (resolved != null ? resolved.getDisplayName() : refName); + } + } + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE, L_LABEL_X + 15, y, 140, 20, btnLabel)); + + // Delay label + field + sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.delay", L_LABEL_X + 160, y + 5, 0xAAAAAA)); + GuiNpcTextField delayField = new GuiNpcTextField(ENTRY_BASE + i * ENTRY_STRIDE + 1, this, fontRendererObj, + L_LABEL_X + 195, y, 40, 20, String.valueOf(entry.getDelayTicks())); + delayField.setIntegersOnly(); + delayField.setMinMaxDefault(0, 6000, 0); + sw.addTextField(delayField); + + // Up button + if (i > 0) { + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 2, L_LABEL_X + 240, y, 20, 20, "\u2191")); + } + + // Down button + if (i < entries.size() - 1) { + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 3, L_LABEL_X + 263, y, 20, 20, "\u2193")); + } + + // Delete button + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 4, L_LABEL_X + 288, y, 20, 20, "X")); + + y += ROW_H; + } + + // Add entry button + if (entries.size() < MAX_ENTRIES) { + sw.addButton(new GuiNpcButton(BTN_ADD_ENTRY, L_LABEL_X, y, 80, 20, "gui.add")); + y += ROW_H; + } + + sw.maxScrollY = Math.max(y - swH, 0); + restoreScroll(sw); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // CONDITIONS TAB + // ═══════════════════════════════════════════════════════════════════════════ + + private void buildConditionsTab(int swX, int swY, int swW, int swH) { + GuiScrollWindow sw = new GuiScrollWindow(this, swX, swY, swW, swH, 0); + sw.backgroundColor = 0x88000000; + addScrollableGui(0, sw); + + int y = 5; + int labelCounter = 0; + + sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55)); + y += 15; + + for (int i = 0; i < conditions.size() && i < 3; i++) { + Condition cond = conditions.get(i); + String condName = getConditionDisplayName(cond); + sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName)); + sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 1, L_LABEL_X + 145, y, 40, 20, "gui.edit")); + sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 2, L_LABEL_X + 190, y, 20, 20, "X")); + y += 22; + } + + if (conditions.size() < 3) { + sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add")); + y += ROW_H; + } + + sw.maxScrollY = Math.max(y - swH, 0); + restoreScroll(sw); + } + + private String getConditionDisplayName(Condition cond) { + if (cond == null) return "None"; + String typeId = cond.getTypeId(); + switch (typeId) { + case "hp_above": + return StatCollector.translateToLocal("condition.hp_above") + " " + (int) (getConditionThreshold(cond) * 100) + "%"; + case "hp_below": + return StatCollector.translateToLocal("condition.hp_below") + " " + (int) (getConditionThreshold(cond) * 100) + "%"; + case "target_hp_above": + return StatCollector.translateToLocal("condition.target_hp_above") + " " + (int) (getConditionThreshold(cond) * 100) + "%"; + case "target_hp_below": + return StatCollector.translateToLocal("condition.target_hp_below") + " " + (int) (getConditionThreshold(cond) * 100) + "%"; + case "hit_count": + Condition.ConditionHitCount hc = (Condition.ConditionHitCount) cond; + return StatCollector.translateToLocal("condition.hit_count") + ": " + hc.getRequiredHits() + "/" + hc.getWithinTicks() + "t"; + default: + return typeId; + } + } + + private float getConditionThreshold(Condition cond) { + NBTTagCompound nbt = cond.writeNBT(); + return nbt.hasKey("threshold") ? nbt.getFloat("threshold") : 0.5f; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // SCROLL RESTORE + // ═══════════════════════════════════════════════════════════════════════════ + + private void restoreScroll(GuiScrollWindow sw) { + if (activeTab < tabScrollY.length) { + float restored = Math.min(tabScrollY[activeTab], sw.maxScrollY); + sw.nextScrollY = restored; + sw.scrollY = restored; + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // BUTTON EVENTS + // ═══════════════════════════════════════════════════════════════════════════ + + @Override + public void buttonEvent(GuiButton guibutton) { + int id = guibutton.id; + + // Tab switching + if (id == BTN_TAB_GENERAL) { activeTab = TAB_GENERAL; initGui(); return; } + if (id == BTN_TAB_ENTRIES) { activeTab = TAB_ENTRIES; initGui(); return; } + if (id == BTN_TAB_CONDITIONS) { activeTab = TAB_CONDITIONS; initGui(); return; } + if (id == BTN_CLOSE) { saveOnClose = true; close(); return; } + + // Entries tab + if (activeTab == TAB_ENTRIES) { + if (handleEntryButton(id)) return; + } + + // Conditions tab + if (activeTab == TAB_CONDITIONS) { + if (handleConditionButton(id)) return; + } + + // General tab — declarative field handling + if (activeTab == TAB_GENERAL && builder != null) { + if (builder.handleButtonEvent(id, guibutton)) { + if (!hasSubGui()) { + initGui(); + } + return; + } + } + } + + private boolean handleEntryButton(int id) { + // Add entry + if (id == BTN_ADD_ENTRY) { + if (entries.size() < MAX_ENTRIES) { + // Both contexts use entry source selector; hide NPC option in global context + setSubGui(new SubGuiChainedEntrySource(npcContext)); + } + return true; + } + + // Per-entry buttons (ENTRY_BASE to ENTRY_END) + if (id >= ENTRY_BASE && id < ENTRY_END) { + int entryIndex = (id - ENTRY_BASE) / ENTRY_STRIDE; + int action = (id - ENTRY_BASE) % ENTRY_STRIDE; + + if (entryIndex < 0 || entryIndex >= entries.size()) return false; + + switch (action) { + case 0: // Entry name button — reference: select ability; inline: edit config + ChainedAbilityEntry clickedEntry = entries.get(entryIndex); + if (clickedEntry.isReference()) { + editingEntryIndex = entryIndex; + setSubGui(new SubGuiAbilitySelect()); + } else if (clickedEntry.isInline() && clickedEntry.getInlineAbility() != null) { + editingEntryIndex = entryIndex; + setSubGui(new SubGuiAbilityConfig(clickedEntry.getInlineAbility(), this)); + } + return true; + case 2: // Up + if (entryIndex > 0) { + ChainedAbilityEntry entry = entries.remove(entryIndex); + entries.add(entryIndex - 1, entry); + initGui(); + } + return true; + case 3: // Down + if (entryIndex < entries.size() - 1) { + ChainedAbilityEntry entry = entries.remove(entryIndex); + entries.add(entryIndex + 1, entry); + initGui(); + } + return true; + case 4: // Delete + entries.remove(entryIndex); + initGui(); + return true; + } + } + + return false; + } + + private boolean handleConditionButton(int id) { + if (id >= COND_BASE && id < COND_END) { + int condIndex = (id - COND_BASE) / COND_STRIDE; + int action = (id - COND_BASE) % COND_STRIDE; + if (action == 0 || action == 1) { + if (condIndex < conditions.size()) { + editingConditionIndex = condIndex; + setSubGui(new SubGuiConditionEdit(conditions.get(condIndex))); + } + } else if (action == 2) { + if (condIndex < conditions.size()) { + conditions.remove(condIndex); + initGui(); + } + } + return true; + } + if (id == BTN_ADD_COND) { + if (conditions.size() < 3) { + editingConditionIndex = conditions.size(); + setSubGui(new SubGuiConditionEdit(null)); + } + return true; + } + return false; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TEXT FIELD EVENTS + // ═══════════════════════════════════════════════════════════════════════════ + + @Override + public void unFocused(GuiNpcTextField textField) { + int id = textField.id; + + // General tab — declarative fields + if (activeTab == TAB_GENERAL && builder != null && id >= DECLARATIVE_ID_START) { + if (builder.handleTextFieldEvent(id, textField)) { + initGui(); + } + return; + } + + // Entries tab — delay fields (ENTRY_BASE + i * ENTRY_STRIDE + 1) + if (activeTab == TAB_ENTRIES && id >= ENTRY_BASE + 1 && id < ENTRY_END) { + int entryIndex = (id - ENTRY_BASE - 1) / ENTRY_STRIDE; + if (entryIndex >= 0 && entryIndex < entries.size()) { + entries.get(entryIndex).setDelayTicks(textField.getInteger()); + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // SUB GUI CLOSED + // ═══════════════════════════════════════════════════════════════════════════ + + @Override + public void subGuiClosed(SubGuiInterface subgui) { + // General tab — declarative sub-gui handling + if (activeTab == TAB_GENERAL && builder != null) { + if (builder.handleSubGuiClosed(subgui)) { + initGui(); + return; + } + } + + if (subgui instanceof SubGuiAbilitySelect) { handleAbilitySelectClosed((SubGuiAbilitySelect) subgui); initGui(); return; } + if (subgui instanceof SubGuiChainedEntrySource) { handleEntrySourceClosed((SubGuiChainedEntrySource) subgui); return; } + if (subgui instanceof SubGuiNpcSlotPicker) { handleSlotPickerClosed((SubGuiNpcSlotPicker) subgui); initGui(); return; } + if (subgui instanceof SubGuiAbilityTypeSelect) { handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui); if (!hasSubGui()) initGui(); return; } + if (subgui instanceof SubGuiAbilityConfig) { editingEntryIndex = -1; initGui(); return; } + if (subgui instanceof SubGuiConditionEdit) { handleConditionEditClosed((SubGuiConditionEdit) subgui); initGui(); return; } + } + + private void handleAbilitySelectClosed(SubGuiAbilitySelect gui) { + String selectedName = gui.getSelectedName(); + if (selectedName != null) { + if (editingEntryIndex >= 0 && editingEntryIndex < entries.size()) { + int delay = entries.get(editingEntryIndex).getDelayTicks(); + entries.set(editingEntryIndex, ChainedAbilityEntry.reference(selectedName, delay)); + } else { + entries.add(ChainedAbilityEntry.reference(selectedName, 0)); + } + } + editingEntryIndex = -1; + } + + private void handleEntrySourceClosed(SubGuiChainedEntrySource gui) { + int source = gui.getResult(); + if (source == SubGuiChainedEntrySource.SOURCE_NPC_SLOTS) { + setSubGui(new SubGuiNpcSlotPicker(npcSlots)); + } else if (source == SubGuiChainedEntrySource.SOURCE_LOAD_PRESET) { + setSubGui(new SubGuiAbilitySelect()); + } else if (source == SubGuiChainedEntrySource.SOURCE_CREATE_NEW) { + setSubGui(new SubGuiAbilityTypeSelect()); + } + // Don't initGui — we're opening another SubGui + } + + private void handleSlotPickerClosed(SubGuiNpcSlotPicker gui) { + int slotIndex = gui.getSelectedIndex(); + if (slotIndex >= 0 && npcSlots != null && slotIndex < npcSlots.size()) { + AbilityAction consumed = npcSlots.get(slotIndex); + Ability ability = consumed.getAbility(); + if (ability != null) { + if (consumed.getSlotType() == AbilityAction.SlotType.INLINE_ABILITY) { + entries.add(ChainedAbilityEntry.inline(ability, 0)); + } else { + entries.add(ChainedAbilityEntry.reference(consumed.getReferenceId(), 0)); + } + consumedSlotIndices.add(slotIndex); + } + } + } + + private void handleTypeSelectClosed(SubGuiAbilityTypeSelect gui) { + String typeId = gui.getSelectedTypeId(); + if (typeId != null) { + Ability newAbility = AbilityController.Instance.create(typeId); + if (newAbility != null) { + newAbility.setId(java.util.UUID.randomUUID().toString()); + entries.add(ChainedAbilityEntry.inline(newAbility, 0)); + editingEntryIndex = entries.size() - 1; + setSubGui(new SubGuiAbilityConfig(newAbility, this)); + } + } + } + + private void handleConditionEditClosed(SubGuiConditionEdit gui) { + Condition result = gui.getResult(); + if (result != null && editingConditionIndex >= 0) { + if (editingConditionIndex < conditions.size()) { + conditions.set(editingConditionIndex, result); + } else { + conditions.add(result); + } + } + editingConditionIndex = -1; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // CLOSE / APPLY + // ═══════════════════════════════════════════════════════════════════════════ + + @Override + public void onAbilitySaved(Ability ability) { + // Inline ability mutated in-place; entry already holds the reference + } + + @Override + public void close() { + if (saveOnClose) { + GuiNpcTextField.unfocus(); + applyToChain(); + callback.onChainedAbilitySaved(chain); + } + super.close(); + } + + + private void applyToChain() { + // Entries + chain.getEntries().clear(); + for (ChainedAbilityEntry entry : entries) { + chain.addEntry(entry); + } + + // Conditions + chain.getConditions().clear(); + for (Condition c : conditions) { + chain.getConditions().add(c); + } + + // Remove consumed NPC slots (descending order to preserve indices) + if (npcSlots != null && !consumedSlotIndices.isEmpty()) { + Collections.sort(consumedSlotIndices, Collections.reverseOrder()); + for (int idx : consumedSlotIndices) { + if (idx < npcSlots.size()) { + npcSlots.remove(idx); + } + } + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedEntrySource.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedEntrySource.java new file mode 100644 index 000000000..2c544a937 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedEntrySource.java @@ -0,0 +1,58 @@ +package noppes.npcs.client.gui.advanced; + +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; + +/** + * Choice dialog for adding an entry to a chain. + * Options: From NPC Slots (optional), Load Preset, Create New. + */ +public class SubGuiChainedEntrySource extends SubGuiSimpleChoice { + + public static final int SOURCE_NONE = -1; + public static final int SOURCE_NPC_SLOTS = 0; + public static final int SOURCE_LOAD_PRESET = 1; + public static final int SOURCE_CREATE_NEW = 2; + + private final boolean showNpcOption; + + public SubGuiChainedEntrySource() { + this(true); + } + + public SubGuiChainedEntrySource(boolean showNpcOption) { + this.showNpcOption = showNpcOption; + setBackground("menubg.png"); + xSize = 200; + ySize = showNpcOption ? 110 : 88; + } + + @Override + public void initGui() { + super.initGui(); + + int y = guiTop + 10; + addLabel(new GuiNpcLabel(0, "ability.entrySource", guiLeft + 10, y)); + + y += 20; + if (showNpcOption) { + addButton(new GuiNpcButton(0, guiLeft + 10, y, 180, 20, "ability.fromNpc")); + y += 22; + } + + addButton(new GuiNpcButton(1, guiLeft + 10, y, 180, 20, "ability.byReference")); + + y += 22; + addButton(new GuiNpcButton(2, guiLeft + 10, y, 180, 20, "ability.createNew")); + } + + @Override + public void buttonEvent(GuiButton guibutton) { + int id = guibutton.id; + if (id >= 0 && id <= 2) { + setResult(id); + close(); + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiLoadTypeChoice.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiLoadTypeChoice.java new file mode 100644 index 000000000..cb3fd4cb3 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiLoadTypeChoice.java @@ -0,0 +1,47 @@ +package noppes.npcs.client.gui.advanced; + +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; + +/** + * Choice dialog for the Load button: Load Ability or Load Chain. + */ +public class SubGuiLoadTypeChoice extends SubGuiSimpleChoice { + + public static final int RESULT_NONE = -1; + public static final int RESULT_ABILITY = 0; + public static final int RESULT_CHAIN = 1; + + public SubGuiLoadTypeChoice() { + setBackground("menubg.png"); + xSize = 200; + ySize = 80; + } + + @Override + public void initGui() { + super.initGui(); + + int y = guiTop + 10; + addLabel(new GuiNpcLabel(0, "ability.loadType", guiLeft + 10, y)); + + y += 20; + addButton(new GuiNpcButton(0, guiLeft + 10, y, 85, 20, "ability.loadAbility")); + addButton(new GuiNpcButton(1, guiLeft + 105, y, 85, 20, "ability.loadChain")); + + y += 24; + addButton(new GuiNpcButton(2, guiLeft + 55, y, 90, 20, "gui.cancel")); + } + + @Override + public void buttonEvent(GuiButton guibutton) { + int id = guibutton.id; + if (id == 0 || id == 1) { + setResult(id); + close(); + } else if (id == 2) { + close(); + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiNpcSlotPicker.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiNpcSlotPicker.java new file mode 100644 index 000000000..90086857f --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiNpcSlotPicker.java @@ -0,0 +1,113 @@ +package noppes.npcs.client.gui.advanced; + +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityAction; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.resources.I18n; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.ICustomScrollListener; +import noppes.npcs.client.gui.util.SubGuiInterface; + +import java.util.ArrayList; +import java.util.List; + +/** + * Picker for selecting an NPC ability slot to consume into a chain entry. + * Shows standalone (non-chain) ability slots from the NPC's current list. + */ +public class SubGuiNpcSlotPicker extends SubGuiInterface implements ICustomScrollListener { + + private final List npcSlots; + private final List validIndices = new ArrayList<>(); + private GuiCustomScroll scroll; + private int selectedIndex = -1; + + public SubGuiNpcSlotPicker(List npcSlots) { + this.npcSlots = npcSlots; + setBackground("menubg.png"); + xSize = 220; + ySize = 200; + } + + @Override + public void initGui() { + super.initGui(); + + int y = guiTop + 5; + addLabel(new GuiNpcLabel(0, "ability.selectSlot", guiLeft + 10, y)); + y += 14; + + List displayList = new ArrayList<>(); + validIndices.clear(); + + for (int i = 0; i < npcSlots.size(); i++) { + AbilityAction slot = npcSlots.get(i); + // Only show standalone abilities (not chains) + if (slot.isChain()) continue; + + Ability ability = slot.getAbility(); + if (ability == null) continue; + + String typeName = I18n.format(ability.getTypeId()); + String customName = ability.getName(); + String display; + if (customName != null && !customName.isEmpty() && !customName.equals(typeName)) { + display = (i + 1) + ". " + customName + " (" + typeName + ")"; + } else { + display = (i + 1) + ". " + typeName; + } + + if (slot.isReference()) { + display = "\u00A7e" + display + " (Ref)"; + } + + displayList.add(display); + validIndices.add(i); + } + + if (scroll == null) { + scroll = new GuiCustomScroll(this, 0); + scroll.setSize(200, 130); + } + scroll.guiLeft = guiLeft + 10; + scroll.guiTop = y; + scroll.setList(displayList); + addScroll(scroll); + + y += 133; + addButton(new GuiNpcButton(0, guiLeft + 10, y, 95, 20, "gui.select")); + addButton(new GuiNpcButton(1, guiLeft + 115, y, 95, 20, "gui.cancel")); + getButton(0).setEnabled(selectedIndex >= 0); + } + + @Override + public void buttonEvent(GuiButton guibutton) { + if (guibutton.id == 0 && selectedIndex >= 0) { + close(); + } else if (guibutton.id == 1) { + selectedIndex = -1; + close(); + } + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 0 && guiCustomScroll.selected >= 0 && guiCustomScroll.selected < validIndices.size()) { + selectedIndex = validIndices.get(guiCustomScroll.selected); + initGui(); + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 0 && selectedIndex >= 0) { + close(); + } + } + + public int getSelectedIndex() { + return selectedIndex; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiSimpleChoice.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiSimpleChoice.java new file mode 100644 index 000000000..1fb3e8fe8 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiSimpleChoice.java @@ -0,0 +1,23 @@ +package noppes.npcs.client.gui.advanced; + +import noppes.npcs.client.gui.util.SubGuiInterface; + +/** + * Base class for simple choice dialogs that present a few buttons and return an int result. + * Subclasses define their own layout in {@code initGui()} and call {@code setResult()} from {@code buttonEvent()}. + * The parent reads the result via {@code getResult()} in {@code subGuiClosed()}. + * + *

Result is -1 by default (cancelled / no selection).

+ */ +public abstract class SubGuiSimpleChoice extends SubGuiInterface { + + private int result = -1; + + protected final void setResult(int result) { + this.result = result; + } + + public int getResult() { + return result; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index 9378e606b..fef120604 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -1,11 +1,14 @@ package noppes.npcs.client.gui.global; -import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; -import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilitiesGetPacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilityGetPacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilityRemovePacket; +import kamkeel.npcs.network.packets.request.ability.ChainedAbilitySavePacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket; @@ -17,9 +20,9 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; import noppes.npcs.client.NoppesUtil; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; import noppes.npcs.client.gui.advanced.SubGuiAbilityTypeSelect; import noppes.npcs.client.gui.advanced.SubGuiAbilityVariantSelect; +import noppes.npcs.client.gui.advanced.SubGuiChainedAbilityConfig; import noppes.npcs.client.gui.advanced.SubGuiDuplicateNameConfirm; import noppes.npcs.client.gui.util.AbilityPreviewExecutor; import noppes.npcs.client.gui.util.GuiAbilityInterface; @@ -29,6 +32,7 @@ import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.GuiTexturedButton; import noppes.npcs.client.gui.util.IAbilityConfigCallback; +import noppes.npcs.client.gui.util.IChainedAbilityConfigCallback; import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IGuiData; import noppes.npcs.client.gui.util.IScrollData; @@ -55,7 +59,28 @@ import java.util.Vector; */ public class GuiNpcManageAbilities extends GuiAbilityInterface implements ICustomScrollListener, ISubGuiListener, IAbilityConfigCallback, - ITextfieldListener, GuiYesNoCallback, IScrollData, IGuiData { + IChainedAbilityConfigCallback, ITextfieldListener, GuiYesNoCallback, + IScrollData, IGuiData { + + // ── Button IDs ──────────────────────────────────────────────────────────── + private static final int BTN_REMOVE = 1; + private static final int BTN_ADD = 2; + private static final int BTN_TOGGLE_VIEW = 10; + private static final int BTN_EDIT = 100; + private static final int BTN_PREVIEW_PLAY = 91; + private static final int BTN_PREVIEW_PAUSE = 92; + private static final int BTN_PREVIEW_STOP = 93; + + // ── TextField / Label IDs ───────────────────────────────────────────────── + private static final int TF_SEARCH = 55; + private static final int LBL_PREVIEW_STATUS = 90; + + // ── Scroll IDs ──────────────────────────────────────────────────────────── + private static final int SCROLL_MAIN = 0; + + // ── Confirm dialog IDs ──────────────────────────────────────────────────── + private static final int CONFIRM_REMOVE_ABILITY = 1; + private static final int CONFIRM_REMOVE_CHAIN = 2; // ==================== DATA ==================== private GuiCustomScroll scroll; @@ -65,10 +90,18 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface private String search = ""; private Ability selectedAbility = null; - // ==================== BUILT-IN TOGGLE ==================== + // ==================== VIEW MODE ==================== + // 0 = Custom, 1 = Built-in, 2 = Chained + private int viewMode = 0; private boolean showingBuiltIn = false; + private boolean showingChained = false; private boolean currentIsBuiltIn = false; + // ==================== CHAINED DATA ==================== + private HashMap chainedData = new HashMap<>(); + private ChainedAbility selectedChain = null; + private ChainedAbility pendingSaveChain = null; + // ==================== PENDING SAVE ==================== private Ability pendingSaveAbility = null; private boolean pendingNewCreation = false; @@ -103,42 +136,49 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface public void initGui() { super.initGui(); - // Toggle button — added FIRST to maintain stable buttonList index - String toggleLabel = showingBuiltIn ? "gui.builtin" : "gui.custom"; - addButton(new GuiNpcButton(10, guiLeft + 368, guiTop + 52, 45, 20, toggleLabel)); + // Toggle button — cycles: Custom → Built-in → Chained + String toggleLabel = showingChained ? "gui.chained" : (showingBuiltIn ? "gui.builtin" : "gui.custom"); + addButton(new GuiNpcButton(BTN_TOGGLE_VIEW, guiLeft + 368, guiTop + 52, 45, 20, toggleLabel)); - if (!showingBuiltIn) { - // Add button — only for custom view - addButton(new GuiNpcButton(2, guiLeft + 368, guiTop + 8, 45, 20, "gui.add")); - - // Remove button — only for custom view - addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove")); - getButton(1).setEnabled(selected != null && !selected.isEmpty() && customData.containsKey(selected)); + if (showingChained) { + // Chained view buttons + addButton(new GuiNpcButton(BTN_ADD, guiLeft + 368, guiTop + 8, 45, 20, "gui.add")); + addButton(new GuiNpcButton(BTN_REMOVE, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove")); + getButton(BTN_REMOVE).setEnabled(selected != null && !selected.isEmpty() && chainedData.containsKey(selected)); + addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 368, guiTop + 74, 45, 20, "gui.edit")); + getButton(BTN_EDIT).setEnabled(selected != null && !selected.isEmpty() && selectedChain != null); + } else if (!showingBuiltIn) { + // Custom view buttons + addButton(new GuiNpcButton(BTN_ADD, guiLeft + 368, guiTop + 8, 45, 20, "gui.add")); + addButton(new GuiNpcButton(BTN_REMOVE, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove")); + getButton(BTN_REMOVE).setEnabled(selected != null && !selected.isEmpty() && customData.containsKey(selected)); + if (!currentIsBuiltIn) { + addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 368, guiTop + 74, 45, 20, "gui.edit")); + getButton(BTN_EDIT).setEnabled(selected != null && !selected.isEmpty() && selectedAbility != null); + } } - // Edit button — hidden when viewing built-in - if (!showingBuiltIn && !currentIsBuiltIn) { - addButton(new GuiNpcButton(100, guiLeft + 368, guiTop + 74, 45, 20, "gui.edit")); - getButton(100).setEnabled(selected != null && !selected.isEmpty() && selectedAbility != null); - } - - // Scroll list of abilities (right side) + // Scroll list (right side) if (scroll == null) { - scroll = new GuiCustomScroll(this, 0); + scroll = new GuiCustomScroll(this, SCROLL_MAIN); scroll.setSize(143, 185); } scroll.guiLeft = guiLeft + 220; scroll.guiTop = guiTop + 4; addScroll(scroll); - // Update scroll list scroll.setList(getSearchList()); if (selected != null) { scroll.setSelected(selected); } // Search bar - addTextField(new GuiNpcTextField(55, this, fontRendererObj, guiLeft + 220, guiTop + 192, 143, 20, search)); + addTextField(new GuiNpcTextField(TF_SEARCH, this, fontRendererObj, guiLeft + 220, guiTop + 192, 143, 20, search)); + + // Preview only for ability views (not chained) + if (showingChained) { + return; + } // Show ability info only if one is selected if (selectedAbility == null || selected == null) { @@ -154,22 +194,20 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface boolean isActive = previewExecutor.isActive(); if (!isPlaying || isPaused) { - // Show Play button String statusKey = isPaused ? "animation.paused" : "animation.stopped"; - addLabel(new GuiNpcLabel(90, statusKey, guiLeft + playButtonOffsetX, guiTop + 198)); - addButton(new GuiTexturedButton(91, "", guiLeft + playButtonOffsetX + 70, guiTop + 192, 11, 20, animTexture, 18, 71)); + addLabel(new GuiNpcLabel(LBL_PREVIEW_STATUS, statusKey, guiLeft + playButtonOffsetX, guiTop + 198)); + addButton(new GuiTexturedButton(BTN_PREVIEW_PLAY, "", guiLeft + playButtonOffsetX + 70, guiTop + 192, 11, 20, animTexture, 18, 71)); } else { - // Show Pause button - addLabel(new GuiNpcLabel(90, "animation.playing", guiLeft + playButtonOffsetX, guiTop + 198)); - addButton(new GuiTexturedButton(92, "", guiLeft + playButtonOffsetX + 70, guiTop + 192, 14, 20, animTexture, 0, 71)); + addLabel(new GuiNpcLabel(LBL_PREVIEW_STATUS, "animation.playing", guiLeft + playButtonOffsetX, guiTop + 198)); + addButton(new GuiTexturedButton(BTN_PREVIEW_PAUSE, "", guiLeft + playButtonOffsetX + 70, guiTop + 192, 14, 20, animTexture, 0, 71)); } if (isActive) { - // Show Stop button - addButton(new GuiTexturedButton(93, "", guiLeft + playButtonOffsetX + 90, guiTop + 192, 14, 20, animTexture, 33, 71)); + addButton(new GuiTexturedButton(BTN_PREVIEW_STOP, "", guiLeft + playButtonOffsetX + 90, guiTop + 192, 14, 20, animTexture, 33, 71)); } } private HashMap getCurrentData() { + if (showingChained) return chainedData; return showingBuiltIn ? builtInData : customData; } @@ -240,33 +278,64 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface int id = guibutton.id; - // Toggle between custom and built-in views - if (id == 10) { - showingBuiltIn = !showingBuiltIn; + // Toggle: Custom → Built-in → Chained → Custom + if (id == BTN_TOGGLE_VIEW) { + if (!showingBuiltIn && !showingChained) { + // Custom → Built-in + showingBuiltIn = true; + showingChained = false; + } else if (showingBuiltIn) { + // Built-in → Chained + showingBuiltIn = false; + showingChained = true; + } else { + // Chained → Custom + showingChained = false; + showingBuiltIn = false; + } selected = null; selectedAbility = null; + selectedChain = null; currentIsBuiltIn = false; search = ""; previewExecutor.stop(); if (scroll != null) { scroll.clear(); } - PacketClient.sendClient(new CustomAbilitiesGetPacket()); + if (showingChained) { + PacketClient.sendClient(new ChainedAbilitiesGetPacket()); + } else { + PacketClient.sendClient(new CustomAbilitiesGetPacket()); + } initGui(); return; } - if (id == 2 && !showingBuiltIn) { + if (showingChained) { + if (id == BTN_ADD) { + // New chained ability + ChainedAbility newChain = new ChainedAbility("New Chain"); + setSubGui(new SubGuiChainedAbilityConfig(newChain, this)); + } else if (id == BTN_REMOVE && selected != null) { + GuiYesNo guiyesno = new GuiYesNo(this, selected, StatCollector.translateToLocal("gui.delete"), CONFIRM_REMOVE_CHAIN); + displayGuiScreen(guiyesno); + } else if (id == BTN_EDIT && selectedChain != null) { + setSubGui(new SubGuiChainedAbilityConfig(selectedChain, this)); + } + return; + } + + if (id == BTN_ADD && !showingBuiltIn) { // Add — open type selection setSubGui(new SubGuiAbilityTypeSelect()); - } else if (id == 1 && !showingBuiltIn && selected != null) { - GuiYesNo guiyesno = new GuiYesNo(this, selected, StatCollector.translateToLocal("gui.delete"), 1); + } else if (id == BTN_REMOVE && !showingBuiltIn && selected != null) { + GuiYesNo guiyesno = new GuiYesNo(this, selected, StatCollector.translateToLocal("gui.delete"), CONFIRM_REMOVE_ABILITY); displayGuiScreen(guiyesno); - } else if (id == 100 && !showingBuiltIn && !currentIsBuiltIn && selectedAbility != null) { + } else if (id == BTN_EDIT && !showingBuiltIn && !currentIsBuiltIn && selectedAbility != null) { // Edit previewExecutor.stop(); setSubGui(selectedAbility.createConfigGui(this)); - } else if (id == 91) { + } else if (id == BTN_PREVIEW_PLAY) { // Play button if (selectedAbility != null) { if (previewExecutor.isPaused()) { @@ -276,11 +345,11 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface } initGui(); } - } else if (id == 92) { + } else if (id == BTN_PREVIEW_PAUSE) { // Pause button previewExecutor.pause(); initGui(); - } else if (id == 93) { + } else if (id == BTN_PREVIEW_STOP) { // Stop button previewExecutor.stop(); initGui(); @@ -290,9 +359,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface @Override public void keyTyped(char c, int i) { super.keyTyped(c, i); - if (getTextField(55) != null && getTextField(55).isFocused()) { - if (!search.equals(getTextField(55).getText())) { - search = getTextField(55).getText(); + if (getTextField(TF_SEARCH) != null && getTextField(TF_SEARCH).isFocused()) { + if (!search.equals(getTextField(TF_SEARCH).getText())) { + search = getTextField(TF_SEARCH).getText(); scroll.resetScroll(); scroll.setList(getSearchList()); } @@ -301,13 +370,15 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface @Override public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) { - if (scroll.id == 0) { + if (scroll.id == SCROLL_MAIN) { String newSelection = scroll.getSelected(); if (newSelection != null && !newSelection.equals(selected)) { previewExecutor.stop(); selected = newSelection; - if (showingBuiltIn) { + if (showingChained) { + PacketClient.sendClient(new ChainedAbilityGetPacket(selected)); + } else if (showingBuiltIn) { currentIsBuiltIn = true; PacketClient.sendClient(new BuiltInAbilityGetPacket(selected)); } else { @@ -320,9 +391,10 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface @Override public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { - if (scroll.id == 0 && selection != null && !selection.isEmpty()) { - // Double-click to edit — only for custom, non-built-in abilities - if (!showingBuiltIn && !currentIsBuiltIn && selectedAbility != null) { + if (scroll.id == SCROLL_MAIN && selection != null && !selection.isEmpty()) { + if (showingChained && selectedChain != null) { + setSubGui(new SubGuiChainedAbilityConfig(selectedChain, this)); + } else if (!showingBuiltIn && !showingChained && !currentIsBuiltIn && selectedAbility != null) { previewExecutor.stop(); setSubGui(selectedAbility.createConfigGui(this)); } @@ -334,7 +406,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface if (type == EnumScrollData.CUSTOM_ABILITIES) { String prevSelected = scroll != null ? scroll.getSelected() : null; this.customData = data; - if (!showingBuiltIn && scroll != null) { + if (!showingBuiltIn && !showingChained && scroll != null) { scroll.setList(getSearchList()); if (prevSelected != null && customData.containsKey(prevSelected)) { scroll.setSelected(prevSelected); @@ -350,11 +422,33 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface } } initGui(); + } else if (type == EnumScrollData.CHAINED_ABILITIES) { + String prevSelected = scroll != null ? scroll.getSelected() : null; + this.chainedData = data; + if (showingChained && scroll != null) { + scroll.setList(getSearchList()); + if (prevSelected != null && chainedData.containsKey(prevSelected)) { + scroll.setSelected(prevSelected); + } + } + initGui(); } } @Override public void setGuiData(NBTTagCompound compound) { + // Detect chained ability response (has "Entries" tag) + if (showingChained && compound.hasKey("Entries")) { + selectedChain = new ChainedAbility(); + selectedChain.readNBT(compound); + selected = selectedChain.getName(); + if (scroll != null) { + scroll.setSelected(selected); + } + initGui(); + return; + } + // Check if this is a built-in ability response currentIsBuiltIn = compound.hasKey("BuiltIn") && compound.getBoolean("BuiltIn"); @@ -380,88 +474,112 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface @Override public void subGuiClosed(SubGuiInterface subgui) { - if (subgui instanceof SubGuiAbilityVariantSelect) { - SubGuiAbilityVariantSelect variantGui = (SubGuiAbilityVariantSelect) subgui; - int idx = variantGui.getSelectedIndex(); - if (idx >= 0 && pendingTypeId != null) { - Ability newAbility = AbilityController.Instance.create(pendingTypeId); - if (newAbility != null) { - variantGui.getVariants().get(idx).apply(newAbility); - if (hasDuplicateName(newAbility)) { - pendingSaveAbility = newAbility; - pendingNewCreation = true; - pendingTypeId = null; - setSubGui(new SubGuiDuplicateNameConfirm()); - return; - } + if (subgui instanceof SubGuiChainedAbilityConfig) { handleChainConfigClosed(); initGui(); return; } + if (subgui instanceof SubGuiAbilityVariantSelect) { if (handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui)) return; } + else if (subgui instanceof SubGuiAbilityTypeSelect) { if (handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui)) return; } + else if (subgui instanceof SubGuiDuplicateNameConfirm) { if (handleDuplicateNameClosed((SubGuiDuplicateNameConfirm) subgui)) return; } + else if (pendingSaveAbility != null) { if (handlePendingSave()) return; } + initGui(); + } + + private void handleChainConfigClosed() { + if (pendingSaveChain != null) { + PacketClient.sendClient(new ChainedAbilitySavePacket(pendingSaveChain.writeNBT())); + PacketClient.sendClient(new ChainedAbilitiesGetPacket()); + selected = pendingSaveChain.getName(); + pendingSaveChain = null; + } + } + + /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + private boolean handleVariantSelectClosed(SubGuiAbilityVariantSelect gui) { + int idx = gui.getSelectedIndex(); + if (idx >= 0 && pendingTypeId != null) { + Ability newAbility = AbilityController.Instance.create(pendingTypeId); + if (newAbility != null) { + gui.getVariants().get(idx).apply(newAbility); + if (hasDuplicateName(newAbility)) { + pendingSaveAbility = newAbility; + pendingNewCreation = true; pendingTypeId = null; - openConfig(newAbility); - return; + setSubGui(new SubGuiDuplicateNameConfirm()); + return true; } + pendingTypeId = null; + openConfig(newAbility); + return true; } - pendingTypeId = null; - } else if (subgui instanceof SubGuiAbilityTypeSelect) { - String typeId = ((SubGuiAbilityTypeSelect) subgui).getSelectedTypeId(); - if (typeId != null) { - java.util.List variants = AbilityController.Instance.getVariantsForType(typeId); - if (variants.size() > 1) { - pendingTypeId = typeId; - setSubGui(new SubGuiAbilityVariantSelect(variants)); - return; - } - Ability newAbility = AbilityController.Instance.create(typeId); - if (newAbility != null) { - if (variants.size() == 1) { - variants.get(0).apply(newAbility); - } - if (hasDuplicateName(newAbility)) { - pendingSaveAbility = newAbility; - pendingNewCreation = true; - setSubGui(new SubGuiDuplicateNameConfirm()); - return; - } - openConfig(newAbility); - return; - } + } + pendingTypeId = null; + return false; + } + + /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + private boolean handleTypeSelectClosed(SubGuiAbilityTypeSelect gui) { + String typeId = gui.getSelectedTypeId(); + if (typeId != null) { + java.util.List variants = AbilityController.Instance.getVariantsForType(typeId); + if (variants.size() > 1) { + pendingTypeId = typeId; + setSubGui(new SubGuiAbilityVariantSelect(variants)); + return true; } - } else if (subgui instanceof SubGuiDuplicateNameConfirm) { - SubGuiDuplicateNameConfirm confirm = (SubGuiDuplicateNameConfirm) subgui; - if (confirm.isConfirmed() && pendingSaveAbility != null) { - if (pendingNewCreation) { - openConfig(pendingSaveAbility); - pendingSaveAbility = null; - pendingNewCreation = false; - return; - } else { - PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); - PacketClient.sendClient(new CustomAbilitiesGetPacket()); - pendingSaveAbility = null; + Ability newAbility = AbilityController.Instance.create(typeId); + if (newAbility != null) { + if (variants.size() == 1) { + variants.get(0).apply(newAbility); } - } else if (pendingSaveAbility != null) { - if (pendingNewCreation) { - // Cancel new creation - discard - pendingSaveAbility = null; - pendingNewCreation = false; - } else { - // Cancel edit save - re-open config GUI to change name - Ability ability = pendingSaveAbility; - pendingSaveAbility = null; - setSubGui(ability.createConfigGui(this)); - return; + if (hasDuplicateName(newAbility)) { + pendingSaveAbility = newAbility; + pendingNewCreation = true; + setSubGui(new SubGuiDuplicateNameConfirm()); + return true; } + openConfig(newAbility); + return true; + } + } + return false; + } + + /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + private boolean handleDuplicateNameClosed(SubGuiDuplicateNameConfirm gui) { + if (gui.isConfirmed() && pendingSaveAbility != null) { + if (pendingNewCreation) { + openConfig(pendingSaveAbility); + pendingSaveAbility = null; + pendingNewCreation = false; + return true; + } else { + PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); + PacketClient.sendClient(new CustomAbilitiesGetPacket()); + pendingSaveAbility = null; } } else if (pendingSaveAbility != null) { - // Config GUI closed with a pending save (set by onAbilitySaved) - if (hasDuplicateName(pendingSaveAbility)) { + if (pendingNewCreation) { + pendingSaveAbility = null; pendingNewCreation = false; - setSubGui(new SubGuiDuplicateNameConfirm()); - return; + } else { + Ability ability = pendingSaveAbility; + pendingSaveAbility = null; + setSubGui(ability.createConfigGui(this)); + return true; } - PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); - PacketClient.sendClient(new CustomAbilitiesGetPacket()); - pendingSaveAbility = null; } - initGui(); + return false; + } + + /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + private boolean handlePendingSave() { + if (hasDuplicateName(pendingSaveAbility)) { + pendingNewCreation = false; + setSubGui(new SubGuiDuplicateNameConfirm()); + return true; + } + PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); + PacketClient.sendClient(new CustomAbilitiesGetPacket()); + pendingSaveAbility = null; + return false; } private void openConfig(Ability ability) { @@ -487,12 +605,18 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface NoppesUtil.openGUI(player, this); if (!result) return; - if (id == 1 && selected != null) { + if (id == CONFIRM_REMOVE_ABILITY && selected != null) { PacketClient.sendClient(new CustomAbilityRemovePacket(selected)); scroll.clear(); selected = null; selectedAbility = null; initGui(); + } else if (id == CONFIRM_REMOVE_CHAIN && selected != null) { + PacketClient.sendClient(new ChainedAbilityRemovePacket(selected)); + scroll.clear(); + selected = null; + selectedChain = null; + initGui(); } } @@ -502,6 +626,11 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface pendingSaveAbility = ability; } + @Override + public void onChainedAbilitySaved(ChainedAbility chain) { + pendingSaveChain = chain; + } + @Override public void unFocused(GuiNpcTextField textfield) { // Search field handling is done in keyTyped diff --git a/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java b/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java new file mode 100644 index 000000000..f714ea2c7 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java @@ -0,0 +1,10 @@ +package noppes.npcs.client.gui.util; + +import kamkeel.npcs.controllers.data.ability.ChainedAbility; + +/** + * Callback for when a chained ability is saved from the config SubGui. + */ +public interface IChainedAbilityConfigCallback { + void onChainedAbilitySaved(ChainedAbility chain); +} diff --git a/src/main/java/noppes/npcs/constants/EnumScrollData.java b/src/main/java/noppes/npcs/constants/EnumScrollData.java index cbec6ad34..3f680c188 100644 --- a/src/main/java/noppes/npcs/constants/EnumScrollData.java +++ b/src/main/java/noppes/npcs/constants/EnumScrollData.java @@ -10,5 +10,6 @@ public enum EnumScrollData { CUSTOM_ABILITIES, BUILTIN_ABILITIES, ANIMATIONS, - BUILTIN_ANIMATIONS + BUILTIN_ANIMATIONS, + CHAINED_ABILITIES } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 1a1751a30..d3add842c 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -1,10 +1,9 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityController; import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -22,9 +21,7 @@ import noppes.npcs.LogWriter; import noppes.npcs.EventHooks; import noppes.npcs.api.ability.IPlayerAbilityData; import noppes.npcs.api.entity.IPlayer; -import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.ScriptController; -import noppes.npcs.controllers.data.Animation; import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.event.player.PlayerAbilityEvent; @@ -322,16 +319,14 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA */ private IAbilityAction resolveActionKey(String key) { if (key.startsWith(CHAIN_PREFIX)) { - if (ChainedAbilityController.Instance == null) return null; - return ChainedAbilityController.Instance.resolve(key.substring(CHAIN_PREFIX.length())); + return AbilityController.Instance.resolveChainedAbility(key.substring(CHAIN_PREFIX.length())); } - if (AbilityController.Instance == null) return null; return AbilityController.Instance.resolveAbility(key); } /** * Activate a specific ability by key. Keys prefixed with "chain:" are - * treated as chained abilities and resolved from {@link ChainedAbilityController}. + * treated as chained abilities and resolved from {@link AbilityController}. * * @param player The player * @param key The ability key (built-in or preset name), or "chain:name" for chains diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index 02ec0f4b3..d1d041c8e 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -6,7 +6,7 @@ import foxz.command.ScriptedCommand; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.TelegraphController; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; import kamkeel.npcs.entity.EntityAbilityLaser; @@ -480,6 +480,8 @@ public class NpcAPI extends AbstractNpcAPI { data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyDome<>((kamkeel.npcs.entity.EntityEnergyDome) entity)); else if (entity instanceof kamkeel.npcs.entity.EntityEnergyPanel) data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergyPanel<>((kamkeel.npcs.entity.EntityEnergyPanel) entity)); + else if (entity instanceof kamkeel.npcs.entity.EntityAbilitySweeper) + data = new ScriptEntityData(new noppes.npcs.scripted.entity.ScriptEnergySweeper<>((kamkeel.npcs.entity.EntityAbilitySweeper) entity)); else data = new ScriptEntityData(new ScriptEntity<>(entity)); entity.registerExtendedProperties("ScriptedObject", data); diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySweeper.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySweeper.java new file mode 100644 index 000000000..cf957c062 --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySweeper.java @@ -0,0 +1,9 @@ +package noppes.npcs.scripted.entity; + +import kamkeel.npcs.entity.EntityAbilitySweeper; + +public class ScriptEnergySweeper extends ScriptEnergyAbility { + public ScriptEnergySweeper(T entity) { + super(entity); + } +} diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java index e08055018..f6b9379c1 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java @@ -1,7 +1,7 @@ package noppes.npcs.scripted.wrapper; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityAction; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.DataAbilities; @@ -90,7 +90,7 @@ public class ScriptDataAbilities implements IDataAbilities { if (slot.getReferenceId().equals(abilityId)) return true; } else if (!slot.isReference()) { Ability a = slot.getAbility(); - if (a != null && a.getId().equals(abilityId)) return false; + if (a != null && abilityId.equals(a.getId())) return false; } } return false; @@ -135,10 +135,11 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public void setGlobalCooldown(int ticks) { - // Set the cooldown by manipulating the internal cooldown end time - // Not directly exposed, but we can reset and let it tick down - data.resetCooldown(); - // This is a best-effort implementation + if (ticks <= 0) { + data.resetCooldown(); + } else { + data.setCooldownEndTime(npc.worldObj.getTotalWorldTime() + ticks); + } } @Override diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java index 42ed3372e..8d3d068d5 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java @@ -1,7 +1,7 @@ package noppes.npcs.scripted.wrapper; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.AbilityController; import net.minecraft.entity.player.EntityPlayer; import noppes.npcs.api.ability.IAbility; import noppes.npcs.api.ability.IPlayerAbilityData; @@ -43,12 +43,14 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { @Override public void lockAbility(String key) { - data.lockAbility(key); + String canonicalKey = resolveCanonicalKey(key); + data.lockAbility(canonicalKey); } @Override public boolean hasUnlockedAbility(String key) { - return data.hasUnlockedAbility(key); + String canonicalKey = resolveCanonicalKey(key); + return data.hasUnlockedAbility(canonicalKey); } @Override @@ -131,4 +133,16 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { if (player == null) return false; return data.activateAbility(player, key); } + + /** + * Resolve a user-provided key to the canonical storage key. + * Uses the same resolution as unlockAbility: registry key for built-in, UUID for custom. + */ + private String resolveCanonicalKey(String key) { + Ability ability = AbilityController.Instance.resolveAbility(key); + if (ability != null && ability.getId() != null) { + return ability.getId(); + } + return key; + } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 347997210..51873686d 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -122,6 +122,7 @@ gui.copy=Copy gui.clear=Clear gui.builtin=Built-in gui.custom=Custom +gui.chained=Chained gui.builtinAnimations=Built-in Animations gui.customAnimations=Custom Animations gui.done=Done @@ -1688,6 +1689,13 @@ ability.minRange=Min Range ability.maxRange=Max Range ability.conditions=Conditions +# Chained Ability +ability.select=Select Ability +ability.entries=Entries +ability.delay=Delay +ability.windUpAll=Wind Up All +ability.allowedBy=Allowed By + # Effects Tab ability.windUpSound=Wind Up Sound ability.activeSound=Active Sound @@ -2030,6 +2038,16 @@ ability.variant.large=Large ability.variant.static=Static ability.variant.launched=Launched +ability.addChain=Chain +ability.entrySource=Add Entry Source +ability.fromNpc=From NPC +ability.byReference=By Reference +ability.createNew=Create New +ability.selectSlot=Select NPC Ability +ability.loadType=Load Type +ability.loadAbility=Load Ability +ability.loadChain=Load Chain + gui.both=Both gui.players=Players From 4510b3bbd1086e3f618d948bc9ca480aada3473c Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 04:55:03 -0500 Subject: [PATCH 04/12] Large Auction Cleanup + Auction Events - Auction Enums - Better Notification Handling - Auction Cancelation Events - Lang Key Cleanup --- src/api | 2 +- .../kamkeel/npcs/command/AuctionCommand.java | 10 +- .../packets/player/AuctionActionPacket.java | 13 +- src/main/java/noppes/npcs/EventHooks.java | 28 + .../npcs/client/AuctionClientConfig.java | 6 +- .../gui/player/AuctionTooltipHandler.java | 69 +- .../client/gui/player/GuiAuctionBidding.java | 19 +- .../gui/player/GuiAuctionInterface.java | 38 +- .../client/gui/player/GuiAuctionListing.java | 5 +- .../client/gui/player/GuiAuctionSell.java | 8 +- .../client/gui/player/SubGuiAuctionBid.java | 20 +- .../gui/player/SubGuiAuctionBuyNow.java | 18 +- .../java/noppes/npcs/config/ConfigMarket.java | 33 +- .../npcs/constants/EnumAuctionLogAction.java | 34 + .../npcs/constants/EnumAuctionPage.java | 30 + .../noppes/npcs/constants/EnumScriptType.java | 9 +- .../npcs/constants/EnumTradeSlotType.java | 35 + .../noppes/npcs/constants/ScriptContext.java | 7 +- .../containers/ContainerAuctionBidding.java | 10 +- .../npcs/containers/ContainerAuctionSell.java | 18 +- .../containers/ContainerAuctionTrades.java | 38 +- .../npcs/controllers/AuctionConfigSync.java | 2 +- .../npcs/controllers/AuctionController.java | 611 ++++++++++-------- .../controllers/ScriptHookController.java | 21 + .../controllers/data/AuctionBlacklist.java | 79 ++- .../npcs/controllers/data/AuctionClaim.java | 2 +- .../npcs/controllers/data/AuctionFilter.java | 11 +- .../controllers/data/AuctionNotification.java | 64 -- .../controllers/data/PlayerTradeData.java | 28 +- .../scripted/event/player/AuctionEvent.java | 131 ++++ .../noppes/npcs/util/AuctionFormatUtil.java | 68 ++ 31 files changed, 889 insertions(+), 578 deletions(-) create mode 100644 src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java create mode 100644 src/main/java/noppes/npcs/constants/EnumAuctionPage.java create mode 100644 src/main/java/noppes/npcs/constants/EnumTradeSlotType.java delete mode 100644 src/main/java/noppes/npcs/controllers/data/AuctionNotification.java create mode 100644 src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java create mode 100644 src/main/java/noppes/npcs/util/AuctionFormatUtil.java diff --git a/src/api b/src/api index 28c0ca840..ae4b84f3b 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 28c0ca840f3d726c07b47aafa29d97b51a69a0cd +Subproject commit ae4b84f3baf19c2998449e4b40ac33bc6a92555f diff --git a/src/main/java/kamkeel/npcs/command/AuctionCommand.java b/src/main/java/kamkeel/npcs/command/AuctionCommand.java index 87d7e4385..a56dce671 100644 --- a/src/main/java/kamkeel/npcs/command/AuctionCommand.java +++ b/src/main/java/kamkeel/npcs/command/AuctionCommand.java @@ -20,6 +20,7 @@ import static kamkeel.npcs.util.ColorUtil.sendMessage; import static kamkeel.npcs.util.ColorUtil.sendResult; public class AuctionCommand extends CommandKamkeelBase { + private static final int COMMAND_PAGE_SIZE = 10; public AuctionCommand() { super(); @@ -62,9 +63,10 @@ public class AuctionCommand extends CommandKamkeelBase { } } - List listings = controller.getActiveListings(null, page - 1, 10); + page = Math.max(1, page); + List listings = controller.getActiveListings(null, page - 1, COMMAND_PAGE_SIZE); int total = controller.getTotalActiveListings(null); - int totalPages = Math.max(1, (int) Math.ceil((double) total / 10)); + int totalPages = Math.max(1, (int) Math.ceil((double) total / COMMAND_PAGE_SIZE)); if (listings.isEmpty()) { sendMessage(sender, "\u00A77No active auctions found"); @@ -98,6 +100,10 @@ public class AuctionCommand extends CommandKamkeelBase { return; } + if (args.length < 1) { + throw new CommandException("Usage: /auction view "); + } + String playername = args[0]; List dataList = PlayerDataController.Instance.getPlayersData(sender, playername); diff --git a/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java b/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java index 3ce74eb4c..b15ff8992 100644 --- a/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java @@ -17,6 +17,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import noppes.npcs.NoppesUtilServer; import noppes.npcs.config.ConfigMarket; +import noppes.npcs.constants.EnumAuctionPage; import noppes.npcs.constants.EnumGuiType; import noppes.npcs.constants.EnumRoleType; import noppes.npcs.controllers.AuctionController; @@ -315,13 +316,8 @@ public class AuctionActionPacket extends AbstractPacket { private void handleOpenPage(ByteBuf in, EntityPlayerMP player) { int page = in.readInt(); - EnumGuiType guiType; - switch (page) { - case 0: guiType = EnumGuiType.PlayerAuction; break; - case 1: guiType = EnumGuiType.PlayerAuctionSell; break; - case 2: guiType = EnumGuiType.PlayerAuctionTrades; break; - default: guiType = EnumGuiType.PlayerAuction; break; - } + EnumAuctionPage auctionPage = EnumAuctionPage.fromOrdinal(page); + EnumGuiType guiType = auctionPage.getGuiType(); NoppesUtilServer.sendOpenGui(player, guiType, npc); // Send trades data when opening the trades page @@ -481,6 +477,9 @@ public class AuctionActionPacket extends AbstractPacket { } else { // Need only part of stack stack.splitStack(needed); + if (stack.stackSize <= 0) { + player.inventory.setInventorySlotContents(i, null); + } needed = 0; } } diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java index 2fefb23ac..2d2496fc6 100644 --- a/src/main/java/noppes/npcs/EventHooks.java +++ b/src/main/java/noppes/npcs/EventHooks.java @@ -67,6 +67,7 @@ import noppes.npcs.scripted.event.RecipeScriptEvent; import noppes.npcs.scripted.event.player.CustomGuiEvent; import noppes.npcs.scripted.event.player.DialogEvent; import noppes.npcs.scripted.event.player.FactionEvent; +import noppes.npcs.scripted.event.player.AuctionEvent; import noppes.npcs.scripted.event.player.PlayerAbilityEvent; import noppes.npcs.scripted.event.player.PlayerEvent; import noppes.npcs.scripted.event.player.PlayerEvent.ChatEvent; @@ -1253,4 +1254,31 @@ public class EventHooks { } postAnimationEvent(new AnimationEvent.FrameEvent.Exited(animation, frame)); } + + // ==================== AUCTION EVENTS ==================== + + public static boolean onAuctionCreate(PlayerDataScript handler, AuctionEvent.CreateEvent event) { + handler.callScript(EnumScriptType.AUCTION_CREATE, event); + return NpcAPI.EVENT_BUS.post(event); + } + + public static boolean onAuctionBid(PlayerDataScript handler, AuctionEvent.BidEvent event) { + handler.callScript(EnumScriptType.AUCTION_BID, event); + return NpcAPI.EVENT_BUS.post(event); + } + + public static boolean onAuctionBuyout(PlayerDataScript handler, AuctionEvent.BuyoutEvent event) { + handler.callScript(EnumScriptType.AUCTION_BUYOUT, event); + return NpcAPI.EVENT_BUS.post(event); + } + + public static boolean onAuctionCancel(PlayerDataScript handler, AuctionEvent.CancelEvent event) { + handler.callScript(EnumScriptType.AUCTION_CANCEL, event); + return NpcAPI.EVENT_BUS.post(event); + } + + public static boolean onAuctionClaim(PlayerDataScript handler, AuctionEvent.ClaimEvent event) { + handler.callScript(EnumScriptType.AUCTION_CLAIM, event); + return NpcAPI.EVENT_BUS.post(event); + } } diff --git a/src/main/java/noppes/npcs/client/AuctionClientConfig.java b/src/main/java/noppes/npcs/client/AuctionClientConfig.java index 7d6854e4c..f993318ed 100644 --- a/src/main/java/noppes/npcs/client/AuctionClientConfig.java +++ b/src/main/java/noppes/npcs/client/AuctionClientConfig.java @@ -14,10 +14,10 @@ import net.minecraft.nbt.NBTTagCompound; @SideOnly(Side.CLIENT) public class AuctionClientConfig { // Cached values from server - private static long listingFee = 0; + private static volatile long listingFee = 0; private static String currencyName = "Coins"; private static int auctionDurationHours = 24; - private static double minBidIncrement = 0.05; + private static volatile double minBidIncrement = 0.05; private static int maxActiveListings = 8; private static int claimExpirationDays = 20; private static boolean auctionEnabled = false; @@ -38,7 +38,7 @@ public class AuctionClientConfig { claimExpirationDays = compound.getInteger("ClaimExpirationDays"); if (currencyName.isEmpty()) { - currencyName = "Gold"; + currencyName = "Coins"; } } diff --git a/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java b/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java index 11ffa198d..a9473f2e9 100644 --- a/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java +++ b/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java @@ -12,6 +12,7 @@ import net.minecraft.util.StatCollector; import net.minecraftforge.event.entity.player.ItemTooltipEvent; import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.constants.EnumClaimType; +import noppes.npcs.util.AuctionFormatUtil; import noppes.npcs.containers.ContainerAuctionListing; import noppes.npcs.containers.ContainerAuctionSell; import noppes.npcs.containers.ContainerAuctionTrades; @@ -88,25 +89,25 @@ public class AuctionTooltipHandler { // Current bid or starting price if (listing.hasBids()) { tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.currentBid") - .replace("%s", EnumChatFormatting.GOLD + formatCurrency(listing.currentBid))); + .replace("%s", EnumChatFormatting.GOLD + AuctionFormatUtil.formatCurrencyWithName(listing.currentBid))); tooltip.add(EnumChatFormatting.GRAY + String.format(StatCollector.translateToLocal("auction.bids"), listing.bidCount)); } else { tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.startingPrice") - .replace("%s", EnumChatFormatting.GOLD + formatCurrency(listing.startingPrice))); + .replace("%s", EnumChatFormatting.GOLD + AuctionFormatUtil.formatCurrencyWithName(listing.startingPrice))); tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.noBids")); } // Buyout price if (listing.hasBuyout()) { tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.buyout") - .replace("%s", EnumChatFormatting.GREEN + formatCurrency(listing.buyoutPrice))); + .replace("%s", EnumChatFormatting.GREEN + AuctionFormatUtil.formatCurrencyWithName(listing.buyoutPrice))); } // Time remaining long timeRemaining = listing.getTimeRemaining(); String timeText = StatCollector.translateToLocal("auction.timeLeft") - .replace("%s", formatTimeRemaining(timeRemaining)); - tooltip.add((timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText); + .replace("%s", AuctionFormatUtil.formatTimeRemaining(timeRemaining)); + tooltip.add((AuctionFormatUtil.isTimeUrgent(timeRemaining) ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText); } // ========== Trades Page ========== @@ -144,7 +145,7 @@ public class AuctionTooltipHandler { if (listing.hasBids()) { tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.currentBid") - .replace("%s", EnumChatFormatting.GOLD + formatCurrency(listing.currentBid))); + .replace("%s", EnumChatFormatting.GOLD + AuctionFormatUtil.formatCurrencyWithName(listing.currentBid))); tooltip.add(EnumChatFormatting.GRAY + String.format(StatCollector.translateToLocal("auction.bids"), listing.bidCount)); } else { tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.noBids")); @@ -152,8 +153,8 @@ public class AuctionTooltipHandler { long timeRemaining = listing.getTimeRemaining(); String timeText = StatCollector.translateToLocal("auction.timeLeft") - .replace("%s", formatTimeRemaining(timeRemaining)); - tooltip.add((timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText); + .replace("%s", AuctionFormatUtil.formatTimeRemaining(timeRemaining)); + tooltip.add((AuctionFormatUtil.isTimeUrgent(timeRemaining) ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText); tooltip.add(""); tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.rightClickToCancel")); @@ -164,13 +165,13 @@ public class AuctionTooltipHandler { tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.bidding")); tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.currentBid") - .replace("%s", EnumChatFormatting.GOLD + formatCurrency(listing.currentBid))); + .replace("%s", EnumChatFormatting.GOLD + AuctionFormatUtil.formatCurrencyWithName(listing.currentBid))); tooltip.add(EnumChatFormatting.GRAY + String.format(StatCollector.translateToLocal("auction.bids"), listing.bidCount)); long timeRemaining = listing.getTimeRemaining(); String timeText = StatCollector.translateToLocal("auction.timeLeft") - .replace("%s", formatTimeRemaining(timeRemaining)); - tooltip.add((timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText); + .replace("%s", AuctionFormatUtil.formatTimeRemaining(timeRemaining)); + tooltip.add((AuctionFormatUtil.isTimeUrgent(timeRemaining) ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText); tooltip.add(""); tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.clickToRebid")); @@ -210,7 +211,7 @@ public class AuctionTooltipHandler { // Amount for currency claims if (claim.type.isCurrency()) { tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.claim.amount") - .replace("%s", EnumChatFormatting.GOLD + formatCurrency(claim.currency))); + .replace("%s", EnumChatFormatting.GOLD + AuctionFormatUtil.formatCurrencyWithName(claim.currency))); } // Expiration @@ -254,48 +255,4 @@ public class AuctionTooltipHandler { } } - // ========== Formatting Utilities ========== - - private static String formatCurrency(long amount) { - if (amount < 1000) { - return amount + " " + AuctionClientConfig.getCurrencyName(); - } - StringBuilder sb = new StringBuilder(); - String str = "" + amount; - int count = 0; - for (int i = str.length() - 1; i >= 0; i--) { - if (count > 0 && count % 3 == 0) { - sb.insert(0, ','); - } - sb.insert(0, str.charAt(i)); - count++; - } - return sb.toString() + " " + AuctionClientConfig.getCurrencyName(); - } - - private static String formatTimeRemaining(long milliseconds) { - if (milliseconds <= 0) { - return StatCollector.translateToLocal("auction.ended"); - } - - long seconds = (milliseconds / 1000) % 60; - long minutes = (milliseconds / 60000) % 60; - long hours = (milliseconds / 3600000) % 24; - long days = milliseconds / 86400000; - - StringBuilder sb = new StringBuilder(); - if (days > 0) { - sb.append(days).append("d "); - } - if (hours > 0 || days > 0) { - sb.append(hours).append("h "); - } - if (minutes > 0 || hours > 0 || days > 0) { - sb.append(minutes).append("m"); - } else { - sb.append(seconds).append("s"); - } - - return sb.toString(); - } } diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java index afe47fb44..f74a354df 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java @@ -15,6 +15,7 @@ import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.containers.ContainerAuctionBidding; import noppes.npcs.controllers.data.AuctionListing; import noppes.npcs.entity.EntityNPCInterface; +import noppes.npcs.util.AuctionFormatUtil; import org.lwjgl.opengl.GL11; import java.util.ArrayList; @@ -36,7 +37,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis private int btnBidY = 110; private int btnBuyoutX = 82; private int btnBuyoutY = 110; - private int infoX = 110; + private int infoX = 105; private int infoY = 50; // Button IDs @@ -186,7 +187,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis /** Draw auction listing information on the right side */ private void drawAuctionInfo() { - int x = guiLeft + 105; + int x = guiLeft + infoX; int y = guiTop + infoY; int xRight = guiLeft + 215; @@ -206,7 +207,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis String bidLabel = StatCollector.translateToLocal("auction.info.currentBid") + ":"; fontRendererObj.drawString(EnumChatFormatting.YELLOW + bidLabel, x, y, 0xFFFFFF); - label = formatCurrency(listing.currentBid); + label = AuctionFormatUtil.formatCurrency(listing.currentBid); length = fontRendererObj.getStringWidth(label); fontRendererObj.drawString(EnumChatFormatting.GOLD + label, xRight - length, y, 0xFFFFFF); y += lineHeight + 4; @@ -223,7 +224,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis String startLabel = StatCollector.translateToLocal("auction.info.startingPrice"); fontRendererObj.drawString(EnumChatFormatting.YELLOW + startLabel, x, y, 0xFFFFFF); - label = formatCurrency(listing.startingPrice); + label = AuctionFormatUtil.formatCurrency(listing.startingPrice); length = fontRendererObj.getStringWidth(label); fontRendererObj.drawString(EnumChatFormatting.GOLD + label, xRight - length, y, 0xFFFFFF); y += lineHeight + 4; @@ -234,7 +235,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis String buyoutLabel = StatCollector.translateToLocal("auction.info.buyoutPrice"); fontRendererObj.drawString(EnumChatFormatting.GREEN + buyoutLabel, x, y, 0xFFFFFF); - label = formatCurrency(listing.buyoutPrice); + label = AuctionFormatUtil.formatCurrency(listing.buyoutPrice); length = fontRendererObj.getStringWidth(label); fontRendererObj.drawString(EnumChatFormatting.GREEN + label, xRight - length, y, 0xFFFFFF); y += lineHeight + 4; @@ -244,9 +245,9 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis String timeLabel = StatCollector.translateToLocal("auction.info.timeLeft"); fontRendererObj.drawString(EnumChatFormatting.GRAY + timeLabel, x, y, 0xFFFFFF); long timeRemaining = listing.getTimeRemaining(); - EnumChatFormatting timeColor = timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE; + EnumChatFormatting timeColor = AuctionFormatUtil.isTimeUrgent(timeRemaining) ? EnumChatFormatting.RED : EnumChatFormatting.WHITE; - label = timeColor + formatTimeRemaining(timeRemaining); + label = timeColor + AuctionFormatUtil.formatTimeRemaining(timeRemaining); length = fontRendererObj.getStringWidth(label); fontRendererObj.drawString(label, xRight - length, y, 0xFFFFFF); y += lineHeight + 4; @@ -278,14 +279,14 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis if (listing != null) { long minBid = listing.getMinimumBid(AuctionClientConfig.getMinBidIncrement()); tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.bid.minimum") - .replace("%s", EnumChatFormatting.WHITE + formatCurrency(minBid) + " " + AuctionClientConfig.getCurrencyName())); + .replace("%s", EnumChatFormatting.WHITE + AuctionFormatUtil.formatCurrency(minBid) + " " + AuctionClientConfig.getCurrencyName())); } } else if (btnBuyout != null && btnBuyout.visible && btnBuyout.isHovered()) { tooltip = new ArrayList<>(); tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.bid.buyout")); if (listing != null && listing.hasBuyout()) { tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.info.buyoutPrice") + ":"); - tooltip.add(EnumChatFormatting.GREEN + formatCurrency(listing.buyoutPrice) + " " + AuctionClientConfig.getCurrencyName()); + tooltip.add(EnumChatFormatting.GREEN + AuctionFormatUtil.formatCurrency(listing.buyoutPrice) + " " + AuctionClientConfig.getCurrencyName()); } } diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java index c34223550..e6e87383a 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java @@ -34,6 +34,9 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { public static final int PAGE_SELL = 1; public static final int PAGE_CLAIMS = 2; + // Overlay color + protected static final int OVERLAY_DARK = 0xC0000000; + // Navigation button IDs protected static final int BTN_NAV_LISTINGS = 100; protected static final int BTN_NAV_SELL = 101; @@ -172,7 +175,7 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { /** Draw dark overlay for unavailable slots */ protected void drawDarkenedOverlay(int x, int y) { - drawColoredOverlay(x, y, 0xC0000000); + drawColoredOverlay(x, y, OVERLAY_DARK); } /** Draw icon texture on slot (16x16) */ @@ -192,39 +195,6 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { /** Subclasses implement this to draw specific content */ protected abstract void drawAuctionContent(float partialTicks, int mouseX, int mouseY); - // ========== Formatting Utilities ========== - - /** Format currency with commas (e.g., 1000 -> "1,000") */ - protected String formatCurrency(long amount) { - if (amount < 1000) return "" + amount; - StringBuilder sb = new StringBuilder(); - String str = "" + amount; - int count = 0; - for (int i = str.length() - 1; i >= 0; i--) { - if (count > 0 && count % 3 == 0) sb.insert(0, ','); - sb.insert(0, str.charAt(i)); - count++; - } - return sb.toString(); - } - - /** Format time remaining (e.g., "2d 5h 30m") */ - protected String formatTimeRemaining(long ms) { - if (ms <= 0) return "Ended"; - - long seconds = (ms / 1000) % 60; - long minutes = (ms / 60000) % 60; - long hours = (ms / 3600000) % 24; - long days = ms / 86400000; - - StringBuilder sb = new StringBuilder(); - if (days > 0) sb.append(days).append("d "); - if (hours > 0 || days > 0) sb.append(hours).append("h "); - if (minutes > 0 || hours > 0 || days > 0) sb.append(minutes).append("m"); - else sb.append(seconds).append("s"); - return sb.toString(); - } - @Override public void save() { // Override in subclasses if needed diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java index 381747c6a..c08569598 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java @@ -58,8 +58,6 @@ public class GuiAuctionListing extends GuiAuctionInterface implements ISubGuiLis private AuctionFilter filter; private int currentPage = 0; private int totalPages = 1; - private int totalListings = 0; - private boolean dataLoaded = false; // Buttons defined on this page private GuiAuctionNavButton btnFilter; @@ -204,7 +202,6 @@ public class GuiAuctionListing extends GuiAuctionInterface implements ISubGuiLis if (compound.hasKey("ListingsData")) { currentPage = compound.getInteger("Page"); totalPages = compound.getInteger("TotalPages"); - totalListings = compound.getInteger("TotalListings"); // Load listings listingContainer.displayInventory.clear(); @@ -213,7 +210,7 @@ public class GuiAuctionListing extends GuiAuctionInterface implements ISubGuiLis AuctionListing listing = AuctionListing.fromNBT(list.getCompoundTagAt(i)); listingContainer.displayInventory.setListing(i, listing); } - dataLoaded = true; + updatePaginationVisibility(); } else if (compound.hasKey("ListingsRefresh")) { requestListings(); } diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java index a8784b3b0..5b514cff6 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java @@ -15,7 +15,9 @@ import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.ITextfieldListener; import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.containers.ContainerAuctionSell; +import noppes.npcs.util.AuctionFormatUtil; import noppes.npcs.entity.EntityNPCInterface; +import org.lwjgl.opengl.GL11; import java.util.ArrayList; import java.util.List; @@ -239,7 +241,7 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis // Draw listing fee info (uses cached config from server) String feeText = StatCollector.translateToLocal("auction.sell.fee") - .replace("%s", formatCurrency(AuctionClientConfig.getListingFee()) + " " + AuctionClientConfig.getCurrencyName()); + .replace("%s", AuctionFormatUtil.formatCurrency(AuctionClientConfig.getListingFee()) + " " + AuctionClientConfig.getCurrencyName()); fontRendererObj.drawString(EnumChatFormatting.GRAY + feeText, guiLeft + contentX, guiTop + contentY + 77, 0xFFFFFF); // Draw error message if any @@ -326,7 +328,11 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis } if (tooltip != null && !tooltip.isEmpty()) { + GL11.glPushMatrix(); + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); drawHoveringText(tooltip, mouseX - guiLeft, mouseY - guiTop, fontRendererObj); + GL11.glPopAttrib(); + GL11.glPopMatrix(); } } } diff --git a/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java b/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java index a1793c545..ae24a9ae9 100644 --- a/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java +++ b/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java @@ -10,6 +10,7 @@ import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.ITextfieldListener; import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.util.AuctionFormatUtil; /** * Sub GUI for placing a bid on an auction. @@ -49,13 +50,13 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe // Minimum bid info String minText = StatCollector.translateToLocal("auction.bid.minimum") - .replace("%s", EnumChatFormatting.DARK_RED + formatCurrency(minimumBid) + " " + currencyName); + .replace("%s", EnumChatFormatting.DARK_RED + AuctionFormatUtil.formatCurrency(minimumBid) + " " + currencyName); addLabel(new GuiNpcLabel(1, minText, guiLeft + 15, y)); y += 14; // Your balance String balanceText = StatCollector.translateToLocal("auction.bid.yourBalance") - .replace("%s", EnumChatFormatting.DARK_GREEN + formatCurrency(playerBalance) + " " + currencyName); + .replace("%s", EnumChatFormatting.DARK_GREEN + AuctionFormatUtil.formatCurrency(playerBalance) + " " + currencyName); addLabel(new GuiNpcLabel(2, balanceText, guiLeft + 15, y)); y += 18; @@ -117,7 +118,7 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe // Validate if (bidAmount < minimumBid) { errorMessage = StatCollector.translateToLocal("auction.error.bidTooLow") - .replace("%s", formatCurrency(minimumBid)); + .replace("%s", AuctionFormatUtil.formatCurrency(minimumBid)); updateErrorLabel(); return; } @@ -144,17 +145,4 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe public boolean wasSuccessful() { return successful; } - - private String formatCurrency(long amount) { - if (amount < 1000) return "" + amount; - StringBuilder sb = new StringBuilder(); - String str = "" + amount; - int count = 0; - for (int i = str.length() - 1; i >= 0; i--) { - if (count > 0 && count % 3 == 0) sb.insert(0, ','); - sb.insert(0, str.charAt(i)); - count++; - } - return sb.toString(); - } } diff --git a/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBuyNow.java b/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBuyNow.java index b63471047..4884c7ae4 100644 --- a/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBuyNow.java +++ b/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBuyNow.java @@ -8,6 +8,7 @@ import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.util.AuctionFormatUtil; /** * Sub GUI for confirming a buyout purchase. @@ -54,14 +55,14 @@ public class SubGuiAuctionBuyNow extends SubGuiInterface { y += 16; // Buyout price - String priceText = EnumChatFormatting.DARK_RED + formatCurrency(buyoutPrice) + " " + currencyName; + String priceText = EnumChatFormatting.DARK_RED + AuctionFormatUtil.formatCurrency(buyoutPrice) + " " + currencyName; int priceWidth = fontRendererObj.getStringWidth(priceText); addLabel(new GuiNpcLabel(2, priceText, centerX - priceWidth / 2, y)); y += 20; // Your balance String balanceLabel = StatCollector.translateToLocal("auction.bid.yourBalance"); - String balanceText = balanceLabel.replace("%s", EnumChatFormatting.DARK_GREEN + formatCurrency(playerBalance) + " " + currencyName); + String balanceText = balanceLabel.replace("%s", EnumChatFormatting.DARK_GREEN + AuctionFormatUtil.formatCurrency(playerBalance) + " " + currencyName); addLabel(new GuiNpcLabel(3, balanceText, guiLeft + 20, y)); y += 14; @@ -105,17 +106,4 @@ public class SubGuiAuctionBuyNow extends SubGuiInterface { public boolean wasSuccessful() { return successful; } - - private String formatCurrency(long amount) { - if (amount < 1000) return "" + amount; - StringBuilder sb = new StringBuilder(); - String str = "" + amount; - int count = 0; - for (int i = str.length() - 1; i >= 0; i--) { - if (count > 0 && count % 3 == 0) sb.insert(0, ','); - sb.insert(0, str.charAt(i)); - count++; - } - return sb.toString(); - } } diff --git a/src/main/java/noppes/npcs/config/ConfigMarket.java b/src/main/java/noppes/npcs/config/ConfigMarket.java index e14861033..2909ec2a6 100644 --- a/src/main/java/noppes/npcs/config/ConfigMarket.java +++ b/src/main/java/noppes/npcs/config/ConfigMarket.java @@ -79,11 +79,12 @@ public class ConfigMarket { CurrencyName = config.get(CURRENCY, "Currency Name", "Coins", "Display name for the currency (e.g. 'Coins', 'Gold', 'Credits')").getString(); - StartingBalance = config.get(CURRENCY, "Starting Balance", 0, - "Starting currency balance for new players").getInt(0); + StartingBalance = parseLongConfig(config.get(CURRENCY, "Starting Balance", "0", + "Starting currency balance for new players").getString(), 0); - MaxBalance = config.get(CURRENCY, "Max Balance", Integer.MAX_VALUE, - "Maximum currency balance a player can have").getInt(Integer.MAX_VALUE); + MaxBalance = parseLongConfig(config.get(CURRENCY, "Max Balance", String.valueOf(Long.MAX_VALUE), + "Maximum currency balance a player can have").getString(), Long.MAX_VALUE); + if (MaxBalance < 0) MaxBalance = Long.MAX_VALUE; // ========================================= // Auction Settings @@ -95,16 +96,19 @@ public class ConfigMarket { AuctionDurationHours = config.get(AUCTION, "Auction Duration Hours", 24, "Default duration for auctions in hours").getInt(24); + if (AuctionDurationHours < 1) AuctionDurationHours = 1; - ListingFee = config.get(AUCTION, "Listing Fee", 10, - "Flat fee charged when creating a listing").getInt(10); + ListingFee = parseLongConfig(config.get(AUCTION, "Listing Fee", "10", + "Flat fee charged when creating a listing").getString(), 10); + if (ListingFee < 0) ListingFee = 0; - MinimumListingPrice = config.get(AUCTION, "Minimum Listing Price", 1, - "Minimum starting price for auction listings").getInt(1); + MinimumListingPrice = parseLongConfig(config.get(AUCTION, "Minimum Listing Price", "1", + "Minimum starting price for auction listings").getString(), 1); if (MinimumListingPrice < 1) MinimumListingPrice = 1; SalesTaxPercent = config.get(AUCTION, "Sales Tax Percent", 0.05, "Percentage of sale price taken as tax (0.05 = 5%). Tax is deleted as a currency sink.").getDouble(0.05); + SalesTaxPercent = Math.max(0.0, Math.min(1.0, SalesTaxPercent)); int maxTrades = config.get(AUCTION, "Default Max Trades", 8, "Default maximum number of trade slots per player (listings + bids + claims). Min: 1, Max: 45. Players can have more via customnpcs.auction.trades.X permissions.").getInt(8); @@ -112,15 +116,19 @@ public class ConfigMarket { SnipeProtectionMinutes = config.get(AUCTION, "Snipe Protection Minutes", 2, "When a bid is placed with less than this many minutes remaining, the auction is extended to this duration").getInt(2); + if (SnipeProtectionMinutes < 0) SnipeProtectionMinutes = 0; ClaimExpirationDays = config.get(AUCTION, "Claim Expiration Days", 20, "Number of days before unclaimed items/currency are deleted").getInt(20); + if (ClaimExpirationDays < 1) ClaimExpirationDays = 1; MinBidIncrementPercent = config.get(AUCTION, "Min Bid Increment Percent", 0.05, "Minimum bid increment as a percentage of current bid (0.05 = 5%)").getDouble(0.05); + MinBidIncrementPercent = Math.max(0.0, Math.min(1.0, MinBidIncrementPercent)); CancellationPenaltyPercent = config.get(AUCTION, "Cancellation Penalty Percent", 0.10, "Percentage of current bid taken as penalty when seller cancels an auction with bids (0.10 = 10%)").getDouble(0.10); + CancellationPenaltyPercent = Math.max(0.0, Math.min(1.0, CancellationPenaltyPercent)); // ========================================= // Auction Blacklist Settings @@ -237,4 +245,13 @@ public class ConfigMarket { } } } + + private static long parseLongConfig(String value, long defaultValue) { + if (value == null || value.trim().isEmpty()) return defaultValue; + try { + return Long.parseLong(value.trim()); + } catch (NumberFormatException e) { + return defaultValue; + } + } } diff --git a/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java b/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java new file mode 100644 index 000000000..7e48589f4 --- /dev/null +++ b/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java @@ -0,0 +1,34 @@ +package noppes.npcs.constants; + +import noppes.npcs.config.ConfigMarket; + +/** + * Enum for auction log action types. + * Used by AuctionController.logAuction() for type-safe logging. + */ +public enum EnumAuctionLogAction { + CREATED, + BID, + BUYOUT, + SOLD, + EXPIRED, + CANCELLED, + CLAIMED, + CLAIM_EXPIRED; + + /** Check if this action should be logged based on config settings */ + public boolean shouldLog() { + if (!ConfigMarket.AuctionLoggingEnabled) return false; + switch (this) { + case CREATED: return ConfigMarket.LogAuctionCreated; + case BID: return ConfigMarket.LogAuctionBid; + case BUYOUT: return ConfigMarket.LogAuctionBuyout; + case SOLD: return ConfigMarket.LogAuctionSold; + case EXPIRED: return ConfigMarket.LogAuctionExpired; + case CANCELLED: return ConfigMarket.LogAuctionCancelled; + case CLAIMED: + case CLAIM_EXPIRED: return ConfigMarket.LogAuctionClaimed; + default: return false; + } + } +} diff --git a/src/main/java/noppes/npcs/constants/EnumAuctionPage.java b/src/main/java/noppes/npcs/constants/EnumAuctionPage.java new file mode 100644 index 000000000..c6c2c2a36 --- /dev/null +++ b/src/main/java/noppes/npcs/constants/EnumAuctionPage.java @@ -0,0 +1,30 @@ +package noppes.npcs.constants; + +import noppes.npcs.constants.EnumGuiType; + +/** + * Enum for auction GUI page navigation. + * Maps page indices to GUI types for packet-based navigation. + */ +public enum EnumAuctionPage { + LISTINGS(EnumGuiType.PlayerAuction), + SELL(EnumGuiType.PlayerAuctionSell), + TRADES(EnumGuiType.PlayerAuctionTrades); + + private final EnumGuiType guiType; + + EnumAuctionPage(EnumGuiType guiType) { + this.guiType = guiType; + } + + public EnumGuiType getGuiType() { + return guiType; + } + + public static EnumAuctionPage fromOrdinal(int ordinal) { + if (ordinal >= 0 && ordinal < values().length) { + return values()[ordinal]; + } + return LISTINGS; + } +} diff --git a/src/main/java/noppes/npcs/constants/EnumScriptType.java b/src/main/java/noppes/npcs/constants/EnumScriptType.java index 9e2588103..1088ef83a 100644 --- a/src/main/java/noppes/npcs/constants/EnumScriptType.java +++ b/src/main/java/noppes/npcs/constants/EnumScriptType.java @@ -137,7 +137,14 @@ public enum EnumScriptType { ENERGY_BARRIER_SPAWNED("energyBarrierSpawned"), ENERGY_BARRIER_TICK("energyBarrierTick"), ENERGY_BARRIER_HIT("energyBarrierHit"), - ENERGY_BARRIER_DESTROYED("energyBarrierDestroyed"); + ENERGY_BARRIER_DESTROYED("energyBarrierDestroyed"), + + // Auction Events + AUCTION_CREATE("auctionCreate"), + AUCTION_BID("auctionBid"), + AUCTION_BUYOUT("auctionBuyout"), + AUCTION_CANCEL("auctionCancel"), + AUCTION_CLAIM("auctionClaim"); public String function; diff --git a/src/main/java/noppes/npcs/constants/EnumTradeSlotType.java b/src/main/java/noppes/npcs/constants/EnumTradeSlotType.java new file mode 100644 index 000000000..16850a79e --- /dev/null +++ b/src/main/java/noppes/npcs/constants/EnumTradeSlotType.java @@ -0,0 +1,35 @@ +package noppes.npcs.constants; + +/** + * Enum for trade slot types in the player's auction trades view. + * Used by ContainerAuctionTrades to identify what each slot contains. + */ +public enum EnumTradeSlotType { + EMPTY, + SELLING, + BIDDING, + CLAIM; + + public boolean isSelling() { + return this == SELLING; + } + + public boolean isBidding() { + return this == BIDDING; + } + + public boolean isClaim() { + return this == CLAIM; + } + + public boolean isEmpty() { + return this == EMPTY; + } + + public static EnumTradeSlotType fromOrdinal(int ordinal) { + if (ordinal >= 0 && ordinal < values().length) { + return values()[ordinal]; + } + return EMPTY; + } +} diff --git a/src/main/java/noppes/npcs/constants/ScriptContext.java b/src/main/java/noppes/npcs/constants/ScriptContext.java index c5861927a..9b06001ff 100644 --- a/src/main/java/noppes/npcs/constants/ScriptContext.java +++ b/src/main/java/noppes/npcs/constants/ScriptContext.java @@ -2,9 +2,11 @@ package noppes.npcs.constants; import noppes.npcs.api.event.IAbilityEvent; import noppes.npcs.api.event.IAnimationEvent; +import noppes.npcs.api.event.IAuctionEvent; import noppes.npcs.api.event.IBlockEvent; import noppes.npcs.api.event.ICustomGuiEvent; import noppes.npcs.api.event.IDialogEvent; +import noppes.npcs.api.event.IEnergyBarrierEvent; import noppes.npcs.api.event.IEnergyProjectileEvent; import noppes.npcs.api.event.IFactionEvent; import noppes.npcs.api.event.IForgeEvent; @@ -57,6 +59,7 @@ public class ScriptContext { INpcEvent.class, IProjectileEvent.class, IEnergyProjectileEvent.class, + IEnergyBarrierEvent.class, IAbilityEvent.class, IAnimationEvent.class ); @@ -70,7 +73,9 @@ public class ScriptContext { IFactionEvent.class, ICustomGuiEvent.class, IEnergyProjectileEvent.class, - IPlayerAbilityEvent.class + IEnergyBarrierEvent.class, + IPlayerAbilityEvent.class, + IAuctionEvent.class ); public static final ScriptContext BLOCK = register("BLOCK", "block", diff --git a/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java b/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java index 2b40000ac..0e46b6b9b 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java @@ -14,9 +14,9 @@ import noppes.npcs.entity.EntityNPCInterface; */ public class ContainerAuctionBidding extends ContainerAuction { // Display slot layout - private int itemSlotX = 71; - private int itemSlotY = 76; - private int itemSlotIndex = PLAYER_INV_SLOT_COUNT; + private static final int ITEM_SLOT_X = 71; + private static final int ITEM_SLOT_Y = 76; + private static final int ITEM_SLOT_INDEX = PLAYER_INV_SLOT_COUNT; private final IInventory displayInventory; private AuctionListing listing; @@ -26,7 +26,7 @@ public class ContainerAuctionBidding extends ContainerAuction { displayInventory = new InventoryBasic("Display", false, 1); // Display slot (read-only) - addSlotToContainer(new Slot(displayInventory, 0, itemSlotX, itemSlotY) { + addSlotToContainer(new Slot(displayInventory, 0, ITEM_SLOT_X, ITEM_SLOT_Y) { @Override public boolean isItemValid(ItemStack stack) { return false; } @Override @@ -58,6 +58,6 @@ public class ContainerAuctionBidding extends ContainerAuction { * Check if this is the display slot. */ public boolean isDisplaySlot(int slotIndex) { - return slotIndex == itemSlotIndex; + return slotIndex == ITEM_SLOT_INDEX; } } diff --git a/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java b/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java index 14b381f40..e5a01dd6f 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java @@ -13,10 +13,10 @@ import noppes.npcs.entity.EntityNPCInterface; * Items are cloned from inventory, not moved. Validation happens on submit. */ public class ContainerAuctionSell extends ContainerAuction { - private int sellSlotX = 59; - private int sellSlotY = 101; - private int sellSlotIndex = PLAYER_INV_SLOT_COUNT; - private int maxStack = 64; + private static final int SELL_SLOT_X = 59; + private static final int SELL_SLOT_Y = 101; + private static final int SELL_SLOT_INDEX = PLAYER_INV_SLOT_COUNT; + private static final int MAX_STACK = 64; private final IInventory sellInventory; @@ -25,7 +25,7 @@ public class ContainerAuctionSell extends ContainerAuction { sellInventory = new InventoryBasic("Sell", false, 1); // Sell slot blocks direct interaction - addSlotToContainer(new Slot(sellInventory, 0, sellSlotX, sellSlotY) { + addSlotToContainer(new Slot(sellInventory, 0, SELL_SLOT_X, SELL_SLOT_Y) { @Override public boolean isItemValid(ItemStack stack) { return false; } @Override @@ -59,11 +59,11 @@ public class ContainerAuctionSell extends ContainerAuction { if (sellStack == null) { // Empty slot - create new stack ItemStack newStack = source.copy(); - newStack.stackSize = Math.min(toAdd, maxStack); + newStack.stackSize = Math.min(toAdd, MAX_STACK); sellInventory.setInventorySlotContents(0, newStack); } else if (itemsMatch(sellStack, source)) { // Same item - add to existing (capped at 64) - int space = maxStack - sellStack.stackSize; + int space = MAX_STACK - sellStack.stackSize; int add = Math.min(toAdd, space); if (add > 0) { sellStack.stackSize += add; @@ -71,7 +71,7 @@ public class ContainerAuctionSell extends ContainerAuction { } else { // Different item - replace ItemStack newStack = source.copy(); - newStack.stackSize = Math.min(toAdd, maxStack); + newStack.stackSize = Math.min(toAdd, MAX_STACK); sellInventory.setInventorySlotContents(0, newStack); } } @@ -93,7 +93,7 @@ public class ContainerAuctionSell extends ContainerAuction { /** Check if sell slot is this container slot index */ public boolean isSellSlot(int slotIndex) { - return slotIndex == sellSlotIndex; + return slotIndex == SELL_SLOT_INDEX; } /** Check if two stacks match using NoppesUtilPlayer.compareItems (same item, damage, and NBT) */ diff --git a/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java b/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java index eab491c70..43e91f428 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java @@ -6,11 +6,13 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import noppes.npcs.constants.EnumClaimType; import noppes.npcs.controllers.AuctionController; +import noppes.npcs.constants.EnumTradeSlotType; import noppes.npcs.controllers.data.AuctionClaim; import noppes.npcs.controllers.data.AuctionListing; import noppes.npcs.entity.EntityNPCInterface; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -34,7 +36,7 @@ public class ContainerAuctionTrades extends ContainerAuction { private int maxTradeSlots = 8; // Player's max trade slots (permission-aware, synced from server) // Slot type tracking for proper accessor methods - private int[] slotTypes = new int[SLOT_COUNT]; // 0=empty, 1=selling, 2=bidding, 3=claim + private EnumTradeSlotType[] slotTypes = new EnumTradeSlotType[SLOT_COUNT]; private int[] slotDataIndex = new int[SLOT_COUNT]; // Index into respective list public ContainerAuctionTrades(EntityNPCInterface npc, EntityPlayer player) { @@ -117,8 +119,8 @@ public class ContainerAuctionTrades extends ContainerAuction { displayInventory.clear(); // Reset slot tracking + Arrays.fill(slotTypes, EnumTradeSlotType.EMPTY); for (int i = 0; i < SLOT_COUNT; i++) { - slotTypes[i] = 0; slotDataIndex[i] = -1; } } @@ -156,7 +158,7 @@ public class ContainerAuctionTrades extends ContainerAuction { if (slot >= SLOT_COUNT) break; claims.add(claim); displayInventory.setClaimItem(slot, claim); - slotTypes[slot] = 3; // claim + slotTypes[slot] = EnumTradeSlotType.CLAIM; slotDataIndex[slot] = claims.size() - 1; slot++; } @@ -166,7 +168,7 @@ public class ContainerAuctionTrades extends ContainerAuction { if (slot >= SLOT_COUNT) break; claims.add(claim); displayInventory.setClaimItem(slot, claim); - slotTypes[slot] = 3; // claim + slotTypes[slot] = EnumTradeSlotType.CLAIM; slotDataIndex[slot] = claims.size() - 1; slot++; } @@ -176,7 +178,7 @@ public class ContainerAuctionTrades extends ContainerAuction { if (slot >= SLOT_COUNT) break; claims.add(claim); displayInventory.setClaimItem(slot, claim); - slotTypes[slot] = 3; // claim + slotTypes[slot] = EnumTradeSlotType.CLAIM; slotDataIndex[slot] = claims.size() - 1; slot++; } @@ -186,7 +188,7 @@ public class ContainerAuctionTrades extends ContainerAuction { for (AuctionListing listing : activeListings) { if (slot >= SLOT_COUNT) break; if (listing.item != null) displayInventory.setInventorySlotContents(slot, listing.item.copy()); - slotTypes[slot] = 1; // selling + slotTypes[slot] = EnumTradeSlotType.SELLING; slotDataIndex[slot] = listingIdx; slot++; listingIdx++; @@ -197,7 +199,7 @@ public class ContainerAuctionTrades extends ContainerAuction { for (AuctionListing bid : activeBids) { if (slot >= SLOT_COUNT) break; if (bid.item != null) displayInventory.setInventorySlotContents(slot, bid.item.copy()); - slotTypes[slot] = 2; // bidding + slotTypes[slot] = EnumTradeSlotType.BIDDING; slotDataIndex[slot] = bidIdx; slot++; bidIdx++; @@ -208,7 +210,7 @@ public class ContainerAuctionTrades extends ContainerAuction { if (slot >= SLOT_COUNT) break; claims.add(claim); displayInventory.setClaimItem(slot, claim); - slotTypes[slot] = 3; // claim + slotTypes[slot] = EnumTradeSlotType.CLAIM; slotDataIndex[slot] = claims.size() - 1; slot++; } @@ -219,13 +221,13 @@ public class ContainerAuctionTrades extends ContainerAuction { /** Get listing at slot (or null if claim/empty) */ public AuctionListing getListingAt(int slot) { if (slot < 0 || slot >= SLOT_COUNT) return null; - int type = slotTypes[slot]; + EnumTradeSlotType type = slotTypes[slot]; int idx = slotDataIndex[slot]; if (idx < 0) return null; - if (type == 1 && idx < activeListings.size()) { + if (type == EnumTradeSlotType.SELLING && idx < activeListings.size()) { return activeListings.get(idx); - } else if (type == 2 && idx < activeBids.size()) { + } else if (type == EnumTradeSlotType.BIDDING && idx < activeBids.size()) { return activeBids.get(idx); } return null; @@ -234,7 +236,7 @@ public class ContainerAuctionTrades extends ContainerAuction { /** Get claim at slot (or null if listing/empty) */ public AuctionClaim getClaimAt(int slot) { if (slot < 0 || slot >= SLOT_COUNT) return null; - if (slotTypes[slot] == 3) { + if (slotTypes[slot] == EnumTradeSlotType.CLAIM) { int idx = slotDataIndex[slot]; if (idx >= 0 && idx < claims.size()) { return claims.get(idx); @@ -245,12 +247,12 @@ public class ContainerAuctionTrades extends ContainerAuction { /** Check if slot is player's active listing */ public boolean isSellingAt(int slot) { - return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == 1; + return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == EnumTradeSlotType.SELLING; } /** Check if slot is player's active bid */ public boolean isBiddingAt(int slot) { - return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == 2; + return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == EnumTradeSlotType.BIDDING; } /** Find listing matching item (searches both selling and bidding) */ @@ -324,23 +326,23 @@ public class ContainerAuctionTrades extends ContainerAuction { private void restoreSlotItem(int slot) { if (slot < 0 || slot >= SLOT_COUNT) return; - int type = slotTypes[slot]; + EnumTradeSlotType type = slotTypes[slot]; int idx = slotDataIndex[slot]; if (idx < 0) return; - if (type == 1 && idx < activeListings.size()) { + if (type == EnumTradeSlotType.SELLING && idx < activeListings.size()) { // Selling AuctionListing listing = activeListings.get(idx); if (listing != null && listing.item != null) { displayInventory.setInventorySlotContents(slot, listing.item.copy()); } - } else if (type == 2 && idx < activeBids.size()) { + } else if (type == EnumTradeSlotType.BIDDING && idx < activeBids.size()) { // Bidding AuctionListing bid = activeBids.get(idx); if (bid != null && bid.item != null) { displayInventory.setInventorySlotContents(slot, bid.item.copy()); } - } else if (type == 3 && idx < claims.size()) { + } else if (type == EnumTradeSlotType.CLAIM && idx < claims.size()) { // Claim displayInventory.setClaimItem(slot, claims.get(idx)); } diff --git a/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java b/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java index 9b8c1f995..2b921aad6 100644 --- a/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java +++ b/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java @@ -40,7 +40,7 @@ public class AuctionConfigSync { public static String getEffectiveCurrencyName() { if (ConfigMarket.UseVault && VaultUtil.isEnabled()) { String vaultName = VaultUtil.getCurrencyNamePlural(); - if (vaultName != null && !vaultName.isEmpty() && !vaultName.equals("coins")) { + if (vaultName != null && !vaultName.isEmpty()) { return vaultName; } } diff --git a/src/main/java/noppes/npcs/controllers/AuctionController.java b/src/main/java/noppes/npcs/controllers/AuctionController.java index e6ca0c6cf..2d02cd9ce 100644 --- a/src/main/java/noppes/npcs/controllers/AuctionController.java +++ b/src/main/java/noppes/npcs/controllers/AuctionController.java @@ -11,22 +11,27 @@ import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import noppes.npcs.CustomNpcs; import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.EventHooks; import noppes.npcs.LogWriter; import noppes.npcs.api.entity.IPlayer; import noppes.npcs.api.handler.IAuctionHandler; import noppes.npcs.api.handler.data.IAuctionListing; import noppes.npcs.api.item.IItemStack; import noppes.npcs.config.ConfigMarket; +import noppes.npcs.constants.EnumAuctionLogAction; import noppes.npcs.constants.EnumAuctionSort; import noppes.npcs.constants.EnumAuctionStatus; import noppes.npcs.constants.EnumClaimType; import noppes.npcs.constants.EnumNotificationType; +import noppes.npcs.controllers.data.PlayerDataScript; import noppes.npcs.controllers.data.AuctionBlacklist; import noppes.npcs.controllers.data.AuctionClaim; import noppes.npcs.controllers.data.AuctionFilter; import noppes.npcs.controllers.data.AuctionListing; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.controllers.data.PlayerTradeData; +import noppes.npcs.scripted.NpcAPI; +import noppes.npcs.scripted.event.player.AuctionEvent; import noppes.npcs.util.CustomNPCsThreader; import java.io.File; @@ -138,52 +143,56 @@ public class AuctionController implements IAuctionHandler { } private void endAuction(AuctionListing listing) { - listing.status = EnumAuctionStatus.ENDED; + synchronized (listing) { + if (listing.status != EnumAuctionStatus.ACTIVE) return; - // Remove from seller's active listings - removeFromPlayerListings(listing.sellerUUID, listing.id); + listing.status = EnumAuctionStatus.ENDED; - // Remove from bidder's active bids if any - if (listing.highBidderUUID != null) { - removeFromPlayerBids(listing.highBidderUUID, listing.id); - } + // Remove from seller's active listings + removeFromPlayerListings(listing.sellerUUID, listing.id); - // Get item display name safely - String itemDisplayName = listing.item != null ? listing.item.getDisplayName() : "Unknown Item"; + // Remove from bidder's active bids if any + if (listing.highBidderUUID != null) { + removeFromPlayerBids(listing.highBidderUUID, listing.id); + } - if (listing.hasBids()) { - // Auction sold - create claims - AuctionClaim itemClaim = AuctionClaim.createItemWonClaim( - listing.highBidderUUID, listing.highBidderName, listing.id, listing.item); - addClaimToPlayer(listing.highBidderUUID, itemClaim); + // Get item display name safely + String itemDisplayName = listing.item != null ? listing.item.getDisplayName() : "Unknown Item"; - long saleAmount = listing.currentBid; - long tax = (long) (saleAmount * ConfigMarket.SalesTaxPercent); - long sellerReceives = saleAmount - tax; + if (listing.hasBids()) { + // Auction sold - create claims + AuctionClaim itemClaim = AuctionClaim.createItemWonClaim( + listing.highBidderUUID, listing.highBidderName, listing.id, listing.item); + addClaimToPlayer(listing.highBidderUUID, itemClaim); - AuctionClaim currencyClaim = AuctionClaim.createCurrencyClaim( - listing.sellerUUID, listing.sellerName, listing.id, sellerReceives, - itemDisplayName, listing.highBidderName); - addClaimToPlayer(listing.sellerUUID, currencyClaim); + long saleAmount = listing.currentBid; + long tax = (long) (saleAmount * ConfigMarket.SalesTaxPercent); + long sellerReceives = saleAmount - tax; - // Notifications - sendNotificationToPlayer(listing.highBidderUUID, EnumNotificationType.AUCTION_WON, listing.id, - "You won the auction for " + itemDisplayName + "!"); - sendNotificationToPlayer(listing.sellerUUID, EnumNotificationType.AUCTION_SOLD, listing.id, - "Your " + itemDisplayName + " sold for " + saleAmount + " " + ConfigMarket.CurrencyName + "!"); + AuctionClaim currencyClaim = AuctionClaim.createCurrencyClaim( + listing.sellerUUID, listing.sellerName, listing.id, sellerReceives, + itemDisplayName, listing.highBidderName); + addClaimToPlayer(listing.sellerUUID, currencyClaim); - logAuction("SOLD", listing.sellerName, itemDisplayName, saleAmount, - "Winner: " + listing.highBidderName + ", Tax: " + tax); - } else { - // No bids - return item to seller - AuctionClaim returnClaim = AuctionClaim.createItemReturnedClaim( - listing.sellerUUID, listing.sellerName, listing.id, listing.item); - addClaimToPlayer(listing.sellerUUID, returnClaim); + // Notifications + sendNotificationToPlayer(listing.highBidderUUID, EnumNotificationType.AUCTION_WON, listing.id, + "You won the auction for " + itemDisplayName + "!"); + sendNotificationToPlayer(listing.sellerUUID, EnumNotificationType.AUCTION_SOLD, listing.id, + "Your " + itemDisplayName + " sold for " + saleAmount + " " + ConfigMarket.CurrencyName + "!"); - sendNotificationToPlayer(listing.sellerUUID, EnumNotificationType.AUCTION_EXPIRED, listing.id, - "Your auction for " + itemDisplayName + " expired with no bids."); + logAuction(EnumAuctionLogAction.SOLD, listing.sellerName, itemDisplayName, saleAmount, + "Winner: " + listing.highBidderName + ", Tax: " + tax); + } else { + // No bids - return item to seller + AuctionClaim returnClaim = AuctionClaim.createItemReturnedClaim( + listing.sellerUUID, listing.sellerName, listing.id, listing.item); + addClaimToPlayer(listing.sellerUUID, returnClaim); - logAuction("EXPIRED", listing.sellerName, itemDisplayName, listing.startingPrice, "No bids"); + sendNotificationToPlayer(listing.sellerUUID, EnumNotificationType.AUCTION_EXPIRED, listing.id, + "Your auction for " + itemDisplayName + " expired with no bids."); + + logAuction(EnumAuctionLogAction.EXPIRED, listing.sellerName, itemDisplayName, listing.startingPrice, "No bids"); + } } } @@ -280,6 +289,15 @@ public class AuctionController implements IAuctionHandler { return "You cannot afford the listing fee (" + fee + " " + ConfigMarket.CurrencyName + ")."; } + // Fire auction create event (cancelable) + PlayerDataScript createHandler = ScriptController.Instance.getPlayerScripts(player); + IPlayer iCreator = (IPlayer) NpcAPI.Instance().getIEntity(player); + AuctionEvent.CreateEvent createEvent = new AuctionEvent.CreateEvent( + iCreator, NpcAPI.Instance().getIItemStack(item), startingPrice, buyoutPrice); + if (EventHooks.onAuctionCreate(createHandler, createEvent)) { + return "Auction creation was cancelled."; + } + // Deduct fee if (!currency.withdraw(fee)) { return "Failed to deduct listing fee."; @@ -295,7 +313,7 @@ public class AuctionController implements IAuctionHandler { markDirty(); - logAuction("CREATED", playerName, item.getDisplayName(), startingPrice, + logAuction(EnumAuctionLogAction.CREATED, playerName, item.getDisplayName(), startingPrice, "Buyout: " + (buyoutPrice > 0 ? buyoutPrice : "None") + ", Fee: " + fee); return null; // Success @@ -307,55 +325,74 @@ public class AuctionController implements IAuctionHandler { return "Listing not found."; } - if (!listing.status.canCancel()) { - return "This auction cannot be cancelled."; + synchronized (listing) { + if (!listing.status.canCancel()) { + return "This auction cannot be cancelled."; + } + + UUID playerUUID = player.getUniqueID(); + if (!isAdmin && !listing.isSeller(playerUUID)) { + return "You can only cancel your own listings."; + } + + // Fire auction cancel event (cancelable) + PlayerDataScript cancelHandler = ScriptController.Instance.getPlayerScripts(player); + IPlayer iCanceller = (IPlayer) NpcAPI.Instance().getIEntity(player); + AuctionEvent.CancelEvent cancelEvent = new AuctionEvent.CancelEvent(iCanceller, listing, isAdmin); + if (EventHooks.onAuctionCancel(cancelHandler, cancelEvent)) { + return "Auction cancellation was prevented."; + } + + // Handle penalty if there are bids + if (listing.hasBids()) { + long penalty = (long) (listing.currentBid * ConfigMarket.CancellationPenaltyPercent); + + // Refund bidder (no item - can't rebid on cancelled auction) + AuctionClaim refundClaim = AuctionClaim.createRefundClaim( + listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, + listing.item.getDisplayName(), listing.sellerName, null); + addClaimToPlayer(listing.highBidderUUID, refundClaim); + + // Remove from bidder's active bids + removeFromPlayerBids(listing.highBidderUUID, listing.id); + + sendNotificationToPlayer(listing.highBidderUUID, EnumNotificationType.AUCTION_OUTBID, listing.id, + "The auction for " + listing.item.getDisplayName() + " was cancelled. Your bid has been refunded."); + + // Return item to seller + AuctionClaim itemClaim = AuctionClaim.createItemReturnedClaim( + listing.sellerUUID, listing.sellerName, listing.id, listing.item); + addClaimToPlayer(listing.sellerUUID, itemClaim); + + // Deduct cancellation penalty from seller + if (penalty > 0) { + PlayerData sellerData = PlayerDataController.Instance.getData(listing.sellerUUID); + if (sellerData != null) { + sellerData.tradeData.withdraw(penalty); + sellerData.save(); + } + } + + logAuction(EnumAuctionLogAction.CANCELLED, listing.sellerName, listing.item.getDisplayName(), listing.currentBid, + "Penalty: " + penalty + ", Bidder refunded: " + listing.highBidderName + + (isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : "")); + } else { + // No bids - just return item + AuctionClaim itemClaim = AuctionClaim.createItemReturnedClaim( + listing.sellerUUID, listing.sellerName, listing.id, listing.item); + addClaimToPlayer(listing.sellerUUID, itemClaim); + + logAuction(EnumAuctionLogAction.CANCELLED, listing.sellerName, listing.item.getDisplayName(), listing.startingPrice, + "No bids" + (isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : "")); + } + + // Remove from seller's active listings + removeFromPlayerListings(listing.sellerUUID, listing.id); + + listing.status = EnumAuctionStatus.CANCELLED; + markDirty(); } - UUID playerUUID = player.getUniqueID(); - if (!isAdmin && !listing.isSeller(playerUUID)) { - return "You can only cancel your own listings."; - } - - // Handle penalty if there are bids - if (listing.hasBids()) { - long penalty = (long) (listing.currentBid * ConfigMarket.CancellationPenaltyPercent); - - // Refund bidder (no item - can't rebid on cancelled auction) - AuctionClaim refundClaim = AuctionClaim.createRefundClaim( - listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, - listing.item.getDisplayName(), listing.sellerName, null); - addClaimToPlayer(listing.highBidderUUID, refundClaim); - - // Remove from bidder's active bids - removeFromPlayerBids(listing.highBidderUUID, listing.id); - - sendNotificationToPlayer(listing.highBidderUUID, EnumNotificationType.AUCTION_OUTBID, listing.id, - "The auction for " + listing.item.getDisplayName() + " was cancelled. Your bid has been refunded."); - - // Return item to seller - AuctionClaim itemClaim = AuctionClaim.createItemReturnedClaim( - listing.sellerUUID, listing.sellerName, listing.id, listing.item); - addClaimToPlayer(listing.sellerUUID, itemClaim); - - logAuction("CANCELLED", listing.sellerName, listing.item.getDisplayName(), listing.currentBid, - "Penalty: " + penalty + ", Bidder refunded: " + listing.highBidderName + - (isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : "")); - } else { - // No bids - just return item - AuctionClaim itemClaim = AuctionClaim.createItemReturnedClaim( - listing.sellerUUID, listing.sellerName, listing.id, listing.item); - addClaimToPlayer(listing.sellerUUID, itemClaim); - - logAuction("CANCELLED", listing.sellerName, listing.item.getDisplayName(), listing.startingPrice, - "No bids" + (isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : "")); - } - - // Remove from seller's active listings - removeFromPlayerListings(listing.sellerUUID, listing.id); - - listing.status = EnumAuctionStatus.CANCELLED; - markDirty(); - return null; // Success } @@ -376,75 +413,85 @@ public class AuctionController implements IAuctionHandler { return "Listing not found."; } - // Check status and expiry - if (!listing.status.canBid()) { - return "This auction has ended."; + synchronized (listing) { + // Check status and expiry + if (!listing.status.canBid()) { + return "This auction has ended."; + } + + if (listing.isExpired()) { + return "This auction has ended."; + } + + // CRITICAL: Prevent bidding on own items + if (listing.isSeller(playerUUID)) { + return "You cannot bid on your own auction."; + } + + // Prevent rebidding if already highest bidder + if (listing.isHighBidder(playerUUID)) { + return "You are already the highest bidder. Use buyout if you want to purchase immediately."; + } + + // Check minimum bid + long minBid = listing.getMinimumBid(ConfigMarket.MinBidIncrementPercent); + if (bidAmount < minBid) { + return "Bid must be at least " + minBid + " " + ConfigMarket.CurrencyName + "."; + } + + // Check currency + PlayerData playerData = PlayerData.get(player); + if (playerData == null) { + return "Could not access player data."; + } + + PlayerTradeData currency = playerData.tradeData; + + // Store previous bidder info + UUID previousBidder = listing.highBidderUUID; + long previousBid = listing.currentBid; + + // Full bid amount charged since we don't allow rebidding + long amountToCharge = bidAmount; + + if (!currency.canAfford(amountToCharge)) { + return "You cannot afford this bid."; + } + + // Fire auction bid event (cancelable) + PlayerDataScript bidHandler = ScriptController.Instance.getPlayerScripts(player); + IPlayer iBidder = (IPlayer) NpcAPI.Instance().getIEntity(player); + AuctionEvent.BidEvent bidEvent = new AuctionEvent.BidEvent(iBidder, listing, bidAmount); + if (EventHooks.onAuctionBid(bidHandler, bidEvent)) { + return "Bid was cancelled."; + } + + // Deduct bid from new bidder FIRST + if (!currency.withdraw(amountToCharge)) { + return "Failed to deduct bid amount."; + } + + // Refund previous bidder (include item for rebid option) + if (listing.hasBids() && previousBidder != null) { + AuctionClaim refundClaim = AuctionClaim.createRefundClaim( + previousBidder, listing.highBidderName, listing.id, previousBid, + listing.item.getDisplayName(), playerName, listing.item); + addClaimToPlayer(previousBidder, refundClaim); + + // Remove from previous bidder's active bids + removeFromPlayerBids(previousBidder, listing.id); + + sendNotificationToPlayer(previousBidder, EnumNotificationType.AUCTION_OUTBID, listing.id, + "You were outbid on " + listing.item.getDisplayName() + "!"); + } + + // Update listing + listing.highBidderUUID = playerUUID; + listing.highBidderName = playerName; + listing.currentBid = bidAmount; + listing.bidCount++; } - if (listing.isExpired()) { - return "This auction has ended."; - } - - // CRITICAL: Prevent bidding on own items - if (listing.isSeller(playerUUID)) { - return "You cannot bid on your own auction."; - } - - // Prevent rebidding if already highest bidder - if (listing.isHighBidder(playerUUID)) { - return "You are already the highest bidder. Use buyout if you want to purchase immediately."; - } - - // Check minimum bid - long minBid = listing.getMinimumBid(ConfigMarket.MinBidIncrementPercent); - if (bidAmount < minBid) { - return "Bid must be at least " + minBid + " " + ConfigMarket.CurrencyName + "."; - } - - // Check currency - PlayerData playerData = PlayerData.get(player); - if (playerData == null) { - return "Could not access player data."; - } - - PlayerTradeData currency = playerData.tradeData; - - // Store previous bidder info - UUID previousBidder = listing.highBidderUUID; - long previousBid = listing.currentBid; - - // Full bid amount charged since we don't allow rebidding - long amountToCharge = bidAmount; - - if (!currency.canAfford(amountToCharge)) { - return "You cannot afford this bid."; - } - - // Deduct bid from new bidder FIRST - if (!currency.withdraw(amountToCharge)) { - return "Failed to deduct bid amount."; - } - - // Refund previous bidder (include item for rebid option) - if (listing.hasBids() && previousBidder != null) { - AuctionClaim refundClaim = AuctionClaim.createRefundClaim( - previousBidder, listing.highBidderName, listing.id, previousBid, - listing.item.getDisplayName(), playerName, listing.item); - addClaimToPlayer(previousBidder, refundClaim); - - // Remove from previous bidder's active bids - removeFromPlayerBids(previousBidder, listing.id); - - sendNotificationToPlayer(previousBidder, EnumNotificationType.AUCTION_OUTBID, listing.id, - "You were outbid on " + listing.item.getDisplayName() + "!"); - } - - // Update listing - listing.highBidderUUID = playerUUID; - listing.highBidderName = playerName; - listing.currentBid = bidAmount; - listing.bidCount++; - // Add to new bidder's active bids addToPlayerBids(playerUUID, listing.id); @@ -453,7 +500,7 @@ public class AuctionController implements IAuctionHandler { markDirty(); - logAuction("BID", playerName, listing.item.getDisplayName(), bidAmount, + logAuction(EnumAuctionLogAction.BID, playerName, listing.item.getDisplayName(), bidAmount, "Bids: " + listing.bidCount + ", Seller: " + listing.sellerName); return null; // Success @@ -472,97 +519,107 @@ public class AuctionController implements IAuctionHandler { return "Listing not found."; } - // Check status and expiry - if (!listing.status.canBid()) { - return "This auction has ended."; - } - - if (listing.isExpired()) { - return "This auction has ended."; - } - - if (!listing.hasBuyout()) { - return "This auction does not have a buyout price."; - } - - // CRITICAL: Prevent buying own items - if (listing.isSeller(playerUUID)) { - return "You cannot buy your own auction."; - } - - // Check currency - PlayerData playerData = PlayerData.get(player); - if (playerData == null) { - return "Could not access player data."; - } - - PlayerTradeData currency = playerData.tradeData; - - // If buyer is already the high bidder, only charge the difference - boolean isCurrentBidder = listing.isHighBidder(playerUUID); - long amountToCharge = isCurrentBidder - ? (listing.buyoutPrice - listing.currentBid) - : listing.buyoutPrice; - - if (!currency.canAfford(amountToCharge)) { - if (isCurrentBidder) { - return "You cannot afford the remaining " + amountToCharge + " " + ConfigMarket.CurrencyName + " for buyout."; + synchronized (listing) { + // Check status and expiry + if (!listing.status.canBid()) { + return "This auction has ended."; } - return "You cannot afford the buyout price."; + + if (listing.isExpired()) { + return "This auction has ended."; + } + + if (!listing.hasBuyout()) { + return "This auction does not have a buyout price."; + } + + // CRITICAL: Prevent buying own items + if (listing.isSeller(playerUUID)) { + return "You cannot buy your own auction."; + } + + // Check currency + PlayerData playerData = PlayerData.get(player); + if (playerData == null) { + return "Could not access player data."; + } + + PlayerTradeData currency = playerData.tradeData; + + // If buyer is already the high bidder, only charge the difference + boolean isCurrentBidder = listing.isHighBidder(playerUUID); + long amountToCharge = isCurrentBidder + ? (listing.buyoutPrice - listing.currentBid) + : listing.buyoutPrice; + + if (!currency.canAfford(amountToCharge)) { + if (isCurrentBidder) { + return "You cannot afford the remaining " + amountToCharge + " " + ConfigMarket.CurrencyName + " for buyout."; + } + return "You cannot afford the buyout price."; + } + + // Fire auction buyout event (cancelable) + PlayerDataScript buyoutHandler = ScriptController.Instance.getPlayerScripts(player); + IPlayer iBuyer = (IPlayer) NpcAPI.Instance().getIEntity(player); + AuctionEvent.BuyoutEvent buyoutEvent = new AuctionEvent.BuyoutEvent(iBuyer, listing); + if (EventHooks.onAuctionBuyout(buyoutHandler, buyoutEvent)) { + return "Buyout was cancelled."; + } + + // Deduct buyout amount + if (!currency.withdraw(amountToCharge)) { + return "Failed to deduct buyout amount."; + } + + // Refund previous bidder if any AND they're not the buyer (no item - auction ended via buyout) + if (listing.hasBids() && listing.highBidderUUID != null && !isCurrentBidder) { + AuctionClaim refundClaim = AuctionClaim.createRefundClaim( + listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, + listing.item.getDisplayName(), playerName, null); + addClaimToPlayer(listing.highBidderUUID, refundClaim); + + // Remove from previous bidder's active bids + removeFromPlayerBids(listing.highBidderUUID, listing.id); + + sendNotificationToPlayer(listing.highBidderUUID, EnumNotificationType.AUCTION_OUTBID, listing.id, + "The auction for " + listing.item.getDisplayName() + " was bought out. Your bid has been refunded."); + } else if (isCurrentBidder) { + // Current bidder is buying out - just remove from their active bids + removeFromPlayerBids(playerUUID, listing.id); + } + + // Create claims + AuctionClaim itemClaim = AuctionClaim.createItemWonClaim(playerUUID, playerName, listing.id, listing.item); + addClaimToPlayer(playerUUID, itemClaim); + + long saleAmount = listing.buyoutPrice; + long tax = (long) (saleAmount * ConfigMarket.SalesTaxPercent); + long sellerReceives = saleAmount - tax; + + AuctionClaim currencyClaim = AuctionClaim.createCurrencyClaim( + listing.sellerUUID, listing.sellerName, listing.id, sellerReceives, + listing.item.getDisplayName(), playerName); + addClaimToPlayer(listing.sellerUUID, currencyClaim); + + // Notifications + sendNotificationToPlayer(listing.sellerUUID, EnumNotificationType.AUCTION_SOLD, listing.id, + "Your " + listing.item.getDisplayName() + " was bought out for " + saleAmount + " " + ConfigMarket.CurrencyName + "!"); + + // Remove from seller's active listings + removeFromPlayerListings(listing.sellerUUID, listing.id); + + listing.status = EnumAuctionStatus.ENDED; + listing.highBidderUUID = playerUUID; + listing.highBidderName = playerName; + listing.currentBid = listing.buyoutPrice; + + markDirty(); + + logAuction(EnumAuctionLogAction.BUYOUT, playerName, listing.item.getDisplayName(), listing.buyoutPrice, + "Seller: " + listing.sellerName + ", Tax: " + tax); } - // Deduct buyout amount - if (!currency.withdraw(amountToCharge)) { - return "Failed to deduct buyout amount."; - } - - // Refund previous bidder if any AND they're not the buyer (no item - auction ended via buyout) - if (listing.hasBids() && listing.highBidderUUID != null && !isCurrentBidder) { - AuctionClaim refundClaim = AuctionClaim.createRefundClaim( - listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, - listing.item.getDisplayName(), playerName, null); - addClaimToPlayer(listing.highBidderUUID, refundClaim); - - // Remove from previous bidder's active bids - removeFromPlayerBids(listing.highBidderUUID, listing.id); - - sendNotificationToPlayer(listing.highBidderUUID, EnumNotificationType.AUCTION_OUTBID, listing.id, - "The auction for " + listing.item.getDisplayName() + " was bought out. Your bid has been refunded."); - } else if (isCurrentBidder) { - // Current bidder is buying out - just remove from their active bids - removeFromPlayerBids(playerUUID, listing.id); - } - - // Create claims - AuctionClaim itemClaim = AuctionClaim.createItemWonClaim(playerUUID, playerName, listing.id, listing.item); - addClaimToPlayer(playerUUID, itemClaim); - - long saleAmount = listing.buyoutPrice; - long tax = (long) (saleAmount * ConfigMarket.SalesTaxPercent); - long sellerReceives = saleAmount - tax; - - AuctionClaim currencyClaim = AuctionClaim.createCurrencyClaim( - listing.sellerUUID, listing.sellerName, listing.id, sellerReceives, - listing.item.getDisplayName(), playerName); - addClaimToPlayer(listing.sellerUUID, currencyClaim); - - // Notifications - sendNotificationToPlayer(listing.sellerUUID, EnumNotificationType.AUCTION_SOLD, listing.id, - "Your " + listing.item.getDisplayName() + " was bought out for " + saleAmount + " " + ConfigMarket.CurrencyName + "!"); - - // Remove from seller's active listings - removeFromPlayerListings(listing.sellerUUID, listing.id); - - listing.status = EnumAuctionStatus.ENDED; - listing.highBidderUUID = playerUUID; - listing.highBidderName = playerName; - listing.currentBid = listing.buyoutPrice; - - markDirty(); - - logAuction("BUYOUT", playerName, listing.item.getDisplayName(), listing.buyoutPrice, - "Seller: " + listing.sellerName + ", Tax: " + tax); - return null; // Success } @@ -615,6 +672,14 @@ public class AuctionController implements IAuctionHandler { return "Item data is missing."; } + // Fire auction claim event (cancelable) + PlayerDataScript itemClaimHandler = ScriptController.Instance.getPlayerScripts(player); + IPlayer iItemClaimer = (IPlayer) NpcAPI.Instance().getIEntity(player); + AuctionEvent.ClaimEvent itemClaimEvent = new AuctionEvent.ClaimEvent(iItemClaimer, claim); + if (EventHooks.onAuctionClaim(itemClaimHandler, itemClaimEvent)) { + return "Claim was prevented."; + } + // Check inventory space if (!player.inventory.addItemStackToInventory(claim.item.copy())) { return "Your inventory is full."; @@ -628,7 +693,7 @@ public class AuctionController implements IAuctionHandler { // Cleanup listing if no more claims cleanupListingIfComplete(claim.listingId); - logAuction("CLAIMED", player.getCommandSenderName(), claim.item.getDisplayName(), 0, "Item claimed"); + logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), claim.item.getDisplayName(), 0, "Item claimed"); return null; // Success } @@ -652,6 +717,14 @@ public class AuctionController implements IAuctionHandler { return "This is not a currency claim."; } + // Fire auction claim event (cancelable) + PlayerDataScript currencyClaimHandler = ScriptController.Instance.getPlayerScripts(player); + IPlayer iCurrencyClaimer = (IPlayer) NpcAPI.Instance().getIEntity(player); + AuctionEvent.ClaimEvent currencyClaimEvent = new AuctionEvent.ClaimEvent(iCurrencyClaimer, claim); + if (EventHooks.onAuctionClaim(currencyClaimHandler, currencyClaimEvent)) { + return "Claim was prevented."; + } + if (!playerData.tradeData.deposit(claim.currency)) { return "Failed to deposit currency."; } @@ -664,7 +737,7 @@ public class AuctionController implements IAuctionHandler { // Cleanup listing if no more claims cleanupListingIfComplete(claim.listingId); - logAuction("CLAIMED", player.getCommandSenderName(), ConfigMarket.CurrencyName, claim.currency, + logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), ConfigMarket.CurrencyName, claim.currency, claim.type == EnumClaimType.REFUND ? "Refund claimed" : "Sale proceeds claimed"); return null; // Success @@ -993,7 +1066,7 @@ public class AuctionController implements IAuctionHandler { if (expired > 0) { playerData.updateClient = true; playerData.save(); - logAuction("CLAIM_EXPIRED", player.getCommandSenderName(), "Multiple", expired, + logAuction(EnumAuctionLogAction.CLAIM_EXPIRED, player.getCommandSenderName(), "Multiple", expired, expired + " claims expired on login"); } } @@ -1041,40 +1114,12 @@ public class AuctionController implements IAuctionHandler { // Logging // ========================================= - private void logAuction(String action, String playerName, String itemName, long amount, String details) { - if (!ConfigMarket.AuctionLoggingEnabled) return; + private void logAuction(EnumAuctionLogAction action, String playerName, String itemName, long amount, String details) { + if (!action.shouldLog()) return; - boolean shouldLog = false; - switch (action) { - case "CREATED": - shouldLog = ConfigMarket.LogAuctionCreated; - break; - case "BID": - shouldLog = ConfigMarket.LogAuctionBid; - break; - case "BUYOUT": - shouldLog = ConfigMarket.LogAuctionBuyout; - break; - case "SOLD": - shouldLog = ConfigMarket.LogAuctionSold; - break; - case "EXPIRED": - shouldLog = ConfigMarket.LogAuctionExpired; - break; - case "CANCELLED": - shouldLog = ConfigMarket.LogAuctionCancelled; - break; - case "CLAIMED": - case "CLAIM_EXPIRED": - shouldLog = ConfigMarket.LogAuctionClaimed; - break; - } - - if (shouldLog) { - String logMessage = String.format("[AUCTION:%s] Player: %s, Item: %s, Amount: %d, Details: %s", - action, playerName, itemName, amount, details); - LogWriter.info(logMessage); - } + String logMessage = String.format("[AUCTION:%s] Player: %s, Item: %s, Amount: %d, Details: %s", + action.name(), playerName, itemName, amount, details); + LogWriter.info(logMessage); } // ========================================= @@ -1141,7 +1186,9 @@ public class AuctionController implements IAuctionHandler { File fileOld = new File(saveDir, "auction.dat_old"); File fileCurrent = new File(saveDir, "auction.dat"); - CompressedStreamTools.writeCompressed(compound, new FileOutputStream(fileNew)); + try (FileOutputStream fos = new FileOutputStream(fileNew)) { + CompressedStreamTools.writeCompressed(compound, fos); + } // Backup rotation if (fileOld.exists()) fileOld.delete(); @@ -1210,7 +1257,10 @@ public class AuctionController implements IAuctionHandler { } private void loadFromFile(File file) throws Exception { - NBTTagCompound compound = CompressedStreamTools.readCompressed(new FileInputStream(file)); + NBTTagCompound compound; + try (FileInputStream fis = new FileInputStream(file)) { + compound = CompressedStreamTools.readCompressed(fis); + } readFromNBT(compound); } @@ -1398,7 +1448,12 @@ public class AuctionController implements IAuctionHandler { } public void adminCancelListing(String listingId, EntityPlayer admin, String reason) { + AuctionListing listing = listings.get(listingId); + String itemName = listing != null && listing.item != null ? listing.item.getDisplayName() : "Unknown"; cancelListing(listingId, admin, true); + if (reason != null && !reason.isEmpty()) { + logAuction(EnumAuctionLogAction.CANCELLED, admin.getCommandSenderName(), itemName, 0, "Admin cancel - Reason: " + reason); + } } public void clearEndedListings() { diff --git a/src/main/java/noppes/npcs/controllers/ScriptHookController.java b/src/main/java/noppes/npcs/controllers/ScriptHookController.java index 88a12e20d..3b79637d8 100644 --- a/src/main/java/noppes/npcs/controllers/ScriptHookController.java +++ b/src/main/java/noppes/npcs/controllers/ScriptHookController.java @@ -2,6 +2,8 @@ package noppes.npcs.controllers; import noppes.npcs.api.event.IAbilityEvent; import noppes.npcs.api.event.IAnimationEvent; +import noppes.npcs.api.event.IAuctionEvent; +import noppes.npcs.api.event.IEnergyBarrierEvent; import noppes.npcs.api.event.IPlayerAbilityEvent; import noppes.npcs.api.event.IBlockEvent; import noppes.npcs.api.event.ICustomGuiEvent; @@ -123,6 +125,12 @@ public class ScriptHookController implements IScriptHookHandler { hook(NPC, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class); hook(NPC, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class); + // Energy Barrier + hook(NPC, ENERGY_BARRIER_SPAWNED, IEnergyBarrierEvent.SpawnedEvent.class); + hook(NPC, ENERGY_BARRIER_TICK, IEnergyBarrierEvent.UpdateEvent.class); + hook(NPC, ENERGY_BARRIER_HIT, IEnergyBarrierEvent.HitEvent.class); + hook(NPC, ENERGY_BARRIER_DESTROYED, IEnergyBarrierEvent.DestroyedEvent.class); + // Animation hook(NPC, ANIMATION_START, IAnimationEvent.Started.class); hook(NPC, ANIMATION_END, IAnimationEvent.Ended.class); @@ -230,6 +238,12 @@ public class ScriptHookController implements IScriptHookHandler { hook(PLAYER, ENERGY_PROJECTILE_BLOCK_IMPACT, IEnergyProjectileEvent.BlockImpactEvent.class); hook(PLAYER, ENERGY_PROJECTILE_EXPIRED, IEnergyProjectileEvent.ExpiredEvent.class); + // Energy Barrier + hook(PLAYER, ENERGY_BARRIER_SPAWNED, IEnergyBarrierEvent.SpawnedEvent.class); + hook(PLAYER, ENERGY_BARRIER_TICK, IEnergyBarrierEvent.UpdateEvent.class); + hook(PLAYER, ENERGY_BARRIER_HIT, IEnergyBarrierEvent.HitEvent.class); + hook(PLAYER, ENERGY_BARRIER_DESTROYED, IEnergyBarrierEvent.DestroyedEvent.class); + // Ability hook(PLAYER, ABILITY_START, IPlayerAbilityEvent.StartEvent.class); hook(PLAYER, ABILITY_EXECUTE, IPlayerAbilityEvent.ExecuteEvent.class); @@ -238,6 +252,13 @@ public class ScriptHookController implements IScriptHookHandler { hook(PLAYER, ABILITY_INTERRUPT, IPlayerAbilityEvent.InterruptEvent.class); hook(PLAYER, ABILITY_COMPLETE, IPlayerAbilityEvent.CompleteEvent.class); + // Auction + hook(PLAYER, AUCTION_CREATE, IAuctionEvent.CreateEvent.class); + hook(PLAYER, AUCTION_BID, IAuctionEvent.BidEvent.class); + hook(PLAYER, AUCTION_BUYOUT, IAuctionEvent.BuyoutEvent.class); + hook(PLAYER, AUCTION_CANCEL, IAuctionEvent.CancelEvent.class); + hook(PLAYER, AUCTION_CLAIM, IAuctionEvent.ClaimEvent.class); + // Profile hook(PLAYER, PROFILE_CHANGE, IPlayerEvent.ProfileEvent.Changed.class); hook(PLAYER, PROFILE_REMOVE, IPlayerEvent.ProfileEvent.Removed.class); diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java b/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java index 75a1a714a..5a950816c 100644 --- a/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java +++ b/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java @@ -13,12 +13,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import java.util.Iterator; public class AuctionBlacklist { - private static Set blacklistedItems = new HashSet<>(); - private static Set wildcardPatterns = new HashSet<>(); - private static Set blacklistedMods = new HashSet<>(); - private static Set blacklistedNBTTags = new HashSet<>(); + private static volatile Set blacklistedItems = new HashSet<>(); + private static volatile Set wildcardPatterns = new HashSet<>(); + private static volatile Set blacklistedMods = new HashSet<>(); + private static volatile Set blacklistedNBTTags = new HashSet<>(); // Built-in bound item requirement keys (always blocked) private static final String SOULBIND_KEY = "cnpc_soulbind"; @@ -28,10 +29,10 @@ public class AuctionBlacklist { private static final String BYPASS_PERMISSION = "customnpcs.auction.blacklist.bypass"; public static void reload() { - blacklistedItems.clear(); - wildcardPatterns.clear(); - blacklistedMods.clear(); - blacklistedNBTTags.clear(); + Set newItems = new HashSet<>(); + Set newWildcards = new HashSet<>(); + Set newMods = new HashSet<>(); + Set newNBTTags = new HashSet<>(); // Load blacklisted items for (String item : ConfigMarket.BlacklistedItems) { @@ -40,23 +41,29 @@ public class AuctionBlacklist { if (item.contains("*")) { String regex = "^" + item.replace(".", "\\.").replace("*", ".*") + "$"; - wildcardPatterns.add(Pattern.compile(regex)); + newWildcards.add(Pattern.compile(regex)); } else { - blacklistedItems.add(item); + newItems.add(item); } } // Load blacklisted mods for (String mod : ConfigMarket.BlacklistedMods) { if (mod == null || mod.isEmpty()) continue; - blacklistedMods.add(mod.toLowerCase().trim()); + newMods.add(mod.toLowerCase().trim()); } // Load blacklisted NBT tags for (String tag : ConfigMarket.BlacklistedNBTTags) { if (tag == null || tag.isEmpty()) continue; - blacklistedNBTTags.add(tag.trim()); + newNBTTags.add(tag.trim()); } + + // Atomic swap + blacklistedItems = newItems; + wildcardPatterns = newWildcards; + blacklistedMods = newMods; + blacklistedNBTTags = newNBTTags; } /** @@ -78,7 +85,6 @@ public class AuctionBlacklist { * Check if item is blacklisted (without bypass check) */ public static boolean isBlacklisted(ItemStack item) { - if (!ConfigMarket.BlacklistEnabled) return false; if (item == null || item.getItem() == null) return true; // Check bound items (always blocked, even if blacklist is "disabled") @@ -86,6 +92,8 @@ public class AuctionBlacklist { return true; } + if (!ConfigMarket.BlacklistEnabled) return false; + // Get registry name GameRegistry.UniqueIdentifier uid = GameRegistry.findUniqueIdentifierFor(item.getItem()); if (uid == null) return true; @@ -172,9 +180,13 @@ public class AuctionBlacklist { if (registryName.contains("*")) { String regex = "^" + registryName.replace(".", "\\.").replace("*", ".*") + "$"; - wildcardPatterns.add(Pattern.compile(regex)); + Set newWildcards = new HashSet<>(wildcardPatterns); + newWildcards.add(Pattern.compile(regex)); + wildcardPatterns = newWildcards; } else { - blacklistedItems.add(registryName); + Set newItems = new HashSet<>(blacklistedItems); + newItems.add(registryName); + blacklistedItems = newItems; } return true; } @@ -185,30 +197,55 @@ public class AuctionBlacklist { if (registryName.contains("*")) { String regex = "^" + registryName.replace(".", "\\.").replace("*", ".*") + "$"; - return wildcardPatterns.removeIf(p -> p.pattern().equals(regex)); + Set newWildcards = new HashSet<>(wildcardPatterns); + boolean removed = false; + Iterator it = newWildcards.iterator(); + while (it.hasNext()) { + if (it.next().pattern().equals(regex)) { + it.remove(); + removed = true; + } + } + wildcardPatterns = newWildcards; + return removed; } else { - return blacklistedItems.remove(registryName); + Set newItems = new HashSet<>(blacklistedItems); + boolean removed = newItems.remove(registryName); + blacklistedItems = newItems; + return removed; } } public static boolean addMod(String modId) { if (modId == null || modId.isEmpty()) return false; - return blacklistedMods.add(modId.toLowerCase().trim()); + Set newMods = new HashSet<>(blacklistedMods); + boolean added = newMods.add(modId.toLowerCase().trim()); + blacklistedMods = newMods; + return added; } public static boolean removeMod(String modId) { if (modId == null || modId.isEmpty()) return false; - return blacklistedMods.remove(modId.toLowerCase().trim()); + Set newMods = new HashSet<>(blacklistedMods); + boolean removed = newMods.remove(modId.toLowerCase().trim()); + blacklistedMods = newMods; + return removed; } public static boolean addNBTTag(String tag) { if (tag == null || tag.isEmpty()) return false; - return blacklistedNBTTags.add(tag.trim()); + Set newTags = new HashSet<>(blacklistedNBTTags); + boolean added = newTags.add(tag.trim()); + blacklistedNBTTags = newTags; + return added; } public static boolean removeNBTTag(String tag) { if (tag == null || tag.isEmpty()) return false; - return blacklistedNBTTags.remove(tag.trim()); + Set newTags = new HashSet<>(blacklistedNBTTags); + boolean removed = newTags.remove(tag.trim()); + blacklistedNBTTags = newTags; + return removed; } // ========================================= diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java b/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java index ad4ae3238..c76aece11 100644 --- a/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java +++ b/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java @@ -140,7 +140,7 @@ public class AuctionClaim implements IAuctionClaim { @Override public boolean isCurrencyClaim() { - return type == EnumClaimType.CURRENCY; + return type.isCurrency(); } @Override diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java b/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java index 95bf2ed24..5018ec2e0 100644 --- a/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java +++ b/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java @@ -4,16 +4,12 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumChatFormatting; import noppes.npcs.constants.EnumAuctionSort; -import java.util.regex.Pattern; - /** * Filter for auction listings. * Supports search by item name or seller name and sorting options. */ public class AuctionFilter { - private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§[0-9a-fk-or]", Pattern.CASE_INSENSITIVE); - - public String searchText; + private String searchText; public EnumAuctionSort sortBy; // Cached normalized search for efficiency @@ -32,9 +28,8 @@ public class AuctionFilter { /** Normalize string for search (strip colors, lowercase, trim) */ public static String normalizeForSearch(String input) { if (input == null || input.isEmpty()) return ""; - String stripped = COLOR_CODE_PATTERN.matcher(input).replaceAll(""); - stripped = EnumChatFormatting.getTextWithoutFormattingCodes(stripped); - return stripped.toLowerCase().trim(); + String stripped = EnumChatFormatting.getTextWithoutFormattingCodes(input); + return stripped != null ? stripped.toLowerCase().trim() : input.toLowerCase().trim(); } /** Check if item name or seller name matches search text */ diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionNotification.java b/src/main/java/noppes/npcs/controllers/data/AuctionNotification.java deleted file mode 100644 index 01ebd1e47..000000000 --- a/src/main/java/noppes/npcs/controllers/data/AuctionNotification.java +++ /dev/null @@ -1,64 +0,0 @@ -package noppes.npcs.controllers.data; - -import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.constants.EnumNotificationType; - -import java.util.UUID; - -public class AuctionNotification { - public String id; - public UUID playerUUID; - public EnumNotificationType type; - public String listingId; - public String message; - public long timestamp; - public boolean sent; - - public AuctionNotification() { - this.id = UUID.randomUUID().toString(); - this.timestamp = System.currentTimeMillis(); - this.sent = false; - } - - public AuctionNotification(UUID playerUUID, EnumNotificationType type, String listingId, String message) { - this(); - this.playerUUID = playerUUID; - this.type = type; - this.listingId = listingId; - this.message = message; - } - - public boolean isForPlayer(UUID uuid) { - return playerUUID != null && playerUUID.equals(uuid); - } - - public NBTTagCompound writeToNBT(NBTTagCompound compound) { - compound.setString("ID", id); - compound.setString("PlayerUUID", playerUUID != null ? playerUUID.toString() : ""); - compound.setInteger("Type", type.ordinal()); - compound.setString("ListingID", listingId != null ? listingId : ""); - compound.setString("Message", message != null ? message : ""); - compound.setLong("Timestamp", timestamp); - compound.setBoolean("Sent", sent); - return compound; - } - - public void readFromNBT(NBTTagCompound compound) { - id = compound.getString("ID"); - - String playerUUIDStr = compound.getString("PlayerUUID"); - playerUUID = playerUUIDStr.isEmpty() ? null : UUID.fromString(playerUUIDStr); - - type = EnumNotificationType.fromOrdinal(compound.getInteger("Type")); - listingId = compound.getString("ListingID"); - message = compound.getString("Message"); - timestamp = compound.getLong("Timestamp"); - sent = compound.getBoolean("Sent"); - } - - public static AuctionNotification fromNBT(NBTTagCompound compound) { - AuctionNotification notification = new AuctionNotification(); - notification.readFromNBT(compound); - return notification; - } -} diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerTradeData.java b/src/main/java/noppes/npcs/controllers/data/PlayerTradeData.java index a0a8e0bb7..2850ddb90 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerTradeData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerTradeData.java @@ -8,7 +8,6 @@ import noppes.npcs.api.handler.data.IAuctionClaim; import noppes.npcs.config.ConfigMarket; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -129,12 +128,13 @@ public class PlayerTradeData implements IPlayerTradeData { // Built-in currency long newBalance = this.balance + amount; - if (newBalance > ConfigMarket.MaxBalance) { - return false; - } + // Overflow protection if (newBalance < this.balance) { - // Overflow protection - return false; + newBalance = ConfigMarket.MaxBalance; + } + // Cap at MaxBalance instead of rejecting + if (newBalance > ConfigMarket.MaxBalance) { + newBalance = ConfigMarket.MaxBalance; } this.balance = newBalance; this.lifetimeEarned += amount; @@ -341,18 +341,16 @@ public class PlayerTradeData implements IPlayerTradeData { */ public int processExpiredClaims() { int expirationDays = ConfigMarket.ClaimExpirationDays; - int removed = 0; - - Iterator iterator = claims.iterator(); - while (iterator.hasNext()) { - AuctionClaim claim = iterator.next(); + List expired = new ArrayList<>(); + for (AuctionClaim claim : claims) { if (!claim.claimed && claim.isExpired(expirationDays)) { - claims.remove(claim); - removed++; + expired.add(claim); } } - - return removed; + if (!expired.isEmpty()) { + claims.removeAll(expired); + } + return expired.size(); } // ========================================= diff --git a/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java b/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java new file mode 100644 index 000000000..8cebf5a6c --- /dev/null +++ b/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java @@ -0,0 +1,131 @@ +package noppes.npcs.scripted.event.player; + +import cpw.mods.fml.common.eventhandler.Cancelable; +import noppes.npcs.api.entity.IPlayer; +import noppes.npcs.api.event.IAuctionEvent; +import noppes.npcs.api.handler.data.IAuctionClaim; +import noppes.npcs.api.handler.data.IAuctionListing; +import noppes.npcs.api.item.IItemStack; +import noppes.npcs.constants.EnumScriptType; + +public class AuctionEvent extends PlayerEvent implements IAuctionEvent { + + public AuctionEvent(IPlayer player) { + super(player); + } + + @Override + public String getHookName() { + return "auctionEvent"; + } + + @Cancelable + public static class CreateEvent extends AuctionEvent implements IAuctionEvent.CreateEvent { + public final IItemStack item; + public final long startingPrice; + public final long buyoutPrice; + + public CreateEvent(IPlayer player, IItemStack item, long startingPrice, long buyoutPrice) { + super(player); + this.item = item; + this.startingPrice = startingPrice; + this.buyoutPrice = buyoutPrice; + } + + @Override + public String getHookName() { + return EnumScriptType.AUCTION_CREATE.function; + } + + @Override + public IItemStack getItem() { return item; } + + @Override + public long getStartingPrice() { return startingPrice; } + + @Override + public long getBuyoutPrice() { return buyoutPrice; } + } + + @Cancelable + public static class BidEvent extends AuctionEvent implements IAuctionEvent.BidEvent { + public final IAuctionListing listing; + public final long bidAmount; + + public BidEvent(IPlayer player, IAuctionListing listing, long bidAmount) { + super(player); + this.listing = listing; + this.bidAmount = bidAmount; + } + + @Override + public String getHookName() { + return EnumScriptType.AUCTION_BID.function; + } + + @Override + public IAuctionListing getListing() { return listing; } + + @Override + public long getBidAmount() { return bidAmount; } + } + + @Cancelable + public static class BuyoutEvent extends AuctionEvent implements IAuctionEvent.BuyoutEvent { + public final IAuctionListing listing; + + public BuyoutEvent(IPlayer player, IAuctionListing listing) { + super(player); + this.listing = listing; + } + + @Override + public String getHookName() { + return EnumScriptType.AUCTION_BUYOUT.function; + } + + @Override + public IAuctionListing getListing() { return listing; } + } + + @Cancelable + public static class CancelEvent extends AuctionEvent implements IAuctionEvent.CancelEvent { + public final IAuctionListing listing; + public final boolean admin; + + public CancelEvent(IPlayer player, IAuctionListing listing, boolean isAdmin) { + super(player); + this.listing = listing; + this.admin = isAdmin; + } + + @Override + public String getHookName() { + return EnumScriptType.AUCTION_CANCEL.function; + } + + @Override + public IAuctionListing getListing() { return listing; } + + @Override + public boolean isAdmin() { return admin; } + } + + @Cancelable + public static class ClaimEvent extends AuctionEvent implements IAuctionEvent.ClaimEvent { + public final IAuctionClaim claim; + + public ClaimEvent(IPlayer player, IAuctionClaim claim) { + super(player); + this.claim = claim; + } + + @Override + public String getHookName() { + return EnumScriptType.AUCTION_CLAIM.function; + } + + @Override + public IAuctionClaim getClaim() { return claim; } + } +} diff --git a/src/main/java/noppes/npcs/util/AuctionFormatUtil.java b/src/main/java/noppes/npcs/util/AuctionFormatUtil.java new file mode 100644 index 000000000..a92193815 --- /dev/null +++ b/src/main/java/noppes/npcs/util/AuctionFormatUtil.java @@ -0,0 +1,68 @@ +package noppes.npcs.util; + +import net.minecraft.util.StatCollector; +import noppes.npcs.client.AuctionClientConfig; + +/** + * Shared formatting utilities for the auction system. + * Consolidates duplicated format methods from GUI classes. + */ +public class AuctionFormatUtil { + // Time constants in milliseconds + public static final long MS_PER_SECOND = 1000L; + public static final long MS_PER_MINUTE = 60L * MS_PER_SECOND; + public static final long MS_PER_HOUR = 60L * MS_PER_MINUTE; + public static final long MS_PER_DAY = 24L * MS_PER_HOUR; + + /** + * Format currency amount with commas (e.g., 1000 -> "1,000"). + * Does NOT append currency name. + */ + public static String formatCurrency(long amount) { + if (amount < 1000) return "" + amount; + StringBuilder sb = new StringBuilder(); + String str = "" + amount; + int count = 0; + for (int i = str.length() - 1; i >= 0; i--) { + if (count > 0 && count % 3 == 0) sb.insert(0, ','); + sb.insert(0, str.charAt(i)); + count++; + } + return sb.toString(); + } + + /** + * Format currency amount with commas and append currency name. + * (e.g., 1000 -> "1,000 Gold") + */ + public static String formatCurrencyWithName(long amount) { + return formatCurrency(amount) + " " + AuctionClientConfig.getCurrencyName(); + } + + /** + * Format time remaining as a compact string (e.g., "2d 5h 30m"). + * Uses localized "Ended" text when time is <= 0. + */ + public static String formatTimeRemaining(long ms) { + if (ms <= 0) return StatCollector.translateToLocal("auction.ended"); + + long seconds = (ms / MS_PER_SECOND) % 60; + long minutes = (ms / MS_PER_MINUTE) % 60; + long hours = (ms / MS_PER_HOUR) % 24; + long days = ms / MS_PER_DAY; + + StringBuilder sb = new StringBuilder(); + if (days > 0) sb.append(days).append("d "); + if (hours > 0 || days > 0) sb.append(hours).append("h "); + if (minutes > 0 || hours > 0 || days > 0) sb.append(minutes).append("m"); + else sb.append(seconds).append("s"); + return sb.toString(); + } + + /** + * Check if time remaining is urgent (less than 1 hour). + */ + public static boolean isTimeUrgent(long ms) { + return ms > 0 && ms < MS_PER_HOUR; + } +} From ca5461c94f6839fe402cb6e0b28188c8ac38898c Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 04:57:28 -0500 Subject: [PATCH 05/12] Ability Wheel Accepts - Chained Abilities Lang File Cleanup / Merging Chained Ability Icon System --- .../npcs/controllers/AbilityController.java | 10 ++ .../data/ability/ChainedAbility.java | 44 +++++-- .../ability/IChainedAbilityFieldProvider.java | 25 ++++ .../advanced/SubGuiChainedAbilityConfig.java | 120 +++++++++++++++--- .../client/gui/player/SubGuiAuctionBid.java | 2 +- .../assets/customnpcs/lang/en_US.lang | 28 +--- 6 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 13101f599..4356ac271 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -7,6 +7,7 @@ import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.IAbilityExtender; import kamkeel.npcs.controllers.data.ability.IAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.UserType; import kamkeel.npcs.controllers.data.ability.type.*; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam; @@ -46,6 +47,7 @@ public class AbilityController implements IAbilityHandler { // ── Extension Points ───────────────────────────────────────────────────── private final Map> externalVariants = new LinkedHashMap<>(); private final List fieldProviders = new ArrayList<>(); + private final List chainedFieldProviders = new ArrayList<>(); private final List extenders = new ArrayList<>(); private final List> flightCheckers = new ArrayList<>(); @@ -807,6 +809,14 @@ public class AbilityController implements IAbilityHandler { return fieldProviders; } + public void registerChainedFieldProvider(IChainedAbilityFieldProvider provider) { + chainedFieldProviders.add(provider); + } + + public List getChainedFieldProviders() { + return chainedFieldProviders; + } + public void registerExtender(IAbilityExtender extender) { extenders.add(extender); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java index 17b4f89d9..eff65e8a9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java @@ -44,15 +44,15 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { /** Maximum range for NPC eligibility check. */ private float maxRange = 20; - /** Which entity types can use this chained ability. */ - private UserType allowedBy = UserType.BOTH; - /** Chain-level conditions (individual ability conditions are ignored). */ private List conditions = new ArrayList<>(); /** Ordered list of ability entries to execute. */ private List entries = new ArrayList<>(); + /** Extension data for external mods (e.g., icons, DBC stats). */ + private NBTTagCompound customData = new NBTTagCompound(); + public ChainedAbility() {} public ChainedAbility(String name) { @@ -145,12 +145,27 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { this.maxRange = Math.max(0, maxRange); } + /** + * Compute the allowed user type from child abilities. + * A chain allows players if ALL resolved entries allow players. + * A chain allows NPCs if ALL resolved entries allow NPCs. + * Unresolvable entries are skipped (don't restrict). + */ public UserType getAllowedBy() { - return allowedBy; - } - - public void setAllowedBy(UserType allowedBy) { - this.allowedBy = allowedBy != null ? allowedBy : UserType.BOTH; + if (entries.isEmpty()) return UserType.BOTH; + boolean allAllowPlayer = true; + boolean allAllowNpc = true; + for (ChainedAbilityEntry entry : entries) { + Ability a = entry.resolve(); + if (a == null) continue; + UserType ut = a.getAllowedBy(); + if (!ut.allowsPlayer()) allAllowPlayer = false; + if (!ut.allowsNpc()) allAllowNpc = false; + } + if (allAllowPlayer && allAllowNpc) return UserType.BOTH; + if (allAllowPlayer) return UserType.PLAYER_ONLY; + if (allAllowNpc) return UserType.NPC_ONLY; + return UserType.NPC_ONLY; } public List getConditions() { @@ -169,6 +184,10 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { this.entries = entries != null ? entries : new ArrayList<>(); } + public NBTTagCompound getCustomData() { + return customData; + } + // ═══════════════════════════════════════════════════════════════════ // IChainedAbility INTERFACE // ═══════════════════════════════════════════════════════════════════ @@ -343,7 +362,6 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { nbt.setInteger("CooldownTicks", cooldownTicks); nbt.setFloat("MinRange", minRange); nbt.setFloat("MaxRange", maxRange); - nbt.setInteger("AllowedBy", allowedBy.ordinal()); // Conditions NBTTagList condList = new NBTTagList(); @@ -359,6 +377,9 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { } nbt.setTag("Entries", entryList); + // Custom data (for external mods) + nbt.setTag("customData", (NBTTagCompound) customData.copy()); + return nbt; } @@ -372,7 +393,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { cooldownTicks = nbt.hasKey("CooldownTicks") ? nbt.getInteger("CooldownTicks") : 100; minRange = nbt.getFloat("MinRange"); maxRange = nbt.hasKey("MaxRange") ? nbt.getFloat("MaxRange") : 20; - allowedBy = UserType.fromOrdinal(nbt.getInteger("AllowedBy")); + // AllowedBy is computed from child abilities — old NBT data silently ignored // Conditions conditions.clear(); @@ -393,5 +414,8 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { entries.add(entry); } } + + // Custom data (for external mods) + customData = nbt.getCompoundTag("customData"); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java b/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java new file mode 100644 index 000000000..95e8b70e0 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java @@ -0,0 +1,25 @@ +package kamkeel.npcs.controllers.data.ability; + +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.List; + +/** + * External field definition provider for chained abilities. + * Called during GUI initialization to allow mods to inject custom tabs/fields + * into the chained ability configuration GUI. + *

+ * Note: Although this interface references FieldDef (client-only), it is safe to + * register on both sides because the generic type is erased at bytecode level. + * The addFieldDefinitions method is only ever called from client-side GUI code. + */ +public interface IChainedAbilityFieldProvider { + /** + * Add field definitions to the chained ability's GUI. + * Fields added here should use .tab() to assign them to a custom tab. + * + * @param chain The chained ability being configured + * @param defs The mutable list of field definitions to add to + */ + void addFieldDefinitions(ChainedAbility chain, List defs); +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index 83a091a87..ccef30b0d 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.Condition; +import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.client.gui.GuiButton; import net.minecraft.nbt.NBTTagCompound; @@ -25,11 +26,14 @@ import noppes.npcs.client.gui.util.SubGuiInterface; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * SubGui for editing a {@link ChainedAbility}. - * Three tabs: General, Entries, Conditions. + * Core tabs: General, Entries, Conditions. + * Additional tabs are injected dynamically by {@link IChainedAbilityFieldProvider}s. */ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements ITextfieldListener, ISubGuiListener, IAbilityConfigCallback { @@ -38,15 +42,17 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private static final int CLEAR_ID_START = 2000; private static final int LABEL_ID_START = 3000; - // ── Tab constants ───────────────────────────────────────────────────────── + // ── Tab constants (core tabs) ──────────────────────────────────────────── private static final int TAB_GENERAL = 0; private static final int TAB_ENTRIES = 1; private static final int TAB_CONDITIONS = 2; + private static final int CORE_TAB_COUNT = 3; // ── Tab button IDs ──────────────────────────────────────────────────────── private static final int BTN_TAB_GENERAL = 90; private static final int BTN_TAB_ENTRIES = 91; private static final int BTN_TAB_CONDITIONS = 92; + private static final int BTN_TAB_EXTRA_BASE = 93; private static final int BTN_CLOSE = -1000; // ── Entries tab: per-entry ID encoding ───────────────────────────────────── @@ -80,8 +86,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private List entries; private List conditions; + // Dynamic tabs injected by field providers (e.g., "Icon") + private List extraTabs = new ArrayList<>(); + private GuiFieldBuilder builder; - private float[] tabScrollY = new float[3]; + private float[] tabScrollY; // Entry editing private int editingEntryIndex = -1; @@ -128,7 +137,57 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText FieldDef.floatField("ability.minRange", chain::getMinRange, chain::setMinRange).range(0, 64), FieldDef.floatField("ability.maxRange", chain::getMaxRange, chain::setMaxRange).range(0, 64) ).tab("General")); - fieldDefs.add(FieldDef.enumField("ability.allowedBy", UserType.class, chain::getAllowedBy, chain::setAllowedBy).tab("General")); + + // Computed compatibility label (derived from child abilities) + fieldDefs.add(FieldDef.labelField("ability.compatible", () -> { + UserType ut = computeAllowedBy(); + return StatCollector.translateToLocal("ability.compatible") + ": \u00A7e" + ut.name(); + }).tab("General")); + + // External field providers (e.g., DBC Addon injecting an "Icon" tab) + if (AbilityController.Instance != null) { + for (IChainedAbilityFieldProvider provider : AbilityController.Instance.getChainedFieldProviders()) { + provider.addFieldDefinitions(chain, fieldDefs); + } + } + + // Collect extra tab names from provider-injected fields + Set tabNames = new LinkedHashSet<>(); + for (FieldDef def : fieldDefs) { + String tab = def.getTab(); + if (tab != null && !tab.isEmpty() && !"General".equals(tab)) { + tabNames.add(tab); + } + } + extraTabs = new ArrayList<>(tabNames); + + // Size scroll array for all tabs + tabScrollY = new float[CORE_TAB_COUNT + extraTabs.size()]; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // COMPUTED USERTYPE + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Compute the allowed UserType from the current (possibly modified) entries list. + * Mirrors ChainedAbility.getAllowedBy() but uses the local entries. + */ + private UserType computeAllowedBy() { + if (entries.isEmpty()) return UserType.BOTH; + boolean allAllowPlayer = true; + boolean allAllowNpc = true; + for (ChainedAbilityEntry entry : entries) { + Ability a = entry.resolve(); + if (a == null) continue; + UserType ut = a.getAllowedBy(); + if (!ut.allowsPlayer()) allAllowPlayer = false; + if (!ut.allowsNpc()) allAllowNpc = false; + } + if (allAllowPlayer && allAllowNpc) return UserType.BOTH; + if (allAllowPlayer) return UserType.PLAYER_ONLY; + if (allAllowNpc) return UserType.NPC_ONLY; + return UserType.NPC_ONLY; } // ═══════════════════════════════════════════════════════════════════════════ @@ -145,7 +204,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText super.initGui(); - // Tab buttons + // Tab buttons — core tabs GuiMenuTopButton generalTab = new GuiMenuTopButton(BTN_TAB_GENERAL, guiLeft + 4, guiTop - 17, "menu.general"); generalTab.active = (activeTab == TAB_GENERAL); addTopButton(generalTab); @@ -158,6 +217,16 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText conditionsTab.active = (activeTab == TAB_CONDITIONS); addTopButton(conditionsTab); + // Dynamic extra tabs from field providers + GuiMenuTopButton prevTab = conditionsTab; + for (int i = 0; i < extraTabs.size(); i++) { + int tabIndex = CORE_TAB_COUNT + i; + GuiMenuTopButton extraTab = new GuiMenuTopButton(BTN_TAB_EXTRA_BASE + i, prevTab, extraTabs.get(i)); + extraTab.active = (activeTab == tabIndex); + addTopButton(extraTab); + prevTab = extraTab; + } + GuiMenuTopButton closeBtn = new GuiMenuTopButton(BTN_CLOSE, guiLeft + xSize - 22, guiTop - 17, "X"); addTopButton(closeBtn); @@ -168,22 +237,27 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText int swH = ySize - 10; if (activeTab == TAB_GENERAL) { - buildGeneralTab(swX, swY, swW, swH); + buildFieldDefTab("General", swX, swY, swW, swH); } else if (activeTab == TAB_ENTRIES) { buildEntriesTab(swX, swY, swW, swH); } else if (activeTab == TAB_CONDITIONS) { buildConditionsTab(swX, swY, swW, swH); + } else if (activeTab >= CORE_TAB_COUNT) { + int extraIndex = activeTab - CORE_TAB_COUNT; + if (extraIndex < extraTabs.size()) { + buildFieldDefTab(extraTabs.get(extraIndex), swX, swY, swW, swH); + } } } // ═══════════════════════════════════════════════════════════════════════════ - // GENERAL TAB + // FIELDDEF-BASED TAB (General + dynamic provider tabs) // ═══════════════════════════════════════════════════════════════════════════ - private void buildGeneralTab(int swX, int swY, int swW, int swH) { + private void buildFieldDefTab(String tabName, int swX, int swY, int swW, int swH) { List tabFields = new ArrayList<>(); for (FieldDef def : fieldDefs) { - if ("General".equals(def.getTab()) && def.isVisible()) { + if (tabName.equals(def.getTab()) && def.isVisible()) { tabFields.add(def); } } @@ -342,12 +416,19 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText public void buttonEvent(GuiButton guibutton) { int id = guibutton.id; - // Tab switching + // Tab switching — core tabs if (id == BTN_TAB_GENERAL) { activeTab = TAB_GENERAL; initGui(); return; } if (id == BTN_TAB_ENTRIES) { activeTab = TAB_ENTRIES; initGui(); return; } if (id == BTN_TAB_CONDITIONS) { activeTab = TAB_CONDITIONS; initGui(); return; } if (id == BTN_CLOSE) { saveOnClose = true; close(); return; } + // Tab switching — extra tabs + if (id >= BTN_TAB_EXTRA_BASE && id < BTN_TAB_EXTRA_BASE + extraTabs.size()) { + activeTab = CORE_TAB_COUNT + (id - BTN_TAB_EXTRA_BASE); + initGui(); + return; + } + // Entries tab if (activeTab == TAB_ENTRIES) { if (handleEntryButton(id)) return; @@ -358,8 +439,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText if (handleConditionButton(id)) return; } - // General tab — declarative field handling - if (activeTab == TAB_GENERAL && builder != null) { + // FieldDef-based tabs (General + extra tabs) — declarative field handling + if (isFieldDefTab() && builder != null) { if (builder.handleButtonEvent(id, guibutton)) { if (!hasSubGui()) { initGui(); @@ -369,6 +450,13 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } } + /** + * Whether the current active tab uses the declarative FieldDef system. + */ + private boolean isFieldDefTab() { + return activeTab == TAB_GENERAL || activeTab >= CORE_TAB_COUNT; + } + private boolean handleEntryButton(int id) { // Add entry if (id == BTN_ADD_ENTRY) { @@ -456,8 +544,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText public void unFocused(GuiNpcTextField textField) { int id = textField.id; - // General tab — declarative fields - if (activeTab == TAB_GENERAL && builder != null && id >= DECLARATIVE_ID_START) { + // FieldDef-based tabs — declarative fields + if (isFieldDefTab() && builder != null && id >= DECLARATIVE_ID_START) { if (builder.handleTextFieldEvent(id, textField)) { initGui(); } @@ -479,8 +567,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText @Override public void subGuiClosed(SubGuiInterface subgui) { - // General tab — declarative sub-gui handling - if (activeTab == TAB_GENERAL && builder != null) { + // FieldDef-based tabs — declarative sub-gui handling + if (isFieldDefTab() && builder != null) { if (builder.handleSubGuiClosed(subgui)) { initGui(); return; diff --git a/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java b/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java index ae24a9ae9..8d7ef3ed4 100644 --- a/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java +++ b/src/main/java/noppes/npcs/client/gui/player/SubGuiAuctionBid.java @@ -78,7 +78,7 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe int totalBtnWidth = btnWidth * 2 + btnSpacing; int btnX = centerX - totalBtnWidth / 2; - addButton(new GuiNpcButton(btnBidId, btnX, y, btnWidth, 20, "auction.bid.placeBid")); + addButton(new GuiNpcButton(btnBidId, btnX, y, btnWidth, 20, "auction.bid.place")); addButton(new GuiNpcButton(btnCancelId, btnX + btnWidth + btnSpacing, y, btnWidth, 20, "gui.cancel")); } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 51873686d..2778a1ec7 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -234,7 +234,7 @@ party.partying=Partying! party.messageNoParty=You're not in a party! Either wait for an invite or create one. party.partyRequirements=Required by party.setQuestConfirm=Setting a Quest to a party will clear its progress! -party.dropQuestConfirm=Are you sure you want to drop the party quest?` +party.dropQuestConfirm=Are you sure you want to drop the party quest? settings.chatBubbles=Chat Bubbles settings.trackAlignment=Tracking Alignment @@ -483,9 +483,9 @@ stock.reset.never=Never stock.reset.mcdaily=MC Daily stock.reset.mcweekly=MC Weekly stock.reset.mccustom=MC Custom -stock.reset.rldaily=Real Daily -stock.reset.rlweekly=Real Weekly -stock.reset.rlcustom=Real Custom +stock.reset.rldaily=RL Daily +stock.reset.rlweekly=RL Weekly +stock.reset.rlcustom=RL Custom stock.options.hover=Configure stock limits and reset timers stock.enable.hover=Enable limited stock for trade slots @@ -534,7 +534,7 @@ stats.respawn=Respawn stats.deadbody=HideDeadBody stats.fireimmune=ImmuneToFire stats.candrown=Drowns -stats.firesound:=SoundFX +stats.firesound=SoundFX stats.creaturetype=CreatureType stats.normal=Normal stats.reverse=Reverse @@ -1518,17 +1518,13 @@ ability.globalCooldown=Global Cooldown ability.minCooldown=Min CD ability.maxCooldown=Max CD ability.edit=Edit Ability -ability.weight=Weight ability.targeting=Targeting -ability.minRange=Min Range -ability.maxRange=Max Range ability.timing=Timing (ticks) ability.cooldown=Cooldown ability.windup=Wind Up ability.syncOn=Sync: On ability.syncOff=Sync: Off ability.dazed=Dazed Ticks -ability.interruptible=Interruptible ability.threshold=Threshold ability.lockMove=Lock Move ability.lockMove.windup=Windup @@ -1637,15 +1633,10 @@ ability.tab.timing=Timing ability.tab.effects=Effects ability.tab.telegraph=Telegraph ability.tab.visual=Visual -ability.showTelegraph=Show Telegraph ability.telegraphColor=Color ability.telegraphSizeNote=Size is based on ability parameters # Ability Effects Tab -ability.windUpSound=Wind Up Sound -ability.activeSound=Active Sound -ability.windUpAnim=Wind Up Anim -ability.activeAnim=Active Anim ability.cnpc.slam=Slam ability.cnpc.heavy_hit=Heavy Hit @@ -1750,7 +1741,6 @@ gui.radius=Radius gui.thickness=Thickness gui.rotationSpeed=Rotation Speed gui.strength=Strength -gui.range=Range gui.delay=Delay gui.density=Density gui.length=Length @@ -2134,7 +2124,6 @@ auction.filter.search=Search auction.bid.minimum=Minimum Bid: %s auction.bid.yourBid=Your Bid: auction.bid.place=Place Bid -auction.bid.placeBid=Place Bid auction.bid.buyout=Buyout Now # Errors @@ -2206,7 +2195,6 @@ auction.trades.leftClickRebid=Left-click to rebid auction.trades.rightClickRefund=Right-click to claim refund auction.claim.soldItem=Item: %s auction.claim.buyer=Buyer: %s -auction.claim.outbidItem=Item: %s auction.claim.outbidBy=Outbid by: %s # Sell Page - Buyout Toggle @@ -2233,9 +2221,3 @@ auction.bid.yourBalance=Your Balance: %s # Auction Buyout SubGui auction.buyout.title=Buy Now auction.buyout.confirm=Purchase this item for: - -# Auction Status -auction.ended=Ended - -# Role -role.auctioneer=Auctioneer From 21e920426e123e197ca64b84a558306becad516b Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 05:06:14 -0500 Subject: [PATCH 06/12] Fix Sub Gui Spam Closing --- .../java/noppes/npcs/client/gui/util/GuiAbilityInterface.java | 3 ++- .../java/noppes/npcs/client/gui/util/GuiModelInterface2.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java b/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java index 3d0573bf2..74bc3abd0 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java @@ -364,8 +364,9 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { @Override public void keyTyped(char par1, int par2) { + boolean hadSubGui = hasSubGui(); super.keyTyped(par1, par2); - if (par2 == 1) { + if (par2 == 1 && !hadSubGui) { close(); } } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java b/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java index 5ca2edfb0..df6651521 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java @@ -151,8 +151,9 @@ public class GuiModelInterface2 extends GuiNPCInterface2 { @Override public void keyTyped(char par1, int par2) { + boolean hadSubGui = hasSubGui(); super.keyTyped(par1, par2); - if (par2 == 1) { + if (par2 == 1 && !hadSubGui) { close(); } } From 1de1c4e7101325332a120e9e038f2010e91bb413 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 05:13:51 -0500 Subject: [PATCH 07/12] Reformat Code --- src/api | 2 +- .../client/renderer/RenderAbilityBeam.java | 30 +- .../client/renderer/RenderAbilityDisc.java | 2 +- .../client/renderer/RenderAbilityLaser.java | 38 +- .../client/renderer/RenderAbilitySweeper.java | 8 +- .../client/renderer/RenderAbilityZone.java | 108 +++-- .../client/renderer/RenderEnergyAbility.java | 11 +- .../client/renderer/RenderEnergyDome.java | 6 +- .../client/renderer/TelegraphRenderer.java | 4 +- .../lightning/AttachedLightningRenderer.java | 8 +- .../renderer/lightning/LightningBolt.java | 2 +- .../renderer/lightning/LightningHandler.java | 15 +- .../kamkeel/npcs/command/AbilityCommand.java | 2 +- .../npcs/command/CommandKamkeelBase.java | 8 +- .../npcs/controllers/AbilityController.java | 31 +- .../npcs/controllers/SyncController.java | 4 +- .../controllers/data/ability/Ability.java | 103 ++-- .../data/ability/AbilityAction.java | 7 +- .../controllers/data/ability/AbilitySlot.java | 7 +- .../data/ability/AbilityVariant.java | 2 +- .../controllers/data/ability/AnchorPoint.java | 24 +- .../data/ability/ChainedAbility.java | 35 +- .../data/ability/ChainedAbilityEntry.java | 17 +- .../data/ability/PreviewEntityHandler.java | 1 + .../data/ability/TargetingMode.java | 15 +- .../data/ability/data/EnergyBarrierData.java | 56 ++- .../data/ability/data/EnergyCombatData.java | 3 +- .../data/ability/data/EnergyDisplayData.java | 3 +- .../data/ability/data/EnergyHomingData.java | 3 +- .../data/ability/data/EnergyLifespanData.java | 3 +- .../ability/data/EnergyLightningData.java | 3 +- .../data/ability/data/EnergyPanelData.java | 66 ++- .../ability/data/EnergyTrajectoryData.java | 10 +- .../data/ability/gui/AbilityFieldBuilder.java | 4 +- .../data/ability/type/AbilityCharge.java | 5 +- .../data/ability/type/AbilityCutter.java | 21 +- .../data/ability/type/AbilityDash.java | 12 +- .../data/ability/type/AbilityGuard.java | 28 +- .../data/ability/type/AbilityHazard.java | 55 ++- .../data/ability/type/AbilityHeal.java | 10 +- .../data/ability/type/AbilityHeavyHit.java | 4 +- .../data/ability/type/AbilityProjectile.java | 13 +- .../data/ability/type/AbilityShockwave.java | 7 +- .../data/ability/type/AbilitySlam.java | 8 +- .../data/ability/type/AbilityTeleport.java | 22 +- .../data/ability/type/AbilityTrap.java | 159 +++++-- .../data/ability/type/AbilityVortex.java | 11 +- .../data/ability/type/AbilityZone.java | 444 ++++++++++++++---- .../data/ability/type/energy/AbilityBeam.java | 37 +- .../data/ability/type/energy/AbilityDisc.java | 64 ++- .../data/ability/type/energy/AbilityDome.java | 19 +- .../ability/type/energy/AbilityEnergy.java | 99 +++- .../type/energy/AbilityEnergyBarrier.java | 104 +++- .../type/energy/AbilityEnergyProjectile.java | 245 +++++++--- .../ability/type/energy/AbilityLaserShot.java | 37 +- .../data/ability/type/energy/AbilityOrb.java | 28 +- .../ability/type/energy/AbilityShield.java | 19 +- .../ability/type/energy/AbilitySlicer.java | 46 +- .../ability/type/energy/AbilitySweeper.java | 91 +++- .../data/ability/type/energy/AbilityWall.java | 68 ++- .../npcs/entity/EntityAbilityBeam.java | 25 +- .../npcs/entity/EntityAbilityDisc.java | 20 +- .../npcs/entity/EntityAbilityLaser.java | 14 +- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 12 +- .../npcs/entity/EntityAbilitySweeper.java | 12 +- .../npcs/entity/EntityAbilityZone.java | 245 +++++++--- .../npcs/entity/EntityEnergyAbility.java | 144 ++++-- .../npcs/entity/EntityEnergyBarrier.java | 34 +- .../kamkeel/npcs/entity/EntityEnergyDome.java | 19 +- .../npcs/entity/EntityEnergyPanel.java | 14 +- .../npcs/entity/EntityEnergyProjectile.java | 178 +++++-- .../npcs/entity/EntityEnergySlicer.java | 35 +- .../kamkeel/npcs/network/PacketHandler.java | 14 +- .../network/packets/data/LoginPacket.java | 2 +- .../ability/PlayerAbilityStatePacket.java | 2 +- .../packets/player/AuctionActionPacket.java | 28 +- .../ability/BuiltInAbilityGetPacket.java | 2 +- .../ability/ChainedAbilityGetPacket.java | 2 +- .../ability/ChainedAbilitySavePacket.java | 2 +- .../ability/CustomAbilityGetPacket.java | 2 +- .../ability/CustomAbilitySavePacket.java | 2 +- .../animation/BuiltInAnimationGetPacket.java | 1 - .../request/clone/CloneTagListPacket.java | 2 +- .../request/script/ScriptFilesPacket.java | 2 - .../kamkeel/npcs/util/AnchorPointHelper.java | 15 +- .../java/kamkeel/npcs/util/BukkitUtil.java | 4 + src/main/java/kamkeel/npcs/util/Register.java | 10 +- .../java/kamkeel/npcs/util/VaultUtil.java | 46 +- .../noppes/npcs/AbstractDataAbilities.java | 184 ++++++-- src/main/java/noppes/npcs/CommonProxy.java | 7 +- src/main/java/noppes/npcs/CustomNpcs.java | 20 +- src/main/java/noppes/npcs/DataAbilities.java | 50 +- src/main/java/noppes/npcs/EventHooks.java | 6 +- .../noppes/npcs/ScriptPlayerEventHandler.java | 2 +- .../java/noppes/npcs/ServerTickHandler.java | 2 +- .../java/noppes/npcs/ai/CombatHandler.java | 1 - .../noppes/npcs/blocks/BlockWaypoint.java | 2 - .../npcs/client/AuctionClientConfig.java | 36 +- .../npcs/client/ClientEventHandler.java | 8 +- .../java/noppes/npcs/client/ClientProxy.java | 62 ++- .../noppes/npcs/client/ClientTickHandler.java | 6 +- .../npcs/client/ScriptClientConfig.java | 50 +- .../noppes/npcs/client/fx/ZoneParticleFX.java | 6 +- .../gui/GuiNpcMobSpawnerFullscreen.java | 6 +- .../client/gui/SubGuiNpcDialogVisual.java | 3 - .../client/gui/advanced/GuiNPCAbilities.java | 98 ++-- .../gui/advanced/SubGuiAbilityConfig.java | 61 ++- .../gui/advanced/SubGuiAbilityLoad.java | 2 +- .../advanced/SubGuiChainedAbilityConfig.java | 89 +++- .../npcs/client/gui/builder/FieldDef.java | 98 +++- .../client/gui/builder/GuiFieldBuilder.java | 103 +++- .../gui/global/GuiNpcManageAbilities.java | 67 ++- .../gui/player/AuctionTooltipHandler.java | 4 +- .../client/gui/player/GuiAuctionBidding.java | 16 +- .../gui/player/GuiAuctionInterface.java | 32 +- .../client/gui/player/GuiAuctionListing.java | 20 +- .../client/gui/player/GuiAuctionSell.java | 20 +- .../client/gui/player/GuiAuctionTrades.java | 30 +- .../gui/select/GuiAnimationSelection.java | 1 - .../gui/util/AbilityPreviewExecutor.java | 60 ++- .../client/gui/util/GuiAbilityInterface.java | 50 +- .../client/gui/util/GuiAuctionNavButton.java | 16 +- .../npcs/client/gui/util/GuiDirectory.java | 56 ++- .../client/gui/util/GuiModelInterface2.java | 2 +- .../npcs/client/gui/util/GuiNpcLabel.java | 9 +- .../npcs/client/gui/util/GuiNpcTextField.java | 34 +- .../npcs/client/gui/util/IClonerGui.java | 2 + .../java/noppes/npcs/config/ConfigMarket.java | 20 +- .../npcs/constants/EnumAuctionLogAction.java | 28 +- .../npcs/constants/EnumAuctionPage.java | 2 - .../npcs/containers/ContainerAuction.java | 1 + .../containers/ContainerAuctionBidding.java | 9 +- .../npcs/containers/ContainerAuctionSell.java | 21 +- .../containers/ContainerAuctionTrades.java | 93 +++- .../npcs/controllers/AnimationController.java | 9 +- .../npcs/controllers/AuctionConfigSync.java | 4 +- .../npcs/controllers/AuctionController.java | 64 ++- .../npcs/controllers/ScriptConfigSync.java | 2 +- .../controllers/ScriptHookController.java | 4 +- .../controllers/data/AuctionBlacklist.java | 2 +- .../npcs/controllers/data/AuctionClaim.java | 16 +- .../npcs/controllers/data/AuctionFilter.java | 33 +- .../controllers/data/BuiltInAnimation.java | 3 + .../controllers/data/PlayerAbilityData.java | 15 +- .../npcs/controllers/data/PlayerData.java | 4 +- .../npcs/controllers/data/action/Action.java | 6 +- .../data/action/action/ConditionalAction.java | 4 +- .../java/noppes/npcs/items/ItemStaff.java | 13 +- .../java/noppes/npcs/scripted/NpcAPI.java | 6 +- .../noppes/npcs/scripted/ScriptWorld.java | 8 +- .../scripted/entity/ScriptEnergyAbility.java | 99 +++- .../scripted/entity/ScriptEnergyBarrier.java | 36 +- .../scripted/entity/ScriptEnergyBeam.java | 31 +- .../scripted/entity/ScriptEnergyDisc.java | 49 +- .../scripted/entity/ScriptEnergyDome.java | 9 +- .../scripted/entity/ScriptEnergyLaser.java | 82 +++- .../scripted/entity/ScriptEnergyPanel.java | 30 +- .../entity/ScriptEnergyProjectile.java | 175 +++++-- .../scripted/entity/ScriptEnergySlicer.java | 27 +- .../npcs/scripted/entity/ScriptNpc.java | 2 +- .../scripted/event/player/AuctionEvent.java | 36 +- .../scripted/wrapper/ScriptDataAbilities.java | 2 +- .../wrapper/ScriptPlayerAbilityData.java | 2 +- .../java/somehussar/gui/AnnotatedGui.java | 2 +- .../somehussar/gui/ImmediateTextField.java | 3 +- .../gui/annotationHandling/GuiEditable.java | 7 +- .../annotationHandling/GuiFieldHandler.java | 6 +- .../constraints/NumericConstraint.java | 1 + .../field/EditableField.java | 51 +- 169 files changed, 3967 insertions(+), 1556 deletions(-) diff --git a/src/api b/src/api index ae4b84f3b..ca2980909 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit ae4b84f3baf19c2998449e4b40ac33bc6a92555f +Subproject commit ca2980909ee597fa5b062ca945a2cce533277afc diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index 725e440e5..d1b969921 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -14,12 +14,12 @@ import java.util.List; /** * Renders the AbilityBeam entity with a head, tail orb, and curving trail. * The trail shows the path the head has traveled using laser-style rectangular segments. - * + *

* IMPORTANT: Trail points are stored RELATIVE to the origin position. * We render by translating to the origin first, then drawing all points relative to that. - * + *

* Trail segments now share vertices at connection points to prevent gaps during curves. - * + *

* Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) @@ -182,7 +182,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility { * Uses averaged perpendicular vectors at connection points to prevent gaps. */ private void renderSmoothTrail(List trail, float width, int innerColor, int outerColor, - boolean outerColorEnabled, float outerColorWidth) { + boolean outerColorEnabled, float outerColorWidth) { if (trail.size() < 2) return; int trailSize = trail.size(); @@ -213,7 +213,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility { * Older trail points fade out based on their age. */ private void renderFadingTrail(EntityAbilityBeam beam, List trail, float width, int innerColor, int outerColor, - boolean outerColorEnabled, float outerColorWidth) { + boolean outerColorEnabled, float outerColorWidth) { if (trail.size() < 2) return; List ages = beam.getTrailPointAges(); @@ -242,7 +242,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility { * Render trail tube with age-based fading. */ private void renderFadingTrailTube(List trail, List perpFrames, List ages, - int fadeTime, float width, int color, float baseAlpha) { + int fadeTime, float width, int color, float baseAlpha) { float[] rgb = extractRGB(color); float r = rgb[0], g = rgb[1], b = rgb[2]; float halfWidth = width * 0.5f; @@ -392,13 +392,17 @@ public class RenderAbilityBeam extends RenderEnergyAbility { dy /= len; dz /= len; } else { - dx = 0; dy = 0; dz = 1; + dx = 0; + dy = 0; + dz = 1; } // Calculate perpendicular vectors using consistent up reference double upX = 0, upY = 1, upZ = 0; if (Math.abs(dy) > 0.9) { - upX = 1; upY = 0; upZ = 0; + upX = 1; + upY = 0; + upZ = 0; } // Cross product for horizontal perpendicular @@ -417,8 +421,12 @@ public class RenderAbilityBeam extends RenderEnergyAbility { double vertY = dz * horzX - dx * horzZ; double vertZ = dx * horzY - dy * horzX; - frame[0] = horzX; frame[1] = horzY; frame[2] = horzZ; - frame[3] = vertX; frame[4] = vertY; frame[5] = vertZ; + frame[0] = horzX; + frame[1] = horzY; + frame[2] = horzZ; + frame[3] = vertX; + frame[4] = vertY; + frame[5] = vertZ; frames.add(frame); } @@ -429,7 +437,7 @@ public class RenderAbilityBeam extends RenderEnergyAbility { * Render the trail as a continuous tube using pre-computed perpendicular frames. */ private void renderTrailTube(List trail, List perpFrames, - float width, int color, float baseAlpha, boolean fadeFromStart) { + float width, int color, float baseAlpha, boolean fadeFromStart) { float[] rgb = extractRGB(color); float r = rgb[0], g = rgb[1], b = rgb[2]; float halfWidth = width * 0.5f; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java index 76ffc6cfb..7f1a2e12c 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java @@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL11; /** * Renders the AbilityDisc entity as a flat spinning square. * Rotates only on Y axis (flat spin like a saw blade). - * + *

* Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index e26f33489..543dba44c 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -10,7 +10,7 @@ import org.lwjgl.opengl.GL11; /** * Renders the AbilityLaser entity as a rectangular beam with clear inner/outer colors. - * + *

* Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) @@ -69,7 +69,9 @@ public class RenderAbilityLaser extends RenderEnergyAbility { // Calculate perpendicular vectors (one horizontal, one vertical relative to beam) double upX = 0, upY = 1, upZ = 0; if (Math.abs(dy) > 0.9) { - upX = 1; upY = 0; upZ = 0; + upX = 1; + upY = 0; + upZ = 0; } // Cross product for horizontal perpendicular @@ -98,29 +100,29 @@ public class RenderAbilityLaser extends RenderEnergyAbility { float outerWidth = innerWidth + laser.getOuterColorWidth() * width; GL11.glDepthMask(false); renderBeamRectangle(renderStartX, renderStartY, renderStartZ, - renderEndX, renderEndY, renderEndZ, - horzX, horzY, horzZ, vertX, vertY, vertZ, - outerWidth, laser.getOuterColor(), alpha * 0.4f); + renderEndX, renderEndY, renderEndZ, + horzX, horzY, horzZ, vertX, vertY, vertZ, + outerWidth, laser.getOuterColor(), alpha * 0.4f); // Render middle layer (halfway between inner and outer) float midWidth = innerWidth + (outerWidth - innerWidth) * 0.5f; renderBeamRectangle(renderStartX, renderStartY, renderStartZ, - renderEndX, renderEndY, renderEndZ, - horzX, horzY, horzZ, vertX, vertY, vertZ, - midWidth, laser.getOuterColor(), alpha * 0.7f); + renderEndX, renderEndY, renderEndZ, + horzX, horzY, horzZ, vertX, vertY, vertZ, + midWidth, laser.getOuterColor(), alpha * 0.7f); GL11.glDepthMask(true); } // Render inner core (solid) renderBeamRectangle(renderStartX, renderStartY, renderStartZ, - renderEndX, renderEndY, renderEndZ, - horzX, horzY, horzZ, vertX, vertY, vertZ, - innerWidth, laser.getInnerColor(), alpha); + renderEndX, renderEndY, renderEndZ, + horzX, horzY, horzZ, vertX, vertY, vertZ, + innerWidth, laser.getInnerColor(), alpha); // Render lightning along the laser beam if (laser.hasLightningEffect() && laser.getCurrentLength() > 0.1f) { renderLaserLightning(laser, renderStartX, renderStartY, renderStartZ, - renderEndX, renderEndY, renderEndZ, width); + renderEndX, renderEndY, renderEndZ, width); } restoreRenderState(); @@ -179,8 +181,8 @@ public class RenderAbilityLaser extends RenderEnergyAbility { * Render lightning effects along the laser beam at multiple points. */ private void renderLaserLightning(EntityAbilityLaser laser, - double startX, double startY, double startZ, - double endX, double endY, double endZ, float width) { + double startX, double startY, double startZ, + double endX, double endY, double endZ, float width) { AttachedLightningRenderer.LightningState state = getLightningState(laser); float density = laser.getLightningDensity(); @@ -229,10 +231,10 @@ public class RenderAbilityLaser extends RenderEnergyAbility { * Render a beam as a 3D rectangular prism (box along a line). */ private void renderBeamRectangle(double x1, double y1, double z1, - double x2, double y2, double z2, - double horzX, double horzY, double horzZ, - double vertX, double vertY, double vertZ, - float width, int color, float alpha) { + double x2, double y2, double z2, + double horzX, double horzY, double horzZ, + double vertX, double vertY, double vertZ, + float width, int color, float alpha) { float[] rgb = extractRGB(color); float r = rgb[0], g = rgb[1], b = rgb[2]; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java index 10705d762..2dbeac3c0 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java @@ -104,8 +104,8 @@ public class RenderAbilitySweeper extends Render { * Uses the same approach as RenderAbilityBeam.renderBeamRectangle. */ private void renderBeamSegment(double x1, double y1, double z1, - double x2, double y2, double z2, - float width, float r, float g, float b, float a) { + double x2, double y2, double z2, + float width, float r, float g, float b, float a) { // Calculate direction double dx = x2 - x1; double dy = y2 - y1; @@ -121,7 +121,9 @@ public class RenderAbilitySweeper extends Render { // Calculate perpendicular vectors double upX = 0, upY = 1, upZ = 0; if (Math.abs(dy) > 0.9) { - upX = 1; upY = 0; upZ = 0; + upX = 1; + upY = 0; + upZ = 0; } // Cross product for horizontal perpendicular diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java index e9775c542..ec21a268a 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java @@ -8,13 +8,13 @@ import kamkeel.npcs.entity.EntityAbilityZone.AccentStyle; import kamkeel.npcs.entity.EntityAbilityZone.ParticleMotion; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; import kamkeel.npcs.entity.EntityAbilityZone.ZoneType; -import noppes.npcs.client.fx.CustomFX; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.Render; import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; +import noppes.npcs.client.fx.CustomFX; import noppes.npcs.client.fx.ZoneParticleFX; import org.lwjgl.opengl.GL11; @@ -373,11 +373,21 @@ public class RenderAbilityZone extends Render { } } - private float ringR(Ctx c) { return c.outerEnabled ? c.outerR : c.ir; } - private float ringG(Ctx c) { return c.outerEnabled ? c.outerG : c.ig; } - private float ringB(Ctx c) { return c.outerEnabled ? c.outerB : c.ib; } + private float ringR(Ctx c) { + return c.outerEnabled ? c.outerR : c.ir; + } - private static float sin(double v) { return (float) Math.sin(v); } + private float ringG(Ctx c) { + return c.outerEnabled ? c.outerG : c.ig; + } + + private float ringB(Ctx c) { + return c.outerEnabled ? c.outerB : c.ib; + } + + private static float sin(double v) { + return (float) Math.sin(v); + } private void renderSwayingAccents(Ctx c, float r, float g, float b, float a, float height) { if (height <= 0.1f) return; @@ -413,10 +423,22 @@ public class RenderAbilityZone extends Render { float frac = (j + 0.5f) / perSide; double lx, lz; switch (side) { - case 0: lx = -c.radius + frac * 2 * c.radius; lz = -c.radius; break; - case 1: lx = c.radius; lz = -c.radius + frac * 2 * c.radius; break; - case 2: lx = c.radius - frac * 2 * c.radius; lz = c.radius; break; - default: lx = -c.radius; lz = c.radius - frac * 2 * c.radius; break; + case 0: + lx = -c.radius + frac * 2 * c.radius; + lz = -c.radius; + break; + case 1: + lx = c.radius; + lz = -c.radius + frac * 2 * c.radius; + break; + case 2: + lx = c.radius - frac * 2 * c.radius; + lz = c.radius; + break; + default: + lx = -c.radius; + lz = c.radius - frac * 2 * c.radius; + break; } float swayX = 0.15f * sin(c.time * 0.08f + idx * 1.7f); float swayZ = 0.15f * sin(c.time * 0.06f + idx * 2.3f); @@ -467,10 +489,22 @@ public class RenderAbilityZone extends Render { float frac = (j + 0.5f) / perSide; double lx, lz; switch (side) { - case 0: lx = -c.radius + frac * 2 * c.radius; lz = -c.radius; break; - case 1: lx = c.radius; lz = -c.radius + frac * 2 * c.radius; break; - case 2: lx = c.radius - frac * 2 * c.radius; lz = c.radius; break; - default: lx = -c.radius; lz = c.radius - frac * 2 * c.radius; break; + case 0: + lx = -c.radius + frac * 2 * c.radius; + lz = -c.radius; + break; + case 1: + lx = c.radius; + lz = -c.radius + frac * 2 * c.radius; + break; + case 2: + lx = c.radius - frac * 2 * c.radius; + lz = c.radius; + break; + default: + lx = -c.radius; + lz = c.radius - frac * 2 * c.radius; + break; } float flicker = 0.3f + 0.7f * Math.abs(sin(c.time * 0.7f + idx * 3.14f)); float lineAlpha = baseAlpha * flicker; @@ -506,10 +540,14 @@ public class RenderAbilityZone extends Render { int side = RANDOM.nextInt(4); double frac = RANDOM.nextDouble() * 2 - 1; switch (side) { - case 0: return new double[]{frac * c.radius, -c.radius}; - case 1: return new double[]{frac * c.radius, c.radius}; - case 2: return new double[]{-c.radius, frac * c.radius}; - default: return new double[]{c.radius, frac * c.radius}; + case 0: + return new double[]{frac * c.radius, -c.radius}; + case 1: + return new double[]{frac * c.radius, c.radius}; + case 2: + return new double[]{-c.radius, frac * c.radius}; + default: + return new double[]{c.radius, frac * c.radius}; } } else { double angle = RANDOM.nextDouble() * Math.PI * 2; @@ -519,9 +557,9 @@ public class RenderAbilityZone extends Render { } private void spawnParticle(EntityAbilityZone zone, double offsetX, double offsetY, double offsetZ, - double mx, double my, double mz, - float r, float g, float b, float alpha, - float scale, int maxAge, float gravity, boolean glow) { + double mx, double my, double mz, + float r, float g, float b, float alpha, + float scale, int maxAge, float gravity, boolean glow) { ZoneParticleFX particle = new ZoneParticleFX( zone.worldObj, zone.posX + offsetX, zone.posY + offsetY, zone.posZ + offsetZ, @@ -587,7 +625,7 @@ public class RenderAbilityZone extends Render { } private void renderRingLine(ZoneShape shape, float radius, - float r, float g, float b, float a, float yOffset) { + float r, float g, float b, float a, float yOffset) { switch (shape) { case CIRCLE: renderCircleRingLine(radius, r, g, b, a, yOffset); @@ -621,8 +659,8 @@ public class RenderAbilityZone extends Render { } private void renderRotatingBorder(ZoneShape shape, float radius, - float r, float g, float b, float a, - float rotationDeg, float yOffset) { + float r, float g, float b, float a, + float rotationDeg, float yOffset) { int dashCount = 16; if (shape == ZoneShape.SQUARE) { @@ -688,8 +726,8 @@ public class RenderAbilityZone extends Render { } private void renderVerticalAccents(ZoneShape shape, float radius, - float r, float g, float b, float a, - float height, float rotationDeg) { + float r, float g, float b, float a, + float height, float rotationDeg) { if (height <= 0.1f) return; Tessellator tess = Tessellator.instance; @@ -701,10 +739,22 @@ public class RenderAbilityZone extends Render { float frac = (j + 0.5f) / perSide; double lx, lz; switch (side) { - case 0: lx = -radius + frac * 2 * radius; lz = -radius; break; - case 1: lx = radius; lz = -radius + frac * 2 * radius; break; - case 2: lx = radius - frac * 2 * radius; lz = radius; break; - default: lx = -radius; lz = radius - frac * 2 * radius; break; + case 0: + lx = -radius + frac * 2 * radius; + lz = -radius; + break; + case 1: + lx = radius; + lz = -radius + frac * 2 * radius; + break; + case 2: + lx = radius - frac * 2 * radius; + lz = radius; + break; + default: + lx = -radius; + lz = radius - frac * 2 * radius; + break; } tess.startDrawing(GL11.GL_LINES); tess.setColorRGBA_F(r, g, b, a); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java index f4aa9c318..9496f06a5 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java @@ -15,7 +15,7 @@ import org.lwjgl.opengl.GL12; /** * Base renderer for ability projectiles. * Provides common GL state setup and rendering utilities. - * + *

* Design inspired by LouisXIV's energy rendering system. */ @SideOnly(Side.CLIENT) @@ -212,8 +212,8 @@ public abstract class RenderEnergyAbility extends Render { * Render a line segment as a quad billboard facing the camera. */ protected void renderLineSegment(double x1, double y1, double z1, - double x2, double y2, double z2, - float width, int color, float alpha) { + double x2, double y2, double z2, + float width, int color, float alpha) { float[] rgb = extractRGB(color); float r = rgb[0], g = rgb[1], b = rgb[2]; @@ -270,9 +270,10 @@ public abstract class RenderEnergyAbility extends Render { /** * Render attached lightning arcs around an energy entity. - * @param entity the energy entity + * + * @param entity the energy entity * @param innerScale multiplier for inner radius (e.g. 0.3f for projectiles, 0.95f for dome) - * @param baseSize the entity's current render size/scale + * @param baseSize the entity's current render size/scale */ protected void renderAttachedLightning(EntityEnergyAbility entity, float innerScale, float baseSize) { AttachedLightningRenderer.LightningState state = getLightningState(entity); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java index ecdba16eb..8653bb27d 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java @@ -95,7 +95,11 @@ public class RenderEnergyDome extends RenderEnergyBarrier { float ny = (y1 + y1 + y2 + y2) * 0.25f; float nz = (z1 + z2 + z3 + z4) * 0.25f; float nLen = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); - if (nLen > 0) { nx /= nLen; ny /= nLen; nz /= nLen; } + if (nLen > 0) { + nx /= nLen; + ny /= nLen; + nz /= nLen; + } tess.setNormal(nx, ny, nz); tess.addVertex(x1, y1, z1); diff --git a/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java index 78ba58aed..9bcf473ac 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java @@ -299,8 +299,8 @@ public class TelegraphRenderer { * @param partialTicks Partial ticks for interpolation */ public void renderTelegraphInGUI(TelegraphInstance instance, - double centerX, double centerY, double centerZ, - float scale, float partialTicks) { + double centerX, double centerY, double centerZ, + float scale, float partialTicks) { if (instance == null) return; Telegraph telegraph = instance.getTelegraph(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java index 2910a9566..5e9e2342b 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java @@ -165,8 +165,8 @@ public class AttachedLightningRenderer { * Generate a jagged lightning path between two points. */ private static List generateLightningPath(double x1, double y1, double z1, - double x2, double y2, double z2, - int segments, float displacement) { + double x2, double y2, double z2, + int segments, float displacement) { List points = new ArrayList<>(); points.add(new double[]{x1, y1, z1}); @@ -257,8 +257,8 @@ public class AttachedLightningRenderer { * Create a lightning arc between two arbitrary points. */ public static LightningArc createArcBetween(double x1, double y1, double z1, - double x2, double y2, double z2, - float displacement, int outerColor, int innerColor, int maxAge) { + double x2, double y2, double z2, + float displacement, int outerColor, int innerColor, int maxAge) { int segments = 4 + rand.nextInt(3); List points = generateLightningPath(x1, y1, z1, x2, y2, z2, segments, displacement); return new LightningArc(points, maxAge, outerColor, innerColor); diff --git a/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java b/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java index 6cf6e7d3a..e077189ab 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java +++ b/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java @@ -243,7 +243,7 @@ public class LightningBolt { public Segment(Vec3d start, Vec3d end) { this(new BoltPoint(start, new Vec3d(0, 0, 0)), - new BoltPoint(end, new Vec3d(0, 0, 0)), 1, 0, 0); + new BoltPoint(end, new Vec3d(0, 0, 0)), 1, 0, 0); } public void calcDiff() { diff --git a/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningHandler.java b/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningHandler.java index 1951666e4..d3f9aeedd 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningHandler.java +++ b/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningHandler.java @@ -5,7 +5,6 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.client.renderer.lightning.LightningBolt.Segment; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.entity.Entity; @@ -19,7 +18,7 @@ import java.awt.Color; /** * Handles rendering of lightning bolts in the world. * Adapted from Botania's LightningHandler by Vazkii/ChickenBones. - * + *

* Register this handler with: MinecraftForge.EVENT_BUS.register(new LightningHandler()); */ @SideOnly(Side.CLIENT) @@ -168,13 +167,13 @@ public class LightningHandler { /** * Spawn a lightning bolt between two points. * - * @param world The world - * @param start Start position - * @param end End position + * @param world The world + * @param start Start position + * @param end End position * @param ticksPerMeter Speed of bolt expansion - * @param seed Random seed for consistent appearance - * @param colorOuter Outer glow color (ARGB) - * @param colorInner Inner core color (ARGB) + * @param seed Random seed for consistent appearance + * @param colorOuter Outer glow color (ARGB) + * @param colorInner Inner core color (ARGB) */ public static void spawnLightningBolt(World world, Vec3d start, Vec3d end, float ticksPerMeter, long seed, int colorOuter, int colorInner) { LightningBolt bolt = new LightningBolt(world, start, end, ticksPerMeter, seed, colorOuter, colorInner); diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 5c5a69f88..9be809697 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -1,7 +1,7 @@ package kamkeel.npcs.command; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.command.ICommandSender; diff --git a/src/main/java/kamkeel/npcs/command/CommandKamkeelBase.java b/src/main/java/kamkeel/npcs/command/CommandKamkeelBase.java index df4d34998..c6ffe7ec1 100644 --- a/src/main/java/kamkeel/npcs/command/CommandKamkeelBase.java +++ b/src/main/java/kamkeel/npcs/command/CommandKamkeelBase.java @@ -11,7 +11,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; public abstract class CommandKamkeelBase extends CommandBase { public Map subcommands = new HashMap(); diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 4356ac271..e872251e5 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -9,7 +9,19 @@ import kamkeel.npcs.controllers.data.ability.IAbilityExtender; import kamkeel.npcs.controllers.data.ability.IAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.UserType; -import kamkeel.npcs.controllers.data.ability.type.*; +import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; +import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; +import kamkeel.npcs.controllers.data.ability.type.AbilityDash; +import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; +import kamkeel.npcs.controllers.data.ability.type.AbilityHazard; +import kamkeel.npcs.controllers.data.ability.type.AbilityHeal; +import kamkeel.npcs.controllers.data.ability.type.AbilityHeavyHit; +import kamkeel.npcs.controllers.data.ability.type.AbilityProjectile; +import kamkeel.npcs.controllers.data.ability.type.AbilityShockwave; +import kamkeel.npcs.controllers.data.ability.type.AbilitySlam; +import kamkeel.npcs.controllers.data.ability.type.AbilityTeleport; +import kamkeel.npcs.controllers.data.ability.type.AbilityTrap; +import kamkeel.npcs.controllers.data.ability.type.AbilityVortex; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDisc; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome; @@ -20,16 +32,22 @@ import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySlicer; import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityWall; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.CustomNpcs; import noppes.npcs.LogWriter; import noppes.npcs.api.handler.IAbilityHandler; import noppes.npcs.util.NBTJsonUtil; -import net.minecraft.entity.player.EntityPlayer; - import java.io.File; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -794,7 +812,8 @@ public class AbilityController implements IAbilityHandler { // If no built-in variants exist but external ones do, // inject a "Base" variant so the user always gets a choice if (result.isEmpty()) { - result.add(new AbilityVariant("ability.variant.base", a -> {})); + result.add(new AbilityVariant("ability.variant.base", a -> { + })); } result.addAll(ext); } @@ -880,7 +899,7 @@ public class AbilityController implements IAbilityHandler { double knockbackDirX, double knockbackDirZ) { for (IAbilityExtender ext : extenders) { if (ext.onAbilityDamage(ability, caster, target, damage, knockback, knockbackUp, - knockbackDirX, knockbackDirZ)) { + knockbackDirX, knockbackDirZ)) { return true; } } diff --git a/src/main/java/kamkeel/npcs/controllers/SyncController.java b/src/main/java/kamkeel/npcs/controllers/SyncController.java index 014568af3..af7366a3f 100644 --- a/src/main/java/kamkeel/npcs/controllers/SyncController.java +++ b/src/main/java/kamkeel/npcs/controllers/SyncController.java @@ -5,6 +5,8 @@ import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import kamkeel.npcs.addon.DBCAddon; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.network.LargeAbstractPacket; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSyncAction; @@ -39,8 +41,6 @@ import noppes.npcs.controllers.data.Quest; import noppes.npcs.controllers.data.QuestCategory; import noppes.npcs.controllers.data.RecipeAnvil; import noppes.npcs.controllers.data.RecipeCarpentry; -import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; import java.io.IOException; import java.util.EnumMap; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 54529b7f3..c44f1bd47 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -7,38 +7,36 @@ import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.block.Block; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.util.AxisAlignedBB; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; import noppes.npcs.DataAbilities; import noppes.npcs.NpcDamageSource; import noppes.npcs.api.INbt; import noppes.npcs.api.ability.IAbility; +import noppes.npcs.api.entity.IPlayer; 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.data.Animation; -import net.minecraft.entity.player.EntityPlayer; -import noppes.npcs.controllers.data.Frame; -import noppes.npcs.entity.EntityNPCInterface; -import noppes.npcs.api.entity.IPlayer; import noppes.npcs.controllers.ScriptController; +import noppes.npcs.controllers.data.Animation; +import noppes.npcs.controllers.data.Frame; import noppes.npcs.controllers.data.PlayerDataScript; +import noppes.npcs.entity.EntityNPCInterface; import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.event.AbilityEvent; import noppes.npcs.scripted.event.player.PlayerAbilityEvent; -import noppes.npcs.client.gui.builder.FieldDef; - import java.util.ArrayList; import java.util.Collections; import java.util.List; -import net.minecraft.entity.Entity; - public abstract class Ability implements IAbility, IAbilityAction { // ═══════════════════════════════════════════════════════════════════ @@ -115,10 +113,14 @@ public abstract class Ability implements IAbility, IAbilityAction { // TOGGLE CONFIGURATION (saved to NBT for custom abilities) // ═══════════════════════════════════════════════════════════════════ - /** Whether this ability is a toggle (ON/OFF) rather than a phased execution */ + /** + * Whether this ability is a toggle (ON/OFF) rather than a phased execution + */ protected boolean toggleable = false; - /** Whether the toggled-ON state ticks each game tick (e.g., Kaioken drains ki per tick) */ + /** + * Whether the toggled-ON state ticks each game tick (e.g., Kaioken drains ki per tick) + */ protected boolean hasActiveToggle = false; // Configurable potion effects @@ -232,17 +234,20 @@ public abstract class Ability implements IAbility, IAbilityAction { * Called when this toggle ability is turned ON for an entity. * Override to apply effects (e.g., set a DBC flag, apply a buff). */ - public void onToggleOn(EntityLivingBase caster) {} + public void onToggleOn(EntityLivingBase caster) { + } /** * Called when this toggle ability is turned OFF for an entity. * Override to remove effects (e.g., clear a DBC flag, remove a buff). */ - public void onToggleOff(EntityLivingBase caster) {} + public void onToggleOff(EntityLivingBase caster) { + } /** * Called every tick while this toggle is active (only if hasActiveToggle is true). * Override for per-tick effects (e.g., ki drain for Kaioken). + * * @return true to keep the toggle active, false to auto-deactivate */ public boolean onToggleTick(EntityLivingBase caster, int tickCount) { @@ -252,6 +257,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * Whether this ability is ready for auto-completion during burst overlap mode. * Override to delay auto-completion until all staggered projectiles have been fired. + * * @param activeTick the current tick within the ACTIVE phase */ public boolean isReadyForBurstCompletion(int activeTick) { @@ -554,27 +560,36 @@ public abstract class Ability implements IAbility, IAbilityAction { () -> this.getLockMovement().getDisplayKey(), v -> { for (LockMovementType t : LockMovementType.values()) { - if (t.getDisplayKey().equals(v)) { this.setLockMovement(t); break; } + if (t.getDisplayKey().equals(v)) { + this.setLockMovement(t); + break; + } } }) - .hover("ability.hover.lockMovement") - .tab("General")); + .hover("ability.hover.lockMovement") + .tab("General")); defs.add(FieldDef.row( FieldDef.stringEnumField("ability.rotationMode", RotationMode.getDisplayKeys(), - () -> this.getRotationMode().getDisplayKey(), - v -> { - for (RotationMode m : RotationMode.values()) { - if (m.getDisplayKey().equals(v)) { this.setRotationMode(m); break; } - } - }) + () -> this.getRotationMode().getDisplayKey(), + v -> { + for (RotationMode m : RotationMode.values()) { + if (m.getDisplayKey().equals(v)) { + this.setRotationMode(m); + break; + } + } + }) .hover("ability.hover.rotationMode"), FieldDef.stringEnumField("ability.rotationPhase", getRotationPhaseKeys(), - () -> this.getRotationPhase().getDisplayKey(), - v -> { - for (LockMovementType t : LockMovementType.values()) { - if (t.getDisplayKey().equals(v)) { this.setRotationPhase(t); break; } - } - }) + () -> this.getRotationPhase().getDisplayKey(), + v -> { + for (LockMovementType t : LockMovementType.values()) { + if (t.getDisplayKey().equals(v)) { + this.setRotationPhase(t); + break; + } + } + }) .hover("ability.hover.rotationPhase") .visibleWhen(() -> this.rotationMode != RotationMode.FREE) ).tab("General")); @@ -617,15 +632,17 @@ public abstract class Ability implements IAbility, IAbilityAction { allowedKeys[i] = allowed[i].name(); } defs.add(FieldDef.stringEnumField("ability.targetingMode", allowedKeys, - () -> this.getTargetingMode().name(), - v -> { - try { this.setTargetingMode(TargetingMode.valueOf(v)); } - catch (Exception ignored) {} - }) + () -> this.getTargetingMode().name(), + v -> { + try { + this.setTargetingMode(TargetingMode.valueOf(v)); + } catch (Exception ignored) { + } + }) .tab("Target").hover("ability.hover.targeting")); } else { defs.add(FieldDef.enumField("ability.targetingMode", TargetingMode.class, - this::getTargetingMode, this::setTargetingMode) + this::getTargetingMode, this::setTargetingMode) .tab("Target").hover("ability.hover.targeting")); } } @@ -638,12 +655,12 @@ public abstract class Ability implements IAbility, IAbilityAction { .tab("Effects")); defs.add(FieldDef.section("ability.section.animations").tab("Effects")); defs.add(FieldDef.animSubGui("ability.windUpAnimation", - this::getWindUpAnimationId, this::setWindUpAnimationId, - this::getWindUpAnimationName, this::setWindUpAnimationName) + this::getWindUpAnimationId, this::setWindUpAnimationId, + this::getWindUpAnimationName, this::setWindUpAnimationName) .tab("Effects")); defs.add(FieldDef.animSubGui("ability.activeAnimation", - this::getActiveAnimationId, this::setActiveAnimationId, - this::getActiveAnimationName, this::setActiveAnimationName) + this::getActiveAnimationId, this::setActiveAnimationId, + this::getActiveAnimationName, this::setActiveAnimationName) .tab("Effects")); defs.add(FieldDef.animSubGui("ability.dazedAnimation", this::getDazedAnimationId, this::setDazedAnimationId, @@ -699,7 +716,7 @@ public abstract class Ability implements IAbility, IAbilityAction { * Create a telegraph instance for this ability. * Override for custom telegraph shapes. * - * @param caster The caster + * @param caster The caster * @param target The target (for position calculation) * @return The telegraph instance, or null if no telegraph */ @@ -1514,6 +1531,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * API method: Get lock movement type as integer. + * * @return 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE */ @Override @@ -1523,6 +1541,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * API method: Set lock movement type from integer. + * * @param type 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE */ @Override @@ -1532,6 +1551,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * API method: Get rotation mode as integer. + * * @return 0=FREE, 1=LOCKED, 2=TRACK */ @Override @@ -1541,6 +1561,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * API method: Set rotation mode from integer. + * * @param type 0=FREE, 1=LOCKED, 2=TRACK */ @Override @@ -1550,6 +1571,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * API method: Get rotation phase as integer. + * * @return 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE */ @Override @@ -1559,6 +1581,7 @@ public abstract class Ability implements IAbility, IAbilityAction { /** * API method: Set rotation phase from integer. + * * @param type 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE */ @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java index e42c52980..830d5d0ec 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -24,14 +24,17 @@ public class AbilityAction { private ChainedAbility inlineChain; private String referenceId; - /** Per-slot enabled override for reference slots (null = use master's default). */ + /** + * Per-slot enabled override for reference slots (null = use master's default). + */ private Boolean enabledOverride; // Cache private transient IAbilityAction cachedAction; private transient int cachedRevision = -1; - private AbilityAction() {} + private AbilityAction() { + } // ═══════════════════════════════════════════════════════════════════ // FACTORY METHODS diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java index 07295aaf7..1afbf0906 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilitySlot.java @@ -18,13 +18,16 @@ public class AbilitySlot { private Ability inlineAbility; private String referenceId; - /** Per-slot enabled override for reference slots (null = use master's default). */ + /** + * Per-slot enabled override for reference slots (null = use master's default). + */ private Boolean enabledOverride; private transient Ability cachedAbility; private transient int cachedVersion = -1; - private AbilitySlot() {} + private AbilitySlot() { + } /** * Create a slot with an inline (embedded) ability. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityVariant.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityVariant.java index 797e6b20b..0ef3b9692 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityVariant.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityVariant.java @@ -6,7 +6,7 @@ import java.util.function.Consumer; * Represents a variant/template for an ability type. * When a user creates an ability that has registered variants, * they are presented with a selection dialog before the ability is created. - * + *

* Each variant has a display name (lang key) and a configurator that * applies preset values to the newly created ability. */ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java index 58e54b5aa..959aa2cf4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java @@ -50,14 +50,22 @@ public enum AnchorPoint { @Override public String toString() { switch (this) { - case FRONT: return "ability.anchor.front"; - case CENTER: return "ability.anchor.center"; - case RIGHT_HAND: return "ability.anchor.rightHand"; - case LEFT_HAND: return "ability.anchor.leftHand"; - case ABOVE_HEAD: return "ability.anchor.aboveHead"; - case CHEST: return "ability.anchor.chest"; - case EYE: return "ability.anchor.eye"; - default: return name(); + case FRONT: + return "ability.anchor.front"; + case CENTER: + return "ability.anchor.center"; + case RIGHT_HAND: + return "ability.anchor.rightHand"; + case LEFT_HAND: + return "ability.anchor.leftHand"; + case ABOVE_HEAD: + return "ability.anchor.aboveHead"; + case CHEST: + return "ability.anchor.chest"; + case EYE: + return "ability.anchor.eye"; + default: + return name(); } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java index eff65e8a9..5de37416e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java @@ -29,31 +29,48 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { private boolean enabled = true; - /** Weight for NPC weighted random selection. */ + /** + * Weight for NPC weighted random selection. + */ private int weight = 10; - /** Whether each ability in the chain uses its own windup phase. */ + /** + * Whether each ability in the chain uses its own windup phase. + */ private boolean windUpAll = true; - /** Chain-level cooldown in ticks (applied after entire chain completes). */ + /** + * Chain-level cooldown in ticks (applied after entire chain completes). + */ private int cooldownTicks = 100; - /** Minimum range for NPC eligibility check. */ + /** + * Minimum range for NPC eligibility check. + */ private float minRange = 0; - /** Maximum range for NPC eligibility check. */ + /** + * Maximum range for NPC eligibility check. + */ private float maxRange = 20; - /** Chain-level conditions (individual ability conditions are ignored). */ + /** + * Chain-level conditions (individual ability conditions are ignored). + */ private List conditions = new ArrayList<>(); - /** Ordered list of ability entries to execute. */ + /** + * Ordered list of ability entries to execute. + */ private List entries = new ArrayList<>(); - /** Extension data for external mods (e.g., icons, DBC stats). */ + /** + * Extension data for external mods (e.g., icons, DBC stats). + */ private NBTTagCompound customData = new NBTTagCompound(); - public ChainedAbility() {} + public ChainedAbility() { + } public ChainedAbility(String name) { this.name = name != null ? name : ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java index 887d85571..be8528ed0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java @@ -12,20 +12,27 @@ import net.minecraft.nbt.NBTTagCompound; */ public class ChainedAbilityEntry { - public enum EntryType { REFERENCE, INLINE } + public enum EntryType {REFERENCE, INLINE} private EntryType entryType = EntryType.REFERENCE; - /** Reference key (built-in name or custom ability name). Used when entryType == REFERENCE. */ + /** + * Reference key (built-in name or custom ability name). Used when entryType == REFERENCE. + */ private String abilityReference = ""; - /** Inline ability data. Used when entryType == INLINE. */ + /** + * Inline ability data. Used when entryType == INLINE. + */ private Ability inlineAbility = null; - /** Delay in ticks BEFORE this ability starts (after previous entry completes). */ + /** + * Delay in ticks BEFORE this ability starts (after previous entry completes). + */ private int delayTicks = 0; - public ChainedAbilityEntry() {} + public ChainedAbilityEntry() { + } public ChainedAbilityEntry(String abilityReference, int delayTicks) { this.entryType = EntryType.REFERENCE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java b/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java index 77fae9774..13c8bc526 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java @@ -9,5 +9,6 @@ import net.minecraft.entity.Entity; */ public interface PreviewEntityHandler { void onEntitySpawned(Entity entity); + void onEntityRemoved(Entity entity); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java index 9bdbdac6f..7e84c6a14 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java @@ -27,11 +27,16 @@ public enum TargetingMode { @Override public String toString() { switch (this) { - case AGGRO_TARGET: return "ability.target.aggro_target"; - case SELF: return "ability.target.self"; - case AOE_SELF: return "ability.target.aoe_self"; - case AOE_TARGET: return "ability.target.aoe_target"; - default: return name(); + case AGGRO_TARGET: + return "ability.target.aggro_target"; + case SELF: + return "ability.target.self"; + case AOE_SELF: + return "ability.target.aoe_self"; + case AOE_TARGET: + return "ability.target.aoe_target"; + default: + return name(); } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java index 2d65b6228..f31466329 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java @@ -22,7 +22,9 @@ public class EnergyBarrierData { // Knockback settings public boolean knockbackEnabled = false; public float knockbackStrength = 1.0f; - /** 0=Both, 1=Player only, 2=NPC only */ + /** + * 0=Both, 1=Player only, 2=NPC only + */ public int knockbackTarget = 0; // Melee settings @@ -36,7 +38,8 @@ public class EnergyBarrierData { */ private Map damageMultipliers = new HashMap<>(); - public EnergyBarrierData() {} + public EnergyBarrierData() { + } public EnergyBarrierData(float maxHealth, boolean useHealth, int durationTicks, boolean useDuration) { this.maxHealth = maxHealth; @@ -70,23 +73,50 @@ public class EnergyBarrierData { // ==================== GETTERS & SETTERS ==================== - public float getMaxHealth() { return maxHealth; } - public void setMaxHealth(float maxHealth) { this.maxHealth = Math.max(0, maxHealth); } + public float getMaxHealth() { + return maxHealth; + } - public boolean isUseHealth() { return useHealth; } - public void setUseHealth(boolean useHealth) { this.useHealth = useHealth; } + public void setMaxHealth(float maxHealth) { + this.maxHealth = Math.max(0, maxHealth); + } - public int getDurationTicks() { return durationTicks; } - public void setDurationTicks(int durationTicks) { this.durationTicks = Math.max(1, durationTicks); } + public boolean isUseHealth() { + return useHealth; + } - public boolean isUseDuration() { return useDuration; } - public void setUseDuration(boolean useDuration) { this.useDuration = useDuration; } + public void setUseHealth(boolean useHealth) { + this.useHealth = useHealth; + } - public float getDefaultMultiplier() { return defaultMultiplier; } - public void setDefaultMultiplier(float defaultMultiplier) { this.defaultMultiplier = defaultMultiplier; } + public int getDurationTicks() { + return durationTicks; + } + + public void setDurationTicks(int durationTicks) { + this.durationTicks = Math.max(1, durationTicks); + } + + public boolean isUseDuration() { + return useDuration; + } + + public void setUseDuration(boolean useDuration) { + this.useDuration = useDuration; + } + + public float getDefaultMultiplier() { + return defaultMultiplier; + } + + public void setDefaultMultiplier(float defaultMultiplier) { + this.defaultMultiplier = defaultMultiplier; + } // Knockback target key helpers - public static String[] getKnockbackTargetKeys() { return KNOCKBACK_TARGET_KEYS; } + public static String[] getKnockbackTargetKeys() { + return KNOCKBACK_TARGET_KEYS; + } public String getKnockbackTargetKey() { return KNOCKBACK_TARGET_KEYS[Math.max(0, Math.min(knockbackTarget, 2))]; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java index e6ccf15a1..2ecbf7cc5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java @@ -15,7 +15,8 @@ public class EnergyCombatData implements IEnergyCombatData { public float explosionRadius = 3.0f; public float explosionDamageFalloff = 0.5f; - public EnergyCombatData() {} + public EnergyCombatData() { + } public EnergyCombatData(float damage, float knockback, float knockbackUp, boolean explosive, float explosionRadius, float explosionDamageFalloff) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java index 415c5796c..3b0998602 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java @@ -20,7 +20,8 @@ public class EnergyDisplayData implements IEnergyDisplayData { // public boolean skyboxEnabled = false; // public String skyboxTexture = ""; - public EnergyDisplayData() {} + public EnergyDisplayData() { + } public EnergyDisplayData(int innerColor, int outerColor) { this.innerColor = innerColor; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java index d385c06e7..a03e70525 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java @@ -14,7 +14,8 @@ public class EnergyHomingData implements IEnergyHomingData { public float homingStrength = 0.15f; public float homingRange = 20.0f; - public EnergyHomingData() {} + public EnergyHomingData() { + } public EnergyHomingData(float speed, boolean homing, float homingStrength, float homingRange) { this.speed = speed; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java index f320acdb0..a18a91021 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java @@ -11,7 +11,8 @@ public class EnergyLifespanData implements IEnergyLifespanData { public float maxDistance = 30.0f; public int maxLifetime = 200; - public EnergyLifespanData() {} + public EnergyLifespanData() { + } public EnergyLifespanData(float maxDistance, int maxLifetime) { this.maxDistance = maxDistance; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java index 66786a14a..1eb2b001a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java @@ -13,7 +13,8 @@ public class EnergyLightningData implements IEnergyLightningData { public float lightningRadius = 0.5f; public int lightningFadeTime = 6; - public EnergyLightningData() {} + public EnergyLightningData() { + } public EnergyLightningData(boolean lightningEffect, float lightningDensity, float lightningRadius, int lightningFadeTime) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java index 1fa8586fe..3a5206f21 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java @@ -17,7 +17,8 @@ public class EnergyPanelData { public float launchDamage = 8.0f; public float launchKnockback = 2.0f; - public EnergyPanelData() {} + public EnergyPanelData() { + } public EnergyPanelData(float panelWidth, float panelHeight, float heightOffset) { this.panelWidth = panelWidth; @@ -27,26 +28,61 @@ public class EnergyPanelData { // ==================== GETTERS & SETTERS ==================== - public float getPanelWidth() { return panelWidth; } - public void setPanelWidth(float panelWidth) { this.panelWidth = Math.max(0.5f, panelWidth); } + public float getPanelWidth() { + return panelWidth; + } - public float getPanelHeight() { return panelHeight; } - public void setPanelHeight(float panelHeight) { this.panelHeight = Math.max(0.5f, panelHeight); } + public void setPanelWidth(float panelWidth) { + this.panelWidth = Math.max(0.5f, panelWidth); + } - public float getHeightOffset() { return heightOffset; } - public void setHeightOffset(float heightOffset) { this.heightOffset = heightOffset; } + public float getPanelHeight() { + return panelHeight; + } - public boolean isLaunching() { return launching; } - public void setLaunching(boolean launching) { this.launching = launching; } + public void setPanelHeight(float panelHeight) { + this.panelHeight = Math.max(0.5f, panelHeight); + } - public float getLaunchSpeed() { return launchSpeed; } - public void setLaunchSpeed(float launchSpeed) { this.launchSpeed = Math.max(0.1f, launchSpeed); } + public float getHeightOffset() { + return heightOffset; + } - public float getLaunchDamage() { return launchDamage; } - public void setLaunchDamage(float launchDamage) { this.launchDamage = Math.max(0, launchDamage); } + public void setHeightOffset(float heightOffset) { + this.heightOffset = heightOffset; + } - public float getLaunchKnockback() { return launchKnockback; } - public void setLaunchKnockback(float launchKnockback) { this.launchKnockback = Math.max(0, launchKnockback); } + public boolean isLaunching() { + return launching; + } + + public void setLaunching(boolean launching) { + this.launching = launching; + } + + public float getLaunchSpeed() { + return launchSpeed; + } + + public void setLaunchSpeed(float launchSpeed) { + this.launchSpeed = Math.max(0.1f, launchSpeed); + } + + public float getLaunchDamage() { + return launchDamage; + } + + public void setLaunchDamage(float launchDamage) { + this.launchDamage = Math.max(0, launchDamage); + } + + public float getLaunchKnockback() { + return launchKnockback; + } + + public void setLaunchKnockback(float launchKnockback) { + this.launchKnockback = Math.max(0, launchKnockback); + } // ==================== NBT ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java index 15adb3062..ea7735e11 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java @@ -15,7 +15,8 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData { public int currentPath = 0; private final List trajectory = new ArrayList<>(); - public EnergyTrajectoryData() {} + public EnergyTrajectoryData() { + } public EnergyTrajectoryData(int startingPath, Path... paths) { trajectory.addAll(Arrays.asList(paths)); @@ -85,6 +86,7 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData { IPath p = getPath(path); return p == null ? 0 : p.getZ(); } + @Override public void setX(int path, double x) { IPath p = getPath(path); @@ -126,8 +128,8 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData { @Override public void setPath(int path, double x, double y, double z, int delay) { Path newPath = new Path( - NpcAPI.Instance().getIPos(x, y, z), - delay + NpcAPI.Instance().getIPos(x, y, z), + delay ); while (trajectory.size() <= path) { @@ -196,7 +198,7 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData { } } - public static class Path implements IEnergyTrajectoryData.IPath{ + public static class Path implements IEnergyTrajectoryData.IPath { public IPos pos; public int delayTicks; public boolean concluded; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java index d41418d91..2456b161d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java @@ -199,5 +199,7 @@ public class AbilityFieldBuilder extends GuiFieldBuilder { return super.handleTextFieldEvent(textFieldId, field); } - public Map getEffectWidgetMeta() { return effectWidgetMeta; } + public Map getEffectWidgetMeta() { + return effectWidgetMeta; + } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java index 780ddc050..9f063ff72 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -11,10 +12,8 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; - -import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import noppes.npcs.api.ability.type.IAbilityCharge; +import noppes.npcs.client.gui.builder.FieldDef; import java.util.Arrays; import java.util.HashSet; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java index 58c92009f..bac7cc234 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java @@ -1,8 +1,11 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -11,15 +14,8 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; -import noppes.npcs.entity.EntityNPCInterface; - import noppes.npcs.api.ability.type.IAbilityCutter; - import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; import java.util.Arrays; import java.util.HashSet; @@ -40,9 +36,12 @@ public class AbilityCutter extends Ability implements IAbilityCutter { @Override public String toString() { switch (this) { - case SWIPE: return "ability.sweep.swipe"; - case SPIN: return "ability.sweep.spin"; - default: return name(); + case SWIPE: + return "ability.sweep.swipe"; + case SPIN: + return "ability.sweep.spin"; + default: + return name(); } } } @@ -232,7 +231,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter { * For SWIPE mode, this covers the continuous arc from startAngle to endAngle. */ private void performSweepDamageRange(EntityLivingBase caster, World world, float minDist, float maxDist, - float startAngle, float endAngle) { + float startAngle, float endAngle) { // Calculate the angular range covered this tick float minAngle = Math.min(startAngle, endAngle); float maxAngle = Math.max(startAngle, endAngle); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index 41c5faaa1..84df55642 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -8,9 +8,8 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; - -import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.api.ability.type.IAbilityDash; +import noppes.npcs.client.gui.builder.FieldDef; import java.util.Arrays; import java.util.List; @@ -32,9 +31,12 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public String toString() { switch (this) { - case AGGRESSIVE: return "ability.dash.aggressive"; - case DEFENSIVE: return "ability.dash.defensive"; - default: return name(); + case AGGRESSIVE: + return "ability.dash.aggressive"; + case DEFENSIVE: + return "ability.dash.defensive"; + default: + return name(); } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java index 331ed0de4..24489b9eb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java @@ -1,25 +1,26 @@ package kamkeel.npcs.controllers.data.ability.type; -import kamkeel.npcs.controllers.data.ability.*; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import noppes.npcs.api.ability.type.IAbilityGuard; import noppes.npcs.api.handler.data.IAnimation; +import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.entity.EntityNPCInterface; - -import noppes.npcs.api.ability.type.IAbilityGuard; - -import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; import noppes.npcs.util.ValueUtil; import java.util.Arrays; @@ -77,9 +78,12 @@ public class AbilityGuard extends Ability implements IAbilityGuard { @Override public String toString() { switch (this) { - case FLAT: return "ability.counter.flat"; - case PERCENT: return "ability.counter.percent"; - default: return name(); + case FLAT: + return "ability.counter.flat"; + case PERCENT: + return "ability.counter.percent"; + default: + return name(); } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java index 7ca81c95a..a23e38963 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java @@ -1,19 +1,15 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; - - import noppes.npcs.api.ability.type.IAbilityHazard; - import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; import java.util.ArrayList; import java.util.Arrays; @@ -106,20 +102,45 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { this.affectsCaster = nbt.getBoolean("affectsCaster"); } - public float getRadius() { return radius; } - public void setRadius(float radius) { this.radius = radius; } + public float getRadius() { + return radius; + } - public float getDamagePerSecond() { return damagePerSecond; } - public void setDamagePerSecond(float damagePerSecond) { this.damagePerSecond = damagePerSecond; } + public void setRadius(float radius) { + this.radius = radius; + } - public int getDamageInterval() { return damageInterval; } - public void setDamageInterval(int damageInterval) { this.damageInterval = damageInterval; } + public float getDamagePerSecond() { + return damagePerSecond; + } - public boolean isIgnoreInvulnFrames() { return ignoreInvulnFrames; } - public void setIgnoreInvulnFrames(boolean ignoreInvulnFrames) { this.ignoreInvulnFrames = ignoreInvulnFrames; } + public void setDamagePerSecond(float damagePerSecond) { + this.damagePerSecond = damagePerSecond; + } - public boolean isAffectsCaster() { return affectsCaster; } - public void setAffectsCaster(boolean affectsCaster) { this.affectsCaster = affectsCaster; } + public int getDamageInterval() { + return damageInterval; + } + + public void setDamageInterval(int damageInterval) { + this.damageInterval = damageInterval; + } + + public boolean isIgnoreInvulnFrames() { + return ignoreInvulnFrames; + } + + public void setIgnoreInvulnFrames(boolean ignoreInvulnFrames) { + this.ignoreInvulnFrames = ignoreInvulnFrames; + } + + public boolean isAffectsCaster() { + return affectsCaster; + } + + public void setAffectsCaster(boolean affectsCaster) { + this.affectsCaster = affectsCaster; + } @SideOnly(Side.CLIENT) @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java index 6119e1340..89cfd2dbf 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; @@ -9,13 +11,9 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.world.World; -import noppes.npcs.entity.EntityNPCInterface; - -import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.api.ability.type.IAbilityHeal; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; +import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.entity.EntityNPCInterface; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java index 7c85826a2..d7314b707 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java @@ -5,13 +5,13 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import noppes.npcs.api.ability.type.IAbilityHeavyHit; +import noppes.npcs.client.gui.builder.FieldDef; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java index 46242fc71..1aba9ff3c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java @@ -5,18 +5,15 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.entity.EntityNPCInterface; - import noppes.npcs.api.ability.type.IAbilityProjectile; - import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import java.util.Arrays; import java.util.List; @@ -94,7 +91,11 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile { double dy = (target.posY + target.height / 2) - (caster.posY + caster.getEyeHeight()); double dz = target.posZ - caster.posZ; double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { dx /= len; dy /= len; dz /= len; } + if (len > 0) { + dx /= len; + dy /= len; + dz /= len; + } applyAbilityDamageWithDirection(caster, target, damage, knockback, dx, dz); world.playSoundAtEntity(caster, "random.bow", 1.0f, 0.8f); @@ -167,7 +168,7 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile { * Apply explosion splash damage around a point. */ private void applyExplosionDamage(EntityLivingBase caster, EntityLivingBase primaryTarget, World world, - double x, double y, double z) { + double x, double y, double z) { @SuppressWarnings("unchecked") List entities = world.getEntitiesWithinAABBExcludingEntity(primaryTarget, primaryTarget.boundingBox.expand(explosionRadius, explosionRadius, explosionRadius)); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java index 1eebdab99..4cca57508 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java @@ -5,16 +5,13 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.DamageSource; -import noppes.npcs.entity.EntityNPCInterface; - -import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import noppes.npcs.api.ability.type.IAbilityShockwave; +import noppes.npcs.client.gui.builder.FieldDef; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index f8a6559ca..1e844271f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -5,6 +5,7 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -13,13 +14,10 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import noppes.npcs.api.ability.type.IAbilitySlam; +import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.entity.EntityNPCInterface; -import noppes.npcs.api.ability.type.IAbilitySlam; -import somehussar.gui.annotationHandling.GuiEditable; - -import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java index 70228652e..cc2deb4ea 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java @@ -1,9 +1,12 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.block.Block; import net.minecraft.entity.Entity; @@ -13,15 +16,8 @@ import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MathHelper; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.entity.EntityNPCInterface; - import noppes.npcs.api.ability.type.IAbilityTeleport; - import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; import java.util.Arrays; import java.util.List; @@ -43,10 +39,14 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport { @Override public String toString() { switch (this) { - case BLINK: return "ability.teleport.blink"; - case BEHIND: return "ability.teleport.behind"; - case SINGLE: return "ability.teleport.single"; - default: return name(); + case BLINK: + return "ability.teleport.blink"; + case BEHIND: + return "ability.teleport.behind"; + case SINGLE: + return "ability.teleport.single"; + default: + return name(); } } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java index a8782122f..d875353f6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; @@ -12,9 +14,6 @@ import noppes.npcs.client.gui.SubGuiTrapPresetSelector; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.constants.EnumPotionType; -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -115,51 +114,80 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { switch (preset) { case "VENOM": groundAlpha = 0.04f; - particles = true; particleDensity = 0.3f; particleScale = 0.5f; - particleMotion = 1; particleDir = "mc:mobSpell"; - colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611; - windUpColor = 0x6044DD44; activeColor = 0xC044FF44; + particles = true; + particleDensity = 0.3f; + particleScale = 0.5f; + particleMotion = 1; + particleDir = "mc:mobSpell"; + colorData.innerColor = 0x44DD44; + colorData.outerColor = 0x116611; + windUpColor = 0x6044DD44; + activeColor = 0xC044FF44; effects.add(new AbilityPotionEffect(EnumPotionType.Poison, 100, 0)); break; case "EXPLOSIVE": groundAlpha = 0.04f; - particles = true; particleDensity = 0.3f; particleScale = 0.6f; - particleMotion = 0; particleDir = "mc:smoke"; - colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200; - windUpColor = 0x60FF6611; activeColor = 0xC0FF4400; + particles = true; + particleDensity = 0.3f; + particleScale = 0.6f; + particleMotion = 0; + particleDir = "mc:smoke"; + colorData.innerColor = 0xFF6611; + colorData.outerColor = 0xCC2200; + windUpColor = 0x60FF6611; + activeColor = 0xC0FF4400; effects.add(new AbilityPotionEffect(EnumPotionType.Fire, 60, 0)); break; case "CURSED": groundAlpha = 0.04f; - particles = true; particleDensity = 0.3f; particleScale = 0.5f; - particleMotion = 1; particleDir = "mc:portal"; - colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB; - windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF; + particles = true; + particleDensity = 0.3f; + particleScale = 0.5f; + particleMotion = 1; + particleDir = "mc:portal"; + colorData.innerColor = 0xAA44FF; + colorData.outerColor = 0x6622BB; + windUpColor = 0x60AA44FF; + activeColor = 0xC0CC66FF; effects.add(new AbilityPotionEffect(EnumPotionType.Weakness, 100, 0)); break; case "SHOCK": groundAlpha = 0.04f; - particles = true; particleDensity = 0.3f; particleScale = 0.4f; - particleMotion = 2; particleDir = "mc:enchantmenttable"; - colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB; - windUpColor = 0x604488FF; activeColor = 0xC066AAFF; + particles = true; + particleDensity = 0.3f; + particleScale = 0.4f; + particleMotion = 2; + particleDir = "mc:enchantmenttable"; + colorData.innerColor = 0x4488FF; + colorData.outerColor = 0x2244BB; + windUpColor = 0x604488FF; + activeColor = 0xC066AAFF; effects.add(new AbilityPotionEffect(EnumPotionType.MiningFatigue, 80, 1)); break; case "SNARE": groundAlpha = 0.04f; - particles = true; particleDensity = 0.3f; particleScale = 0.5f; - particleMotion = 1; particleDir = "mc:snowshovel"; - colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC; - windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF; + particles = true; + particleDensity = 0.3f; + particleScale = 0.5f; + particleMotion = 1; + particleDir = "mc:snowshovel"; + colorData.innerColor = 0x88CCFF; + colorData.outerColor = 0x4488CC; + windUpColor = 0x6088CCFF; + activeColor = 0xC0AADDFF; effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 100, 1)); break; case "HIDDEN": default: groundAlpha = 0.03f; - particles = false; particleDensity = 0.0f; particleScale = 1.0f; + particles = false; + particleDensity = 0.0f; + particleScale = 1.0f; particleMotion = 0; - colorData.innerColor = 0x888888; colorData.outerColor = 0x444444; - windUpColor = 0x60888888; activeColor = 0xC0AAAAAA; + colorData.innerColor = 0x888888; + colorData.outerColor = 0x444444; + windUpColor = 0x60888888; + activeColor = 0xC0AAAAAA; break; } } @@ -190,22 +218,69 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { this.visible = nbt.getBoolean("visible"); } - public float getTriggerRadius() { return triggerRadius; } - public void setTriggerRadius(float triggerRadius) { this.triggerRadius = triggerRadius; } - public int getArmTime() { return armTime; } - public void setArmTime(int armTime) { this.armTime = armTime; } - public int getMaxTriggers() { return maxTriggers; } - public void setMaxTriggers(int maxTriggers) { this.maxTriggers = maxTriggers; } - public int getTriggerCooldown() { return triggerCooldown; } - public void setTriggerCooldown(int triggerCooldown) { this.triggerCooldown = triggerCooldown; } - public float getDamage() { return damage; } - public void setDamage(float damage) { this.damage = damage; } - public float getDamageRadius() { return damageRadius; } - public void setDamageRadius(float damageRadius) { this.damageRadius = damageRadius; } - public float getKnockback() { return knockback; } - public void setKnockback(float knockback) { this.knockback = knockback; } - public boolean isVisible() { return visible; } - public void setVisible(boolean visible) { this.visible = visible; } + public float getTriggerRadius() { + return triggerRadius; + } + + public void setTriggerRadius(float triggerRadius) { + this.triggerRadius = triggerRadius; + } + + public int getArmTime() { + return armTime; + } + + public void setArmTime(int armTime) { + this.armTime = armTime; + } + + public int getMaxTriggers() { + return maxTriggers; + } + + public void setMaxTriggers(int maxTriggers) { + this.maxTriggers = maxTriggers; + } + + public int getTriggerCooldown() { + return triggerCooldown; + } + + public void setTriggerCooldown(int triggerCooldown) { + this.triggerCooldown = triggerCooldown; + } + + public float getDamage() { + return damage; + } + + public void setDamage(float damage) { + this.damage = damage; + } + + public float getDamageRadius() { + return damageRadius; + } + + public void setDamageRadius(float damageRadius) { + this.damageRadius = damageRadius; + } + + public float getKnockback() { + return knockback; + } + + public void setKnockback(float knockback) { + this.knockback = knockback; + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } @SideOnly(Side.CLIENT) @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index f969c27cd..d5f68e013 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -1,21 +1,18 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.world.World; -import noppes.npcs.entity.EntityNPCInterface; - -import noppes.npcs.client.gui.builder.FieldDef; -import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import noppes.npcs.api.ability.type.IAbilityVortex; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; +import noppes.npcs.client.gui.builder.FieldDef; import java.util.Arrays; import java.util.HashSet; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java index b75e41c3e..0b233a637 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java @@ -1,5 +1,7 @@ package kamkeel.npcs.controllers.data.ability.type; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.LockMovementType; @@ -14,15 +16,10 @@ import kamkeel.npcs.entity.EntityAbilityZone.ParticleMotion; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.constants.EnumPotionType; import net.minecraft.util.DamageSource; - - import noppes.npcs.client.gui.SubGuiZonePresetSelector; import noppes.npcs.client.gui.builder.FieldDef; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; +import noppes.npcs.constants.EnumPotionType; import java.util.ArrayList; import java.util.Arrays; @@ -213,57 +210,134 @@ public abstract class AbilityZone extends Ability { particleDir = ""; switch (styleName) { case "TOXIC": - groundFill = true; groundAlpha = 0.30f; rings = true; ringCount = 3; - border = false; borderSpeed = 1.0f; accents = true; accentStyle = 1; - lightning = false; particles = true; particleDensity = 1.5f; particleScale = 0.8f; - particleMotion = 1; particleGlow = true; particleDir = "mc:mobSpell"; - colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611; - windUpColor = 0x6044DD44; activeColor = 0xC044FF44; + groundFill = true; + groundAlpha = 0.30f; + rings = true; + ringCount = 3; + border = false; + borderSpeed = 1.0f; + accents = true; + accentStyle = 1; + lightning = false; + particles = true; + particleDensity = 1.5f; + particleScale = 0.8f; + particleMotion = 1; + particleGlow = true; + particleDir = "mc:mobSpell"; + colorData.innerColor = 0x44DD44; + colorData.outerColor = 0x116611; + windUpColor = 0x6044DD44; + activeColor = 0xC044FF44; effects.add(new AbilityPotionEffect(EnumPotionType.Poison, 100, 0)); break; case "INFERNO": - groundFill = true; groundAlpha = 0.35f; rings = true; ringCount = 3; - border = true; borderSpeed = 2.0f; accents = true; accentStyle = 2; - lightning = false; particles = true; particleDensity = 2.0f; particleScale = 1.2f; - particleMotion = 0; particleGlow = true; particleDir = "mc:flame"; - colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200; - windUpColor = 0x60FF6611; activeColor = 0xC0FF4400; + groundFill = true; + groundAlpha = 0.35f; + rings = true; + ringCount = 3; + border = true; + borderSpeed = 2.0f; + accents = true; + accentStyle = 2; + lightning = false; + particles = true; + particleDensity = 2.0f; + particleScale = 1.2f; + particleMotion = 0; + particleGlow = true; + particleDir = "mc:flame"; + colorData.innerColor = 0xFF6611; + colorData.outerColor = 0xCC2200; + windUpColor = 0x60FF6611; + activeColor = 0xC0FF4400; effects.add(new AbilityPotionEffect(EnumPotionType.Fire, 60, 0)); break; case "ARCANE": - groundFill = true; groundAlpha = 0.20f; rings = true; ringCount = 1; - border = true; borderSpeed = 0.8f; accents = true; accentStyle = 0; - lightning = false; particles = true; particleDensity = 1.0f; particleScale = 1.0f; - particleMotion = 1; particleGlow = true; particleDir = "mc:portal"; - colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB; - windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF; + groundFill = true; + groundAlpha = 0.20f; + rings = true; + ringCount = 1; + border = true; + borderSpeed = 0.8f; + accents = true; + accentStyle = 0; + lightning = false; + particles = true; + particleDensity = 1.0f; + particleScale = 1.0f; + particleMotion = 1; + particleGlow = true; + particleDir = "mc:portal"; + colorData.innerColor = 0xAA44FF; + colorData.outerColor = 0x6622BB; + windUpColor = 0x60AA44FF; + activeColor = 0xC0CC66FF; effects.add(new AbilityPotionEffect(EnumPotionType.Weakness, 100, 0)); break; case "ELECTRIC": - groundFill = true; groundAlpha = 0.15f; rings = false; ringCount = 1; - border = false; borderSpeed = 1.0f; accents = false; accentStyle = 0; - lightning = true; particles = true; particleDensity = 0.8f; particleScale = 0.6f; - particleMotion = 2; particleGlow = true; particleDir = "mc:enchantmenttable"; - colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB; - windUpColor = 0x604488FF; activeColor = 0xC066AAFF; + groundFill = true; + groundAlpha = 0.15f; + rings = false; + ringCount = 1; + border = false; + borderSpeed = 1.0f; + accents = false; + accentStyle = 0; + lightning = true; + particles = true; + particleDensity = 0.8f; + particleScale = 0.6f; + particleMotion = 2; + particleGlow = true; + particleDir = "mc:enchantmenttable"; + colorData.innerColor = 0x4488FF; + colorData.outerColor = 0x2244BB; + windUpColor = 0x604488FF; + activeColor = 0xC066AAFF; effects.add(new AbilityPotionEffect(EnumPotionType.MiningFatigue, 80, 1)); break; case "FROST": - groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3; - border = true; borderSpeed = 0.3f; accents = true; accentStyle = 0; - lightning = false; particles = true; particleDensity = 1.5f; particleScale = 1.0f; - particleMotion = 1; particleGlow = true; particleDir = "mc:snowshovel"; - colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC; - windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF; + groundFill = true; + groundAlpha = 0.25f; + rings = true; + ringCount = 3; + border = true; + borderSpeed = 0.3f; + accents = true; + accentStyle = 0; + lightning = false; + particles = true; + particleDensity = 1.5f; + particleScale = 1.0f; + particleMotion = 1; + particleGlow = true; + particleDir = "mc:snowshovel"; + colorData.innerColor = 0x88CCFF; + colorData.outerColor = 0x4488CC; + windUpColor = 0x6088CCFF; + activeColor = 0xC0AADDFF; effects.add(new AbilityPotionEffect(EnumPotionType.Slowness, 100, 1)); break; case "DEFAULT": - groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3; - border = true; borderSpeed = 1.0f; accents = true; accentStyle = 0; - lightning = false; particles = false; particleDensity = 1.0f; particleScale = 1.0f; - particleMotion = 0; particleGlow = true; - colorData.innerColor = 0xCCCCCC; colorData.outerColor = 0x666666; - windUpColor = 0x60CCCCCC; activeColor = 0xC0FFFFFF; + groundFill = true; + groundAlpha = 0.25f; + rings = true; + ringCount = 3; + border = true; + borderSpeed = 1.0f; + accents = true; + accentStyle = 0; + lightning = false; + particles = false; + particleDensity = 1.0f; + particleScale = 1.0f; + particleMotion = 0; + particleGlow = true; + colorData.innerColor = 0xCCCCCC; + colorData.outerColor = 0x666666; + windUpColor = 0x60CCCCCC; + activeColor = 0xC0FFFFFF; break; default: break; @@ -310,8 +384,11 @@ public abstract class AbilityZone extends Ability { protected void readZoneNBT(NBTTagCompound nbt, int defaultDuration) { this.durationTicks = nbt.getInteger("durationTicks"); - try { this.zoneShape = ZoneShape.valueOf(nbt.getString("zoneShape")); } - catch (Exception e) { this.zoneShape = ZoneShape.CIRCLE; } + try { + this.zoneShape = ZoneShape.valueOf(nbt.getString("zoneShape")); + } catch (Exception e) { + this.zoneShape = ZoneShape.CIRCLE; + } this.spawnRadius = nbt.getFloat("spawnRadius"); this.zoneCount = nbt.getInteger("zoneCount"); this.zoneHeight = nbt.getFloat("zoneHeight"); @@ -337,73 +414,238 @@ public abstract class AbilityZone extends Ability { this.particleGlow = nbt.getBoolean("particleGlow"); } - public int getDurationTicks() { return durationTicks; } - public void setDurationTicks(int durationTicks) { this.durationTicks = Math.max(1, durationTicks); } + public int getDurationTicks() { + return durationTicks; + } - public ZoneShape getZoneShapeEnum() { return zoneShape; } - public void setZoneShapeEnum(ZoneShape shape) { this.zoneShape = shape; } + public void setDurationTicks(int durationTicks) { + this.durationTicks = Math.max(1, durationTicks); + } + + public ZoneShape getZoneShapeEnum() { + return zoneShape; + } + + public void setZoneShapeEnum(ZoneShape shape) { + this.zoneShape = shape; + } + + public int getZoneShapeOrdinal() { + return zoneShape.ordinal(); + } - public int getZoneShapeOrdinal() { return zoneShape.ordinal(); } public void setZoneShapeOrdinal(int shape) { ZoneShape[] values = ZoneShape.values(); this.zoneShape = shape >= 0 && shape < values.length ? values[shape] : ZoneShape.CIRCLE; } - public float getSpawnRadius() { return spawnRadius; } - public void setSpawnRadius(float spawnRadius) { this.spawnRadius = Math.max(0, spawnRadius); } - public int getZoneCount() { return zoneCount; } - public void setZoneCount(int zoneCount) { this.zoneCount = Math.max(1, zoneCount); } - public float getZoneHeight() { return zoneHeight; } - public void setZoneHeight(float zoneHeight) { this.zoneHeight = Math.max(0.5f, zoneHeight); } + public float getSpawnRadius() { + return spawnRadius; + } - public float getParticleDensity() { return particleDensity; } - public void setParticleDensity(float v) { this.particleDensity = Math.max(0, Math.min(5, v)); } - public float getParticleScale() { return particleScale; } - public void setParticleScale(float v) { this.particleScale = Math.max(0.1f, Math.min(5, v)); } - public float getAnimSpeed() { return animSpeed; } - public void setAnimSpeed(float v) { this.animSpeed = Math.max(0.1f, Math.min(5, v)); } - public float getLightningDensity() { return lightningDensity; } - public void setLightningDensity(float v) { this.lightningDensity = Math.max(0, Math.min(3, v)); } + public void setSpawnRadius(float spawnRadius) { + this.spawnRadius = Math.max(0, spawnRadius); + } - public int getInnerColor() { return colorData.innerColor; } - public void setInnerColor(int color) { colorData.innerColor = color; } - public int getOuterColor() { return colorData.outerColor; } - public void setOuterColor(int color) { colorData.outerColor = color; } - public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; } - public void setOuterColorEnabled(boolean enabled) { colorData.outerColorEnabled = enabled; } + public int getZoneCount() { + return zoneCount; + } - public boolean isGroundFill() { return groundFill; } - public void setGroundFill(boolean v) { this.groundFill = v; } - public float getGroundAlpha() { return groundAlpha; } - public void setGroundAlpha(float v) { this.groundAlpha = Math.max(0, Math.min(1, v)); } - public boolean isRings() { return rings; } - public void setRings(boolean v) { this.rings = v; } - public int getRingCount() { return ringCount; } - public void setRingCount(int v) { this.ringCount = Math.max(1, Math.min(5, v)); } - public boolean isBorder() { return border; } - public void setBorder(boolean v) { this.border = v; } - public float getBorderSpeed() { return borderSpeed; } - public void setBorderSpeed(float v) { this.borderSpeed = Math.max(0.1f, Math.min(5, v)); } - public boolean isAccents() { return accents; } - public void setAccents(boolean v) { this.accents = v; } - public int getAccentStyle() { return accentStyle; } - public void setAccentStyle(int v) { this.accentStyle = Math.max(0, Math.min(AccentStyle.values().length - 1, v)); } - public AccentStyle getAccentStyleEnum() { return AccentStyle.values()[Math.max(0, Math.min(accentStyle, AccentStyle.values().length - 1))]; } - public void setAccentStyleEnum(AccentStyle s) { this.accentStyle = s.ordinal(); } - public boolean isLightning() { return lightning; } - public void setLightning(boolean v) { this.lightning = v; } - public boolean isParticles() { return particles; } - public void setParticles(boolean v) { this.particles = v; } - public int getParticleMotion() { return particleMotion; } - public void setParticleMotion(int v) { this.particleMotion = Math.max(0, Math.min(ParticleMotion.values().length - 1, v)); } - public ParticleMotion getParticleMotionEnum() { return ParticleMotion.values()[Math.max(0, Math.min(particleMotion, ParticleMotion.values().length - 1))]; } - public void setParticleMotionEnum(ParticleMotion m) { this.particleMotion = m.ordinal(); } - public String getParticleDir() { return particleDir; } - public void setParticleDir(String v) { this.particleDir = v != null ? v : ""; } - public int getParticleSize() { return particleSize; } - public void setParticleSize(int v) { this.particleSize = Math.max(1, Math.min(256, v)); } - public boolean isParticleGlow() { return particleGlow; } - public void setParticleGlow(boolean v) { this.particleGlow = v; } + public void setZoneCount(int zoneCount) { + this.zoneCount = Math.max(1, zoneCount); + } + + public float getZoneHeight() { + return zoneHeight; + } + + public void setZoneHeight(float zoneHeight) { + this.zoneHeight = Math.max(0.5f, zoneHeight); + } + + public float getParticleDensity() { + return particleDensity; + } + + public void setParticleDensity(float v) { + this.particleDensity = Math.max(0, Math.min(5, v)); + } + + public float getParticleScale() { + return particleScale; + } + + public void setParticleScale(float v) { + this.particleScale = Math.max(0.1f, Math.min(5, v)); + } + + public float getAnimSpeed() { + return animSpeed; + } + + public void setAnimSpeed(float v) { + this.animSpeed = Math.max(0.1f, Math.min(5, v)); + } + + public float getLightningDensity() { + return lightningDensity; + } + + public void setLightningDensity(float v) { + this.lightningDensity = Math.max(0, Math.min(3, v)); + } + + public int getInnerColor() { + return colorData.innerColor; + } + + public void setInnerColor(int color) { + colorData.innerColor = color; + } + + public int getOuterColor() { + return colorData.outerColor; + } + + public void setOuterColor(int color) { + colorData.outerColor = color; + } + + public boolean isOuterColorEnabled() { + return colorData.outerColorEnabled; + } + + public void setOuterColorEnabled(boolean enabled) { + colorData.outerColorEnabled = enabled; + } + + public boolean isGroundFill() { + return groundFill; + } + + public void setGroundFill(boolean v) { + this.groundFill = v; + } + + public float getGroundAlpha() { + return groundAlpha; + } + + public void setGroundAlpha(float v) { + this.groundAlpha = Math.max(0, Math.min(1, v)); + } + + public boolean isRings() { + return rings; + } + + public void setRings(boolean v) { + this.rings = v; + } + + public int getRingCount() { + return ringCount; + } + + public void setRingCount(int v) { + this.ringCount = Math.max(1, Math.min(5, v)); + } + + public boolean isBorder() { + return border; + } + + public void setBorder(boolean v) { + this.border = v; + } + + public float getBorderSpeed() { + return borderSpeed; + } + + public void setBorderSpeed(float v) { + this.borderSpeed = Math.max(0.1f, Math.min(5, v)); + } + + public boolean isAccents() { + return accents; + } + + public void setAccents(boolean v) { + this.accents = v; + } + + public int getAccentStyle() { + return accentStyle; + } + + public void setAccentStyle(int v) { + this.accentStyle = Math.max(0, Math.min(AccentStyle.values().length - 1, v)); + } + + public AccentStyle getAccentStyleEnum() { + return AccentStyle.values()[Math.max(0, Math.min(accentStyle, AccentStyle.values().length - 1))]; + } + + public void setAccentStyleEnum(AccentStyle s) { + this.accentStyle = s.ordinal(); + } + + public boolean isLightning() { + return lightning; + } + + public void setLightning(boolean v) { + this.lightning = v; + } + + public boolean isParticles() { + return particles; + } + + public void setParticles(boolean v) { + this.particles = v; + } + + public int getParticleMotion() { + return particleMotion; + } + + public void setParticleMotion(int v) { + this.particleMotion = Math.max(0, Math.min(ParticleMotion.values().length - 1, v)); + } + + public ParticleMotion getParticleMotionEnum() { + return ParticleMotion.values()[Math.max(0, Math.min(particleMotion, ParticleMotion.values().length - 1))]; + } + + public void setParticleMotionEnum(ParticleMotion m) { + this.particleMotion = m.ordinal(); + } + + public String getParticleDir() { + return particleDir; + } + + public void setParticleDir(String v) { + this.particleDir = v != null ? v : ""; + } + + public int getParticleSize() { + return particleSize; + } + + public void setParticleSize(int v) { + this.particleSize = Math.max(1, Math.min(256, v)); + } + + public boolean isParticleGlow() { + return particleGlow; + } + + public void setParticleGlow(boolean v) { + this.particleGlow = v; + } @SideOnly(Side.CLIENT) protected void addPresetFieldDef(List defs) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java index 66e5ef4ac..c565ac9fc 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java @@ -6,7 +6,11 @@ import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.RotationMode; import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBeam; @@ -56,7 +60,7 @@ public class AbilityBeam extends AbilityEnergyProjectile impl @Override protected EntityAbilityBeam createEntity(EntityLivingBase caster, EntityLivingBase target, - Vec3 spawnPos, EnergyDisplayData resolved, int index) { + Vec3 spawnPos, EnergyDisplayData resolved, int index) { return new EntityAbilityBeam( caster.worldObj, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, @@ -81,7 +85,7 @@ public class AbilityBeam extends AbilityEnergyProjectile impl @Override protected void setupEntityPreview(EntityAbilityBeam beam, EntityLivingBase caster, - EnergyDisplayData resolved, ProjectileData projData, int index) { + EnergyDisplayData resolved, ProjectileData projData, int index) { beam.setupPreview(caster, beamWidth, headSize, resolved, lightningData, projData.anchor, windUpTicks, 1.0f); } @@ -139,14 +143,29 @@ public class AbilityBeam extends AbilityEnergyProjectile impl // ==================== TYPE-SPECIFIC GETTERS ==================== - public float getSpeed() { return homingData.speed; } - public void setSpeed(float speed) { homingData.speed = speed; } + public float getSpeed() { + return homingData.speed; + } - public float getBeamWidth() { return beamWidth; } - public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; } + public void setSpeed(float speed) { + homingData.speed = speed; + } - public float getHeadSize() { return headSize; } - public void setHeadSize(float headSize) { this.headSize = headSize; } + public float getBeamWidth() { + return beamWidth; + } + + public void setBeamWidth(float beamWidth) { + this.beamWidth = beamWidth; + } + + public float getHeadSize() { + return headSize; + } + + public void setHeadSize(float headSize) { + this.headSize = headSize; + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index cd36eeab3..dc6b18add 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -5,7 +5,11 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityDisc; @@ -56,7 +60,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl @Override protected EntityAbilityDisc createEntity(EntityLivingBase caster, EntityLivingBase target, - Vec3 spawnPos, EnergyDisplayData resolved, int index) { + Vec3 spawnPos, EnergyDisplayData resolved, int index) { return new EntityAbilityDisc( caster.worldObj, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, @@ -81,7 +85,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl @Override protected void setupEntityPreview(EntityAbilityDisc disc, EntityLivingBase caster, - EnergyDisplayData resolved, ProjectileData projData, int index) { + EnergyDisplayData resolved, ProjectileData projData, int index) { disc.setupPreview(caster, discRadius, discThickness, resolved, lightningData, projData.anchor, windUpTicks, vertical); } @@ -139,23 +143,53 @@ public class AbilityDisc extends AbilityEnergyProjectile impl // ==================== TYPE-SPECIFIC GETTERS ==================== - public float getSpeed() { return homingData.speed; } - public void setSpeed(float speed) { homingData.speed = speed; } + public float getSpeed() { + return homingData.speed; + } - public float getDiscRadius() { return discRadius; } - public void setDiscRadius(float discRadius) { this.discRadius = discRadius; } + public void setSpeed(float speed) { + homingData.speed = speed; + } - public float getDiscThickness() { return discThickness; } - public void setDiscThickness(float discThickness) { this.discThickness = discThickness; } + public float getDiscRadius() { + return discRadius; + } - public boolean isVertical() { return vertical; } - public void setVertical(boolean vertical) { this.vertical = vertical; } + public void setDiscRadius(float discRadius) { + this.discRadius = discRadius; + } - public boolean isBoomerang() { return boomerang; } - public void setBoomerang(boolean boomerang) { this.boomerang = boomerang; } + public float getDiscThickness() { + return discThickness; + } - public int getBoomerangDelay() { return boomerangDelay; } - public void setBoomerangDelay(int boomerangDelay) { this.boomerangDelay = boomerangDelay; } + public void setDiscThickness(float discThickness) { + this.discThickness = discThickness; + } + + public boolean isVertical() { + return vertical; + } + + public void setVertical(boolean vertical) { + this.vertical = vertical; + } + + public boolean isBoomerang() { + return boomerang; + } + + public void setBoomerang(boolean boomerang) { + this.boomerang = boomerang; + } + + public int getBoomerangDelay() { + return boomerangDelay; + } + + public void setBoomerangDelay(int boomerangDelay) { + this.boomerangDelay = boomerangDelay; + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index b6bb57e96..2fe0c4572 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -10,7 +10,6 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyDome; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; @@ -111,11 +110,21 @@ public class AbilityDome extends AbilityEnergyBarrier { // ==================== GETTERS & SETTERS ==================== - public float getDomeRadius() { return domeRadius; } - public void setDomeRadius(float radius) { this.domeRadius = Math.max(1.0f, radius); } + public float getDomeRadius() { + return domeRadius; + } - public boolean isFollowCaster() { return followCaster; } - public void setFollowCaster(boolean follow) { this.followCaster = follow; } + public void setDomeRadius(float radius) { + this.domeRadius = Math.max(1.0f, radius); + } + + public boolean isFollowCaster() { + return followCaster; + } + + public void setFollowCaster(boolean follow) { + this.followCaster = follow; + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java index 3e0747160..59917889e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java @@ -26,40 +26,95 @@ public abstract class AbilityEnergy extends Ability { // ==================== DISPLAY DATA ==================== - public int getInnerColor() { return displayData.innerColor; } - public void setInnerColor(int color) { displayData.innerColor = color; } + public int getInnerColor() { + return displayData.innerColor; + } - public float getInnerAlpha() { return displayData.innerAlpha; } - public void setInnerAlpha(float alpha) { displayData.innerAlpha = alpha; } + public void setInnerColor(int color) { + displayData.innerColor = color; + } - public int getOuterColor() { return displayData.outerColor; } - public void setOuterColor(int color) { displayData.outerColor = color; } + public float getInnerAlpha() { + return displayData.innerAlpha; + } - public boolean isOuterColorEnabled() { return displayData.outerColorEnabled; } - public void setOuterColorEnabled(boolean enabled) { displayData.outerColorEnabled = enabled; } + public void setInnerAlpha(float alpha) { + displayData.innerAlpha = alpha; + } - public float getOuterColorWidth() { return displayData.outerColorWidth; } - public void setOuterColorWidth(float width) { displayData.outerColorWidth = width; } + public int getOuterColor() { + return displayData.outerColor; + } - public float getOuterColorAlpha() { return displayData.outerColorAlpha; } - public void setOuterColorAlpha(float alpha) { displayData.outerColorAlpha = alpha; } + public void setOuterColor(int color) { + displayData.outerColor = color; + } - public float getRotationSpeed() { return displayData.rotationSpeed; } - public void setRotationSpeed(float speed) { displayData.rotationSpeed = speed; } + public boolean isOuterColorEnabled() { + return displayData.outerColorEnabled; + } + + public void setOuterColorEnabled(boolean enabled) { + displayData.outerColorEnabled = enabled; + } + + public float getOuterColorWidth() { + return displayData.outerColorWidth; + } + + public void setOuterColorWidth(float width) { + displayData.outerColorWidth = width; + } + + public float getOuterColorAlpha() { + return displayData.outerColorAlpha; + } + + public void setOuterColorAlpha(float alpha) { + displayData.outerColorAlpha = alpha; + } + + public float getRotationSpeed() { + return displayData.rotationSpeed; + } + + public void setRotationSpeed(float speed) { + displayData.rotationSpeed = speed; + } // ==================== LIGHTNING DATA ==================== - public boolean hasLightningEffect() { return lightningData.lightningEffect; } - public void setLightningEffect(boolean enabled) { lightningData.lightningEffect = enabled; } + public boolean hasLightningEffect() { + return lightningData.lightningEffect; + } - public float getLightningDensity() { return lightningData.lightningDensity; } - public void setLightningDensity(float density) { lightningData.lightningDensity = density; } + public void setLightningEffect(boolean enabled) { + lightningData.lightningEffect = enabled; + } - public float getLightningRadius() { return lightningData.lightningRadius; } - public void setLightningRadius(float radius) { lightningData.lightningRadius = radius; } + public float getLightningDensity() { + return lightningData.lightningDensity; + } - public int getLightningFadeTime() { return lightningData.lightningFadeTime; } - public void setLightningFadeTime(int fadeTime) { lightningData.lightningFadeTime = fadeTime; } + public void setLightningDensity(float density) { + lightningData.lightningDensity = density; + } + + public float getLightningRadius() { + return lightningData.lightningRadius; + } + + public void setLightningRadius(float radius) { + lightningData.lightningRadius = radius; + } + + public int getLightningFadeTime() { + return lightningData.lightningFadeTime; + } + + public void setLightningFadeTime(int fadeTime) { + lightningData.lightningFadeTime = fadeTime; + } // ==================== NBT HELPERS ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java index 7909f5a56..3319b7dc6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java @@ -142,37 +142,95 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { // Display and lightning data inherited from AbilityEnergy // Barrier data - public float getBarrierMaxHealth() { return barrierData.maxHealth; } - public void setBarrierMaxHealth(float maxHealth) { barrierData.setMaxHealth(maxHealth); } + public float getBarrierMaxHealth() { + return barrierData.maxHealth; + } - public boolean isUseHealth() { return barrierData.useHealth; } - public void setUseHealth(boolean useHealth) { barrierData.useHealth = useHealth; } + public void setBarrierMaxHealth(float maxHealth) { + barrierData.setMaxHealth(maxHealth); + } - public int getBarrierDuration() { return barrierData.durationTicks; } - public void setBarrierDuration(int ticks) { barrierData.setDurationTicks(ticks); } + public boolean isUseHealth() { + return barrierData.useHealth; + } - public boolean isUseDuration() { return barrierData.useDuration; } - public void setUseDuration(boolean useDuration) { barrierData.useDuration = useDuration; } + public void setUseHealth(boolean useHealth) { + barrierData.useHealth = useHealth; + } - public float getDefaultMultiplier() { return barrierData.defaultMultiplier; } - public void setDefaultMultiplier(float mult) { barrierData.defaultMultiplier = mult; } + public int getBarrierDuration() { + return barrierData.durationTicks; + } - public void setDamageMultiplier(String typeId, float mult) { barrierData.setMultiplier(typeId, mult); } - public float getDamageMultiplier(String typeId) { return barrierData.getMultiplier(typeId); } + public void setBarrierDuration(int ticks) { + barrierData.setDurationTicks(ticks); + } + + public boolean isUseDuration() { + return barrierData.useDuration; + } + + public void setUseDuration(boolean useDuration) { + barrierData.useDuration = useDuration; + } + + public float getDefaultMultiplier() { + return barrierData.defaultMultiplier; + } + + public void setDefaultMultiplier(float mult) { + barrierData.defaultMultiplier = mult; + } + + public void setDamageMultiplier(String typeId, float mult) { + barrierData.setMultiplier(typeId, mult); + } + + public float getDamageMultiplier(String typeId) { + return barrierData.getMultiplier(typeId); + } // Knockback data - public boolean isKnockbackEnabled() { return barrierData.knockbackEnabled; } - public void setKnockbackEnabled(boolean enabled) { barrierData.knockbackEnabled = enabled; } - public float getKnockbackStrength() { return barrierData.knockbackStrength; } - public void setKnockbackStrength(float strength) { barrierData.knockbackStrength = strength; } - public String getKnockbackTargetKey() { return barrierData.getKnockbackTargetKey(); } - public void setKnockbackTargetKey(String key) { barrierData.setKnockbackTargetFromKey(key); } + public boolean isKnockbackEnabled() { + return barrierData.knockbackEnabled; + } + + public void setKnockbackEnabled(boolean enabled) { + barrierData.knockbackEnabled = enabled; + } + + public float getKnockbackStrength() { + return barrierData.knockbackStrength; + } + + public void setKnockbackStrength(float strength) { + barrierData.knockbackStrength = strength; + } + + public String getKnockbackTargetKey() { + return barrierData.getKnockbackTargetKey(); + } + + public void setKnockbackTargetKey(String key) { + barrierData.setKnockbackTargetFromKey(key); + } // Melee data - public boolean isMeleeEnabled() { return barrierData.meleeEnabled; } - public void setMeleeEnabled(boolean enabled) { barrierData.meleeEnabled = enabled; } - public float getMeleeDamageMultiplier() { return barrierData.meleeDamageMultiplier; } - public void setMeleeDamageMultiplier(float mult) { barrierData.meleeDamageMultiplier = mult; } + public boolean isMeleeEnabled() { + return barrierData.meleeEnabled; + } + + public void setMeleeEnabled(boolean enabled) { + barrierData.meleeEnabled = enabled; + } + + public float getMeleeDamageMultiplier() { + return barrierData.meleeDamageMultiplier; + } + + public void setMeleeDamageMultiplier(float mult) { + barrierData.meleeDamageMultiplier = mult; + } // ==================== GUI ==================== @@ -199,7 +257,7 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { FieldDef.floatField("ability.knockbackStrength", this::getKnockbackStrength, this::setKnockbackStrength) .range(0, 10).visibleWhen(this::isKnockbackEnabled), FieldDef.stringEnumField("ability.knockbackTarget", EnergyBarrierData.getKnockbackTargetKeys(), - this::getKnockbackTargetKey, this::setKnockbackTargetKey) + this::getKnockbackTargetKey, this::setKnockbackTargetKey) .visibleWhen(this::isKnockbackEnabled) )); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index d9ab67646..6575b29c0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -2,10 +2,14 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -24,7 +28,7 @@ import java.util.List; * Abstract base for energy projectile abilities (Orb, Disc, Beam, LaserShot). * Handles shared lifecycle (windup → execute → active → completion), multi-projectile * support with staggered firing, telegraph creation, NBT persistence, and GUI definitions. - * + *

* Subclasses provide entity creation/firing/charging logic and type-specific fields. * * @param The entity type this ability spawns @@ -74,7 +78,7 @@ public abstract class AbilityEnergyProjectile * Called during windup (tick 1) and burst refire (onExecute when entities are null). */ protected abstract E createEntity(EntityLivingBase caster, EntityLivingBase target, - Vec3 spawnPos, EnergyDisplayData resolved, int index); + Vec3 spawnPos, EnergyDisplayData resolved, int index); /** * Fire an entity (start moving toward target). Entity is guaranteed non-null and alive. @@ -90,7 +94,7 @@ public abstract class AbilityEnergyProjectile * Setup entity for preview mode during windup. */ protected abstract void setupEntityPreview(E entity, EntityLivingBase caster, - EnergyDisplayData resolved, ProjectileData projData, int index); + EnergyDisplayData resolved, ProjectileData projData, int index); /** * Create typed array (Java generics can't create generic arrays). @@ -129,9 +133,12 @@ public abstract class AbilityEnergyProjectile */ protected AnchorPoint getDefaultAnchor(int index) { switch (index) { - case 0: return AnchorPoint.RIGHT_HAND; - case 1: return AnchorPoint.LEFT_HAND; - default: return AnchorPoint.FRONT; + case 0: + return AnchorPoint.RIGHT_HAND; + case 1: + return AnchorPoint.LEFT_HAND; + default: + return AnchorPoint.FRONT; } } @@ -168,15 +175,31 @@ public abstract class AbilityEnergyProjectile } } - public int getProjectileCount() { return projectileCount; } + public int getProjectileCount() { + return projectileCount; + } - public int getFireDelay() { return fireDelay; } - public void setFireDelay(int delay) { this.fireDelay = Math.max(0, delay); } + public int getFireDelay() { + return fireDelay; + } - /** @deprecated Use {@link #getFireDelay()} */ - public int getDualFireDelay() { return fireDelay; } - /** @deprecated Use {@link #setFireDelay(int)} */ - public void setDualFireDelay(int delay) { setFireDelay(delay); } + public void setFireDelay(int delay) { + this.fireDelay = Math.max(0, delay); + } + + /** + * @deprecated Use {@link #getFireDelay()} + */ + public int getDualFireDelay() { + return fireDelay; + } + + /** + * @deprecated Use {@link #setFireDelay(int)} + */ + public void setDualFireDelay(int delay) { + setFireDelay(delay); + } protected int clampIndex(int index) { return Math.max(0, Math.min(index, projectileCount - 1)); @@ -317,7 +340,7 @@ public abstract class AbilityEnergyProjectile // Check if entity was removed from world (chunk unload, etc.) // Skip this check for preview entities - they're not registered in the world if (!isPreview() && tick > 5 && entity.worldObj != null - && entity.worldObj.getEntityByID(entity.getEntityId()) != entity) { + && entity.worldObj.getEntityByID(entity.getEntityId()) != entity) { entity.setDead(); continue; } @@ -449,64 +472,163 @@ public abstract class AbilityEnergyProjectile // ==================== API GETTERS & SETTERS ==================== // Combat data - @Override public float getDamage() { return combatData.damage; } - @Override public void setDamage(float damage) { combatData.damage = damage; } + @Override + public float getDamage() { + return combatData.damage; + } - @Override public float getKnockback() { return combatData.knockback; } - @Override public void setKnockback(float knockback) { combatData.knockback = knockback; } + @Override + public void setDamage(float damage) { + combatData.damage = damage; + } - @Override public float getKnockbackUp() { return combatData.knockbackUp; } - @Override public void setKnockbackUp(float knockbackUp) { combatData.knockbackUp = knockbackUp; } + @Override + public float getKnockback() { + return combatData.knockback; + } - @Override public boolean isExplosive() { return combatData.explosive; } - @Override public void setExplosive(boolean explosive) { combatData.explosive = explosive; } + @Override + public void setKnockback(float knockback) { + combatData.knockback = knockback; + } - @Override public float getExplosionRadius() { return combatData.explosionRadius; } - @Override public void setExplosionRadius(float explosionRadius) { combatData.explosionRadius = explosionRadius; } + @Override + public float getKnockbackUp() { + return combatData.knockbackUp; + } - @Override public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; } - @Override public void setExplosionDamageFalloff(float falloff) { combatData.explosionDamageFalloff = falloff; } + @Override + public void setKnockbackUp(float knockbackUp) { + combatData.knockbackUp = knockbackUp; + } + + @Override + public boolean isExplosive() { + return combatData.explosive; + } + + @Override + public void setExplosive(boolean explosive) { + combatData.explosive = explosive; + } + + @Override + public float getExplosionRadius() { + return combatData.explosionRadius; + } + + @Override + public void setExplosionRadius(float explosionRadius) { + combatData.explosionRadius = explosionRadius; + } + + @Override + public float getExplosionDamageFalloff() { + return combatData.explosionDamageFalloff; + } + + @Override + public void setExplosionDamageFalloff(float falloff) { + combatData.explosionDamageFalloff = falloff; + } // Lifespan data - @Override public float getMaxDistance() { return lifespanData.maxDistance; } - @Override public void setMaxDistance(float maxDistance) { lifespanData.maxDistance = maxDistance; } + @Override + public float getMaxDistance() { + return lifespanData.maxDistance; + } - @Override public int getMaxLifetime() { return lifespanData.maxLifetime; } - @Override public void setMaxLifetime(int maxLifetime) { lifespanData.maxLifetime = maxLifetime; } + @Override + public void setMaxDistance(float maxDistance) { + lifespanData.maxDistance = maxDistance; + } + + @Override + public int getMaxLifetime() { + return lifespanData.maxLifetime; + } + + @Override + public void setMaxLifetime(int maxLifetime) { + lifespanData.maxLifetime = maxLifetime; + } // Homing data - public boolean isHoming() { return homingData.homing; } - public void setHoming(boolean homing) { homingData.homing = homing; } + public boolean isHoming() { + return homingData.homing; + } - public float getHomingStrength() { return homingData.homingStrength; } - public void setHomingStrength(float strength) { homingData.homingStrength = strength; } + public void setHoming(boolean homing) { + homingData.homing = homing; + } - public float getHomingRange() { return homingData.homingRange; } - public void setHomingRange(float range) { homingData.homingRange = range; } + public float getHomingStrength() { + return homingData.homingStrength; + } + + public void setHomingStrength(float strength) { + homingData.homingStrength = strength; + } + + public float getHomingRange() { + return homingData.homingRange; + } + + public void setHomingRange(float range) { + homingData.homingRange = range; + } // Display and lightning data inherited from AbilityEnergy // Anchor - default to projectile 0 - public AnchorPoint getAnchorPointEnum() { return projectiles[0].anchor.anchorPoint; } - public void setAnchorPointEnum(AnchorPoint point) { projectiles[0].anchor.anchorPoint = point; } + public AnchorPoint getAnchorPointEnum() { + return projectiles[0].anchor.anchorPoint; + } - public float getAnchorOffsetX() { return projectiles[0].anchor.anchorOffsetX; } - public void setAnchorOffsetX(float x) { projectiles[0].anchor.anchorOffsetX = x; } + public void setAnchorPointEnum(AnchorPoint point) { + projectiles[0].anchor.anchorPoint = point; + } - public float getAnchorOffsetY() { return projectiles[0].anchor.anchorOffsetY; } - public void setAnchorOffsetY(float y) { projectiles[0].anchor.anchorOffsetY = y; } + public float getAnchorOffsetX() { + return projectiles[0].anchor.anchorOffsetX; + } - public float getAnchorOffsetZ() { return projectiles[0].anchor.anchorOffsetZ; } - public void setAnchorOffsetZ(float z) { projectiles[0].anchor.anchorOffsetZ = z; } + public void setAnchorOffsetX(float x) { + projectiles[0].anchor.anchorOffsetX = x; + } - @Override public int getAnchorPoint() { return projectiles[0].anchor.anchorPoint.ordinal(); } - @Override public void setAnchorPoint(int point) { projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); } + public float getAnchorOffsetY() { + return projectiles[0].anchor.anchorOffsetY; + } + + public void setAnchorOffsetY(float y) { + projectiles[0].anchor.anchorOffsetY = y; + } + + public float getAnchorOffsetZ() { + return projectiles[0].anchor.anchorOffsetZ; + } + + public void setAnchorOffsetZ(float z) { + projectiles[0].anchor.anchorOffsetZ = z; + } + + @Override + public int getAnchorPoint() { + return projectiles[0].anchor.anchorPoint.ordinal(); + } + + @Override + public void setAnchorPoint(int point) { + projectiles[0].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); + } // Indexed API methods public int getInnerColor(int index) { ProjectileData p = projectiles[clampIndex(index)]; return p.colorOverride ? p.innerColor : displayData.innerColor; } + public void setInnerColor(int index, int color) { ProjectileData p = projectiles[clampIndex(index)]; p.colorOverride = true; @@ -517,17 +639,32 @@ public abstract class AbilityEnergyProjectile ProjectileData p = projectiles[clampIndex(index)]; return p.colorOverride ? p.outerColor : displayData.outerColor; } + public void setOuterColor(int index, int color) { ProjectileData p = projectiles[clampIndex(index)]; p.colorOverride = true; p.outerColor = color; } - public int getAnchorPoint(int index) { return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); } - public void setAnchorPoint(int index, int point) { projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); } - public void setAnchorOffsetX(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; } - public void setAnchorOffsetY(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; } - public void setAnchorOffsetZ(int index, float offset) { projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; } + public int getAnchorPoint(int index) { + return projectiles[clampIndex(index)].anchor.anchorPoint.ordinal(); + } + + public void setAnchorPoint(int index, int point) { + projectiles[clampIndex(index)].anchor.anchorPoint = AnchorPoint.fromOrdinal(point); + } + + public void setAnchorOffsetX(int index, float offset) { + projectiles[clampIndex(index)].anchor.anchorOffsetX = offset; + } + + public void setAnchorOffsetY(int index, float offset) { + projectiles[clampIndex(index)].anchor.anchorOffsetY = offset; + } + + public void setAnchorOffsetZ(int index, float offset) { + projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset; + } // ==================== GUI FIELD DEFINITIONS ==================== @@ -555,7 +692,7 @@ public abstract class AbilityEnergyProjectile .visibleWhen(() -> idx < projectileCount)); defs.add(FieldDef.enumField("ability.anchorPoint", AnchorPoint.class, - () -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v) + () -> projectiles[idx].anchor.anchorPoint, v -> projectiles[idx].anchor.anchorPoint = v) .tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount)); defs.add(FieldDef.row( FieldDef.floatField("ability.anchor.offsetX", () -> projectiles[idx].anchor.anchorOffsetX, v -> projectiles[idx].anchor.anchorOffsetX = v) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index c62c886e7..51f3da8ca 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -6,7 +6,11 @@ import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.RotationMode; import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -111,7 +115,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile implem @Override protected EntityAbilityOrb createEntity(EntityLivingBase caster, EntityLivingBase target, - Vec3 spawnPos, EnergyDisplayData resolved, int index) { + Vec3 spawnPos, EnergyDisplayData resolved, int index) { return new EntityAbilityOrb( caster.worldObj, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, orbSize, @@ -74,7 +78,7 @@ public class AbilityOrb extends AbilityEnergyProjectile implem @Override protected void setupEntityPreview(EntityAbilityOrb orb, EntityLivingBase caster, - EnergyDisplayData resolved, ProjectileData projData, int index) { + EnergyDisplayData resolved, ProjectileData projData, int index) { orb.setupPreview(caster, orbSize, resolved, lightningData, projData.anchor, windUpTicks); } @@ -137,11 +141,21 @@ public class AbilityOrb extends AbilityEnergyProjectile implem // ==================== TYPE-SPECIFIC GETTERS ==================== - public float getOrbSpeed() { return homingData.speed; } - public void setOrbSpeed(float speed) { homingData.speed = speed; } + public float getOrbSpeed() { + return homingData.speed; + } - public float getOrbSize() { return orbSize; } - public void setOrbSize(float size) { this.orbSize = size; } + public void setOrbSpeed(float speed) { + homingData.speed = speed; + } + + public float getOrbSize() { + return orbSize; + } + + public void setOrbSize(float size) { + this.orbSize = size; + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java index 3cb407ef3..5d78e8c1d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java @@ -12,7 +12,6 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyPanel; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; @@ -124,11 +123,21 @@ public class AbilityShield extends AbilityEnergyBarrier { // ==================== GETTERS & SETTERS ==================== - public float getShieldWidth() { return shieldWidth; } - public void setShieldWidth(float width) { this.shieldWidth = Math.max(0.5f, width); } + public float getShieldWidth() { + return shieldWidth; + } - public float getShieldHeight() { return shieldHeight; } - public void setShieldHeight(float height) { this.shieldHeight = Math.max(0.5f, height); } + public void setShieldWidth(float width) { + this.shieldWidth = Math.max(0.5f, width); + } + + public float getShieldHeight() { + return shieldHeight; + } + + public void setShieldHeight(float height) { + this.shieldHeight = Math.max(0.5f, height); + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java index c79822f52..387762c9b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java @@ -5,7 +5,11 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityEnergySlicer; @@ -53,7 +57,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile { @Override protected EntityEnergySlicer createEntity(EntityLivingBase caster, EntityLivingBase target, - Vec3 spawnPos, EnergyDisplayData resolved, int index) { + Vec3 spawnPos, EnergyDisplayData resolved, int index) { return new EntityEnergySlicer( caster.worldObj, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, @@ -79,7 +83,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile { @Override protected void setupEntityPreview(EntityEnergySlicer slicer, EntityLivingBase caster, - EnergyDisplayData resolved, ProjectileData projData, int index) { + EnergyDisplayData resolved, ProjectileData projData, int index) { slicer.setupPreview(caster, sliceWidth, sliceThickness, resolved, lightningData, projData.anchor, windUpTicks); } @@ -130,17 +134,37 @@ public class AbilitySlicer extends AbilityEnergyProjectile { // ==================== TYPE-SPECIFIC GETTERS ==================== - public float getSlicerSpeed() { return homingData.speed; } - public void setSlicerSpeed(float speed) { homingData.speed = speed; } + public float getSlicerSpeed() { + return homingData.speed; + } - public float getSliceWidth() { return sliceWidth; } - public void setSliceWidth(float width) { this.sliceWidth = Math.max(0.5f, width); } + public void setSlicerSpeed(float speed) { + homingData.speed = speed; + } - public float getSliceThickness() { return sliceThickness; } - public void setSliceThickness(float thickness) { this.sliceThickness = Math.max(0.05f, thickness); } + public float getSliceWidth() { + return sliceWidth; + } - public boolean isPiercing() { return piercing; } - public void setPiercing(boolean piercing) { this.piercing = piercing; } + public void setSliceWidth(float width) { + this.sliceWidth = Math.max(0.5f, width); + } + + public float getSliceThickness() { + return sliceThickness; + } + + public void setSliceThickness(float thickness) { + this.sliceThickness = Math.max(0.05f, thickness); + } + + public boolean isPiercing() { + return piercing; + } + + public void setPiercing(boolean piercing) { + this.piercing = piercing; + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index 67f9643d3..1e0302522 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -22,7 +22,7 @@ import java.util.List; * Sweeper ability: A low sweeping beam that rotates around the NPC. * Players can jump over it. The beam completes a set number of rotations before ending. * Telegraph shows a large circle on the ground indicating range. - * + *

* All damage logic is handled by the EntityAbilitySweeper entity to ensure * visual and damage hitbox alignment. */ @@ -173,24 +173,77 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { } // Getters & Setters (type-specific only; color/lightning inherited from AbilityEnergy) - public float getBeamLength() { return beamLength; } - public void setBeamLength(float beamLength) { this.beamLength = beamLength; } - public float getBeamWidth() { return beamWidth; } - public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; } - public float getBeamHeight() { return beamHeight; } - public void setBeamHeight(float beamHeight) { this.beamHeight = beamHeight; } - public float getDamage() { return damage; } - public void setDamage(float damage) { this.damage = damage; } - public int getDamageInterval() { return damageInterval; } - public void setDamageInterval(int damageInterval) { this.damageInterval = damageInterval; } - public boolean isPiercing() { return piercing; } - public void setPiercing(boolean piercing) { this.piercing = piercing; } - public float getSweepSpeed() { return sweepSpeed; } - public void setSweepSpeed(float sweepSpeed) { this.sweepSpeed = sweepSpeed; } - public int getNumberOfRotations() { return numberOfRotations; } - public void setNumberOfRotations(int numberOfRotations) { this.numberOfRotations = numberOfRotations; } - public boolean isLockOnTarget() { return lockOnTarget; } - public void setLockOnTarget(boolean lockOnTarget) { this.lockOnTarget = lockOnTarget; } + public float getBeamLength() { + return beamLength; + } + + public void setBeamLength(float beamLength) { + this.beamLength = beamLength; + } + + public float getBeamWidth() { + return beamWidth; + } + + public void setBeamWidth(float beamWidth) { + this.beamWidth = beamWidth; + } + + public float getBeamHeight() { + return beamHeight; + } + + public void setBeamHeight(float beamHeight) { + this.beamHeight = beamHeight; + } + + public float getDamage() { + return damage; + } + + public void setDamage(float damage) { + this.damage = damage; + } + + public int getDamageInterval() { + return damageInterval; + } + + public void setDamageInterval(int damageInterval) { + this.damageInterval = damageInterval; + } + + public boolean isPiercing() { + return piercing; + } + + public void setPiercing(boolean piercing) { + this.piercing = piercing; + } + + public float getSweepSpeed() { + return sweepSpeed; + } + + public void setSweepSpeed(float sweepSpeed) { + this.sweepSpeed = sweepSpeed; + } + + public int getNumberOfRotations() { + return numberOfRotations; + } + + public void setNumberOfRotations(int numberOfRotations) { + this.numberOfRotations = numberOfRotations; + } + + public boolean isLockOnTarget() { + return lockOnTarget; + } + + public void setLockOnTarget(boolean lockOnTarget) { + this.lockOnTarget = lockOnTarget; + } @Override public int getMaxPreviewDuration() { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java index f6bc037a4..60680888f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java @@ -11,7 +11,6 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyPanel; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; @@ -139,28 +138,65 @@ public class AbilityWall extends AbilityEnergyBarrier { // ==================== GETTERS & SETTERS ==================== - public EnergyPanelData getPanelData() { return panelData; } + public EnergyPanelData getPanelData() { + return panelData; + } - public float getPanelWidth() { return panelData.panelWidth; } - public void setPanelWidth(float width) { panelData.setPanelWidth(width); } + public float getPanelWidth() { + return panelData.panelWidth; + } - public float getPanelHeight() { return panelData.panelHeight; } - public void setPanelHeight(float height) { panelData.setPanelHeight(height); } + public void setPanelWidth(float width) { + panelData.setPanelWidth(width); + } - public float getHeightOffset() { return panelData.heightOffset; } - public void setHeightOffset(float offset) { panelData.heightOffset = offset; } + public float getPanelHeight() { + return panelData.panelHeight; + } - public boolean isLaunching() { return panelData.launching; } - public void setLaunching(boolean launching) { panelData.launching = launching; } + public void setPanelHeight(float height) { + panelData.setPanelHeight(height); + } - public float getLaunchSpeed() { return panelData.launchSpeed; } - public void setLaunchSpeed(float speed) { panelData.setLaunchSpeed(speed); } + public float getHeightOffset() { + return panelData.heightOffset; + } - public float getLaunchDamage() { return panelData.launchDamage; } - public void setLaunchDamage(float damage) { panelData.setLaunchDamage(damage); } + public void setHeightOffset(float offset) { + panelData.heightOffset = offset; + } - public float getLaunchKnockback() { return panelData.launchKnockback; } - public void setLaunchKnockback(float kb) { panelData.setLaunchKnockback(kb); } + public boolean isLaunching() { + return panelData.launching; + } + + public void setLaunching(boolean launching) { + panelData.launching = launching; + } + + public float getLaunchSpeed() { + return panelData.launchSpeed; + } + + public void setLaunchSpeed(float speed) { + panelData.setLaunchSpeed(speed); + } + + public float getLaunchDamage() { + return panelData.launchDamage; + } + + public void setLaunchDamage(float damage) { + panelData.setLaunchDamage(damage); + } + + public float getLaunchKnockback() { + return panelData.launchKnockback; + } + + public void setLaunchKnockback(float kb) { + panelData.setLaunchKnockback(kb); + } // ==================== TYPE-SPECIFIC GUI ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 4b6b6e18d..87cddc415 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -1,9 +1,14 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; -import noppes.npcs.EventHooks; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; @@ -11,6 +16,7 @@ import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; +import noppes.npcs.EventHooks; import noppes.npcs.LogWriter; import java.util.ArrayList; @@ -19,10 +25,10 @@ import java.util.List; /** * Beam projectile - head with trailing path that curves with homing. * Stays attached to origin point, trail shows path of head. - * + *

* IMPORTANT: Trail points are stored RELATIVE to startX/Y/Z (origin). * This ensures rendering is stable regardless of entity position interpolation. - * + *

* Design inspired by LouisXIV's energy attack system. */ public class EntityAbilityBeam extends EntityEnergyProjectile { @@ -63,6 +69,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { /** * Full constructor with all parameters using data classes. + * * @param anchoredMode If true, origin follows owner and tail orb is rendered. * If false, beam is free-moving with trailing length (no tail orb). */ @@ -214,7 +221,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { /** * Start the beam firing (exit charging mode). * Called by ability when windup ends. - * + *

* For anchored beams: origin follows owner, head starts at charged position * For non-anchored beams: origin fixed at charged position, head starts there */ @@ -281,8 +288,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { // Check distance using head offset (distance from origin) double distFromOrigin = Math.sqrt( headOffsetX * headOffsetX + - headOffsetY * headOffsetY + - headOffsetZ * headOffsetZ + headOffsetY * headOffsetY + + headOffsetZ * headOffsetZ ); boolean exceeded = distFromOrigin >= getMaxDistance(); if (exceeded && !worldObj.isRemote && DEBUG_LOGGING) { @@ -440,8 +447,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { Vec3 lastPoint = trailPoints.get(trailPoints.size() - 1); double dist = Math.sqrt( (headOffsetX - lastPoint.xCoord) * (headOffsetX - lastPoint.xCoord) + - (headOffsetY - lastPoint.yCoord) * (headOffsetY - lastPoint.yCoord) + - (headOffsetZ - lastPoint.zCoord) * (headOffsetZ - lastPoint.zCoord) + (headOffsetY - lastPoint.yCoord) * (headOffsetY - lastPoint.yCoord) + + (headOffsetZ - lastPoint.zCoord) * (headOffsetZ - lastPoint.zCoord) ); if (dist >= MIN_POINT_DISTANCE) { diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 72ebe29ae..2b8a99d07 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -1,22 +1,28 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; -import noppes.npcs.EventHooks; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; +import noppes.npcs.EventHooks; import java.util.List; /** * Disc projectile - flat spinning disc with optional boomerang behavior. * Has a wider, thinner hitbox compared to Orb. - * + *

* Design inspired by LouisXIV's energy attack system. */ public class EntityAbilityDisc extends EntityEnergyProjectile { @@ -252,8 +258,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { protected boolean checkMaxDistance() { double distTraveled = Math.sqrt( (posX - startX) * (posX - startX) + - (posY - startY) * (posY - startY) + - (posZ - startZ) * (posZ - startZ) + (posY - startY) * (posY - startY) + + (posZ - startZ) * (posZ - startZ) ); if (boomerang) { @@ -270,8 +276,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { if (owner != null) { double distToOwner = Math.sqrt( (posX - owner.posX) * (posX - owner.posX) + - (posY - owner.posY) * (posY - owner.posY) + - (posZ - owner.posZ) * (posZ - owner.posZ) + (posY - owner.posY) * (posY - owner.posY) + + (posZ - owner.posZ) * (posZ - owner.posZ) ); return distToOwner < 1.5; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 391d9466c..1a13c1060 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -1,15 +1,19 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; -import noppes.npcs.EventHooks; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; +import noppes.npcs.EventHooks; import java.util.HashSet; import java.util.List; @@ -18,7 +22,7 @@ import java.util.Set; /** * Laser projectile - fast expanding thin line that pierces through multiple targets. * No homing, travels in a straight line from origin to max distance. - * + *

* Design inspired by LouisXIV's energy attack system. */ public class EntityAbilityLaser extends EntityEnergyProjectile { @@ -246,8 +250,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { // Laser stops at block currentLength = (float) Math.sqrt( (blockHit.hitVec.xCoord - startX) * (blockHit.hitVec.xCoord - startX) + - (blockHit.hitVec.yCoord - startY) * (blockHit.hitVec.yCoord - startY) + - (blockHit.hitVec.zCoord - startZ) * (blockHit.hitVec.zCoord - startZ) + (blockHit.hitVec.yCoord - startY) * (blockHit.hitVec.yCoord - startY) + + (blockHit.hitVec.zCoord - startZ) * (blockHit.hitVec.zCoord - startZ) ); endX = blockHit.hitVec.xCoord; endY = blockHit.hitVec.yCoord; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 61cbe7a0b..82447edd2 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -1,22 +1,28 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; -import noppes.npcs.EventHooks; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; +import noppes.npcs.EventHooks; import java.util.List; /** * Orb projectile - spherical homing energy ball. * Extends EntityEnergyAbility for shared functionality. - * + *

* Design inspired by LouisXIV's energy attack system. */ public class EntityAbilityOrb extends EntityEnergyProjectile { diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index f8b0aaac1..f215ca6d6 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -23,7 +23,7 @@ import java.util.Set; * Unlike other projectiles, this handles its own damage logic since it's * attached to and rotates around the caster. * Extends EntityEnergyAbility for shared visual/owner/charging state. - * + *

* Design: Similar to Beam trail visuals but rotating around origin. */ public class EntityAbilitySweeper extends EntityEnergyAbility { @@ -65,11 +65,11 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { } public EntityAbilitySweeper(World world, EntityLivingBase owner, EntityLivingBase target, - float beamLength, float beamWidth, float beamHeight, - EnergyDisplayData displayData, - float sweepSpeed, int numberOfRotations, - float damage, int damageInterval, boolean piercing, - boolean lockOnTarget) { + float beamLength, float beamWidth, float beamHeight, + EnergyDisplayData displayData, + float sweepSpeed, int numberOfRotations, + float damage, int damageInterval, boolean piercing, + boolean lockOnTarget) { this(world); this.ownerEntityId = owner.getEntityId(); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index 11eaade79..ac42b3b1d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -34,16 +34,20 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD // ENUMS // ═══════════════════════════════════════════════════════════════════ - public enum ZoneType { TRAP, HAZARD } + public enum ZoneType {TRAP, HAZARD} + public enum ZoneShape { CIRCLE, SQUARE; @Override public String toString() { switch (this) { - case CIRCLE: return "ability.shape.circle"; - case SQUARE: return "ability.shape.square"; - default: return name(); + case CIRCLE: + return "ability.shape.circle"; + case SQUARE: + return "ability.shape.square"; + default: + return name(); } } } @@ -54,10 +58,14 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD @Override public String toString() { switch (this) { - case STATIC: return "ability.accentStyle.static"; - case SWAYING: return "ability.accentStyle.swaying"; - case FLICKERING: return "ability.accentStyle.flickering"; - default: return name(); + case STATIC: + return "ability.accentStyle.static"; + case SWAYING: + return "ability.accentStyle.swaying"; + case FLICKERING: + return "ability.accentStyle.flickering"; + default: + return name(); } } } @@ -68,10 +76,14 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD @Override public String toString() { switch (this) { - case RISING: return "ability.particleMotion.rising"; - case DRIFTING: return "ability.particleMotion.drifting"; - case SPARKS: return "ability.particleMotion.sparks"; - default: return name(); + case RISING: + return "ability.particleMotion.rising"; + case DRIFTING: + return "ability.particleMotion.drifting"; + case SPARKS: + return "ability.particleMotion.sparks"; + default: + return name(); } } } @@ -174,7 +186,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD } private EntityAbilityZone(World world, ZoneType type, EntityLivingBase owner, - double x, double y, double z) { + double x, double y, double z) { this(world); this.zoneType = type; this.ownerEntityId = owner.getEntityId(); @@ -184,17 +196,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD } public static EntityAbilityZone createTrap(World world, EntityLivingBase owner, - double x, double y, double z, - ZoneShape shape, - float triggerRadius, int armTime, int maxTriggers, - int triggerCooldown, float damage, float damageRadius, - float knockback, int durationTicks, - int innerColor, int outerColor, boolean outerColorEnabled, - float zoneHeight, - float particleDensity, float particleScale, - float animSpeed, float lightningDensity, - boolean visible, - List effects) { + double x, double y, double z, + ZoneShape shape, + float triggerRadius, int armTime, int maxTriggers, + int triggerCooldown, float damage, float damageRadius, + float knockback, int durationTicks, + int innerColor, int outerColor, boolean outerColorEnabled, + float zoneHeight, + float particleDensity, float particleScale, + float animSpeed, float lightningDensity, + boolean visible, + List effects) { EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.TRAP, owner, x, y, z); zone.shape = shape; zone.radius = triggerRadius; @@ -226,17 +238,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD } public static EntityAbilityZone createHazard(World world, EntityLivingBase owner, - double x, double y, double z, - ZoneShape shape, - float radius, - float damagePerSecond, int damageInterval, - boolean ignoreInvulnFrames, boolean affectsCaster, - int durationTicks, - int innerColor, int outerColor, boolean outerColorEnabled, - float zoneHeight, - float particleDensity, float particleScale, - float animSpeed, float lightningDensity, - List effects) { + double x, double y, double z, + ZoneShape shape, + float radius, + float damagePerSecond, int damageInterval, + boolean ignoreInvulnFrames, boolean affectsCaster, + int durationTicks, + int innerColor, int outerColor, boolean outerColorEnabled, + float zoneHeight, + float particleDensity, float particleScale, + float animSpeed, float lightningDensity, + List effects) { EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.HAZARD, owner, x, y, z); zone.shape = shape; zone.radius = radius; @@ -268,7 +280,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD // ═══════════════════════════════════════════════════════════════════ @Override - protected void entityInit() {} + protected void entityInit() { + } @Override public void handleHealthUpdate(byte id) { @@ -549,45 +562,133 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD // GETTERS FOR RENDERER // ═══════════════════════════════════════════════════════════════════ - public ZoneType getZoneType() { return zoneType; } - public ZoneShape getShape() { return shape; } - public float getRadius() { return radius; } - public int getInnerColor() { return innerColor; } - public int getOuterColor() { return outerColor; } - public boolean isOuterColorEnabled() { return outerColorEnabled; } - public float getZoneHeight() { return zoneHeight; } - public boolean isArmed() { return armed || ticksExisted >= armTime; } - public boolean isVisible() { return visible; } - public int getTriggerFlashTick() { return triggerFlashTick; } - public int getDurationTicks() { return durationTicks; } - public int getMaxTicks() { return maxTicks; } - public float getParticleDensity() { return particleDensity; } - public float getParticleScale() { return particleScale; } - public float getAnimSpeed() { return animSpeed; } - public float getLightningDensity() { return lightningDensity; } + public ZoneType getZoneType() { + return zoneType; + } - public boolean isGroundFill() { return groundFill; } - public float getGroundAlpha() { return groundAlpha; } - public boolean isRings() { return rings; } - public int getRingCount() { return ringCount; } - public boolean isBorder() { return border; } - public float getBorderSpeed() { return borderSpeed; } - public boolean isAccents() { return accents; } - public int getAccentStyle() { return accentStyle; } - public boolean isLightning() { return lightning; } - public boolean isParticles() { return particles; } - public int getParticleMotion() { return particleMotion; } - public String getParticleDir() { return particleDir; } - public int getParticleSize() { return particleSize; } - public boolean isParticleGlow() { return particleGlow; } + public ZoneShape getShape() { + return shape; + } + + public float getRadius() { + return radius; + } + + public int getInnerColor() { + return innerColor; + } + + public int getOuterColor() { + return outerColor; + } + + public boolean isOuterColorEnabled() { + return outerColorEnabled; + } + + public float getZoneHeight() { + return zoneHeight; + } + + public boolean isArmed() { + return armed || ticksExisted >= armTime; + } + + public boolean isVisible() { + return visible; + } + + public int getTriggerFlashTick() { + return triggerFlashTick; + } + + public int getDurationTicks() { + return durationTicks; + } + + public int getMaxTicks() { + return maxTicks; + } + + public float getParticleDensity() { + return particleDensity; + } + + public float getParticleScale() { + return particleScale; + } + + public float getAnimSpeed() { + return animSpeed; + } + + public float getLightningDensity() { + return lightningDensity; + } + + public boolean isGroundFill() { + return groundFill; + } + + public float getGroundAlpha() { + return groundAlpha; + } + + public boolean isRings() { + return rings; + } + + public int getRingCount() { + return ringCount; + } + + public boolean isBorder() { + return border; + } + + public float getBorderSpeed() { + return borderSpeed; + } + + public boolean isAccents() { + return accents; + } + + public int getAccentStyle() { + return accentStyle; + } + + public boolean isLightning() { + return lightning; + } + + public boolean isParticles() { + return particles; + } + + public int getParticleMotion() { + return particleMotion; + } + + public String getParticleDir() { + return particleDir; + } + + public int getParticleSize() { + return particleSize; + } + + public boolean isParticleGlow() { + return particleGlow; + } public void applyVisual(boolean groundFill, float groundAlpha, - boolean rings, int ringCount, - boolean border, float borderSpeed, - boolean accents, int accentStyle, - boolean lightning, - boolean particles, int particleMotion, - String particleDir, int particleSize, boolean particleGlow) { + boolean rings, int ringCount, + boolean border, float borderSpeed, + boolean accents, int accentStyle, + boolean lightning, + boolean particles, int particleMotion, + String particleDir, int particleSize, boolean particleGlow) { this.groundFill = groundFill; this.groundAlpha = groundAlpha; this.rings = rings; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index c98fae96a..9721fb60e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -30,7 +30,9 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit protected int ownerEntityId = -1; // ==================== STATE ==================== - /** The ability that spawned this entity. Transient, not saved to NBT. */ + /** + * The ability that spawned this entity. Transient, not saved to NBT. + */ protected transient Ability sourceAbility = null; protected boolean previewMode = false; protected EntityLivingBase previewOwner = null; @@ -98,59 +100,137 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit return worldObj.getEntityByID(ownerEntityId); } - public int getOwnerEntityId() { return ownerEntityId; } - public void setOwnerEntityId(int id) { this.ownerEntityId = id; } + public int getOwnerEntityId() { + return ownerEntityId; + } + + public void setOwnerEntityId(int id) { + this.ownerEntityId = id; + } // ==================== SOURCE ABILITY ==================== - public Ability getSourceAbility() { return sourceAbility; } - public void setSourceAbility(Ability ability) { this.sourceAbility = ability; } + public Ability getSourceAbility() { + return sourceAbility; + } + + public void setSourceAbility(Ability ability) { + this.sourceAbility = ability; + } // ==================== PREVIEW MODE ==================== - public boolean isPreviewMode() { return previewMode; } - public void setPreviewMode(boolean preview) { this.previewMode = preview; } - public void setPreviewOwner(EntityLivingBase owner) { this.previewOwner = owner; } + public boolean isPreviewMode() { + return previewMode; + } + + public void setPreviewMode(boolean preview) { + this.previewMode = preview; + } + + public void setPreviewOwner(EntityLivingBase owner) { + this.previewOwner = owner; + } // ==================== DISPLAY GETTERS & SETTERS ==================== - public EnergyDisplayData getDisplayData() { return displayData; } - public EnergyLightningData getLightningData() { return lightningData; } + public EnergyDisplayData getDisplayData() { + return displayData; + } - public int getInnerColor() { return displayData.getInnerColor(); } - public void setInnerColor(int color) { displayData.setInnerColor(color); } + public EnergyLightningData getLightningData() { + return lightningData; + } - public float getInnerAlpha() { return displayData.getInnerAlpha(); } - public void setInnerAlpha(float alpha) { displayData.setInnerAlpha(alpha); } + public int getInnerColor() { + return displayData.getInnerColor(); + } - public int getOuterColor() { return displayData.getOuterColor(); } - public void setOuterColor(int color) { displayData.setOuterColor(color); } + public void setInnerColor(int color) { + displayData.setInnerColor(color); + } - public boolean isOuterColorEnabled() { return displayData.isOuterColorEnabled(); } - public void setOuterColorEnabled(boolean enabled) { displayData.setOuterColorEnabled(enabled); } + public float getInnerAlpha() { + return displayData.getInnerAlpha(); + } - public float getOuterColorWidth() { return displayData.getOuterColorWidth(); } - public void setOuterColorWidth(float width) { displayData.setOuterColorWidth(width); } + public void setInnerAlpha(float alpha) { + displayData.setInnerAlpha(alpha); + } - public float getOuterColorAlpha() { return displayData.getOuterColorAlpha(); } - public void setOuterColorAlpha(float alpha) { displayData.setOuterColorAlpha(alpha); } + public int getOuterColor() { + return displayData.getOuterColor(); + } - public float getRotationSpeed() { return displayData.getRotationSpeed(); } - public void setRotationSpeed(float speed) { displayData.setRotationSpeed(speed); } + public void setOuterColor(int color) { + displayData.setOuterColor(color); + } + + public boolean isOuterColorEnabled() { + return displayData.isOuterColorEnabled(); + } + + public void setOuterColorEnabled(boolean enabled) { + displayData.setOuterColorEnabled(enabled); + } + + public float getOuterColorWidth() { + return displayData.getOuterColorWidth(); + } + + public void setOuterColorWidth(float width) { + displayData.setOuterColorWidth(width); + } + + public float getOuterColorAlpha() { + return displayData.getOuterColorAlpha(); + } + + public void setOuterColorAlpha(float alpha) { + displayData.setOuterColorAlpha(alpha); + } + + public float getRotationSpeed() { + return displayData.getRotationSpeed(); + } + + public void setRotationSpeed(float speed) { + displayData.setRotationSpeed(speed); + } // ==================== LIGHTNING GETTERS & SETTERS ==================== - public boolean hasLightningEffect() { return lightningData.isLightningEffect(); } - public void setLightningEffect(boolean enabled) { lightningData.setLightningEffect(enabled); } + public boolean hasLightningEffect() { + return lightningData.isLightningEffect(); + } - public float getLightningDensity() { return lightningData.getLightningDensity(); } - public void setLightningDensity(float density) { lightningData.setLightningDensity(density); } + public void setLightningEffect(boolean enabled) { + lightningData.setLightningEffect(enabled); + } - public float getLightningRadius() { return lightningData.getLightningRadius(); } - public void setLightningRadius(float radius) { lightningData.setLightningRadius(radius); } + public float getLightningDensity() { + return lightningData.getLightningDensity(); + } - public int getLightningFadeTime() { return lightningData.getLightningFadeTime(); } - public void setLightningFadeTime(int ticks) { lightningData.setLightningFadeTime(ticks); } + public void setLightningDensity(float density) { + lightningData.setLightningDensity(density); + } + + public float getLightningRadius() { + return lightningData.getLightningRadius(); + } + + public void setLightningRadius(float radius) { + lightningData.setLightningRadius(radius); + } + + public int getLightningFadeTime() { + return lightningData.getLightningFadeTime(); + } + + public void setLightningFadeTime(int ticks) { + lightningData.setLightningFadeTime(ticks); + } // ==================== BRIGHTNESS ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java index c295f586d..7ff4b04c2 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java @@ -1,10 +1,6 @@ package kamkeel.npcs.entity; -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -144,6 +140,7 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { * Shared barrier tick logic for onUpdate(). Handles spawned event, owner death, * duration check, hit flash decay, and tick event. * Call this after incrementing ticksAlive in subclass onUpdate(). + * * @return true if barrier died this tick (caller should return early) */ protected boolean updateBarrierTick() { @@ -223,18 +220,33 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { protected boolean isKnockbackTarget(EntityLivingBase entity) { switch (barrierData.knockbackTarget) { - case 1: return entity instanceof EntityPlayer; - case 2: return entity instanceof EntityNPCInterface; - default: return entity instanceof EntityPlayer || entity instanceof EntityNPCInterface; + case 1: + return entity instanceof EntityPlayer; + case 2: + return entity instanceof EntityNPCInterface; + default: + return entity instanceof EntityPlayer || entity instanceof EntityNPCInterface; } } // ==================== GETTERS ==================== - public EnergyBarrierData getBarrierData() { return barrierData; } - public float getCurrentHealth() { return currentHealth; } - public void setCurrentHealth(float health) { this.currentHealth = health; syncHealthPercent(); } - public int getTicksAlive() { return ticksAlive; } + public EnergyBarrierData getBarrierData() { + return barrierData; + } + + public float getCurrentHealth() { + return currentHealth; + } + + public void setCurrentHealth(float health) { + this.currentHealth = health; + syncHealthPercent(); + } + + public int getTicksAlive() { + return ticksAlive; + } // ==================== NBT HELPERS ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index d0a2a8a96..6ba436914 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -195,10 +195,21 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== GETTERS ==================== - public float getDomeRadius() { return domeRadius; } - public void setDomeRadius(float radius) { this.domeRadius = Math.max(0.1f, radius); } - public boolean isFollowCaster() { return followCaster; } - public void setFollowCaster(boolean follow) { this.followCaster = follow; } + public float getDomeRadius() { + return domeRadius; + } + + public void setDomeRadius(float radius) { + this.domeRadius = Math.max(0.1f, radius); + } + + public boolean isFollowCaster() { + return followCaster; + } + + public void setFollowCaster(boolean follow) { + this.followCaster = follow; + } // ==================== NBT ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 909e0dda8..98145836e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -343,9 +343,17 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { // ==================== GETTERS ==================== - public PanelMode getMode() { return mode; } - public float getPanelYaw() { return panelYaw; } - public EnergyPanelData getPanelData() { return panelData; } + public PanelMode getMode() { + return mode; + } + + public float getPanelYaw() { + return panelYaw; + } + + public EnergyPanelData getPanelData() { + return panelData; + } // ==================== NBT ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index b4e63bace..b65439f83 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -5,7 +5,13 @@ import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -37,7 +43,7 @@ import java.util.Map; * Base class for all ability projectiles (Orb, Disc, Beam, Laser, Slicer). * Provides common functionality for combat, effects, homing, and interpolation. * Extends EntityEnergyAbility for shared visual/owner/charging state. - * + *

* Design inspired by LouisXIV's energy attack system. */ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @@ -274,13 +280,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { /** * Check if projectile has exceeded max distance. + * * @return true if should die */ protected boolean checkMaxDistance() { double distTraveled = Math.sqrt( (posX - startX) * (posX - startX) + - (posY - startY) * (posY - startY) + - (posZ - startZ) * (posZ - startZ) + (posY - startY) * (posY - startY) + + (posZ - startZ) * (posZ - startZ) ); return distTraveled >= getMaxDistance(); } @@ -295,6 +302,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { /** * Check for collision with energy barrier entities. * Barriers only block incoming projectiles from enemies. + * * @return true if projectile was absorbed (caller should stop processing) */ protected boolean checkBarrierCollision() { @@ -455,8 +463,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { double dist = Math.sqrt( Math.pow(target.posX - posX, 2) + - Math.pow(target.posY - posY, 2) + - Math.pow(target.posZ - posZ, 2) + Math.pow(target.posY - posY, 2) + + Math.pow(target.posZ - posZ, 2) ); float radius = getExplosionRadius(); @@ -518,7 +526,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * Update during charging state - grow size and follow anchor. * Default implementation grows {@code size} from 0 to {@code targetSize}. * Override in subclasses for type-specific charging (Disc, Beam). - * + *

* Note: Owner death checks are handled by onUpdate() before this is called. */ protected void updateCharging() { @@ -580,7 +588,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * Call from subclass constructors after initProjectile(). */ protected void calculateInitialVelocity(EntityLivingBase owner, EntityLivingBase target, - double x, double y, double z) { + double x, double y, double z) { if (target != null) { double dx = target.posX - x; double dy = (target.posY + target.getEyeHeight()) - y; @@ -701,7 +709,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== VISUAL GETTERS & SETTERS ==================== - public float getSize() { return size; } + public float getSize() { + return size; + } + public void setProjectileSize(float size) { this.size = size; this.renderCurrentSize = size; @@ -738,64 +749,147 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== LIFESPAN GETTERS & SETTERS ==================== - public float getMaxDistance() { return lifespanData.getMaxDistance(); } - public void setMaxDistance(float distance) { lifespanData.setMaxDistance(distance); } + public float getMaxDistance() { + return lifespanData.getMaxDistance(); + } - public int getMaxLifetime() { return lifespanData.getMaxLifetime(); } - public void setMaxLifetime(int ticks) { lifespanData.setMaxLifetime(ticks); } + public void setMaxDistance(float distance) { + lifespanData.setMaxDistance(distance); + } + + public int getMaxLifetime() { + return lifespanData.getMaxLifetime(); + } + + public void setMaxLifetime(int ticks) { + lifespanData.setMaxLifetime(ticks); + } // ==================== COMBAT GETTERS & SETTERS ==================== - public float getDamage() { return combatData.getDamage(); } - public void setCombatDamage(float damage) { combatData.setDamage(damage); } + public float getDamage() { + return combatData.getDamage(); + } - public float getKnockback() { return combatData.knockback; } - public void setCombatKnockback(float knockback) { combatData.setKnockback(knockback); } + public void setCombatDamage(float damage) { + combatData.setDamage(damage); + } - public float getKnockbackUp() { return combatData.knockbackUp; } - public void setCombatKnockbackUp(float knockbackUp) { combatData.setKnockbackUp(knockbackUp); } + public float getKnockback() { + return combatData.knockback; + } - public boolean isExplosive() { return combatData.isExplosive(); } - public void setExplosive(boolean explosive) { combatData.setExplosive(explosive); } + public void setCombatKnockback(float knockback) { + combatData.setKnockback(knockback); + } - public float getExplosionRadius() { return combatData.explosionRadius; } - public void setExplosionRadius(float radius) { combatData.setExplosionRadius(radius); } + public float getKnockbackUp() { + return combatData.knockbackUp; + } - public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; } - public void setExplosionDamageFalloff(float falloff) { combatData.setExplosionDamageFalloff(falloff); } + public void setCombatKnockbackUp(float knockbackUp) { + combatData.setKnockbackUp(knockbackUp); + } + + public boolean isExplosive() { + return combatData.isExplosive(); + } + + public void setExplosive(boolean explosive) { + combatData.setExplosive(explosive); + } + + public float getExplosionRadius() { + return combatData.explosionRadius; + } + + public void setExplosionRadius(float radius) { + combatData.setExplosionRadius(radius); + } + + public float getExplosionDamageFalloff() { + return combatData.explosionDamageFalloff; + } + + public void setExplosionDamageFalloff(float falloff) { + combatData.setExplosionDamageFalloff(falloff); + } // ==================== HOMING GETTERS & SETTERS ==================== - public boolean isHoming() { return homingData.isHoming(); } - public void setHomingEnabled(boolean homing) { homingData.setHoming(homing); } + public boolean isHoming() { + return homingData.isHoming(); + } - public float getHomingStrength() { return homingData.getHomingStrength(); } - public void setHomingStrength(float strength) { homingData.setHomingStrength(strength); } + public void setHomingEnabled(boolean homing) { + homingData.setHoming(homing); + } - public float getHomingRange() { return homingData.getHomingRange(); } - public void setHomingRange(float range) { homingData.setHomingRange(range); } + public float getHomingStrength() { + return homingData.getHomingStrength(); + } + + public void setHomingStrength(float strength) { + homingData.setHomingStrength(strength); + } + + public float getHomingRange() { + return homingData.getHomingRange(); + } + + public void setHomingRange(float range) { + homingData.setHomingRange(range); + } // ==================== SPEED & ANCHOR GETTERS & SETTERS ==================== - public float getSpeed() { return homingData.getSpeed(); } - public void setSpeed(float speed) { homingData.setSpeed(speed); } + public float getSpeed() { + return homingData.getSpeed(); + } - public AnchorPoint getAnchorPoint() { return anchorData.getAnchorPoint(); } + public void setSpeed(float speed) { + homingData.setSpeed(speed); + } - public int getAnchor() { return anchorData.getAnchor(); } - public float getAnchorOffsetX() { return anchorData.getAnchorOffsetX(); } - public float getAnchorOffsetY() { return anchorData.getAnchorOffsetY(); } - public float getAnchorOffsetZ() { return anchorData.getAnchorOffsetZ(); } + public AnchorPoint getAnchorPoint() { + return anchorData.getAnchorPoint(); + } + + public int getAnchor() { + return anchorData.getAnchor(); + } + + public float getAnchorOffsetX() { + return anchorData.getAnchorOffsetX(); + } + + public float getAnchorOffsetY() { + return anchorData.getAnchorOffsetY(); + } + + public float getAnchorOffsetZ() { + return anchorData.getAnchorOffsetZ(); + } // ==================== MOVEMENT GETTERS ==================== - public double getStartX() { return startX; } - public double getStartY() { return startY; } - public double getStartZ() { return startZ; } + public double getStartX() { + return startX; + } + + public double getStartY() { + return startY; + } + + public double getStartZ() { + return startZ; + } // ==================== STATE GETTERS ==================== - public boolean getHasHit() { return hasHit; } + public boolean getHasHit() { + return hasHit; + } // ==================== NBT ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index c76fc7bad..34fc986e6 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -1,6 +1,12 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -293,14 +299,29 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { // ==================== GETTERS ==================== - public float getSliceWidth() { return sliceWidth; } - public void setSliceWidth(float width) { this.sliceWidth = width; } + public float getSliceWidth() { + return sliceWidth; + } - public float getSliceThickness() { return sliceThickness; } - public void setSliceThickness(float thickness) { this.sliceThickness = thickness; } + public void setSliceWidth(float width) { + this.sliceWidth = width; + } - public boolean isPiercing() { return piercing; } - public void setPiercing(boolean piercing) { this.piercing = piercing; } + public float getSliceThickness() { + return sliceThickness; + } + + public void setSliceThickness(float thickness) { + this.sliceThickness = thickness; + } + + public boolean isPiercing() { + return piercing; + } + + public void setPiercing(boolean piercing) { + this.piercing = piercing; + } // ==================== NBT ==================== diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index eca69684d..c3c555d48 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -28,8 +28,6 @@ import kamkeel.npcs.network.packets.data.UpdateAnimationsPacket; import kamkeel.npcs.network.packets.data.VillagerListPacket; import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket; import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; -import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; -import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import kamkeel.npcs.network.packets.data.gui.GuiClosePacket; import kamkeel.npcs.network.packets.data.gui.GuiErrorPacket; import kamkeel.npcs.network.packets.data.gui.GuiOpenBookPacket; @@ -53,6 +51,9 @@ import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket; import kamkeel.npcs.network.packets.data.script.ScriptOverlayClosePacket; import kamkeel.npcs.network.packets.data.script.ScriptOverlayDataPacket; import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket; +import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; +import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; +import kamkeel.npcs.network.packets.player.AuctionActionPacket; import kamkeel.npcs.network.packets.player.BankActionPacket; import kamkeel.npcs.network.packets.player.CheckPlayerValue; import kamkeel.npcs.network.packets.player.CompanionActionPacket; @@ -61,7 +62,6 @@ import kamkeel.npcs.network.packets.player.FollowerPacket; import kamkeel.npcs.network.packets.player.GetNPCRole; import kamkeel.npcs.network.packets.player.GetTraderData; import kamkeel.npcs.network.packets.player.InputDevicePacket; -import kamkeel.npcs.network.packets.player.AuctionActionPacket; import kamkeel.npcs.network.packets.player.MailActionPacket; import kamkeel.npcs.network.packets.player.SaveBookPacket; import kamkeel.npcs.network.packets.player.SaveSignPacket; @@ -94,14 +94,14 @@ import kamkeel.npcs.network.packets.request.TraderMarketSavePacket; import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket; import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket; import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcSavePacket; -import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; -import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket; -import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket; import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket; import kamkeel.npcs.network.packets.request.ability.ChainedAbilitiesGetPacket; import kamkeel.npcs.network.packets.request.ability.ChainedAbilityGetPacket; import kamkeel.npcs.network.packets.request.ability.ChainedAbilityRemovePacket; import kamkeel.npcs.network.packets.request.ability.ChainedAbilitySavePacket; +import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; +import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket; +import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket; import kamkeel.npcs.network.packets.request.animation.AnimationGetPacket; import kamkeel.npcs.network.packets.request.animation.AnimationRemovePacket; @@ -116,8 +116,8 @@ import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsShortPacket; import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket; import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; -import kamkeel.npcs.network.packets.request.clone.CloneMovePacket; import kamkeel.npcs.network.packets.request.clone.CloneListPacket; +import kamkeel.npcs.network.packets.request.clone.CloneMovePacket; import kamkeel.npcs.network.packets.request.clone.ClonePreSavePacket; import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket; import kamkeel.npcs.network.packets.request.clone.CloneSavePacket; diff --git a/src/main/java/kamkeel/npcs/network/packets/data/LoginPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/LoginPacket.java index 75e0c40ae..da9eb9aa2 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/LoginPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/LoginPacket.java @@ -15,8 +15,8 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.client.ClientCacheHandler; -import noppes.npcs.config.ConfigMain; import noppes.npcs.client.ScriptClientConfig; +import noppes.npcs.config.ConfigMain; import noppes.npcs.controllers.AuctionConfigSync; import noppes.npcs.controllers.ScriptConfigSync; diff --git a/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java b/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java index 03c0b82b4..6de87cb22 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java @@ -16,7 +16,7 @@ import java.io.IOException; /** * Lightweight packet syncing ability lock state from server to client. * Sent every tick during ability execution when flags change, and once when ability ends. - * + *

* Flags byte layout: * bit 0: movement locked (suppress WASD, zero motion) * bit 1: rotation locked (freeze yaw/pitch) diff --git a/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java b/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java index b15ff8992..5eed2aa93 100644 --- a/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/player/AuctionActionPacket.java @@ -40,7 +40,8 @@ public class AuctionActionPacket extends AbstractPacket { private long amount; private NBTTagCompound data; - public AuctionActionPacket() {} + public AuctionActionPacket() { + } private AuctionActionPacket(Action action) { this.action = action; @@ -58,7 +59,9 @@ public class AuctionActionPacket extends AbstractPacket { } @Override - public boolean needsNPC() { return false; } // Allow opening via command without NPC + public boolean needsNPC() { + return false; + } // Allow opening via command without NPC // ========================================= // Client-side static methods @@ -117,7 +120,9 @@ public class AuctionActionPacket extends AbstractPacket { PacketClient.sendClient(packet); } - /** Request listings from server with filter and page */ + /** + * Request listings from server with filter and page + */ @SideOnly(Side.CLIENT) public static void requestListings(AuctionFilter filter, int page) { AuctionActionPacket packet = new AuctionActionPacket(Action.RequestListings); @@ -131,10 +136,14 @@ public class AuctionActionPacket extends AbstractPacket { // ========================================= @Override - public Enum getType() { return EnumPlayerPacket.AuctionAction; } + public Enum getType() { + return EnumPlayerPacket.AuctionAction; + } @Override - public PacketChannel getChannel() { return PacketHandler.PLAYER_PACKET; } + public PacketChannel getChannel() { + return PacketHandler.PLAYER_PACKET; + } @Override @SideOnly(Side.CLIENT) @@ -291,7 +300,10 @@ public class AuctionActionPacket extends AbstractPacket { long startingPrice = in.readLong(); String buyoutStr = ByteBufUtils.readString(in); long buyoutPrice = 0; - try { buyoutPrice = Long.parseLong(buyoutStr); } catch (NumberFormatException ignored) {} + try { + buyoutPrice = Long.parseLong(buyoutStr); + } catch (NumberFormatException ignored) { + } NBTTagCompound itemNBT = ByteBufUtils.readNBT(in); if (itemNBT == null) return "No item data received."; @@ -329,7 +341,9 @@ public class AuctionActionPacket extends AbstractPacket { } } - /** Send full trades data to player (listings, bids, claims) */ + /** + * Send full trades data to player (listings, bids, claims) + */ private void sendTradesData(EntityPlayerMP player, AuctionController controller) { NBTTagCompound response = new NBTTagCompound(); response.setBoolean("TradesData", true); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java index 14f22d983..67a8f8241 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/BuiltInAbilityGetPacket.java @@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java index 9772bc4ad..da8ca9d9f 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java @@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java index aaaf8d6a9..ca9925bbf 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java @@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java index e53d53d49..b4588935b 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilityGetPacket.java @@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java index b1662e246..f360b0eff 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/CustomAbilitySavePacket.java @@ -3,8 +3,8 @@ package kamkeel.npcs.network.packets.request.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/animation/BuiltInAnimationGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/animation/BuiltInAnimationGetPacket.java index e214689d8..d25935892 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/animation/BuiltInAnimationGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/animation/BuiltInAnimationGetPacket.java @@ -15,7 +15,6 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.controllers.AnimationController; -import noppes.npcs.controllers.data.Animation; import noppes.npcs.controllers.data.BuiltInAnimation; import java.io.IOException; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java index 926c5519c..158896eaf 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java @@ -10,10 +10,10 @@ import kamkeel.npcs.network.PacketUtil; import kamkeel.npcs.network.enums.EnumItemPacketType; import kamkeel.npcs.network.enums.EnumRequestPacket; import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import kamkeel.npcs.util.ByteBufUtils; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; -import kamkeel.npcs.util.ByteBufUtils; import noppes.npcs.controllers.ServerTagMapController; import noppes.npcs.controllers.data.TagMap; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/ScriptFilesPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/ScriptFilesPacket.java index d025fcb26..8930fabc3 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/ScriptFilesPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/ScriptFilesPacket.java @@ -1,7 +1,5 @@ package kamkeel.npcs.network.packets.request.script; -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; diff --git a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java index 3c9b10f1d..6a1a17c76 100644 --- a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java +++ b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java @@ -411,6 +411,7 @@ public class AnchorPointHelper { /** * Rotate an offset vector by the entity's body yaw so offsets are entity-relative. * +X = entity's right, +Y = up, +Z = entity's forward. + * * @param bodyYawRad body yaw in radians */ private static Vec3 rotateOffsetByYaw(double offsetX, double offsetY, double offsetZ, float bodyYawRad) { @@ -444,19 +445,19 @@ public class AnchorPointHelper { * Apply FULL_MODEL rotation to a world-space offset from entity position. * Converts the offset to model space, applies FULL_MODEL rotation and pivots, * then converts back to world space. - * + *

* The rendering pipeline is: entity_pos * body_yaw * scale(-1,-1,1) * FULL_MODEL * parts * Combined body_yaw + scale maps model space to world space: - * worldX = mx*cos(yaw) + mz*sin(yaw) - * worldY = -my (model Y-down → world Y-up) - * worldZ = mx*sin(yaw) - mz*cos(yaw) + * worldX = mx*cos(yaw) + mz*sin(yaw) + * worldY = -my (model Y-down → world Y-up) + * worldZ = mx*sin(yaw) - mz*cos(yaw) * This transform is its own inverse for X,Z. - * + *

* FULL_MODEL rotation order matches renderer: vertex sees Z, then Y, then X. */ private static Vec3 applyFullModelToWorldOffset(FramePart fullModel, EntityLivingBase entity, - double worldDX, double worldDY, double worldDZ, - float bodyYaw, float scale) { + double worldDX, double worldDY, double worldDZ, + float bodyYaw, float scale) { float[] rotations = getRotations(fullModel, entity); float[] pivots = getPivots(fullModel, entity); diff --git a/src/main/java/kamkeel/npcs/util/BukkitUtil.java b/src/main/java/kamkeel/npcs/util/BukkitUtil.java index 29f4a522f..1252fc67c 100644 --- a/src/main/java/kamkeel/npcs/util/BukkitUtil.java +++ b/src/main/java/kamkeel/npcs/util/BukkitUtil.java @@ -98,6 +98,7 @@ public class BukkitUtil { /** * Checks if a specific Bukkit plugin is enabled. + * * @param pluginName the plugin name to check * @return true if the plugin is enabled */ @@ -115,6 +116,7 @@ public class BukkitUtil { /** * Gets a Bukkit OfflinePlayer by name. + * * @param playerName the player name * @return the OfflinePlayer object, or null if not available */ @@ -131,6 +133,7 @@ public class BukkitUtil { /** * Gets a Bukkit Player by name (online players only). + * * @param playerName the player name * @return the Player object, or null if not online or not available */ @@ -147,6 +150,7 @@ public class BukkitUtil { /** * Gets a service provider from Bukkit's ServicesManager. + * * @param serviceClass the service class to get * @return the service provider, or null if not available */ diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java index 4a0d2bb78..5c28d5e1d 100644 --- a/src/main/java/kamkeel/npcs/util/Register.java +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -1,13 +1,19 @@ package kamkeel.npcs.util; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import noppes.npcs.LogWriter; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; diff --git a/src/main/java/kamkeel/npcs/util/VaultUtil.java b/src/main/java/kamkeel/npcs/util/VaultUtil.java index c25ea3f3a..47e294e57 100644 --- a/src/main/java/kamkeel/npcs/util/VaultUtil.java +++ b/src/main/java/kamkeel/npcs/util/VaultUtil.java @@ -178,6 +178,7 @@ public class VaultUtil { /** * Checks if a player has an account in the economy system. + * * @param player the player to check * @return true if the player has an account */ @@ -187,6 +188,7 @@ public class VaultUtil { /** * Checks if a player has an account in the economy system. + * * @param playerName the player name to check * @return true if the player has an account */ @@ -207,6 +209,7 @@ public class VaultUtil { /** * Gets the balance of a player. + * * @param player the player * @return the player's balance, or 0 if not available */ @@ -216,6 +219,7 @@ public class VaultUtil { /** * Gets the balance of a player by name. + * * @param playerName the player name * @return the player's balance, or 0 if not available */ @@ -236,6 +240,7 @@ public class VaultUtil { /** * Checks if a player has at least the specified amount. + * * @param player the player * @param amount the amount to check * @return true if the player has at least the specified amount @@ -246,8 +251,9 @@ public class VaultUtil { /** * Checks if a player has at least the specified amount. + * * @param playerName the player name - * @param amount the amount to check + * @param amount the amount to check * @return true if the player has at least the specified amount */ public static boolean has(String playerName, double amount) { @@ -267,6 +273,7 @@ public class VaultUtil { /** * Withdraws money from a player's account. + * * @param player the player * @param amount the amount to withdraw * @return true if the transaction was successful @@ -277,8 +284,9 @@ public class VaultUtil { /** * Withdraws money from a player's account. + * * @param playerName the player name - * @param amount the amount to withdraw + * @param amount the amount to withdraw * @return true if the transaction was successful */ public static boolean withdrawMoney(String playerName, double amount) { @@ -301,6 +309,7 @@ public class VaultUtil { /** * Adds money to a player's account (deposit). + * * @param player the player * @param amount the amount to add * @return true if the transaction was successful @@ -311,8 +320,9 @@ public class VaultUtil { /** * Adds money to a player's account (deposit). + * * @param playerName the player name - * @param amount the amount to add + * @param amount the amount to add * @return true if the transaction was successful */ public static boolean addMoney(String playerName, double amount) { @@ -336,6 +346,7 @@ public class VaultUtil { /** * Sets a player's balance to the specified amount. * This withdraws all money first, then deposits the new amount. + * * @param player the player * @param amount the new balance * @return true if the transaction was successful @@ -347,8 +358,9 @@ public class VaultUtil { /** * Sets a player's balance to the specified amount. * This withdraws all money first, then deposits the new amount. + * * @param playerName the player name - * @param amount the new balance + * @param amount the new balance * @return true if the transaction was successful */ public static boolean setBalance(String playerName, double amount) { @@ -368,6 +380,7 @@ public class VaultUtil { /** * Formats an amount according to the economy plugin's settings. + * * @param amount the amount to format * @return the formatted string, or the raw number as string if not available */ @@ -384,6 +397,7 @@ public class VaultUtil { /** * Gets the plural currency name (e.g., "Dollars"). + * * @return the plural currency name, or "coins" if not available */ public static String getCurrencyNamePlural() { @@ -399,6 +413,7 @@ public class VaultUtil { /** * Gets the singular currency name (e.g., "Dollar"). + * * @return the singular currency name, or "coin" if not available */ public static String getCurrencyNameSingular() { @@ -414,6 +429,7 @@ public class VaultUtil { /** * Creates an account for the player if one doesn't exist. + * * @param player the player * @return true if the account was created or already exists */ @@ -423,6 +439,7 @@ public class VaultUtil { /** * Creates an account for the player if one doesn't exist. + * * @param playerName the player name * @return true if the account was created or already exists */ @@ -459,6 +476,7 @@ public class VaultUtil { /** * Gets a list of all bank names. + * * @return list of bank names, or empty list if not available */ @SuppressWarnings("unchecked") @@ -475,6 +493,7 @@ public class VaultUtil { /** * Gets the balance of a bank. + * * @param bankName the bank name * @return the bank balance, or 0 if not available */ @@ -492,8 +511,9 @@ public class VaultUtil { /** * Checks if a bank has at least the specified amount. + * * @param bankName the bank name - * @param amount the amount to check + * @param amount the amount to check * @return true if the bank has at least the specified amount */ public static boolean bankHas(String bankName, double amount) { @@ -510,8 +530,9 @@ public class VaultUtil { /** * Withdraws money from a bank. + * * @param bankName the bank name - * @param amount the amount to withdraw + * @param amount the amount to withdraw * @return true if the transaction was successful */ public static boolean bankWithdraw(String bankName, double amount) { @@ -528,8 +549,9 @@ public class VaultUtil { /** * Deposits money into a bank. + * * @param bankName the bank name - * @param amount the amount to deposit + * @param amount the amount to deposit * @return true if the transaction was successful */ public static boolean bankDeposit(String bankName, double amount) { @@ -546,8 +568,9 @@ public class VaultUtil { /** * Transfers money from one player to another. - * @param from the player to take money from - * @param to the player to give money to + * + * @param from the player to take money from + * @param to the player to give money to * @param amount the amount to transfer * @return true if the transfer was successful */ @@ -557,9 +580,10 @@ public class VaultUtil { /** * Transfers money from one player to another. + * * @param fromPlayerName the player name to take money from - * @param toPlayerName the player name to give money to - * @param amount the amount to transfer + * @param toPlayerName the player name to give money to + * @param amount the amount to transfer * @return true if the transfer was successful */ public static boolean transfer(String fromPlayerName, String toPlayerName, double amount) { diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 341ed4e08..dea49be68 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -1,7 +1,7 @@ package noppes.npcs; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; @@ -12,7 +12,12 @@ import net.minecraft.util.DamageSource; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Shared base class for NPC and Player ability data management. @@ -31,81 +36,119 @@ public abstract class AbstractDataAbilities { // SHARED RUNTIME STATE (not saved to NBT) // ═══════════════════════════════════════════════════════════════════ - /** Currently executing ability (null if none) */ + /** + * Currently executing ability (null if none) + */ protected Ability currentAbility; - /** World time when cooldown ends */ + /** + * World time when cooldown ends + */ protected long cooldownEndTime = 0; - /** Rotation lock state */ + /** + * Rotation lock state + */ protected boolean rotationLocked = false; protected float lockedYaw = 0; protected float lockedPitch = 0; - /** Position lock state */ + /** + * Position lock state + */ protected boolean positionLocked = false; protected double lockedPosX = 0; protected double lockedPosY = 0; protected double lockedPosZ = 0; - /** Chained ability execution state */ + /** + * Chained ability execution state + */ protected ChainedAbility currentChain; protected int chainEntryIndex = -1; protected int chainDelayRemaining = -1; - /** Set when interrupt already rolled cooldown (prevents double cooldown on NPC dazed completion). */ + /** + * Set when interrupt already rolled cooldown (prevents double cooldown on NPC dazed completion). + */ protected boolean interruptCooldownRolled = false; // ═══════════════════════════════════════════════════════════════════ // ABSTRACT METHODS - Subclasses must implement // ═══════════════════════════════════════════════════════════════════ - /** Get the entity this ability data belongs to (NPC or Player). */ + /** + * Get the entity this ability data belongs to (NPC or Player). + */ protected abstract EntityLivingBase getEntity(); - /** Get the current target for ability execution. */ + /** + * Get the current target for ability execution. + */ protected abstract EntityLivingBase getTarget(); - /** Get the current world time. */ + /** + * Get the current world time. + */ protected abstract long getWorldTime(); // -- Event Firing -- - /** Fire tick event. Implementation differs between NPC (NpcAPI) and Player (ScriptController). */ + /** + * Fire tick event. Implementation differs between NPC (NpcAPI) and Player (ScriptController). + */ protected abstract void fireTickEvent(Ability ability, EntityLivingBase target); - /** Fire execute event. Returns true if cancelled. */ + /** + * Fire execute event. Returns true if cancelled. + */ protected abstract boolean fireExecuteEvent(Ability ability, EntityLivingBase target); - /** Fire complete event. */ + /** + * Fire complete event. + */ protected abstract void fireCompleteEvent(Ability ability, EntityLivingBase target); - /** Fire interrupt event. */ + /** + * Fire interrupt event. + */ protected abstract void fireInterruptEvent(Ability ability, EntityLivingBase target, DamageSource source, float damage); // -- Telegraph -- - /** Spawn telegraphs for an ability. */ + /** + * Spawn telegraphs for an ability. + */ protected abstract void spawnTelegraph(Ability ability, EntityLivingBase target); - /** Remove telegraphs for an ability. */ + /** + * Remove telegraphs for an ability. + */ protected abstract void removeTelegraph(Ability ability); // -- Animation/Sound Data Access -- - /** Set animation data on the entity. */ + /** + * Set animation data on the entity. + */ protected abstract void setAnimationData(Animation animation); - /** Clear animation data on the entity. */ + /** + * Clear animation data on the entity. + */ protected abstract void clearAnimationData(); - /** Play a sound at the entity's location. */ + /** + * Play a sound at the entity's location. + */ protected abstract void playAbilitySound(String sound); // -- Rotation (subclass handles extra fields and application) -- - /** Capture rotation values for locking. NPC captures 4 fields, Player captures 2. */ + /** + * Capture rotation values for locking. NPC captures 4 fields, Player captures 2. + */ protected abstract void captureLockedRotation(); // -- Completion -- @@ -116,7 +159,9 @@ public abstract class AbstractDataAbilities { */ protected abstract void rollCooldown(Ability ability); - /** Additional completion logic (NPC clears lastTarget, Player clears key/target and syncs). */ + /** + * Additional completion logic (NPC clears lastTarget, Player clears key/target and syncs). + */ protected abstract void onAbilityComplete(); /** @@ -128,17 +173,30 @@ public abstract class AbstractDataAbilities { // -- Hooks (optional overrides) -- - /** Called before onExecute in ACTIVE phase. NPC uses this for faceTarget/hitScan. */ - protected void onPreExecute(Ability ability, EntityLivingBase target) {} + /** + * Called before onExecute in ACTIVE phase. NPC uses this for faceTarget/hitScan. + */ + protected void onPreExecute(Ability ability, EntityLivingBase target) { + } - /** Called after the phase switch. NPC uses this for hit scan updates and movement control. */ - protected void onPostPhaseTick(Ability ability, EntityLivingBase target) {} + /** + * Called after the phase switch. NPC uses this for hit scan updates and movement control. + */ + protected void onPostPhaseTick(Ability ability, EntityLivingBase target) { + } - /** Additional lock releases in BURST_DELAY. NPC releases hitScan. */ - protected void onBurstDelayReleaseLocks() {} + /** + * Additional lock releases in BURST_DELAY. NPC releases hitScan. + */ + protected void onBurstDelayReleaseLocks() { + } - /** Hook for retargeting during chain execution when target dies. NPC overrides, Player returns null. */ - protected EntityLivingBase retargetForChain() { return null; } + /** + * Hook for retargeting during chain execution when target dies. NPC overrides, Player returns null. + */ + protected EntityLivingBase retargetForChain() { + return null; + } // ═══════════════════════════════════════════════════════════════════ // SHARED ANIMATION METHODS @@ -178,22 +236,30 @@ public abstract class AbstractDataAbilities { // STATE QUERIES // ═══════════════════════════════════════════════════════════════════ - /** Check if an ability is currently executing. */ + /** + * Check if an ability is currently executing. + */ public boolean isExecutingAbility() { return currentAbility != null && currentAbility.isExecuting(); } - /** Get the currently executing ability. */ + /** + * Get the currently executing ability. + */ public Ability getCurrentAbility() { return currentAbility; } - /** Check if a chained ability is currently executing. */ + /** + * Check if a chained ability is currently executing. + */ public boolean isExecutingChain() { return currentChain != null; } - /** Get the currently executing chained ability. */ + /** + * Get the currently executing chained ability. + */ public ChainedAbility getCurrentChain() { return currentChain; } @@ -202,11 +268,14 @@ public abstract class AbstractDataAbilities { // TOGGLE STATE (active toggles, independent of currentAbility) // ═══════════════════════════════════════════════════════════════════ - /** Currently active toggles. Key = ability key, Value = toggle entry with tick counter. */ + /** + * Currently active toggles. Key = ability key, Value = toggle entry with tick counter. + */ protected Map activeToggles = new LinkedHashMap<>(); /** * Toggle an ability ON or OFF. If currently on, turns off; if off, turns on. + * * @param key The ability key (e.g., "npcdbc:ki_fist") * @return true if the toggle is now ON, false if OFF */ @@ -221,8 +290,9 @@ public abstract class AbstractDataAbilities { /** * Set a toggle to a specific state. + * * @param key The ability key - * @param on true to activate, false to deactivate + * @param on true to activate, false to deactivate */ public void setAbilityToggled(String key, boolean on) { if (on && !activeToggles.containsKey(key)) { @@ -300,8 +370,11 @@ public abstract class AbstractDataAbilities { } } - /** Hook for subclass to react to toggle state changes (sync packets, script events). */ - protected void onToggleStateChanged(String key, boolean active) {} + /** + * Hook for subclass to react to toggle state changes (sync packets, script events). + */ + protected void onToggleStateChanged(String key, boolean active) { + } /** * Clear all active toggles (e.g., on death/reset). @@ -336,23 +409,31 @@ public abstract class AbstractDataAbilities { // COOLDOWN MANAGEMENT // ═══════════════════════════════════════════════════════════════════ - /** Check if on cooldown. */ + /** + * Check if on cooldown. + */ public boolean isOnCooldown() { return getWorldTime() < cooldownEndTime; } - /** Get remaining cooldown ticks. */ + /** + * Get remaining cooldown ticks. + */ public long getRemainingCooldown() { long remaining = cooldownEndTime - getWorldTime(); return remaining > 0 ? remaining : 0; } - /** Reset cooldown (allow immediate ability use). */ + /** + * Reset cooldown (allow immediate ability use). + */ public void resetCooldown() { cooldownEndTime = 0; } - /** Set the cooldown end time directly. */ + /** + * Set the cooldown end time directly. + */ public void setCooldownEndTime(long endTime) { cooldownEndTime = endTime; } @@ -381,8 +462,11 @@ public abstract class AbstractDataAbilities { onPositionLockChanged(false); } - /** Hook for subclass to react to position lock state changes (e.g., NPC sets data watcher flag). */ - protected void onPositionLockChanged(boolean locked) {} + /** + * Hook for subclass to react to position lock state changes (e.g., NPC sets data watcher flag). + */ + protected void onPositionLockChanged(boolean locked) { + } // ═══════════════════════════════════════════════════════════════════ // ROTATION LOCKING @@ -396,8 +480,11 @@ public abstract class AbstractDataAbilities { onRotationLockChanged(false); } - /** Hook for subclass to react to rotation lock state changes (e.g., NPC sets flag, clears hitScan). */ - protected void onRotationLockChanged(boolean locked) {} + /** + * Hook for subclass to react to rotation lock state changes (e.g., NPC sets flag, clears hitScan). + */ + protected void onRotationLockChanged(boolean locked) { + } // ═══════════════════════════════════════════════════════════════════ // CORE TICK - Phase transition logic shared between NPC and Player @@ -441,7 +528,7 @@ public abstract class AbstractDataAbilities { // Fire extender tick hook (e.g., per-tick resource drain) if (!AbilityController.Instance.fireOnAbilityTick(currentAbility, entity, target, - currentAbility.getPhase(), currentAbility.getCurrentTick())) { + currentAbility.getPhase(), currentAbility.getCurrentTick())) { interruptCurrentAbility(null, 0); return; } @@ -711,7 +798,8 @@ public abstract class AbstractDataAbilities { * Hook called after interrupt completes. Subclass handles clearing state and syncing. * NPC: keeps currentAbility (ticks through DAZED). Player: clears immediately. */ - protected void onInterruptComplete() {} + protected void onInterruptComplete() { + } // ═══════════════════════════════════════════════════════════════════ // CHAINED ABILITY EXECUTION diff --git a/src/main/java/noppes/npcs/CommonProxy.java b/src/main/java/noppes/npcs/CommonProxy.java index ee65ad6a5..e059e9104 100644 --- a/src/main/java/noppes/npcs/CommonProxy.java +++ b/src/main/java/noppes/npcs/CommonProxy.java @@ -13,13 +13,13 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.FakePlayer; import noppes.npcs.api.IWorld; import noppes.npcs.blocks.tiles.TileNpcContainer; +import noppes.npcs.config.ConfigScript; import noppes.npcs.constants.EnumGuiType; -import noppes.npcs.containers.ContainerAuction; +import noppes.npcs.containers.ContainerAnvilRepair; import noppes.npcs.containers.ContainerAuctionBidding; -import noppes.npcs.containers.ContainerAuctionTrades; import noppes.npcs.containers.ContainerAuctionListing; import noppes.npcs.containers.ContainerAuctionSell; -import noppes.npcs.containers.ContainerAnvilRepair; +import noppes.npcs.containers.ContainerAuctionTrades; import noppes.npcs.containers.ContainerCarpentryBench; import noppes.npcs.containers.ContainerCrate; import noppes.npcs.containers.ContainerCustomGui; @@ -43,7 +43,6 @@ import noppes.npcs.containers.ContainerNpcQuestReward; import noppes.npcs.containers.ContainerNpcQuestTypeItem; import noppes.npcs.controllers.data.AnimationData; import noppes.npcs.controllers.data.PlayerData; -import noppes.npcs.config.ConfigScript; import noppes.npcs.entity.EntityNPCInterface; public class CommonProxy implements IGuiHandler { diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index b063c790d..185131a15 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -21,13 +21,22 @@ import io.github.somehussar.janinoloader.api.IDynamicCompilerBuilder; import kamkeel.npcs.addon.AddonManager; import kamkeel.npcs.command.CommandKamkeel; import kamkeel.npcs.command.profile.CommandProfile; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.TelegraphController; -import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.profile.CNPCData; import kamkeel.npcs.developer.Developer; +import kamkeel.npcs.entity.EntityAbilityBeam; +import kamkeel.npcs.entity.EntityAbilityDisc; +import kamkeel.npcs.entity.EntityAbilityLaser; +import kamkeel.npcs.entity.EntityAbilityOrb; +import kamkeel.npcs.entity.EntityAbilitySweeper; +import kamkeel.npcs.entity.EntityAbilityZone; +import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityEnergySlicer; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.util.BukkitUtil; import net.minecraft.block.Block; @@ -86,15 +95,6 @@ import noppes.npcs.entity.EntityNpcDragon; import noppes.npcs.entity.EntityNpcPony; import noppes.npcs.entity.EntityNpcSlime; import noppes.npcs.entity.EntityProjectile; -import kamkeel.npcs.entity.EntityAbilityOrb; -import kamkeel.npcs.entity.EntityAbilityDisc; -import kamkeel.npcs.entity.EntityAbilityLaser; -import kamkeel.npcs.entity.EntityAbilityBeam; -import kamkeel.npcs.entity.EntityAbilitySweeper; -import kamkeel.npcs.entity.EntityAbilityZone; -import kamkeel.npcs.entity.EntityEnergyDome; -import kamkeel.npcs.entity.EntityEnergyPanel; -import kamkeel.npcs.entity.EntityEnergySlicer; import noppes.npcs.entity.old.EntityNPCDwarfFemale; import noppes.npcs.entity.old.EntityNPCDwarfMale; import noppes.npcs.entity.old.EntityNPCElfFemale; diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 29e4822b1..1caca740a 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -1,9 +1,9 @@ package noppes.npcs; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; @@ -86,10 +86,14 @@ public class DataAbilities extends AbstractDataAbilities { private transient boolean hitScanActive = false; private transient EntityLivingBase hitScanTarget = null; - /** Bit flag for rotation control (LOCKED or TRACK) in data watcher slot 15 */ + /** + * Bit flag for rotation control (LOCKED or TRACK) in data watcher slot 15 + */ private static final int ROTATION_CONTROLLED_FLAG = 16; - /** Bit flag for position lock in data watcher slot 15 */ + /** + * Bit flag for position lock in data watcher slot 15 + */ private static final int POSITION_LOCKED_FLAG = 32; // ═══════════════════════════════════════════════════════════════════ @@ -143,7 +147,7 @@ public class DataAbilities extends AbstractDataAbilities { @Override protected void fireInterruptEvent(Ability ability, EntityLivingBase target, - DamageSource source, float damage) { + DamageSource source, float damage) { AbilityEvent.InterruptEvent interruptEvent = new AbilityEvent.InterruptEvent( npc.wrappedNPC, ability, target, source, damage); NpcAPI.EVENT_BUS.post(interruptEvent); @@ -247,7 +251,7 @@ public class DataAbilities extends AbstractDataAbilities { // Update hit scan state - actual facing is deferred to applyRotationControl() // which runs AFTER super.onLivingUpdate() to override AI look helper if (ability != null && ability.isExecuting() - && ability.isHitScanForCurrentPhase() && target != null) { + && ability.isHitScanForCurrentPhase() && target != null) { enableHitScan(target); } else if (hitScanActive) { releaseRotationControl(); @@ -613,7 +617,7 @@ public class DataAbilities extends AbstractDataAbilities { * If an ability is currently executing, it will be cancelled. * * @param ability The ability to start - * @param target The target entity (can be null for self-targeted abilities) + * @param target The target entity (can be null for self-targeted abilities) * @return true if the ability was started successfully */ public boolean forceStartAbility(Ability ability, EntityLivingBase target) { @@ -640,7 +644,7 @@ public class DataAbilities extends AbstractDataAbilities { * Execute an ability on this NPC by key (built-in name or custom UUID). * The NPC does NOT need to have this ability assigned. * - * @param key The ability key (built-in name or custom UUID) + * @param key The ability key (built-in name or custom UUID) * @param target The target entity (can be null for self-targeted abilities) * @return true if the ability was started successfully */ @@ -729,7 +733,9 @@ public class DataAbilities extends AbstractDataAbilities { // ACTION SLOT MANAGEMENT // ═══════════════════════════════════════════════════════════════════ - /** Get the unified action slot list. */ + /** + * Get the unified action slot list. + */ public List getAbilityActions() { return actionSlots; } @@ -748,17 +754,23 @@ public class DataAbilities extends AbstractDataAbilities { return resolved; } - /** Add an inline ability. */ + /** + * Add an inline ability. + */ public void addAbility(Ability ability) { actionSlots.add(AbilityAction.inline(ability)); } - /** Add a reference ability by key (built-in name or custom ability name). */ + /** + * Add a reference ability by key (built-in name or custom ability name). + */ public void addAbilityReference(String key) { actionSlots.add(AbilityAction.abilityReference(key)); } - /** Add a chained ability reference by name. */ + /** + * Add a chained ability reference by name. + */ public void addChainReference(String name) { if (name != null && !name.isEmpty()) { for (AbilityAction slot : actionSlots) { @@ -770,7 +782,9 @@ public class DataAbilities extends AbstractDataAbilities { } } - /** Remove an action slot by index. */ + /** + * Remove an action slot by index. + */ public void removeAction(int index) { if (index >= 0 && index < actionSlots.size()) { actionSlots.remove(index); @@ -804,13 +818,17 @@ public class DataAbilities extends AbstractDataAbilities { return null; } - /** Check if a slot at a given index is a reference. */ + /** + * Check if a slot at a given index is a reference. + */ public boolean isSlotReference(int index) { if (index < 0 || index >= actionSlots.size()) return false; return actionSlots.get(index).isReference(); } - /** Convert a reference slot to inline. Returns false if resolution fails or is a chain. */ + /** + * Convert a reference slot to inline. Returns false if resolution fails or is a chain. + */ public boolean convertToInline(int index) { if (index < 0 || index >= actionSlots.size()) return false; return actionSlots.get(index).convertToInline(); @@ -934,7 +952,7 @@ public class DataAbilities extends AbstractDataAbilities { /** * Apply rotation control after super.onLivingUpdate() and super.onUpdate(). * Handles both LOCKED (freeze at captured values) and TRACK (face target) modes. - * + *

* Server: computes the correct rotation (locked values or target facing). * Client: trusts the server-synced rotation values and prevents body smoothing override. */ diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java index 2d2496fc6..2b9ebaabe 100644 --- a/src/main/java/noppes/npcs/EventHooks.java +++ b/src/main/java/noppes/npcs/EventHooks.java @@ -20,9 +20,9 @@ import net.minecraftforge.event.world.WorldEvent; import noppes.npcs.api.IBlock; import noppes.npcs.api.IPos; import noppes.npcs.api.IWorld; -import noppes.npcs.api.entity.IEnergyProjectile; import noppes.npcs.api.entity.IAnimatable; import noppes.npcs.api.entity.ICustomNpc; +import noppes.npcs.api.entity.IEnergyProjectile; import noppes.npcs.api.entity.IEntity; import noppes.npcs.api.entity.IPlayer; import noppes.npcs.api.entity.IProjectile; @@ -64,10 +64,10 @@ import noppes.npcs.scripted.event.NpcEvent; import noppes.npcs.scripted.event.PartyEvent; import noppes.npcs.scripted.event.ProjectileEvent; import noppes.npcs.scripted.event.RecipeScriptEvent; +import noppes.npcs.scripted.event.player.AuctionEvent; import noppes.npcs.scripted.event.player.CustomGuiEvent; import noppes.npcs.scripted.event.player.DialogEvent; import noppes.npcs.scripted.event.player.FactionEvent; -import noppes.npcs.scripted.event.player.AuctionEvent; import noppes.npcs.scripted.event.player.PlayerAbilityEvent; import noppes.npcs.scripted.event.player.PlayerEvent; import noppes.npcs.scripted.event.player.PlayerEvent.ChatEvent; @@ -561,7 +561,7 @@ public class EventHooks { // ==================== ENERGY BARRIER EVENTS ==================== private static void dispatchBarrierEvent(net.minecraft.entity.Entity barrierEntity, int ownerEntityId, - noppes.npcs.scripted.event.EnergyBarrierEvent event, EnumScriptType type) { + noppes.npcs.scripted.event.EnergyBarrierEvent event, EnumScriptType type) { ScriptController.Instance.globalNpcScripts.callScript(type, event); net.minecraft.entity.Entity owner = barrierEntity.worldObj.getEntityByID(ownerEntityId); diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index a0c2ad0cc..d793dcea9 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -6,10 +6,10 @@ import cpw.mods.fml.common.gameevent.PlayerEvent; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.addon.DBCAddon; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.util.AttributeAttackUtil; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/noppes/npcs/ServerTickHandler.java b/src/main/java/noppes/npcs/ServerTickHandler.java index 2639c6ab5..220a0e6ef 100644 --- a/src/main/java/noppes/npcs/ServerTickHandler.java +++ b/src/main/java/noppes/npcs/ServerTickHandler.java @@ -6,9 +6,9 @@ import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; -import noppes.npcs.controllers.AuctionController; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.world.WorldServer; +import noppes.npcs.controllers.AuctionController; import noppes.npcs.controllers.ScriptController; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.controllers.data.action.ActionManager; diff --git a/src/main/java/noppes/npcs/ai/CombatHandler.java b/src/main/java/noppes/npcs/ai/CombatHandler.java index ff81e4821..97d9a90eb 100644 --- a/src/main/java/noppes/npcs/ai/CombatHandler.java +++ b/src/main/java/noppes/npcs/ai/CombatHandler.java @@ -1,7 +1,6 @@ package noppes.npcs.ai; import kamkeel.npcs.addon.DBCAddon; -import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/noppes/npcs/blocks/BlockWaypoint.java b/src/main/java/noppes/npcs/blocks/BlockWaypoint.java index 7e2dd87d3..e91f43d25 100644 --- a/src/main/java/noppes/npcs/blocks/BlockWaypoint.java +++ b/src/main/java/noppes/npcs/blocks/BlockWaypoint.java @@ -12,10 +12,8 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import noppes.npcs.CustomItems; -import noppes.npcs.CustomNpcs; import noppes.npcs.CustomNpcsPermissions; import noppes.npcs.blocks.tiles.TileWaypoint; -import noppes.npcs.constants.EnumGuiType; public class BlockWaypoint extends BlockContainer { diff --git a/src/main/java/noppes/npcs/client/AuctionClientConfig.java b/src/main/java/noppes/npcs/client/AuctionClientConfig.java index f993318ed..a94bab504 100644 --- a/src/main/java/noppes/npcs/client/AuctionClientConfig.java +++ b/src/main/java/noppes/npcs/client/AuctionClientConfig.java @@ -7,7 +7,7 @@ import net.minecraft.nbt.NBTTagCompound; /** * Client-side cache for auction configuration synced from server. * Prevents client from using local ConfigMarket which may differ from server settings. - * + *

* IMPORTANT: This class is CLIENT-ONLY. Server-side NBT writing is done in * AuctionConfigSync to avoid @SideOnly crashes on dedicated servers. */ @@ -56,11 +56,31 @@ public class AuctionClientConfig { } // Getters - public static boolean isAuctionEnabled() { return auctionEnabled; } - public static long getListingFee() { return listingFee; } - public static String getCurrencyName() { return currencyName; } - public static int getAuctionDurationHours() { return auctionDurationHours; } - public static double getMinBidIncrement() { return minBidIncrement; } - public static int getMaxActiveListings() { return maxActiveListings; } - public static int getClaimExpirationDays() { return claimExpirationDays; } + public static boolean isAuctionEnabled() { + return auctionEnabled; + } + + public static long getListingFee() { + return listingFee; + } + + public static String getCurrencyName() { + return currencyName; + } + + public static int getAuctionDurationHours() { + return auctionDurationHours; + } + + public static double getMinBidIncrement() { + return minBidIncrement; + } + + public static int getMaxActiveListings() { + return maxActiveListings; + } + + public static int getClaimExpirationDays() { + return claimExpirationDays; + } } diff --git a/src/main/java/noppes/npcs/client/ClientEventHandler.java b/src/main/java/noppes/npcs/client/ClientEventHandler.java index 6fc690b5a..a73ccf707 100644 --- a/src/main/java/noppes/npcs/client/ClientEventHandler.java +++ b/src/main/java/noppes/npcs/client/ClientEventHandler.java @@ -20,12 +20,12 @@ import net.minecraftforge.client.event.RenderHandEvent; import net.minecraftforge.client.event.RenderLivingEvent; import net.minecraftforge.client.event.RenderPlayerEvent; import net.minecraftforge.event.entity.player.ItemTooltipEvent; -import noppes.npcs.client.gui.player.AuctionTooltipHandler; import noppes.npcs.client.gui.customoverlay.OverlayCustom; import noppes.npcs.client.gui.hud.ClientHudManager; import noppes.npcs.client.gui.hud.CompassHudComponent; import noppes.npcs.client.gui.hud.EnumHudComponent; import noppes.npcs.client.gui.hud.QuestTrackingComponent; +import noppes.npcs.client.gui.player.AuctionTooltipHandler; import noppes.npcs.client.renderer.MarkRenderer; import noppes.npcs.client.renderer.RenderCNPCPlayer; import noppes.npcs.constants.EnumAnimationPart; @@ -91,9 +91,9 @@ public class ClientEventHandler { // Block mouse movement during ability rotation lock. // Allow clicks (button >= 0) and scroll (dwheel != 0) to pass through. if (mc.theWorld != null && mc.thePlayer != null - && ClientAbilityState.shouldLockRotation() - && (event.dx != 0 || event.dy != 0) - && event.button == -1 && event.dwheel == 0) { + && ClientAbilityState.shouldLockRotation() + && (event.dx != 0 || event.dy != 0) + && event.button == -1 && event.dwheel == 0) { event.setCanceled(true); return; } diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 28e028afe..a0a5ce060 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -6,8 +6,27 @@ import cpw.mods.fml.client.registry.RenderingRegistry; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ObfuscationReflectionHelper; import kamkeel.npcs.addon.client.DBCClient; +import kamkeel.npcs.client.renderer.RenderAbilityBeam; +import kamkeel.npcs.client.renderer.RenderAbilityDisc; +import kamkeel.npcs.client.renderer.RenderAbilityLaser; +import kamkeel.npcs.client.renderer.RenderAbilityOrb; +import kamkeel.npcs.client.renderer.RenderAbilitySweeper; +import kamkeel.npcs.client.renderer.RenderAbilityZone; +import kamkeel.npcs.client.renderer.RenderEnergyDome; +import kamkeel.npcs.client.renderer.RenderEnergyPanel; +import kamkeel.npcs.client.renderer.RenderEnergySlicer; import kamkeel.npcs.client.renderer.TelegraphRenderer; +import kamkeel.npcs.client.renderer.lightning.LightningHandler; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; +import kamkeel.npcs.entity.EntityAbilityBeam; +import kamkeel.npcs.entity.EntityAbilityDisc; +import kamkeel.npcs.entity.EntityAbilityLaser; +import kamkeel.npcs.entity.EntityAbilityOrb; +import kamkeel.npcs.entity.EntityAbilitySweeper; +import kamkeel.npcs.entity.EntityAbilityZone; +import kamkeel.npcs.entity.EntityEnergyDome; +import kamkeel.npcs.entity.EntityEnergyPanel; +import kamkeel.npcs.entity.EntityEnergySlicer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.model.ModelBiped; @@ -88,12 +107,12 @@ import noppes.npcs.client.gui.global.GuiNPCManageAnimations; import noppes.npcs.client.gui.global.GuiNPCManageBanks; import noppes.npcs.client.gui.global.GuiNPCManageDialogs; import noppes.npcs.client.gui.global.GuiNPCManageEffects; -import noppes.npcs.client.gui.global.GuiNpcManageAbilities; import noppes.npcs.client.gui.global.GuiNPCManageFactions; import noppes.npcs.client.gui.global.GuiNPCManageLinked; import noppes.npcs.client.gui.global.GuiNPCManageQuest; import noppes.npcs.client.gui.global.GuiNPCManageTags; import noppes.npcs.client.gui.global.GuiNPCManageTransporters; +import noppes.npcs.client.gui.global.GuiNpcManageAbilities; import noppes.npcs.client.gui.global.GuiNpcManageMagic; import noppes.npcs.client.gui.global.GuiNpcManageRecipes; import noppes.npcs.client.gui.global.GuiNpcQuestReward; @@ -105,15 +124,15 @@ import noppes.npcs.client.gui.mainmenu.GuiNpcAI; import noppes.npcs.client.gui.mainmenu.GuiNpcAdvanced; import noppes.npcs.client.gui.mainmenu.GuiNpcDisplay; import noppes.npcs.client.gui.mainmenu.GuiNpcStats; +import noppes.npcs.client.gui.player.GuiAuctionBidding; +import noppes.npcs.client.gui.player.GuiAuctionListing; +import noppes.npcs.client.gui.player.GuiAuctionSell; +import noppes.npcs.client.gui.player.GuiAuctionTrades; import noppes.npcs.client.gui.player.GuiBigSign; import noppes.npcs.client.gui.player.GuiCrate; import noppes.npcs.client.gui.player.GuiMailbox; import noppes.npcs.client.gui.player.GuiMailmanWrite; import noppes.npcs.client.gui.player.GuiNPCBankChest; -import noppes.npcs.client.gui.player.GuiAuctionBidding; -import noppes.npcs.client.gui.player.GuiAuctionTrades; -import noppes.npcs.client.gui.player.GuiAuctionListing; -import noppes.npcs.client.gui.player.GuiAuctionSell; import noppes.npcs.client.gui.player.GuiNPCTrader; import noppes.npcs.client.gui.player.GuiNpcAnvil; import noppes.npcs.client.gui.player.GuiNpcCarpentryBench; @@ -146,25 +165,6 @@ import noppes.npcs.client.renderer.RenderNpcCrystal; import noppes.npcs.client.renderer.RenderNpcDragon; import noppes.npcs.client.renderer.RenderNpcSlime; import noppes.npcs.client.renderer.RenderProjectile; -import kamkeel.npcs.client.renderer.RenderAbilityOrb; -import kamkeel.npcs.client.renderer.RenderAbilityDisc; -import kamkeel.npcs.client.renderer.RenderAbilityLaser; -import kamkeel.npcs.client.renderer.RenderAbilityBeam; -import kamkeel.npcs.client.renderer.RenderAbilitySweeper; -import kamkeel.npcs.client.renderer.RenderAbilityZone; -import kamkeel.npcs.client.renderer.RenderEnergyDome; -import kamkeel.npcs.client.renderer.RenderEnergyPanel; -import kamkeel.npcs.client.renderer.RenderEnergySlicer; -import kamkeel.npcs.client.renderer.lightning.LightningHandler; -import kamkeel.npcs.entity.EntityAbilityOrb; -import kamkeel.npcs.entity.EntityAbilityDisc; -import kamkeel.npcs.entity.EntityAbilityLaser; -import kamkeel.npcs.entity.EntityAbilityBeam; -import kamkeel.npcs.entity.EntityAbilitySweeper; -import kamkeel.npcs.entity.EntityAbilityZone; -import kamkeel.npcs.entity.EntityEnergyDome; -import kamkeel.npcs.entity.EntityEnergyPanel; -import kamkeel.npcs.entity.EntityEnergySlicer; import noppes.npcs.client.renderer.blocks.BlockBannerRenderer; import noppes.npcs.client.renderer.blocks.BlockBarrelRenderer; import noppes.npcs.client.renderer.blocks.BlockBeamRenderer; @@ -200,13 +200,16 @@ import noppes.npcs.client.renderer.items.ItemShortLampRenderer; import noppes.npcs.client.renderer.items.ItemTallLampRenderer; import noppes.npcs.client.renderer.items.ItemToolRenderer; import noppes.npcs.client.renderer.items.ScriptedBlockItemRenderer; -import noppes.npcs.client.ScriptClientConfig; import noppes.npcs.config.ConfigClient; import noppes.npcs.config.ConfigItem; import noppes.npcs.config.ConfigMain; import noppes.npcs.config.StringCache; import noppes.npcs.constants.EnumGuiType; import noppes.npcs.containers.ContainerAnvilRepair; +import noppes.npcs.containers.ContainerAuctionBidding; +import noppes.npcs.containers.ContainerAuctionListing; +import noppes.npcs.containers.ContainerAuctionSell; +import noppes.npcs.containers.ContainerAuctionTrades; import noppes.npcs.containers.ContainerCarpentryBench; import noppes.npcs.containers.ContainerCrate; import noppes.npcs.containers.ContainerCustomGui; @@ -219,11 +222,6 @@ import noppes.npcs.containers.ContainerNPCFollower; import noppes.npcs.containers.ContainerNPCFollowerHire; import noppes.npcs.containers.ContainerNPCFollowerSetup; import noppes.npcs.containers.ContainerNPCInv; -import noppes.npcs.containers.ContainerAuction; -import noppes.npcs.containers.ContainerAuctionBidding; -import noppes.npcs.containers.ContainerAuctionTrades; -import noppes.npcs.containers.ContainerAuctionListing; -import noppes.npcs.containers.ContainerAuctionSell; import noppes.npcs.containers.ContainerNPCTrader; import noppes.npcs.containers.ContainerNPCTraderSetup; import noppes.npcs.containers.ContainerNpcItemGiver; @@ -525,9 +523,7 @@ public class ClientProxy extends CommonProxy { abilityNpc.width = 0.6f; } return new GuiNpcManageAbilities(abilityNpc, npc != null); - } - - else if (gui == EnumGuiType.MainMenuGlobal) + } else if (gui == EnumGuiType.MainMenuGlobal) return new GuiNPCGlobalMainMenu(npc); else if (gui == EnumGuiType.MainMenuAI) diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 05af02800..5a59461b7 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -136,8 +136,8 @@ public class ClientTickHandler { // Prevent jumping when locked (allow gravity only if not flying) if ((ClientAbilityState.movementLocked || ClientAbilityState.positionLocked) - && !ClientAbilityState.hasAbilityMovement - && !AbilityController.Instance.isPlayerFlying(mc.thePlayer)) { + && !ClientAbilityState.hasAbilityMovement + && !AbilityController.Instance.isPlayerFlying(mc.thePlayer)) { mc.thePlayer.motionY = Math.min(mc.thePlayer.motionY, 0); } } @@ -226,7 +226,7 @@ public class ClientTickHandler { // Block movement keys during ability lock (only when no GUI is open and world exists). // Non-movement keys (ESC, chat, inventory, etc.) always pass through. if (mc.theWorld != null && mc.currentScreen == null && keyDown - && ClientAbilityState.shouldSuppressMovementInput() && isMovementKey(key)) { + && ClientAbilityState.shouldSuppressMovementInput() && isMovementKey(key)) { // Unpress the keybind so vanilla doesn't process it KeyBinding.setKeyBindState(key, false); return; diff --git a/src/main/java/noppes/npcs/client/ScriptClientConfig.java b/src/main/java/noppes/npcs/client/ScriptClientConfig.java index 863f991ba..2af091723 100644 --- a/src/main/java/noppes/npcs/client/ScriptClientConfig.java +++ b/src/main/java/noppes/npcs/client/ScriptClientConfig.java @@ -7,7 +7,7 @@ import net.minecraft.nbt.NBTTagCompound; /** * Client-side cache for script configuration synced from server. * Prevents client from using local ConfigScript which may differ from server settings. - * + *

* IMPORTANT: This class is CLIENT-ONLY. Server-side NBT writing is done in * ScriptConfigSync to avoid @SideOnly crashes on dedicated servers. */ @@ -46,16 +46,44 @@ public class ScriptClientConfig { } // Setters for individual packet updates - public static void setScriptingEnabled(boolean value) { scriptingEnabled = value; } - public static void setRunLoadedScriptsFirst(boolean value) { runLoadedScriptsFirst = value; } - public static void setGlobalPlayerScripts(boolean value) { globalPlayerScripts = value; } - public static void setGlobalForgeScripts(boolean value) { globalForgeScripts = value; } - public static void setGlobalNPCScripts(boolean value) { globalNPCScripts = value; } + public static void setScriptingEnabled(boolean value) { + scriptingEnabled = value; + } + + public static void setRunLoadedScriptsFirst(boolean value) { + runLoadedScriptsFirst = value; + } + + public static void setGlobalPlayerScripts(boolean value) { + globalPlayerScripts = value; + } + + public static void setGlobalForgeScripts(boolean value) { + globalForgeScripts = value; + } + + public static void setGlobalNPCScripts(boolean value) { + globalNPCScripts = value; + } // Getters - public static boolean isScriptingEnabled() { return scriptingEnabled; } - public static boolean isRunLoadedScriptsFirst() { return runLoadedScriptsFirst; } - public static boolean isGlobalPlayerScripts() { return globalPlayerScripts; } - public static boolean isGlobalForgeScripts() { return globalForgeScripts; } - public static boolean isGlobalNPCScripts() { return globalNPCScripts; } + public static boolean isScriptingEnabled() { + return scriptingEnabled; + } + + public static boolean isRunLoadedScriptsFirst() { + return runLoadedScriptsFirst; + } + + public static boolean isGlobalPlayerScripts() { + return globalPlayerScripts; + } + + public static boolean isGlobalForgeScripts() { + return globalForgeScripts; + } + + public static boolean isGlobalNPCScripts() { + return globalNPCScripts; + } } diff --git a/src/main/java/noppes/npcs/client/fx/ZoneParticleFX.java b/src/main/java/noppes/npcs/client/fx/ZoneParticleFX.java index 80f6f687b..eb04c2159 100644 --- a/src/main/java/noppes/npcs/client/fx/ZoneParticleFX.java +++ b/src/main/java/noppes/npcs/client/fx/ZoneParticleFX.java @@ -16,9 +16,9 @@ public class ZoneParticleFX extends EntityFX { private final boolean glow; public ZoneParticleFX(World world, double x, double y, double z, - double motionX, double motionY, double motionZ, - float r, float g, float b, float alpha, - float scale, int maxAge, float gravity, boolean glow) { + double motionX, double motionY, double motionZ, + float r, float g, float b, float alpha, + float scale, int maxAge, float gravity, boolean glow) { super(world, x, y, z, 0, 0, 0); this.motionX = motionX; this.motionY = motionY; diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java index 4919ccbae..80e0a971f 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java @@ -74,10 +74,10 @@ public class GuiNpcMobSpawnerFullscreen extends GuiDirectory implements IGuiData this.posZ = posZ; // Configure GuiDirectory layout percentages - leftPanelPercent = 0.15f; + leftPanelPercent = 0.15f; rightPanelPercent = 0.08f; - minLeftPanelW = 120; - minRightPanelW = 62; + minLeftPanelW = 120; + minRightPanelW = 62; PacketClient.sendClient(new CloneAllTagsPacket()); if (GuiNpcMobSpawner.showingClones == 2) { diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiNpcDialogVisual.java b/src/main/java/noppes/npcs/client/gui/SubGuiNpcDialogVisual.java index 86f174bc4..17996759e 100644 --- a/src/main/java/noppes/npcs/client/gui/SubGuiNpcDialogVisual.java +++ b/src/main/java/noppes/npcs/client/gui/SubGuiNpcDialogVisual.java @@ -1,9 +1,6 @@ package noppes.npcs.client.gui; -import kamkeel.npcs.network.PacketClient; -import kamkeel.npcs.network.packets.request.dialog.DialogSavePacket; import net.minecraft.client.gui.GuiButton; -import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.NoppesUtil; import noppes.npcs.client.gui.util.GuiCustomScroll; import noppes.npcs.client.gui.util.GuiMenuTopButton; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index 8e0db5e71..e95b2c97b 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -1,7 +1,7 @@ package noppes.npcs.client.gui.advanced; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.ChainedAbility; @@ -33,7 +33,14 @@ import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.constants.EnumScrollData; import noppes.npcs.entity.EntityNPCInterface; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Vector; /** * GUI for managing NPC abilities. @@ -42,30 +49,30 @@ import java.util.*; public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, ICustomScrollListener, IGuiData, ITextfieldListener, ISubGuiListener, IAbilityConfigCallback, IChainedAbilityConfigCallback { // ── Button IDs ──────────────────────────────────────────────────────────── - private static final int BTN_SCROLL_TYPE = 50; - private static final int BTN_ADD_ABILITY = 70; - private static final int BTN_REMOVE = 71; - private static final int BTN_EDIT = 72; - private static final int BTN_MOVE_UP = 73; - private static final int BTN_MOVE_DOWN = 74; - private static final int BTN_LOAD = 75; - private static final int BTN_SAVE_PRESET = 76; - private static final int BTN_TOGGLE_SLOT = 77; - private static final int BTN_ADD_CHAIN = 78; - private static final int BTN_ENABLED = 100; + private static final int BTN_SCROLL_TYPE = 50; + private static final int BTN_ADD_ABILITY = 70; + private static final int BTN_REMOVE = 71; + private static final int BTN_EDIT = 72; + private static final int BTN_MOVE_UP = 73; + private static final int BTN_MOVE_DOWN = 74; + private static final int BTN_LOAD = 75; + private static final int BTN_SAVE_PRESET = 76; + private static final int BTN_TOGGLE_SLOT = 77; + private static final int BTN_ADD_CHAIN = 78; + private static final int BTN_ENABLED = 100; // ── Scroll IDs ──────────────────────────────────────────────────────────── - private static final int SCROLL_TYPES = 0; - private static final int SCROLL_NPC = 1; + private static final int SCROLL_TYPES = 0; + private static final int SCROLL_NPC = 1; // ── TextField / Label IDs ───────────────────────────────────────────────── - private static final int TF_MIN_COOLDOWN = 101; - private static final int TF_MAX_COOLDOWN = 102; - private static final int TF_SEARCH = 4; - private static final int LBL_TYPES = 1; - private static final int LBL_NPC = 2; - private static final int LBL_MIN_COOLDOWN = 101; - private static final int LBL_MAX_COOLDOWN = 102; + private static final int TF_MIN_COOLDOWN = 101; + private static final int TF_MAX_COOLDOWN = 102; + private static final int TF_SEARCH = 4; + private static final int LBL_TYPES = 1; + private static final int LBL_NPC = 2; + private static final int LBL_MIN_COOLDOWN = 101; + private static final int LBL_MAX_COOLDOWN = 102; // ── Scroll lists ────────────────────────────────────────────────────────── private GuiCustomScroll availableTypesScroll; @@ -127,7 +134,8 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public String toString() { switch (this) { - case CNPC: return "CustomNPCs"; + case CNPC: + return "CustomNPCs"; case MODDED: if (Register.isEmpty("ability")) return "modded"; @@ -135,8 +143,10 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC String namespace = Register.REGISTERED_NAMESPACES.get("ability").get(modIndex); String displayName = Register.NAMESPACE_DISPLAY_NAMES.get(namespace); return displayName != null ? displayName : namespace; - case ALL: return "filter.all"; - default: return name(); + case ALL: + return "filter.all"; + default: + return name(); } } } @@ -855,13 +865,37 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void subGuiClosed(SubGuiInterface subgui) { - if (subgui instanceof SubGuiAbilityVariantSelect) { handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui); initGui(); return; } - if (subgui instanceof SubGuiChainedAbilityConfig) { handleChainConfigClosed(); initGui(); return; } - if (subgui instanceof SubGuiAbilityEditMode) { handleEditModeClosed((SubGuiAbilityEditMode) subgui); return; } - if (subgui instanceof SubGuiAbilitySaveConfirm) { handleSaveConfirmClosed((SubGuiAbilitySaveConfirm) subgui); initGui(); return; } - if (subgui instanceof SubGuiLoadTypeChoice) { handleLoadTypeChoiceClosed((SubGuiLoadTypeChoice) subgui); return; } - if (subgui instanceof SubGuiChainSelect) { handleChainSelectClosed((SubGuiChainSelect) subgui); return; } - if (subgui instanceof SubGuiAbilityConfig) { handleAbilityConfigClosed(); return; } + if (subgui instanceof SubGuiAbilityVariantSelect) { + handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui); + initGui(); + return; + } + if (subgui instanceof SubGuiChainedAbilityConfig) { + handleChainConfigClosed(); + initGui(); + return; + } + if (subgui instanceof SubGuiAbilityEditMode) { + handleEditModeClosed((SubGuiAbilityEditMode) subgui); + return; + } + if (subgui instanceof SubGuiAbilitySaveConfirm) { + handleSaveConfirmClosed((SubGuiAbilitySaveConfirm) subgui); + initGui(); + return; + } + if (subgui instanceof SubGuiLoadTypeChoice) { + handleLoadTypeChoiceClosed((SubGuiLoadTypeChoice) subgui); + return; + } + if (subgui instanceof SubGuiChainSelect) { + handleChainSelectClosed((SubGuiChainSelect) subgui); + return; + } + if (subgui instanceof SubGuiAbilityConfig) { + handleAbilityConfigClosed(); + return; + } } private void handleVariantSelectClosed(SubGuiAbilityVariantSelect gui) { diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java index bcfce7687..f0b610d19 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -3,10 +3,10 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.Condition; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldBuilder; -import noppes.npcs.client.gui.builder.FieldDef; import net.minecraft.client.gui.GuiButton; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.util.GuiMenuTopButton; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; @@ -75,11 +75,11 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi for (FieldDef def : fieldDefs) { String tab = def.getTab(); if (tab != null - && !TAB_NAME_GENERAL.equals(tab) - && !TAB_NAME_TYPE.equals(tab) - && !TAB_NAME_TARGET.equals(tab) - && !TAB_NAME_EFFECTS.equals(tab) - && !customTabNames.contains(tab)) { + && !TAB_NAME_GENERAL.equals(tab) + && !TAB_NAME_TYPE.equals(tab) + && !TAB_NAME_TARGET.equals(tab) + && !TAB_NAME_EFFECTS.equals(tab) + && !customTabNames.contains(tab)) { customTabNames.add(tab); } } @@ -187,11 +187,21 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } } else { switch (tabIndex) { - case TAB_GENERAL: tabName = TAB_NAME_GENERAL; break; - case TAB_TYPE: tabName = TAB_NAME_TYPE; break; - case TAB_TARGET: tabName = TAB_NAME_TARGET; break; - case TAB_EFFECTS: tabName = TAB_NAME_EFFECTS; break; - default: tabName = TAB_NAME_TYPE; break; + case TAB_GENERAL: + tabName = TAB_NAME_GENERAL; + break; + case TAB_TYPE: + tabName = TAB_NAME_TYPE; + break; + case TAB_TARGET: + tabName = TAB_NAME_TARGET; + break; + case TAB_EFFECTS: + tabName = TAB_NAME_EFFECTS; + break; + default: + tabName = TAB_NAME_TYPE; + break; } } @@ -264,17 +274,36 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi int id = guibutton.id; // Tab switching — fixed tabs - if (id == 90) { activeTab = TAB_GENERAL; initGui(); return; } - if (id == 91) { activeTab = TAB_TYPE; initGui(); return; } - if (id == 92) { activeTab = TAB_TARGET; initGui(); return; } - if (id == 93) { activeTab = TAB_EFFECTS; initGui(); return; } + if (id == 90) { + activeTab = TAB_GENERAL; + initGui(); + return; + } + if (id == 91) { + activeTab = TAB_TYPE; + initGui(); + return; + } + if (id == 92) { + activeTab = TAB_TARGET; + initGui(); + return; + } + if (id == 93) { + activeTab = TAB_EFFECTS; + initGui(); + return; + } // Custom tabs (94+) if (id >= 94 && id < 94 + customTabNames.size()) { activeTab = 4 + (id - 94); initGui(); return; } - if (id == -1000) { close(); return; } + if (id == -1000) { + close(); + return; + } // Condition buttons (50-80) if (handleConditionButton(id)) return; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java index 595f5a773..dda5ee8e6 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityLoad.java @@ -1,7 +1,7 @@ package noppes.npcs.client.gui.advanced; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket; import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index ccef30b0d..090d86dbb 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -43,37 +43,37 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private static final int LABEL_ID_START = 3000; // ── Tab constants (core tabs) ──────────────────────────────────────────── - private static final int TAB_GENERAL = 0; - private static final int TAB_ENTRIES = 1; + private static final int TAB_GENERAL = 0; + private static final int TAB_ENTRIES = 1; private static final int TAB_CONDITIONS = 2; private static final int CORE_TAB_COUNT = 3; // ── Tab button IDs ──────────────────────────────────────────────────────── - private static final int BTN_TAB_GENERAL = 90; - private static final int BTN_TAB_ENTRIES = 91; + private static final int BTN_TAB_GENERAL = 90; + private static final int BTN_TAB_ENTRIES = 91; private static final int BTN_TAB_CONDITIONS = 92; private static final int BTN_TAB_EXTRA_BASE = 93; - private static final int BTN_CLOSE = -1000; + private static final int BTN_CLOSE = -1000; // ── Entries tab: per-entry ID encoding ───────────────────────────────────── // Actual ID = ENTRY_BASE + i * ENTRY_STRIDE + offset // offset 0 = name/select button, 1 = delay text field, 2 = up, 3 = down, 4 = delete - private static final int ENTRY_BASE = 100; - private static final int ENTRY_END = 200; - private static final int ENTRY_STRIDE = 10; + private static final int ENTRY_BASE = 100; + private static final int ENTRY_END = 200; + private static final int ENTRY_STRIDE = 10; private static final int BTN_ADD_ENTRY = 200; // ── Conditions tab: per-condition ID encoding ───────────────────────────── // Actual ID = COND_BASE + i * COND_STRIDE + offset // offset 0 = name/click, 1 = edit, 2 = delete - private static final int COND_BASE = 50; - private static final int COND_END = 80; - private static final int COND_STRIDE = 10; - private static final int BTN_ADD_COND = 80; + private static final int COND_BASE = 50; + private static final int COND_END = 80; + private static final int COND_STRIDE = 10; + private static final int BTN_ADD_COND = 80; // ── Layout constants ────────────────────────────────────────────────────── - private static final int L_LABEL_X = 5; - private static final int ROW_H = 24; + private static final int L_LABEL_X = 5; + private static final int ROW_H = 24; private static final int MAX_ENTRIES = 10; private final ChainedAbility chain; @@ -109,7 +109,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback, - boolean npcContext, List npcSlots) { + boolean npcContext, List npcSlots) { this.chain = chain; this.callback = callback; this.npcContext = npcContext; @@ -417,10 +417,26 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText int id = guibutton.id; // Tab switching — core tabs - if (id == BTN_TAB_GENERAL) { activeTab = TAB_GENERAL; initGui(); return; } - if (id == BTN_TAB_ENTRIES) { activeTab = TAB_ENTRIES; initGui(); return; } - if (id == BTN_TAB_CONDITIONS) { activeTab = TAB_CONDITIONS; initGui(); return; } - if (id == BTN_CLOSE) { saveOnClose = true; close(); return; } + if (id == BTN_TAB_GENERAL) { + activeTab = TAB_GENERAL; + initGui(); + return; + } + if (id == BTN_TAB_ENTRIES) { + activeTab = TAB_ENTRIES; + initGui(); + return; + } + if (id == BTN_TAB_CONDITIONS) { + activeTab = TAB_CONDITIONS; + initGui(); + return; + } + if (id == BTN_CLOSE) { + saveOnClose = true; + close(); + return; + } // Tab switching — extra tabs if (id >= BTN_TAB_EXTRA_BASE && id < BTN_TAB_EXTRA_BASE + extraTabs.size()) { @@ -575,12 +591,35 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } } - if (subgui instanceof SubGuiAbilitySelect) { handleAbilitySelectClosed((SubGuiAbilitySelect) subgui); initGui(); return; } - if (subgui instanceof SubGuiChainedEntrySource) { handleEntrySourceClosed((SubGuiChainedEntrySource) subgui); return; } - if (subgui instanceof SubGuiNpcSlotPicker) { handleSlotPickerClosed((SubGuiNpcSlotPicker) subgui); initGui(); return; } - if (subgui instanceof SubGuiAbilityTypeSelect) { handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui); if (!hasSubGui()) initGui(); return; } - if (subgui instanceof SubGuiAbilityConfig) { editingEntryIndex = -1; initGui(); return; } - if (subgui instanceof SubGuiConditionEdit) { handleConditionEditClosed((SubGuiConditionEdit) subgui); initGui(); return; } + if (subgui instanceof SubGuiAbilitySelect) { + handleAbilitySelectClosed((SubGuiAbilitySelect) subgui); + initGui(); + return; + } + if (subgui instanceof SubGuiChainedEntrySource) { + handleEntrySourceClosed((SubGuiChainedEntrySource) subgui); + return; + } + if (subgui instanceof SubGuiNpcSlotPicker) { + handleSlotPickerClosed((SubGuiNpcSlotPicker) subgui); + initGui(); + return; + } + if (subgui instanceof SubGuiAbilityTypeSelect) { + handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui); + if (!hasSubGui()) initGui(); + return; + } + if (subgui instanceof SubGuiAbilityConfig) { + editingEntryIndex = -1; + initGui(); + return; + } + if (subgui instanceof SubGuiConditionEdit) { + handleConditionEditClosed((SubGuiConditionEdit) subgui); + initGui(); + return; + } } private void handleAbilitySelectClosed(SubGuiAbilitySelect gui) { diff --git a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java index aec4b766a..66e433f45 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java +++ b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java @@ -2,12 +2,12 @@ package noppes.npcs.client.gui.builder; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import noppes.npcs.controllers.AnimationController; -import noppes.npcs.controllers.data.Animation; import noppes.npcs.client.gui.SubGuiColorSelector; import noppes.npcs.client.gui.select.GuiAnimationSelection; import noppes.npcs.client.gui.select.GuiSoundSelection; import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.AnimationController; +import noppes.npcs.controllers.data.Animation; import java.util.List; import java.util.function.BooleanSupplier; @@ -175,8 +175,8 @@ public class FieldDef { } public static FieldDef animSubGui(String label, - Supplier idGetter, Consumer idSetter, - Supplier nameGetter, Consumer nameSetter) { + Supplier idGetter, Consumer idSetter, + Supplier nameGetter, Consumer nameSetter) { return subGuiField(label, () -> new GuiAnimationSelection(idGetter.get(), nameGetter.get()), gui -> { @@ -202,7 +202,10 @@ public class FieldDef { } return "gui.none"; }) - .clearable(() -> { idSetter.accept(-1); nameSetter.accept(""); }); + .clearable(() -> { + idSetter.accept(-1); + nameSetter.accept(""); + }); } // ═══════════════════════════════════════════════════════════════════ @@ -264,21 +267,65 @@ public class FieldDef { // GETTERS // ═══════════════════════════════════════════════════════════════════ - public String getLabel() { return label; } - public FieldType getType() { return type; } - public String getTab() { return tab; } - public boolean isVisible() { return visibleWhen.getAsBoolean(); } - public boolean isEnabled() { return enabledWhen.getAsBoolean(); } - public String getHoverText() { return hoverText; } - public float getMin() { return min; } - public float getMax() { return max; } - public boolean hasRange() { return min != Float.NEGATIVE_INFINITY || max != Float.POSITIVE_INFINITY; } - public Class> getEnumClass() { return enumClass; } - public String[] getStringEnumValues() { return stringEnumValues; } - public Supplier getSubGuiFactory() { return subGuiFactory; } - public Consumer getSubGuiResultHandler() { return subGuiResultHandler; } - public boolean hasClearAction() { return clearAction != null; } - public Runnable getClearAction() { return clearAction; } + public String getLabel() { + return label; + } + + public FieldType getType() { + return type; + } + + public String getTab() { + return tab; + } + + public boolean isVisible() { + return visibleWhen.getAsBoolean(); + } + + public boolean isEnabled() { + return enabledWhen.getAsBoolean(); + } + + public String getHoverText() { + return hoverText; + } + + public float getMin() { + return min; + } + + public float getMax() { + return max; + } + + public boolean hasRange() { + return min != Float.NEGATIVE_INFINITY || max != Float.POSITIVE_INFINITY; + } + + public Class> getEnumClass() { + return enumClass; + } + + public String[] getStringEnumValues() { + return stringEnumValues; + } + + public Supplier getSubGuiFactory() { + return subGuiFactory; + } + + public Consumer getSubGuiResultHandler() { + return subGuiResultHandler; + } + + public boolean hasClearAction() { + return clearAction != null; + } + + public Runnable getClearAction() { + return clearAction; + } public Object getValue() { return getter != null ? getter.get() : null; @@ -305,8 +352,13 @@ public class FieldDef { return buttonTextColorSupplier != null ? buttonTextColorSupplier.get() : null; } - public FieldDef getLeftChild() { return leftChild; } - public FieldDef getRightChild() { return rightChild; } + public FieldDef getLeftChild() { + return leftChild; + } + + public FieldDef getRightChild() { + return rightChild; + } // ═══════════════════════════════════════════════════════════════════ // LIST MANIPULATION (for mod injection) @@ -356,7 +408,7 @@ public class FieldDef { * * @param fields The field list to modify * @param targetLabel The label of the field to modify - * @param condition The condition to replace + * @param condition The condition to replace * @return true if the target was found and the condition was modified */ public static boolean modifyVisibility(List fields, String targetLabel, BooleanSupplier condition) { diff --git a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java index 1ef3d52e5..bb069b8f5 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java +++ b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java @@ -69,13 +69,32 @@ public class GuiFieldBuilder { // CONFIG CHAINING // ═══════════════════════════════════════════════════════════════════ - public GuiFieldBuilder scrollWindowId(int id) { this.scrollWindowId = id; return this; } - public GuiFieldBuilder contentRight(int v) { this.contentRight = v; return this; } - public GuiFieldBuilder startIds(int widget, int clear, int label) { - this.widgetId = widget; this.clearId = clear; this.labelId = label; return this; + public GuiFieldBuilder scrollWindowId(int id) { + this.scrollWindowId = id; + return this; + } + + public GuiFieldBuilder contentRight(int v) { + this.contentRight = v; + return this; + } + + public GuiFieldBuilder startIds(int widget, int clear, int label) { + this.widgetId = widget; + this.clearId = clear; + this.labelId = label; + return this; + } + + public GuiFieldBuilder startY(int y) { + this.startY = y; + return this; + } + + public GuiFieldBuilder rowHeight(int h) { + this.rowHeight = h; + return this; } - public GuiFieldBuilder startY(int y) { this.startY = y; return this; } - public GuiFieldBuilder rowHeight(int h) { this.rowHeight = h; return this; } // ═══════════════════════════════════════════════════════════════════ // BUILD @@ -98,6 +117,7 @@ public class GuiFieldBuilder { /** * Processes all fields and adds widgets to the scroll window. + * * @return the final Y position after all fields */ protected int build(List fields) { @@ -173,8 +193,14 @@ public class GuiFieldBuilder { } private int lastHandledIndex = -1; - protected void setLastHandledIndex(int index) { this.lastHandledIndex = index; } - protected int getLastHandledIndex() { return lastHandledIndex; } + + protected void setLastHandledIndex(int index) { + this.lastHandledIndex = index; + } + + protected int getLastHandledIndex() { + return lastHandledIndex; + } // ═══════════════════════════════════════════════════════════════════ // FIELD RENDERING @@ -268,7 +294,10 @@ public class GuiFieldBuilder { String curVal = def.getStringEnumValue(); int selected = 0; for (int i = 0; i < values.length; i++) { - if (values[i].equals(curVal)) { selected = i; break; } + if (values[i].equals(curVal)) { + selected = i; + break; + } } GuiNpcButton btn = new GuiNpcButton(widgetId, fieldX, y, fieldW, 20, values, selected); if (!def.isEnabled()) btn.setEnabled(false); @@ -321,14 +350,21 @@ public class GuiFieldBuilder { protected int getFullFieldWidth(FieldDef def) { switch (def.getType()) { case FLOAT: - case INT: return 60; - case STRING: return 200; - case BOOLEAN: return 50; - case LABEL: return 60; + case INT: + return 60; + case STRING: + return 200; + case BOOLEAN: + return 50; + case LABEL: + return 60; case ENUM: - case STRING_ENUM: return 120; - case SUB_GUI: return def.hasClearAction() ? 175 : 200; - default: return 80; + case STRING_ENUM: + return 120; + case SUB_GUI: + return def.hasClearAction() ? 175 : 200; + default: + return 80; } } @@ -410,7 +446,8 @@ public class GuiFieldBuilder { def.setValue(newVal); return true; } - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + } return false; case INT: try { @@ -421,7 +458,8 @@ public class GuiFieldBuilder { def.setValue(newVal); return true; } - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + } return false; case STRING: String newVal = field.getText(); @@ -453,10 +491,27 @@ public class GuiFieldBuilder { // GETTERS // ═══════════════════════════════════════════════════════════════════ - public Map getButtonFieldMap() { return buttonFieldMap; } - public Map getTextFieldMap() { return textFieldMap; } - public Map getClearFieldMap() { return clearFieldMap; } - public GuiScrollWindow getScrollWindow() { return sw; } - public int getLastBuildY() { return lastBuildY; } - public int getNextLabelId() { return labelId; } + public Map getButtonFieldMap() { + return buttonFieldMap; + } + + public Map getTextFieldMap() { + return textFieldMap; + } + + public Map getClearFieldMap() { + return clearFieldMap; + } + + public GuiScrollWindow getScrollWindow() { + return sw; + } + + public int getLastBuildY() { + return lastBuildY; + } + + public int getNextLabelId() { + return labelId; + } } diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index fef120604..52ddbba9d 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -1,7 +1,8 @@ package noppes.npcs.client.gui.global; -import kamkeel.npcs.controllers.data.ability.Ability; 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.ChainedAbility; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket; @@ -19,7 +20,6 @@ import net.minecraft.client.gui.GuiYesNoCallback; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; import noppes.npcs.client.NoppesUtil; -import kamkeel.npcs.controllers.data.ability.AbilityVariant; import noppes.npcs.client.gui.advanced.SubGuiAbilityTypeSelect; import noppes.npcs.client.gui.advanced.SubGuiAbilityVariantSelect; import noppes.npcs.client.gui.advanced.SubGuiChainedAbilityConfig; @@ -50,7 +50,7 @@ import java.util.Vector; /** * Global GUI for managing saved ability presets with 3D preview. - * + *

* Features: * - Left side: 3D preview of NPC with ability effects * - Right side: Ability list with search @@ -58,29 +58,29 @@ import java.util.Vector; * - Toggle between Custom and Built-in ability views */ public class GuiNpcManageAbilities extends GuiAbilityInterface - implements ICustomScrollListener, ISubGuiListener, IAbilityConfigCallback, - IChainedAbilityConfigCallback, ITextfieldListener, GuiYesNoCallback, - IScrollData, IGuiData { + implements ICustomScrollListener, ISubGuiListener, IAbilityConfigCallback, + IChainedAbilityConfigCallback, ITextfieldListener, GuiYesNoCallback, + IScrollData, IGuiData { // ── Button IDs ──────────────────────────────────────────────────────────── - private static final int BTN_REMOVE = 1; - private static final int BTN_ADD = 2; - private static final int BTN_TOGGLE_VIEW = 10; - private static final int BTN_EDIT = 100; - 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_REMOVE = 1; + private static final int BTN_ADD = 2; + private static final int BTN_TOGGLE_VIEW = 10; + private static final int BTN_EDIT = 100; + private static final int BTN_PREVIEW_PLAY = 91; + private static final int BTN_PREVIEW_PAUSE = 92; + private static final int BTN_PREVIEW_STOP = 93; // ── TextField / Label IDs ───────────────────────────────────────────────── - private static final int TF_SEARCH = 55; + private static final int TF_SEARCH = 55; private static final int LBL_PREVIEW_STATUS = 90; // ── Scroll IDs ──────────────────────────────────────────────────────────── - private static final int SCROLL_MAIN = 0; + private static final int SCROLL_MAIN = 0; // ── Confirm dialog IDs ──────────────────────────────────────────────────── private static final int CONFIRM_REMOVE_ABILITY = 1; - private static final int CONFIRM_REMOVE_CHAIN = 2; + private static final int CONFIRM_REMOVE_CHAIN = 2; // ==================== DATA ==================== private GuiCustomScroll scroll; @@ -474,11 +474,20 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface @Override public void subGuiClosed(SubGuiInterface subgui) { - if (subgui instanceof SubGuiChainedAbilityConfig) { handleChainConfigClosed(); initGui(); return; } - if (subgui instanceof SubGuiAbilityVariantSelect) { if (handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui)) return; } - else if (subgui instanceof SubGuiAbilityTypeSelect) { if (handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui)) return; } - else if (subgui instanceof SubGuiDuplicateNameConfirm) { if (handleDuplicateNameClosed((SubGuiDuplicateNameConfirm) subgui)) return; } - else if (pendingSaveAbility != null) { if (handlePendingSave()) return; } + if (subgui instanceof SubGuiChainedAbilityConfig) { + handleChainConfigClosed(); + initGui(); + return; + } + if (subgui instanceof SubGuiAbilityVariantSelect) { + if (handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui)) return; + } else if (subgui instanceof SubGuiAbilityTypeSelect) { + if (handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui)) return; + } else if (subgui instanceof SubGuiDuplicateNameConfirm) { + if (handleDuplicateNameClosed((SubGuiDuplicateNameConfirm) subgui)) return; + } else if (pendingSaveAbility != null) { + if (handlePendingSave()) return; + } initGui(); } @@ -491,7 +500,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface } } - /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + /** + * @return true if a SubGui was opened (caller should return early, skipping initGui) + */ private boolean handleVariantSelectClosed(SubGuiAbilityVariantSelect gui) { int idx = gui.getSelectedIndex(); if (idx >= 0 && pendingTypeId != null) { @@ -514,7 +525,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface return false; } - /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + /** + * @return true if a SubGui was opened (caller should return early, skipping initGui) + */ private boolean handleTypeSelectClosed(SubGuiAbilityTypeSelect gui) { String typeId = gui.getSelectedTypeId(); if (typeId != null) { @@ -542,7 +555,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface return false; } - /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + /** + * @return true if a SubGui was opened (caller should return early, skipping initGui) + */ private boolean handleDuplicateNameClosed(SubGuiDuplicateNameConfirm gui) { if (gui.isConfirmed() && pendingSaveAbility != null) { if (pendingNewCreation) { @@ -569,7 +584,9 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface return false; } - /** @return true if a SubGui was opened (caller should return early, skipping initGui) */ + /** + * @return true if a SubGui was opened (caller should return early, skipping initGui) + */ private boolean handlePendingSave() { if (hasDuplicateName(pendingSaveAbility)) { pendingNewCreation = false; diff --git a/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java b/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java index a9473f2e9..f07cb5ac6 100644 --- a/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java +++ b/src/main/java/noppes/npcs/client/gui/player/AuctionTooltipHandler.java @@ -12,20 +12,20 @@ import net.minecraft.util.StatCollector; import net.minecraftforge.event.entity.player.ItemTooltipEvent; import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.constants.EnumClaimType; -import noppes.npcs.util.AuctionFormatUtil; import noppes.npcs.containers.ContainerAuctionListing; import noppes.npcs.containers.ContainerAuctionSell; import noppes.npcs.containers.ContainerAuctionTrades; import noppes.npcs.containers.SlotAuctionDisplay; import noppes.npcs.controllers.data.AuctionClaim; import noppes.npcs.controllers.data.AuctionListing; +import noppes.npcs.util.AuctionFormatUtil; import java.util.List; /** * Handles adding auction information to item tooltips when viewing auction GUIs. * Called from ItemTooltipEvent in ClientEventHandler. - * + *

* Uses slot-based lookup (slot index) instead of ItemStack comparison to correctly * handle duplicate items with different auction data (e.g., two porkchops at different prices). */ diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java index f74a354df..61634bb0a 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionBidding.java @@ -82,7 +82,9 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis return -1; } - /** Update button visibility based on listing state */ + /** + * Update button visibility based on listing state + */ private void updateButtonState() { if (listing == null) { if (btnBid != null) btnBid.setVisible(false); @@ -185,7 +187,9 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis drawAuctionInfo(); } - /** Draw auction listing information on the right side */ + /** + * Draw auction listing information on the right side + */ private void drawAuctionInfo() { int x = guiLeft + infoX; int y = guiTop + infoY; @@ -226,7 +230,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis label = AuctionFormatUtil.formatCurrency(listing.startingPrice); length = fontRendererObj.getStringWidth(label); - fontRendererObj.drawString(EnumChatFormatting.GOLD + label, xRight - length, y, 0xFFFFFF); + fontRendererObj.drawString(EnumChatFormatting.GOLD + label, xRight - length, y, 0xFFFFFF); y += lineHeight + 4; } @@ -237,7 +241,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis label = AuctionFormatUtil.formatCurrency(listing.buyoutPrice); length = fontRendererObj.getStringWidth(label); - fontRendererObj.drawString(EnumChatFormatting.GREEN + label, xRight - length, y, 0xFFFFFF); + fontRendererObj.drawString(EnumChatFormatting.GREEN + label, xRight - length, y, 0xFFFFFF); y += lineHeight + 4; } @@ -306,5 +310,7 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis // Block all slot interactions - view only } - public AuctionListing getListing() { return listing; } + public AuctionListing getListing() { + return listing; + } } diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java index e6e87383a..048260d4c 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionInterface.java @@ -69,7 +69,9 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { initNavigationButtons(); } - /** Initialize navigation buttons */ + /** + * Initialize navigation buttons + */ protected void initNavigationButtons() { int page = getCurrentPage(); @@ -89,7 +91,9 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { addButton(btnClaims); } - /** Returns current page constant - implement in subclass */ + /** + * Returns current page constant - implement in subclass + */ protected abstract int getCurrentPage(); @Override @@ -136,7 +140,9 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { } } - /** Draw navigation button tooltips */ + /** + * Draw navigation button tooltips + */ protected void drawNavButtonTooltips(int mouseX, int mouseY) { List tooltip = null; if (btnListings != null && btnListings.isHovered()) tooltip = btnListings.getTooltipLines(); @@ -154,14 +160,18 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { // ========== Slot Drawing Utilities ========== - /** Draw auction slot background (18x18) */ + /** + * Draw auction slot background (18x18) + */ protected void drawAuctionSlot(int x, int y) { GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); mc.renderEngine.bindTexture(AUCTION_SLOT); Gui.func_146110_a(x, y, 0, 0, 18, 18, 18, 18); } - /** Draw colored overlay on slot (ARGB format) */ + /** + * Draw colored overlay on slot (ARGB format) + */ protected void drawColoredOverlay(int x, int y, int color) { GL11.glDisable(GL11.GL_LIGHTING); GL11.glDisable(GL11.GL_DEPTH_TEST); @@ -173,12 +183,16 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); } - /** Draw dark overlay for unavailable slots */ + /** + * Draw dark overlay for unavailable slots + */ protected void drawDarkenedOverlay(int x, int y) { drawColoredOverlay(x, y, OVERLAY_DARK); } - /** Draw icon texture on slot (16x16) */ + /** + * Draw icon texture on slot (16x16) + */ protected void drawIconOverlay(int x, int y, ResourceLocation icon) { GL11.glPushMatrix(); GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); @@ -192,7 +206,9 @@ public abstract class GuiAuctionInterface extends GuiContainerNPCInterface { GL11.glPopMatrix(); } - /** Subclasses implement this to draw specific content */ + /** + * Subclasses implement this to draw specific content + */ protected abstract void drawAuctionContent(float partialTicks, int mouseX, int mouseY); @Override diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java index c08569598..85312f5ad 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionListing.java @@ -98,12 +98,16 @@ public class GuiAuctionListing extends GuiAuctionInterface implements ISubGuiLis return PAGE_LISTINGS; } - /** Request listings from server */ + /** + * Request listings from server + */ private void requestListings() { AuctionActionPacket.requestListings(filter, currentPage); } - /** Update pagination button visibility based on current page state */ + /** + * Update pagination button visibility based on current page state + */ private void updatePaginationVisibility() { if (btnPageUp != null) { btnPageUp.setVisible(currentPage > 0); @@ -193,7 +197,8 @@ public class GuiAuctionListing extends GuiAuctionInterface implements ISubGuiLis } @Override - public void subGuiClosed(SubGuiInterface subgui) {} + public void subGuiClosed(SubGuiInterface subgui) { + } // ========== Data Handling ========== @@ -294,6 +299,11 @@ public class GuiAuctionListing extends GuiAuctionInterface implements ISubGuiLis // Block other slot interactions } - public AuctionFilter getFilter() { return filter; } - public ContainerAuctionListing getListingContainer() { return listingContainer; } + public AuctionFilter getFilter() { + return filter; + } + + public ContainerAuctionListing getListingContainer() { + return listingContainer; + } } diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java index 5b514cff6..af87ab39b 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionSell.java @@ -7,16 +7,16 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.ResourceLocation; import net.minecraft.util.StatCollector; +import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.client.NoppesUtil; import noppes.npcs.client.gui.util.GuiAuctionNavButton; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.ITextfieldListener; -import noppes.npcs.client.AuctionClientConfig; import noppes.npcs.containers.ContainerAuctionSell; -import noppes.npcs.util.AuctionFormatUtil; import noppes.npcs.entity.EntityNPCInterface; +import noppes.npcs.util.AuctionFormatUtil; import org.lwjgl.opengl.GL11; import java.util.ArrayList; @@ -105,7 +105,9 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis return PAGE_SELL; } - /** Update visibility of buyout price field based on toggle state */ + /** + * Update visibility of buyout price field based on toggle state + */ private void updateBuyoutVisibility() { if (lblBuyout != null) { lblBuyout.enabled = allowBuyout; @@ -119,7 +121,9 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis } } - /** Update the buyout toggle button tooltip */ + /** + * Update the buyout toggle button tooltip + */ private void updateBuyoutTooltip() { if (btnAllowBuyout == null) return; @@ -252,7 +256,9 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis drawSellSlotIndicator(slotX, slotY); } - /** Draw indicator showing staged vs available items */ + /** + * Draw indicator showing staged vs available items + */ private void drawSellSlotIndicator(int slotX, int slotY) { ItemStack sellItem = sellContainer.getItemToSell(); if (sellItem != null) { @@ -310,7 +316,9 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis drawSellPageTooltips(mouseX, mouseY); } - /** Draw tooltips for sell page buttons */ + /** + * Draw tooltips for sell page buttons + */ private void drawSellPageTooltips(int mouseX, int mouseY) { List tooltip = null; diff --git a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java index f52d5fa63..45a527dca 100644 --- a/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java +++ b/src/main/java/noppes/npcs/client/gui/player/GuiAuctionTrades.java @@ -50,7 +50,7 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { private int pendingSlot = -1; private PendingOp pendingOp = PendingOp.NONE; - private enum PendingOp { NONE, CANCEL, CLAIM } + private enum PendingOp {NONE, CANCEL, CLAIM} public GuiAuctionTrades(EntityNPCInterface npc, ContainerAuctionTrades container) { super(npc, container); @@ -117,7 +117,9 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { } } - /** Handle click on pending operation slot */ + /** + * Handle click on pending operation slot + */ private void handlePendingClick(int mouseButton) { if (pendingOp == PendingOp.CANCEL && mouseButton == 1) { // Right-click confirms cancel @@ -159,14 +161,18 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { } } - /** Set pending operation and hide slot item */ + /** + * Set pending operation and hide slot item + */ private void setPending(int slot, PendingOp op) { pendingSlot = slot; pendingOp = op; tradesContainer.setHiddenSlot(slot); } - /** Clear pending operation and restore slot */ + /** + * Clear pending operation and restore slot + */ private void clearPending() { if (pendingSlot >= 0) { tradesContainer.clearHiddenSlot(); @@ -175,13 +181,17 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { pendingOp = PendingOp.NONE; } - /** Play confirmation sound */ + /** + * Play confirmation sound + */ private void playConfirmSound() { mc.getSoundHandler().playSound(PositionedSoundRecord.func_147674_a( new ResourceLocation("random.orb"), 1.0F)); } - /** Get slot index at mouse position */ + /** + * Get slot index at mouse position + */ private int getSlotAt(int mouseX, int mouseY) { int relX = mouseX - guiLeft - GRID_X; int relY = mouseY - guiTop - GRID_Y; @@ -239,7 +249,9 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { } } - /** Get tint color for slot based on contents */ + /** + * Get tint color for slot based on contents + */ private int getSlotTint(int slot) { AuctionListing listing = tradesContainer.getListingAt(slot); AuctionClaim claim = tradesContainer.getClaimAt(slot); @@ -281,7 +293,9 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData { } } - /** Draw tooltip for a trade slot */ + /** + * Draw tooltip for a trade slot + */ private void drawTradeTooltip(int slot, int x, int y) { List tooltip = new ArrayList<>(); diff --git a/src/main/java/noppes/npcs/client/gui/select/GuiAnimationSelection.java b/src/main/java/noppes/npcs/client/gui/select/GuiAnimationSelection.java index 7161722d5..e6f2ce61c 100644 --- a/src/main/java/noppes/npcs/client/gui/select/GuiAnimationSelection.java +++ b/src/main/java/noppes/npcs/client/gui/select/GuiAnimationSelection.java @@ -5,7 +5,6 @@ import kamkeel.npcs.network.packets.request.animation.AnimationsGetPacket; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.gui.util.GuiCustomScroll; import noppes.npcs.client.gui.util.GuiNpcButton; -import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IScrollData; diff --git a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java b/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java index 6ed26666f..6d2534633 100644 --- a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java +++ b/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java @@ -19,10 +19,10 @@ import java.util.List; /** * Client-side executor for previewing abilities in the GUI. - * + *

* Uses unified execution: calls the REAL ability methods (onWindUpTick, onExecute, onActiveTick) * with previewMode=true. Abilities skip damage/effects/sounds when isPreview() returns true. - * + *

* Lifecycle: * 1. WINDUP phase - real onWindUpTick() called, telegraph shown, entities may spawn * 2. ACTIVE phase - real onExecute() then onActiveTick() called, physics simulated @@ -36,8 +36,11 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { private Ability previewAbility; private EntityNPCInterface previewNpc; - /** Executor's own phase tracking (separate from ability.getPhase()) */ - private enum ExecutorPhase { IDLE, WINDUP, ACTIVE, EXTENDED } + /** + * Executor's own phase tracking (separate from ability.getPhase()) + */ + private enum ExecutorPhase {IDLE, WINDUP, ACTIVE, EXTENDED} + private ExecutorPhase executorPhase = ExecutorPhase.IDLE; private int currentTick = 0; @@ -424,21 +427,48 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { // ==================== GETTERS ==================== - public boolean isPlaying() { return playing; } - public boolean isPaused() { return paused; } - public boolean isActive() { return playing || paused; } + public boolean isPlaying() { + return playing; + } + + public boolean isPaused() { + return paused; + } + + public boolean isActive() { + return playing || paused; + } + public AbilityPhase getPhase() { switch (executorPhase) { - case WINDUP: return AbilityPhase.WINDUP; - case ACTIVE: return AbilityPhase.ACTIVE; - default: return AbilityPhase.IDLE; + case WINDUP: + return AbilityPhase.WINDUP; + case ACTIVE: + return AbilityPhase.ACTIVE; + default: + return AbilityPhase.IDLE; } } - public int getCurrentTick() { return currentTick; } - public TelegraphInstance getTelegraph() { return previewTelegraph; } - public List getPreviewEntities() { return previewEntities; } - public Ability getPreviewAbility() { return previewAbility; } - public EntityNPCInterface getPreviewNpc() { return previewNpc; } + + public int getCurrentTick() { + return currentTick; + } + + public TelegraphInstance getTelegraph() { + return previewTelegraph; + } + + public List getPreviewEntities() { + return previewEntities; + } + + public Ability getPreviewAbility() { + return previewAbility; + } + + public EntityNPCInterface getPreviewNpc() { + return previewNpc; + } public String getStatusString() { if (!playing && !paused) { diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java b/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java index 74bc3abd0..4bf2ffd58 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java @@ -21,7 +21,7 @@ import java.util.List; /** * Base GUI class for ability preview with fixed camera. - * + *

* Key concepts: * - NPC positioned on left side of preview area * - NPC faces a fixed direction (NPC_FACING_YAW) @@ -33,15 +33,23 @@ import java.util.List; public class GuiAbilityInterface extends GuiNPCInterface2 { // ==================== FIXED CAMERA SETTINGS ==================== - /** Fixed camera zoom level (higher = more zoomed in) */ + /** + * Fixed camera zoom level (higher = more zoomed in) + */ protected static float CAMERA_ZOOM = 45.0f; - /** NPC facing direction - affects anchor points and visual facing */ + /** + * NPC facing direction - affects anchor points and visual facing + */ protected static float NPC_FACING_YAW = 160.0f; - /** Camera pitch (slight downward angle) */ + /** + * Camera pitch (slight downward angle) + */ protected static float CAMERA_PITCH = 10.0f; // ==================== VIEWING ROTATION ==================== - /** Viewing direction rotation (rotates entire 3D space) */ + /** + * Viewing direction rotation (rotates entire 3D space) + */ protected static float viewRotation = 0.0f; private GuiNpcButton btnLeft, btnRight; @@ -49,21 +57,33 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { // ==================== NPC STATE ==================== public EntityNPCInterface npc; - /** NPC starting position (saved when preview starts, used to track movement) */ + /** + * NPC starting position (saved when preview starts, used to track movement) + */ protected double npcStartX, npcStartY, npcStartZ; - /** Whether we're tracking NPC movement */ + /** + * Whether we're tracking NPC movement + */ protected boolean trackingNpcMovement = false; - /** Entities to render in preview space */ + /** + * Entities to render in preview space + */ protected List previewEntities = new ArrayList<>(); - /** Current telegraph to render in preview */ + /** + * Current telegraph to render in preview + */ protected TelegraphInstance previewTelegraph; // ==================== LAYOUT ==================== - /** Preview area bounds */ + /** + * Preview area bounds + */ protected int previewX, previewY, previewWidth, previewHeight; - /** NPC render position on screen */ + /** + * NPC render position on screen + */ protected int npcScreenX, npcScreenY; public int xOffset = 0; @@ -74,7 +94,7 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { } /** - * @param npc The NPC for preview rendering + * @param npc The NPC for preview rendering * @param hasMenuNpc If false, the top menu tabs (Display, Stats, etc.) are hidden. * Use false when opened from the global menu without a real NPC. */ @@ -99,8 +119,8 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { previewHeight = 180; // NPC position: left side of preview, slightly below center - npcScreenX = previewX + (int)(previewWidth * 0.33f) + xOffset; - npcScreenY = previewY + (int)(previewHeight * 0.85f) + yOffset; + npcScreenX = previewX + (int) (previewWidth * 0.33f) + xOffset; + npcScreenY = previewY + (int) (previewHeight * 0.85f) + yOffset; // Viewing rotation buttons (below preview area) int btnY = guiTop + 192; @@ -351,7 +371,7 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { super.drawBackground(); drawGradientRect(previewX, previewY, previewX + previewWidth, previewY + previewHeight, - 0xc0101010, 0xd0101010); + 0xc0101010, 0xd0101010); GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiAuctionNavButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiAuctionNavButton.java index 665dd2259..a71c9762f 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiAuctionNavButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiAuctionNavButton.java @@ -64,14 +64,14 @@ public class GuiAuctionNavButton extends GuiNpcButton { /** * Create a fully customizable button. * - * @param id Button ID - * @param x X position - * @param y Y position - * @param buttonSize Size of the button background texture (e.g., 18 for 18x18, 10 for 10x10) - * @param iconSize Size of the icon texture (e.g., 16 for 16x16, 8 for 8x8) - * @param tooltipKey Lang key for tooltip - * @param iconTexture Icon texture to draw inside the button - * @param textureNormal Background texture for normal state + * @param id Button ID + * @param x X position + * @param y Y position + * @param buttonSize Size of the button background texture (e.g., 18 for 18x18, 10 for 10x10) + * @param iconSize Size of the icon texture (e.g., 16 for 16x16, 8 for 8x8) + * @param tooltipKey Lang key for tooltip + * @param iconTexture Icon texture to draw inside the button + * @param textureNormal Background texture for normal state * @param texturePressed Background texture for pressed/hover state */ public GuiAuctionNavButton(int id, int x, int y, int buttonSize, int iconSize, String tooltipKey, diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java b/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java index 750c60668..957237610 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java @@ -4,29 +4,29 @@ package noppes.npcs.client.gui.util; * Abstract base class for fullscreen directory-browsing GUIs. * Provides a configurable three-panel layout (left nav, center content, right actions) * with percentage-based sizing, panel backgrounds/borders, and an optional resizable divider. - * + *

* Subclasses override abstract hooks to populate each panel region. * Designed for reuse across Cloner, Quest Manager, Dialog Manager, etc. */ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScrollListener, ISubGuiListener { // ===== LAYOUT CONFIG (subclasses may override in constructor) ===== - protected float leftPanelPercent = 0.20f; + protected float leftPanelPercent = 0.20f; protected float rightPanelPercent = 0.10f; - protected int minLeftPanelW = 80; + protected int minLeftPanelW = 80; protected int minRightPanelW = 40; - protected int minCenterW = 100; + protected int minCenterW = 100; // ===== LAYOUT CONSTANTS ===== - protected int pad = 10; + protected int pad = 10; protected int topBarH = 24; - protected int gap = 4; - protected int btnH = 20; + protected int gap = 4; + protected int btnH = 20; // ===== PANEL COLORS ===== - protected int panelBg = 0xC0101010; + protected int panelBg = 0xC0101010; protected int panelBorder = 0xFF333333; - protected int topBarBg = 0xC0181818; + protected int topBarBg = 0xC0181818; // ===== COMPUTED LAYOUT (populated by computeLayout) ===== protected int originX, originY, usableW, usableH; @@ -35,10 +35,10 @@ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScr protected int rightX; // ===== OPTIONAL RESIZABLE DIVIDER (between left and center) ===== - protected boolean enableDivider = false; - protected int dividerWidth = 5; - protected int dividerLineHeight = 20; - protected int minDividerPanelW = 50; + protected boolean enableDivider = false; + protected int dividerWidth = 5; + protected int dividerLineHeight = 20; + protected int minDividerPanelW = 50; private int dividerOffset; private boolean isDragging = false; private int dragStartX; @@ -57,8 +57,8 @@ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScr usableW = width - 2 * pad; usableH = height - 2 * pad; - leftPanelW = Math.max(minLeftPanelW, (int)(usableW * leftPanelPercent)); - rightPanelW = Math.max(minRightPanelW, (int)(usableW * rightPanelPercent)); + leftPanelW = Math.max(minLeftPanelW, (int) (usableW * leftPanelPercent)); + rightPanelW = Math.max(minRightPanelW, (int) (usableW * rightPanelPercent)); if (rightPanelPercent <= 0) { rightPanelW = 0; @@ -73,7 +73,7 @@ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScr contentX = originX + leftPanelW + gap; contentY = originY + topBarH + gap; contentH = usableH - topBarH - gap; - rightX = contentX + contentW + gap; + rightX = contentX + contentW + gap; if (enableDivider && dividerOffset == 0) { dividerOffset = leftPanelW; @@ -128,7 +128,9 @@ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScr drawRect(divX + 1, handleTop, divX + dividerWidth - 1, handleTop + dividerLineHeight, 0xFF707070); } - /** Override for extra drawing after super.drawScreen (e.g. status text) */ + /** + * Override for extra drawing after super.drawScreen (e.g. status text) + */ protected void drawOverlay(int mouseX, int mouseY, float partialTicks) { } @@ -177,7 +179,9 @@ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScr super.mouseMovedOrUp(mouseX, mouseY, state); } - /** Called when divider is dragged. Override to update scroll sizes. */ + /** + * Called when divider is dragged. Override to update scroll sizes. + */ protected void onDividerMoved(int newOffset) { } @@ -192,15 +196,23 @@ public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScr // ===== ABSTRACT HOOKS (subclass must implement) ===== - /** Populate top bar buttons. topBtnY is the y-coordinate for the button row. */ + /** + * Populate top bar buttons. topBtnY is the y-coordinate for the button row. + */ protected abstract void initTopBar(int topBtnY); - /** Populate the left navigation panel (scrolls, search fields, buttons). */ + /** + * Populate the left navigation panel (scrolls, search fields, buttons). + */ protected abstract void initLeftPanel(); - /** Populate the center content panel (main scroll list). */ + /** + * Populate the center content panel (main scroll list). + */ protected abstract void initCenterPanel(); - /** Populate the right action panel. startY is the first button y-coordinate. */ + /** + * Populate the right action panel. startY is the first button y-coordinate. + */ protected abstract void initRightPanel(int startY); } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java b/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java index df6651521..37ce2b789 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiModelInterface2.java @@ -34,7 +34,7 @@ public class GuiModelInterface2 extends GuiNPCInterface2 { } /** - * @param npc The NPC for preview rendering + * @param npc The NPC for preview rendering * @param hasMenuNpc If false, the top menu tabs (Display, Stats, etc.) are hidden. * Use false when opened from the global menu without a real NPC. */ diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java index 60b4afdb6..4a1ac4b24 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcLabel.java @@ -60,12 +60,13 @@ public class GuiNpcLabel extends Gui { /** * Draws hover tooltip if the mouse is over this label. - * @param localMouseX Mouse X in scroll-local coordinates (for hit testing) - * @param localMouseY Mouse Y in scroll-local coordinates (for hit testing) + * + * @param localMouseX Mouse X in scroll-local coordinates (for hit testing) + * @param localMouseY Mouse Y in scroll-local coordinates (for hit testing) * @param screenMouseX Mouse X in screen coordinates (for tooltip positioning) * @param screenMouseY Mouse Y in screen coordinates (for tooltip positioning) - * @param hasSubGui Whether a sub-gui is open - * @param fr FontRenderer for measuring label width + * @param hasSubGui Whether a sub-gui is open + * @param fr FontRenderer for measuring label width */ public void drawHover(int localMouseX, int localMouseY, int screenMouseX, int screenMouseY, boolean hasSubGui, FontRenderer fr) { if (hasSubGui || hoverableText.isEmpty()) return; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java index a30b53fa8..e6e02d10e 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java @@ -1,17 +1,17 @@ package noppes.npcs.client.gui.util; +import kamkeel.npcs.util.TextSplitter; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiTextField; -import net.minecraft.util.StatCollector; import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.util.StatCollector; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; -import net.minecraft.client.renderer.RenderHelper; -import kamkeel.npcs.util.TextSplitter; + import java.util.List; -import java.util.Iterator; public class GuiNpcTextField extends GuiTextField { public boolean enabled = true; @@ -224,7 +224,7 @@ public class GuiNpcTextField extends GuiTextField { public boolean isMouseOver(int mouseX, int mouseY) { return mouseX >= xPosition && mouseX < xPosition + width && - mouseY >= yPosition && mouseY < yPosition + height; + mouseY >= yPosition && mouseY < yPosition + height; } public void drawHover(int mouseX, int mouseY, boolean hasSubGui) { @@ -334,14 +334,14 @@ public class GuiNpcTextField extends GuiTextField { } protected void drawGradientRect(int left, int top, int right, int bottom, int startColor, int endColor, int zLevel) { - float f = (float)(startColor >> 24 & 255) / 255.0F; - float f1 = (float)(startColor >> 16 & 255) / 255.0F; - float f2 = (float)(startColor >> 8 & 255) / 255.0F; - float f3 = (float)(startColor & 255) / 255.0F; - float f4 = (float)(endColor >> 24 & 255) / 255.0F; - float f5 = (float)(endColor >> 16 & 255) / 255.0F; - float f6 = (float)(endColor >> 8 & 255) / 255.0F; - float f7 = (float)(endColor & 255) / 255.0F; + float f = (float) (startColor >> 24 & 255) / 255.0F; + float f1 = (float) (startColor >> 16 & 255) / 255.0F; + float f2 = (float) (startColor >> 8 & 255) / 255.0F; + float f3 = (float) (startColor & 255) / 255.0F; + float f4 = (float) (endColor >> 24 & 255) / 255.0F; + float f5 = (float) (endColor >> 16 & 255) / 255.0F; + float f6 = (float) (endColor >> 8 & 255) / 255.0F; + float f7 = (float) (endColor & 255) / 255.0F; GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_BLEND); GL11.glDisable(GL11.GL_ALPHA_TEST); @@ -350,11 +350,11 @@ public class GuiNpcTextField extends GuiTextField { net.minecraft.client.renderer.Tessellator tessellator = net.minecraft.client.renderer.Tessellator.instance; tessellator.startDrawingQuads(); tessellator.setColorRGBA_F(f1, f2, f3, f); - tessellator.addVertex((double)right, (double)top, (double)zLevel); - tessellator.addVertex((double)left, (double)top, (double)zLevel); + tessellator.addVertex((double) right, (double) top, (double) zLevel); + tessellator.addVertex((double) left, (double) top, (double) zLevel); tessellator.setColorRGBA_F(f5, f6, f7, f4); - tessellator.addVertex((double)left, (double)bottom, (double)zLevel); - tessellator.addVertex((double)right, (double)bottom, (double)zLevel); + tessellator.addVertex((double) left, (double) bottom, (double) zLevel); + tessellator.addVertex((double) right, (double) bottom, (double) zLevel); tessellator.draw(); GL11.glShadeModel(GL11.GL_FLAT); GL11.glDisable(GL11.GL_BLEND); diff --git a/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java b/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java index db4989b56..60346560a 100644 --- a/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java +++ b/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java @@ -8,6 +8,8 @@ import java.util.UUID; public interface IClonerGui { int getShowingClones(); + HashMap getTags(); + TagMap getTagMap(); } diff --git a/src/main/java/noppes/npcs/config/ConfigMarket.java b/src/main/java/noppes/npcs/config/ConfigMarket.java index 2909ec2a6..976452d08 100644 --- a/src/main/java/noppes/npcs/config/ConfigMarket.java +++ b/src/main/java/noppes/npcs/config/ConfigMarket.java @@ -135,11 +135,11 @@ public class ConfigMarket { // ========================================= config.setCategoryComment(AUCTION_BLACKLIST, "Item Blacklist settings for the Auction House.\n" + - "Prevents specific items, mods, or items with certain NBT tags from being listed.\n\n" + - "ITEM FORMAT: Use 'modid:itemname' format (e.g., 'minecraft:bedrock', 'customnpcs:npcWand')\n" + - "WILDCARDS: Use * for wildcards (e.g., 'customnpcs:npc*' blocks all items starting with 'npc')\n" + - "MOD FORMAT: Use just the mod ID (e.g., 'projecte' blocks all items from that mod)\n" + - "NBT FORMAT: Use the NBT tag key name (e.g., 'AdminOnly' blocks items with that tag)"); + "Prevents specific items, mods, or items with certain NBT tags from being listed.\n\n" + + "ITEM FORMAT: Use 'modid:itemname' format (e.g., 'minecraft:bedrock', 'customnpcs:npcWand')\n" + + "WILDCARDS: Use * for wildcards (e.g., 'customnpcs:npc*' blocks all items starting with 'npc')\n" + + "MOD FORMAT: Use just the mod ID (e.g., 'projecte' blocks all items from that mod)\n" + + "NBT FORMAT: Use the NBT tag key name (e.g., 'AdminOnly' blocks items with that tag)"); BlacklistEnabled = config.get(AUCTION_BLACKLIST, "Enable Blacklist", true, "Enable item blacklist checking when creating listings").getBoolean(true); @@ -157,17 +157,17 @@ public class ConfigMarket { "customnpcs:npcSoulstoneFilled" }, "Items that cannot be listed on the Auction House.\n" + - "Format: modid:itemname (supports * wildcards)\n" + - "Example: 'minecraft:diamond_sword' or 'customnpcs:npc*'").getStringList(); + "Format: modid:itemname (supports * wildcards)\n" + + "Example: 'minecraft:diamond_sword' or 'customnpcs:npc*'").getStringList(); BlacklistedMods = config.get(AUCTION_BLACKLIST, "Blacklisted Mods", new String[]{}, "All items from these mods are blocked from the Auction House.\n" + - "Format: modid (e.g., 'projecte', 'thaumcraft')").getStringList(); + "Format: modid (e.g., 'projecte', 'thaumcraft')").getStringList(); BlacklistedNBTTags = config.get(AUCTION_BLACKLIST, "Blacklisted NBT Tags", new String[]{}, "Items containing any of these NBT tag keys are blocked.\n" + - "Checks the root level of the item's NBT compound.\n" + - "Example: 'AdminOnly', 'CreativeMode'").getStringList(); + "Checks the root level of the item's NBT compound.\n" + + "Example: 'AdminOnly', 'CreativeMode'").getStringList(); // ========================================= // Auction Logging Settings diff --git a/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java b/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java index 7e48589f4..6c6fdce44 100644 --- a/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java +++ b/src/main/java/noppes/npcs/constants/EnumAuctionLogAction.java @@ -16,19 +16,29 @@ public enum EnumAuctionLogAction { CLAIMED, CLAIM_EXPIRED; - /** Check if this action should be logged based on config settings */ + /** + * Check if this action should be logged based on config settings + */ public boolean shouldLog() { if (!ConfigMarket.AuctionLoggingEnabled) return false; switch (this) { - case CREATED: return ConfigMarket.LogAuctionCreated; - case BID: return ConfigMarket.LogAuctionBid; - case BUYOUT: return ConfigMarket.LogAuctionBuyout; - case SOLD: return ConfigMarket.LogAuctionSold; - case EXPIRED: return ConfigMarket.LogAuctionExpired; - case CANCELLED: return ConfigMarket.LogAuctionCancelled; + case CREATED: + return ConfigMarket.LogAuctionCreated; + case BID: + return ConfigMarket.LogAuctionBid; + case BUYOUT: + return ConfigMarket.LogAuctionBuyout; + case SOLD: + return ConfigMarket.LogAuctionSold; + case EXPIRED: + return ConfigMarket.LogAuctionExpired; + case CANCELLED: + return ConfigMarket.LogAuctionCancelled; case CLAIMED: - case CLAIM_EXPIRED: return ConfigMarket.LogAuctionClaimed; - default: return false; + case CLAIM_EXPIRED: + return ConfigMarket.LogAuctionClaimed; + default: + return false; } } } diff --git a/src/main/java/noppes/npcs/constants/EnumAuctionPage.java b/src/main/java/noppes/npcs/constants/EnumAuctionPage.java index c6c2c2a36..7f98cb2f0 100644 --- a/src/main/java/noppes/npcs/constants/EnumAuctionPage.java +++ b/src/main/java/noppes/npcs/constants/EnumAuctionPage.java @@ -1,7 +1,5 @@ package noppes.npcs.constants; -import noppes.npcs.constants.EnumGuiType; - /** * Enum for auction GUI page navigation. * Maps page indices to GUI types for packet-based navigation. diff --git a/src/main/java/noppes/npcs/containers/ContainerAuction.java b/src/main/java/noppes/npcs/containers/ContainerAuction.java index 2ccaa2e52..0741fdd9a 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuction.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuction.java @@ -71,6 +71,7 @@ public class ContainerAuction extends ContainerNpcInterface { /** * Get the ItemStack in a player inventory slot. + * * @param containerSlotIndex The container slot index (0-35 for player inv) */ public ItemStack getPlayerInventoryStack(int containerSlotIndex) { diff --git a/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java b/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java index 0e46b6b9b..2b6f9a67f 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuctionBidding.java @@ -28,9 +28,14 @@ public class ContainerAuctionBidding extends ContainerAuction { // Display slot (read-only) addSlotToContainer(new Slot(displayInventory, 0, ITEM_SLOT_X, ITEM_SLOT_Y) { @Override - public boolean isItemValid(ItemStack stack) { return false; } + public boolean isItemValid(ItemStack stack) { + return false; + } + @Override - public boolean canTakeStack(EntityPlayer player) { return false; } + public boolean canTakeStack(EntityPlayer player) { + return false; + } }); } diff --git a/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java b/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java index e5a01dd6f..f703a206d 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuctionSell.java @@ -27,9 +27,14 @@ public class ContainerAuctionSell extends ContainerAuction { // Sell slot blocks direct interaction addSlotToContainer(new Slot(sellInventory, 0, SELL_SLOT_X, SELL_SLOT_Y) { @Override - public boolean isItemValid(ItemStack stack) { return false; } + public boolean isItemValid(ItemStack stack) { + return false; + } + @Override - public boolean canTakeStack(EntityPlayer player) { return false; } + public boolean canTakeStack(EntityPlayer player) { + return false; + } }); } @@ -44,8 +49,9 @@ public class ContainerAuctionSell extends ContainerAuction { /** * Add items from inventory to sell slot. * Not limited by inventory - just stages items for sale. Validation on submit. + * * @param sourceSlot Inventory slot index - * @param fullStack True=full stack, False=1 item + * @param fullStack True=full stack, False=1 item */ public void addToSellSlot(int sourceSlot, boolean fullStack) { if (!isPlayerInventorySlot(sourceSlot)) return; @@ -78,6 +84,7 @@ public class ContainerAuctionSell extends ContainerAuction { /** * Remove items from sell slot. + * * @param removeAll True=clear slot, False=remove 1 item */ public void removeFromSellSlot(boolean removeAll) { @@ -91,12 +98,16 @@ public class ContainerAuctionSell extends ContainerAuction { } } - /** Check if sell slot is this container slot index */ + /** + * Check if sell slot is this container slot index + */ public boolean isSellSlot(int slotIndex) { return slotIndex == SELL_SLOT_INDEX; } - /** Check if two stacks match using NoppesUtilPlayer.compareItems (same item, damage, and NBT) */ + /** + * Check if two stacks match using NoppesUtilPlayer.compareItems (same item, damage, and NBT) + */ private boolean itemsMatch(ItemStack a, ItemStack b) { return NoppesUtilPlayer.compareItems(a, b, false, false); } diff --git a/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java b/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java index 43e91f428..187d50d13 100644 --- a/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java +++ b/src/main/java/noppes/npcs/containers/ContainerAuctionTrades.java @@ -5,8 +5,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import noppes.npcs.constants.EnumClaimType; -import noppes.npcs.controllers.AuctionController; import noppes.npcs.constants.EnumTradeSlotType; +import noppes.npcs.controllers.AuctionController; import noppes.npcs.controllers.data.AuctionClaim; import noppes.npcs.controllers.data.AuctionListing; import noppes.npcs.entity.EntityNPCInterface; @@ -55,7 +55,9 @@ public class ContainerAuctionTrades extends ContainerAuction { refreshData(); } - /** Refresh data from controller (server-side only) */ + /** + * Refresh data from controller (server-side only) + */ public void refreshData() { // Clear existing data clearData(); @@ -70,7 +72,9 @@ public class ContainerAuctionTrades extends ContainerAuction { populateDisplay(newListings, newBids, newClaims); } - /** Set data from NBT received from server (client-side) */ + /** + * Set data from NBT received from server (client-side) + */ public void setTradesData(NBTTagCompound compound) { // Clear existing data clearData(); @@ -111,7 +115,9 @@ public class ContainerAuctionTrades extends ContainerAuction { populateDisplay(newListings, newBids, newClaims); } - /** Clear all data and reset display */ + /** + * Clear all data and reset display + */ private void clearData() { activeListings.clear(); activeBids.clear(); @@ -125,7 +131,9 @@ public class ContainerAuctionTrades extends ContainerAuction { } } - /** Populate display from provided data lists */ + /** + * Populate display from provided data lists + */ private void populateDisplay(List newListings, List newBids, List allClaims) { activeListings = new ArrayList<>(newListings); activeBids = new ArrayList<>(newBids); @@ -218,7 +226,9 @@ public class ContainerAuctionTrades extends ContainerAuction { // ========== Slot Data Accessors ========== - /** Get listing at slot (or null if claim/empty) */ + /** + * Get listing at slot (or null if claim/empty) + */ public AuctionListing getListingAt(int slot) { if (slot < 0 || slot >= SLOT_COUNT) return null; EnumTradeSlotType type = slotTypes[slot]; @@ -233,7 +243,9 @@ public class ContainerAuctionTrades extends ContainerAuction { return null; } - /** Get claim at slot (or null if listing/empty) */ + /** + * Get claim at slot (or null if listing/empty) + */ public AuctionClaim getClaimAt(int slot) { if (slot < 0 || slot >= SLOT_COUNT) return null; if (slotTypes[slot] == EnumTradeSlotType.CLAIM) { @@ -245,17 +257,23 @@ public class ContainerAuctionTrades extends ContainerAuction { return null; } - /** Check if slot is player's active listing */ + /** + * Check if slot is player's active listing + */ public boolean isSellingAt(int slot) { return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == EnumTradeSlotType.SELLING; } - /** Check if slot is player's active bid */ + /** + * Check if slot is player's active bid + */ public boolean isBiddingAt(int slot) { return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == EnumTradeSlotType.BIDDING; } - /** Find listing matching item (searches both selling and bidding) */ + /** + * Find listing matching item (searches both selling and bidding) + */ public AuctionListing getListingForItem(ItemStack stack) { if (stack == null) return null; for (AuctionListing listing : activeListings) { @@ -267,7 +285,9 @@ public class ContainerAuctionTrades extends ContainerAuction { return null; } - /** Find listing in selling list */ + /** + * Find listing in selling list + */ public AuctionListing getSellingListingForItem(ItemStack stack) { if (stack == null) return null; for (AuctionListing listing : activeListings) { @@ -276,7 +296,9 @@ public class ContainerAuctionTrades extends ContainerAuction { return null; } - /** Find listing in bidding list */ + /** + * Find listing in bidding list + */ public AuctionListing getBiddingListingForItem(ItemStack stack) { if (stack == null) return null; for (AuctionListing bid : activeBids) { @@ -285,7 +307,9 @@ public class ContainerAuctionTrades extends ContainerAuction { return null; } - /** Find claim matching item */ + /** + * Find claim matching item + */ public AuctionClaim getClaimForItem(ItemStack stack) { if (stack == null) return null; for (AuctionClaim claim : claims) { @@ -296,14 +320,27 @@ public class ContainerAuctionTrades extends ContainerAuction { // ========== List Getters ========== - public List getActiveListings() { return activeListings; } - public List getActiveBids() { return activeBids; } - public List getClaims() { return claims; } - public int getTotalTradeCount() { return activeListings.size() + activeBids.size() + claims.size(); } + public List getActiveListings() { + return activeListings; + } + + public List getActiveBids() { + return activeBids; + } + + public List getClaims() { + return claims; + } + + public int getTotalTradeCount() { + return activeListings.size() + activeBids.size() + claims.size(); + } // ========== Slot Hiding (for pending operations) ========== - /** Hide slot for pending operation display */ + /** + * Hide slot for pending operation display + */ public void setHiddenSlot(int slot) { if (hiddenSlot >= 0 && hiddenSlot < SLOT_COUNT) { restoreSlotItem(hiddenSlot); @@ -314,7 +351,9 @@ public class ContainerAuctionTrades extends ContainerAuction { } } - /** Restore hidden slot */ + /** + * Restore hidden slot + */ public void clearHiddenSlot() { if (hiddenSlot >= 0 && hiddenSlot < SLOT_COUNT) { restoreSlotItem(hiddenSlot); @@ -322,7 +361,9 @@ public class ContainerAuctionTrades extends ContainerAuction { hiddenSlot = -1; } - /** Restore item at slot from data using slot tracking */ + /** + * Restore item at slot from data using slot tracking + */ private void restoreSlotItem(int slot) { if (slot < 0 || slot >= SLOT_COUNT) return; @@ -348,8 +389,14 @@ public class ContainerAuctionTrades extends ContainerAuction { } } - public int getHiddenSlot() { return hiddenSlot; } + public int getHiddenSlot() { + return hiddenSlot; + } - /** Get player's max trade slots (synced from server based on permissions) */ - public int getMaxTradeSlots() { return maxTradeSlots; } + /** + * Get player's max trade slots (synced from server based on permissions) + */ + public int getMaxTradeSlots() { + return maxTradeSlots; + } } diff --git a/src/main/java/noppes/npcs/controllers/AnimationController.java b/src/main/java/noppes/npcs/controllers/AnimationController.java index e7c29f324..bfbf978d2 100644 --- a/src/main/java/noppes/npcs/controllers/AnimationController.java +++ b/src/main/java/noppes/npcs/controllers/AnimationController.java @@ -22,14 +22,17 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; @@ -364,6 +367,7 @@ public class AnimationController implements IAnimationHandler { /** * Get a built-in animation by name. + * * @param name The animation name (case-insensitive) * @return The built-in animation, or null if not found */ @@ -395,6 +399,7 @@ public class AnimationController implements IAnimationHandler { /** * Check if an animation name is a built-in animation. + * * @param name The animation name to check * @return true if this is a built-in animation */ diff --git a/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java b/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java index 2b921aad6..dae214f5c 100644 --- a/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java +++ b/src/main/java/noppes/npcs/controllers/AuctionConfigSync.java @@ -7,7 +7,7 @@ import noppes.npcs.config.ConfigMarket; /** * Server-side utility for syncing auction configuration to clients. * Handles Vault integration for currency name resolution. - * + *

* This class is SERVER-SIDE ONLY. Client reads via AuctionClientConfig. */ public class AuctionConfigSync { @@ -15,7 +15,7 @@ public class AuctionConfigSync { /** * Write auction config to NBT for sending to client. * Called on server side when building login packet. - * + *

* Currency name is resolved from Vault if enabled and available, * otherwise falls back to ConfigMarket.CurrencyName. */ diff --git a/src/main/java/noppes/npcs/controllers/AuctionController.java b/src/main/java/noppes/npcs/controllers/AuctionController.java index 2d02cd9ce..7ea79baeb 100644 --- a/src/main/java/noppes/npcs/controllers/AuctionController.java +++ b/src/main/java/noppes/npcs/controllers/AuctionController.java @@ -6,7 +6,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; -import net.minecraft.server.MinecraftServer; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import noppes.npcs.CustomNpcs; @@ -23,12 +22,12 @@ import noppes.npcs.constants.EnumAuctionSort; import noppes.npcs.constants.EnumAuctionStatus; import noppes.npcs.constants.EnumClaimType; import noppes.npcs.constants.EnumNotificationType; -import noppes.npcs.controllers.data.PlayerDataScript; import noppes.npcs.controllers.data.AuctionBlacklist; import noppes.npcs.controllers.data.AuctionClaim; import noppes.npcs.controllers.data.AuctionFilter; import noppes.npcs.controllers.data.AuctionListing; import noppes.npcs.controllers.data.PlayerData; +import noppes.npcs.controllers.data.PlayerDataScript; import noppes.npcs.controllers.data.PlayerTradeData; import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.event.player.AuctionEvent; @@ -37,18 +36,23 @@ import noppes.npcs.util.CustomNPCsThreader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; /** * Main controller for the Auction system. - * + *

* Thread Safety: * - Uses ConcurrentHashMap for all data stores - no locking required * - Claims are stored in PlayerData, not here - reduces contention * - Saves are performed asynchronously on the CNPC+ thread - * + *

* Performance: * - Auction processing runs every 30 seconds (600 ticks) * - Save interval is every 30 seconds (600 ticks) @@ -65,29 +69,41 @@ public class AuctionController implements IAuctionHandler { // Player Indices for O(1) Lookups // ========================================= - /** Listings by seller UUID - for quick "my listings" lookups */ + /** + * Listings by seller UUID - for quick "my listings" lookups + */ private final Map> playerListingIds = new ConcurrentHashMap<>(); - /** Listings where player is current high bidder */ + /** + * Listings where player is current high bidder + */ private final Map> playerBidIds = new ConcurrentHashMap<>(); - /** Cached max trade slots per player - computed on login, cleared on logout */ + /** + * Cached max trade slots per player - computed on login, cleared on logout + */ private final Map playerMaxTradesCache = new ConcurrentHashMap<>(); // ========================================= // Threading & State // ========================================= - /** Flag indicating data needs to be saved */ + /** + * Flag indicating data needs to be saved + */ private final AtomicBoolean dirty = new AtomicBoolean(false); - /** Flag to prevent concurrent saves */ + /** + * Flag to prevent concurrent saves + */ private final AtomicBoolean saving = new AtomicBoolean(false); private String filePath = ""; private int tickCounter = 0; - /** Process auctions and save every 30 seconds (600 ticks) */ + /** + * Process auctions and save every 30 seconds (600 ticks) + */ private static final int TICK_INTERVAL = 600; public AuctionController() { @@ -375,7 +391,7 @@ public class AuctionController implements IAuctionHandler { logAuction(EnumAuctionLogAction.CANCELLED, listing.sellerName, listing.item.getDisplayName(), listing.currentBid, "Penalty: " + penalty + ", Bidder refunded: " + listing.highBidderName + - (isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : "")); + (isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : "")); } else { // No bids - just return item AuctionClaim itemClaim = AuctionClaim.createItemReturnedClaim( @@ -1126,12 +1142,16 @@ public class AuctionController implements IAuctionHandler { // Save / Load - Async on CNPC+ Thread // ========================================= - /** Mark data as dirty - will be saved on next save cycle */ + /** + * Mark data as dirty - will be saved on next save cycle + */ private void markDirty() { dirty.set(true); } - /** Save synchronously - used for server shutdown */ + /** + * Save synchronously - used for server shutdown + */ public void save() { if (!dirty.compareAndSet(true, false)) { return; // Nothing to save @@ -1139,7 +1159,9 @@ public class AuctionController implements IAuctionHandler { saveInternal(); } - /** Save asynchronously on CNPC+ thread */ + /** + * Save asynchronously on CNPC+ thread + */ public void saveAsync() { if (!dirty.compareAndSet(true, false)) { return; // Nothing to save @@ -1167,7 +1189,9 @@ public class AuctionController implements IAuctionHandler { }); } - /** Internal synchronous save */ + /** + * Internal synchronous save + */ private void saveInternal() { try { NBTTagCompound compound = writeToNBT(new NBTTagCompound()); @@ -1236,7 +1260,9 @@ public class AuctionController implements IAuctionHandler { } } - /** Rebuild all indices from loaded data */ + /** + * Rebuild all indices from loaded data + */ private void rebuildIndices() { playerListingIds.clear(); playerBidIds.clear(); @@ -1460,8 +1486,8 @@ public class AuctionController implements IAuctionHandler { // Remove ended/cancelled listings (claims are in PlayerData now) listings.entrySet().removeIf(entry -> entry.getValue().status == EnumAuctionStatus.CLAIMED || - entry.getValue().status == EnumAuctionStatus.ENDED || - entry.getValue().status == EnumAuctionStatus.CANCELLED); + entry.getValue().status == EnumAuctionStatus.ENDED || + entry.getValue().status == EnumAuctionStatus.CANCELLED); // Rebuild indices rebuildIndices(); diff --git a/src/main/java/noppes/npcs/controllers/ScriptConfigSync.java b/src/main/java/noppes/npcs/controllers/ScriptConfigSync.java index 5a9d32ed3..9e322ea10 100644 --- a/src/main/java/noppes/npcs/controllers/ScriptConfigSync.java +++ b/src/main/java/noppes/npcs/controllers/ScriptConfigSync.java @@ -5,7 +5,7 @@ import noppes.npcs.config.ConfigScript; /** * Server-side utility for syncing script configuration to clients. - * + *

* This class is SERVER-SIDE ONLY. Client reads via ScriptClientConfig. */ public class ScriptConfigSync { diff --git a/src/main/java/noppes/npcs/controllers/ScriptHookController.java b/src/main/java/noppes/npcs/controllers/ScriptHookController.java index 3b79637d8..dd8c18550 100644 --- a/src/main/java/noppes/npcs/controllers/ScriptHookController.java +++ b/src/main/java/noppes/npcs/controllers/ScriptHookController.java @@ -3,12 +3,11 @@ package noppes.npcs.controllers; import noppes.npcs.api.event.IAbilityEvent; import noppes.npcs.api.event.IAnimationEvent; import noppes.npcs.api.event.IAuctionEvent; -import noppes.npcs.api.event.IEnergyBarrierEvent; -import noppes.npcs.api.event.IPlayerAbilityEvent; import noppes.npcs.api.event.IBlockEvent; import noppes.npcs.api.event.ICustomGuiEvent; import noppes.npcs.api.event.ICustomNPCsEvent; import noppes.npcs.api.event.IDialogEvent; +import noppes.npcs.api.event.IEnergyBarrierEvent; import noppes.npcs.api.event.IEnergyProjectileEvent; import noppes.npcs.api.event.IFactionEvent; import noppes.npcs.api.event.IForgeEvent; @@ -16,6 +15,7 @@ import noppes.npcs.api.event.IItemEvent; import noppes.npcs.api.event.ILinkedItemEvent; import noppes.npcs.api.event.INpcEvent; import noppes.npcs.api.event.IPartyEvent; +import noppes.npcs.api.event.IPlayerAbilityEvent; import noppes.npcs.api.event.IPlayerEvent; import noppes.npcs.api.event.IProjectileEvent; import noppes.npcs.api.event.IQuestEvent; diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java b/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java index 5a950816c..1f4d331a4 100644 --- a/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java +++ b/src/main/java/noppes/npcs/controllers/data/AuctionBlacklist.java @@ -10,10 +10,10 @@ import noppes.npcs.config.ConfigMarket; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.regex.Pattern; -import java.util.Iterator; public class AuctionBlacklist { private static volatile Set blacklistedItems = new HashSet<>(); diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java b/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java index c76aece11..604ff1023 100644 --- a/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java +++ b/src/main/java/noppes/npcs/controllers/data/AuctionClaim.java @@ -33,7 +33,9 @@ public class AuctionClaim implements IAuctionClaim { this.otherPlayerName = ""; } - /** Create claim for winning an item (buyer won auction or buyout) */ + /** + * Create claim for winning an item (buyer won auction or buyout) + */ public static AuctionClaim createItemWonClaim(UUID playerUUID, String playerName, String listingId, ItemStack item) { AuctionClaim claim = new AuctionClaim(); claim.playerUUID = playerUUID; @@ -46,7 +48,9 @@ public class AuctionClaim implements IAuctionClaim { return claim; } - /** Create claim for returned item (seller's expired/cancelled listing) */ + /** + * Create claim for returned item (seller's expired/cancelled listing) + */ public static AuctionClaim createItemReturnedClaim(UUID playerUUID, String playerName, String listingId, ItemStack item) { AuctionClaim claim = new AuctionClaim(); claim.playerUUID = playerUUID; @@ -59,7 +63,9 @@ public class AuctionClaim implements IAuctionClaim { return claim; } - /** Create claim for seller's proceeds (stores item name for tooltip, not full item) */ + /** + * Create claim for seller's proceeds (stores item name for tooltip, not full item) + */ public static AuctionClaim createCurrencyClaim(UUID playerUUID, String playerName, String listingId, long currency, String itemName, String buyerName) { AuctionClaim claim = new AuctionClaim(); claim.playerUUID = playerUUID; @@ -74,7 +80,9 @@ public class AuctionClaim implements IAuctionClaim { return claim; } - /** Create claim for outbid refund (stores item for display and rebid option) */ + /** + * Create claim for outbid refund (stores item for display and rebid option) + */ public static AuctionClaim createRefundClaim(UUID playerUUID, String playerName, String listingId, long currency, String itemName, String outbidderName, ItemStack item) { AuctionClaim claim = new AuctionClaim(); claim.playerUUID = playerUUID; diff --git a/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java b/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java index 5018ec2e0..e6841fcea 100644 --- a/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java +++ b/src/main/java/noppes/npcs/controllers/data/AuctionFilter.java @@ -25,14 +25,18 @@ public class AuctionFilter { return searchText != null && !searchText.trim().isEmpty(); } - /** Normalize string for search (strip colors, lowercase, trim) */ + /** + * Normalize string for search (strip colors, lowercase, trim) + */ public static String normalizeForSearch(String input) { if (input == null || input.isEmpty()) return ""; String stripped = EnumChatFormatting.getTextWithoutFormattingCodes(input); return stripped != null ? stripped.toLowerCase().trim() : input.toLowerCase().trim(); } - /** Check if item name or seller name matches search text */ + /** + * Check if item name or seller name matches search text + */ public boolean matchesSearch(String itemName, String sellerName) { if (!hasSearchText()) return true; String normalizedItem = normalizeForSearch(itemName); @@ -41,12 +45,16 @@ public class AuctionFilter { return normalizedItem.contains(normalizedSearch) || normalizedSeller.contains(normalizedSearch); } - /** Legacy method - only checks item name */ + /** + * Legacy method - only checks item name + */ public boolean matchesSearch(String itemName) { return matchesSearch(itemName, ""); } - /** Advanced search - all words must be present in item name OR seller name */ + /** + * Advanced search - all words must be present in item name OR seller name + */ public boolean matchesSearchAdvanced(String itemName, String sellerName) { if (!hasSearchText()) return true; String normalizedItem = normalizeForSearch(itemName); @@ -63,7 +71,9 @@ public class AuctionFilter { return true; } - /** Legacy method - only checks item name */ + /** + * Legacy method - only checks item name + */ public boolean matchesSearchAdvanced(String itemName) { return matchesSearchAdvanced(itemName, ""); } @@ -101,13 +111,20 @@ public class AuctionFilter { } // Getters and setters - public String getSearchText() { return searchText; } + public String getSearchText() { + return searchText; + } public void setSearchText(String searchText) { this.searchText = searchText; this.normalizedSearch = normalizeForSearch(searchText); } - public EnumAuctionSort getSortBy() { return sortBy; } - public void setSortBy(EnumAuctionSort sortBy) { this.sortBy = sortBy; } + public EnumAuctionSort getSortBy() { + return sortBy; + } + + public void setSortBy(EnumAuctionSort sortBy) { + this.sortBy = sortBy; + } } diff --git a/src/main/java/noppes/npcs/controllers/data/BuiltInAnimation.java b/src/main/java/noppes/npcs/controllers/data/BuiltInAnimation.java index a30923eac..50ef13c0e 100644 --- a/src/main/java/noppes/npcs/controllers/data/BuiltInAnimation.java +++ b/src/main/java/noppes/npcs/controllers/data/BuiltInAnimation.java @@ -23,6 +23,7 @@ public class BuiltInAnimation extends Animation { /** * Built-in animations cannot be saved. + * * @return this animation unchanged */ @Override @@ -33,6 +34,7 @@ public class BuiltInAnimation extends Animation { /** * Built-in animations have no valid ID. + * * @return always -1 */ @Override @@ -50,6 +52,7 @@ public class BuiltInAnimation extends Animation { /** * Check if this is a built-in animation. + * * @return always true for BuiltInAnimation */ public boolean isBuiltIn() { diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index d3add842c..a11483bc0 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -1,12 +1,14 @@ package noppes.npcs.controllers.data; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.IAbilityAction; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; +import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket; +import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import net.minecraft.entity.EntityLivingBase; @@ -17,17 +19,14 @@ import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagString; import net.minecraft.util.DamageSource; import noppes.npcs.AbstractDataAbilities; -import noppes.npcs.LogWriter; import noppes.npcs.EventHooks; +import noppes.npcs.LogWriter; import noppes.npcs.api.ability.IPlayerAbilityData; import noppes.npcs.api.entity.IPlayer; import noppes.npcs.controllers.ScriptController; import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.event.player.PlayerAbilityEvent; -import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket; -import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -152,7 +151,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA @Override protected void fireInterruptEvent(Ability ability, EntityLivingBase target, - DamageSource source, float damage) { + DamageSource source, float damage) { EntityPlayer player = playerData.player; if (ScriptController.Instance == null || player == null) return; PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player); @@ -310,7 +309,9 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA return activateAbility(player, key); } - /** Prefix used to identify chained ability keys in the unlocked list. */ + /** + * Prefix used to identify chained ability keys in the unlocked list. + */ public static final String CHAIN_PREFIX = "chain:"; /** diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerData.java b/src/main/java/noppes/npcs/controllers/data/PlayerData.java index 7f326a344..9052811cf 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerData.java @@ -18,13 +18,13 @@ import noppes.npcs.LogWriter; import noppes.npcs.api.ability.IPlayerAbilityData; import noppes.npcs.api.entity.ICustomNpc; import noppes.npcs.api.handler.IPlayerBankData; -import noppes.npcs.api.handler.IPlayerTradeData; import noppes.npcs.api.handler.IPlayerData; import noppes.npcs.api.handler.IPlayerDialogData; import noppes.npcs.api.handler.IPlayerFactionData; import noppes.npcs.api.handler.IPlayerItemGiverData; import noppes.npcs.api.handler.IPlayerMailData; import noppes.npcs.api.handler.IPlayerQuestData; +import noppes.npcs.api.handler.IPlayerTradeData; import noppes.npcs.api.handler.IPlayerTransportData; import noppes.npcs.config.ConfigMain; import noppes.npcs.constants.EnumRoleType; @@ -47,8 +47,6 @@ import java.io.FileOutputStream; import java.util.HashSet; import java.util.UUID; -import static noppes.npcs.util.CustomNPCsThreader.customNPCThread; - public class PlayerData implements IExtendedEntityProperties, IPlayerData { public PlayerDialogData dialogData = new PlayerDialogData(this); public PlayerBankData bankData = new PlayerBankData(this); 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 cb2af9454..cfa80afc6 100644 --- a/src/main/java/noppes/npcs/controllers/data/action/Action.java +++ b/src/main/java/noppes/npcs/controllers/data/action/Action.java @@ -345,10 +345,10 @@ public class Action implements IAction { try { onStart.accept(this); } catch (Throwable t) { - String err = "IAction" + getName() + " onStart threw an exception:"; + String err = "IAction" + getName() + " onStart threw an exception:"; if (reportTo != null) reportTo.appendConsole(err + "\n" + ExceptionUtils.getStackTrace(t)); - + manager.LOGGER.error("onStart Task of " + this + " threw an exception:", t); } @@ -384,7 +384,7 @@ public class Action implements IAction { try { onDone.accept(this); } catch (Throwable t) { - String err = "IAction" + getName() + " onDone threw an exception:"; + String err = "IAction" + getName() + " onDone threw an exception:"; if (reportTo != null) reportTo.appendConsole(err + "\n" + ExceptionUtils.getStackTrace(t)); diff --git a/src/main/java/noppes/npcs/controllers/data/action/action/ConditionalAction.java b/src/main/java/noppes/npcs/controllers/data/action/action/ConditionalAction.java index 427749e33..035003cfe 100644 --- a/src/main/java/noppes/npcs/controllers/data/action/action/ConditionalAction.java +++ b/src/main/java/noppes/npcs/controllers/data/action/action/ConditionalAction.java @@ -134,7 +134,7 @@ public class ConditionalAction extends Action implements IConditionalAction { String err = "IConditionalAction '" + getName() + "' threw an exception:"; if (reportTo != null) reportTo.appendConsole(err + "\n" + ExceptionUtils.getStackTrace(t)); - + manager.LOGGER.error("Task of " + this + " threw an exception:", t); markDone(); } @@ -151,7 +151,7 @@ public class ConditionalAction extends Action implements IConditionalAction { try { onTermination.accept(this); } catch (Throwable t) { - String err = "IConditionalAction" + getName() + " onTermination threw an exception:"; + String err = "IConditionalAction" + getName() + " onTermination threw an exception:"; if (reportTo != null) reportTo.appendConsole(err + "\n" + ExceptionUtils.getStackTrace(t)); diff --git a/src/main/java/noppes/npcs/items/ItemStaff.java b/src/main/java/noppes/npcs/items/ItemStaff.java index 2a54c49a3..5891df2e1 100644 --- a/src/main/java/noppes/npcs/items/ItemStaff.java +++ b/src/main/java/noppes/npcs/items/ItemStaff.java @@ -1,7 +1,13 @@ package noppes.npcs.items; import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; +import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; import kamkeel.npcs.entity.EntityAbilityOrb; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -22,7 +28,7 @@ import noppes.npcs.entity.EntityProjectile; import noppes.npcs.util.IProjectileCallback; import org.lwjgl.opengl.GL11; -import java.awt.*; +import java.awt.Color; public class ItemStaff extends ItemNpcInterface implements IProjectileCallback { @@ -33,7 +39,8 @@ public class ItemStaff extends ItemNpcInterface implements IProjectileCallback { public ItemStaff(int par1, EnumNpcToolMaterial material) { super(par1); this.material = material; - this.color = OrbColor.get(material.ordinal());; + this.color = OrbColor.get(material.ordinal()); + ; setCreativeTab(CustomItems.tabWeapon); } diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index d1d041c8e..aebc9321d 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -3,10 +3,10 @@ package noppes.npcs.scripted; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.eventhandler.EventBus; import foxz.command.ScriptedCommand; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.TelegraphController; -import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; import kamkeel.npcs.entity.EntityAbilityLaser; @@ -58,9 +58,9 @@ import noppes.npcs.api.IContainer; import noppes.npcs.api.IDamageSource; import noppes.npcs.api.INbt; import noppes.npcs.api.IParticle; -import noppes.npcs.api.ITelegraph; import noppes.npcs.api.IPos; import noppes.npcs.api.ISkinOverlay; +import noppes.npcs.api.ITelegraph; import noppes.npcs.api.ITileEntity; import noppes.npcs.api.IWorld; import noppes.npcs.api.entity.ICustomNpc; @@ -70,8 +70,8 @@ import noppes.npcs.api.gui.ICustomGui; import noppes.npcs.api.handler.IAbilityHandler; import noppes.npcs.api.handler.IActionManager; import noppes.npcs.api.handler.IAnimationHandler; -import noppes.npcs.api.handler.IAuctionHandler; import noppes.npcs.api.handler.IAttributeHandler; +import noppes.npcs.api.handler.IAuctionHandler; import noppes.npcs.api.handler.ICloneHandler; import noppes.npcs.api.handler.ICustomEffectHandler; import noppes.npcs.api.handler.IDialogHandler; diff --git a/src/main/java/noppes/npcs/scripted/ScriptWorld.java b/src/main/java/noppes/npcs/scripted/ScriptWorld.java index b827315d2..5bbe3909a 100644 --- a/src/main/java/noppes/npcs/scripted/ScriptWorld.java +++ b/src/main/java/noppes/npcs/scripted/ScriptWorld.java @@ -1,5 +1,9 @@ package noppes.npcs.scripted; +import kamkeel.npcs.entity.EntityAbilityBeam; +import kamkeel.npcs.entity.EntityAbilityDisc; +import kamkeel.npcs.entity.EntityAbilityLaser; +import kamkeel.npcs.entity.EntityAbilityOrb; import net.minecraft.block.Block; import net.minecraft.block.BlockAir; import net.minecraft.entity.Entity; @@ -21,10 +25,6 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.world.BlockEvent; -import kamkeel.npcs.entity.EntityAbilityBeam; -import kamkeel.npcs.entity.EntityAbilityDisc; -import kamkeel.npcs.entity.EntityAbilityLaser; -import kamkeel.npcs.entity.EntityAbilityOrb; import noppes.npcs.CustomNpcs; import noppes.npcs.NoppesUtilServer; import noppes.npcs.api.IBlock; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java index b84c2bf4b..86bae44b3 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java @@ -29,40 +29,95 @@ public abstract class ScriptEnergyAbility extends // ==================== DISPLAY ==================== - public int getInnerColor() { return entity.getInnerColor(); } - public void setInnerColor(int color) { entity.setInnerColor(color); } + public int getInnerColor() { + return entity.getInnerColor(); + } - public float getInnerAlpha() { return entity.getInnerAlpha(); } - public void setInnerAlpha(float alpha) { entity.setInnerAlpha(alpha); } + public void setInnerColor(int color) { + entity.setInnerColor(color); + } - public int getOuterColor() { return entity.getOuterColor(); } - public void setOuterColor(int color) { entity.setOuterColor(color); } + public float getInnerAlpha() { + return entity.getInnerAlpha(); + } - public boolean isOuterColorEnabled() { return entity.isOuterColorEnabled(); } - public void setOuterColorEnabled(boolean enabled) { entity.setOuterColorEnabled(enabled); } + public void setInnerAlpha(float alpha) { + entity.setInnerAlpha(alpha); + } - public float getOuterColorWidth() { return entity.getOuterColorWidth(); } - public void setOuterColorWidth(float width) { entity.setOuterColorWidth(width); } + public int getOuterColor() { + return entity.getOuterColor(); + } - public float getOuterColorAlpha() { return entity.getOuterColorAlpha(); } - public void setOuterColorAlpha(float alpha) { entity.setOuterColorAlpha(alpha); } + public void setOuterColor(int color) { + entity.setOuterColor(color); + } + + public boolean isOuterColorEnabled() { + return entity.isOuterColorEnabled(); + } + + public void setOuterColorEnabled(boolean enabled) { + entity.setOuterColorEnabled(enabled); + } + + public float getOuterColorWidth() { + return entity.getOuterColorWidth(); + } + + public void setOuterColorWidth(float width) { + entity.setOuterColorWidth(width); + } + + public float getOuterColorAlpha() { + return entity.getOuterColorAlpha(); + } + + public void setOuterColorAlpha(float alpha) { + entity.setOuterColorAlpha(alpha); + } // ==================== LIGHTNING ==================== - public boolean hasLightningEffect() { return entity.hasLightningEffect(); } - public void setLightningEffect(boolean enabled) { entity.setLightningEffect(enabled); } + public boolean hasLightningEffect() { + return entity.hasLightningEffect(); + } - public float getLightningDensity() { return entity.getLightningDensity(); } - public void setLightningDensity(float density) { entity.setLightningDensity(density); } + public void setLightningEffect(boolean enabled) { + entity.setLightningEffect(enabled); + } - public float getLightningRadius() { return entity.getLightningRadius(); } - public void setLightningRadius(float radius) { entity.setLightningRadius(radius); } + public float getLightningDensity() { + return entity.getLightningDensity(); + } - public int getLightningFadeTime() { return entity.getLightningFadeTime(); } - public void setLightningFadeTime(int ticks) { entity.setLightningFadeTime(ticks); } + public void setLightningDensity(float density) { + entity.setLightningDensity(density); + } + + public float getLightningRadius() { + return entity.getLightningRadius(); + } + + public void setLightningRadius(float radius) { + entity.setLightningRadius(radius); + } + + public int getLightningFadeTime() { + return entity.getLightningFadeTime(); + } + + public void setLightningFadeTime(int ticks) { + entity.setLightningFadeTime(ticks); + } // ==================== STATE ==================== - public boolean isCharging() { return entity.isCharging(); } - public float getChargeProgress() { return entity.getChargeProgress(); } + public boolean isCharging() { + return entity.isCharging(); + } + + public float getChargeProgress() { + return entity.getChargeProgress(); + } } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java index 9455b28cd..29f330ff7 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBarrier.java @@ -81,17 +81,37 @@ public class ScriptEnergyBarrier extends ScriptEn // ==================== BARRIER ==================== - public float getDefaultMultiplier() { return entity.getBarrierData().defaultMultiplier; } - public void setDefaultMultiplier(float multiplier) { entity.getBarrierData().defaultMultiplier = multiplier; } + public float getDefaultMultiplier() { + return entity.getBarrierData().defaultMultiplier; + } - public boolean isKnockbackEnabled() { return entity.getBarrierData().knockbackEnabled; } - public void setKnockbackEnabled(boolean enabled) { entity.getBarrierData().knockbackEnabled = enabled; } + public void setDefaultMultiplier(float multiplier) { + entity.getBarrierData().defaultMultiplier = multiplier; + } - public float getKnockbackStrength() { return entity.getBarrierData().knockbackStrength; } - public void setKnockbackStrength(float strength) { entity.getBarrierData().knockbackStrength = strength; } + public boolean isKnockbackEnabled() { + return entity.getBarrierData().knockbackEnabled; + } - public int getKnockbackTarget() { return entity.getBarrierData().knockbackTarget; } - public void setKnockbackTarget(int target) { entity.getBarrierData().knockbackTarget = target; } + public void setKnockbackEnabled(boolean enabled) { + entity.getBarrierData().knockbackEnabled = enabled; + } + + public float getKnockbackStrength() { + return entity.getBarrierData().knockbackStrength; + } + + public void setKnockbackStrength(float strength) { + entity.getBarrierData().knockbackStrength = strength; + } + + public int getKnockbackTarget() { + return entity.getBarrierData().knockbackTarget; + } + + public void setKnockbackTarget(int target) { + entity.getBarrierData().knockbackTarget = target; + } public int getBarrierType() { return entity instanceof EntityEnergyDome ? 0 : 1; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java index b42a32b1d..9c5a8b8be 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyBeam.java @@ -29,16 +29,33 @@ public class ScriptEnergyBeam extends ScriptEnergyP // ==================== BEAM-SPECIFIC ==================== - public float getBeamWidth() { return entity.getBeamWidth(); } - public void setBeamWidth(float width) { entity.setBeamWidth(width); } + public float getBeamWidth() { + return entity.getBeamWidth(); + } - public float getHeadSize() { return entity.getHeadSize(); } - public void setHeadSize(float size) { entity.setHeadSize(size); } + public void setBeamWidth(float width) { + entity.setBeamWidth(width); + } - public boolean isAttachedToOwner() { return entity.isAttachedToOwner(); } - public void setAttachedToOwner(boolean attached) { entity.setAttachedToOwner(attached); } + public float getHeadSize() { + return entity.getHeadSize(); + } - public boolean shouldRenderTailOrb() { return entity.shouldRenderTailOrb(); } + public void setHeadSize(float size) { + entity.setHeadSize(size); + } + + public boolean isAttachedToOwner() { + return entity.isAttachedToOwner(); + } + + public void setAttachedToOwner(boolean attached) { + entity.setAttachedToOwner(attached); + } + + public boolean shouldRenderTailOrb() { + return entity.shouldRenderTailOrb(); + } // ==================== FIRE ==================== diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java index 62dde7979..c02fdad31 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDisc.java @@ -29,22 +29,49 @@ public class ScriptEnergyDisc extends ScriptEnergyP // ==================== DISC-SPECIFIC ==================== - public float getDiscRadius() { return entity.getDiscRadius(); } - public void setDiscRadius(float radius) { entity.setDiscRadius(radius); } + public float getDiscRadius() { + return entity.getDiscRadius(); + } - public float getDiscThickness() { return entity.getDiscThickness(); } - public void setDiscThickness(float thickness) { entity.setDiscThickness(thickness); } + public void setDiscRadius(float radius) { + entity.setDiscRadius(radius); + } - public boolean isVertical() { return entity.isVertical(); } - public void setVertical(boolean vertical) { entity.setVertical(vertical); } + public float getDiscThickness() { + return entity.getDiscThickness(); + } - public boolean isBoomerang() { return entity.isBoomerang(); } - public void setBoomerang(boolean boomerang) { entity.setBoomerang(boomerang); } + public void setDiscThickness(float thickness) { + entity.setDiscThickness(thickness); + } - public int getBoomerangDelay() { return entity.getBoomerangDelay(); } - public void setBoomerangDelay(int ticks) { entity.setBoomerangDelay(ticks); } + public boolean isVertical() { + return entity.isVertical(); + } - public boolean isReturning() { return entity.isReturning(); } + public void setVertical(boolean vertical) { + entity.setVertical(vertical); + } + + public boolean isBoomerang() { + return entity.isBoomerang(); + } + + public void setBoomerang(boolean boomerang) { + entity.setBoomerang(boomerang); + } + + public int getBoomerangDelay() { + return entity.getBoomerangDelay(); + } + + public void setBoomerangDelay(int ticks) { + entity.setBoomerangDelay(ticks); + } + + public boolean isReturning() { + return entity.isReturning(); + } // ==================== FIRE ==================== diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java index 8d331c170..e022118f7 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyDome.java @@ -27,6 +27,11 @@ public class ScriptEnergyDome extends ScriptEnergyBa // ==================== DOME-SPECIFIC ==================== - public float getDomeRadius() { return entity.getDomeRadius(); } - public void setDomeRadius(float radius) { entity.setDomeRadius(radius); } + public float getDomeRadius() { + return entity.getDomeRadius(); + } + + public void setDomeRadius(float radius) { + entity.setDomeRadius(radius); + } } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java index 976fd948d..3963794fc 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyLaser.java @@ -29,31 +29,77 @@ public class ScriptEnergyLaser extends ScriptEnerg // ==================== LASER-SPECIFIC ==================== - public float getLaserWidth() { return entity.getLaserWidth(); } - public void setLaserWidth(float width) { entity.setLaserWidth(width); } + public float getLaserWidth() { + return entity.getLaserWidth(); + } - public float getExpansionSpeed() { return entity.getExpansionSpeed(); } - public void setExpansionSpeed(float speed) { entity.setExpansionSpeed(speed); } + public void setLaserWidth(float width) { + entity.setLaserWidth(width); + } - public int getLingerTicks() { return entity.getLingerTicks(); } - public void setLingerTicks(int ticks) { entity.setLingerTicks(ticks); } + public float getExpansionSpeed() { + return entity.getExpansionSpeed(); + } - public float getCurrentLength() { return entity.getCurrentLength(); } - public boolean isFullyExtended() { return entity.isFullyExtended(); } + public void setExpansionSpeed(float speed) { + entity.setExpansionSpeed(speed); + } - public double getDirX() { return entity.getDirX(); } - public double getDirY() { return entity.getDirY(); } - public double getDirZ() { return entity.getDirZ(); } - public void setDirection(double x, double y, double z) { entity.setDirection(x, y, z); } + public int getLingerTicks() { + return entity.getLingerTicks(); + } - public double getEndX() { return entity.getEndX(); } - public double getEndY() { return entity.getEndY(); } - public double getEndZ() { return entity.getEndZ(); } + public void setLingerTicks(int ticks) { + entity.setLingerTicks(ticks); + } - public float getLingerAlpha() { return entity.getLingerAlpha(); } + public float getCurrentLength() { + return entity.getCurrentLength(); + } - public boolean isLockVerticalDirection() { return entity.isLockVerticalDirection(); } - public void setLockVerticalDirection(boolean lock) { entity.setLockVerticalDirection(lock); } + public boolean isFullyExtended() { + return entity.isFullyExtended(); + } + + public double getDirX() { + return entity.getDirX(); + } + + public double getDirY() { + return entity.getDirY(); + } + + public double getDirZ() { + return entity.getDirZ(); + } + + public void setDirection(double x, double y, double z) { + entity.setDirection(x, y, z); + } + + public double getEndX() { + return entity.getEndX(); + } + + public double getEndY() { + return entity.getEndY(); + } + + public double getEndZ() { + return entity.getEndZ(); + } + + public float getLingerAlpha() { + return entity.getLingerAlpha(); + } + + public boolean isLockVerticalDirection() { + return entity.isLockVerticalDirection(); + } + + public void setLockVerticalDirection(boolean lock) { + entity.setLockVerticalDirection(lock); + } // ==================== FIRE ==================== diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java index 5da93dc4a..04c730fad 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyPanel.java @@ -27,15 +27,31 @@ public class ScriptEnergyPanel extends ScriptEnergy // ==================== PANEL-SPECIFIC ==================== - public float getPanelWidth() { return entity.getPanelData().panelWidth; } - public void setPanelWidth(float width) { entity.getPanelData().setPanelWidth(width); } + public float getPanelWidth() { + return entity.getPanelData().panelWidth; + } - public float getPanelHeight() { return entity.getPanelData().panelHeight; } - public void setPanelHeight(float height) { entity.getPanelData().setPanelHeight(height); } + public void setPanelWidth(float width) { + entity.getPanelData().setPanelWidth(width); + } - public float getPanelYaw() { return entity.getPanelYaw(); } + public float getPanelHeight() { + return entity.getPanelData().panelHeight; + } - public int getPanelMode() { return entity.getMode().ordinal(); } + public void setPanelHeight(float height) { + entity.getPanelData().setPanelHeight(height); + } - public boolean isLaunched() { return entity.getMode() == EntityEnergyPanel.PanelMode.LAUNCHED; } + public float getPanelYaw() { + return entity.getPanelYaw(); + } + + public int getPanelMode() { + return entity.getMode().ordinal(); + } + + public boolean isLaunched() { + return entity.getMode() == EntityEnergyPanel.PanelMode.LAUNCHED; + } } diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java index 9da13936f..38a95db0c 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java @@ -3,10 +3,10 @@ package noppes.npcs.scripted.entity; import kamkeel.npcs.entity.EntityEnergyProjectile; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import noppes.npcs.EventHooks; import noppes.npcs.api.entity.IEnergyProjectile; import noppes.npcs.api.entity.IEntity; import noppes.npcs.api.entity.IEntityLivingBase; -import noppes.npcs.EventHooks; import noppes.npcs.scripted.constants.EntityType; public class ScriptEnergyProjectile extends ScriptEnergyAbility implements IEnergyProjectile { @@ -67,74 +67,171 @@ public class ScriptEnergyProjectile extends Sc // ==================== DISPLAY (PROJECTILE-SPECIFIC) ==================== - public float getRotationSpeed() { return entity.getRotationSpeed(); } - public void setRotationSpeed(float speed) { entity.setRotationSpeed(speed); } + public float getRotationSpeed() { + return entity.getRotationSpeed(); + } + + public void setRotationSpeed(float speed) { + entity.setRotationSpeed(speed); + } // ==================== INTERPOLATION ==================== - public float getInterpolatedRotationX(float partialTicks) { return entity.getInterpolatedRotationX(partialTicks); } - public float getInterpolatedRotationY(float partialTicks) { return entity.getInterpolatedRotationY(partialTicks); } - public float getInterpolatedRotationZ(float partialTicks) { return entity.getInterpolatedRotationZ(partialTicks); } - public float getInterpolatedSize(float partialTicks) { return entity.getInterpolatedSize(partialTicks); } + public float getInterpolatedRotationX(float partialTicks) { + return entity.getInterpolatedRotationX(partialTicks); + } + + public float getInterpolatedRotationY(float partialTicks) { + return entity.getInterpolatedRotationY(partialTicks); + } + + public float getInterpolatedRotationZ(float partialTicks) { + return entity.getInterpolatedRotationZ(partialTicks); + } + + public float getInterpolatedSize(float partialTicks) { + return entity.getInterpolatedSize(partialTicks); + } // ==================== LIFESPAN ==================== - public float getMaxDistance() { return entity.getMaxDistance(); } - public void setMaxDistance(float distance) { entity.setMaxDistance(distance); } + public float getMaxDistance() { + return entity.getMaxDistance(); + } - public int getMaxLifetime() { return entity.getMaxLifetime(); } - public void setMaxLifetime(int ticks) { entity.setMaxLifetime(ticks); } + public void setMaxDistance(float distance) { + entity.setMaxDistance(distance); + } + + public int getMaxLifetime() { + return entity.getMaxLifetime(); + } + + public void setMaxLifetime(int ticks) { + entity.setMaxLifetime(ticks); + } // ==================== COMBAT ==================== - public float getDamage() { return entity.getDamage(); } - public void setDamage(float damage) { entity.setCombatDamage(damage); } + public float getDamage() { + return entity.getDamage(); + } - public float getKnockback() { return entity.getKnockback(); } - public void setKnockback(float knockback) { entity.setCombatKnockback(knockback); } + public void setDamage(float damage) { + entity.setCombatDamage(damage); + } - public float getKnockbackUp() { return entity.getKnockbackUp(); } - public void setKnockbackUp(float knockbackUp) { entity.setCombatKnockbackUp(knockbackUp); } + public float getKnockback() { + return entity.getKnockback(); + } - public boolean isExplosive() { return entity.isExplosive(); } - public void setExplosive(boolean explosive) { entity.setExplosive(explosive); } + public void setKnockback(float knockback) { + entity.setCombatKnockback(knockback); + } - public float getExplosionRadius() { return entity.getExplosionRadius(); } - public void setExplosionRadius(float radius) { entity.setExplosionRadius(radius); } + public float getKnockbackUp() { + return entity.getKnockbackUp(); + } - public float getExplosionDamageFalloff() { return entity.getExplosionDamageFalloff(); } - public void setExplosionDamageFalloff(float falloff) { entity.setExplosionDamageFalloff(falloff); } + public void setKnockbackUp(float knockbackUp) { + entity.setCombatKnockbackUp(knockbackUp); + } + + public boolean isExplosive() { + return entity.isExplosive(); + } + + public void setExplosive(boolean explosive) { + entity.setExplosive(explosive); + } + + public float getExplosionRadius() { + return entity.getExplosionRadius(); + } + + public void setExplosionRadius(float radius) { + entity.setExplosionRadius(radius); + } + + public float getExplosionDamageFalloff() { + return entity.getExplosionDamageFalloff(); + } + + public void setExplosionDamageFalloff(float falloff) { + entity.setExplosionDamageFalloff(falloff); + } // ==================== MOVEMENT ==================== - public float getSpeed() { return entity.getSpeed(); } - public void setSpeed(float speed) { entity.setSpeed(speed); } + public float getSpeed() { + return entity.getSpeed(); + } - public boolean isHoming() { return entity.isHoming(); } - public void setHoming(boolean homing) { entity.setHomingEnabled(homing); } + public void setSpeed(float speed) { + entity.setSpeed(speed); + } - public float getHomingStrength() { return entity.getHomingStrength(); } - public void setHomingStrength(float strength) { entity.setHomingStrength(strength); } + public boolean isHoming() { + return entity.isHoming(); + } - public float getHomingRange() { return entity.getHomingRange(); } - public void setHomingRange(float range) { entity.setHomingRange(range); } + public void setHoming(boolean homing) { + entity.setHomingEnabled(homing); + } + + public float getHomingStrength() { + return entity.getHomingStrength(); + } + + public void setHomingStrength(float strength) { + entity.setHomingStrength(strength); + } + + public float getHomingRange() { + return entity.getHomingRange(); + } + + public void setHomingRange(float range) { + entity.setHomingRange(range); + } // ==================== ANCHOR ==================== - public int getAnchor() { return entity.getAnchor(); } - public float getAnchorOffsetX() { return entity.getAnchorOffsetX(); } - public float getAnchorOffsetY() { return entity.getAnchorOffsetY(); } - public float getAnchorOffsetZ() { return entity.getAnchorOffsetZ(); } + public int getAnchor() { + return entity.getAnchor(); + } + + public float getAnchorOffsetX() { + return entity.getAnchorOffsetX(); + } + + public float getAnchorOffsetY() { + return entity.getAnchorOffsetY(); + } + + public float getAnchorOffsetZ() { + return entity.getAnchorOffsetZ(); + } // ==================== POSITION ==================== - public double getStartX() { return entity.getStartX(); } - public double getStartY() { return entity.getStartY(); } - public double getStartZ() { return entity.getStartZ(); } + public double getStartX() { + return entity.getStartX(); + } + + public double getStartY() { + return entity.getStartY(); + } + + public double getStartZ() { + return entity.getStartZ(); + } // ==================== STATE ==================== - public boolean hasHit() { return entity.getHasHit(); } + public boolean hasHit() { + return entity.getHasHit(); + } // ==================== FIRE ==================== diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java index 270266123..f0105cdd9 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java @@ -29,14 +29,29 @@ public class ScriptEnergySlicer extends ScriptEner // ==================== SLICER-SPECIFIC ==================== - public float getSliceWidth() { return entity.getSliceWidth(); } - public void setSliceWidth(float width) { entity.setSliceWidth(width); } + public float getSliceWidth() { + return entity.getSliceWidth(); + } - public float getSliceThickness() { return entity.getSliceThickness(); } - public void setSliceThickness(float thickness) { entity.setSliceThickness(thickness); } + public void setSliceWidth(float width) { + entity.setSliceWidth(width); + } - public boolean isPiercing() { return entity.isPiercing(); } - public void setPiercing(boolean piercing) { entity.setPiercing(piercing); } + public float getSliceThickness() { + return entity.getSliceThickness(); + } + + public void setSliceThickness(float thickness) { + entity.setSliceThickness(thickness); + } + + public boolean isPiercing() { + return entity.isPiercing(); + } + + public void setPiercing(boolean piercing) { + entity.setPiercing(piercing); + } // ==================== FIRE ==================== diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java index 78134a3fc..25734b7e4 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java @@ -1,5 +1,6 @@ package noppes.npcs.scripted.entity; +import kamkeel.npcs.entity.EntityEnergyProjectile; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import noppes.npcs.NoppesUtilServer; @@ -29,7 +30,6 @@ import noppes.npcs.constants.EnumMovingType; import noppes.npcs.constants.EnumNavType; import noppes.npcs.constants.EnumRoleType; import noppes.npcs.constants.EnumStandingType; -import kamkeel.npcs.entity.EntityEnergyProjectile; import noppes.npcs.controllers.FactionController; import noppes.npcs.controllers.data.DialogOption; import noppes.npcs.controllers.data.Line; diff --git a/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java b/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java index 8cebf5a6c..fc3a121e1 100644 --- a/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java +++ b/src/main/java/noppes/npcs/scripted/event/player/AuctionEvent.java @@ -38,13 +38,19 @@ public class AuctionEvent extends PlayerEvent implements IAuctionEvent { } @Override - public IItemStack getItem() { return item; } + public IItemStack getItem() { + return item; + } @Override - public long getStartingPrice() { return startingPrice; } + public long getStartingPrice() { + return startingPrice; + } @Override - public long getBuyoutPrice() { return buyoutPrice; } + public long getBuyoutPrice() { + return buyoutPrice; + } } @Cancelable @@ -64,10 +70,14 @@ public class AuctionEvent extends PlayerEvent implements IAuctionEvent { } @Override - public IAuctionListing getListing() { return listing; } + public IAuctionListing getListing() { + return listing; + } @Override - public long getBidAmount() { return bidAmount; } + public long getBidAmount() { + return bidAmount; + } } @Cancelable @@ -85,7 +95,9 @@ public class AuctionEvent extends PlayerEvent implements IAuctionEvent { } @Override - public IAuctionListing getListing() { return listing; } + public IAuctionListing getListing() { + return listing; + } } @Cancelable @@ -105,10 +117,14 @@ public class AuctionEvent extends PlayerEvent implements IAuctionEvent { } @Override - public IAuctionListing getListing() { return listing; } + public IAuctionListing getListing() { + return listing; + } @Override - public boolean isAdmin() { return admin; } + public boolean isAdmin() { + return admin; + } } @Cancelable @@ -126,6 +142,8 @@ public class AuctionEvent extends PlayerEvent implements IAuctionEvent { } @Override - public IAuctionClaim getClaim() { return claim; } + public IAuctionClaim getClaim() { + return claim; + } } } diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java index f6b9379c1..8e0bda906 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java @@ -1,7 +1,7 @@ package noppes.npcs.scripted.wrapper; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.DataAbilities; diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java index 8d3d068d5..c66a12a48 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java @@ -1,7 +1,7 @@ package noppes.npcs.scripted.wrapper; -import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import net.minecraft.entity.player.EntityPlayer; import noppes.npcs.api.ability.IAbility; import noppes.npcs.api.ability.IPlayerAbilityData; diff --git a/src/main/java/somehussar/gui/AnnotatedGui.java b/src/main/java/somehussar/gui/AnnotatedGui.java index 8cdf224d5..7a1244f89 100644 --- a/src/main/java/somehussar/gui/AnnotatedGui.java +++ b/src/main/java/somehussar/gui/AnnotatedGui.java @@ -33,7 +33,7 @@ public class AnnotatedGui extends SubGuiInterface { int i = 0; for (EditableField field : metadata.getParent().getDeclaredFields()) { - addLabel(new GuiNpcLabel(i++, field.getName(), guiLeft+8, guiTop+i*15+5)); + addLabel(new GuiNpcLabel(i++, field.getName(), guiLeft + 8, guiTop + i * 15 + 5)); } } diff --git a/src/main/java/somehussar/gui/ImmediateTextField.java b/src/main/java/somehussar/gui/ImmediateTextField.java index 5f49a9d84..fba3c5a4d 100644 --- a/src/main/java/somehussar/gui/ImmediateTextField.java +++ b/src/main/java/somehussar/gui/ImmediateTextField.java @@ -5,7 +5,7 @@ import net.minecraft.client.gui.FontRenderer; import noppes.npcs.client.gui.util.GuiNpcTextField; import somehussar.gui.annotationHandling.field.EditableField; -public class ImmediateTextField extends GuiNpcTextField{ +public class ImmediateTextField extends GuiNpcTextField { private final EditableField editableField; // // private final GuiNpcTextField encapsulatedField; @@ -13,6 +13,7 @@ public class ImmediateTextField extends GuiNpcTextField{ public ImmediateTextField(int id, EditableField field, int xPos, int yPos) { this(id, field, Minecraft.getMinecraft().fontRenderer, xPos, yPos); } + public ImmediateTextField(int id, EditableField field, FontRenderer fontRenderer, int xPos, int yPos) { super(id, null, fontRenderer, xPos, yPos, 0, 0, null); diff --git a/src/main/java/somehussar/gui/annotationHandling/GuiEditable.java b/src/main/java/somehussar/gui/annotationHandling/GuiEditable.java index 596f3ab2f..95a74658e 100644 --- a/src/main/java/somehussar/gui/annotationHandling/GuiEditable.java +++ b/src/main/java/somehussar/gui/annotationHandling/GuiEditable.java @@ -27,6 +27,7 @@ public @interface GuiEditable { @Retention(RetentionPolicy.RUNTIME) @interface Size { int labelWidth() default 60; + int fieldWidth() default 50; GuiEditable.Size DEFAULT_SIZE = new GuiEditable.Size() { @@ -39,7 +40,11 @@ public @interface GuiEditable { public int fieldWidth() { return 50; } - @Override public Class annotationType() { return GuiEditable.Size.class; } + + @Override + public Class annotationType() { + return GuiEditable.Size.class; + } }; } diff --git a/src/main/java/somehussar/gui/annotationHandling/GuiFieldHandler.java b/src/main/java/somehussar/gui/annotationHandling/GuiFieldHandler.java index c6b7f3625..b54e70e52 100644 --- a/src/main/java/somehussar/gui/annotationHandling/GuiFieldHandler.java +++ b/src/main/java/somehussar/gui/annotationHandling/GuiFieldHandler.java @@ -26,7 +26,8 @@ public final class GuiFieldHandler { } - private GuiFieldHandler() {} + private GuiFieldHandler() { + } public static ClassMetadata getMetadata(Class type) { return CACHE.computeIfAbsent(type, GuiFieldHandler::scanClass); @@ -69,7 +70,8 @@ public final class GuiFieldHandler { if (Modifier.isFinal(field.getModifiers())) return null; GuiEditable.Size size = field.getAnnotation(GuiEditable.Size.class); - if (size == null) size = GuiEditable.Size.DEFAULT_SIZE;; + if (size == null) size = GuiEditable.Size.DEFAULT_SIZE; + ; field.setAccessible(true); EditableField.Builder builder = new EditableField.Builder() diff --git a/src/main/java/somehussar/gui/annotationHandling/constraints/NumericConstraint.java b/src/main/java/somehussar/gui/annotationHandling/constraints/NumericConstraint.java index 3b9268a30..faccc6418 100644 --- a/src/main/java/somehussar/gui/annotationHandling/constraints/NumericConstraint.java +++ b/src/main/java/somehussar/gui/annotationHandling/constraints/NumericConstraint.java @@ -9,5 +9,6 @@ import java.lang.annotation.Target; @Target(ElementType.FIELD) public @interface NumericConstraint { double min() default Double.NEGATIVE_INFINITY; + double max() default Double.POSITIVE_INFINITY; } diff --git a/src/main/java/somehussar/gui/annotationHandling/field/EditableField.java b/src/main/java/somehussar/gui/annotationHandling/field/EditableField.java index 73172ef42..97e8500f1 100644 --- a/src/main/java/somehussar/gui/annotationHandling/field/EditableField.java +++ b/src/main/java/somehussar/gui/annotationHandling/field/EditableField.java @@ -46,7 +46,9 @@ public final class EditableField { return this.constraints; } - public int getOrder() { return this.order; } + public int getOrder() { + return this.order; + } EditableField(Builder b) { this.name = b.name; @@ -69,14 +71,45 @@ public final class EditableField { private String group = null; private GuiEditable.Size size; - public Builder name(String v) { this.name = v; return this; } - public Builder type(Class v) { this.type = v; return this; } - public Builder getter(MethodHandle v) { this.getter = v; return this; } - public Builder setter(MethodHandle v) { this.setter = v; return this; } - public Builder constraints(FieldConstraints v) { this.constraints = v; return this; } - public Builder order(int order) { this.order = order; return this; } - public Builder group(String value) { this.group = value; return this; } - public Builder size(GuiEditable.Size size) { this.size = size; return this; } + public Builder name(String v) { + this.name = v; + return this; + } + + public Builder type(Class v) { + this.type = v; + return this; + } + + public Builder getter(MethodHandle v) { + this.getter = v; + return this; + } + + public Builder setter(MethodHandle v) { + this.setter = v; + return this; + } + + public Builder constraints(FieldConstraints v) { + this.constraints = v; + return this; + } + + public Builder order(int order) { + this.order = order; + return this; + } + + public Builder group(String value) { + this.group = value; + return this; + } + + public Builder size(GuiEditable.Size size) { + this.size = size; + return this; + } public EditableField build() { return new EditableField<>(this); From 1569204d838b024cd0c8ab79c641ee923acc03c0 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 14:07:05 -0500 Subject: [PATCH 08/12] - Extend Energy Projectile Distance to 150 by Default - Fix a bug with Wheel not Clearing for Adding / Removing Abilities - Fix a bug not being able to resolve Built In Abilities - Fix a Chained Ability Windup System --- .../java/kamkeel/npcs/controllers/AbilityController.java | 9 ++++++++- .../data/ability/data/EnergyLifespanData.java | 2 +- .../data/ability/type/energy/AbilityBeam.java | 2 +- .../data/ability/type/energy/AbilityDisc.java | 2 +- .../data/ability/type/energy/AbilityLaserShot.java | 2 +- .../data/ability/type/energy/AbilitySlicer.java | 2 +- src/main/java/noppes/npcs/AbstractDataAbilities.java | 4 ++-- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index e872251e5..9c73edc7c 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -653,13 +653,20 @@ public class AbilityController implements IAbilityHandler { Ability builtIn = builtAbilities.get(key); if (builtIn != null) return builtIn.deepCopy(); - // Built-in: case-insensitive name + // Built-in: case-insensitive map key for (Map.Entry entry : builtAbilities.entrySet()) { if (entry.getKey().equalsIgnoreCase(key)) { return entry.getValue().deepCopy(); } } + // Built-in: by ability name (name may differ from map key, e.g. "Ki Fist" vs "ki_fist") + for (Ability ability : builtAbilities.values()) { + if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) { + return ability.deepCopy(); + } + } + // Built-in: registry key / ID for (Ability ability : builtAbilities.values()) { if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java index a18a91021..cce2a8283 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java @@ -8,7 +8,7 @@ import noppes.npcs.api.ability.data.IEnergyLifespanData; * Used as a parameter object for entity constructors and ability configuration. */ public class EnergyLifespanData implements IEnergyLifespanData { - public float maxDistance = 30.0f; + public float maxDistance = 150.0f; public int maxLifetime = 200; public EnergyLifespanData() { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java index c565ac9fc..ca1e1ac47 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java @@ -38,7 +38,7 @@ public class AbilityBeam extends AbilityEnergyProjectile impl new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f), new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f), new EnergyHomingData(0.4f, true, 0.1f, 15.0f), - new EnergyLifespanData(25.0f, 200) + new EnergyLifespanData(150.0f, 200) ); this.typeId = "ability.cnpc.beam"; this.name = "Beam"; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index dc6b18add..0fd8b494c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -40,7 +40,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f), new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f), new EnergyHomingData(0.6f, true, 0.12f, 18.0f), - new EnergyLifespanData(35.0f, 200) + new EnergyLifespanData(150.0f, 200) ); this.typeId = "ability.cnpc.disc"; this.name = "Disc"; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index 51f3da8ca..91467556a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -40,7 +40,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile { new EnergyDisplayData(0xFF4444, 0xFF0000, true, 0.2f, 0.6f, 0.0f), new EnergyCombatData(12.0f, 0.5f, 0.0f, false, 3.0f, 0.5f), new EnergyHomingData(0.8f, false, 0.0f, 0.0f), - new EnergyLifespanData(40.0f, 100) + new EnergyLifespanData(150.0f, 100) ); this.typeId = "ability.cnpc.slicer"; this.name = "Slicer"; diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index dea49be68..c31154bc3 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -861,8 +861,8 @@ public abstract class AbstractDataAbilities { return false; } - // If windUpAll=false, force windup to 0 (skip individual windups) - if (!currentChain.isWindUpAll()) { + // If windUpAll=false, only the first ability windups; subsequent abilities skip windup + if (!currentChain.isWindUpAll() && chainEntryIndex > 0) { ability.setWindUpTicks(0); } From 48f6c162d541d40f3ada9b15f470c4158f72d812 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 14:10:55 -0500 Subject: [PATCH 09/12] Bugs fixes to Abilities - Add Hit Scan to Laser - Switching Dimensions Ability fix - Chained Ability Targeting Conditions Fix - Chained Ability Previewing - Lang Keys Added --- .../ability/type/energy/AbilityLaserShot.java | 16 +- .../npcs/entity/EntityAbilityLaser.java | 27 +++ .../noppes/npcs/ScriptPlayerEventHandler.java | 4 +- .../gui/advanced/SubGuiAbilityConfig.java | 3 + .../advanced/SubGuiChainedAbilityConfig.java | 81 +++++--- .../gui/global/GuiNpcManageAbilities.java | 51 ++--- .../gui/util/AbilityPreviewExecutor.java | 187 ++++++++++++++++-- .../controllers/data/PlayerAbilityData.java | 45 +++++ .../assets/customnpcs/lang/en_US.lang | 5 + 9 files changed, 343 insertions(+), 76 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index 91467556a..4639901d4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -34,6 +34,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile hitEntities = new HashSet<>(); @@ -298,6 +301,20 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { hitEntities.add(entity.getEntityId()); applyDamage(entity); + if (dieOnImpact) { + // Stop at impact point and begin linger/fade + double dx = entity.posX - startX; + double dy = (entity.posY + entity.height * 0.5) - startY; + double dz = entity.posZ - startZ; + float impactDist = (float) Math.sqrt(dx * dx + dy * dy + dz * dz); + currentLength = Math.min(impactDist, currentLength); + endX = startX + dirX * currentLength; + endY = startY + dirY * currentLength; + endZ = startZ + dirZ * currentLength; + fullyExtended = true; + return; // Stop checking further entities + } + // Piercing - don't stop, continue to next entity } } @@ -386,6 +403,14 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.lingerTicks = ticks; } + public boolean isDieOnImpact() { + return dieOnImpact; + } + + public void setDieOnImpact(boolean dieOnImpact) { + this.dieOnImpact = dieOnImpact; + } + public void setDirection(double x, double y, double z) { this.dirX = x; this.dirY = y; @@ -451,6 +476,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.laserWidth = nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f; this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f; this.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10; + this.dieOnImpact = nbt.getBoolean("DieOnImpact"); this.dirX = nbt.getDouble("DirX"); this.dirY = nbt.getDouble("DirY"); this.dirZ = nbt.getDouble("DirZ"); @@ -468,6 +494,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { nbt.setFloat("LaserWidth", laserWidth); nbt.setFloat("ExpansionSpeed", expansionSpeed); nbt.setInteger("LingerTicks", lingerTicks); + nbt.setBoolean("DieOnImpact", dieOnImpact); nbt.setDouble("DirX", dirX); nbt.setDouble("DirY", dirY); nbt.setDouble("DirZ", dirZ); diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index d793dcea9..81ff9ed6b 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -332,10 +332,10 @@ public class ScriptPlayerEventHandler { return; if (event.player.worldObj instanceof WorldServer && event.player instanceof EntityPlayerMP) { - // Cancel any executing ability on dimension change + // Fully reset ability state on dimension change (no cooldown rollover) PlayerData playerData = PlayerData.get(event.player); if (playerData != null) { - playerData.abilityData.interruptCurrentAbility(); + playerData.abilityData.resetOnDimensionChange(); } PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(event.player); diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java index f0b610d19..f662da835 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -92,6 +92,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi @Override public void initGui() { + // Commit any focused text field before rebuilding + GuiNpcTextField.unfocus(); + // Save scroll position before super clears scroll windows GuiScrollWindow oldSw = getScrollableGui(0); if (oldSw != null && activeTab < tabScrollY.length) { diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index 090d86dbb..aa2df26f9 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -45,13 +45,13 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText // ── Tab constants (core tabs) ──────────────────────────────────────────── private static final int TAB_GENERAL = 0; private static final int TAB_ENTRIES = 1; - private static final int TAB_CONDITIONS = 2; + private static final int TAB_TARGET = 2; private static final int CORE_TAB_COUNT = 3; // ── Tab button IDs ──────────────────────────────────────────────────────── private static final int BTN_TAB_GENERAL = 90; private static final int BTN_TAB_ENTRIES = 91; - private static final int BTN_TAB_CONDITIONS = 92; + private static final int BTN_TAB_TARGET = 92; private static final int BTN_TAB_EXTRA_BASE = 93; private static final int BTN_CLOSE = -1000; @@ -133,16 +133,18 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText fieldDefs.add(FieldDef.intField("ability.weight", chain::getWeight, chain::setWeight).range(1, 100).tab("General")); fieldDefs.add(FieldDef.boolField("ability.windUpAll", chain::isWindUpAll, chain::setWindUpAll).tab("General")); fieldDefs.add(FieldDef.intField("ability.cooldown", chain::getCooldownTicks, chain::setCooldownTicks).range(0, 12000).tab("General")); + + // ── Target tab ─────────────────────────────────────────────── fieldDefs.add(FieldDef.row( FieldDef.floatField("ability.minRange", chain::getMinRange, chain::setMinRange).range(0, 64), FieldDef.floatField("ability.maxRange", chain::getMaxRange, chain::setMaxRange).range(0, 64) - ).tab("General")); + ).tab("Target")); - // Computed compatibility label (derived from child abilities) - fieldDefs.add(FieldDef.labelField("ability.compatible", () -> { + // Computed "Valid For" label (derived from child abilities) + fieldDefs.add(FieldDef.labelField("ability.validFor", () -> { UserType ut = computeAllowedBy(); - return StatCollector.translateToLocal("ability.compatible") + ": \u00A7e" + ut.name(); - }).tab("General")); + return StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + StatCollector.translateToLocal("ability.userType." + ut.name()); + }).tab("Target")); // External field providers (e.g., DBC Addon injecting an "Icon" tab) if (AbilityController.Instance != null) { @@ -155,7 +157,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText Set tabNames = new LinkedHashSet<>(); for (FieldDef def : fieldDefs) { String tab = def.getTab(); - if (tab != null && !tab.isEmpty() && !"General".equals(tab)) { + if (tab != null && !tab.isEmpty() && !"General".equals(tab) && !"Target".equals(tab)) { tabNames.add(tab); } } @@ -196,6 +198,9 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText @Override public void initGui() { + // Commit any focused text field before rebuilding + GuiNpcTextField.unfocus(); + // Save scroll position GuiScrollWindow oldSw = getScrollableGui(0); if (oldSw != null && activeTab < tabScrollY.length) { @@ -213,12 +218,12 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText entriesTab.active = (activeTab == TAB_ENTRIES); addTopButton(entriesTab); - GuiMenuTopButton conditionsTab = new GuiMenuTopButton(BTN_TAB_CONDITIONS, entriesTab, "ability.conditions"); - conditionsTab.active = (activeTab == TAB_CONDITIONS); - addTopButton(conditionsTab); + GuiMenuTopButton targetTab = new GuiMenuTopButton(BTN_TAB_TARGET, entriesTab, "script.target"); + targetTab.active = (activeTab == TAB_TARGET); + addTopButton(targetTab); // Dynamic extra tabs from field providers - GuiMenuTopButton prevTab = conditionsTab; + GuiMenuTopButton prevTab = targetTab; for (int i = 0; i < extraTabs.size(); i++) { int tabIndex = CORE_TAB_COUNT + i; GuiMenuTopButton extraTab = new GuiMenuTopButton(BTN_TAB_EXTRA_BASE + i, prevTab, extraTabs.get(i)); @@ -240,8 +245,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText buildFieldDefTab("General", swX, swY, swW, swH); } else if (activeTab == TAB_ENTRIES) { buildEntriesTab(swX, swY, swW, swH); - } else if (activeTab == TAB_CONDITIONS) { - buildConditionsTab(swX, swY, swW, swH); + } else if (activeTab == TAB_TARGET) { + buildTargetTab(swX, swY, swW, swH); } else if (activeTab >= CORE_TAB_COUNT) { int extraIndex = activeTab - CORE_TAB_COUNT; if (extraIndex < extraTabs.size()) { @@ -339,18 +344,35 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } // ═══════════════════════════════════════════════════════════════════════════ - // CONDITIONS TAB + // TARGET TAB (FieldDef fields + conditions list) // ═══════════════════════════════════════════════════════════════════════════ - private void buildConditionsTab(int swX, int swY, int swW, int swH) { - GuiScrollWindow sw = new GuiScrollWindow(this, swX, swY, swW, swH, 0); - sw.backgroundColor = 0x88000000; - addScrollableGui(0, sw); + private void buildTargetTab(int swX, int swY, int swW, int swH) { + // Build FieldDef fields (ranges, Valid For) first + List tabFields = new ArrayList<>(); + for (FieldDef def : fieldDefs) { + if ("Target".equals(def.getTab()) && def.isVisible()) { + tabFields.add(def); + } + } - int y = 5; - int labelCounter = 0; + builder = new GuiFieldBuilder(this, fontRendererObj); + builder.startIds(DECLARATIVE_ID_START, CLEAR_ID_START, LABEL_ID_START); + builder.startY(5); - sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55)); + GuiScrollWindow sw = builder.buildScrollWindow(tabFields, swX, swY, swW, swH); + + // Append conditions list below the FieldDef fields + int y = builder.getLastBuildY(); + int condY = renderConditions(sw, y, builder.getNextLabelId()); + sw.maxScrollY = Math.max(condY - swH, 0); + + restoreScroll(sw); + } + + private int renderConditions(GuiScrollWindow sw, int y, int labelCounter) { + y += 3; + sw.addLabel(new GuiNpcLabel(labelCounter, "ability.conditions", L_LABEL_X, y + 2, 0xFFFF55)); y += 15; for (int i = 0; i < conditions.size() && i < 3; i++) { @@ -367,8 +389,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText y += ROW_H; } - sw.maxScrollY = Math.max(y - swH, 0); - restoreScroll(sw); + return y; } private String getConditionDisplayName(Condition cond) { @@ -427,8 +448,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText initGui(); return; } - if (id == BTN_TAB_CONDITIONS) { - activeTab = TAB_CONDITIONS; + if (id == BTN_TAB_TARGET) { + activeTab = TAB_TARGET; initGui(); return; } @@ -450,8 +471,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText if (handleEntryButton(id)) return; } - // Conditions tab - if (activeTab == TAB_CONDITIONS) { + // Target tab (includes conditions) + if (activeTab == TAB_TARGET) { if (handleConditionButton(id)) return; } @@ -470,7 +491,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText * Whether the current active tab uses the declarative FieldDef system. */ private boolean isFieldDefTab() { - return activeTab == TAB_GENERAL || activeTab >= CORE_TAB_COUNT; + return activeTab == TAB_GENERAL || activeTab == TAB_TARGET || activeTab >= CORE_TAB_COUNT; } private boolean handleEntryButton(int id) { @@ -568,7 +589,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText return; } - // Entries tab — delay fields (ENTRY_BASE + i * ENTRY_STRIDE + 1) + // Entries tab - delay fields (ENTRY_BASE + i * ENTRY_STRIDE + 1) if (activeTab == TAB_ENTRIES && id >= ENTRY_BASE + 1 && id < ENTRY_END) { int entryIndex = (id - ENTRY_BASE - 1) / ENTRY_STRIDE; if (entryIndex >= 0 && entryIndex < entries.size()) { diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index 52ddbba9d..63e78dd11 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -175,13 +175,12 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface // Search bar addTextField(new GuiNpcTextField(TF_SEARCH, this, fontRendererObj, guiLeft + 220, guiTop + 192, 143, 20, search)); - // Preview only for ability views (not chained) + // Check if we have something to preview if (showingChained) { - return; - } - - // Show ability info only if one is selected - if (selectedAbility == null || selected == null) { + if (selectedChain == null || selected == null || selectedChain.getEntries().isEmpty()) { + return; + } + } else if (selectedAbility == null || selected == null) { return; } @@ -311,6 +310,27 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface return; } + // Preview controls (shared across all views) + if (id == BTN_PREVIEW_PLAY) { + if (previewExecutor.isPaused()) { + previewExecutor.play(); + } else if (showingChained && selectedChain != null) { + previewExecutor.startChainPreview(selectedChain, npc); + } else if (selectedAbility != null) { + previewExecutor.startPreview(selectedAbility, npc); + } + initGui(); + return; + } else if (id == BTN_PREVIEW_PAUSE) { + previewExecutor.pause(); + initGui(); + return; + } else if (id == BTN_PREVIEW_STOP) { + previewExecutor.stop(); + initGui(); + return; + } + if (showingChained) { if (id == BTN_ADD) { // New chained ability @@ -320,6 +340,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface GuiYesNo guiyesno = new GuiYesNo(this, selected, StatCollector.translateToLocal("gui.delete"), CONFIRM_REMOVE_CHAIN); displayGuiScreen(guiyesno); } else if (id == BTN_EDIT && selectedChain != null) { + previewExecutor.stop(); setSubGui(new SubGuiChainedAbilityConfig(selectedChain, this)); } return; @@ -335,24 +356,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface // Edit previewExecutor.stop(); setSubGui(selectedAbility.createConfigGui(this)); - } else if (id == BTN_PREVIEW_PLAY) { - // Play button - if (selectedAbility != null) { - if (previewExecutor.isPaused()) { - previewExecutor.play(); - } else { - previewExecutor.startPreview(selectedAbility, npc); - } - initGui(); - } - } else if (id == BTN_PREVIEW_PAUSE) { - // Pause button - previewExecutor.pause(); - initGui(); - } else if (id == BTN_PREVIEW_STOP) { - // Stop button - previewExecutor.stop(); - initGui(); } } diff --git a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java b/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java index 6d2534633..bde8ae194 100644 --- a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java +++ b/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java @@ -2,8 +2,11 @@ package noppes.npcs.client.gui.util; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.PreviewEntityHandler; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.Entity; @@ -39,7 +42,7 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { /** * Executor's own phase tracking (separate from ability.getPhase()) */ - private enum ExecutorPhase {IDLE, WINDUP, ACTIVE, EXTENDED} + private enum ExecutorPhase {IDLE, WINDUP, ACTIVE, EXTENDED, CHAIN_DELAY} private ExecutorPhase executorPhase = ExecutorPhase.IDLE; @@ -62,6 +65,14 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { private TelegraphInstance previewTelegraph; private List previewEntities = new ArrayList<>(); + // ==================== CHAIN PREVIEW ==================== + private ChainedAbility previewChain; + private List chainAbilities; + private List chainDelays; + private int chainIndex = -1; + private int chainDelayRemaining = 0; + private boolean chainWindUpAll = true; + // ==================== CALLBACKS ==================== private GuiAbilityInterface parentGui; @@ -73,28 +84,21 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { } /** - * Start previewing an ability using unified execution. + * Start previewing a single ability using unified execution. * Sets previewMode=true on the ability, then calls its real methods. */ public void startPreview(Ability ability, EntityNPCInterface npc) { stop(); - this.previewAbility = ability; this.previewNpc = npc; - this.currentTick = 0; this.playing = true; this.paused = false; - this.maxPreviewDuration = ability.getMaxPreviewDuration(); // Save NPC start position for restoring later this.npcStartX = npc.posX; this.npcStartY = npc.posY; this.npcStartZ = npc.posZ; - // Enable preview mode on the ability - ability.setPreviewMode(true); - ability.setPreviewEntityHandler(this); - // Create invisible fake target NPC for targeting createFakeTarget(npc); @@ -103,10 +107,86 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { parentGui.startTrackingMovement(); } + beginAbilityPreview(ability); + } + + /** + * Start previewing a chained ability — plays all entries in sequence with delays. + * Each entry is resolved and deep-copied to avoid modifying originals. + */ + public void startChainPreview(ChainedAbility chain, EntityNPCInterface npc) { + stop(); + + this.previewNpc = npc; + this.playing = true; + this.paused = false; + this.previewChain = chain; + this.chainWindUpAll = chain.isWindUpAll(); + + // Save NPC start position for restoring later + this.npcStartX = npc.posX; + this.npcStartY = npc.posY; + this.npcStartZ = npc.posZ; + + // Resolve all entries to deep-copied abilities + chainAbilities = new ArrayList<>(); + chainDelays = new ArrayList<>(); + for (ChainedAbilityEntry entry : chain.getEntries()) { + Ability resolved = entry.resolve(); + if (resolved != null && AbilityController.Instance != null) { + Ability copy = AbilityController.Instance.fromNBT(resolved.writeNBT()); + if (copy != null) { + chainAbilities.add(copy); + chainDelays.add(entry.getDelayTicks()); + } + } + } + + if (chainAbilities.isEmpty()) { + playing = false; + return; + } + + // Create invisible fake target NPC for targeting + createFakeTarget(npc); + + // Start tracking NPC movement + if (parentGui != null) { + parentGui.startTrackingMovement(); + } + + chainIndex = 0; + beginAbilityPreview(chainAbilities.get(0)); + } + + /** + * Begin previewing a single ability (shared by single and chain preview). + * Cleans up the previous ability if any (for chain advancement). + */ + private void beginAbilityPreview(Ability ability) { + // Clean up previous ability if any (for chain advancement) + if (previewAbility != null) { + previewAbility.cleanup(); + previewAbility.setPreviewMode(false); + previewAbility.setPreviewEntityHandler(null); + previewAbility.reset(); + } + + this.previewAbility = ability; + this.currentTick = 0; + this.maxPreviewDuration = ability.getMaxPreviewDuration(); + + // Enable preview mode on the ability + ability.setPreviewMode(true); + ability.setPreviewEntityHandler(this); + // Use the ability's real start() method with the fake target ability.start(fakeTarget); - if (ability.getWindUpTicks() <= 0) { + // Skip windup for non-first chain entries when windUpAll is false + boolean skipWindup = previewChain != null && !chainWindUpAll && chainIndex > 0; + + if (ability.getWindUpTicks() <= 0 || skipWindup) { // No windup — go straight to active this.executorPhase = ExecutorPhase.ACTIVE; transitionToActive(); @@ -115,12 +195,12 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { this.executorPhase = ExecutorPhase.WINDUP; // Create telegraph for preview - previewTelegraph = ability.createTelegraph(npc, fakeTarget); + previewTelegraph = ability.createTelegraph(previewNpc, fakeTarget); // Start windup animation Animation windUpAnim = ability.getWindUpAnimation(); if (windUpAnim != null) { - AnimationData data = npc.display.animationData; + AnimationData data = previewNpc.display.animationData; data.setEnabled(true); data.setAnimation(windUpAnim); data.animation.paused = false; @@ -216,6 +296,13 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { executorPhase = ExecutorPhase.IDLE; currentTick = 0; + // Clear chain state + previewChain = null; + chainAbilities = null; + chainDelays = null; + chainIndex = -1; + chainDelayRemaining = 0; + previewTelegraph = null; if (parentGui != null) { @@ -286,6 +373,9 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { case EXTENDED: tickExtended(); break; + case CHAIN_DELAY: + tickChainDelay(); + break; default: break; } @@ -309,13 +399,21 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { // Check if the ability signaled completion (phase went to IDLE) if (previewAbility.getPhase() == AbilityPhase.IDLE) { - transitionToExtended(); + if (hasNextChainEntry()) { + transitionToChainDelay(); + } else { + transitionToExtended(); + } return; } // Safety timeout if (currentTick >= maxPreviewDuration) { - transitionToExtended(); + if (hasNextChainEntry()) { + transitionToChainDelay(); + } else { + transitionToExtended(); + } } } @@ -326,6 +424,44 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { } } + // ==================== CHAIN ADVANCEMENT ==================== + + private boolean hasNextChainEntry() { + return previewChain != null && chainAbilities != null && chainIndex < chainAbilities.size() - 1; + } + + private void transitionToChainDelay() { + executorPhase = ExecutorPhase.CHAIN_DELAY; + currentTick = 0; + + // Use the NEXT entry's delay + int nextIndex = chainIndex + 1; + chainDelayRemaining = (nextIndex < chainDelays.size()) ? chainDelays.get(nextIndex) : 0; + + // Clear current animation + if (previewNpc != null) { + AnimationData data = previewNpc.display.animationData; + data.setAnimation(new Animation()); + } + } + + private void tickChainDelay() { + if (chainDelayRemaining > 0) { + chainDelayRemaining--; + return; + } + advanceChainEntry(); + } + + private void advanceChainEntry() { + chainIndex++; + if (chainIndex >= chainAbilities.size()) { + transitionToExtended(); + return; + } + beginAbilityPreview(chainAbilities.get(chainIndex)); + } + private void transitionToActive() { executorPhase = ExecutorPhase.ACTIVE; currentTick = 0; @@ -439,6 +575,10 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { return playing || paused; } + public boolean isChainPreview() { + return previewChain != null; + } + public AbilityPhase getPhase() { switch (executorPhase) { case WINDUP: @@ -473,14 +613,23 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler { public String getStatusString() { if (!playing && !paused) { return "Stopped"; - } else if (paused) { - return "Paused"; + } + + String chainPrefix = ""; + if (previewChain != null && chainAbilities != null) { + chainPrefix = "Chain " + (chainIndex + 1) + "/" + chainAbilities.size() + " - "; + } + + if (paused) { + return chainPrefix + "Paused"; } else if (executorPhase == ExecutorPhase.WINDUP) { - return "Windup: " + currentTick + "/" + (previewAbility != null ? previewAbility.getWindUpTicks() : 0); + return chainPrefix + "Windup: " + currentTick + "/" + (previewAbility != null ? previewAbility.getWindUpTicks() : 0); } else if (executorPhase == ExecutorPhase.ACTIVE) { - return "Active: " + currentTick + "/" + maxPreviewDuration; + return chainPrefix + "Active: " + currentTick + "/" + maxPreviewDuration; } else if (executorPhase == ExecutorPhase.EXTENDED) { - return "Extended: " + currentTick + "/" + EXTENDED_DURATION; + return chainPrefix + "Extended: " + currentTick + "/" + EXTENDED_DURATION; + } else if (executorPhase == ExecutorPhase.CHAIN_DELAY) { + return chainPrefix + "Delay: " + chainDelayRemaining; } return ""; } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index a11483bc0..8562053c2 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -581,6 +581,51 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA interruptCurrentAbility(null, 0); } + // ═══════════════════════════════════════════════════════════════════ + // DIMENSION CHANGE RESET + // ═══════════════════════════════════════════════════════════════════ + + /** + * Fully reset ability state when changing dimensions. + * Unlike interruptCurrentAbility(), this does NOT roll any cooldowns + * and ensures the player can immediately use abilities in the new dimension. + * Also re-syncs to client since the entity may be re-created. + */ + public void resetOnDimensionChange() { + // Clean up any executing ability without rolling cooldowns + if (currentAbility != null && currentAbility.isExecuting()) { + removeTelegraph(currentAbility); + currentAbility.cleanup(); + currentAbility.interrupt(); + stopAbilityAnimation(); + releaseRotationControl(); + releaseLockedPosition(); + } + + // Clear chain state + currentChain = null; + chainEntryIndex = -1; + chainDelayRemaining = -1; + + // Clear all transient state + currentAbility = null; + currentAbilityKey = null; + currentTarget = null; + + // Reset cooldowns completely + cooldownEndTime = 0; + interruptCooldownRolled = false; + + // Sync cleared state to client + EntityPlayer player = playerData.player; + if (player != null) { + syncAbilityStateClear(player); + } + + // Re-sync ability data (unlocked list, selection) to client + syncToClient(); + } + // ═══════════════════════════════════════════════════════════════════ // DAMAGE HANDLING // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 2778a1ec7..6574f71bc 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2037,6 +2037,11 @@ ability.selectSlot=Select NPC Ability ability.loadType=Load Type ability.loadAbility=Load Ability ability.loadChain=Load Chain +ability.validFor=Valid For +ability.userType.NPC_ONLY=NPCs +ability.userType.PLAYER_ONLY=Players +ability.userType.BOTH=Both +ability.dieOnImpact=Die on Impact gui.both=Both gui.players=Players From 40a02b73e2f456d0fc2de3a1fe17fc23625582c6 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 15:31:33 -0500 Subject: [PATCH 10/12] Fixes to some GUI and Chained Abiltiy Logic for Clearing States --- .../controllers/data/ability/Ability.java | 22 ++- .../data/ability/type/AbilityCharge.java | 6 + .../data/ability/type/AbilityDash.java | 6 + .../data/ability/type/AbilityMovement.java | 6 + .../type/energy/AbilityEnergyProjectile.java | 1 + .../noppes/npcs/AbstractDataAbilities.java | 29 ++-- .../client/gui/advanced/GuiNPCAbilities.java | 35 ++-- .../advanced/SubGuiChainedAbilityConfig.java | 156 ++++++++++++++---- 8 files changed, 196 insertions(+), 65 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index c44f1bd47..f93a5c772 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -14,6 +14,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; +import net.minecraft.util.StatCollector; import net.minecraft.world.World; import noppes.npcs.DataAbilities; import noppes.npcs.NpcDamageSource; @@ -281,6 +282,17 @@ public abstract class Ability implements IAbility, IAbilityAction { public void onComplete(EntityLivingBase caster, EntityLivingBase target) { } + /** + * Called when the ability enters BURST_DELAY between burst iterations. + * Override in subclasses to reset transient state that should not carry + * across burst repetitions (e.g., movement direction, hit entity lists). + *

+ * Unlike {@link #cleanup()}, this does NOT kill spawned entities (they may + * still be in flight for overlap mode). + */ + public void resetForBurst() { + } + public void onDamageTaken(EntityLivingBase caster, EntityLivingBase attacker, DamageSource source, float damage) { } @@ -535,6 +547,10 @@ public abstract class Ability implements IAbility, IAbilityAction { .tab("General")); defs.add(FieldDef.stringField("gui.displayName", this::getRawDisplayName, this::setDisplayName) .tab("General")); + defs.add(FieldDef.labelField("ability.validFor", () -> + StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + + StatCollector.translateToLocal("ability.userType." + getAllowedBy().name())) + .tab("General")); defs.add(FieldDef.row( FieldDef.intField("ability.weight", this::getWeight, this::setWeight).range(1, 1000), FieldDef.boolField("gui.enabled", this::isEnabled, this::setEnabled) @@ -979,9 +995,9 @@ public abstract class Ability implements IAbility, IAbilityAction { if (phase == AbilityPhase.ACTIVE) { // Check if more burst iterations remain if (burstEnabled && burstAmount > 0 && burstIndex < burstAmount) { - // Don't cleanup here - entities may still be in flight - // For non-overlap: cleaned up when next burst starts (in onExecute) - // For overlap: entities continue flying indefinitely + // Reset transient state for next burst iteration (e.g., movement direction, hit lists) + // Does NOT kill entities - they may still be in flight (overlap mode) + resetForBurst(); burstIndex++; phase = AbilityPhase.BURST_DELAY; currentTick = 0; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java index 9f063ff72..120f6190d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java @@ -171,6 +171,12 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { hitEntities.clear(); } + @Override + public void resetForBurst() { + super.resetForBurst(); + hitEntities.clear(); + } + @Override public float getTelegraphLength() { return maxRange; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index 84df55642..fffbf6ca5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -196,6 +196,12 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { chosenDirection = null; } + @Override + public void resetForBurst() { + super.resetForBurst(); + chosenDirection = null; + } + @Override public int getMaxPreviewDuration() { return (int) Math.ceil(dashDistance / dashSpeed) + 5; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java index de2090cc3..d5b8d8c52 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java @@ -53,6 +53,12 @@ public abstract class AbilityMovement extends Ability { maxActiveTicks = 0; } + @Override + public void resetForBurst() { + movementDirection = null; + maxActiveTicks = 0; + } + // ═══════════════════════════════════════════════════════════════════ // DIRECTION HELPERS // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 6575b29c0..2286919d6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -68,6 +68,7 @@ public abstract class AbilityEnergyProjectile this.homingData = homingData; this.lifespanData = lifespanData; this.trajectoryData = new EnergyTrajectoryData(); + this.burstOverlap = true; initProjectiles(1); } diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index c31154bc3..13d3095c9 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -616,8 +616,10 @@ public abstract class AbstractDataAbilities { return; } - // Auto-complete for burst overlap mode + // Auto-complete for burst overlap mode only. + // Non-overlap waits for all entities to die naturally (via allDead check in onActiveTick). if (currentAbility.isBurstEnabled() + && currentAbility.isBurstOverlap() && currentAbility.getBurstIndex() < currentAbility.getBurstAmount() && currentAbility.getPhase() == AbilityPhase.ACTIVE && currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) { @@ -682,10 +684,12 @@ public abstract class AbstractDataAbilities { // Chain mode: advance to next entry instead of completing if (currentChain != null) { + // AFTER semantics: delay from the CURRENT (just-completed) entry + ChainedAbilityEntry completedEntry = currentChain.getEntries().get(chainEntryIndex); + int delay = completedEntry.getDelayTicks(); + chainEntryIndex++; if (chainEntryIndex < currentChain.getEntries().size()) { - ChainedAbilityEntry nextEntry = currentChain.getEntries().get(chainEntryIndex); - // Check if target died and retarget if (target != null && target.isDead) { target = retargetForChain(); @@ -695,13 +699,9 @@ public abstract class AbstractDataAbilities { } } - int delay = nextEntry.getDelayTicks(); - if (delay > 0) { - chainDelayRemaining = delay; - currentAbility = null; - } else { - startChainEntry(target); - } + // Enforce minimum 1-tick delay between chain entries + chainDelayRemaining = Math.max(1, delay); + currentAbility = null; return; } // All entries complete @@ -819,14 +819,7 @@ public abstract class AbstractDataAbilities { chainEntryIndex = 0; chainDelayRemaining = -1; - // Check if first entry has a delay - ChainedAbilityEntry firstEntry = chain.getEntries().get(0); - int delay = firstEntry.getDelayTicks(); - if (delay > 0) { - chainDelayRemaining = delay; - return true; - } - + // AFTER semantics: no delay before first entry (delay applies after each entry completes) return startChainEntry(target); } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index e95b2c97b..0a83895ed 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -100,6 +100,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // ── Pending: chain editing ──────────────────────────────────────────────── private ChainedAbility pendingChain = null; + private int pendingChainSlotIdx = -1; // ── Pending: inline chain-entry editing ────────────────────────────────── private boolean editingChainEntry = false; @@ -233,17 +234,22 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Check if selected slot is editable boolean selectedIsBuiltIn = false; boolean selectedIsChainEntry = selectedEntryIndex >= 0; - if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() && !selectedIsChainEntry) { + boolean selectedIsRefChainEntry = false; + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { AbilityAction sel = npcSlots.get(selectedSlotIndex); - if (!sel.isChain()) { + if (!selectedIsChainEntry && !sel.isChain()) { Ability a = sel.getAbility(); if (a != null && a.isBuiltIn()) selectedIsBuiltIn = true; } + // Sub-entries of reference chains are not directly editable from the NPC list + if (selectedIsChainEntry && sel.isChainReference()) { + selectedIsRefChainEntry = true; + } } // Right side buttons: Edit and Up/Down carrots with Save button addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 210, y + 145, 55, 20, "gui.edit")); - getButton(BTN_EDIT).setEnabled(selectedSlotIndex >= 0 && !selectedIsBuiltIn); + getButton(BTN_EDIT).setEnabled(selectedSlotIndex >= 0 && !selectedIsBuiltIn && !selectedIsRefChainEntry); // Up/Down carrot buttons addButton(new GuiNpcButton(BTN_MOVE_UP, guiLeft + 270, y + 145, 20, 20, "<")); @@ -354,6 +360,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC ChainedAbility chain = slot.getInlineChain(); if (chain != null) { pendingChain = chain; + pendingChainSlotIdx = selectedSlotIndex; setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); } } @@ -484,6 +491,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC newChain.setId(UUID.randomUUID().toString()); newChain.setName("NewChain"); pendingChain = newChain; + pendingChainSlotIdx = -1; setSubGui(new SubGuiChainedAbilityConfig(newChain, this, true, npcSlots)); return; } @@ -837,8 +845,10 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { if (scroll == npcAbilitiesScroll && selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { - // Double-click mirrors Edit button logic - buttonEvent(new GuiNpcButton(BTN_EDIT, 0, 0, 0, 0, "")); + GuiNpcButton editBtn = getButton(BTN_EDIT); + if (editBtn != null && editBtn.enabled) { + buttonEvent(new GuiNpcButton(BTN_EDIT, 0, 0, 0, 0, "")); + } } } @@ -918,22 +928,17 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC private void handleChainConfigClosed() { if (pendingChain != null) { - boolean found = false; - for (int i = 0; i < npcSlots.size(); i++) { - AbilityAction slot = npcSlots.get(i); - if (slot.isInlineChain() && slot.getInlineChain() == pendingChain) { - found = true; - break; - } - } - if (!found) { + if (pendingChainSlotIdx < 0) { + // New chain — add to NPC slots npcSlots.add(AbilityAction.inlineChain(pendingChain)); selectedSlotIndex = npcSlots.size() - 1; selectedEntryIndex = -1; selectedAbilityIndex = selectedSlotIndex; if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); } + // Existing slot (pendingChainSlotIdx >= 0): already updated in-place, nothing to add pendingChain = null; + pendingChainSlotIdx = -1; } updateNpcAbilitiesList(); save(); @@ -953,6 +958,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC ChainedAbility chain = slot.getInlineChain(); if (chain != null) { pendingChain = chain; + pendingChainSlotIdx = selectedSlotIndex; setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); } updateNpcAbilitiesList(); @@ -962,6 +968,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC ChainedAbility chain = slot.getChainedAbility(); if (chain != null) { pendingChain = chain; + pendingChainSlotIdx = selectedSlotIndex; setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index aa2df26f9..ba3bfb955 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -3,11 +3,14 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.Condition; import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket; import net.minecraft.client.gui.GuiButton; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; @@ -79,6 +82,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private final ChainedAbility chain; private final IChainedAbilityConfigCallback callback; private final boolean npcContext; + private final boolean readOnlyEntries; private final List npcSlots; private int activeTab = TAB_GENERAL; @@ -94,6 +98,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText // Entry editing private int editingEntryIndex = -1; + private String pendingTypeId; + private boolean editingParentAbility = false; // Condition editing private int editingConditionIndex = -1; @@ -105,14 +111,20 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private final List consumedSlotIndices = new ArrayList<>(); public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback) { - this(chain, callback, false, null); + this(chain, callback, false, null, false); } public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback, boolean npcContext, List npcSlots) { + this(chain, callback, npcContext, npcSlots, false); + } + + public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback, + boolean npcContext, List npcSlots, boolean readOnlyEntries) { this.chain = chain; this.callback = callback; this.npcContext = npcContext; + this.readOnlyEntries = readOnlyEntries; this.npcSlots = npcSlots; this.entries = new ArrayList<>(chain.getEntries()); this.conditions = new ArrayList<>(chain.getConditions()); @@ -129,6 +141,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText fieldDefs.add(FieldDef.stringField("gui.name", chain::getName, chain::setName).tab("General")); fieldDefs.add(FieldDef.stringField("gui.displayName", chain::getRawDisplayName, chain::setDisplayName).tab("General")); + // Computed "Valid For" label (derived from child abilities) + fieldDefs.add(FieldDef.labelField("ability.validFor", () -> { + UserType ut = computeAllowedBy(); + return StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + StatCollector.translateToLocal("ability.userType." + ut.name()); + }).tab("General")); fieldDefs.add(FieldDef.boolField("gui.enabled", chain::isEnabled, chain::setEnabled).tab("General")); fieldDefs.add(FieldDef.intField("ability.weight", chain::getWeight, chain::setWeight).range(1, 100).tab("General")); fieldDefs.add(FieldDef.boolField("ability.windUpAll", chain::isWindUpAll, chain::setWindUpAll).tab("General")); @@ -140,12 +157,6 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText FieldDef.floatField("ability.maxRange", chain::getMaxRange, chain::setMaxRange).range(0, 64) ).tab("Target")); - // Computed "Valid For" label (derived from child abilities) - fieldDefs.add(FieldDef.labelField("ability.validFor", () -> { - UserType ut = computeAllowedBy(); - return StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + StatCollector.translateToLocal("ability.userType." + ut.name()); - }).tab("Target")); - // External field providers (e.g., DBC Addon injecting an "Icon" tab) if (AbilityController.Instance != null) { for (IChainedAbilityFieldProvider provider : AbilityController.Instance.getChainedFieldProviders()) { @@ -307,34 +318,46 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText btnLabel = "\u00A7e> " + (resolved != null ? resolved.getDisplayName() : refName); } } - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE, L_LABEL_X + 15, y, 140, 20, btnLabel)); - - // Delay label + field - sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.delay", L_LABEL_X + 160, y + 5, 0xAAAAAA)); - GuiNpcTextField delayField = new GuiNpcTextField(ENTRY_BASE + i * ENTRY_STRIDE + 1, this, fontRendererObj, - L_LABEL_X + 195, y, 40, 20, String.valueOf(entry.getDelayTicks())); - delayField.setIntegersOnly(); - delayField.setMinMaxDefault(0, 6000, 0); - sw.addTextField(delayField); - - // Up button - if (i > 0) { - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 2, L_LABEL_X + 240, y, 20, 20, "\u2191")); + GuiNpcButton entryBtn = new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE, L_LABEL_X + 15, y, 140, 20, btnLabel); + if (readOnlyEntries) { + entryBtn.setEnabled(false); } + sw.addButton(entryBtn); - // Down button - if (i < entries.size() - 1) { - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 3, L_LABEL_X + 263, y, 20, 20, "\u2193")); + if (!readOnlyEntries) { + // Delay label + field + sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.delay", L_LABEL_X + 160, y + 5, 0xAAAAAA)); + GuiNpcTextField delayField = new GuiNpcTextField(ENTRY_BASE + i * ENTRY_STRIDE + 1, this, fontRendererObj, + L_LABEL_X + 195, y, 40, 20, String.valueOf(entry.getDelayTicks())); + delayField.setIntegersOnly(); + delayField.setMinMaxDefault(0, 6000, 0); + sw.addTextField(delayField); + + // Up button + if (i > 0) { + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 2, L_LABEL_X + 240, y, 20, 20, "\u2191")); + } + + // Down button + if (i < entries.size() - 1) { + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 3, L_LABEL_X + 263, y, 20, 20, "\u2193")); + } + + // Delete button + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 4, L_LABEL_X + 288, y, 20, 20, "X")); + } else { + // Read-only: show delay as label only + int delay = entry.getDelayTicks(); + if (delay > 0) { + sw.addLabel(new GuiNpcLabel(labelCounter++, delay + "t", L_LABEL_X + 165, y + 5, 0x888888)); + } } - // Delete button - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 4, L_LABEL_X + 288, y, 20, 20, "X")); - y += ROW_H; } - // Add entry button - if (entries.size() < MAX_ENTRIES) { + // Add entry button (hidden in read-only mode) + if (!readOnlyEntries && entries.size() < MAX_ENTRIES) { sw.addButton(new GuiNpcButton(BTN_ADD_ENTRY, L_LABEL_X, y, 80, 20, "gui.add")); y += ROW_H; } @@ -512,11 +535,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText if (entryIndex < 0 || entryIndex >= entries.size()) return false; switch (action) { - case 0: // Entry name button — reference: select ability; inline: edit config + case 0: // Entry name button — reference: clone/modify dialog; inline: edit config ChainedAbilityEntry clickedEntry = entries.get(entryIndex); if (clickedEntry.isReference()) { editingEntryIndex = entryIndex; - setSubGui(new SubGuiAbilitySelect()); + setSubGui(new SubGuiAbilityEditMode()); } else if (clickedEntry.isInline() && clickedEntry.getInlineAbility() != null) { editingEntryIndex = entryIndex; setSubGui(new SubGuiAbilityConfig(clickedEntry.getInlineAbility(), this)); @@ -626,13 +649,24 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText initGui(); return; } + if (subgui instanceof SubGuiAbilityVariantSelect) { + handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui); + if (!hasSubGui()) initGui(); + return; + } if (subgui instanceof SubGuiAbilityTypeSelect) { handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui); if (!hasSubGui()) initGui(); return; } + if (subgui instanceof SubGuiAbilityEditMode) { + handleEditModeClosed((SubGuiAbilityEditMode) subgui); + if (!hasSubGui()) initGui(); + return; + } if (subgui instanceof SubGuiAbilityConfig) { editingEntryIndex = -1; + editingParentAbility = false; initGui(); return; } @@ -687,8 +721,17 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private void handleTypeSelectClosed(SubGuiAbilityTypeSelect gui) { String typeId = gui.getSelectedTypeId(); if (typeId != null) { + List variants = AbilityController.Instance.getVariantsForType(typeId); + if (variants.size() > 1) { + pendingTypeId = typeId; + setSubGui(new SubGuiAbilityVariantSelect(variants)); + return; + } Ability newAbility = AbilityController.Instance.create(typeId); if (newAbility != null) { + if (variants.size() == 1) { + variants.get(0).apply(newAbility); + } newAbility.setId(java.util.UUID.randomUUID().toString()); entries.add(ChainedAbilityEntry.inline(newAbility, 0)); editingEntryIndex = entries.size() - 1; @@ -697,6 +740,53 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } } + private void handleVariantSelectClosed(SubGuiAbilityVariantSelect gui) { + int idx = gui.getSelectedIndex(); + if (idx >= 0 && pendingTypeId != null) { + Ability newAbility = AbilityController.Instance.create(pendingTypeId); + if (newAbility != null) { + gui.getVariants().get(idx).apply(newAbility); + newAbility.setId(java.util.UUID.randomUUID().toString()); + entries.add(ChainedAbilityEntry.inline(newAbility, 0)); + editingEntryIndex = entries.size() - 1; + pendingTypeId = null; + setSubGui(new SubGuiAbilityConfig(newAbility, this)); + return; + } + } + pendingTypeId = null; + } + + private void handleEditModeClosed(SubGuiAbilityEditMode gui) { + int mode = gui.getResult(); + if (mode < 0 || editingEntryIndex < 0 || editingEntryIndex >= entries.size()) { + editingEntryIndex = -1; + return; + } + + ChainedAbilityEntry entry = entries.get(editingEntryIndex); + + if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { + if (entry.convertToInline()) { + Ability a = entry.getInlineAbility(); + if (a != null) { + a.setId(java.util.UUID.randomUUID().toString()); + setSubGui(new SubGuiAbilityConfig(a, this)); + return; + } + } + editingEntryIndex = -1; + } else if (mode == SubGuiAbilityEditMode.MODE_MODIFY_PARENT) { + Ability resolved = entry.resolve(); + if (resolved != null) { + editingParentAbility = true; + setSubGui(resolved.createConfigGui(this)); + return; + } + editingEntryIndex = -1; + } + } + private void handleConditionEditClosed(SubGuiConditionEdit gui) { Condition result = gui.getResult(); if (result != null && editingConditionIndex >= 0) { @@ -715,6 +805,12 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText @Override public void onAbilitySaved(Ability ability) { + if (editingParentAbility) { + // Persist the global parent ability to the server + PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT())); + editingParentAbility = false; + return; + } // Inline ability mutated in-place; entry already holds the reference } From 0e6ece56c621f9d87caf7be13269067cbb71acd8 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 22:19:18 -0500 Subject: [PATCH 11/12] Fix DBC Toggle Abilities and Wheel System --- src/api | 2 +- .../controllers/data/ability/Ability.java | 82 ++++++++-- .../controllers/data/ability/ToggleEntry.java | 15 +- .../noppes/npcs/AbstractDataAbilities.java | 147 ++++++++++++++---- src/main/java/noppes/npcs/DataAbilities.java | 34 +++- src/main/java/noppes/npcs/EventHooks.java | 10 ++ .../noppes/npcs/constants/EnumScriptType.java | 2 + .../controllers/data/PlayerAbilityData.java | 44 +++++- .../npcs/scripted/event/AbilityEvent.java | 77 +++++++++ .../event/player/PlayerAbilityEvent.java | 77 +++++++++ .../wrapper/ScriptPlayerAbilityData.java | 22 +++ 11 files changed, 453 insertions(+), 59 deletions(-) diff --git a/src/api b/src/api index ca2980909..2c2f0c0d8 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit ca2980909ee597fa5b062ca945a2cce533277afc +Subproject commit 2c2f0c0d8f21f0ab3a3bc6bea168b3d3b85aa806 diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index f93a5c772..9f332e12d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -12,6 +12,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.util.StatCollector; @@ -115,15 +116,23 @@ public abstract class Ability implements IAbility, IAbilityAction { // ═══════════════════════════════════════════════════════════════════ /** - * Whether this ability is a toggle (ON/OFF) rather than a phased execution + * Number of toggle states. 0 = not toggleable, 1 = binary on/off, 2+ = multi-state cycling. + * Cycling order: Off(0) -> State 1 -> State 2 -> ... -> State N -> Off(0) */ - protected boolean toggleable = false; + protected int toggleStates = 0; /** * Whether the toggled-ON state ticks each game tick (e.g., Kaioken drains ki per tick) */ protected boolean hasActiveToggle = false; + /** + * Optional display labels for each toggle state (1-indexed). + * Used in ability wheel/hotbar to show current state name. + * Null means no labels. Array length should match toggleStates. + */ + protected String[] toggleStateLabels; + // Configurable potion effects protected List effects = new ArrayList<>(); @@ -232,26 +241,38 @@ public abstract class Ability implements IAbility, IAbilityAction { // ═══════════════════════════════════════════════════════════════════ /** - * Called when this toggle ability is turned ON for an entity. + * Called when this toggle ability is first activated (state goes from 0 to any active state). * Override to apply effects (e.g., set a DBC flag, apply a buff). */ public void onToggleOn(EntityLivingBase caster) { } /** - * Called when this toggle ability is turned OFF for an entity. + * Called when this toggle ability is fully deactivated (state goes to 0). * Override to remove effects (e.g., clear a DBC flag, remove a buff). */ public void onToggleOff(EntityLivingBase caster) { } + /** + * Called whenever the toggle state changes (including on/off transitions and mid-cycling). + * For multi-state toggles, this fires on every state transition. + * + * @param caster The entity + * @param oldState Previous state (0 = was off) + * @param newState New state (0 = now off, 1+ = active state) + */ + public void onToggleStateChanged(EntityLivingBase caster, int oldState, int newState) { + } + /** * Called every tick while this toggle is active (only if hasActiveToggle is true). * Override for per-tick effects (e.g., ki drain for Kaioken). * + * @param state Current toggle state (1-based) * @return true to keep the toggle active, false to auto-deactivate */ - public boolean onToggleTick(EntityLivingBase caster, int tickCount) { + public boolean onToggleTick(EntityLivingBase caster, int tickCount, int state) { return true; } @@ -1231,8 +1252,15 @@ public abstract class Ability implements IAbility, IAbilityAction { nbt.setBoolean("ignoreCooldown", ignoreCooldown); // Toggle - nbt.setBoolean("toggleable", toggleable); + nbt.setInteger("toggleStates", toggleStates); nbt.setBoolean("hasActiveToggle", hasActiveToggle); + if (toggleStateLabels != null && toggleStateLabels.length > 0) { + NBTTagList labelList = new NBTTagList(); + for (String label : toggleStateLabels) { + labelList.appendTag(new NBTTagString(label != null ? label : "")); + } + nbt.setTag("toggleStateLabels", labelList); + } // Burst nbt.setBoolean("burstEnabled", burstEnabled); @@ -1305,8 +1333,18 @@ public abstract class Ability implements IAbility, IAbilityAction { ignoreCooldown = nbt.getBoolean("ignoreCooldown"); // Toggle - toggleable = nbt.getBoolean("toggleable"); + toggleStates = nbt.getInteger("toggleStates"); hasActiveToggle = nbt.getBoolean("hasActiveToggle"); + if (nbt.hasKey("toggleStateLabels")) { + NBTTagList labelList = nbt.getTagList("toggleStateLabels", 8); + toggleStateLabels = new String[labelList.tagCount()]; + for (int i = 0; i < labelList.tagCount(); i++) { + String label = labelList.getStringTagAt(i); + toggleStateLabels[i] = (label != null && !label.isEmpty()) ? label : null; + } + } else { + toggleStateLabels = null; + } // Burst burstEnabled = nbt.getBoolean("burstEnabled"); @@ -1882,11 +1920,15 @@ public abstract class Ability implements IAbility, IAbilityAction { } public boolean isToggleable() { - return toggleable; + return toggleStates > 0; } - public void setToggleable(boolean toggleable) { - this.toggleable = toggleable; + public int getToggleStates() { + return toggleStates; + } + + public void setToggleStates(int toggleStates) { + this.toggleStates = Math.max(0, toggleStates); } public boolean hasActiveToggle() { @@ -1897,6 +1939,26 @@ public abstract class Ability implements IAbility, IAbilityAction { this.hasActiveToggle = hasActiveToggle; } + /** + * Get the display label for a specific toggle state. + * @param state 1-based state index + * @return the label, or null if none set + */ + public String getToggleStateLabel(int state) { + if (toggleStateLabels != null && state >= 1 && state <= toggleStateLabels.length) { + return toggleStateLabels[state - 1]; + } + return null; + } + + /** + * Set display labels for toggle states (1-indexed). + * @param labels one label per state, null entries allowed + */ + public void setToggleStateLabels(String... labels) { + this.toggleStateLabels = (labels != null && labels.length > 0) ? labels : null; + } + public List getEffects() { return effects; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java index e08350601..4a758bead 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java @@ -3,14 +3,19 @@ package kamkeel.npcs.controllers.data.ability; /** * Tracks the state of a single active toggle for an entity. * Stored in AbstractDataAbilities.activeToggles map. + *

+ * State is 1-based: state 1 = first ON state, state 2 = second, etc. + * State 0 means OFF (entry should not exist in the map). */ public class ToggleEntry { private final Ability ability; private int tickCount; + private int state; - public ToggleEntry(Ability ability) { + public ToggleEntry(Ability ability, int state) { this.ability = ability; this.tickCount = 0; + this.state = Math.max(1, state); } public Ability getAbility() { @@ -24,4 +29,12 @@ public class ToggleEntry { public void incrementTick() { tickCount++; } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = Math.max(1, state); + } } diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 13d3095c9..717c6c0c9 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -269,41 +269,65 @@ public abstract class AbstractDataAbilities { // ═══════════════════════════════════════════════════════════════════ /** - * Currently active toggles. Key = ability key, Value = toggle entry with tick counter. + * Currently active toggles. Key = ability key, Value = toggle entry with state and tick counter. */ protected Map activeToggles = new LinkedHashMap<>(); /** - * Toggle an ability ON or OFF. If currently on, turns off; if off, turns on. + * Cycle a toggle ability to its next state. + * Off(0) -> State 1 -> State 2 -> ... -> State N -> Off(0) * * @param key The ability key (e.g., "npcdbc:ki_fist") - * @return true if the toggle is now ON, false if OFF + * @return The new state (0 = off, 1+ = active state number) */ - public boolean toggleAbility(String key) { - if (activeToggles.containsKey(key)) { - deactivateToggle(key); - return false; + public int toggleAbility(String key) { + ToggleEntry existing = activeToggles.get(key); + if (existing != null) { + int currentState = existing.getState(); + int maxStates = existing.getAbility().getToggleStates(); + if (currentState < maxStates) { + return cycleToggleState(key, existing, currentState, currentState + 1); + } else { + deactivateToggle(key); + return 0; + } } else { - return activateToggle(key); + return activateToggle(key, 1) ? 1 : 0; } } /** - * Set a toggle to a specific state. - * - * @param key The ability key - * @param on true to activate, false to deactivate + * Get the current toggle state for an ability. + * @return 0 if not active, 1+ for active state */ - public void setAbilityToggled(String key, boolean on) { - if (on && !activeToggles.containsKey(key)) { - activateToggle(key); - } else if (!on && activeToggles.containsKey(key)) { - deactivateToggle(key); + public int getToggleState(String key) { + ToggleEntry entry = activeToggles.get(key); + return entry != null ? entry.getState() : 0; + } + + /** + * Set a toggle to a specific state. 0 = deactivate, 1+ = specific state. + */ + public void setToggleState(String key, int state) { + if (state <= 0) { + if (activeToggles.containsKey(key)) { + deactivateToggle(key); + } + return; + } + ToggleEntry existing = activeToggles.get(key); + if (existing != null) { + int currentState = existing.getState(); + if (currentState != state) { + cycleToggleState(key, existing, currentState, state); + } + } else { + activateToggle(key, state); } } /** - * Check if a toggle ability is currently active. + * Check if a toggle ability is currently active (any state > 0). */ public boolean isAbilityToggled(String key) { return activeToggles.containsKey(key); @@ -316,30 +340,52 @@ public abstract class AbstractDataAbilities { return new LinkedHashSet<>(activeToggles.keySet()); } - private boolean activateToggle(String key) { + private boolean activateToggle(String key, int state) { Ability ability = AbilityController.Instance != null ? AbilityController.Instance.resolveAbility(key) : null; if (ability == null || !ability.isToggleable()) return false; + if (state < 1 || state > ability.getToggleStates()) state = 1; - ToggleEntry entry = new ToggleEntry(ability); + if (fireToggleEvent(ability, 0, state)) return false; + + ToggleEntry entry = new ToggleEntry(ability, state); activeToggles.put(key, entry); ability.onToggleOn(getEntity()); - onToggleStateChanged(key, true); + ability.onToggleStateChanged(getEntity(), 0, state); + onToggleStateChanged(key, true, state); return true; } + private int cycleToggleState(String key, ToggleEntry entry, int oldState, int newState) { + Ability ability = entry.getAbility(); + if (newState < 1 || newState > ability.getToggleStates()) return oldState; + + if (fireToggleEvent(ability, oldState, newState)) return oldState; + + entry.setState(newState); + ability.onToggleStateChanged(getEntity(), oldState, newState); + onToggleStateChanged(key, true, newState); + return newState; + } + private void deactivateToggle(String key) { - ToggleEntry entry = activeToggles.remove(key); - if (entry != null) { - entry.getAbility().onToggleOff(getEntity()); - onToggleStateChanged(key, false); - } + ToggleEntry entry = activeToggles.get(key); + if (entry == null) return; + int oldState = entry.getState(); + + if (fireToggleEvent(entry.getAbility(), oldState, 0)) return; + + activeToggles.remove(key); + entry.getAbility().onToggleOff(getEntity()); + entry.getAbility().onToggleStateChanged(getEntity(), oldState, 0); + onToggleStateChanged(key, false, 0); } /** * Tick all active toggles. Call from tick() each game tick. * Toggles with hasActiveToggle=true get their onToggleTick() called. * If onToggleTick returns false, the toggle is auto-deactivated. + * Every 10 ticks, fires a toggle update event for ALL active toggles. */ protected void tickActiveToggles() { if (activeToggles.isEmpty()) return; @@ -352,7 +398,16 @@ public abstract class AbstractDataAbilities { entry.incrementTick(); if (entry.getAbility().hasActiveToggle()) { - if (!entry.getAbility().onToggleTick(entity, entry.getTickCount())) { + if (!entry.getAbility().onToggleTick(entity, entry.getTickCount(), entry.getState())) { + if (toRemove == null) toRemove = new ArrayList<>(); + toRemove.add(mapEntry.getKey()); + continue; + } + } + + if (entry.getTickCount() % 10 == 0) { + boolean enabled = fireToggleUpdateEvent(entry.getAbility(), entry.getTickCount(), entry.getState()); + if (!enabled) { if (toRemove == null) toRemove = new ArrayList<>(); toRemove.add(mapEntry.getKey()); } @@ -363,8 +418,10 @@ public abstract class AbstractDataAbilities { for (String key : toRemove) { ToggleEntry entry = activeToggles.remove(key); if (entry != null) { + int oldState = entry.getState(); entry.getAbility().onToggleOff(entity); - onToggleStateChanged(key, false); + entry.getAbility().onToggleStateChanged(entity, oldState, 0); + onToggleStateChanged(key, false, 0); } } } @@ -373,18 +430,38 @@ public abstract class AbstractDataAbilities { /** * Hook for subclass to react to toggle state changes (sync packets, script events). */ - protected void onToggleStateChanged(String key, boolean active) { + protected void onToggleStateChanged(String key, boolean active, int state) { + } + + /** + * Fire a toggle event before state change. + * @param oldState Previous state (0 = off) + * @param newState Target state (0 = off) + * @return true if the event was canceled + */ + protected boolean fireToggleEvent(Ability ability, int oldState, int newState) { + return false; + } + + /** + * Fire a toggle update event every 10 ticks. + * @return true if the toggle should remain active, false to force-deactivate + */ + protected boolean fireToggleUpdateEvent(Ability ability, int tick, int state) { + return true; } /** * Clear all active toggles (e.g., on death/reset). - * Calls onToggleOff for each active toggle. + * Calls onToggleOff and onToggleStateChanged for each active toggle. */ protected void clearActiveToggles() { if (activeToggles.isEmpty()) return; EntityLivingBase entity = getEntity(); - for (ToggleEntry entry : activeToggles.values()) { + for (Map.Entry mapEntry : activeToggles.entrySet()) { + ToggleEntry entry = mapEntry.getValue(); entry.getAbility().onToggleOff(entity); + entry.getAbility().onToggleStateChanged(entity, entry.getState(), 0); } activeToggles.clear(); } @@ -392,13 +469,15 @@ public abstract class AbstractDataAbilities { /** * Add a toggle entry directly without calling onToggleOn. * Used for client-side sync and NBT restoration. + * @param key The ability key + * @param state The toggle state (0 = remove, 1+ = set at state) */ - public void setToggleEntryDirect(String key, boolean active) { - if (active) { + public void setToggleEntryDirect(String key, int state) { + if (state > 0) { Ability ability = AbilityController.Instance != null ? AbilityController.Instance.resolveAbility(key) : null; if (ability != null && ability.isToggleable()) { - activeToggles.put(key, new ToggleEntry(ability)); + activeToggles.put(key, new ToggleEntry(ability, state)); } } else { activeToggles.remove(key); diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 1caca740a..988a2f073 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.ToggleEntry; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; @@ -22,6 +23,7 @@ import noppes.npcs.scripted.event.AbilityEvent; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Random; /** @@ -153,6 +155,21 @@ public class DataAbilities extends AbstractDataAbilities { NpcAPI.EVENT_BUS.post(interruptEvent); } + @Override + protected boolean fireToggleEvent(Ability ability, int oldState, int newState) { + AbilityEvent.ToggleEvent event = new AbilityEvent.ToggleEvent( + npc.wrappedNPC, ability, oldState, newState); + return NpcAPI.EVENT_BUS.post(event); + } + + @Override + protected boolean fireToggleUpdateEvent(Ability ability, int tick, int state) { + AbilityEvent.ToggleUpdateEvent event = new AbilityEvent.ToggleUpdateEvent( + npc.wrappedNPC, ability, tick, state); + NpcAPI.EVENT_BUS.post(event); + return event.isEnabled(); + } + @Override protected void spawnTelegraph(Ability ability, EntityLivingBase target) { List telegraphs = ability.createTelegraphs(npc, target); @@ -1060,10 +1077,13 @@ public class DataAbilities extends AbstractDataAbilities { } compound.setTag("AbilityActions", actionList); - // Active toggles + // Active toggles (compound format with state) NBTTagList toggleList = new NBTTagList(); - for (String key : activeToggles.keySet()) { - toggleList.appendTag(new NBTTagString(key)); + for (Map.Entry entry : activeToggles.entrySet()) { + NBTTagCompound toggleNbt = new NBTTagCompound(); + toggleNbt.setString("Key", entry.getKey()); + toggleNbt.setInteger("State", entry.getValue().getState()); + toggleList.appendTag(toggleNbt); } compound.setTag("ActiveToggles", toggleList); @@ -1111,10 +1131,12 @@ public class DataAbilities extends AbstractDataAbilities { // Active toggles - restore state directly (no onToggleOn callback during load) activeToggles.clear(); if (compound.hasKey("ActiveToggles")) { - NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 8); // 8 = TAG_STRING + NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 10); // 10 = TAG_COMPOUND for (int i = 0; i < toggleNbt.tagCount(); i++) { - String key = toggleNbt.getStringTagAt(i); - setToggleEntryDirect(key, true); + NBTTagCompound entry = toggleNbt.getCompoundTagAt(i); + String key = entry.getString("Key"); + int state = entry.hasKey("State") ? entry.getInteger("State") : 1; + setToggleEntryDirect(key, state); } } } diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java index 2b9ebaabe..76029adab 100644 --- a/src/main/java/noppes/npcs/EventHooks.java +++ b/src/main/java/noppes/npcs/EventHooks.java @@ -881,6 +881,16 @@ public class EventHooks { return NpcAPI.EVENT_BUS.post(event); } + public static boolean onPlayerAbilityToggle(PlayerDataScript handler, PlayerAbilityEvent.ToggleEvent event) { + handler.callScript(EnumScriptType.ABILITY_TOGGLE, event); + return NpcAPI.EVENT_BUS.post(event); + } + + public static void onPlayerAbilityToggleUpdate(PlayerDataScript handler, PlayerAbilityEvent.ToggleUpdateEvent event) { + handler.callScript(EnumScriptType.ABILITY_TOGGLE_UPDATE, event); + NpcAPI.EVENT_BUS.post(event); + } + public static void onPlayerChangeDim(PlayerDataScript handler, IPlayer player, int fromDim, int toDim) { PlayerEvent.ChangedDimension event = new PlayerEvent.ChangedDimension(player, fromDim, toDim); handler.callScript(EnumScriptType.CHANGED_DIM, event); diff --git a/src/main/java/noppes/npcs/constants/EnumScriptType.java b/src/main/java/noppes/npcs/constants/EnumScriptType.java index 1088ef83a..5b5a318b3 100644 --- a/src/main/java/noppes/npcs/constants/EnumScriptType.java +++ b/src/main/java/noppes/npcs/constants/EnumScriptType.java @@ -126,6 +126,8 @@ public enum EnumScriptType { ABILITY_COMPLETE("abilityComplete"), ABILITY_HIT("abilityHit"), ABILITY_TICK("abilityTick"), + ABILITY_TOGGLE("abilityToggle"), + ABILITY_TOGGLE_UPDATE("abilityToggleUpdate"), ENERGY_PROJECTILE_FIRED("energyProjectileFired"), ENERGY_PROJECTILE_TICK("energyProjectileTick"), diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 8562053c2..f06c21686 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -5,6 +5,7 @@ import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.ToggleEntry; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket; @@ -30,6 +31,7 @@ import noppes.npcs.scripted.event.player.PlayerAbilityEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; /** * Manages player ability data - unlocked abilities, selection, cooldowns, and execution. @@ -422,10 +424,33 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA } @Override - protected void onToggleStateChanged(String key, boolean active) { + protected void onToggleStateChanged(String key, boolean active, int state) { syncToClient(); } + @Override + protected boolean fireToggleEvent(Ability ability, int oldState, int newState) { + EntityPlayer player = playerData.player; + if (ScriptController.Instance == null || player == null) return false; + PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player); + if (handler == null) return false; + IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player); + PlayerAbilityEvent.ToggleEvent event = new PlayerAbilityEvent.ToggleEvent(iPlayer, ability, oldState, newState); + return EventHooks.onPlayerAbilityToggle(handler, event); + } + + @Override + protected boolean fireToggleUpdateEvent(Ability ability, int tick, int state) { + EntityPlayer player = playerData.player; + if (ScriptController.Instance == null || player == null) return true; + PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player); + if (handler == null) return true; + IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player); + PlayerAbilityEvent.ToggleUpdateEvent event = new PlayerAbilityEvent.ToggleUpdateEvent(iPlayer, ability, tick, state); + EventHooks.onPlayerAbilityToggleUpdate(handler, event); + return event.isEnabled(); + } + // ═══════════════════════════════════════════════════════════════════ // SELECTION // ═══════════════════════════════════════════════════════════════════ @@ -837,10 +862,13 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA compound.setInteger("PlayerAbilitySelected", selectedIndex); compound.setBoolean("AbilityAnimating", playingAbilityAnimation); - // Active toggles + // Active toggles (compound format with state) NBTTagList toggleList = new NBTTagList(); - for (String key : activeToggles.keySet()) { - toggleList.appendTag(new NBTTagString(key)); + for (Map.Entry entry : activeToggles.entrySet()) { + NBTTagCompound toggleNbt = new NBTTagCompound(); + toggleNbt.setString("Key", entry.getKey()); + toggleNbt.setInteger("State", entry.getValue().getState()); + toggleList.appendTag(toggleNbt); } compound.setTag("ActiveToggles", toggleList); } @@ -869,10 +897,12 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Active toggles - restore state directly (no onToggleOn callback during load) activeToggles.clear(); if (compound.hasKey("ActiveToggles")) { - NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 8); // 8 = TAG_STRING + NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 10); // 10 = TAG_COMPOUND for (int i = 0; i < toggleNbt.tagCount(); i++) { - String key = toggleNbt.getStringTagAt(i); - setToggleEntryDirect(key, true); + NBTTagCompound entry = toggleNbt.getCompoundTagAt(i); + String key = entry.getString("Key"); + int state = entry.hasKey("State") ? entry.getInteger("State") : 1; + setToggleEntryDirect(key, state); } } } diff --git a/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java b/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java index 9bf7719d5..c7a43cfe7 100644 --- a/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java +++ b/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java @@ -203,4 +203,81 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent { return EnumScriptType.ABILITY_TICK.function; } } + + /** + * Fired when a toggle ability changes state. + * Canceling prevents the toggle from changing state. + */ + @Cancelable + public static class ToggleEvent extends AbilityEvent implements IAbilityEvent.ToggleEvent { + private final int oldState; + private final int newState; + + public ToggleEvent(ICustomNpc npc, Ability ability, int oldState, int newState) { + super(npc, ability, null); + this.oldState = oldState; + this.newState = newState; + } + + @Override + public boolean isTogglingOn() { + return newState > 0; + } + + @Override + public int getOldState() { + return oldState; + } + + @Override + public int getNewState() { + return newState; + } + + @Override + public String getHookName() { + return EnumScriptType.ABILITY_TOGGLE.function; + } + } + + /** + * Fired every 10 ticks for each active toggle ability. + * Scripts can set enabled to false to force-deactivate. + */ + public static class ToggleUpdateEvent extends AbilityEvent implements IAbilityEvent.ToggleUpdateEvent { + private final int tick; + private final int state; + private boolean enabled = true; + + public ToggleUpdateEvent(ICustomNpc npc, Ability ability, int tick, int state) { + super(npc, ability, null); + this.tick = tick; + this.state = state; + } + + @Override + public int getTick() { + return tick; + } + + @Override + public int getState() { + return state; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getHookName() { + return EnumScriptType.ABILITY_TOGGLE_UPDATE.function; + } + } } diff --git a/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java b/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java index b99aac1a1..e5d46fd15 100644 --- a/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java +++ b/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java @@ -202,4 +202,81 @@ public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEve return EnumScriptType.ABILITY_HIT.function; } } + + /** + * Fired when a toggle ability changes state. + * Canceling prevents the toggle from changing state. + */ + @Cancelable + public static class ToggleEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.ToggleEvent { + private final int oldState; + private final int newState; + + public ToggleEvent(IPlayer player, Ability ability, int oldState, int newState) { + super(player, ability, null); + this.oldState = oldState; + this.newState = newState; + } + + @Override + public boolean isTogglingOn() { + return newState > 0; + } + + @Override + public int getOldState() { + return oldState; + } + + @Override + public int getNewState() { + return newState; + } + + @Override + public String getHookName() { + return EnumScriptType.ABILITY_TOGGLE.function; + } + } + + /** + * Fired every 10 ticks for each active toggle ability. + * Scripts can set enabled to false to force-deactivate. + */ + public static class ToggleUpdateEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.ToggleUpdateEvent { + private final int tick; + private final int state; + private boolean enabled = true; + + public ToggleUpdateEvent(IPlayer player, Ability ability, int tick, int state) { + super(player, ability, null); + this.tick = tick; + this.state = state; + } + + @Override + public int getTick() { + return tick; + } + + @Override + public int getState() { + return state; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getHookName() { + return EnumScriptType.ABILITY_TOGGLE_UPDATE.function; + } + } } diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java index c66a12a48..70d915537 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java @@ -134,6 +134,28 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { return data.activateAbility(player, key); } + // Toggle system + + @Override + public int toggleAbility(String key) { + return data.toggleAbility(key); + } + + @Override + public int getToggleState(String key) { + return data.getToggleState(key); + } + + @Override + public void setToggleState(String key, int state) { + data.setToggleState(key, state); + } + + @Override + public boolean isAbilityToggled(String key) { + return data.isAbilityToggled(key); + } + /** * Resolve a user-provided key to the canonical storage key. * Uses the same resolution as unlockAbility: registry key for built-in, UUID for custom. From 9f8f742a0ecc0f83700d4b6c4c069a65a9d07cc3 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 22:41:19 -0500 Subject: [PATCH 12/12] Beta 4 --- build.gradle | 2 +- src/main/java/noppes/npcs/CustomNpcs.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0babda6bb..ef7d8145f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'idea' } -version = "1.11-beta3" +version = "1.11-beta4" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 185131a15..f7c76f555 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -121,7 +121,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta3") +@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta4") public class CustomNpcs { @SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy")