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>
86 lines
3 KiB
C#
86 lines
3 KiB
C#
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);
|
|
}
|
|
}
|