using System.Numerics; using AcDream.Core.Physics; namespace AcDream.Core.Tests.Physics; /// /// Enter-world overlap conformance for retail /// CTransition::find_placement_pos (0x0050BA50). /// 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(), Array.Empty(), 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); } }