mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 22:27:32 +00:00
Add Track Delay and Anchor Handling
This commit is contained in:
1 parent
36aab9e86e
commit
dad49f1867
6 files changed
+119
-23
No files matched your search
@@ -91,6 +91,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
protected LockMode lockMovement = LockMode.WINDUP;
|
||||
protected RotationMode rotationMode = RotationMode.FREE;
|
||||
protected LockMode rotationPhase = LockMode.WINDUP_AND_ACTIVE;
|
||||
protected float trackDelay = 0; // 0 = instant tracking (legacy). Higher = slower lerp toward target.
|
||||
protected int windUpColor = 0x80FF4400; // Telegraph color during wind up
|
||||
protected int activeColor = 0xC0FF0000; // Telegraph warning/active color
|
||||
|
||||
@@ -687,6 +688,11 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
.hover("ability.hover.rotationPhase")
|
||||
.visibleWhen(() -> this.rotationMode != RotationMode.FREE)
|
||||
).tab("General"));
|
||||
defs.add(FieldDef.floatField("ability.trackDelay", this::getTrackDelay, this::setTrackDelay)
|
||||
.range(0.0f, 20.0f)
|
||||
.hover("ability.hover.trackDelay")
|
||||
.visibleWhen(() -> this.rotationMode == RotationMode.TRACK)
|
||||
.tab("General"));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible)
|
||||
.hover("ability.hover.interruptible")
|
||||
@@ -1388,6 +1394,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
nbt.setInteger("lockMovement", lockMovement.ordinal());
|
||||
nbt.setInteger("rotationMode", rotationMode.ordinal());
|
||||
nbt.setInteger("rotationPhase", rotationPhase.ordinal());
|
||||
nbt.setFloat("trackDelay", trackDelay);
|
||||
nbt.setInteger("windUpColor", windUpColor);
|
||||
nbt.setInteger("activeColor", activeColor);
|
||||
nbt.setString("windUpSound", windUpSound);
|
||||
@@ -1482,6 +1489,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
lockMovement = LockMode.fromOrdinal(nbt.getInteger("lockMovement"));
|
||||
rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode"));
|
||||
rotationPhase = LockMode.fromOrdinal(nbt.getInteger("rotationPhase"));
|
||||
trackDelay = nbt.hasKey("trackDelay") ? nbt.getFloat("trackDelay") : 0;
|
||||
windUpColor = nbt.hasKey("windUpColor") ? nbt.getInteger("windUpColor") : 0x80FF4400;
|
||||
activeColor = nbt.hasKey("activeColor") ? nbt.getInteger("activeColor") : 0xC0FF0000;
|
||||
windUpSound = nbt.getString("windUpSound");
|
||||
@@ -1817,6 +1825,14 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this.rotationPhase = rotationPhase;
|
||||
}
|
||||
|
||||
public float getTrackDelay() {
|
||||
return trackDelay;
|
||||
}
|
||||
|
||||
public void setTrackDelay(float trackDelay) {
|
||||
this.trackDelay = trackDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* API method: Get lock movement type as integer.
|
||||
*
|
||||
@@ -1877,6 +1893,25 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this.rotationPhase = LockMode.fromOrdinal(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* API method: Get track delay for TRACK rotation mode.
|
||||
* 0 = instant tracking (legacy). Higher = slower lerp toward target.
|
||||
*/
|
||||
@Override
|
||||
public float getTrackDelayValue() {
|
||||
return trackDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* API method: Set track delay for TRACK rotation mode.
|
||||
*
|
||||
* @param delay 0 = instant tracking. Higher = slower lerp toward target.
|
||||
*/
|
||||
@Override
|
||||
public void setTrackDelayValue(float delay) {
|
||||
this.trackDelay = Math.max(0, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if movement (pathfinding/motion) should be locked during WINDUP phase.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,7 @@ public class AbilityLaser extends AbilityEnergyProjectile<EntityAbilityLaser> im
|
||||
this.lockMovement = LockMode.WINDUP_AND_ACTIVE;
|
||||
this.rotationMode = RotationMode.TRACK;
|
||||
this.rotationPhase = LockMode.WINDUP_AND_ACTIVE;
|
||||
this.trackDelay = 3;
|
||||
this.telegraphType = TelegraphType.LINE;
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Laser_Windup";
|
||||
|
||||
@@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -224,11 +225,16 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
dirZ = 0.0;
|
||||
}
|
||||
|
||||
// Keep origin centered on look vector while active and clear owner bbox.
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
setLookVectorLaunchPosition(livingOwner, direction, false);
|
||||
// Lower beam origin slightly so it doesn't obscure the player's crosshair
|
||||
posY -= 0.15;
|
||||
// Position: use anchor if launchFromAnchor, otherwise use look vector position
|
||||
if (anchorData.launchFromAnchor) {
|
||||
Vec3 anchorPos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData);
|
||||
setPosition(anchorPos.xCoord, anchorPos.yCoord, anchorPos.zCoord);
|
||||
} else {
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
setLookVectorLaunchPosition(livingOwner, direction, false);
|
||||
// Lower beam origin slightly so it doesn't obscure the player's crosshair
|
||||
posY -= 0.15;
|
||||
}
|
||||
syncStartPositionToCurrent();
|
||||
syncPositionStateToCurrent(false);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityAction;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.RotationMode;
|
||||
import kamkeel.npcs.controllers.data.ability.data.ChainedAbility;
|
||||
import kamkeel.npcs.controllers.data.ability.data.IAbilityAction;
|
||||
import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry;
|
||||
@@ -87,6 +88,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
*/
|
||||
private transient boolean hitScanActive = false;
|
||||
private transient EntityLivingBase hitScanTarget = null;
|
||||
private transient float currentTrackDelay = 0;
|
||||
|
||||
/**
|
||||
* Bit flag for rotation control (LOCKED or TRACK) in data watcher slot 15
|
||||
@@ -227,9 +229,19 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
protected void onPostPhaseTick(Ability ability, EntityLivingBase target) {
|
||||
// Update hit scan state - actual facing is deferred to applyRotationControl()
|
||||
// which runs AFTER super.onLivingUpdate() to override AI look helper
|
||||
if (ability != null && ability.isExecuting()
|
||||
&& ability.isHitScanForCurrentPhase() && target != null) {
|
||||
enableHitScan(target);
|
||||
if (ability != null && ability.isExecuting() && target != null) {
|
||||
if (ability.isHitScanForCurrentPhase()) {
|
||||
enableHitScan(target, ability.getTrackDelay());
|
||||
} else {
|
||||
// Release hit scan if it was previously active
|
||||
if (hitScanActive) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
// FREE mode: use AI look helper during ability execution since combat AI may not run
|
||||
if (ability.getRotationMode() == RotationMode.FREE) {
|
||||
npc.getLookHelper().setLookPositionWithEntity(target, 30.0F, 30.0F);
|
||||
}
|
||||
}
|
||||
} else if (hitScanActive) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
@@ -867,34 +879,74 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
/**
|
||||
* Enable hit scan tracking for the given target.
|
||||
*/
|
||||
private void enableHitScan(EntityLivingBase target) {
|
||||
private void enableHitScan(EntityLivingBase target, float trackDelay) {
|
||||
if (!hitScanActive) {
|
||||
hitScanActive = true;
|
||||
npc.setBoolFlag(true, ROTATION_CONTROLLED_FLAG);
|
||||
}
|
||||
hitScanTarget = target;
|
||||
currentTrackDelay = trackDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snap the NPC to face the target instantly.
|
||||
* Face the target with optional track delay.
|
||||
* When trackDelay <= 0, snaps instantly (legacy behavior).
|
||||
* When trackDelay > 0, lerps toward the target each tick.
|
||||
*/
|
||||
private void faceTarget(EntityLivingBase target) {
|
||||
private void faceTarget(EntityLivingBase target, float trackDelay) {
|
||||
double dx = target.posX - npc.posX;
|
||||
double dz = target.posZ - npc.posZ;
|
||||
double dy = (target.posY + target.getEyeHeight() * 0.5) - (npc.posY + npc.getEyeHeight());
|
||||
double distXZ = Math.sqrt(dx * dx + dz * dz);
|
||||
|
||||
float yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI);
|
||||
float pitch = (float) (-(Math.atan2(dy, distXZ)) * 180.0 / Math.PI);
|
||||
float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI);
|
||||
float targetPitch = (float) (-(Math.atan2(dy, distXZ)) * 180.0 / Math.PI);
|
||||
|
||||
npc.rotationYaw = yaw;
|
||||
npc.rotationYawHead = yaw;
|
||||
npc.renderYawOffset = yaw;
|
||||
npc.rotationPitch = pitch;
|
||||
npc.prevRotationYaw = yaw;
|
||||
npc.prevRotationYawHead = yaw;
|
||||
npc.prevRenderYawOffset = yaw;
|
||||
npc.prevRotationPitch = pitch;
|
||||
if (trackDelay <= 0) {
|
||||
// Instant snap (legacy behavior)
|
||||
npc.rotationYaw = targetYaw;
|
||||
npc.rotationYawHead = targetYaw;
|
||||
npc.renderYawOffset = targetYaw;
|
||||
npc.rotationPitch = targetPitch;
|
||||
npc.prevRotationYaw = targetYaw;
|
||||
npc.prevRotationYawHead = targetYaw;
|
||||
npc.prevRenderYawOffset = targetYaw;
|
||||
npc.prevRotationPitch = targetPitch;
|
||||
} else {
|
||||
float lerpFactor = 1.0f / (1.0f + trackDelay);
|
||||
|
||||
// Wrap yaw difference to [-180, 180] for correct interpolation
|
||||
float yawDiff = wrapAngle(targetYaw - npc.rotationYawHead);
|
||||
float pitchDiff = targetPitch - npc.rotationPitch;
|
||||
|
||||
float newYaw = npc.rotationYawHead + yawDiff * lerpFactor;
|
||||
float newPitch = npc.rotationPitch + pitchDiff * lerpFactor;
|
||||
|
||||
// Set prev to current before updating (for smooth client interpolation)
|
||||
npc.prevRotationYaw = npc.rotationYaw;
|
||||
npc.prevRotationYawHead = npc.rotationYawHead;
|
||||
npc.prevRenderYawOffset = npc.renderYawOffset;
|
||||
npc.prevRotationPitch = npc.rotationPitch;
|
||||
|
||||
npc.rotationYaw = newYaw;
|
||||
npc.rotationYawHead = newYaw;
|
||||
npc.renderYawOffset = newYaw;
|
||||
npc.rotationPitch = newPitch;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Snap the NPC to face the target instantly (no delay).
|
||||
*/
|
||||
private void faceTarget(EntityLivingBase target) {
|
||||
faceTarget(target, 0);
|
||||
}
|
||||
|
||||
private static float wrapAngle(float angle) {
|
||||
angle = angle % 360.0f;
|
||||
if (angle >= 180.0f) angle -= 360.0f;
|
||||
if (angle < -180.0f) angle += 360.0f;
|
||||
return angle;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -922,7 +974,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
|
||||
// Server: apply the appropriate rotation
|
||||
if (hitScanActive && hitScanTarget != null && !hitScanTarget.isDead) {
|
||||
faceTarget(hitScanTarget);
|
||||
faceTarget(hitScanTarget, currentTrackDelay);
|
||||
} else if (rotationLocked) {
|
||||
npc.rotationYaw = lockedYaw;
|
||||
npc.rotationYawHead = lockedYawHead;
|
||||
|
||||
Reference in new issue
Block a user