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
|
|
@ -0,0 +1,86 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Enter-world overlap conformance for retail
|
||||
/// <c>CTransition::find_placement_pos</c> (0x0050BA50).
|
||||
/// </summary>
|
||||
public sealed class InitialPlacementOverlapTests
|
||||
{
|
||||
private const uint Landblock = 0xA9B40000u;
|
||||
private const uint Cell = Landblock | 0x0001u;
|
||||
private const uint PlayerId = 0x50000001u;
|
||||
private const uint MonsterId = 0x50000002u;
|
||||
private const float Radius = 0.48f;
|
||||
|
||||
[Fact]
|
||||
public void PlayerReloggingInsideMonster_SearchesOutToNearestClearRing()
|
||||
{
|
||||
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
|
||||
engine.AddLandblock(
|
||||
Landblock,
|
||||
new TerrainSurface(new byte[81], new float[256]),
|
||||
Array.Empty<CellSurface>(),
|
||||
Array.Empty<PortalPlane>(),
|
||||
0f,
|
||||
0f);
|
||||
|
||||
var savedFeet = new Vector3(10f, 10f, 0f);
|
||||
var playerCenter = savedFeet + new Vector3(0f, 0f, Radius);
|
||||
var monsterCenter = playerCenter + new Vector3(0.10f, 0f, 0f);
|
||||
|
||||
// Retail registers every placed CPhysicsObj, including the local
|
||||
// player. The mover's OBJECTINFO::object self pointer excludes only
|
||||
// its own shadow while the monster remains an obstruction.
|
||||
RegisterSphere(engine, PlayerId, playerCenter,
|
||||
EntityCollisionFlags.IsPlayer | EntityCollisionFlags.IsCreature);
|
||||
RegisterSphere(engine, MonsterId, monsterCenter,
|
||||
EntityCollisionFlags.IsCreature);
|
||||
|
||||
ResolveResult result = engine.ResolvePlacement(
|
||||
savedFeet,
|
||||
Cell,
|
||||
sphereRadius: Radius,
|
||||
sphereHeight: 1.835f,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f,
|
||||
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: PlayerId);
|
||||
|
||||
Assert.True(result.Ok);
|
||||
Assert.NotEqual(savedFeet, result.Position);
|
||||
float centerDistance = Vector3.Distance(
|
||||
result.Position + new Vector3(0f, 0f, Radius),
|
||||
monsterCenter);
|
||||
Assert.True(centerDistance >= Radius * 2f - PhysicsGlobals.EPSILON,
|
||||
$"placement must clear the monster; centers remain {centerDistance:F3} m apart");
|
||||
Assert.True(Vector3.Distance(savedFeet, result.Position) <= 4f,
|
||||
"retail placement search is bounded to four metres");
|
||||
}
|
||||
|
||||
private static void RegisterSphere(
|
||||
PhysicsEngine engine,
|
||||
uint id,
|
||||
Vector3 center,
|
||||
EntityCollisionFlags flags)
|
||||
{
|
||||
engine.ShadowObjects.Register(
|
||||
id,
|
||||
gfxObjId: 0u,
|
||||
worldPos: center,
|
||||
rotation: Quaternion.Identity,
|
||||
radius: Radius,
|
||||
worldOffsetX: 0f,
|
||||
worldOffsetY: 0f,
|
||||
landblockId: Landblock,
|
||||
collisionType: ShadowCollisionType.Sphere,
|
||||
cylHeight: 0f,
|
||||
scale: 1f,
|
||||
state: 0u,
|
||||
flags: flags,
|
||||
seedCellId: Cell,
|
||||
isStatic: false);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue