mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 06:07:27 +00:00
Refactor ability handling: replace damage handler with ability extenders for improved lifecycle management and damage routing
This commit is contained in:
1 parent
f8c75dd79b
commit
0f774a68fc
7 files changed
+184
-53
No files matched your search
@@ -329,13 +329,10 @@ public abstract class Ability implements IAbility {
|
||||
|
||||
// Apply damage
|
||||
if (damage > 0) {
|
||||
// Check for external damage handler first (e.g., DBC Addon)
|
||||
IAbilityDamageHandler handler = AbilityController.Instance.getDamageHandler();
|
||||
boolean handled = false;
|
||||
if (handler != null) {
|
||||
handled = handler.handleDamage(this, caster, hitEntity, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ);
|
||||
}
|
||||
// Check for ability extenders (e.g., DBC Addon damage routing)
|
||||
boolean handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
this, caster, hitEntity, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ);
|
||||
if (!handled) {
|
||||
// Default damage path
|
||||
if (caster instanceof EntityNPCInterface) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.type.*;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.LogWriter;
|
||||
@@ -24,7 +25,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
// ── Extension Points ─────────────────────────────────────────────────────
|
||||
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
|
||||
private final List<IAbilityFieldProvider> fieldProviders = new ArrayList<>();
|
||||
private IAbilityDamageHandler damageHandler = null;
|
||||
private final List<IAbilityExtender> extenders = new ArrayList<>();
|
||||
|
||||
// ── Derived State ────────────────────────────────────────────────────────
|
||||
private final Set<String> builtInTypeIds = new HashSet<>();
|
||||
@@ -350,14 +351,6 @@ public class AbilityController implements IAbilityHandler {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void registerDamageHandler(IAbilityDamageHandler handler) {
|
||||
this.damageHandler = handler;
|
||||
}
|
||||
|
||||
public IAbilityDamageHandler getDamageHandler() {
|
||||
return damageHandler;
|
||||
}
|
||||
|
||||
public void registerFieldProvider(IAbilityFieldProvider provider) {
|
||||
fieldProviders.add(provider);
|
||||
}
|
||||
@@ -366,6 +359,64 @@ public class AbilityController implements IAbilityHandler {
|
||||
return fieldProviders;
|
||||
}
|
||||
|
||||
public void registerExtender(IAbilityExtender extender) {
|
||||
extenders.add(extender);
|
||||
}
|
||||
|
||||
public List<IAbilityExtender> getExtenders() {
|
||||
return extenders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire onAbilityStart on all extenders. Returns false if ANY extender cancels.
|
||||
*/
|
||||
public boolean fireOnAbilityStart(Ability ability, EntityLivingBase caster, EntityLivingBase target) {
|
||||
for (IAbilityExtender ext : extenders) {
|
||||
if (!ext.onAbilityStart(ability, caster, target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire onAbilityTick on all extenders. Returns false if ANY extender says to interrupt.
|
||||
*/
|
||||
public boolean fireOnAbilityTick(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
AbilityPhase phase, int tick) {
|
||||
for (IAbilityExtender ext : extenders) {
|
||||
if (!ext.onAbilityTick(ability, caster, target, phase, tick)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire onAbilityComplete on all extenders.
|
||||
*/
|
||||
public void fireOnAbilityComplete(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
boolean interrupted) {
|
||||
for (IAbilityExtender ext : extenders) {
|
||||
ext.onAbilityComplete(ability, caster, target, interrupted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire onAbilityDamage on all extenders. Chain of responsibility — first true wins.
|
||||
*/
|
||||
public boolean fireOnAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
double knockbackDirX, double knockbackDirZ) {
|
||||
for (IAbilityExtender ext : extenders) {
|
||||
if (ext.onAbilityDamage(ability, caster, target, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// IAbilityHandler
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
|
||||
/**
|
||||
* External damage handler for abilities.
|
||||
* When registered, called AFTER script events but BEFORE default attackEntityFrom.
|
||||
* If handleDamage returns true, default damage is skipped.
|
||||
*/
|
||||
public interface IAbilityDamageHandler {
|
||||
/**
|
||||
* Handle damage from an ability.
|
||||
*
|
||||
* @param ability The ability dealing damage
|
||||
* @param caster The entity executing the ability
|
||||
* @param target The entity being hit
|
||||
* @param damage The damage amount (after script event modifications)
|
||||
* @param knockback The horizontal knockback
|
||||
* @param knockbackUp The vertical knockback
|
||||
* @param knockbackDirX The X component of knockback direction
|
||||
* @param knockbackDirZ The Z component of knockback direction
|
||||
* @return true if damage was handled (skip default), false to use default damage
|
||||
*/
|
||||
boolean handleDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
double knockbackDirX, double knockbackDirZ);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
|
||||
/**
|
||||
* General-purpose extension point for abilities. Multiple extenders can be registered.
|
||||
* Covers lifecycle hooks (start/tick/complete) and damage handling.
|
||||
* <p>
|
||||
* All methods have no-op defaults so extenders only override what they need.
|
||||
*/
|
||||
public interface IAbilityExtender {
|
||||
|
||||
/**
|
||||
* Called before an ability starts executing.
|
||||
* Return false to cancel the ability (e.g., insufficient resources).
|
||||
*
|
||||
* @param ability The ability about to start
|
||||
* @param caster The entity executing the ability (NPC or Player)
|
||||
* @param target The target entity (may be null for player self-cast)
|
||||
* @return true to allow, false to cancel
|
||||
*/
|
||||
default boolean onAbilityStart(Ability ability, EntityLivingBase caster, EntityLivingBase target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called each tick during ability execution.
|
||||
* Return false to interrupt the ability (e.g., resource depleted).
|
||||
*
|
||||
* @param ability The executing ability
|
||||
* @param caster The entity executing the ability
|
||||
* @param target The target entity (may be null)
|
||||
* @param phase The current ability phase (WINDUP, ACTIVE, DAZED, BURST_DELAY)
|
||||
* @param tick The current tick within the phase
|
||||
* @return true to continue, false to interrupt
|
||||
*/
|
||||
default boolean onAbilityTick(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
AbilityPhase phase, int tick) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an ability completes (normally or via interruption).
|
||||
*
|
||||
* @param ability The completed ability
|
||||
* @param caster The entity that was executing the ability
|
||||
* @param target The target entity (may be null)
|
||||
* @param interrupted true if the ability was interrupted, false if it completed normally
|
||||
*/
|
||||
default void onAbilityComplete(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
boolean interrupted) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an ability deals damage, AFTER script events but BEFORE default attackEntityFrom.
|
||||
* Return true to handle the damage (skips default damage application).
|
||||
* <p>
|
||||
* Chain of responsibility: extenders are called in registration order.
|
||||
* The first extender returning true claims the damage; remaining extenders are skipped.
|
||||
*
|
||||
* @param ability The ability dealing damage
|
||||
* @param caster The entity executing the ability
|
||||
* @param target The entity being hit
|
||||
* @param damage The damage amount (after script event modifications)
|
||||
* @param knockback The horizontal knockback
|
||||
* @param knockbackUp The vertical knockback
|
||||
* @param knockbackDirX The X component of knockback direction
|
||||
* @param knockbackDirZ The Z component of knockback direction
|
||||
* @return true if damage was handled (skip default), false to pass to next extender or default
|
||||
*/
|
||||
default boolean onAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
double knockbackDirX, double knockbackDirZ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.IAbilityDamageHandler;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.data.*;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -326,17 +326,15 @@ public abstract class EntityAbilityProjectile extends Entity implements IEnergyP
|
||||
if (previewMode) return; // Skip damage in preview mode
|
||||
Entity owner = getOwnerEntity();
|
||||
|
||||
// Check for external damage handler (e.g., DBC Addon)
|
||||
// Check for ability extenders (e.g., DBC Addon damage routing)
|
||||
boolean handled = false;
|
||||
if (sourceAbility != null && owner instanceof EntityLivingBase) {
|
||||
IAbilityDamageHandler handler = AbilityController.Instance.getDamageHandler();
|
||||
if (handler != null) {
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
float kbUp = getKnockbackUp() > 0 ? getKnockbackUp() : 0.1f;
|
||||
handled = handler.handleDamage(sourceAbility, (EntityLivingBase) owner, target,
|
||||
dmg, kb, kbUp, dx, dz);
|
||||
}
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
float kbUp = getKnockbackUp() > 0 ? getKnockbackUp() : 0.1f;
|
||||
handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
sourceAbility, (EntityLivingBase) owner, target,
|
||||
dmg, kb, kbUp, dx, dz);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
|
||||
@@ -171,6 +171,13 @@ public class DataAbilities {
|
||||
// Fire tick event for scripts
|
||||
fireTickEvent(currentAbility, target);
|
||||
|
||||
// Fire extender tick hook (e.g., per-tick resource drain)
|
||||
if (!AbilityController.Instance.fireOnAbilityTick(currentAbility, npc, target,
|
||||
currentAbility.getPhase(), currentAbility.getCurrentTick())) {
|
||||
interruptCurrentAbility(null, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle phase-specific logic
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
@@ -342,6 +349,9 @@ public class DataAbilities {
|
||||
// Remove any remaining telegraphs (for abilities that keep telegraph during active)
|
||||
removeTelegraph(currentAbility);
|
||||
|
||||
// Fire extender complete hook
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, npc, target, false);
|
||||
|
||||
// Call onComplete callback
|
||||
currentAbility.onComplete(npc, target);
|
||||
|
||||
@@ -501,6 +511,11 @@ public class DataAbilities {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fire extender start hook (e.g., resource cost checks)
|
||||
if (!AbilityController.Instance.fireOnAbilityStart(ability, npc, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentAbility = ability;
|
||||
lastTarget = target;
|
||||
ability.start(target);
|
||||
@@ -750,6 +765,9 @@ public class DataAbilities {
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Fire extender complete hook (interrupted)
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, npc, lastTarget, true);
|
||||
|
||||
// Fire interrupt event
|
||||
AbilityEvent.InterruptEvent interruptEvent = new AbilityEvent.InterruptEvent(
|
||||
npc.wrappedNPC, currentAbility, lastTarget, source, damage);
|
||||
|
||||
@@ -147,6 +147,13 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
// Tick advances time and possibly changes phase
|
||||
boolean phaseChanged = currentAbility.tick();
|
||||
|
||||
// Fire extender tick hook (e.g., per-tick resource drain)
|
||||
if (!AbilityController.Instance.fireOnAbilityTick(currentAbility, player, currentTarget,
|
||||
currentAbility.getPhase(), currentAbility.getCurrentTick())) {
|
||||
interruptCurrentAbility();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.BURST_DELAY) {
|
||||
@@ -249,6 +256,9 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
private void handleAbilityCompletion(EntityPlayer player) {
|
||||
if (currentAbility == null) return;
|
||||
|
||||
// Fire extender complete hook
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, player, currentTarget, false);
|
||||
|
||||
currentAbility.onComplete(player, currentTarget);
|
||||
|
||||
// Release all locks
|
||||
@@ -318,6 +328,11 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
// Check conditions (skip target-requiring ones)
|
||||
if (!ability.checkConditionsForPlayer(player)) return false;
|
||||
|
||||
// Fire extender start hook (e.g., resource cost checks)
|
||||
if (!AbilityController.Instance.fireOnAbilityStart(ability, player, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start the ability
|
||||
currentAbility = ability;
|
||||
currentAbilityKey = key;
|
||||
@@ -517,6 +532,9 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
public void interruptCurrentAbility() {
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
// Fire extender complete hook (interrupted)
|
||||
AbilityController.Instance.fireOnAbilityComplete(currentAbility, playerData.player, currentTarget, true);
|
||||
|
||||
currentAbility.interrupt();
|
||||
stopAbilityAnimation();
|
||||
releaseRotationControl();
|
||||
|
||||
Reference in new issue
Block a user