Better Lock Movement handling

This commit is contained in:
Kam committed 2026-01-28 15:16:28 -05:00
1 parent 83ccd25b33
commit 7d2bba7b95
24 files changed
+227 -60

No files matched your search

@@ -55,7 +55,7 @@ public abstract class Ability implements IAbility {
protected boolean interruptible = true;
// Feedback
protected boolean lockMovement = true;
protected LockMovementType lockMovement = LockMovementType.WINDUP;
protected int windUpColor = 0x80FF4400; // Telegraph color during wind up
protected int activeColor = 0xC0FF0000; // Telegraph warning/active color
@@ -662,7 +662,7 @@ public abstract class Ability implements IAbility {
nbt.setInteger("windUp", windUpTicks);
nbt.setInteger("recovery", dazedTicks);
nbt.setBoolean("interruptible", interruptible);
nbt.setBoolean("lockMovement", lockMovement);
nbt.setInteger("lockMovement", lockMovement.ordinal());
nbt.setInteger("windUpColor", windUpColor);
nbt.setInteger("activeColor", activeColor);
nbt.setString("windUpSound", windUpSound);
@@ -707,7 +707,7 @@ public abstract class Ability implements IAbility {
windUpTicks = nbt.getInteger("windUp");
dazedTicks = nbt.getInteger("recovery");
interruptible = nbt.getBoolean("interruptible");
lockMovement = nbt.getBoolean("lockMovement");
lockMovement = LockMovementType.fromOrdinal(nbt.getInteger("lockMovement"));
// Support old telegraphColor key for backwards compatibility
if (nbt.hasKey("windUpColor")) {
windUpColor = nbt.getInteger("windUpColor");
@@ -868,14 +868,62 @@ public abstract class Ability implements IAbility {
this.interruptible = interruptible;
}
public boolean isLockMovement() {
public LockMovementType getLockMovement() {
return lockMovement;
}
public void setLockMovement(boolean lockMovement) {
public void setLockMovement(LockMovementType lockMovement) {
this.lockMovement = lockMovement;
}
/**
* API method: Get lock movement type as integer.
* @return 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
*/
@Override
public int getLockMovementType() {
return lockMovement.ordinal();
}
/**
* API method: Set lock movement type from integer.
* @param type 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
*/
@Override
public void setLockMovementType(int type) {
this.lockMovement = LockMovementType.fromOrdinal(type);
}
/**
* Check if movement should be locked during WINDUP phase.
*/
@Override
public boolean isMovementLockedDuringWindup() {
return lockMovement.locksWindup();
}
/**
* Check if movement should be locked during ACTIVE phase.
*/
@Override
public boolean isMovementLockedDuringActive() {
return lockMovement.locksActive();
}
/**
* Check if movement should be locked during the current phase.
*/
public boolean isMovementLockedForCurrentPhase() {
switch (phase) {
case WINDUP:
return lockMovement.locksWindup();
case ACTIVE:
return lockMovement.locksActive();
default:
return false;
}
}
public int getWindUpColor() {
return windUpColor;
}
@@ -0,0 +1,83 @@
package kamkeel.npcs.controllers.data.ability;
/**
* Defines when an ability locks NPC movement and rotation.
*/
public enum LockMovementType {
/**
* No movement lock - NPC can move freely during both phases.
*/
NO,
/**
* Lock movement during WINDUP phase only.
* NPC can move during ACTIVE phase.
*/
WINDUP,
/**
* Lock movement during ACTIVE phase only.
* NPC can move during WINDUP phase.
*/
ACTIVE,
/**
* Lock movement during both WINDUP and ACTIVE phases.
*/
WINDUP_AND_ACTIVE;
/**
* Check if movement should be locked during WINDUP phase.
*/
public boolean locksWindup() {
return this == WINDUP || this == WINDUP_AND_ACTIVE;
}
/**
* Check if movement should be locked during ACTIVE phase.
*/
public boolean locksActive() {
return this == ACTIVE || this == WINDUP_AND_ACTIVE;
}
/**
* Get display name for GUI.
*/
public String getDisplayKey() {
switch (this) {
case NO:
return "gui.no";
case WINDUP:
return "ability.lockMove.windup";
case ACTIVE:
return "ability.lockMove.active";
case WINDUP_AND_ACTIVE:
return "ability.lockMove.both";
default:
return "gui.no";
}
}
/**
* Get all display keys for GUI dropdown.
*/
public static String[] getDisplayKeys() {
return new String[]{
"gui.no",
"ability.lockMove.windup",
"ability.lockMove.active",
"ability.lockMove.both"
};
}
/**
* Get LockMovementType from ordinal, with fallback to WINDUP.
*/
public static LockMovementType fromOrdinal(int ordinal) {
LockMovementType[] values = values();
if (ordinal >= 0 && ordinal < values.length) {
return values[ordinal];
}
return WINDUP;
}
}
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -47,7 +48,7 @@ public class AbilityCharge extends Ability implements IAbilityCharge {
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 20.0f;
this.minRange = 4.0f;
this.lockMovement = false; // Movement IS the ability
this.lockMovement = LockMovementType.WINDUP;
this.cooldownTicks = 0;
this.windUpTicks = 20;
// LINE telegraph showing charge path
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
@@ -54,7 +55,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter {
this.name = "Cutter";
this.targetingMode = TargetingMode.AOE_SELF;
this.maxRange = 8.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.NO;
this.cooldownTicks = 0;
this.windUpTicks = 20;
this.telegraphType = TelegraphType.CONE;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
@@ -88,7 +89,7 @@ public class AbilityDash extends Ability implements IAbilityDash {
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 20.0f;
this.minRange = 0.0f;
this.lockMovement = false;
this.lockMovement = LockMovementType.NO;
this.cooldownTicks = 0;
this.windUpTicks = 5;
// No telegraph for dash - it's a quick evasive move
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -83,6 +84,7 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
this.minRange = 5.0f;
this.cooldownTicks = 0;
this.windUpTicks = 20;
this.lockMovement = LockMovementType.WINDUP;
this.telegraphType = TelegraphType.CIRCLE;
this.showTelegraph = true;
}
@@ -5,6 +5,7 @@ import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityAnimation;
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -85,6 +86,7 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
this.minRange = 5.0f;
this.cooldownTicks = 0;
this.windUpTicks = 40;
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
this.telegraphType = TelegraphType.CIRCLE;
this.showTelegraph = true;
}
@@ -145,7 +147,7 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
stunDuration, slowDuration, slowLevel,
maxDistance, maxLifetime,
lightningEffect, lightningDensity, lightningRadius,
lockMovement, // Anchored mode
lockMovement.locksActive(), // Anchored mode during firing
windUpTicks, // Charge duration = windup duration
offsetDist, // Offset distance (used by FRONT anchor)
anchorPoint // Anchor point for charging position
@@ -311,7 +313,7 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
public float getLightningRadius() { return lightningRadius; }
public void setLightningRadius(float lightningRadius) { this.lightningRadius = lightningRadius; }
@Override
public Animation getWindUpAnimation() {
if (windUpAnimationId == -1)
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
@@ -50,7 +51,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard {
this.typeId = "ability.cnpc.guard";
this.name = "Guard";
this.targetingMode = TargetingMode.SELF;
this.lockMovement = true;
this.lockMovement = LockMovementType.ACTIVE;
this.cooldownTicks = 0;
this.windUpTicks = 10;
this.interruptible = false; // Hard to interrupt while guarding
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
@@ -83,7 +84,7 @@ public class AbilityHazard extends Ability implements IAbilityHazard {
this.name = "Hazard";
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 15.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP;
this.cooldownTicks = 0;
this.windUpTicks = 30;
this.telegraphType = TelegraphType.CIRCLE;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
@@ -49,7 +50,7 @@ public class AbilityHeal extends Ability implements IAbilityHeal {
this.typeId = "ability.cnpc.heal";
this.name = "Heal";
this.targetingMode = TargetingMode.SELF;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP;
this.cooldownTicks = 0;
this.windUpTicks = 30;
// No telegraph for heal - it's a self/ally buff
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
@@ -29,7 +30,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit {
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 3.0f;
this.minRange = 0.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.NO;
this.cooldownTicks = 0;
this.windUpTicks = 30;
this.telegraphType = TelegraphType.POINT;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import kamkeel.npcs.entity.EntityAbilityLaser;
@@ -63,6 +64,7 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
this.minRange = 3.0f;
this.cooldownTicks = 0;
this.windUpTicks = 15;
this.lockMovement = LockMovementType.WINDUP;
this.telegraphType = TelegraphType.LINE;
this.showTelegraph = true;
}
@@ -5,6 +5,7 @@ import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityAnimation;
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -84,6 +85,7 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
this.minRange = 5.0f;
this.cooldownTicks = 0;
this.windUpTicks = 30;
this.lockMovement = LockMovementType.WINDUP;
this.telegraphType = TelegraphType.CIRCLE;
this.showTelegraph = true;
}
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
@@ -42,6 +43,7 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile {
this.maxRange = 20.0f;
this.cooldownTicks = 0;
this.windUpTicks = 15;
this.lockMovement = LockMovementType.NO;
// No telegraph for projectile - it's a ranged attack
this.telegraphType = TelegraphType.NONE;
this.showTelegraph = false;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
@@ -35,7 +36,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave {
this.name = "Shockwave";
this.targetingMode = TargetingMode.AOE_SELF;
this.maxRange = 15.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
this.cooldownTicks = 0;
this.windUpTicks = 25;
this.telegraphType = TelegraphType.CIRCLE;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -52,7 +53,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
this.targetingMode = TargetingMode.AOE_SELF; // Can also be AOE_TARGET to leap to target
this.windUpTicks = 30;
this.cooldownTicks = 0;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP;
this.minRange = 2.0f;
this.maxRange = 15.0f;
this.telegraphType = TelegraphType.CIRCLE;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
@@ -54,7 +55,7 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 15.0f;
this.minRange = 0.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
this.cooldownTicks = 0;
this.windUpTicks = 60;
// Circle telegraph to show range
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.block.Block;
@@ -63,6 +64,7 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport {
this.minRange = 5.0f;
this.cooldownTicks = 0;
this.windUpTicks = 10;
this.lockMovement = LockMovementType.NO;
// No telegraph for teleport - it's instant repositioning
this.telegraphType = TelegraphType.NONE;
this.showTelegraph = false;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
@@ -66,7 +67,7 @@ public class AbilityTrap extends Ability implements IAbilityTrap {
this.name = "Trap";
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 15.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP;
this.cooldownTicks = 0;
this.windUpTicks = 20;
this.telegraphType = TelegraphType.CIRCLE;
@@ -3,6 +3,7 @@ package kamkeel.npcs.controllers.data.ability.type;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
@@ -53,7 +54,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex {
this.name = "Vortex";
this.targetingMode = TargetingMode.AOE_SELF;
this.maxRange = 15.0f;
this.lockMovement = true;
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
this.cooldownTicks = 0;
this.windUpTicks = 30;
this.telegraphType = TelegraphType.CIRCLE;
+43 -35
View File
@@ -79,7 +79,7 @@ public class DataAbilities {
private transient List<Long> recentHitTimes = new ArrayList<>();
/**
* Locked rotation values for ACTIVE phase with lockMovement.
* Locked rotation values for ACTIVE phase when movement is locked.
* Stored when entering ACTIVE phase, applied every tick to prevent rotation changes.
*/
private transient boolean rotationLocked = false;
@@ -146,9 +146,9 @@ public class DataAbilities {
// Remove telegraph - it has served its purpose
removeTelegraph(currentAbility);
// Lock rotation if lockMovement is true
// Lock rotation if movement is locked during ACTIVE phase
// Capture current rotation values to force them every tick
if (currentAbility.isLockMovement()) {
if (currentAbility.isMovementLockedDuringActive()) {
captureLockedRotation();
}
@@ -191,6 +191,32 @@ public class DataAbilities {
handleAbilityCompletion(target);
break;
}
// Apply movement control if ability is still executing
if (currentAbility != null && currentAbility.isExecuting()) {
applyMovementControl();
}
}
/**
* Apply movement control based on lock movement settings.
* Called every tick during ability execution.
*/
private void applyMovementControl() {
if (currentAbility == null) return;
// Check if movement should be locked for the current phase
if (currentAbility.isMovementLockedForCurrentPhase()) {
// Clear navigator to prevent AI pathfinding
npc.getNavigator().clearPathEntity();
// Zero motion if ability doesn't have its own movement
// This allows abilities like Charge/Slam to control their own motion
if (!currentAbility.hasAbilityMovement()) {
npc.motionX = 0;
npc.motionZ = 0;
}
}
}
/**
@@ -210,29 +236,11 @@ public class DataAbilities {
npc.wrappedNPC, currentAbility, target);
NpcAPI.EVENT_BUS.post(completeEvent);
// Clean up and roll cooldown
// Release rotation lock before cleanup
releaseLockedRotation();
// Clean up and roll cooldown (this sets currentAbility to null)
onAbilityComplete();
// Clear AI pathfinding if ability is controlling movement
// But DON'T zero motion - let the ability set it if needed
if (currentAbility != null && currentAbility.isExecuting()) {
if (isAbilityControllingMovement()) {
// Clear navigator so AI doesn't fight with ability movement
npc.getNavigator().clearPathEntity();
// Only zero motion if lockMovement is true AND ability doesn't have its own movement
// This allows stationary abilities to freeze the NPC, while movement abilities can set their own motion
if (currentAbility.isLockMovement() && !currentAbility.hasAbilityMovement()) {
npc.motionX = 0;
npc.motionZ = 0;
}
}
// Unlock rotation when leaving ACTIVE phase
if (rotationLocked && currentAbility.getPhase() != AbilityPhase.ACTIVE) {
releaseLockedRotation();
}
}
}
/**
@@ -787,8 +795,8 @@ public class DataAbilities {
* Called by AI tasks to determine if they should try to path.
* <p>
* Rules:
* - WINDUP phase: Block AI if lockMovement is true
* - ACTIVE phase: Block AI if lockMovement OR hasAbilityMovement is true
* - WINDUP phase: Block AI if movement is locked during windup
* - ACTIVE phase: Block AI if movement is locked during active OR hasAbilityMovement is true
* - DAZED phase: Always block AI (NPC is stunned)
*/
public boolean isAbilityControllingMovement() {
@@ -800,15 +808,15 @@ public class DataAbilities {
switch (phase) {
case WINDUP:
// During windup, only block if lockMovement is set
// This allows NPC to keep chasing while winding up if lockMovement=false
return currentAbility.isLockMovement();
// During windup, only block if movement is locked during windup
// This allows NPC to keep chasing while winding up if not locked
return currentAbility.isMovementLockedDuringWindup();
case ACTIVE:
// During active phase, block if either:
// - lockMovement is true (stationary ability)
// - movement is locked during active (stationary ability)
// - hasAbilityMovement is true (ability is moving the NPC itself)
return currentAbility.isLockMovement() || currentAbility.hasAbilityMovement();
return currentAbility.isMovementLockedDuringActive() || currentAbility.hasAbilityMovement();
case DAZED:
// During dazed phase, always block - NPC is stunned from interrupt
@@ -821,7 +829,7 @@ public class DataAbilities {
/**
* Check if NPC's look direction should be locked to target.
* Only locks during ACTIVE phase when lockMovement is true.
* Only locks during ACTIVE phase when movement is locked during active.
* This allows the NPC to freely track target during WINDUP and DAZED.
*/
public boolean shouldLockLookDirection() {
@@ -834,7 +842,7 @@ public class DataAbilities {
return false;
}
return currentAbility.isLockMovement();
return currentAbility.isMovementLockedDuringActive();
}
/**
@@ -854,7 +862,7 @@ public class DataAbilities {
/**
* Capture current rotation values to lock NPC's look direction.
* Called when entering ACTIVE phase with lockMovement enabled.
* Called when entering ACTIVE phase with movement locked during active.
*/
private void captureLockedRotation() {
lockedYaw = npc.rotationYaw;
@@ -2,6 +2,7 @@ package noppes.npcs.client.gui.advanced;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.Condition;
import kamkeel.npcs.controllers.data.ability.LockMovementType;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import noppes.npcs.controllers.data.Animation;
@@ -58,7 +59,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
protected String name;
protected boolean enabled;
protected int weight;
protected boolean lockMovement;
protected LockMovementType lockMovement;
protected boolean interruptible;
// Timing
@@ -111,7 +112,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
this.name = ability.getName() != null ? ability.getName() : "";
this.enabled = ability.isEnabled();
this.weight = ability.getWeight();
this.lockMovement = ability.isLockMovement();
this.lockMovement = ability.getLockMovement();
this.interruptible = ability.isInterruptible();
// Timing
@@ -251,7 +252,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
addTextField(weightField);
addLabel(new GuiNpcLabel(5, "ability.lockMove", col2LabelX, y + 5));
GuiNpcButton lockMoveBtn = new GuiNpcButton(16, col2FieldX, y, 40, 20, new String[]{"gui.no", "gui.yes"}, lockMovement ? 1 : 0);
GuiNpcButton lockMoveBtn = new GuiNpcButton(16, col2FieldX, y, 60, 20, LockMovementType.getDisplayKeys(), lockMovement.ordinal());
lockMoveBtn.setHoverText("ability.hover.lockMove");
addButton(lockMoveBtn);
@@ -644,7 +645,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
interruptible = ((GuiNpcButton) guibutton).getValue() == 1;
initGui(); // Refresh to show/hide dazed ticks field
} else if (id == 16) {
lockMovement = ((GuiNpcButton) guibutton).getValue() == 1;
lockMovement = LockMovementType.fromOrdinal(((GuiNpcButton) guibutton).getValue());
} else if (id == 17) {
// Sync windup ticks with animation duration
syncWindupWithAnimation();
@@ -920,7 +921,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
this.name = ability.getName() != null ? ability.getName() : "";
this.enabled = ability.isEnabled();
this.weight = ability.getWeight();
this.lockMovement = ability.isLockMovement();
this.lockMovement = ability.getLockMovement();
this.interruptible = ability.isInterruptible();
this.cooldownTicks = ability.getCooldownTicks();