fix(combat): escape occupied login positions
Port retail's radius-aware placement ring so a relogging player is seated beside creatures occupying the saved location, and register the local body in the shared resolved-shadow pipeline. Route new forward movement and jump through AbortAutomaticAttack so repeat combat cancels immediately on movement. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
e671b8a5c4
commit
00fe993f6f
11 changed files with 643 additions and 16 deletions
|
|
@ -1325,4 +1325,47 @@ public sealed class PhysicsEngine
|
|||
|
||||
return resolveResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs retail's radius-aware placement-ring search after the host has
|
||||
/// validated the server's cell and grounded the initial position. This is
|
||||
/// the <c>CTransition::find_placement_pos</c> half of enter-world
|
||||
/// <c>SetPosition</c>; unlike an ordinary zero-distance transition it tests
|
||||
/// object occupancy and can seat a relogging player beside a creature that
|
||||
/// now occupies the saved location.
|
||||
/// </summary>
|
||||
public ResolveResult ResolvePlacement(
|
||||
Vector3 position,
|
||||
uint cellId,
|
||||
float sphereRadius,
|
||||
float sphereHeight,
|
||||
float stepUpHeight,
|
||||
float stepDownHeight,
|
||||
ObjectInfoState moverFlags = ObjectInfoState.None,
|
||||
uint movingEntityId = 0)
|
||||
{
|
||||
var transition = new Transition();
|
||||
transition.ObjectInfo.StepUpHeight = stepUpHeight;
|
||||
transition.ObjectInfo.StepDownHeight = stepDownHeight;
|
||||
transition.ObjectInfo.StepDown = true;
|
||||
transition.ObjectInfo.SelfEntityId = movingEntityId;
|
||||
transition.ObjectInfo.State = moverFlags;
|
||||
transition.SpherePath.InitPath(
|
||||
position, position, cellId, sphereRadius, sphereHeight);
|
||||
transition.SpherePath.InsertType = InsertType.Placement;
|
||||
|
||||
bool ok = transition.FindPlacementPos(this);
|
||||
var sp = transition.SpherePath;
|
||||
var ci = transition.CollisionInfo;
|
||||
bool onGround = ci.ContactPlaneValid
|
||||
|| transition.ObjectInfo.State.HasFlag(ObjectInfoState.OnWalkable);
|
||||
|
||||
return new ResolveResult(
|
||||
sp.CurPos,
|
||||
sp.CurCellId != 0 ? sp.CurCellId : cellId,
|
||||
onGround,
|
||||
ci.CollisionNormalValid,
|
||||
ci.CollisionNormal,
|
||||
ok);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ public sealed class SpherePath
|
|||
public Vector3 NegCollisionNormal;
|
||||
public bool CheckWalkable;
|
||||
public InsertType InsertType = InsertType.Transition;
|
||||
public bool PlacementAllowsSliding = true;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>SPHEREPATH.obstruction_ethereal</c>: set to <c>1</c> per-target
|
||||
|
|
@ -987,6 +988,122 @@ public sealed class Transition
|
|||
return transitionState == TransitionState.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CTransition::find_placement_pos</c> (0x0050BA50).
|
||||
/// Tests the requested position first, then searches concentric rings up
|
||||
/// to four metres away for the nearest valid placement. The ring spacing
|
||||
/// and angular sampling are radius-dependent exactly as in retail.
|
||||
/// </summary>
|
||||
public bool FindPlacementPos(PhysicsEngine engine)
|
||||
{
|
||||
var sp = SpherePath;
|
||||
|
||||
sp.SetCheckPos(sp.CurPos, sp.CurCellId);
|
||||
CollisionInfo.SlidingNormalValid = false;
|
||||
CollisionInfo.ContactPlaneValid = false;
|
||||
CollisionInfo.ContactPlaneIsWater = false;
|
||||
|
||||
var transitionState = ValidatePlacementTransition(
|
||||
TransitionalInsert(3, engine));
|
||||
if (transitionState == TransitionState.OK)
|
||||
return true;
|
||||
|
||||
if (!sp.PlacementAllowsSliding)
|
||||
return false;
|
||||
|
||||
float adjustDistance = 4f;
|
||||
float adjustRadius = adjustDistance;
|
||||
float sphereRadius = sp.LocalSphere[0].Radius;
|
||||
bool fakeSphere = false;
|
||||
|
||||
if (sphereRadius < 0.125f)
|
||||
{
|
||||
fakeSphere = true;
|
||||
adjustRadius = 2f;
|
||||
}
|
||||
else if (sphereRadius < 0.48f)
|
||||
{
|
||||
sphereRadius = 0.48f;
|
||||
}
|
||||
|
||||
float stepCountExact = 4f / sphereRadius;
|
||||
if (fakeSphere)
|
||||
stepCountExact *= 0.5f;
|
||||
if (stepCountExact <= 1f)
|
||||
return false;
|
||||
|
||||
int stepCount = (int)MathF.Ceiling(stepCountExact);
|
||||
float distancePerStep = adjustRadius / stepCount;
|
||||
float radiansPerStep = MathF.PI * distancePerStep / sphereRadius;
|
||||
float totalDistance = 0f;
|
||||
float totalRadians = 0f;
|
||||
|
||||
for (int ring = 0; ring < stepCount; ring++)
|
||||
{
|
||||
totalDistance += distancePerStep;
|
||||
totalRadians += radiansPerStep;
|
||||
|
||||
int sampleCount = (int)MathF.Ceiling(totalRadians) * 2;
|
||||
float headingStep = 360f / sampleCount;
|
||||
|
||||
for (int sample = 0; sample < sampleCount; sample++)
|
||||
{
|
||||
sp.SetCheckPos(sp.CurPos, sp.CurCellId);
|
||||
|
||||
// Frame::set_heading/get_vector_heading: 0 degrees is +Y,
|
||||
// 90 degrees is +X in AC's compass convention.
|
||||
float headingRadians = headingStep * sample * (MathF.PI / 180f);
|
||||
var offset = new Vector3(
|
||||
MathF.Sin(headingRadians) * totalDistance,
|
||||
MathF.Cos(headingRadians) * totalDistance,
|
||||
0f);
|
||||
sp.GlobalOffset = AdjustOffset(offset);
|
||||
|
||||
if (sp.GlobalOffset.Length() < PhysicsGlobals.EPSILON)
|
||||
continue;
|
||||
|
||||
sp.AddOffsetToCheckPos(sp.GlobalOffset);
|
||||
CollisionInfo.SlidingNormalValid = false;
|
||||
CollisionInfo.ContactPlaneValid = false;
|
||||
CollisionInfo.ContactPlaneIsWater = false;
|
||||
|
||||
transitionState = ValidatePlacementTransition(
|
||||
TransitionalInsert(3, engine));
|
||||
if (transitionState == TransitionState.OK)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private TransitionState ValidatePlacementTransition(TransitionState transitionState)
|
||||
{
|
||||
var sp = SpherePath;
|
||||
if (sp.CheckCellId == 0)
|
||||
return TransitionState.Collided;
|
||||
|
||||
if (transitionState == TransitionState.OK)
|
||||
{
|
||||
sp.CurPos = sp.CheckPos;
|
||||
sp.CurCellId = sp.CheckCellId;
|
||||
sp.CurOrientation = sp.CheckOrientation;
|
||||
for (int i = 0; i < sp.NumSphere; i++)
|
||||
{
|
||||
sp.GlobalCurrCenter[i].Origin = sp.LocalSphere[i].Origin + sp.CurPos;
|
||||
sp.GlobalCurrCenter[i].Radius = sp.LocalSphere[i].Radius;
|
||||
}
|
||||
}
|
||||
else if (sp.PlacementAllowsSliding)
|
||||
{
|
||||
// COLLISIONINFO::init at retail 0x0050B052. Placement probes are
|
||||
// independent; a failed compass sample must not bias the next one.
|
||||
CollisionInfo = new CollisionInfo();
|
||||
}
|
||||
|
||||
return transitionState;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Per-step collision check
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue