fix(physics): keep remote placement and targeting in one world frame
CreateObject positions are landblock-local, but Runtime first-entry previously submitted remotes with a zero world offset. Runtime now owns the accepted local-player world-frame center and converts remote placements before SetPosition. The local physics host also publishes body.Position rather than CellPosition's landblock-local origin, so TargetManager no longer directs monsters toward a phantom player position. User gate: monster/static placement, chase, and attacks accepted outside Tusker Barracks.
This commit is contained in:
parent
01f4791e95
commit
670f307c84
7 changed files with 140 additions and 12 deletions
|
|
@ -1522,6 +1522,32 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
.RetrySetupUnavailable;
|
||||
}
|
||||
|
||||
RuntimeSetPositionMoverPreparation effectivePreparation = preparation;
|
||||
if (preparation.ResolveWorldOffsetFromRuntimeFrame)
|
||||
{
|
||||
// CreateObject/Position origins are local to their authored
|
||||
// landblock. Resolve them through Runtime's accepted world frame,
|
||||
// which is initialized by the local-player Create and advanced
|
||||
// only by authoritative teleport transitions. This is available
|
||||
// before streaming publishes the target collision generation, so
|
||||
// remote admission cannot starve world loading while still using
|
||||
// the exact same coordinate frame that generation will publish.
|
||||
if (!_physics.TryGetWorldFrameOffset(
|
||||
authority.AcceptedPosition.LandblockId,
|
||||
out float worldOffsetX,
|
||||
out float worldOffsetY))
|
||||
{
|
||||
return RuntimeSetPositionMoverPreparationStatus
|
||||
.RetrySetupUnavailable;
|
||||
}
|
||||
|
||||
effectivePreparation = preparation with
|
||||
{
|
||||
ShadowWorldOffsetX = worldOffsetX,
|
||||
ShadowWorldOffsetY = worldOffsetY,
|
||||
};
|
||||
}
|
||||
|
||||
if (!RuntimeSetPositionMoverPreparer.TryBuild(
|
||||
operation.Record,
|
||||
authority.AcceptedPosition,
|
||||
|
|
@ -1529,7 +1555,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
operation.Kind,
|
||||
operation.Portal,
|
||||
authority.VelocityAuthorityVersion,
|
||||
preparation,
|
||||
effectivePreparation,
|
||||
out command)
|
||||
|| !IsStructurallyValid(command.Physics))
|
||||
{
|
||||
|
|
@ -1586,7 +1612,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
float scatterRadiusY = 0f,
|
||||
uint scatterAttempts = 0u,
|
||||
float shadowWorldOffsetX = 0f,
|
||||
float shadowWorldOffsetY = 0f)
|
||||
float shadowWorldOffsetY = 0f,
|
||||
bool resolveWorldOffsetFromRuntimeFrame = false)
|
||||
{
|
||||
outcome = default;
|
||||
|
||||
|
|
@ -1606,7 +1633,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
scatterRadiusY,
|
||||
scatterAttempts,
|
||||
shadowWorldOffsetX,
|
||||
shadowWorldOffsetY);
|
||||
shadowWorldOffsetY,
|
||||
resolveWorldOffsetFromRuntimeFrame);
|
||||
if (status != RuntimeSetPositionMoverPreparationStatus.Prepared)
|
||||
return status;
|
||||
|
||||
|
|
@ -1644,7 +1672,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
float scatterRadiusY = 0f,
|
||||
uint scatterAttempts = 0u,
|
||||
float shadowWorldOffsetX = 0f,
|
||||
float shadowWorldOffsetY = 0f)
|
||||
float shadowWorldOffsetY = 0f,
|
||||
bool resolveWorldOffsetFromRuntimeFrame = false)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
|
|
@ -1683,7 +1712,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
scatterAttempts,
|
||||
shadowWorldOffsetX,
|
||||
shadowWorldOffsetY,
|
||||
portal);
|
||||
portal,
|
||||
resolveWorldOffsetFromRuntimeFrame);
|
||||
return PrepareMover(token, preparation, out command);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue