-
Notifications
You must be signed in to change notification settings - Fork 533
Fix player position desync during movement #770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
43a1cb0
f94098a
e79a9ab
a76a971
e6f72d0
5d72e3f
e62da4f
9be86a4
10b0260
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ namespace MUnique.OpenMU.GameLogic; | |
| using System.Threading; | ||
| using MUnique.OpenMU.AttributeSystem; | ||
| using MUnique.OpenMU.DataModel.Attributes; | ||
| using MUnique.OpenMU.DataModel.Configuration.Items; | ||
| using MUnique.OpenMU.GameLogic.Attributes; | ||
| using MUnique.OpenMU.GameLogic.GuildWar; | ||
| using MUnique.OpenMU.GameLogic.MiniGames; | ||
|
|
@@ -41,6 +42,8 @@ namespace MUnique.OpenMU.GameLogic; | |
| /// </summary> | ||
| public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacker, ITrader, IPartyMember, IRotatable, IHasBucketInformation, ISupportWalk, IMovable, ILoggerOwner<Player> | ||
| { | ||
| private const double WalkMovementSpeed = 12.0; | ||
|
|
||
| private static readonly MagicEffectDefinition GMEffect = new GMMagicEffectDefinition | ||
| { | ||
| InformObservers = true, | ||
|
|
@@ -148,7 +151,7 @@ public Player(IGameContext gameContext) | |
| public bool IsWalking => this._walker.CurrentTarget != default; | ||
|
|
||
| /// <inheritdoc /> | ||
| public TimeSpan StepDelay => this.GetStepDelay(); | ||
| public TimeSpan StepDelay => this.GetStepDelay(null); | ||
|
|
||
| /// <inheritdoc /> | ||
| public Point WalkTarget => this._walker.CurrentTarget; | ||
|
|
@@ -715,6 +718,11 @@ public ValueTask ShowBlueMessageAsync(string message) | |
| throw new InvalidOperationException("AttributeSystem not set."); | ||
| } | ||
|
|
||
| if (this.IsAttackBlockedBySafezone(attacker)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| if (!this.GameContext.PvpEnabled && this.CurrentMap?.Definition.BattleZone == null && | ||
| this.CurrentMiniGame?.AllowPlayerKilling is false) | ||
| { | ||
|
|
@@ -2149,19 +2157,48 @@ private async ValueTask RegenerateHeroStateAsync() | |
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the step delay depending on the equipped items. | ||
| /// Gets the step delay depending on the equipped items and current movement effects. | ||
| /// </summary> | ||
| /// <param name="step">The walking step for which the delay is calculated.</param> | ||
| /// <returns>The current step delay, depending on equipped items.</returns> | ||
| private TimeSpan GetStepDelay() | ||
| private TimeSpan GetStepDelay(WalkingStep? step) | ||
| { | ||
| const double referenceFrameTimeMilliseconds = 40.0; | ||
| const double terrainScale = 100.0; | ||
|
|
||
| var speed = this.GetClientMovementSpeed(step?.From); | ||
| var tileDistance = step is { } walkingStep ? walkingStep.From.EuclideanDistanceTo(walkingStep.To) : 1.0; | ||
| var movementMilliseconds = terrainScale * Math.Max(1.0, tileDistance) / speed * referenceFrameTimeMilliseconds; | ||
|
|
||
| return TimeSpan.FromMilliseconds(movementMilliseconds); | ||
| } | ||
|
|
||
| private double GetClientMovementSpeed(Point? position = null) | ||
| { | ||
| if (this.Inventory?.EquippedItems.Any(item => item.Definition?.ItemSlot?.ItemSlots.Contains(7) ?? false) ?? false) | ||
| if (this.IsInClientSafezone(position)) | ||
| { | ||
| // Wings | ||
| return TimeSpan.FromMilliseconds(300); | ||
| return this.ApplyMovementSpeedFactor(WalkMovementSpeed); | ||
| } | ||
|
|
||
| // TODO: Consider pets etc. | ||
| return TimeSpan.FromMilliseconds(500); | ||
| var speedAttribute = this.Attributes?[Stats.IsUnderwater] > 0 | ||
| ? Stats.MaxMovementSpeedUnderwater | ||
| : Stats.MaxMovementSpeed; | ||
| var speed = this.Attributes?[speedAttribute] ?? 0; | ||
|
|
||
| return this.ApplyMovementSpeedFactor(Math.Max(WalkMovementSpeed, speed)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A player with no +5 boots/wings/mount gets Generated by Claude Code
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least with MuMain, players do need +5 boots to run (just verified)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Evidence from S6E3 client: |
||
| } | ||
|
|
||
| private double ApplyMovementSpeedFactor(double speed) | ||
| { | ||
| var movementSpeedFactor = this.Attributes?[Stats.MovementSpeedFactor] ?? 1.0; | ||
|
|
||
| return speed * (movementSpeedFactor > 0 ? movementSpeedFactor : 1.0); | ||
| } | ||
|
|
||
| private bool IsInClientSafezone(Point? position = null) | ||
| { | ||
| var checkedPosition = position ?? this.Position; | ||
| return this.CurrentMap?.Terrain.SafezoneMap[checkedPosition.X, checkedPosition.Y] ?? false; | ||
| } | ||
|
|
||
| private async ValueTask<ExitGate> GetSpawnGateOfCurrentMapAsync() | ||
|
|
@@ -2455,21 +2492,20 @@ private void RaisePlayerEnteredMap(GameMap map) | |
| { | ||
| foreach (var powerUpDefinition in powerUpDefinitions) | ||
| { | ||
| if (powerUpDefinition.TargetAttribute is not { } targetAttribute) | ||
| if (powerUpDefinition.TargetAttribute is null) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| var powerUps = PowerUpWrapper.CreateByPowerUpDefinition(powerUpDefinition, attributes); | ||
| powerUps.ForEach(p => | ||
| { | ||
| this.Attributes?.AddElement(p, targetAttribute); | ||
| this.PlayerLeftMap += OnPlayerLeftMap; | ||
|
|
||
| void OnPlayerLeftMap(object? o, (Player, GameMap) args) | ||
| { | ||
| this.PlayerLeftMap -= OnPlayerLeftMap; | ||
| attributes.RemoveElement(p, targetAttribute); | ||
| p.Dispose(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think
MovementSpeedandMovementSpeedUnderwaterare more appropriate names for char attributes/stats (which vary depending on items equipped)?MaxMovementSpeedandMaxMovementSpeedUnderwatersounds more like hard constant names to me. (I understand you might have named like that due to these being comprised ofAggregateType.Maximumvalues.)