mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
083d38b9d3
5 files changed
+99
-8
No files matched your search
@@ -1169,8 +1169,8 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
public void interrupt() {
|
||||
cleanupBurstEntities();
|
||||
cleanup();
|
||||
if (interruptible && (phase == AbilityPhase.WINDUP || phase == AbilityPhase.BURST_DELAY)) {
|
||||
// Interrupted during windup or burst delay - go to dazed state
|
||||
if (interruptible && phase == AbilityPhase.WINDUP) {
|
||||
// Interrupted during windup - go to dazed state
|
||||
phase = AbilityPhase.DAZED;
|
||||
currentTick = 0;
|
||||
} else {
|
||||
@@ -1207,13 +1207,15 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
/**
|
||||
* Check if can be interrupted by this damage source.
|
||||
* Only direct physical hits (not magic, fire, or other indirect damage) can interrupt.
|
||||
* Interruption only occurs during WINDUP phase - if ability is already active, it completes.
|
||||
* Interruption only occurs during WINDUP phase (including burst replay windups).
|
||||
* BURST_DELAY is not interruptible - only the windup animation itself can be interrupted.
|
||||
* For chained abilities, windUpAll controls whether subsequent entries even have a windup.
|
||||
*
|
||||
* @param source The damage source
|
||||
* @return true if the ability should be interrupted
|
||||
*/
|
||||
public boolean canInterrupt(DamageSource source) {
|
||||
if (!interruptible || (phase != AbilityPhase.WINDUP && phase != AbilityPhase.BURST_DELAY)) {
|
||||
if (!interruptible || phase != AbilityPhase.WINDUP) {
|
||||
return false;
|
||||
}
|
||||
if (isInvulnerableForCurrentPhase()) {
|
||||
|
||||
@@ -35,6 +35,13 @@ import java.util.Set;
|
||||
*/
|
||||
public abstract class AbstractDataAbilities {
|
||||
|
||||
/**
|
||||
* Safety timeout (ticks) for abilities stuck in ACTIVE phase.
|
||||
* If an ability stays in ACTIVE for longer than this without calling signalCompletion(),
|
||||
* it is force-completed to prevent permanent stuck state. 30 seconds at 20 TPS.
|
||||
*/
|
||||
protected static final int MAX_ACTIVE_PHASE_TICKS = 600;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SHARED RUNTIME STATE (not saved to NBT)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -864,6 +871,16 @@ public abstract class AbstractDataAbilities {
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
onBurstDelayReleaseLocks();
|
||||
}
|
||||
|
||||
// Safety timeout: force-complete if stuck in ACTIVE too long.
|
||||
// Individual ability types should have their own timeouts well below this;
|
||||
// this is a last-resort failsafe to prevent permanent stuck state.
|
||||
if (currentAbility != null && currentAbility.getPhase() == AbilityPhase.ACTIVE
|
||||
&& currentAbility.getCurrentTick() > MAX_ACTIVE_PHASE_TICKS) {
|
||||
currentAbility.cancel();
|
||||
handleAbilityCompletion(target);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
|
||||
@@ -50,6 +50,9 @@ public class ServerTickHandler {
|
||||
SyncController.syncEffects(player);
|
||||
ScriptController.Instance.syncClientScripts(player);
|
||||
|
||||
// Sync clean ability state to the new client (after beginLogin initializes sync state)
|
||||
SyncController.syncAbilities(player);
|
||||
|
||||
// Send auction notifications on login
|
||||
if (AuctionController.Instance != null) {
|
||||
AuctionController.Instance.onPlayerLogin(player);
|
||||
|
||||
@@ -285,6 +285,13 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
@Override
|
||||
protected void onInterruptComplete() {
|
||||
// Player clears state immediately (unlike NPC which ticks through DAZED)
|
||||
// Roll cooldown before clearing state — NPC rolls during handleAbilityCompletion
|
||||
// after ticking through DAZED, but the player skips DAZED entirely.
|
||||
if (!interruptCooldownRolled && currentAbility != null) {
|
||||
rollCooldown(currentAbility);
|
||||
}
|
||||
interruptCooldownRolled = false;
|
||||
|
||||
// Stop the dazed animation that was started in interruptCurrentAbility()
|
||||
stopAbilityAnimation();
|
||||
currentAbility = null;
|
||||
@@ -787,13 +794,70 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
/**
|
||||
* Reset ability state after death/respawn.
|
||||
* Preserves cooldowns (interrupt on death already rolled them) but clears
|
||||
* active toggles (death should end ongoing effects).
|
||||
* Preserves cooldowns (the death handler interrupted the ability which rolled cooldown
|
||||
* via onInterruptComplete) but clears active toggles (death should end ongoing effects).
|
||||
*/
|
||||
public void resetOnRespawn() {
|
||||
onEntityReconstructed(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset ability state on login. Clears all transient execution state (current ability,
|
||||
* locks, chains, cooldowns) that may have leaked from a previous session via the
|
||||
* PlayerData cache. Toggles are NOT cleared — they are restored from NBT by readFromNBT.
|
||||
* <p>
|
||||
* Does NOT send packets — called before the client's sync state is initialized.
|
||||
* The caller must trigger a full ability sync afterwards (e.g. via syncToClient).
|
||||
*/
|
||||
public void resetOnLogin() {
|
||||
// Silently clean up any stale executing ability
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
removeTelegraph(currentAbility);
|
||||
currentAbility.cleanup();
|
||||
currentAbility.interrupt();
|
||||
stopAbilityAnimation();
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Clear chain and concurrent state
|
||||
currentChain = null;
|
||||
chainEntryIndex = -1;
|
||||
chainDelayRemaining = -1;
|
||||
interruptConcurrentSlots();
|
||||
|
||||
// Clear all transient state
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
lastAbilityActivationTime = -1;
|
||||
|
||||
// Clear cooldowns (fresh start after relog)
|
||||
cooldownEndTime = 0;
|
||||
resetAllPerAbilityCooldowns();
|
||||
interruptCooldownRolled = false;
|
||||
|
||||
// Reset sync tracking so next sync sends fresh state
|
||||
lastSyncedFlags = 0;
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up ability state on disconnect. Interrupts any executing ability (fires events,
|
||||
* rolls cooldown) before the player entity goes away, then clears all transient state.
|
||||
* This ensures scripts receive proper interrupt/complete events and prevents stale state
|
||||
* from leaking into the next session via the PlayerData cache.
|
||||
*/
|
||||
public void resetOnDisconnect() {
|
||||
// Interrupt with events so scripts know the ability ended
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
interruptCurrentAbility(null, 0);
|
||||
}
|
||||
// Clear any remaining transient state (cooldowns, chains, locks)
|
||||
// Don't clear toggles — they were already saved to NBT by this point
|
||||
onEntityReconstructed(true, false);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// DAMAGE HANDLING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -94,6 +94,10 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
|
||||
private boolean specialKeyDown = false;
|
||||
|
||||
public void onLogin() {
|
||||
// Clear any stale transient ability state that leaked via the PlayerData cache.
|
||||
// Must happen before the animation check since it may clear ability animation state.
|
||||
abilityData.resetOnLogin();
|
||||
|
||||
// Handle animation state from previous session
|
||||
AnimationData animationData = this.animationData;
|
||||
if (animationData != null && animationData.isClientAnimating()) {
|
||||
@@ -120,8 +124,9 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
|
||||
}
|
||||
|
||||
public void onLogout() {
|
||||
// Cancel any executing ability and release all locks
|
||||
abilityData.interruptCurrentAbility();
|
||||
// Interrupt executing ability (fires events, rolls cooldown), then clear
|
||||
// all transient state so nothing leaks via the PlayerData cache.
|
||||
abilityData.resetOnDisconnect();
|
||||
this.partyInvites.clear();
|
||||
this.actionManager.clear();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user