refactor(world): extract live projection mechanics
Move appearance rebinding, collision construction, default-pose resolution, local shadow ownership, and exact leave-world presentation into focused owners. Preserve retail parent ordering with staged validation, committed recovery, recursive attached-subtree withdrawal, and retryable exact teardown across parent, pickup, position, unwield, and pose-loss edges. Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
c87bac31a0
commit
69a2ca0c6d
26 changed files with 4172 additions and 430 deletions
|
|
@ -216,6 +216,92 @@ public sealed class ShadowObjectRegistry
|
|||
CollisionType: ShadowCollisionType.BSP, CylHeight: 0f, Scale: 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces an existing live PartArray collision payload in its current
|
||||
/// shadow-cell membership. Retail <c>CPartArray::SetPart</c> changes the
|
||||
/// part read by later collision tests; it does not recalculate cross-cells.
|
||||
/// A suspended owner updates only its retained payload. A newly shaped
|
||||
/// owner may perform its first flood, then immediately suspend when its
|
||||
/// canonical projection is Hidden, attached, or cell-less.
|
||||
/// </summary>
|
||||
public void ReplaceMultiPartPayload(
|
||||
uint entityId,
|
||||
Vector3 entityWorldPos,
|
||||
Quaternion entityWorldRot,
|
||||
System.Collections.Generic.IReadOnlyList<ShadowShape> shapes,
|
||||
uint state,
|
||||
EntityCollisionFlags flags,
|
||||
float worldOffsetX,
|
||||
float worldOffsetY,
|
||||
uint landblockId,
|
||||
uint seedCellId = 0u,
|
||||
bool isStatic = false,
|
||||
bool suspendIfNew = false)
|
||||
{
|
||||
if (!_entityReg.TryGetValue(entityId, out RegistrationRecord? prior)
|
||||
|| !prior.IsMultiPart)
|
||||
{
|
||||
if (shapes.Count == 0)
|
||||
return;
|
||||
RegisterMultiPart(
|
||||
entityId,
|
||||
entityWorldPos,
|
||||
entityWorldRot,
|
||||
shapes,
|
||||
state,
|
||||
flags,
|
||||
worldOffsetX,
|
||||
worldOffsetY,
|
||||
landblockId,
|
||||
seedCellId,
|
||||
isStatic);
|
||||
if (suspendIfNew)
|
||||
Suspend(entityId);
|
||||
return;
|
||||
}
|
||||
|
||||
bool suspended = _suspendedEntities.Contains(entityId);
|
||||
_entityShapes[entityId] = shapes;
|
||||
_entityReg[entityId] = prior with
|
||||
{
|
||||
EntityWorldPos = entityWorldPos,
|
||||
EntityWorldRot = entityWorldRot,
|
||||
State = state,
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
if (suspended || !_entityToCells.TryGetValue(entityId, out List<uint>? cells))
|
||||
return;
|
||||
|
||||
foreach (uint cellId in cells)
|
||||
{
|
||||
if (_cells.TryGetValue(cellId, out List<ShadowEntry>? entries))
|
||||
entries.RemoveAll(entry => entry.EntityId == entityId);
|
||||
}
|
||||
|
||||
foreach (ShadowShape shape in shapes)
|
||||
{
|
||||
Vector3 partWorldPos = entityWorldPos
|
||||
+ Vector3.Transform(shape.LocalPosition, entityWorldRot);
|
||||
Quaternion partWorldRot = entityWorldRot * shape.LocalRotation;
|
||||
var entry = new ShadowEntry(
|
||||
EntityId: entityId,
|
||||
GfxObjId: shape.GfxObjId,
|
||||
Position: partWorldPos,
|
||||
Rotation: partWorldRot,
|
||||
Radius: shape.Radius,
|
||||
CollisionType: shape.CollisionType,
|
||||
CylHeight: shape.CylHeight,
|
||||
Scale: shape.Scale,
|
||||
State: state,
|
||||
Flags: flags,
|
||||
LocalPosition: shape.LocalPosition,
|
||||
LocalRotation: shape.LocalRotation);
|
||||
foreach (uint cellId in cells)
|
||||
AddEntryToCell(entry, cellId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail flood-sphere rule (CylSphere overload, Ghidra 0x0052b9f0):
|
||||
/// when the object has cylinder shapes, each contributes one sphere at
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue