fix(world): preserve portal materialization effects

Route accepted Hidden and UnHide transitions through retail's internal typed-script path so their DAT scripts queue while the owner is cell-less and resume at the destination. Consume ACE's successful teleport control statuses silently, matching retail HandleFailureEvent.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-16 08:14:48 +02:00
parent bfb4b26dff
commit 4b1bceefbb
12 changed files with 142 additions and 8 deletions

View file

@ -256,8 +256,36 @@ public sealed class EntityEffectController : IAnimationHookSink
public bool PlayTyped(uint ownerLocalId, uint rawScriptType, float intensity)
{
if (!CanStartOwner(ownerLocalId)
|| !_profilesByLocalId.TryGetValue(ownerLocalId, out EntityEffectProfile? profile)
// Retail CPhysicsObj::play_script @ 0x00513260 does not enqueue for a
// cell-less object. Network F755/default-hook playback comes through
// this ordinary path.
if (!CanStartOwner(ownerLocalId))
return false;
return ResolveAndQueueTyped(ownerLocalId, rawScriptType, intensity);
}
/// <summary>
/// Queues a typed script from <c>CPhysicsObj::set_hidden</c> without the
/// ordinary <c>play_script</c> cell gate.
/// </summary>
/// <remarks>
/// Retail <c>set_hidden</c> at <c>0x00514C60</c> resolves Hidden/UnHide
/// directly and calls <c>play_script_internal</c>. This is load-bearing
/// during portal travel: the owner may already be cell-less while its
/// purple materialization script must remain queued for destination entry.
/// </remarks>
public bool PlayTypedFromHiddenTransition(
uint ownerLocalId,
uint rawScriptType,
float intensity)
=> ResolveAndQueueTyped(ownerLocalId, rawScriptType, intensity);
private bool ResolveAndQueueTyped(
uint ownerLocalId,
uint rawScriptType,
float intensity)
{
if (!_profilesByLocalId.TryGetValue(ownerLocalId, out EntityEffectProfile? profile)
|| profile.CurrentPhysicsScriptTableDid is not { } tableDid)
{
DiagnosticSink?.Invoke(