mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 04:57:27 +00:00
Remove all Legacy Ability Content, cleanup API.
- Add Script Tab to Abilities - Add Custom Ability - Add Icon Support for Abilities / Default Icon Support - Remove Icon Tab and throw it into Effects with allowed Override - Removed Deprecated API Sources
This commit is contained in:
1 parent
936dc7f0cc
commit
675c4ef4f9
50 files changed
+1488
-938
No files matched your search
+1
-1
Submodule src/api updated: aac5f98be8...ae98d12faf.
@@ -17,6 +17,7 @@ import kamkeel.npcs.controllers.data.ability.enums.UserType;
|
||||
import kamkeel.npcs.controllers.data.ability.extender.IAbilityExtender;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.IAbilityFieldProvider;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.IChainedAbilityFieldProvider;
|
||||
import kamkeel.npcs.controllers.data.ability.CustomAbility;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityCharge;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityCutter;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityDash;
|
||||
@@ -37,11 +38,15 @@ import kamkeel.npcs.controllers.data.ability.type.energy.AbilityOrb;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityWall;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import noppes.npcs.controllers.data.AbilityScript;
|
||||
import noppes.npcs.controllers.data.ChainedAbilityScript;
|
||||
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.ability.IChainedAbility;
|
||||
import noppes.npcs.api.handler.IAbilityHandler;
|
||||
import noppes.npcs.controllers.PlayerDataController;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
@@ -52,7 +57,6 @@ import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AbilityController implements IAbilityHandler {
|
||||
|
||||
@@ -64,6 +68,10 @@ public class AbilityController implements IAbilityHandler {
|
||||
private final Map<String, Ability> customAbilities = new LinkedHashMap<>(); // name → Ability
|
||||
private final Map<String, Ability> customAbilitiesById = new LinkedHashMap<>(); // UUID → Ability
|
||||
|
||||
// ── Script Handlers ────────────────────────────────────────────────────
|
||||
public final HashMap<String, AbilityScript> abilityScriptHandlers = new HashMap<>();
|
||||
public final HashMap<String, ChainedAbilityScript> chainedAbilityScriptHandlers = new HashMap<>();
|
||||
|
||||
// ── Extension Points ─────────────────────────────────────────────────────
|
||||
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
|
||||
private final List<IAbilityFieldProvider> fieldProviders = new ArrayList<>();
|
||||
@@ -72,11 +80,6 @@ public class AbilityController implements IAbilityHandler {
|
||||
private final Map<String, Supplier<AbilityCondition>> conditionTypes = new HashMap<>();
|
||||
private final List<Predicate<EntityPlayer>> flightCheckers = new ArrayList<>();
|
||||
|
||||
// ── Legacy Migration ─────────────────────────────────────────────────────
|
||||
private static final Pattern UUID_PATTERN =
|
||||
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<String, ChainedAbility> chainedAbilities = new LinkedHashMap<>(); // name → ChainedAbility
|
||||
private final Map<String, ChainedAbility> chainedAbilitiesById = new LinkedHashMap<>(); // UUID → ChainedAbility
|
||||
@@ -97,7 +100,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
// TYPE REGISTRATION
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void registerType(String factoryKey, Supplier<Ability> factory) {
|
||||
public void registerType(Supplier<Ability> factory) {
|
||||
Ability temp = factory.get();
|
||||
String typeId = temp.getTypeId();
|
||||
if (abilityTypes.containsKey(typeId)) {
|
||||
@@ -132,36 +135,35 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
private void registerBuiltinTypes() {
|
||||
registerType("cnpc:slam", AbilitySlam::new);
|
||||
registerType("cnpc:charge", AbilityCharge::new);
|
||||
registerType(CustomAbility::new);
|
||||
|
||||
registerType("cnpc:heavy_hit", AbilityHeavyHit::new);
|
||||
registerType("cnpc:cutter", AbilityCutter::new);
|
||||
registerType(AbilitySlam::new);
|
||||
registerType(AbilityCharge::new);
|
||||
|
||||
// registerType("cnpc:projectile", AbilityProjectile::new);
|
||||
registerType(AbilityHeavyHit::new);
|
||||
registerType(AbilityCutter::new);
|
||||
|
||||
registerType("cnpc:sweeper", AbilitySweeper::new);
|
||||
registerType("cnpc:orb", AbilityOrb::new);
|
||||
registerType("cnpc:disc", AbilityDisc::new);
|
||||
registerType("cnpc:laser_shot", AbilityLaser::new);
|
||||
registerType("cnpc:beam", AbilityBeam::new);
|
||||
// registerType("cnpc:slicer", AbilitySlicer::new);
|
||||
registerType(AbilitySweeper::new);
|
||||
registerType(AbilityOrb::new);
|
||||
registerType(AbilityDisc::new);
|
||||
registerType(AbilityLaser::new);
|
||||
registerType(AbilityBeam::new);
|
||||
|
||||
registerType("cnpc:dash", AbilityDash::new);
|
||||
registerType("cnpc:teleport", AbilityTeleport::new);
|
||||
registerType(AbilityDash::new);
|
||||
registerType(AbilityTeleport::new);
|
||||
|
||||
registerType("cnpc:vortex", AbilityVortex::new);
|
||||
registerType("cnpc:shockwave", AbilityShockwave::new);
|
||||
registerType(AbilityVortex::new);
|
||||
registerType(AbilityShockwave::new);
|
||||
|
||||
registerType("cnpc:guard", AbilityGuard::new);
|
||||
registerType("cnpc:effect", AbilityEffect::new);
|
||||
registerType(AbilityGuard::new);
|
||||
registerType(AbilityEffect::new);
|
||||
|
||||
registerType("cnpc:hazard", AbilityHazard::new);
|
||||
registerType("cnpc:trap", AbilityTrap::new);
|
||||
registerType(AbilityHazard::new);
|
||||
registerType(AbilityTrap::new);
|
||||
|
||||
registerType("cnpc:dome", AbilityDome::new);
|
||||
registerType("cnpc:wall", AbilityWall::new);
|
||||
registerType("cnpc:shield", AbilityShield::new);
|
||||
registerType(AbilityDome::new);
|
||||
registerType(AbilityWall::new);
|
||||
registerType(AbilityShield::new);
|
||||
}
|
||||
|
||||
private void registerBuiltinConditionTypes() {
|
||||
@@ -193,7 +195,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
|
||||
public Ability getAbilityByDisplayName(String displayName) {
|
||||
for (Ability ability : builtAbilities.values()) {
|
||||
if (ability.getName().equals(displayName)) {
|
||||
if (ability.getDisplayName().equals(displayName)) {
|
||||
return ability;
|
||||
}
|
||||
}
|
||||
@@ -214,8 +216,6 @@ public class AbilityController implements IAbilityHandler {
|
||||
public void load() {
|
||||
customAbilities.clear();
|
||||
customAbilitiesById.clear();
|
||||
int migrated = 0;
|
||||
Map<String, String> migratedAbilityNames = new HashMap<String, String>();
|
||||
|
||||
File dir = getDir();
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
@@ -230,59 +230,29 @@ 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;
|
||||
}
|
||||
|
||||
String storedName = ability.getName();
|
||||
String preferredName;
|
||||
if (UUID_PATTERN.matcher(fileKey).matches()) {
|
||||
preferredName = (storedName != null && !storedName.isEmpty()) ? storedName : fileKey;
|
||||
} else {
|
||||
preferredName = fileKey;
|
||||
}
|
||||
String name = FileNameHelper.sanitizeName(preferredName, "Ability");
|
||||
String name = FileNameHelper.sanitizeName(fileKey, "Ability");
|
||||
name = makeUniqueNameForLoad(customAbilities, dir, file, name, fileKey);
|
||||
|
||||
boolean dirty = false;
|
||||
if (!name.equals(fileKey)) {
|
||||
File renamedFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(renamedFile)) {
|
||||
LogWriter.info("Migrated ability file: " + fileKey + ".json -> " + name + ".json");
|
||||
file = renamedFile;
|
||||
migrated++;
|
||||
dirty = true;
|
||||
} else {
|
||||
LogWriter.error("Failed to rename ability file: " + fileKey + ".json -> " + name + ".json");
|
||||
}
|
||||
}
|
||||
|
||||
ability.setName(name);
|
||||
if (storedName != null && !storedName.equals(name)) {
|
||||
dirty = true;
|
||||
if (!storedName.isEmpty()) {
|
||||
migratedAbilityNames.put(storedName, name);
|
||||
}
|
||||
}
|
||||
if (!fileKey.equals(name)) {
|
||||
migratedAbilityNames.put(fileKey, name);
|
||||
}
|
||||
|
||||
// Ensure ability has a UUID; generate one for legacy abilities
|
||||
String uuid = ability.getId();
|
||||
if (uuid == null || uuid.isEmpty() || uuid.equals(fileKey) || uuid.equals(name)) {
|
||||
// Legacy: id was the name or empty — generate a real UUID
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
ability.setId(uuid);
|
||||
migrated++;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
NBTJsonUtil.SaveFile(file, ability.writeNBT());
|
||||
NBTJsonUtil.SaveFile(file, ability.writeNBT(true));
|
||||
}
|
||||
|
||||
customAbilities.put(name, ability);
|
||||
@@ -294,12 +264,9 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
customAbilityRevision++;
|
||||
if (migrated > 0) {
|
||||
LogWriter.info("Migrated " + migrated + " abilities (filename normalization / UUID repair)");
|
||||
}
|
||||
LogWriter.info("Loaded " + customAbilities.size() + " custom abilities");
|
||||
|
||||
loadChainedAbilities(migratedAbilityNames);
|
||||
loadChainedAbilities();
|
||||
}
|
||||
|
||||
private boolean nameFileExists(File dir, String name, File ignoredFile) {
|
||||
@@ -325,32 +292,6 @@ public class AbilityController implements IAbilityHandler {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private boolean remapChainEntryReferences(ChainedAbility chain, Map<String, String> migratedAbilityNames) {
|
||||
if (chain == null || migratedAbilityNames == null || migratedAbilityNames.isEmpty()) return false;
|
||||
boolean changed = false;
|
||||
for (ChainedAbilityEntry entry : chain.getEntries()) {
|
||||
if (entry == null || !entry.isReference()) continue;
|
||||
String ref = entry.getAbilityReference();
|
||||
if (ref == null || ref.isEmpty()) continue;
|
||||
|
||||
String mapped = migratedAbilityNames.get(ref);
|
||||
if (mapped == null) {
|
||||
for (Map.Entry<String, String> e : migratedAbilityNames.entrySet()) {
|
||||
if (e.getKey().equalsIgnoreCase(ref)) {
|
||||
mapped = e.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mapped != null && !mapped.equals(ref)) {
|
||||
entry.setAbilityReference(mapped);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean saveCustomAbility(Ability ability) {
|
||||
if (ability == null) return false;
|
||||
if (ability.isBuiltIn()) return false;
|
||||
@@ -396,7 +337,8 @@ public class AbilityController implements IAbilityHandler {
|
||||
File fileCurrent = new File(dir, name + ".json");
|
||||
|
||||
try {
|
||||
NBTTagCompound nbt = ability.writeNBT();
|
||||
NBTTagCompound nbt = ability.writeNBT(true);
|
||||
|
||||
NBTJsonUtil.SaveFile(fileNew, nbt);
|
||||
|
||||
if (fileCurrent.exists()) {
|
||||
@@ -434,10 +376,11 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
if (removed == null) return false;
|
||||
|
||||
// Also remove from UUID index
|
||||
// Also remove from UUID index and script handler
|
||||
String uuid = removed.getId();
|
||||
if (uuid != null && !uuid.isEmpty()) {
|
||||
customAbilitiesById.remove(uuid);
|
||||
abilityScriptHandlers.remove(uuid);
|
||||
}
|
||||
|
||||
File dir = getDir();
|
||||
@@ -459,6 +402,9 @@ public class AbilityController implements IAbilityHandler {
|
||||
pData.abilityData.lockAbility(name);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
pData.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -516,10 +462,9 @@ public class AbilityController implements IAbilityHandler {
|
||||
// CHAINED ABILITIES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
private void loadChainedAbilities(Map<String, String> migratedAbilityNames) {
|
||||
private void loadChainedAbilities() {
|
||||
chainedAbilities.clear();
|
||||
chainedAbilitiesById.clear();
|
||||
int migrated = 0;
|
||||
|
||||
File dir = getChainedDir();
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
@@ -534,7 +479,6 @@ public class AbilityController implements IAbilityHandler {
|
||||
ChainedAbility chain = new ChainedAbility();
|
||||
chain.readNBT(nbt);
|
||||
|
||||
String storedName = chain.getName();
|
||||
String name = FileNameHelper.sanitizeName(fileKey, "Chain");
|
||||
name = makeUniqueNameForLoad(chainedAbilities, dir, file, name, fileKey);
|
||||
|
||||
@@ -542,33 +486,22 @@ public class AbilityController implements IAbilityHandler {
|
||||
if (!name.equals(fileKey)) {
|
||||
File renamedFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(renamedFile)) {
|
||||
LogWriter.info("Migrated chained ability file: " + fileKey + ".json -> " + name + ".json");
|
||||
file = renamedFile;
|
||||
dirty = true;
|
||||
} else {
|
||||
LogWriter.error("Failed to rename chained ability file: " + fileKey + ".json -> " + name + ".json");
|
||||
}
|
||||
}
|
||||
|
||||
chain.setName(name);
|
||||
if (storedName != null && !storedName.equals(name)) {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (remapChainEntryReferences(chain, migratedAbilityNames)) {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
String uuid = chain.getId();
|
||||
if (uuid == null || uuid.isEmpty() || uuid.equals(fileKey) || uuid.equals(name)) {
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
chain.setId(uuid);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
NBTJsonUtil.SaveFile(file, chain.writeNBT());
|
||||
migrated++;
|
||||
NBTJsonUtil.SaveFile(file, chain.writeNBT(true));
|
||||
}
|
||||
|
||||
chainedAbilities.put(name, chain);
|
||||
@@ -580,9 +513,6 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
chainedAbilityRevision++;
|
||||
if (migrated > 0) {
|
||||
LogWriter.info("Migrated " + migrated + " chained abilities");
|
||||
}
|
||||
LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities");
|
||||
}
|
||||
|
||||
@@ -626,7 +556,8 @@ public class AbilityController implements IAbilityHandler {
|
||||
File fileCurrent = new File(dir, name + ".json");
|
||||
|
||||
try {
|
||||
NBTTagCompound nbt = chain.writeNBT();
|
||||
NBTTagCompound nbt = chain.writeNBT(true);
|
||||
|
||||
NBTJsonUtil.SaveFile(fileNew, nbt);
|
||||
|
||||
if (fileCurrent.exists()) {
|
||||
@@ -667,6 +598,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
String uuid = removed.getId();
|
||||
if (uuid != null && !uuid.isEmpty()) {
|
||||
chainedAbilitiesById.remove(uuid);
|
||||
chainedAbilityScriptHandlers.remove(uuid);
|
||||
}
|
||||
|
||||
File dir = getChainedDir();
|
||||
@@ -775,7 +707,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveChainedAbility(noppes.npcs.api.ability.IChainedAbility chain) {
|
||||
public boolean saveChainedAbility(IChainedAbility chain) {
|
||||
if (chain instanceof ChainedAbility) {
|
||||
return saveChainedAbility((ChainedAbility) chain);
|
||||
}
|
||||
@@ -818,48 +750,36 @@ public class AbilityController implements IAbilityHandler {
|
||||
Ability byUuid = customAbilitiesById.get(key);
|
||||
if (byUuid != null) return byUuid.deepCopy();
|
||||
|
||||
// Built-in: exact name
|
||||
// Built-in: exact key
|
||||
Ability builtIn = builtAbilities.get(key);
|
||||
if (builtIn != null) return builtIn.deepCopy();
|
||||
|
||||
// Built-in: case-insensitive map key
|
||||
// Built-in: case-insensitive key
|
||||
String lowerKey = key.toLowerCase();
|
||||
for (Map.Entry<String, Ability> entry : builtAbilities.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(key)) {
|
||||
if (entry.getKey().equalsIgnoreCase(lowerKey)) {
|
||||
return entry.getValue().deepCopy();
|
||||
}
|
||||
}
|
||||
|
||||
// Built-in: by ability name (name may differ from map key, e.g. "Ki Fist" vs "ki_fist")
|
||||
// Built-in: by ID (case-insensitive)
|
||||
for (Ability ability : builtAbilities.values()) {
|
||||
if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) {
|
||||
if (key.equalsIgnoreCase(ability.getId())) {
|
||||
return ability.deepCopy();
|
||||
}
|
||||
}
|
||||
|
||||
// Built-in: registry key / ID
|
||||
for (Ability ability : builtAbilities.values()) {
|
||||
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) {
|
||||
return ability.deepCopy();
|
||||
}
|
||||
}
|
||||
|
||||
// Custom: exact name (fallback for legacy references)
|
||||
// Custom: exact name
|
||||
Ability custom = customAbilities.get(key);
|
||||
if (custom != null) return custom.deepCopy();
|
||||
|
||||
// Custom: case-insensitive name
|
||||
for (Ability ability : customAbilities.values()) {
|
||||
if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) {
|
||||
return ability.deepCopy();
|
||||
for (Map.Entry<String, Ability> entry : customAbilities.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(key)) {
|
||||
return entry.getValue().deepCopy();
|
||||
}
|
||||
}
|
||||
|
||||
String sanitizedKey = FileNameHelper.sanitizeTextInput(key);
|
||||
if (!sanitizedKey.equals(key)) {
|
||||
Ability sanitized = customAbilities.get(sanitizedKey);
|
||||
if (sanitized != null) return sanitized.deepCopy();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -904,14 +824,8 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
public boolean hasAbility(String key) {
|
||||
if (builtAbilities.containsKey(key) || customAbilities.containsKey(key)
|
||||
|| customAbilitiesById.containsKey(key)) {
|
||||
return true;
|
||||
}
|
||||
String sanitized = FileNameHelper.sanitizeTextInput(key);
|
||||
return !sanitized.equals(key)
|
||||
&& (builtAbilities.containsKey(sanitized) || customAbilities.containsKey(sanitized)
|
||||
|| customAbilitiesById.containsKey(sanitized));
|
||||
return builtAbilities.containsKey(key) || customAbilities.containsKey(key)
|
||||
|| customAbilitiesById.containsKey(key);
|
||||
}
|
||||
|
||||
public Ability getCustomAbilityByUUID(String uuid) {
|
||||
@@ -919,12 +833,30 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
|
||||
public Ability getCustomAbilityByName(String name) {
|
||||
Ability ability = customAbilities.get(name);
|
||||
if (ability != null) return ability;
|
||||
String sanitized = FileNameHelper.sanitizeTextInput(name);
|
||||
if (!sanitized.equals(name)) {
|
||||
return customAbilities.get(sanitized);
|
||||
return customAbilities.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an ability by key and return the live (non-copied) reference.
|
||||
* For validation/permission checks only — do NOT expose or modify the returned ability.
|
||||
* Uses the same lookup chain as {@link #resolveAbility(String)}.
|
||||
*/
|
||||
public Ability peekAbility(String key) {
|
||||
if (key == null || key.isEmpty()) return null;
|
||||
|
||||
Ability byUuid = customAbilitiesById.get(key);
|
||||
if (byUuid != null) return byUuid;
|
||||
|
||||
Ability builtIn = builtAbilities.get(key);
|
||||
if (builtIn != null) return builtIn;
|
||||
|
||||
for (Ability ability : builtAbilities.values()) {
|
||||
if (key.equals(ability.getId())) return ability;
|
||||
}
|
||||
|
||||
Ability custom = customAbilities.get(key);
|
||||
if (custom != null) return custom;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -935,33 +867,20 @@ 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)
|
||||
// Custom: UUID lookup
|
||||
if (customAbilitiesById.containsKey(key)) return true;
|
||||
|
||||
// Built-in: exact name
|
||||
// Built-in: exact key
|
||||
if (builtAbilities.containsKey(key)) return true;
|
||||
|
||||
// Built-in: case-insensitive name
|
||||
for (String name : builtAbilities.keySet()) {
|
||||
if (name.equalsIgnoreCase(key)) return true;
|
||||
}
|
||||
|
||||
// Built-in: registry key / ID
|
||||
// Built-in: by ID
|
||||
for (Ability ability : builtAbilities.values()) {
|
||||
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) return true;
|
||||
if (key.equals(ability.getId())) return true;
|
||||
}
|
||||
|
||||
// Custom: exact name (fallback for legacy references)
|
||||
// Custom: exact name
|
||||
if (customAbilities.containsKey(key)) return true;
|
||||
|
||||
// Custom: case-insensitive name
|
||||
for (Ability ability : customAbilities.values()) {
|
||||
if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) return true;
|
||||
}
|
||||
|
||||
String sanitizedKey = FileNameHelper.sanitizeTextInput(key);
|
||||
if (!sanitizedKey.equals(key) && customAbilities.containsKey(sanitizedKey)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ public class SyncController {
|
||||
NBTTagList list = new NBTTagList();
|
||||
NBTTagCompound compound = new NBTTagCompound();
|
||||
for (Map.Entry<String, Ability> entry : AbilityController.Instance.getCustomAbilities().entrySet()) {
|
||||
NBTTagCompound abilityNBT = entry.getValue().writeNBT();
|
||||
NBTTagCompound abilityNBT = entry.getValue().writeNBT(false);
|
||||
abilityNBT.setString("CustomAbilityId", entry.getKey());
|
||||
list.appendTag(abilityNBT);
|
||||
}
|
||||
@@ -358,7 +358,7 @@ public class SyncController {
|
||||
NBTTagList list = new NBTTagList();
|
||||
NBTTagCompound compound = new NBTTagCompound();
|
||||
for (Map.Entry<String, ChainedAbility> entry : AbilityController.Instance.getChainedAbilities().entrySet()) {
|
||||
NBTTagCompound chainNBT = entry.getValue().writeNBT();
|
||||
NBTTagCompound chainNBT = entry.getValue().writeNBT(false);
|
||||
chainNBT.setString("ChainedAbilityId", entry.getKey());
|
||||
list.appendTag(chainNBT);
|
||||
}
|
||||
|
||||
@@ -36,23 +36,20 @@ import net.minecraft.util.MovingObjectPosition;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import net.minecraft.util.Vec3;
|
||||
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.ScriptController;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.controllers.data.Frame;
|
||||
import noppes.npcs.controllers.data.PlayerDataScript;
|
||||
import noppes.npcs.controllers.data.AbilityScript;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.scripted.event.AbilityEvent;
|
||||
import noppes.npcs.scripted.event.player.PlayerAbilityEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -139,6 +136,13 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
protected transient boolean builtIn = false;
|
||||
protected transient String registryKey;
|
||||
|
||||
// Default icon (set in subclass constructors, NOT persisted)
|
||||
protected transient String defaultIconTexture = "";
|
||||
protected transient java.util.function.IntSupplier defaultIconColorSource = null;
|
||||
protected transient int defaultIconWidth = 48;
|
||||
protected transient int defaultIconHeight = 48;
|
||||
protected transient String[] defaultIconStateTextures = null;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// TOGGLE CONFIGURATION (saved to NBT for custom abilities)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -295,28 +299,14 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
* Called whenever the toggle state changes (activation, deactivation, or mid-cycling).
|
||||
* Override to apply/remove effects based on state transitions.
|
||||
*
|
||||
* @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) {
|
||||
public void onToggle(EntityLivingBase caster, int oldState, int newState) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,39 +422,15 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
private boolean applyAbilityDamageInternal(EntityLivingBase caster, EntityLivingBase hitEntity,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
double knockbackDirX, double knockbackDirZ) {
|
||||
// Fire hit event for NPC casters (NPC event system)
|
||||
if (caster instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) caster;
|
||||
DataAbilities dataAbilities = npc.abilities;
|
||||
AbilityEvent.HitEvent event = dataAbilities.fireHitEvent(
|
||||
this, currentTarget, hitEntity, damage, knockback, knockbackUp);
|
||||
|
||||
if (event == null) {
|
||||
return false; // Cancelled
|
||||
}
|
||||
|
||||
damage = event.getDamage();
|
||||
knockback = event.getKnockback();
|
||||
knockbackUp = event.getKnockbackUp();
|
||||
}
|
||||
// Fire hit event for Player casters (Player event system)
|
||||
else if (caster instanceof EntityPlayer && ScriptController.Instance != null) {
|
||||
EntityPlayer player = (EntityPlayer) caster;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler != null) {
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.HitEvent event = new PlayerAbilityEvent.HitEvent(
|
||||
iPlayer, this, currentTarget, hitEntity, damage, knockback, knockbackUp);
|
||||
|
||||
if (noppes.npcs.EventHooks.onPlayerAbilityHit(handler, event)) {
|
||||
return false; // Cancelled
|
||||
}
|
||||
|
||||
damage = event.getDamage();
|
||||
knockback = event.getKnockback();
|
||||
knockbackUp = event.getKnockbackUp();
|
||||
}
|
||||
// Fire hit event (unified — works for both NPC and Player casters)
|
||||
AbilityEvent.HitEvent hitEvent = new AbilityEvent.HitEvent(
|
||||
caster, this, currentTarget, hitEntity, damage, knockback, knockbackUp);
|
||||
if (EventHooks.onAbilityHit(this, hitEvent)) {
|
||||
return false; // Cancelled
|
||||
}
|
||||
damage = hitEvent.getDamage();
|
||||
knockback = hitEvent.getKnockback();
|
||||
knockbackUp = hitEvent.getKnockbackUp();
|
||||
|
||||
// Save velocity before damage to cancel vanilla knockback when ability knockback is zero
|
||||
double prevMotionX = hitEntity.motionX;
|
||||
@@ -809,23 +775,30 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Icon tab (native) ─────────────────────────────────────
|
||||
// ── Icon section (Effects tab) ───────────────────────────
|
||||
if (!isNpcInlineEdit()) {
|
||||
AbilityIconData icon = AbilityIconData.fromAbility(this);
|
||||
defs.add(FieldDef.section("ability.section.icon").tab("Effects"));
|
||||
defs.add(FieldDef.boolField("ability.hasIcon", icon::isEnabled, icon::setEnabled)
|
||||
.tab("Effects"));
|
||||
defs.add(FieldDef.stringField("gui.texture", icon::getTexture, icon::setTexture)
|
||||
.tab("Icon"));
|
||||
defs.add(FieldDef.section("ability.icon.section.uv").tab("Icon"));
|
||||
.tab("Effects").visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.section("ability.icon.section.uv").tab("Effects")
|
||||
.visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.intField("ability.icon.x", icon::getIconX, icon::setIconX)
|
||||
.tab("Icon").range(0, 4096));
|
||||
.tab("Effects").range(0, 4096).visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.intField("ability.icon.y", icon::getIconY, icon::setIconY)
|
||||
.tab("Icon").range(0, 4096));
|
||||
defs.add(FieldDef.section("gui.size").tab("Icon"));
|
||||
.tab("Effects").range(0, 4096).visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.section("gui.size").tab("Effects")
|
||||
.visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.intField("gui.width", icon::getWidth, icon::setWidth)
|
||||
.tab("Icon").range(1, 256));
|
||||
.tab("Effects").range(1, 256).visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.intField("gui.height", icon::getHeight, icon::setHeight)
|
||||
.tab("Icon").range(1, 256));
|
||||
.tab("Effects").range(1, 256).visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.floatField("gui.scale", icon::getScale, icon::setScale)
|
||||
.tab("Icon").range(0.1f, 10.0f));
|
||||
.tab("Effects").range(0.1f, 10.0f).visibleWhen(icon::isEnabled));
|
||||
defs.add(FieldDef.colorSubGui("ability.icon.tint", icon::getTintColor, icon::setTintColor)
|
||||
.tab("Effects").visibleWhen(icon::isEnabled));
|
||||
}
|
||||
|
||||
// External field providers (e.g., DBC Addon injecting a "DBC" tab)
|
||||
@@ -1378,7 +1351,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
// NBT (config only, execution state is transient)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public NBTTagCompound writeNBT() {
|
||||
public NBTTagCompound writeNBT(boolean saveScripts) {
|
||||
NBTTagCompound nbt = new NBTTagCompound();
|
||||
nbt.setString("id", id);
|
||||
nbt.setString("name", name);
|
||||
@@ -1452,6 +1425,16 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
writeTypeNBT(typeNBT);
|
||||
nbt.setTag("typeData", typeNBT);
|
||||
|
||||
// Script handler data (matching CustomEffect.writeToNBT pattern)
|
||||
if (saveScripts) {
|
||||
AbilityScript handler = getScriptHandler();
|
||||
if (handler != null) {
|
||||
NBTTagCompound scriptData = new NBTTagCompound();
|
||||
handler.writeToNBT(scriptData);
|
||||
nbt.setTag("ScriptData", scriptData);
|
||||
}
|
||||
}
|
||||
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@@ -1548,6 +1531,13 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
if (nbt.hasKey("typeData")) {
|
||||
readTypeNBT(nbt.getCompoundTag("typeData"));
|
||||
}
|
||||
|
||||
// Script handler data (matching CustomEffect.readFromNBT pattern)
|
||||
if (nbt.hasKey("ScriptData", 10)) {
|
||||
AbilityScript handler = new AbilityScript(this.id);
|
||||
handler.readFromNBT(nbt.getCompoundTag("ScriptData"));
|
||||
setScriptHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -1627,8 +1617,48 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this.typeId = "ability." + registryKey.replace(':', '.');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// DEFAULT ICON
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public String getDefaultIconTexture() {
|
||||
return defaultIconTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default icon texture for a specific toggle state.
|
||||
* Falls back to the base defaultIconTexture if no state-specific texture exists.
|
||||
* @param state 1-indexed toggle state (0 = off/default)
|
||||
*/
|
||||
public String getDefaultIconTextureForState(int state) {
|
||||
if (state > 0 && defaultIconStateTextures != null && state - 1 < defaultIconStateTextures.length) {
|
||||
return defaultIconStateTextures[state - 1];
|
||||
}
|
||||
return defaultIconTexture;
|
||||
}
|
||||
|
||||
public int getDefaultIconColor() {
|
||||
return defaultIconColorSource != null ? defaultIconColorSource.getAsInt() : 0xFFFFFF;
|
||||
}
|
||||
|
||||
public int getDefaultIconWidth() {
|
||||
return defaultIconWidth;
|
||||
}
|
||||
|
||||
public int getDefaultIconHeight() {
|
||||
return defaultIconHeight;
|
||||
}
|
||||
|
||||
public String[] getDefaultIconStateTextures() {
|
||||
return defaultIconStateTextures;
|
||||
}
|
||||
|
||||
public boolean hasDefaultIcon() {
|
||||
return defaultIconTexture != null && !defaultIconTexture.isEmpty();
|
||||
}
|
||||
|
||||
public Ability deepCopy() {
|
||||
return AbilityController.Instance.fromNBT(this.writeNBT());
|
||||
return AbilityController.Instance.fromNBT(this.writeNBT(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2497,11 +2527,35 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
|
||||
@Override
|
||||
public INbt getNbt() {
|
||||
return NpcAPI.Instance().getINbt(writeNBT());
|
||||
return NpcAPI.Instance().getINbt(writeNBT(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNbt(INbt nbt) {
|
||||
readNBT(nbt.getMCNBT());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SCRIPT HANDLER
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public AbilityScript getScriptHandler() {
|
||||
if (this.id == null || this.id.isEmpty()) return null;
|
||||
return AbilityController.Instance.abilityScriptHandlers.get(this.id);
|
||||
}
|
||||
|
||||
public void setScriptHandler(AbilityScript handler) {
|
||||
if (this.id == null || this.id.isEmpty()) return;
|
||||
AbilityController.Instance.abilityScriptHandlers.put(this.id, handler);
|
||||
}
|
||||
|
||||
public AbilityScript getOrCreateScriptHandler() {
|
||||
if (this.id == null || this.id.isEmpty()) return null;
|
||||
AbilityScript handler = getScriptHandler();
|
||||
if (handler == null) {
|
||||
handler = new AbilityScript(this.id);
|
||||
AbilityController.Instance.abilityScriptHandlers.put(this.id, handler);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ public class AbilityAction {
|
||||
Ability resolved = getAbility();
|
||||
if (resolved == null) return false;
|
||||
|
||||
inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT());
|
||||
inlineAbility = AbilityController.Instance.fromNBT(resolved.writeNBT(true));
|
||||
slotType = SlotType.INLINE_ABILITY;
|
||||
referenceId = null;
|
||||
cachedAction = null;
|
||||
@@ -245,12 +245,12 @@ public class AbilityAction {
|
||||
// NBT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public NBTTagCompound writeNBT() {
|
||||
public NBTTagCompound writeNBT(boolean saveScripts) {
|
||||
switch (slotType) {
|
||||
case INLINE_CHAIN: {
|
||||
NBTTagCompound nbt = new NBTTagCompound();
|
||||
if (inlineChain != null) {
|
||||
nbt.setTag("InlineChain", inlineChain.writeNBT());
|
||||
nbt.setTag("InlineChain", inlineChain.writeNBT(saveScripts));
|
||||
}
|
||||
return nbt;
|
||||
}
|
||||
@@ -275,7 +275,7 @@ public class AbilityAction {
|
||||
|
||||
case INLINE_ABILITY:
|
||||
default:
|
||||
return inlineAbility != null ? inlineAbility.writeNBT() : new NBTTagCompound();
|
||||
return inlineAbility != null ? inlineAbility.writeNBT(saveScripts) : new NBTTagCompound();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.enums.LockMode;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.UserType;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.api.ability.ICustomAbility;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A blank-slate ability that does nothing by itself.
|
||||
* All behavior is driven by script event hooks (abilityStart, abilityTick, abilityComplete,
|
||||
* abilityToggle, abilityToggleTick).
|
||||
* <p>
|
||||
* Supports two modes based on toggle configuration:
|
||||
* <ul>
|
||||
* <li><b>Instant mode</b> (toggleStates = 0): Uses standard execution lifecycle with
|
||||
* configurable duration. Script hooks: abilityStart, abilityTick, abilityComplete.</li>
|
||||
* <li><b>Toggle mode</b> (toggleStates >= 1): Uses toggle lifecycle.
|
||||
* Script hooks: abilityToggle, abilityToggleTick.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CustomAbility extends Ability implements ICustomAbility {
|
||||
|
||||
private int durationTicks = 20;
|
||||
|
||||
public CustomAbility() {
|
||||
this.typeId = "ability.cnpc.custom";
|
||||
this.name = "Custom";
|
||||
this.targetingMode = TargetingMode.SELF;
|
||||
this.lockMovement = LockMode.NO;
|
||||
this.cooldownTicks = 0;
|
||||
this.windUpTicks = 0;
|
||||
this.telegraphType = TelegraphType.NONE;
|
||||
this.showTelegraph = false;
|
||||
}
|
||||
|
||||
// ==================== EXECUTION ====================
|
||||
|
||||
@Override
|
||||
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (durationTicks <= 0) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (durationTicks > 0 && tick >= durationTicks) {
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PROPERTIES ====================
|
||||
|
||||
@Override
|
||||
public boolean hasDamage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConcurrentCapable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetingModeLocked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowFreeOnCast() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== FIELDS ====================
|
||||
|
||||
@Override
|
||||
public int getDurationTicks() {
|
||||
return durationTicks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDurationTicks(int ticks) {
|
||||
this.durationTicks = Math.max(0, ticks);
|
||||
}
|
||||
|
||||
// ==================== ICustomAbility ====================
|
||||
|
||||
@Override
|
||||
public int getTelegraphShapeType() {
|
||||
return getTelegraphType().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTelegraphShapeType(int type) {
|
||||
TelegraphType[] values = TelegraphType.values();
|
||||
if (type >= 0 && type < values.length) {
|
||||
setTelegraphType(values[type]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTargetingModeType() {
|
||||
return getTargetingMode().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTargetingModeType(int type) {
|
||||
TargetingMode[] values = TargetingMode.values();
|
||||
if (type >= 0 && type < values.length) {
|
||||
setTargetingMode(values[type]);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setInteger("durationTicks", durationTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readTypeNBT(NBTTagCompound nbt) {
|
||||
this.durationTicks = nbt.getInteger("durationTicks");
|
||||
}
|
||||
|
||||
// ==================== GUI ====================
|
||||
|
||||
@Override
|
||||
public void getAbilityDefinitions(List<FieldDef> defs) {
|
||||
// User Type (NPC / Player / Both)
|
||||
defs.add(FieldDef.enumField("ability.allowedBy", UserType.class,
|
||||
this::getAllowedBy, this::setAllowedBy));
|
||||
|
||||
// Targeting Mode
|
||||
defs.add(FieldDef.enumField("ability.targetingMode", TargetingMode.class,
|
||||
this::getTargetingMode, this::setTargetingMode));
|
||||
|
||||
// Telegraph Shape
|
||||
defs.add(FieldDef.enumField("ability.telegraphShape", TelegraphType.class,
|
||||
this::getTelegraphType, this::setTelegraphType));
|
||||
|
||||
// Toggle section
|
||||
defs.add(FieldDef.section("ability.section.toggle"));
|
||||
defs.add(FieldDef.boolField("ability.toggleEnabled",
|
||||
this::isToggleable, enabled -> setToggleStates(enabled ? Math.max(1, getToggleStates()) : 0)));
|
||||
defs.add(FieldDef.intField("ability.toggleStates", this::getToggleStates, this::setToggleStates)
|
||||
.range(1, 10)
|
||||
.visibleWhen(this::isToggleable));
|
||||
defs.add(FieldDef.boolField("ability.hasActiveToggle", this::hasActiveToggle, this::setHasActiveToggle)
|
||||
.visibleWhen(this::isToggleable));
|
||||
|
||||
// Duration (instant mode only)
|
||||
defs.add(FieldDef.section("ability.section.execution")
|
||||
.visibleWhen(() -> !this.isToggleable()));
|
||||
defs.add(FieldDef.intField("ability.duration", this::getDurationTicks, this::setDurationTicks)
|
||||
.range(0, 6000)
|
||||
.visibleWhen(() -> !this.isToggleable()));
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,12 @@ public class AbilityIconData {
|
||||
public int height = 32;
|
||||
public float scale = 1.0f;
|
||||
|
||||
/** Whether a custom icon override is active. Default false = use type's default icon. */
|
||||
private boolean enabled = false;
|
||||
|
||||
/** Manual tint color for the custom icon (0xRRGGBB). Default white = no tint. */
|
||||
private int tintColor = 0xFFFFFF;
|
||||
|
||||
/** Per-state icon UV overrides (0-indexed, maps to toggle states 1, 2, ...). */
|
||||
private int[][] stateIcons = null;
|
||||
|
||||
@@ -64,12 +70,14 @@ public class AbilityIconData {
|
||||
}
|
||||
|
||||
public void writeToNBT(NBTTagCompound nbt) {
|
||||
nbt.setBoolean("Enabled", enabled);
|
||||
nbt.setString("Texture", texture);
|
||||
nbt.setInteger("IconX", iconX);
|
||||
nbt.setInteger("IconY", iconY);
|
||||
nbt.setInteger("Width", width);
|
||||
nbt.setInteger("Height", height);
|
||||
nbt.setFloat("Scale", scale);
|
||||
nbt.setInteger("TintColor", tintColor);
|
||||
if (stateIcons != null && stateIcons.length > 0) {
|
||||
NBTTagList list = new NBTTagList();
|
||||
for (int[] pair : stateIcons) {
|
||||
@@ -83,7 +91,9 @@ public class AbilityIconData {
|
||||
}
|
||||
|
||||
public void readFromNBT(NBTTagCompound nbt) {
|
||||
enabled = nbt.getBoolean("Enabled");
|
||||
texture = nbt.getString("Texture");
|
||||
tintColor = nbt.hasKey("TintColor") ? nbt.getInteger("TintColor") : 0xFFFFFF;
|
||||
iconX = nbt.getInteger("IconX");
|
||||
iconY = nbt.getInteger("IconY");
|
||||
width = nbt.getInteger("Width");
|
||||
@@ -114,6 +124,24 @@ public class AbilityIconData {
|
||||
// GETTERS AND SETTERS (with auto-save)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
save();
|
||||
}
|
||||
|
||||
public int getTintColor() {
|
||||
return tintColor;
|
||||
}
|
||||
|
||||
public void setTintColor(int tintColor) {
|
||||
this.tintColor = tintColor & 0xFFFFFF;
|
||||
save();
|
||||
}
|
||||
|
||||
public String getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package kamkeel.npcs.controllers.data.ability.data;
|
||||
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import noppes.npcs.controllers.data.ChainedAbilityScript;
|
||||
import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.UserType;
|
||||
import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition;
|
||||
@@ -358,7 +359,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
|
||||
*/
|
||||
public ChainedAbility deepCopy() {
|
||||
ChainedAbility copy = new ChainedAbility();
|
||||
copy.readNBT(this.writeNBT());
|
||||
copy.readNBT(this.writeNBT(true));
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -376,7 +377,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
|
||||
// NBT SERIALIZATION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public NBTTagCompound writeNBT() {
|
||||
public NBTTagCompound writeNBT(boolean saveScripts) {
|
||||
NBTTagCompound nbt = new NBTTagCompound();
|
||||
nbt.setString("Id", id);
|
||||
nbt.setString("Name", name);
|
||||
@@ -398,13 +399,23 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
|
||||
// Entries
|
||||
NBTTagList entryList = new NBTTagList();
|
||||
for (ChainedAbilityEntry entry : entries) {
|
||||
entryList.appendTag(entry.writeNBT());
|
||||
entryList.appendTag(entry.writeNBT(saveScripts));
|
||||
}
|
||||
nbt.setTag("Entries", entryList);
|
||||
|
||||
// Custom data (for external mods)
|
||||
nbt.setTag("customData", (NBTTagCompound) customData.copy());
|
||||
|
||||
// Script handler data (matching CustomEffect.writeToNBT pattern)
|
||||
if (saveScripts) {
|
||||
ChainedAbilityScript handler = getScriptHandler();
|
||||
if (handler != null) {
|
||||
NBTTagCompound scriptData = new NBTTagCompound();
|
||||
handler.writeToNBT(scriptData);
|
||||
nbt.setTag("ScriptData", scriptData);
|
||||
}
|
||||
}
|
||||
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@@ -418,7 +429,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 is computed from child abilities — old NBT data silently ignored
|
||||
// AllowedBy is computed from child abilities
|
||||
|
||||
// Conditions
|
||||
conditions.clear();
|
||||
@@ -442,5 +453,33 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
|
||||
|
||||
// Custom data (for external mods)
|
||||
customData = (NBTTagCompound) nbt.getCompoundTag("customData").copy();
|
||||
|
||||
// Script handler data (matching CustomEffect.readFromNBT pattern)
|
||||
if (nbt.hasKey("ScriptData", 10)) {
|
||||
ChainedAbilityScript handler = new ChainedAbilityScript(this.id);
|
||||
handler.readFromNBT(nbt.getCompoundTag("ScriptData"));
|
||||
setScriptHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SCRIPT HANDLER
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public ChainedAbilityScript getScriptHandler() {
|
||||
return AbilityController.Instance.chainedAbilityScriptHandlers.get(this.id);
|
||||
}
|
||||
|
||||
public void setScriptHandler(ChainedAbilityScript handler) {
|
||||
AbilityController.Instance.chainedAbilityScriptHandlers.put(this.id, handler);
|
||||
}
|
||||
|
||||
public ChainedAbilityScript getOrCreateScriptHandler() {
|
||||
ChainedAbilityScript handler = getScriptHandler();
|
||||
if (handler == null) {
|
||||
handler = new ChainedAbilityScript(this.id);
|
||||
AbilityController.Instance.chainedAbilityScriptHandlers.put(this.id, handler);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
@@ -50,5 +50,5 @@ public interface IAbilityAction {
|
||||
*/
|
||||
IAbilityAction deepCopyAction();
|
||||
|
||||
NBTTagCompound writeNBT();
|
||||
NBTTagCompound writeNBT(boolean saveScripts);
|
||||
}
|
||||
-13
@@ -16,10 +16,6 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
public float innerAlpha = 1.0f;
|
||||
public float rotationSpeed = 4.0f;
|
||||
|
||||
// TODO: Skybox Feature
|
||||
// public boolean skyboxEnabled = false;
|
||||
// public String skyboxTexture = "";
|
||||
|
||||
public EnergyDisplayData() {
|
||||
}
|
||||
|
||||
@@ -122,9 +118,6 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
nbt.setFloat("outerColorAlpha", outerColorAlpha);
|
||||
nbt.setFloat("innerAlpha", innerAlpha);
|
||||
nbt.setFloat("rotationSpeed", rotationSpeed);
|
||||
// TODO: Skybox Feature
|
||||
// nbt.setBoolean("skyboxEnabled", skyboxEnabled);
|
||||
// nbt.setString("skyboxTexture", skyboxTexture);
|
||||
}
|
||||
|
||||
public void readNBT(NBTTagCompound nbt) {
|
||||
@@ -135,16 +128,10 @@ public class EnergyDisplayData implements IEnergyDisplayData {
|
||||
outerColorAlpha = nbt.hasKey("outerColorAlpha") ? nbt.getFloat("outerColorAlpha") : 0.5f;
|
||||
innerAlpha = nbt.hasKey("innerAlpha") ? nbt.getFloat("innerAlpha") : 1.0f;
|
||||
rotationSpeed = nbt.hasKey("rotationSpeed") ? nbt.getFloat("rotationSpeed") : 4.0f;
|
||||
// TODO: Skybox Feature
|
||||
// skyboxEnabled = nbt.hasKey("skyboxEnabled") && nbt.getBoolean("skyboxEnabled");
|
||||
// skyboxTexture = nbt.hasKey("skyboxTexture") ? nbt.getString("skyboxTexture") : "";
|
||||
}
|
||||
|
||||
public EnergyDisplayData copy() {
|
||||
EnergyDisplayData copy = new EnergyDisplayData(innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, innerAlpha, rotationSpeed);
|
||||
// TODO: Skybox Feature
|
||||
// copy.skyboxEnabled = skyboxEnabled;
|
||||
// copy.skyboxTexture = skyboxTexture;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -155,7 +155,7 @@ public class ChainedAbilityEntry {
|
||||
Ability resolved = ctrl.resolveAbility(abilityReference);
|
||||
if (resolved == null) return false;
|
||||
|
||||
inlineAbility = ctrl.fromNBT(resolved.writeNBT());
|
||||
inlineAbility = ctrl.fromNBT(resolved.writeNBT(true));
|
||||
entryType = EntryType.INLINE;
|
||||
abilityReference = "";
|
||||
return true;
|
||||
@@ -175,22 +175,22 @@ public class ChainedAbilityEntry {
|
||||
copy.delayTicks = this.delayTicks;
|
||||
copy.concurrentEnabled = this.concurrentEnabled;
|
||||
if (this.inlineAbility != null) {
|
||||
copy.inlineAbility = AbilityController.Instance.fromNBT(this.inlineAbility.writeNBT());
|
||||
copy.inlineAbility = AbilityController.Instance.fromNBT(this.inlineAbility.writeNBT(true));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// NBT (backwards compatible)
|
||||
// NBT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
public NBTTagCompound writeNBT() {
|
||||
public NBTTagCompound writeNBT(boolean saveScripts) {
|
||||
NBTTagCompound nbt = new NBTTagCompound();
|
||||
nbt.setInteger("Delay", delayTicks);
|
||||
nbt.setBoolean("Concurrent", concurrentEnabled);
|
||||
|
||||
if (entryType == EntryType.INLINE && inlineAbility != null) {
|
||||
nbt.setTag("InlineAbility", inlineAbility.writeNBT());
|
||||
nbt.setTag("InlineAbility", inlineAbility.writeNBT(saveScripts));
|
||||
} else {
|
||||
nbt.setString("Reference", abilityReference);
|
||||
}
|
||||
@@ -204,14 +204,12 @@ public class ChainedAbilityEntry {
|
||||
entry.delayTicks = Math.max(0, nbt.getInteger("Delay"));
|
||||
entry.concurrentEnabled = nbt.hasKey("Concurrent") ? nbt.getBoolean("Concurrent") : true;
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ public class AbilityPreviewExecutor implements PreviewEntityHandler {
|
||||
for (ChainedAbilityEntry entry : chain.getEntries()) {
|
||||
Ability resolved = entry.resolve();
|
||||
if (resolved != null && AbilityController.Instance != null) {
|
||||
Ability copy = AbilityController.Instance.fromNBT(resolved.writeNBT());
|
||||
Ability copy = AbilityController.Instance.fromNBT(resolved.writeNBT(true));
|
||||
if (copy != null) {
|
||||
chainAbilities.add(copy);
|
||||
chainDelays.add(entry.getDelayTicks());
|
||||
|
||||
@@ -55,6 +55,9 @@ public class AbilityBeam extends AbilityEnergyProjectile<EntityAbilityBeam> impl
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Beam_Windup";
|
||||
this.activeAnimationName = "Ability_Beam_Active";
|
||||
|
||||
this.defaultIconTexture = "customnpcs:textures/gui/ability/beam.png";
|
||||
this.defaultIconColorSource = () -> isOuterColorEnabled() ? getOuterColor() : getInnerColor();
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@@ -55,6 +55,9 @@ public class AbilityDisc extends AbilityEnergyProjectile<EntityAbilityDisc> impl
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Disc_Windup";
|
||||
this.activeAnimationName = "Ability_Disc_Active";
|
||||
|
||||
this.defaultIconTexture = "customnpcs:textures/gui/ability/disc.png";
|
||||
this.defaultIconColorSource = () -> isOuterColorEnabled() ? getOuterColor() : getInnerColor();
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
-14
@@ -192,20 +192,6 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ public class AbilityLaser extends AbilityEnergyProjectile<EntityAbilityLaser> im
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Laser_Windup";
|
||||
this.activeAnimationName = "Ability_Laser_Active";
|
||||
|
||||
this.defaultIconTexture = "customnpcs:textures/gui/ability/laser.png";
|
||||
this.defaultIconColorSource = () -> isOuterColorEnabled() ? getOuterColor() : getInnerColor();
|
||||
}
|
||||
|
||||
// ==================== OVERRIDES ====================
|
||||
|
||||
@@ -49,6 +49,9 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implem
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Orb_Windup";
|
||||
this.activeAnimationName = "Ability_Orb_Active";
|
||||
|
||||
this.defaultIconTexture = "customnpcs:textures/gui/ability/orb.png";
|
||||
this.defaultIconColorSource = () -> isOuterColorEnabled() ? getOuterColor() : getInnerColor();
|
||||
}
|
||||
|
||||
// ==================== ABSTRACT IMPLEMENTATIONS ====================
|
||||
|
||||
@@ -226,6 +226,8 @@ import kamkeel.npcs.network.packets.request.role.RoleCompanionUpdatePacket;
|
||||
import kamkeel.npcs.network.packets.request.role.RoleGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.role.RoleSavePacket;
|
||||
import kamkeel.npcs.network.packets.request.script.BlockScriptPacket;
|
||||
import kamkeel.npcs.network.packets.request.script.AbilityScriptPacket;
|
||||
import kamkeel.npcs.network.packets.request.script.ChainedAbilityScriptPacket;
|
||||
import kamkeel.npcs.network.packets.request.script.EffectScriptPacket;
|
||||
import kamkeel.npcs.network.packets.request.script.EventScriptPacket;
|
||||
import kamkeel.npcs.network.packets.request.script.ForgeScriptPacket;
|
||||
@@ -316,6 +318,8 @@ public class PacketHandler {
|
||||
REQUEST_PACKET.registerPacket(new ScriptInfoPacket());
|
||||
REQUEST_PACKET.registerPacket(new ScriptFilesPacket());
|
||||
REQUEST_PACKET.registerPacket(new EffectScriptPacket());
|
||||
REQUEST_PACKET.registerPacket(new AbilityScriptPacket());
|
||||
REQUEST_PACKET.registerPacket(new ChainedAbilityScriptPacket());
|
||||
|
||||
// Cloner Packets
|
||||
REQUEST_PACKET.registerPacket(new CloneListPacket());
|
||||
|
||||
@@ -214,4 +214,6 @@ public enum EnumRequestPacket {
|
||||
|
||||
RecipeScript,
|
||||
ManageAuctionAction,
|
||||
AbilityScript,
|
||||
ChainedAbilityScript,
|
||||
}
|
||||
+1
-1
@@ -62,7 +62,7 @@ public final class BuiltInAbilityGetPacket extends AbstractPacket {
|
||||
String name = ByteBufUtils.readString(in);
|
||||
Ability ability = AbilityController.Instance.getAbilityByDisplayName(name);
|
||||
if (ability != null) {
|
||||
NBTTagCompound compound = ability.writeNBT();
|
||||
NBTTagCompound compound = ability.writeNBT(false);
|
||||
compound.setBoolean("BuiltIn", true);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
}
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ public final class ChainedAbilityGetPacket extends AbstractPacket {
|
||||
String chainName = ByteBufUtils.readString(in);
|
||||
ChainedAbility chain = AbilityController.Instance.getChainedAbility(chainName);
|
||||
if (chain != null) {
|
||||
NBTTagCompound compound = chain.writeNBT();
|
||||
NBTTagCompound compound = chain.writeNBT(false);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ public final class ChainedAbilitySavePacket extends AbstractPacket {
|
||||
if (chain.getName() != null && !chain.getName().isEmpty()) {
|
||||
AbilityController.Instance.saveChainedAbility(chain);
|
||||
NoppesUtilServer.sendChainedAbilitiesData((EntityPlayerMP) player);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, chain.writeNBT());
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, chain.writeNBT(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -61,7 +61,7 @@ public final class CustomAbilityGetPacket extends AbstractPacket {
|
||||
String abilityName = ByteBufUtils.readString(in);
|
||||
Ability ability = AbilityController.Instance.getCustomAbility(abilityName);
|
||||
if (ability != null) {
|
||||
NBTTagCompound compound = ability.writeNBT();
|
||||
NBTTagCompound compound = ability.writeNBT(false);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public final class CustomAbilitySavePacket extends AbstractPacket {
|
||||
if (ability != null) {
|
||||
AbilityController.Instance.saveCustomAbility(ability);
|
||||
NoppesUtilServer.sendCustomAbilitiesData((EntityPlayerMP) player);
|
||||
NBTTagCompound responseNbt = ability.writeNBT();
|
||||
NBTTagCompound responseNbt = ability.writeNBT(false);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, responseNbt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package kamkeel.npcs.network.packets.request.script;
|
||||
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
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.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.PacketUtil;
|
||||
import kamkeel.npcs.network.enums.EnumRequestPacket;
|
||||
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.LogWriter;
|
||||
import noppes.npcs.config.ConfigDebug;
|
||||
import noppes.npcs.config.ConfigScript;
|
||||
import noppes.npcs.controllers.data.AbilityScript;
|
||||
import noppes.npcs.controllers.data.IScriptHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class AbilityScriptPacket extends AbstractPacket {
|
||||
public static String packetName = "Request|AbilityScript";
|
||||
|
||||
private Action type;
|
||||
private String abilityId;
|
||||
private int page;
|
||||
private int maxSize;
|
||||
private NBTTagCompound compound;
|
||||
|
||||
public AbilityScriptPacket() {
|
||||
}
|
||||
|
||||
public AbilityScriptPacket(Action type, String abilityId, int page, int maxSize, NBTTagCompound compound) {
|
||||
this.type = type;
|
||||
this.abilityId = abilityId;
|
||||
this.page = page;
|
||||
this.maxSize = maxSize;
|
||||
this.compound = compound;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.AbilityScript;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomNpcsPermissions.Permission getPermission() {
|
||||
return CustomNpcsPermissions.SCRIPT_PLAYER;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(type.ordinal());
|
||||
ByteBufUtils.writeUTF8String(out, abilityId);
|
||||
|
||||
if (type == Action.SAVE) {
|
||||
out.writeInt(this.page);
|
||||
out.writeInt(this.maxSize);
|
||||
ByteBufUtils.writeNBT(out, this.compound);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT))
|
||||
return;
|
||||
|
||||
Action requestedAction = Action.values()[in.readInt()];
|
||||
String id = ByteBufUtils.readUTF8String(in);
|
||||
|
||||
Ability ability = AbilityController.Instance.resolveAbility(id);
|
||||
if (ability == null)
|
||||
return;
|
||||
|
||||
AbilityScript data = ability.getOrCreateScriptHandler();
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
data.saveScript(in);
|
||||
// Persist to disk
|
||||
AbilityController.Instance.saveCustomAbility(ability);
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
LogWriter.script(String.format("[%s] (Player) %s SAVED ABILITY SCRIPT %s [%s]",
|
||||
"ABILITY SCRIPTS", player.getCommandSenderName(), ability.getName(), ability.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(String abilityId, int id, int maxSize, NBTTagCompound compound) {
|
||||
PacketClient.sendClient(new AbilityScriptPacket(Action.SAVE, abilityId, id, maxSize, compound));
|
||||
}
|
||||
|
||||
public static void Get(String abilityId) {
|
||||
PacketClient.sendClient(new AbilityScriptPacket(Action.GET, abilityId, -1, -1, new NBTTagCompound()));
|
||||
}
|
||||
|
||||
private enum Action {
|
||||
GET,
|
||||
SAVE
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package kamkeel.npcs.network.packets.request.script;
|
||||
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
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.controllers.data.ability.data.ChainedAbility;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.PacketUtil;
|
||||
import kamkeel.npcs.network.enums.EnumRequestPacket;
|
||||
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.LogWriter;
|
||||
import noppes.npcs.config.ConfigDebug;
|
||||
import noppes.npcs.config.ConfigScript;
|
||||
import noppes.npcs.controllers.data.ChainedAbilityScript;
|
||||
import noppes.npcs.controllers.data.IScriptHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class ChainedAbilityScriptPacket extends AbstractPacket {
|
||||
public static String packetName = "Request|ChainedAbilityScript";
|
||||
|
||||
private Action type;
|
||||
private String chainId;
|
||||
private int page;
|
||||
private int maxSize;
|
||||
private NBTTagCompound compound;
|
||||
|
||||
public ChainedAbilityScriptPacket() {
|
||||
}
|
||||
|
||||
public ChainedAbilityScriptPacket(Action type, String chainId, int page, int maxSize, NBTTagCompound compound) {
|
||||
this.type = type;
|
||||
this.chainId = chainId;
|
||||
this.page = page;
|
||||
this.maxSize = maxSize;
|
||||
this.compound = compound;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.ChainedAbilityScript;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomNpcsPermissions.Permission getPermission() {
|
||||
return CustomNpcsPermissions.SCRIPT_PLAYER;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(type.ordinal());
|
||||
ByteBufUtils.writeUTF8String(out, chainId);
|
||||
|
||||
if (type == Action.SAVE) {
|
||||
out.writeInt(this.page);
|
||||
out.writeInt(this.maxSize);
|
||||
ByteBufUtils.writeNBT(out, this.compound);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT))
|
||||
return;
|
||||
|
||||
Action requestedAction = Action.values()[in.readInt()];
|
||||
String id = ByteBufUtils.readUTF8String(in);
|
||||
|
||||
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(id);
|
||||
if (chain == null)
|
||||
return;
|
||||
|
||||
ChainedAbilityScript data = chain.getOrCreateScriptHandler();
|
||||
if (requestedAction == Action.GET) {
|
||||
PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player);
|
||||
} else {
|
||||
data.saveScript(in);
|
||||
// Persist to disk
|
||||
AbilityController.Instance.saveChainedAbility(chain);
|
||||
if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
|
||||
LogWriter.script(String.format("[%s] (Player) %s SAVED CHAIN SCRIPT %s [%s]",
|
||||
"CHAIN SCRIPTS", player.getCommandSenderName(), chain.getName(), chain.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(String chainId, int id, int maxSize, NBTTagCompound compound) {
|
||||
PacketClient.sendClient(new ChainedAbilityScriptPacket(Action.SAVE, chainId, id, maxSize, compound));
|
||||
}
|
||||
|
||||
public static void Get(String chainId) {
|
||||
PacketClient.sendClient(new ChainedAbilityScriptPacket(Action.GET, chainId, -1, -1, new NBTTagCompound()));
|
||||
}
|
||||
|
||||
private enum Action {
|
||||
GET,
|
||||
SAVE
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class Register<T> {
|
||||
|
||||
public void register() {
|
||||
for (Map.Entry<String, Supplier<Ability>> entry : entries.entrySet()) {
|
||||
AbilityController.Instance.registerType(entry.getKey(), entry.getValue());
|
||||
AbilityController.Instance.registerType(entry.getValue());
|
||||
if (!typeOnly.contains(entry.getKey())) {
|
||||
AbilityController.Instance.registerAbility(uniqueNames.get(entry.getKey()), entry.getValue().get());
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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.scripted.event.AbilityEvent;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -124,28 +125,53 @@ public abstract class AbstractDataAbilities {
|
||||
*/
|
||||
protected abstract long getWorldTime();
|
||||
|
||||
// -- Event Firing --
|
||||
// -- Event Firing (unified via EventHooks) --
|
||||
|
||||
/**
|
||||
* Fire tick event. Implementation differs between NPC (NpcAPI) and Player (ScriptController).
|
||||
*/
|
||||
protected abstract void fireTickEvent(Ability ability, EntityLivingBase target);
|
||||
protected void fireTickEvent(Ability ability, EntityLivingBase target) {
|
||||
EventHooks.onAbilityTick(ability, getEntity(), target, ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire execute event. Returns true if cancelled.
|
||||
*/
|
||||
protected abstract boolean fireExecuteEvent(Ability ability, EntityLivingBase target);
|
||||
protected boolean fireExecuteEvent(Ability ability, EntityLivingBase target) {
|
||||
return EventHooks.onAbilityExecute(ability, getEntity(), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire complete event.
|
||||
*/
|
||||
protected abstract void fireCompleteEvent(Ability ability, EntityLivingBase target);
|
||||
protected void fireCompleteEvent(Ability ability, EntityLivingBase target) {
|
||||
EventHooks.onAbilityComplete(ability, getEntity(), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire interrupt event.
|
||||
*/
|
||||
protected abstract void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage);
|
||||
protected void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
EventHooks.onAbilityInterrupt(ability, getEntity(), target, source, damage);
|
||||
}
|
||||
|
||||
protected boolean fireToggleEvent(Ability ability, int oldState, int newState) {
|
||||
return EventHooks.onAbilityToggle(ability, getEntity(), oldState, newState);
|
||||
}
|
||||
|
||||
protected boolean fireToggleUpdateEvent(Ability ability, int tick, int state) {
|
||||
AbilityEvent.ToggleUpdateEvent event = new AbilityEvent.ToggleUpdateEvent(getEntity(), ability, tick, state);
|
||||
EventHooks.onAbilityToggleUpdate(ability, event);
|
||||
return event.isEnabled();
|
||||
}
|
||||
|
||||
// -- Chain Events (unified via EventHooks) --
|
||||
|
||||
protected void fireChainStartEvent(ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
EventHooks.onChainStart(chain, getEntity(), entryIndex, target);
|
||||
}
|
||||
|
||||
protected void fireChainNextEvent(ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
EventHooks.onChainNext(chain, getEntity(), entryIndex, target);
|
||||
}
|
||||
|
||||
protected void fireChainCompleteEvent(ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
EventHooks.onChainComplete(chain, getEntity(), entryIndex, target);
|
||||
}
|
||||
|
||||
protected void fireChainInterruptEvent(ChainedAbility chain, int entryIndex, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
EventHooks.onChainInterrupt(chain, getEntity(), entryIndex, target, source, damage);
|
||||
}
|
||||
|
||||
// -- Telegraph --
|
||||
|
||||
@@ -391,8 +417,7 @@ public abstract class AbstractDataAbilities {
|
||||
|
||||
AbilityToggleEntry entry = new AbilityToggleEntry(ability, state);
|
||||
activeToggles.put(key, entry);
|
||||
ability.onToggleOn(getEntity());
|
||||
ability.onToggleStateChanged(getEntity(), 0, state);
|
||||
ability.onToggle(getEntity(), 0, state);
|
||||
onToggleStateChanged(key, true, state);
|
||||
return true;
|
||||
}
|
||||
@@ -404,7 +429,7 @@ public abstract class AbstractDataAbilities {
|
||||
if (fireToggleEvent(ability, oldState, newState)) return oldState;
|
||||
|
||||
entry.setState(newState);
|
||||
ability.onToggleStateChanged(getEntity(), oldState, newState);
|
||||
ability.onToggle(getEntity(), oldState, newState);
|
||||
onToggleStateChanged(key, true, newState);
|
||||
return newState;
|
||||
}
|
||||
@@ -417,8 +442,7 @@ public abstract class AbstractDataAbilities {
|
||||
if (fireToggleEvent(entry.getAbility(), oldState, 0)) return;
|
||||
|
||||
activeToggles.remove(key);
|
||||
entry.getAbility().onToggleOff(getEntity());
|
||||
entry.getAbility().onToggleStateChanged(getEntity(), oldState, 0);
|
||||
entry.getAbility().onToggle(getEntity(), oldState, 0);
|
||||
onToggleStateChanged(key, false, 0);
|
||||
}
|
||||
|
||||
@@ -460,8 +484,7 @@ public abstract class AbstractDataAbilities {
|
||||
AbilityToggleEntry entry = activeToggles.remove(key);
|
||||
if (entry != null) {
|
||||
int oldState = entry.getState();
|
||||
entry.getAbility().onToggleOff(entity);
|
||||
entry.getAbility().onToggleStateChanged(entity, oldState, 0);
|
||||
entry.getAbility().onToggle(entity, oldState, 0);
|
||||
onToggleStateChanged(key, false, 0);
|
||||
}
|
||||
}
|
||||
@@ -474,41 +497,23 @@ public abstract class AbstractDataAbilities {
|
||||
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 and onToggleStateChanged for each active toggle.
|
||||
* Calls onToggle for each active toggle to signal deactivation.
|
||||
*/
|
||||
protected void clearActiveToggles() {
|
||||
if (activeToggles.isEmpty()) return;
|
||||
EntityLivingBase entity = getEntity();
|
||||
for (Map.Entry<String, AbilityToggleEntry> mapEntry : activeToggles.entrySet()) {
|
||||
AbilityToggleEntry entry = mapEntry.getValue();
|
||||
entry.getAbility().onToggleOff(entity);
|
||||
entry.getAbility().onToggleStateChanged(entity, entry.getState(), 0);
|
||||
entry.getAbility().onToggle(entity, entry.getState(), 0);
|
||||
}
|
||||
activeToggles.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a toggle entry directly without calling onToggleOn.
|
||||
* Add a toggle entry directly without calling onToggle.
|
||||
* Used for client-side sync and NBT restoration.
|
||||
* @param key The ability key
|
||||
* @param state The toggle state (0 = remove, 1+ = set at state)
|
||||
@@ -747,6 +752,7 @@ public abstract class AbstractDataAbilities {
|
||||
}
|
||||
}
|
||||
if (startChainEntry(chainTarget)) {
|
||||
fireChainNextEvent(currentChain, chainEntryIndex, chainTarget);
|
||||
launchConsecutiveConcurrentEntries(chainTarget);
|
||||
}
|
||||
}
|
||||
@@ -1036,10 +1042,11 @@ public abstract class AbstractDataAbilities {
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// If chain was active, roll chain cooldown and clear chain state
|
||||
// If chain was active, fire interrupt event, roll cooldown and clear chain state
|
||||
if (currentChain != null) {
|
||||
rollChainCooldown(currentChain);
|
||||
interruptCooldownRolled = true;
|
||||
fireChainInterruptEvent(currentChain, chainEntryIndex, getTarget(), source, damage);
|
||||
currentChain = null;
|
||||
chainEntryIndex = -1;
|
||||
chainDelayRemaining = -1;
|
||||
@@ -1116,6 +1123,25 @@ public abstract class AbstractDataAbilities {
|
||||
onAbilityComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanly complete the currently executing ability.
|
||||
* Unlike cancel/interrupt, this triggers the normal completion path
|
||||
* (fires abilityComplete event, rolls cooldown, advances chains).
|
||||
* Only works during the ACTIVE phase.
|
||||
*/
|
||||
public void completeCurrentAbility() {
|
||||
if (currentAbility == null || !currentAbility.isExecuting()) return;
|
||||
if (currentAbility.getPhase() != AbilityPhase.ACTIVE) return;
|
||||
|
||||
// Signal completion on the ability (handles cleanup, detach/freeOnCast, burst logic)
|
||||
if (!currentAbility.signalCompletion()) return;
|
||||
|
||||
// If signalCompletion moved to IDLE, handle the clean completion path
|
||||
if (currentAbility.getPhase() == AbilityPhase.IDLE) {
|
||||
handleAbilityCompletion(getTarget());
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CHAINED ABILITY EXECUTION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -1137,6 +1163,7 @@ public abstract class AbstractDataAbilities {
|
||||
// AFTER semantics: no delay before first entry (delay applies after each entry completes)
|
||||
boolean started = startChainEntry(target);
|
||||
if (started) {
|
||||
fireChainStartEvent(currentChain, 0, target);
|
||||
launchConsecutiveConcurrentEntries(target);
|
||||
}
|
||||
return started;
|
||||
@@ -1253,7 +1280,7 @@ public abstract class AbstractDataAbilities {
|
||||
chainEntryIndex++;
|
||||
|
||||
// Deep copy the ability so it has its own state
|
||||
Ability concurrentCopy = AbilityController.Instance.fromNBT(resolved.writeNBT());
|
||||
Ability concurrentCopy = AbilityController.Instance.fromNBT(resolved.writeNBT(true));
|
||||
if (concurrentCopy == null) continue;
|
||||
|
||||
ConcurrentSlot slot = new ConcurrentSlot(concurrentCopy);
|
||||
@@ -1279,6 +1306,7 @@ public abstract class AbstractDataAbilities {
|
||||
protected void completeChain() {
|
||||
if (currentChain != null) {
|
||||
rollChainCooldown(currentChain);
|
||||
fireChainCompleteEvent(currentChain, chainEntryIndex, getTarget());
|
||||
}
|
||||
currentChain = null;
|
||||
chainEntryIndex = -1;
|
||||
|
||||
@@ -17,8 +17,8 @@ import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.scripted.event.AbilityEvent;
|
||||
import noppes.npcs.EventHooks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -124,51 +124,6 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
return npc.worldObj.getTotalWorldTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireTickEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.TickEvent event = new AbilityEvent.TickEvent(
|
||||
npc.wrappedNPC, ability, target,
|
||||
ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fireExecuteEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.ExecuteEvent executeEvent = new AbilityEvent.ExecuteEvent(
|
||||
npc.wrappedNPC, ability, target);
|
||||
return NpcAPI.EVENT_BUS.post(executeEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireCompleteEvent(Ability ability, EntityLivingBase target) {
|
||||
AbilityEvent.CompleteEvent completeEvent = new AbilityEvent.CompleteEvent(
|
||||
npc.wrappedNPC, ability, target);
|
||||
NpcAPI.EVENT_BUS.post(completeEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
AbilityEvent.InterruptEvent interruptEvent = new AbilityEvent.InterruptEvent(
|
||||
npc.wrappedNPC, ability, target, source, damage);
|
||||
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<TelegraphInstance> telegraphs = ability.createTelegraphs(npc, target);
|
||||
@@ -502,10 +457,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
*/
|
||||
private boolean startAbility(Ability ability, EntityLivingBase target) {
|
||||
// Fire start event (cancelable)
|
||||
AbilityEvent.StartEvent startEvent = new AbilityEvent.StartEvent(
|
||||
npc.wrappedNPC, ability, target);
|
||||
if (NpcAPI.EVENT_BUS.post(startEvent)) {
|
||||
// Event was cancelled - don't start the ability
|
||||
if (EventHooks.onAbilityStart(ability, npc, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -705,20 +657,11 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
* @param ability The ability doing the hit
|
||||
* @param target The original target of the ability
|
||||
* @param hitEntity The entity being hit
|
||||
* @param damage The damage amount
|
||||
* @param knockback The horizontal knockback
|
||||
* @param knockbackUp The vertical knockback
|
||||
* @return The event (with possibly modified values), or null if cancelled
|
||||
* @param event The mutable hit event to fire
|
||||
* @return true if the event was cancelled
|
||||
*/
|
||||
public AbilityEvent.HitEvent fireHitEvent(Ability ability, EntityLivingBase target,
|
||||
EntityLivingBase hitEntity, float damage,
|
||||
float knockback, float knockbackUp) {
|
||||
AbilityEvent.HitEvent event = new AbilityEvent.HitEvent(
|
||||
npc.wrappedNPC, ability, target, hitEntity, damage, knockback, knockbackUp);
|
||||
if (NpcAPI.EVENT_BUS.post(event)) {
|
||||
return null; // Cancelled
|
||||
}
|
||||
return event;
|
||||
public boolean fireHitEvent(Ability ability, AbilityEvent.HitEvent event) {
|
||||
return EventHooks.onAbilityHit(ability, event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1091,7 +1034,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
|
||||
NBTTagList actionList = new NBTTagList();
|
||||
for (AbilityAction slot : actionSlots) {
|
||||
actionList.appendTag(slot.writeNBT());
|
||||
actionList.appendTag(slot.writeNBT(true));
|
||||
}
|
||||
compound.setTag("AbilityActions", actionList);
|
||||
|
||||
@@ -1146,7 +1089,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
}
|
||||
}
|
||||
|
||||
// Active toggles - restore state directly (no onToggleOn callback during load)
|
||||
// Active toggles - restore state directly (no onToggle callback during load)
|
||||
activeToggles.clear();
|
||||
if (compound.hasKey("ActiveToggles")) {
|
||||
NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 10); // 10 = TAG_COMPOUND
|
||||
|
||||
@@ -53,7 +53,16 @@ import noppes.npcs.controllers.data.RecipeScript;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.entity.EntityProjectile;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.data.ChainedAbility;
|
||||
import noppes.npcs.api.entity.IEntityLivingBase;
|
||||
import noppes.npcs.api.event.IAbilityEvent;
|
||||
import noppes.npcs.api.event.IChainEvent;
|
||||
import noppes.npcs.controllers.data.AbilityScript;
|
||||
import noppes.npcs.controllers.data.ChainedAbilityScript;
|
||||
import noppes.npcs.scripted.event.AbilityEvent;
|
||||
import noppes.npcs.scripted.event.AnimationEvent;
|
||||
import noppes.npcs.scripted.event.ChainEvent;
|
||||
import noppes.npcs.scripted.event.BlockEvent;
|
||||
import noppes.npcs.scripted.event.CustomNPCsEvent;
|
||||
import noppes.npcs.scripted.event.EnergyProjectileEvent;
|
||||
@@ -68,7 +77,6 @@ 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.PlayerAbilityEvent;
|
||||
import noppes.npcs.scripted.event.player.PlayerEvent;
|
||||
import noppes.npcs.scripted.event.player.PlayerEvent.ChatEvent;
|
||||
import noppes.npcs.scripted.event.player.PlayerEvent.ContainerOpen;
|
||||
@@ -851,44 +859,146 @@ public class EventHooks {
|
||||
return NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static boolean onPlayerAbilityStart(PlayerDataScript handler, PlayerAbilityEvent.StartEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_START, event);
|
||||
// ==================== ABILITY EVENTS (Unified NPC/Player) ====================
|
||||
|
||||
private static IScriptHandler getEntityScriptHandler(IEntityLivingBase entity) {
|
||||
if (entity instanceof ICustomNpc<?>) {
|
||||
return ((EntityNPCInterface) ((ICustomNpc<?>) entity).getMCEntity()).script;
|
||||
} else if (entity instanceof IPlayer && ScriptController.Instance != null) {
|
||||
return ScriptController.Instance.getPlayerScripts(
|
||||
(EntityPlayer) ((IPlayer) entity).getMCEntity());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an ability event through the three-tier routing chain:
|
||||
* <ol>
|
||||
* <li><b>AbilityScript</b> — fires for BOTH NPC and Player casters</li>
|
||||
* <li><b>Entity script handler</b> — routes to the caster's own handler:
|
||||
* NPC casters fire on the NPC script handler only,
|
||||
* Player casters fire on the Player script handler only</li>
|
||||
* <li><b>EVENT_BUS</b> — global event bus, fires last</li>
|
||||
* </ol>
|
||||
*
|
||||
* @return true if the event was cancelled (for @Cancelable events)
|
||||
*/
|
||||
private static boolean postAbilityEvent(Ability ability, Event event) {
|
||||
IAbilityEvent abilityEvent = (IAbilityEvent) event;
|
||||
|
||||
// 1. Internal ability script (fires for ALL — both NPC and Player)
|
||||
AbilityScript abScript = ability.getScriptHandler();
|
||||
if (abScript != null) {
|
||||
abScript.callScript(abilityEvent.getHookName(), event);
|
||||
}
|
||||
|
||||
// Short-circuit if cancelled by ability script
|
||||
if (event.isCancelable() && event.isCanceled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Entity script handler (NPC → NPC handler, Player → Player handler, never cross-fires)
|
||||
IScriptHandler handler = getEntityScriptHandler(abilityEvent.getEntity());
|
||||
if (handler != null && !handler.isClient()) {
|
||||
handler.callScript(abilityEvent.getHookName(), event);
|
||||
}
|
||||
|
||||
// Short-circuit if cancelled by entity script
|
||||
if (event.isCancelable() && event.isCanceled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. EVENT_BUS (global — fires last)
|
||||
return NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static boolean onPlayerAbilityExecute(PlayerDataScript handler, PlayerAbilityEvent.ExecuteEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_EXECUTE, event);
|
||||
private static boolean postChainEvent(ChainedAbility chain, Event event) {
|
||||
IChainEvent chainEvent = (IChainEvent) event;
|
||||
|
||||
// 1. Internal chain script (fires first)
|
||||
ChainedAbilityScript chainScript = chain.getScriptHandler();
|
||||
if (chainScript != null) {
|
||||
chainScript.callScript(chainEvent.getHookName(), event);
|
||||
}
|
||||
|
||||
// Short-circuit if cancelled by chain script
|
||||
if (event.isCancelable() && event.isCanceled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Entity script handler (NPC or Player)
|
||||
IScriptHandler handler = getEntityScriptHandler(chainEvent.getEntity());
|
||||
if (handler != null && !handler.isClient()) {
|
||||
handler.callScript(chainEvent.getHookName(), event);
|
||||
}
|
||||
|
||||
// Short-circuit if cancelled by entity script
|
||||
if (event.isCancelable() && event.isCanceled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. EVENT_BUS (global — fires last)
|
||||
return NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void onPlayerAbilityTick(PlayerDataScript handler, PlayerAbilityEvent.TickEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_TICK, event);
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
public static boolean onAbilityStart(Ability ability, EntityLivingBase entity, EntityLivingBase target) {
|
||||
if (ability == null || entity == null) return false;
|
||||
return postAbilityEvent(ability, new AbilityEvent.StartEvent(entity, ability, target));
|
||||
}
|
||||
|
||||
public static void onPlayerAbilityInterrupt(PlayerDataScript handler, PlayerAbilityEvent.InterruptEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_INTERRUPT, event);
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
public static boolean onAbilityExecute(Ability ability, EntityLivingBase entity, EntityLivingBase target) {
|
||||
if (ability == null || entity == null) return false;
|
||||
return postAbilityEvent(ability, new AbilityEvent.ExecuteEvent(entity, ability, target));
|
||||
}
|
||||
|
||||
public static void onPlayerAbilityComplete(PlayerDataScript handler, PlayerAbilityEvent.CompleteEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_COMPLETE, event);
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
public static boolean onAbilityTick(Ability ability, EntityLivingBase entity, EntityLivingBase target, int phase, int tick) {
|
||||
if (ability == null || entity == null) return false;
|
||||
return postAbilityEvent(ability, new AbilityEvent.TickEvent(entity, ability, target, phase, tick));
|
||||
}
|
||||
|
||||
public static boolean onPlayerAbilityHit(PlayerDataScript handler, PlayerAbilityEvent.HitEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_HIT, event);
|
||||
return NpcAPI.EVENT_BUS.post(event);
|
||||
public static boolean onAbilityComplete(Ability ability, EntityLivingBase entity, EntityLivingBase target) {
|
||||
if (ability == null || entity == null) return false;
|
||||
return postAbilityEvent(ability, new AbilityEvent.CompleteEvent(entity, ability, target));
|
||||
}
|
||||
|
||||
public static boolean onPlayerAbilityToggle(PlayerDataScript handler, PlayerAbilityEvent.ToggleEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_TOGGLE, event);
|
||||
return NpcAPI.EVENT_BUS.post(event);
|
||||
public static boolean onAbilityInterrupt(Ability ability, EntityLivingBase entity, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
if (ability == null || entity == null) return false;
|
||||
return postAbilityEvent(ability, new AbilityEvent.InterruptEvent(entity, ability, target, source, damage));
|
||||
}
|
||||
|
||||
public static void onPlayerAbilityToggleUpdate(PlayerDataScript handler, PlayerAbilityEvent.ToggleUpdateEvent event) {
|
||||
handler.callScript(EnumScriptType.ABILITY_TOGGLE_UPDATE, event);
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
public static boolean onAbilityHit(Ability ability, AbilityEvent.HitEvent event) {
|
||||
if (ability == null || event == null) return false;
|
||||
return postAbilityEvent(ability, event);
|
||||
}
|
||||
|
||||
public static boolean onAbilityToggle(Ability ability, EntityLivingBase entity, int oldState, int newState) {
|
||||
if (ability == null || entity == null) return false;
|
||||
return postAbilityEvent(ability, new AbilityEvent.ToggleEvent(entity, ability, oldState, newState));
|
||||
}
|
||||
|
||||
public static boolean onAbilityToggleUpdate(Ability ability, AbilityEvent.ToggleUpdateEvent event) {
|
||||
if (ability == null || event == null) return false;
|
||||
return postAbilityEvent(ability, event);
|
||||
}
|
||||
|
||||
// Chain events
|
||||
|
||||
public static void onChainStart(ChainedAbility chain, EntityLivingBase entity, int entryIndex, EntityLivingBase target) {
|
||||
postChainEvent(chain, new ChainEvent.StartEvent(entity, chain, entryIndex, target));
|
||||
}
|
||||
|
||||
public static void onChainNext(ChainedAbility chain, EntityLivingBase entity, int entryIndex, EntityLivingBase target) {
|
||||
postChainEvent(chain, new ChainEvent.NextEvent(entity, chain, entryIndex, target));
|
||||
}
|
||||
|
||||
public static void onChainComplete(ChainedAbility chain, EntityLivingBase entity, int entryIndex, EntityLivingBase target) {
|
||||
postChainEvent(chain, new ChainEvent.CompleteEvent(entity, chain, entryIndex, target));
|
||||
}
|
||||
|
||||
public static void onChainInterrupt(ChainedAbility chain, EntityLivingBase entity, int entryIndex,
|
||||
EntityLivingBase target, DamageSource source, float damage) {
|
||||
postChainEvent(chain, new ChainEvent.InterruptEvent(entity, chain, entryIndex, target, source, damage));
|
||||
}
|
||||
|
||||
public static void onPlayerChangeDim(PlayerDataScript handler, IPlayer player, int fromDim, int toDim) {
|
||||
|
||||
@@ -649,9 +649,9 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
// Search matches either the display name or the typeId (strip color codes for matching)
|
||||
String stripped = displayName.replaceAll("\u00A7.", "");
|
||||
if (search.isEmpty() || stripped.toLowerCase().contains(search) || typeId.toLowerCase().contains(search)) {
|
||||
// Yellow for concurrent-capable types, gray for other built-in types
|
||||
// Lime green for concurrent-capable types, gray for other built-in types
|
||||
if (AbilityController.Instance.isConcurrentCapableType(typeId)) {
|
||||
displayName = "\u00A7e" + displayName;
|
||||
displayName = "\u00A7a" + displayName;
|
||||
} else if (AbilityController.Instance.isBuiltInType(typeId)) {
|
||||
displayName = "\u00A77" + displayName;
|
||||
}
|
||||
@@ -787,7 +787,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
|
||||
NBTTagList actionList = new NBTTagList();
|
||||
for (AbilityAction slot : npcSlots) {
|
||||
actionList.appendTag(slot.writeNBT());
|
||||
actionList.appendTag(slot.writeNBT(false));
|
||||
}
|
||||
compound.setTag("AbilityActions", actionList);
|
||||
|
||||
@@ -1105,7 +1105,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
public void onAbilitySaved(Ability ability) {
|
||||
// Editing a global parent ability via "Modify Parent" on a chain entry
|
||||
if (editingChainEntryParent) {
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT()));
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT(false)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1119,7 +1119,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
|
||||
AbilityAction slot = npcSlots.get(selectedAbilityIndex);
|
||||
if (slot.isReference()) {
|
||||
// Modify Parent on standalone reference — save to global preset
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT()));
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT(false)));
|
||||
} else {
|
||||
// Inline ability — update slot data
|
||||
npcSlots.set(selectedAbilityIndex, AbilityAction.inline(ability));
|
||||
|
||||
@@ -3,6 +3,8 @@ package noppes.npcs.client.gui.advanced;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldBuilder;
|
||||
import noppes.npcs.client.gui.script.GuiScriptInterface;
|
||||
import noppes.npcs.controllers.data.AbilityScript;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.StatCollector;
|
||||
@@ -143,6 +145,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
GuiMenuTopButton closeBtn = new GuiMenuTopButton(-1000, guiLeft + xSize - 22, guiTop - 17, "X");
|
||||
addTopButton(closeBtn);
|
||||
|
||||
GuiMenuTopButton scriptBtn = new GuiMenuTopButton(-999, "script.scripts", closeBtn);
|
||||
addTopButton(scriptBtn);
|
||||
|
||||
// Build scroll window dimensions
|
||||
int swX = guiLeft + 4;
|
||||
int swY = guiTop + 5;
|
||||
@@ -152,9 +157,12 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
int y = 5;
|
||||
int labelCounter = LABEL_ID_START;
|
||||
|
||||
// General tab: type label + optional concurrent label at top — reserve space
|
||||
// General tab: type label + optional info labels at top — reserve space
|
||||
if (activeTab == TAB_GENERAL) {
|
||||
y += ROW_H;
|
||||
if ("ability.cnpc.custom".equals(ability.getTypeId())) {
|
||||
y += 15;
|
||||
}
|
||||
if (ability.isConcurrentCapable()) {
|
||||
y += 15;
|
||||
}
|
||||
@@ -168,14 +176,20 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
|
||||
GuiScrollWindow sw = builder.buildScrollWindow(tabFields, swX, swY, swW, swH);
|
||||
|
||||
// General tab: add type label + optional concurrent label at top of scroll window
|
||||
// General tab: add type label + optional info labels at top of scroll window
|
||||
if (activeTab == TAB_GENERAL) {
|
||||
int baseId = builder.getNextLabelId();
|
||||
int infoY = 25;
|
||||
sw.addLabel(new GuiNpcLabel(baseId, "gui.type", 5, 10, 0xFFFFFF));
|
||||
sw.addLabel(new GuiNpcLabel(baseId + 1, ability.getTypeId(), 55, 10, 0xFFFFFF));
|
||||
if (ability.isConcurrentCapable()) {
|
||||
if ("ability.cnpc.custom".equals(ability.getTypeId())) {
|
||||
sw.addLabel(new GuiNpcLabel(baseId + 2,
|
||||
StatCollector.translateToLocal("ability.concurrentInfo"), 5, 25, 0x55FF55));
|
||||
StatCollector.translateToLocal("ability.customScriptInfo"), 5, infoY, 0xFF55FF));
|
||||
infoY += 15;
|
||||
}
|
||||
if (ability.isConcurrentCapable()) {
|
||||
sw.addLabel(new GuiNpcLabel(baseId + 3,
|
||||
StatCollector.translateToLocal("ability.concurrentInfo"), 5, infoY, 0x55FF55));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +316,13 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
if (id == -999) {
|
||||
GuiNpcTextField.unfocus();
|
||||
applyToAbility();
|
||||
callback.onAbilitySaved(ability);
|
||||
GuiScriptInterface.open(parent, new AbilityScript(ability.getId()));
|
||||
return;
|
||||
}
|
||||
if (id == -1000) {
|
||||
close();
|
||||
return;
|
||||
@@ -405,7 +426,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
|
||||
public void loadAbility(Ability loadedAbility) {
|
||||
if (loadedAbility == null) return;
|
||||
NBTTagCompound nbt = loadedAbility.writeNBT();
|
||||
NBTTagCompound nbt = loadedAbility.writeNBT(false);
|
||||
nbt.setString("typeId", ability.getTypeId());
|
||||
ability.readNBT(nbt);
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class SubGuiAbilitySaveConfirm extends SubGuiInterface implements ISubGui
|
||||
|
||||
private void doSave() {
|
||||
saved = true;
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT()));
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT(false)));
|
||||
if (callback != null) {
|
||||
callback.onAbilitySaved(ability);
|
||||
}
|
||||
|
||||
@@ -83,8 +83,10 @@ public class SubGuiAbilityTypeSelect extends SubGuiInterface implements ICustomS
|
||||
continue;
|
||||
|
||||
String displayName = I18n.format(typeId);
|
||||
if (AbilityController.Instance.isConcurrentCapableType(typeId)) {
|
||||
displayName = "\u00A7e" + displayName;
|
||||
if (typeId.equals("ability.cnpc.custom")) {
|
||||
displayName = "\u00A7d" + displayName;
|
||||
} else if (AbilityController.Instance.isConcurrentCapableType(typeId)) {
|
||||
displayName = "\u00A7a" + displayName;
|
||||
}
|
||||
allDisplayNameToTypeId.put(displayName, typeId);
|
||||
}
|
||||
@@ -122,6 +124,21 @@ public class SubGuiAbilityTypeSelect extends SubGuiInterface implements ICustomS
|
||||
}
|
||||
Collections.sort(list, (a, b) -> String.CASE_INSENSITIVE_ORDER.compare(
|
||||
a.replaceAll("\u00A7.", ""), b.replaceAll("\u00A7.", "")));
|
||||
|
||||
// Pin Custom Ability to top of list
|
||||
String customEntry = null;
|
||||
for (String name : list) {
|
||||
String tid = displayNameToTypeId.get(name);
|
||||
if (tid != null && tid.equals("ability.cnpc.custom")) {
|
||||
customEntry = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (customEntry != null) {
|
||||
list.remove(customEntry);
|
||||
list.add(0, customEntry);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ 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.data.ChainedAbility;
|
||||
import noppes.npcs.client.gui.script.GuiScriptInterface;
|
||||
import noppes.npcs.controllers.data.ChainedAbilityScript;
|
||||
import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry;
|
||||
import kamkeel.npcs.controllers.data.ability.data.AbilityIconData;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.IChainedAbilityFieldProvider;
|
||||
@@ -57,6 +59,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
|
||||
private static final int BTN_TAB_ENTRIES = 91;
|
||||
private static final int BTN_TAB_TARGET = 92;
|
||||
private static final int BTN_TAB_EXTRA_BASE = 93;
|
||||
private static final int BTN_SCRIPT = -999;
|
||||
private static final int BTN_CLOSE = -1000;
|
||||
|
||||
// ── Entries tab: per-entry ID encoding ─────────────────────────────────────
|
||||
@@ -258,6 +261,9 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
|
||||
GuiMenuTopButton closeBtn = new GuiMenuTopButton(BTN_CLOSE, guiLeft + xSize - 22, guiTop - 17, "X");
|
||||
addTopButton(closeBtn);
|
||||
|
||||
GuiMenuTopButton scriptBtn = new GuiMenuTopButton(BTN_SCRIPT, "script.scripts", closeBtn);
|
||||
addTopButton(scriptBtn);
|
||||
|
||||
// Scroll window dimensions
|
||||
int swX = guiLeft + 4;
|
||||
int swY = guiTop + 5;
|
||||
@@ -487,6 +493,10 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
if (id == BTN_SCRIPT) {
|
||||
GuiScriptInterface.open(parent, new ChainedAbilityScript(chain.getId()));
|
||||
return;
|
||||
}
|
||||
if (id == BTN_CLOSE) {
|
||||
saveOnClose = true;
|
||||
close();
|
||||
@@ -853,7 +863,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
|
||||
public void onAbilitySaved(Ability ability) {
|
||||
if (editingParentAbility) {
|
||||
// Persist the global parent ability to the server
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT()));
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT(false)));
|
||||
editingParentAbility = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -495,7 +495,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
|
||||
private void handleChainConfigClosed() {
|
||||
if (pendingSaveChain != null) {
|
||||
PacketClient.sendClient(new ChainedAbilitySavePacket(pendingSaveChain.writeNBT()));
|
||||
PacketClient.sendClient(new ChainedAbilitySavePacket(pendingSaveChain.writeNBT(false)));
|
||||
selected = pendingSaveChain.getName();
|
||||
pendingSaveChain = null;
|
||||
}
|
||||
@@ -550,7 +550,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
if (pendingSaveAbility == null) return false;
|
||||
|
||||
if (gui.isConfirmed()) {
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT()));
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT(false)));
|
||||
pendingSaveAbility = null;
|
||||
return false;
|
||||
}
|
||||
@@ -574,7 +574,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
setSubGui(new SubGuiDuplicateNameConfirm());
|
||||
return true;
|
||||
}
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT()));
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT(false)));
|
||||
pendingSaveAbility = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@ public class AbilityIcon extends Gui {
|
||||
public int width;
|
||||
public int height;
|
||||
|
||||
// Default icon info (extracted from ability)
|
||||
private String defaultTexture = "";
|
||||
private String[] defaultStateTextures = null;
|
||||
private int defaultIconWidth = 48;
|
||||
private int defaultIconHeight = 48;
|
||||
private Ability sourceAbility = null;
|
||||
|
||||
private AbilityIcon(AbilityIconData data) {
|
||||
this.data = data;
|
||||
this.width = data.width;
|
||||
@@ -33,7 +40,13 @@ public class AbilityIcon extends Gui {
|
||||
|
||||
public static AbilityIcon fromAbility(Ability ability) {
|
||||
if (ability != null) {
|
||||
return new AbilityIcon(AbilityIconData.fromAbility(ability));
|
||||
AbilityIcon icon = new AbilityIcon(AbilityIconData.fromAbility(ability));
|
||||
icon.defaultTexture = ability.getDefaultIconTexture();
|
||||
icon.defaultStateTextures = ability.getDefaultIconStateTextures();
|
||||
icon.defaultIconWidth = ability.getDefaultIconWidth();
|
||||
icon.defaultIconHeight = ability.getDefaultIconHeight();
|
||||
icon.sourceAbility = ability;
|
||||
return icon;
|
||||
}
|
||||
return fromDefaults();
|
||||
}
|
||||
@@ -66,30 +79,85 @@ public class AbilityIcon extends Gui {
|
||||
public void draw(int state, float alpha) {
|
||||
TextureManager renderEngine = Minecraft.getMinecraft().renderEngine;
|
||||
|
||||
ImageData imageData = null;
|
||||
if (data.hasTexture()) {
|
||||
imageData = ClientCacheHandler.getImageData(data.texture);
|
||||
}
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glColor4f(1, 1, 1, alpha);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
Tessellator t;
|
||||
|
||||
if (imageData == null || !imageData.imageLoaded()) {
|
||||
GL11.glScalef(1.0f, 1.0f, 1);
|
||||
if (data.isEnabled() && data.hasTexture()) {
|
||||
// Path 1: Custom icon enabled with texture — draw with manual tintColor
|
||||
ImageData imageData = ClientCacheHandler.getImageData(data.texture);
|
||||
if (imageData != null && imageData.imageLoaded()) {
|
||||
int tint = data.getTintColor();
|
||||
float r = ((tint >> 16) & 0xFF) / 255f;
|
||||
float g = ((tint >> 8) & 0xFF) / 255f;
|
||||
float b = (tint & 0xFF) / 255f;
|
||||
GL11.glColor4f(r, g, b, alpha);
|
||||
GL11.glScalef(data.scale, data.scale, 1);
|
||||
renderEngine.bindTexture(imageData.getLocation());
|
||||
t = getTessellator(imageData, state);
|
||||
t.draw();
|
||||
} else {
|
||||
GL11.glColor4f(1, 1, 1, alpha);
|
||||
renderEngine.bindTexture(FALLBACK_TEXTURE);
|
||||
t = getFallbackTessellator();
|
||||
t.draw();
|
||||
}
|
||||
} else if (hasDefaultTexture()) {
|
||||
// Path 2: No custom icon, but type has a default icon — draw with auto-color tint
|
||||
String texturePath = resolveDefaultTexture(state);
|
||||
ImageData defaultImageData = ClientCacheHandler.getImageData(texturePath);
|
||||
if (defaultImageData != null && defaultImageData.imageLoaded()) {
|
||||
int autoColor = sourceAbility != null ? sourceAbility.getDefaultIconColor() : 0xFFFFFF;
|
||||
float r = ((autoColor >> 16) & 0xFF) / 255f;
|
||||
float g = ((autoColor >> 8) & 0xFF) / 255f;
|
||||
float b = (autoColor & 0xFF) / 255f;
|
||||
GL11.glColor4f(r, g, b, alpha);
|
||||
renderEngine.bindTexture(defaultImageData.getLocation());
|
||||
t = getDefaultIconTessellator();
|
||||
t.draw();
|
||||
} else {
|
||||
// Default texture not found — use fallback at default icon dimensions with auto-color tint
|
||||
int autoColor = sourceAbility != null ? sourceAbility.getDefaultIconColor() : 0xFFFFFF;
|
||||
float r = ((autoColor >> 16) & 0xFF) / 255f;
|
||||
float g = ((autoColor >> 8) & 0xFF) / 255f;
|
||||
float b = (autoColor & 0xFF) / 255f;
|
||||
GL11.glColor4f(r, g, b, alpha);
|
||||
renderEngine.bindTexture(FALLBACK_TEXTURE);
|
||||
t = getDefaultIconTessellator();
|
||||
t.draw();
|
||||
}
|
||||
} else {
|
||||
// Path 3: No custom icon, no default icon — generic fallback with auto-color tint if available
|
||||
int autoColor = sourceAbility != null ? sourceAbility.getDefaultIconColor() : 0xFFFFFF;
|
||||
float r = ((autoColor >> 16) & 0xFF) / 255f;
|
||||
float g = ((autoColor >> 8) & 0xFF) / 255f;
|
||||
float b = (autoColor & 0xFF) / 255f;
|
||||
GL11.glColor4f(r, g, b, alpha);
|
||||
renderEngine.bindTexture(FALLBACK_TEXTURE);
|
||||
t = getFallbackTessellator();
|
||||
} else {
|
||||
GL11.glScalef(data.scale, data.scale, 1);
|
||||
renderEngine.bindTexture(imageData.getLocation());
|
||||
t = getTessellator(imageData, state);
|
||||
t.draw();
|
||||
}
|
||||
|
||||
t.draw();
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the default texture path for a given toggle state.
|
||||
* Falls back to the base defaultTexture if no state-specific texture exists.
|
||||
*/
|
||||
private String resolveDefaultTexture(int state) {
|
||||
if (state > 0 && defaultStateTextures != null && state - 1 < defaultStateTextures.length) {
|
||||
return defaultStateTextures[state - 1];
|
||||
}
|
||||
return defaultTexture;
|
||||
}
|
||||
|
||||
private boolean hasDefaultTexture() {
|
||||
return defaultTexture != null && !defaultTexture.isEmpty();
|
||||
}
|
||||
|
||||
private Tessellator getTessellator(ImageData imageData, int state) {
|
||||
float hw = data.width / 2f;
|
||||
float hh = data.height / 2f;
|
||||
@@ -114,6 +182,19 @@ public class AbilityIcon extends Gui {
|
||||
return t;
|
||||
}
|
||||
|
||||
private Tessellator getDefaultIconTessellator() {
|
||||
float hw = defaultIconWidth / 2f;
|
||||
float hh = defaultIconHeight / 2f;
|
||||
|
||||
Tessellator t = Tessellator.instance;
|
||||
t.startDrawingQuads();
|
||||
t.addVertexWithUV(-hw, hh, zLevel, 0, 1);
|
||||
t.addVertexWithUV(hw, hh, zLevel, 1, 1);
|
||||
t.addVertexWithUV(hw, -hh, zLevel, 1, 0);
|
||||
t.addVertexWithUV(-hw, -hh, zLevel, 0, 0);
|
||||
return t;
|
||||
}
|
||||
|
||||
private Tessellator getFallbackTessellator() {
|
||||
float hw = 16f;
|
||||
float hh = 16f;
|
||||
@@ -128,17 +209,20 @@ public class AbilityIcon extends Gui {
|
||||
}
|
||||
|
||||
public boolean hasTexture() {
|
||||
return data.hasTexture();
|
||||
return (data.isEnabled() && data.hasTexture()) || hasDefaultTexture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the approximate visual size (max dimension) that draw() produces.
|
||||
* Textured: max(width,height) * scale. Fallback: 32.
|
||||
* Custom icon: max(width,height) * scale. Default icon: max(defaultW,defaultH). Fallback: 32.
|
||||
*/
|
||||
public float getDrawSize() {
|
||||
if (data.hasTexture()) {
|
||||
if (data.isEnabled() && data.hasTexture()) {
|
||||
return Math.max(width, height) * data.scale;
|
||||
}
|
||||
if (hasDefaultTexture()) {
|
||||
return Math.max(defaultIconWidth, defaultIconHeight);
|
||||
}
|
||||
return 32f;
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,12 @@ public enum EnumScriptType {
|
||||
ABILITY_HIT("abilityHit"),
|
||||
ABILITY_TICK("abilityTick"),
|
||||
ABILITY_TOGGLE("abilityToggle"),
|
||||
ABILITY_TOGGLE_UPDATE("abilityToggleUpdate"),
|
||||
ABILITY_TOGGLE_TICK("abilityToggleTick"),
|
||||
|
||||
CHAIN_START("chainStart"),
|
||||
CHAIN_NEXT("chainNext"),
|
||||
CHAIN_COMPLETE("chainComplete"),
|
||||
CHAIN_INTERRUPT("chainInterrupt"),
|
||||
|
||||
ENERGY_PROJECTILE_FIRED("energyProjectileFired"),
|
||||
ENERGY_PROJECTILE_TICK("energyProjectileTick"),
|
||||
|
||||
@@ -13,7 +13,7 @@ import noppes.npcs.api.event.IForgeEvent;
|
||||
import noppes.npcs.api.event.IItemEvent;
|
||||
import noppes.npcs.api.event.INpcEvent;
|
||||
import noppes.npcs.api.event.IPartyEvent;
|
||||
import noppes.npcs.api.event.IPlayerAbilityEvent;
|
||||
import noppes.npcs.api.event.IChainEvent;
|
||||
import noppes.npcs.api.event.IPlayerEvent;
|
||||
import noppes.npcs.api.event.IProjectileEvent;
|
||||
import noppes.npcs.api.event.IQuestEvent;
|
||||
@@ -61,6 +61,7 @@ public class ScriptContext {
|
||||
IEnergyProjectileEvent.class,
|
||||
IEnergyBarrierEvent.class,
|
||||
IAbilityEvent.class,
|
||||
IChainEvent.class,
|
||||
IAnimationEvent.class
|
||||
);
|
||||
|
||||
@@ -74,7 +75,8 @@ public class ScriptContext {
|
||||
ICustomGuiEvent.class,
|
||||
IEnergyProjectileEvent.class,
|
||||
IEnergyBarrierEvent.class,
|
||||
IPlayerAbilityEvent.class,
|
||||
IAbilityEvent.class,
|
||||
IChainEvent.class,
|
||||
IAuctionEvent.class
|
||||
);
|
||||
|
||||
@@ -102,6 +104,14 @@ public class ScriptContext {
|
||||
"Effect"
|
||||
);
|
||||
|
||||
public static final ScriptContext ABILITY = register("ABILITY", "ability",
|
||||
"Ability"
|
||||
);
|
||||
|
||||
public static final ScriptContext CHAINED_ABILITY = register("CHAINED_ABILITY", "chained_ability",
|
||||
"ChainedAbility"
|
||||
);
|
||||
|
||||
public static final ScriptContext GLOBAL = register("GLOBAL", "",
|
||||
"Global" // Special case: Global namespace doesn't have a corresponding event class
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package noppes.npcs.controllers;
|
||||
|
||||
import noppes.npcs.api.event.IAbilityEvent;
|
||||
import noppes.npcs.api.event.IChainEvent;
|
||||
import noppes.npcs.api.event.IAnimationEvent;
|
||||
import noppes.npcs.api.event.IAuctionEvent;
|
||||
import noppes.npcs.api.event.IBlockEvent;
|
||||
@@ -15,7 +16,6 @@ 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;
|
||||
@@ -41,6 +41,8 @@ import static noppes.npcs.constants.ScriptContext.ITEM;
|
||||
import static noppes.npcs.constants.ScriptContext.LINKED_ITEM;
|
||||
import static noppes.npcs.constants.ScriptContext.NPC;
|
||||
import static noppes.npcs.constants.ScriptContext.PLAYER;
|
||||
import static noppes.npcs.constants.ScriptContext.ABILITY;
|
||||
import static noppes.npcs.constants.ScriptContext.CHAINED_ABILITY;
|
||||
import static noppes.npcs.constants.ScriptContext.RECIPE;
|
||||
|
||||
/**
|
||||
@@ -81,6 +83,7 @@ public class ScriptHookController implements IScriptHookHandler {
|
||||
initializeLinkedItemHooks();
|
||||
initializeRecipeHooks();
|
||||
initializeEffectHooks();
|
||||
initializeAbilityHooks();
|
||||
initializeForgeHooks();
|
||||
}
|
||||
|
||||
@@ -124,6 +127,14 @@ public class ScriptHookController implements IScriptHookHandler {
|
||||
hook(NPC, ABILITY_TICK, IAbilityEvent.TickEvent.class);
|
||||
hook(NPC, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class);
|
||||
hook(NPC, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class);
|
||||
hook(NPC, ABILITY_TOGGLE, IAbilityEvent.ToggleEvent.class);
|
||||
hook(NPC, ABILITY_TOGGLE_TICK, IAbilityEvent.ToggleUpdateEvent.class);
|
||||
|
||||
// Chain
|
||||
hook(NPC, CHAIN_START, IChainEvent.StartEvent.class);
|
||||
hook(NPC, CHAIN_NEXT, IChainEvent.NextEvent.class);
|
||||
hook(NPC, CHAIN_COMPLETE, IChainEvent.CompleteEvent.class);
|
||||
hook(NPC, CHAIN_INTERRUPT, IChainEvent.InterruptEvent.class);
|
||||
|
||||
// Energy Barrier
|
||||
hook(NPC, ENERGY_BARRIER_SPAWNED, IEnergyBarrierEvent.SpawnedEvent.class);
|
||||
@@ -245,12 +256,20 @@ public class ScriptHookController implements IScriptHookHandler {
|
||||
hook(PLAYER, ENERGY_BARRIER_DESTROYED, IEnergyBarrierEvent.DestroyedEvent.class);
|
||||
|
||||
// Ability
|
||||
hook(PLAYER, ABILITY_START, IPlayerAbilityEvent.StartEvent.class);
|
||||
hook(PLAYER, ABILITY_EXECUTE, IPlayerAbilityEvent.ExecuteEvent.class);
|
||||
hook(PLAYER, ABILITY_HIT, IPlayerAbilityEvent.HitEvent.class);
|
||||
hook(PLAYER, ABILITY_TICK, IPlayerAbilityEvent.TickEvent.class);
|
||||
hook(PLAYER, ABILITY_INTERRUPT, IPlayerAbilityEvent.InterruptEvent.class);
|
||||
hook(PLAYER, ABILITY_COMPLETE, IPlayerAbilityEvent.CompleteEvent.class);
|
||||
hook(PLAYER, ABILITY_START, IAbilityEvent.StartEvent.class);
|
||||
hook(PLAYER, ABILITY_EXECUTE, IAbilityEvent.ExecuteEvent.class);
|
||||
hook(PLAYER, ABILITY_HIT, IAbilityEvent.HitEvent.class);
|
||||
hook(PLAYER, ABILITY_TICK, IAbilityEvent.TickEvent.class);
|
||||
hook(PLAYER, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class);
|
||||
hook(PLAYER, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class);
|
||||
hook(PLAYER, ABILITY_TOGGLE, IAbilityEvent.ToggleEvent.class);
|
||||
hook(PLAYER, ABILITY_TOGGLE_TICK, IAbilityEvent.ToggleUpdateEvent.class);
|
||||
|
||||
// Chain
|
||||
hook(PLAYER, CHAIN_START, IChainEvent.StartEvent.class);
|
||||
hook(PLAYER, CHAIN_NEXT, IChainEvent.NextEvent.class);
|
||||
hook(PLAYER, CHAIN_COMPLETE, IChainEvent.CompleteEvent.class);
|
||||
hook(PLAYER, CHAIN_INTERRUPT, IChainEvent.InterruptEvent.class);
|
||||
|
||||
// Auction
|
||||
hook(PLAYER, AUCTION_CREATE, IAuctionEvent.CreateEvent.class);
|
||||
@@ -333,6 +352,24 @@ public class ScriptHookController implements IScriptHookHandler {
|
||||
hook(EFFECT, ON_EFFECT_REMOVE, IPlayerEvent.EffectEvent.Removed.class);
|
||||
}
|
||||
|
||||
private void initializeAbilityHooks() {
|
||||
// Ability context (internal per-ability scripts)
|
||||
hook(ABILITY, ABILITY_START, IAbilityEvent.StartEvent.class);
|
||||
hook(ABILITY, ABILITY_EXECUTE, IAbilityEvent.ExecuteEvent.class);
|
||||
hook(ABILITY, ABILITY_HIT, IAbilityEvent.HitEvent.class);
|
||||
hook(ABILITY, ABILITY_TICK, IAbilityEvent.TickEvent.class);
|
||||
hook(ABILITY, ABILITY_INTERRUPT, IAbilityEvent.InterruptEvent.class);
|
||||
hook(ABILITY, ABILITY_COMPLETE, IAbilityEvent.CompleteEvent.class);
|
||||
hook(ABILITY, ABILITY_TOGGLE, IAbilityEvent.ToggleEvent.class);
|
||||
hook(ABILITY, ABILITY_TOGGLE_TICK, IAbilityEvent.ToggleUpdateEvent.class);
|
||||
|
||||
// Chained ability context (chain-level lifecycle hooks)
|
||||
hook(CHAINED_ABILITY, CHAIN_START, IChainEvent.StartEvent.class);
|
||||
hook(CHAINED_ABILITY, CHAIN_NEXT, IChainEvent.NextEvent.class);
|
||||
hook(CHAINED_ABILITY, CHAIN_COMPLETE, IChainEvent.CompleteEvent.class);
|
||||
hook(CHAINED_ABILITY, CHAIN_INTERRUPT, IChainEvent.InterruptEvent.class);
|
||||
}
|
||||
|
||||
private void initializeForgeHooks() {
|
||||
hook(FORGE, INIT, IForgeEvent.InitEvent.class);
|
||||
hook(FORGE, FORGE_WORLD, IForgeEvent.WorldEvent.class);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package noppes.npcs.controllers.data;
|
||||
|
||||
import kamkeel.npcs.network.packets.request.script.AbilityScriptPacket;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.constants.ScriptContext;
|
||||
|
||||
/**
|
||||
* Script handler for ability scripts.
|
||||
* Manages a single script container for ability hooks (abilityStart, abilityTick, abilityComplete, etc.).
|
||||
*/
|
||||
public class AbilityScript extends SingleScriptHandler implements IScriptHandlerPacket {
|
||||
|
||||
/**
|
||||
* The ability ID (UUID) for packet communication. Empty if not bound.
|
||||
*/
|
||||
private String abilityId = "";
|
||||
|
||||
/**
|
||||
* Create an unbound AbilityScript (for server-side use).
|
||||
*/
|
||||
public AbilityScript() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AbilityScript bound to a specific ability (for GUI use).
|
||||
*
|
||||
* @param abilityId The UUID of the ability
|
||||
*/
|
||||
public AbilityScript(String abilityId) {
|
||||
this.abilityId = abilityId != null ? abilityId : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptContext getContext() {
|
||||
return ScriptContext.ABILITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String noticeString() {
|
||||
return !abilityId.isEmpty() ? "Ability[" + abilityId + "]" : "Ability";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestData() {
|
||||
if (!abilityId.isEmpty())
|
||||
AbilityScriptPacket.Get(abilityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSavePacket(int index, int totalCount, NBTTagCompound nbt) {
|
||||
if (!abilityId.isEmpty())
|
||||
AbilityScriptPacket.Save(abilityId, index, totalCount, nbt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package noppes.npcs.controllers.data;
|
||||
|
||||
import kamkeel.npcs.network.packets.request.script.ChainedAbilityScriptPacket;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.constants.ScriptContext;
|
||||
|
||||
/**
|
||||
* Script handler for chained ability scripts.
|
||||
* Manages a single script container for chained ability hooks.
|
||||
*/
|
||||
public class ChainedAbilityScript extends SingleScriptHandler implements IScriptHandlerPacket {
|
||||
|
||||
/**
|
||||
* The chained ability ID (UUID) for packet communication. Empty if not bound.
|
||||
*/
|
||||
private String chainId = "";
|
||||
|
||||
/**
|
||||
* Create an unbound ChainedAbilityScript (for server-side use).
|
||||
*/
|
||||
public ChainedAbilityScript() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ChainedAbilityScript bound to a specific chained ability (for GUI use).
|
||||
*
|
||||
* @param chainId The UUID of the chained ability
|
||||
*/
|
||||
public ChainedAbilityScript(String chainId) {
|
||||
this.chainId = chainId != null ? chainId : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptContext getContext() {
|
||||
return ScriptContext.CHAINED_ABILITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String noticeString() {
|
||||
return !chainId.isEmpty() ? "ChainedAbility[" + chainId + "]" : "ChainedAbility";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestData() {
|
||||
if (!chainId.isEmpty())
|
||||
ChainedAbilityScriptPacket.Get(chainId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSavePacket(int index, int totalCount, NBTTagCompound nbt) {
|
||||
if (!chainId.isEmpty())
|
||||
ChainedAbilityScriptPacket.Save(chainId, index, totalCount, nbt);
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,6 @@ public class CustomEffect implements ICustomEffect {
|
||||
public String icon = "";
|
||||
public int iconX = 0, iconY = 0;
|
||||
|
||||
/**
|
||||
* Experimental script stuff.
|
||||
*/
|
||||
public Consumer<PlayerEvent.EffectEvent.Added> onAddedConsumer;
|
||||
public Consumer<PlayerEvent.EffectEvent.Ticked> onTickConsumer;
|
||||
public Consumer<PlayerEvent.EffectEvent.Removed> onRemovedConsumer;
|
||||
public String menuName = "§aNEW EFFECT";
|
||||
public int width = 16, height = 16;
|
||||
|
||||
@@ -169,26 +163,10 @@ public class CustomEffect implements ICustomEffect {
|
||||
}
|
||||
|
||||
|
||||
public void onAdded(Consumer<PlayerEvent.EffectEvent.Added> function) {
|
||||
onAddedConsumer = function;
|
||||
}
|
||||
|
||||
public void onTick(Consumer<PlayerEvent.EffectEvent.Ticked> function) {
|
||||
onTickConsumer = function;
|
||||
}
|
||||
|
||||
public void onRemoved(Consumer<PlayerEvent.EffectEvent.Removed> function) {
|
||||
onRemovedConsumer = function;
|
||||
}
|
||||
|
||||
public void onAdded(EntityPlayer player, PlayerEffect playerEffect) {
|
||||
IPlayer iPlayer = NoppesUtilServer.getIPlayer(player);
|
||||
if (playerEffect.index == 0) {
|
||||
PlayerEvent.EffectEvent.Added event = new PlayerEvent.EffectEvent.Added(iPlayer, playerEffect);
|
||||
|
||||
if (onAddedConsumer != null)
|
||||
onAddedConsumer.accept(event);
|
||||
|
||||
EffectScript script = getScriptHandler();
|
||||
if (script == null) {
|
||||
return;
|
||||
@@ -204,11 +182,6 @@ public class CustomEffect implements ICustomEffect {
|
||||
IPlayer iPlayer = NoppesUtilServer.getIPlayer(player);
|
||||
if (playerEffect.index == 0) {
|
||||
PlayerEvent.EffectEvent.Ticked event = new PlayerEvent.EffectEvent.Ticked(iPlayer, playerEffect);
|
||||
|
||||
if (onTickConsumer != null) {
|
||||
onTickConsumer.accept(event);
|
||||
}
|
||||
|
||||
EffectScript script = getScriptHandler();
|
||||
if (script == null) {
|
||||
return;
|
||||
@@ -225,11 +198,6 @@ public class CustomEffect implements ICustomEffect {
|
||||
|
||||
if (playerEffect.index == 0) {
|
||||
PlayerEvent.EffectEvent.Removed event = new PlayerEvent.EffectEvent.Removed(iPlayer, playerEffect, type);
|
||||
|
||||
if (onRemovedConsumer != null) {
|
||||
onRemovedConsumer.accept(event);
|
||||
}
|
||||
|
||||
EffectScript script = getScriptHandler();
|
||||
if (script == null) {
|
||||
return;
|
||||
|
||||
@@ -25,10 +25,6 @@ import noppes.npcs.AbstractDataAbilities;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -129,53 +125,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
return player != null ? player.worldObj.getTotalWorldTime() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireTickEvent(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.TickEvent event = new PlayerAbilityEvent.TickEvent(
|
||||
iPlayer, ability, target, ability.getPhase().ordinal(), ability.getCurrentTick());
|
||||
EventHooks.onPlayerAbilityTick(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fireExecuteEvent(Ability ability, EntityLivingBase target) {
|
||||
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.ExecuteEvent event = new PlayerAbilityEvent.ExecuteEvent(iPlayer, ability, target);
|
||||
return EventHooks.onPlayerAbilityExecute(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireCompleteEvent(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.CompleteEvent event = new PlayerAbilityEvent.CompleteEvent(iPlayer, ability, target);
|
||||
EventHooks.onPlayerAbilityComplete(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireInterruptEvent(Ability ability, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (ScriptController.Instance == null || player == null) return;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.InterruptEvent event = new PlayerAbilityEvent.InterruptEvent(
|
||||
iPlayer, ability, target, source, damage);
|
||||
EventHooks.onPlayerAbilityInterrupt(handler, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnTelegraph(Ability ability, EntityLivingBase target) {
|
||||
EntityPlayer player = playerData.player;
|
||||
@@ -423,7 +372,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
}
|
||||
|
||||
// Fire start event (cancelable)
|
||||
if (firePlayerStartEvent(player, ability)) {
|
||||
if (EventHooks.onAbilityStart(ability, player, null)) {
|
||||
return false; // Cancelled
|
||||
}
|
||||
|
||||
@@ -491,29 +440,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
protected void onToggleStateChanged(String key, boolean active, int state) {
|
||||
}
|
||||
|
||||
@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
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -1035,22 +961,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SCRIPT EVENTS (Player-specific: start event only, others via abstract)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Fire the ability start event. Returns true if cancelled.
|
||||
*/
|
||||
private boolean firePlayerStartEvent(EntityPlayer player, Ability ability) {
|
||||
if (ScriptController.Instance == null) return false;
|
||||
PlayerDataScript handler = ScriptController.Instance.getPlayerScripts(player);
|
||||
if (handler == null) return false;
|
||||
IPlayer iPlayer = (IPlayer) NpcAPI.Instance().getIEntity(player);
|
||||
PlayerAbilityEvent.StartEvent event = new PlayerAbilityEvent.StartEvent(iPlayer, ability, null);
|
||||
return EventHooks.onPlayerAbilityStart(handler, event);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// NBT SERIALIZATION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -1096,7 +1006,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
}
|
||||
playingAbilityAnimation = compound.getBoolean("AbilityAnimating");
|
||||
|
||||
// Active toggles - restore state directly (no onToggleOn callback during load)
|
||||
// Active toggles - restore state directly (no onToggle callback during load)
|
||||
activeToggles.clear();
|
||||
if (compound.hasKey("ActiveToggles")) {
|
||||
NBTTagList toggleNbt = compound.getTagList("ActiveToggles", 10); // 10 = TAG_COMPOUND
|
||||
|
||||
@@ -8,22 +8,48 @@ import noppes.npcs.api.IDamageSource;
|
||||
import noppes.npcs.api.ability.IAbility;
|
||||
import noppes.npcs.api.entity.ICustomNpc;
|
||||
import noppes.npcs.api.entity.IEntityLivingBase;
|
||||
import noppes.npcs.api.entity.IPlayer;
|
||||
import noppes.npcs.api.event.IAbilityEvent;
|
||||
import noppes.npcs.constants.EnumScriptType;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
* Events fired during NPC ability execution lifecycle.
|
||||
* Unified ability events for both NPCs and Players.
|
||||
*/
|
||||
public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
public class AbilityEvent extends CustomNPCsEvent implements IAbilityEvent {
|
||||
protected final IEntityLivingBase entity;
|
||||
public final IPlayer player;
|
||||
public final ICustomNpc<?> npc;
|
||||
protected final Ability ability;
|
||||
protected final IEntityLivingBase target;
|
||||
private transient IAbility abilityCopy;
|
||||
|
||||
public AbilityEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) {
|
||||
super(npc);
|
||||
public AbilityEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target) {
|
||||
this.entity = entity != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(entity) : null;
|
||||
this.ability = ability;
|
||||
this.target = target != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(target) : null;
|
||||
this.player = (this.entity instanceof IPlayer) ? (IPlayer) this.entity : null;
|
||||
this.npc = (this.entity instanceof ICustomNpc) ? (ICustomNpc<?>) this.entity : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingBase getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICustomNpc<?> getNpc() {
|
||||
return npc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNPC() {
|
||||
return entity instanceof ICustomNpc;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,13 +70,10 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
return "abilityEvent";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when an ability starts executing (enters WINDUP phase).
|
||||
*/
|
||||
@Cancelable
|
||||
public static class StartEvent extends AbilityEvent implements IAbilityEvent.StartEvent {
|
||||
public StartEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) {
|
||||
super(npc, ability, target);
|
||||
public StartEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target) {
|
||||
super(entity, ability, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,13 +82,10 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when an ability enters the ACTIVE phase and performs its effect.
|
||||
*/
|
||||
@Cancelable
|
||||
public static class ExecuteEvent extends AbilityEvent implements IAbilityEvent.ExecuteEvent {
|
||||
public ExecuteEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) {
|
||||
super(npc, ability, target);
|
||||
public ExecuteEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target) {
|
||||
super(entity, ability, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,16 +94,13 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when an ability is interrupted by damage.
|
||||
*/
|
||||
public static class InterruptEvent extends AbilityEvent implements IAbilityEvent.InterruptEvent {
|
||||
private final IDamageSource damageSource;
|
||||
private final float damage;
|
||||
|
||||
public InterruptEvent(ICustomNpc npc, Ability ability, EntityLivingBase target, DamageSource source, float damage) {
|
||||
super(npc, ability, target);
|
||||
this.damageSource = NpcAPI.Instance().getIDamageSource(source);
|
||||
public InterruptEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target, DamageSource source, float damage) {
|
||||
super(entity, ability, target);
|
||||
this.damageSource = source != null ? NpcAPI.Instance().getIDamageSource(source) : null;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@@ -103,12 +120,9 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when an ability completes its full execution cycle.
|
||||
*/
|
||||
public static class CompleteEvent extends AbilityEvent implements IAbilityEvent.CompleteEvent {
|
||||
public CompleteEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) {
|
||||
super(npc, ability, target);
|
||||
public CompleteEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target) {
|
||||
super(entity, ability, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,10 +131,6 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when an ability hits an entity with damage.
|
||||
* This is a cancelable event - canceling prevents the damage from being applied.
|
||||
*/
|
||||
@Cancelable
|
||||
public static class HitEvent extends AbilityEvent implements IAbilityEvent.HitEvent {
|
||||
private final IEntityLivingBase hitEntity;
|
||||
@@ -128,9 +138,9 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
private float knockback;
|
||||
private float knockbackUp;
|
||||
|
||||
public HitEvent(ICustomNpc npc, Ability ability, EntityLivingBase target,
|
||||
public HitEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target,
|
||||
EntityLivingBase hitEntity, float damage, float knockback, float knockbackUp) {
|
||||
super(npc, ability, target);
|
||||
super(entity, ability, target);
|
||||
this.hitEntity = hitEntity != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(hitEntity) : null;
|
||||
this.damage = damage;
|
||||
this.knockback = knockback;
|
||||
@@ -178,16 +188,12 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired every tick while an ability is executing.
|
||||
* Provides information about the current phase and tick.
|
||||
*/
|
||||
public static class TickEvent extends AbilityEvent implements IAbilityEvent.TickEvent {
|
||||
private final int abilityPhase;
|
||||
private final int tick;
|
||||
|
||||
public TickEvent(ICustomNpc npc, Ability ability, EntityLivingBase target, int phase, int tick) {
|
||||
super(npc, ability, target);
|
||||
public TickEvent(EntityLivingBase entity, Ability ability, EntityLivingBase target, int phase, int tick) {
|
||||
super(entity, ability, target);
|
||||
this.abilityPhase = phase;
|
||||
this.tick = tick;
|
||||
}
|
||||
@@ -208,17 +214,13 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public ToggleEvent(EntityLivingBase entity, Ability ability, int oldState, int newState) {
|
||||
super(entity, ability, null);
|
||||
this.oldState = oldState;
|
||||
this.newState = newState;
|
||||
}
|
||||
@@ -244,17 +246,13 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public ToggleUpdateEvent(EntityLivingBase entity, Ability ability, int tick, int state) {
|
||||
super(entity, ability, null);
|
||||
this.tick = tick;
|
||||
this.state = state;
|
||||
}
|
||||
@@ -281,7 +279,7 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ABILITY_TOGGLE_UPDATE.function;
|
||||
return EnumScriptType.ABILITY_TOGGLE_TICK.function;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package noppes.npcs.scripted.event;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.data.ChainedAbility;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.api.IDamageSource;
|
||||
import noppes.npcs.api.ability.IChainedAbility;
|
||||
import noppes.npcs.api.entity.ICustomNpc;
|
||||
import noppes.npcs.api.entity.IEntityLivingBase;
|
||||
import noppes.npcs.api.entity.IPlayer;
|
||||
import noppes.npcs.api.event.IChainEvent;
|
||||
import noppes.npcs.constants.EnumScriptType;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
* Unified chain events for both NPCs and Players.
|
||||
*/
|
||||
public class ChainEvent extends CustomNPCsEvent implements IChainEvent {
|
||||
protected final IEntityLivingBase entity;
|
||||
public final IPlayer player;
|
||||
public final ICustomNpc<?> npc;
|
||||
protected final ChainedAbility chain;
|
||||
protected final IEntityLivingBase target;
|
||||
protected final int entryIndex;
|
||||
|
||||
public ChainEvent(EntityLivingBase entity, ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
this.entity = entity != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(entity) : null;
|
||||
this.chain = chain;
|
||||
this.entryIndex = entryIndex;
|
||||
this.target = target != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(target) : null;
|
||||
this.player = (this.entity instanceof IPlayer) ? (IPlayer) this.entity : null;
|
||||
this.npc = (this.entity instanceof ICustomNpc) ? (ICustomNpc<?>) this.entity : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingBase getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICustomNpc<?> getNpc() {
|
||||
return npc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNPC() {
|
||||
return entity instanceof ICustomNpc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChainedAbility getChain() {
|
||||
return chain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingBase getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntryIndex() {
|
||||
return entryIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return "chainEvent";
|
||||
}
|
||||
|
||||
public static class StartEvent extends ChainEvent implements IChainEvent.StartEvent {
|
||||
public StartEvent(EntityLivingBase entity, ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
super(entity, chain, entryIndex, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.CHAIN_START.function;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NextEvent extends ChainEvent implements IChainEvent.NextEvent {
|
||||
public NextEvent(EntityLivingBase entity, ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
super(entity, chain, entryIndex, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.CHAIN_NEXT.function;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CompleteEvent extends ChainEvent implements IChainEvent.CompleteEvent {
|
||||
public CompleteEvent(EntityLivingBase entity, ChainedAbility chain, int entryIndex, EntityLivingBase target) {
|
||||
super(entity, chain, entryIndex, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.CHAIN_COMPLETE.function;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InterruptEvent extends ChainEvent implements IChainEvent.InterruptEvent {
|
||||
private final IDamageSource damageSource;
|
||||
private final float damage;
|
||||
|
||||
public InterruptEvent(EntityLivingBase entity, ChainedAbility chain, int entryIndex, EntityLivingBase target,
|
||||
DamageSource source, float damage) {
|
||||
super(entity, chain, entryIndex, target);
|
||||
this.damageSource = source != null ? NpcAPI.Instance().getIDamageSource(source) : null;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDamageSource getDamageSource() {
|
||||
return damageSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.CHAIN_INTERRUPT.function;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
package noppes.npcs.scripted.event.player;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.Cancelable;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.api.IDamageSource;
|
||||
import noppes.npcs.api.ability.IAbility;
|
||||
import noppes.npcs.api.entity.IEntityLivingBase;
|
||||
import noppes.npcs.api.entity.IPlayer;
|
||||
import noppes.npcs.api.event.IPlayerAbilityEvent;
|
||||
import noppes.npcs.constants.EnumScriptType;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
* Events fired during player ability execution lifecycle.
|
||||
*/
|
||||
public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEvent {
|
||||
protected final Ability ability;
|
||||
protected final IEntityLivingBase target;
|
||||
private transient IAbility abilityCopy;
|
||||
|
||||
public PlayerAbilityEvent(IPlayer player, Ability ability, EntityLivingBase target) {
|
||||
super(player);
|
||||
this.ability = ability;
|
||||
this.target = target != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(target) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAbility getAbility() {
|
||||
if (abilityCopy == null && ability != null) {
|
||||
abilityCopy = ability.deepCopy();
|
||||
}
|
||||
return abilityCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingBase getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return "playerAbilityEvent";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a player's ability starts executing (enters WINDUP phase).
|
||||
*/
|
||||
@Cancelable
|
||||
public static class StartEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.StartEvent {
|
||||
public StartEvent(IPlayer player, Ability ability, EntityLivingBase target) {
|
||||
super(player, ability, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ABILITY_START.function;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a player's ability enters the ACTIVE phase and performs its effect.
|
||||
*/
|
||||
@Cancelable
|
||||
public static class ExecuteEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.ExecuteEvent {
|
||||
public ExecuteEvent(IPlayer player, Ability ability, EntityLivingBase target) {
|
||||
super(player, ability, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ABILITY_EXECUTE.function;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a player's ability is interrupted by damage.
|
||||
*/
|
||||
public static class InterruptEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.InterruptEvent {
|
||||
private final IDamageSource damageSource;
|
||||
private final float damage;
|
||||
|
||||
public InterruptEvent(IPlayer player, Ability ability, EntityLivingBase target, DamageSource source, float damage) {
|
||||
super(player, ability, target);
|
||||
this.damageSource = source != null ? NpcAPI.Instance().getIDamageSource(source) : null;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDamageSource getDamageSource() {
|
||||
return damageSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ABILITY_INTERRUPT.function;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a player's ability completes its full execution cycle.
|
||||
*/
|
||||
public static class CompleteEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.CompleteEvent {
|
||||
public CompleteEvent(IPlayer player, Ability ability, EntityLivingBase target) {
|
||||
super(player, ability, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ABILITY_COMPLETE.function;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired every tick while a player's ability is executing.
|
||||
*/
|
||||
public static class TickEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.TickEvent {
|
||||
private final int abilityPhase;
|
||||
private final int tick;
|
||||
|
||||
public TickEvent(IPlayer player, Ability ability, EntityLivingBase target, int phase, int tick) {
|
||||
super(player, ability, target);
|
||||
this.abilityPhase = phase;
|
||||
this.tick = tick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAbilityPhase() {
|
||||
return abilityPhase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTick() {
|
||||
return tick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ABILITY_TICK.function;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a player's ability hits an entity with damage.
|
||||
* Canceling prevents the damage from being applied.
|
||||
*/
|
||||
@Cancelable
|
||||
public static class HitEvent extends PlayerAbilityEvent implements IPlayerAbilityEvent.HitEvent {
|
||||
private final IEntityLivingBase hitEntity;
|
||||
private float damage;
|
||||
private float knockback;
|
||||
private float knockbackUp;
|
||||
|
||||
public HitEvent(IPlayer player, Ability ability, EntityLivingBase target,
|
||||
EntityLivingBase hitEntity, float damage, float knockback, float knockbackUp) {
|
||||
super(player, ability, target);
|
||||
this.hitEntity = hitEntity != null ? (IEntityLivingBase) NpcAPI.Instance().getIEntity(hitEntity) : null;
|
||||
this.damage = damage;
|
||||
this.knockback = knockback;
|
||||
this.knockbackUp = knockbackUp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingBase getHitEntity() {
|
||||
return hitEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDamage(float damage) {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getKnockback() {
|
||||
return knockback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKnockback(float knockback) {
|
||||
this.knockback = knockback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getKnockbackUp() {
|
||||
return knockbackUp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKnockbackUp(float knockbackUp) {
|
||||
this.knockbackUp = knockbackUp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,8 @@ public class ScriptDataAbilities implements IDataAbilities {
|
||||
|
||||
@Override
|
||||
public void addAbilityReference(String key) {
|
||||
// Validate ability exists and allows NPCs
|
||||
Ability ability = AbilityController.Instance.resolveAbility(key);
|
||||
// Validate ability exists and allows NPCs (peek avoids deep copy)
|
||||
Ability ability = AbilityController.Instance.peekAbility(key);
|
||||
if (ability == null) {
|
||||
return; // Ability doesn't exist
|
||||
}
|
||||
@@ -145,6 +145,11 @@ public class ScriptDataAbilities implements IDataAbilities {
|
||||
data.interruptCurrentAbility(null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeCurrentAbility() {
|
||||
data.completeCurrentAbility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGlobalCooldown() {
|
||||
return (int) data.getRemainingCooldown();
|
||||
@@ -191,8 +196,8 @@ public class ScriptDataAbilities implements IDataAbilities {
|
||||
|
||||
@Override
|
||||
public boolean executeAbility(String key, Object target) {
|
||||
// Validate ability exists and allows NPCs
|
||||
Ability ability = AbilityController.Instance.resolveAbility(key);
|
||||
// Validate ability exists and allows NPCs (peek avoids deep copy)
|
||||
Ability ability = AbilityController.Instance.peekAbility(key);
|
||||
if (ability == null) {
|
||||
return false; // Ability doesn't exist
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
@Override
|
||||
public void unlockAbility(String key) {
|
||||
// Validate ability exists and allows players
|
||||
Ability ability = AbilityController.Instance.resolveAbility(key);
|
||||
// Validate ability exists and allows players (peek avoids deep copy)
|
||||
Ability ability = AbilityController.Instance.peekAbility(key);
|
||||
if (ability == null) {
|
||||
return; // Ability doesn't exist
|
||||
}
|
||||
@@ -94,6 +94,11 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData {
|
||||
data.interruptCurrentAbility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeCurrentAbility() {
|
||||
data.completeCurrentAbility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnCooldown() {
|
||||
return data.isOnCooldown();
|
||||
@@ -162,7 +167,7 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData {
|
||||
* 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);
|
||||
Ability ability = AbilityController.Instance.peekAbility(key);
|
||||
if (ability != null && ability.getId() != null) {
|
||||
return ability.getId();
|
||||
}
|
||||
|
||||
@@ -1678,6 +1678,8 @@ telegraph.ring=Ring
|
||||
telegraph.line=Line
|
||||
telegraph.cone=Cone
|
||||
telegraph.point=Point
|
||||
telegraph.square=Square
|
||||
telegraph.none=None
|
||||
|
||||
ability.tab.timing=Timing
|
||||
ability.tab.effects=Effects
|
||||
@@ -1688,6 +1690,13 @@ ability.telegraphSizeNote=Size is based on ability parameters
|
||||
|
||||
# Ability Effects Tab
|
||||
|
||||
ability.cnpc.custom=Custom
|
||||
ability.telegraphShape=Telegraph Shape
|
||||
ability.toggleEnabled=Toggle
|
||||
ability.toggleStates=Toggle States
|
||||
ability.hasActiveToggle=Active Toggle Tick
|
||||
ability.section.toggle=Toggle
|
||||
ability.section.execution=Execution
|
||||
ability.cnpc.slam=Slam
|
||||
ability.cnpc.heavy_hit=Heavy Hit
|
||||
ability.cnpc.cutter=Cutter
|
||||
@@ -1915,6 +1924,7 @@ ability.targetFilter.all=All
|
||||
ability.includeSelf=Include Self
|
||||
ability.section.targeting=Targeting
|
||||
ability.concurrent=Concurrent
|
||||
ability.customScriptInfo=This ability is for scripting purposes
|
||||
ability.concurrentInfo=This ability allows Concurrency in Chains
|
||||
ability.hover.concurrent=When enabled, this ability fires at the same time as the ability above it in the chain
|
||||
ability.customEffects=Custom Effects
|
||||
@@ -1961,7 +1971,6 @@ ability.lightningRadius=Radius
|
||||
|
||||
# Projectile Count / Settings
|
||||
ability.projectileCount=Count
|
||||
ability.dualFireDelay=Fire Delay
|
||||
ability.fireDelay=Fire Delay
|
||||
ability.colorOverride=Color Override
|
||||
|
||||
@@ -2085,6 +2094,9 @@ ability.section.properties=Properties
|
||||
ability.section.panel=Panel
|
||||
ability.section.launch=Launch
|
||||
ability.section.slicer=Slicer
|
||||
ability.section.icon=Icon
|
||||
ability.hasIcon=Custom Icon
|
||||
ability.icon.tint=Tint Color
|
||||
|
||||
ability.domeRadius=Dome Radius
|
||||
ability.useHealth=Use Health
|
||||
@@ -2197,6 +2209,9 @@ condition.hover.hitCount=Activates after receiving X hits within the time window
|
||||
condition.hover.hits=Number of hits required to trigger condition
|
||||
condition.hover.within=Time window in ticks (20 ticks = 1 second)
|
||||
condition.hover.threshold=Health percentage threshold (1-99%)
|
||||
condition.hover.armor_slots=Equipment in specific armor slots
|
||||
condition.hover.item_name=Filter by item name or identifier
|
||||
condition.hover.required_count=Number of matching items required
|
||||
|
||||
# Effect Hover Tooltips
|
||||
effect.hover.type=Potion effect type to apply to targets
|
||||
|
||||
Reference in new issue
Block a user