diff --git a/src/ZoneServer/World/Actors/Components/StateLockComponent.cs b/src/ZoneServer/World/Actors/Components/StateLockComponent.cs index 455afbdbd..87902407b 100644 --- a/src/ZoneServer/World/Actors/Components/StateLockComponent.cs +++ b/src/ZoneServer/World/Actors/Components/StateLockComponent.cs @@ -155,6 +155,54 @@ public void Unlock(string lockType) } } + /// + /// Adds a lock without executing movement side effects. + /// Must be called from within a lock(_syncLock) block. + /// + private void LockInternal(string lockType, TimeSpan duration) + { + if (_lockCounts.TryGetValue(lockType, out var value)) + _lockCounts[lockType] = ++value; + else + _lockCounts[lockType] = 1; + + if (duration != TimeSpan.MaxValue) + { + var endTime = DateTime.Now.Add(duration); + _lockEnds.Add(new(lockType, endTime)); + } + } + + /// + /// Releases a lock without executing movement side effects. + /// Must be called from within a lock(_syncLock) block. + /// + private void UnlockInternal(string lockType) + { + if (!_lockCounts.TryGetValue(lockType, out var value)) + return; + + _lockCounts[lockType] = --value; + + if (_lockCounts[lockType] <= 0) + _lockCounts.Remove(lockType); + } + + /// + /// Applies movement side effects (stop movement, invalidate MSPD). + /// Must be called outside of lock(_syncLock) to avoid deadlock. + /// + private void ApplyMovementSideEffects() + { + if (this.Owner is ICombatEntity entity) + { + if (entity.Components.TryGet(out var movement)) + movement.Stop(); + + entity.Properties.Invalidate(PropertyName.MSPD); + } + } + /// /// Registers a new state, which represents a set of locks the will /// be set when the state is activated. @@ -198,10 +246,7 @@ public void AddState(string stateType) /// public void AddState(string stateType, TimeSpan duration) { - // Technically we could do away with tracking states, now that they're - // mostly glorified lock lists. We could just get the locks to apply - // on add and remove. But we'll leave it like this for now, in case - // we need to be able to tell whether a specific state is active. + var needsMovementSideEffects = false; lock (_syncLock) { @@ -209,7 +254,11 @@ public void AddState(string stateType, TimeSpan duration) throw new ArgumentException($"Unknown state '{stateType}'."); foreach (var lockType in state.Locks) - this.Lock(lockType); + { + this.LockInternal(lockType, TimeSpan.MaxValue); + if (lockType == LockType.Movement) + needsMovementSideEffects = true; + } if (_stateCounts.TryGetValue(stateType, out var value)) _stateCounts[stateType] = ++value; @@ -222,6 +271,9 @@ public void AddState(string stateType, TimeSpan duration) _stateEnds.Add(new(stateType, endTime)); } } + + if (needsMovementSideEffects) + this.ApplyMovementSideEffects(); } /// @@ -231,6 +283,8 @@ public void AddState(string stateType, TimeSpan duration) /// public void RemoveState(string stateType) { + var needsMovementSideEffects = false; + lock (_syncLock) { if (!_states.TryGetValue(stateType, out var state)) @@ -245,8 +299,15 @@ public void RemoveState(string stateType) _stateCounts.Remove(stateType); foreach (var lockType in state.Locks) - this.Unlock(lockType); + { + this.UnlockInternal(lockType); + if (lockType == LockType.Movement) + needsMovementSideEffects = true; + } } + + if (needsMovementSideEffects) + this.ApplyMovementSideEffects(); } /// @@ -256,6 +317,7 @@ public void RemoveState(string stateType) public void Update(TimeSpan elapsed) { var now = DateTime.Now; + var needsMovementSideEffects = false; if (this.Owner.Map.ClassName.StartsWith("c_high") && this.Owner is Mob mob && mob.Id == 400001) { @@ -270,7 +332,24 @@ public void Update(TimeSpan elapsed) if (now >= stateEnd.EndTime) { - this.RemoveState(stateEnd.Type); + if (_states.TryGetValue(stateEnd.Type, out var state)) + { + if (_stateCounts.TryGetValue(stateEnd.Type, out var value)) + { + _stateCounts[stateEnd.Type] = --value; + + if (_stateCounts[stateEnd.Type] <= 0) + _stateCounts.Remove(stateEnd.Type); + + foreach (var lockType in state.Locks) + { + this.UnlockInternal(lockType); + if (lockType == LockType.Movement) + needsMovementSideEffects = true; + } + } + } + _stateEnds.RemoveAt(i); } } @@ -281,11 +360,17 @@ public void Update(TimeSpan elapsed) if (now >= lockEnd.EndTime) { - this.Unlock(lockEnd.Type); + this.UnlockInternal(lockEnd.Type); + if (lockEnd.Type == LockType.Movement) + needsMovementSideEffects = true; + _lockEnds.RemoveAt(i); } } } + + if (needsMovementSideEffects) + this.ApplyMovementSideEffects(); } }