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:
parent
bfb4b26dff
commit
4b1bceefbb
12 changed files with 142 additions and 8 deletions
|
|
@ -2574,7 +2574,7 @@ public sealed class GameWindow : IDisposable
|
|||
_liveEntityPresentation = new AcDream.App.World.LiveEntityPresentationController(
|
||||
_liveEntities,
|
||||
_physicsEngine.ShadowObjects,
|
||||
entityEffects.PlayTyped,
|
||||
entityEffects.PlayTypedFromHiddenTransition,
|
||||
_equippedChildRenderer.SetDirectChildrenNoDraw,
|
||||
ClearTargetForHiddenEntity,
|
||||
() => (_liveCenterX, _liveCenterY),
|
||||
|
|
@ -3496,6 +3496,9 @@ public sealed class GameWindow : IDisposable
|
|||
? $"({sp.PositionX:F1},{sp.PositionY:F1},{sp.PositionZ:F1})@0x{sp.LandblockId:X8}"
|
||||
: "no-pos";
|
||||
string setupStr = spawn.SetupTableId is { } su ? $"0x{su:X8}" : "no-setup";
|
||||
string physicsTableStr = spawn.Physics?.PhysicsScriptTableId is { } pe
|
||||
? $"0x{pe:X8}"
|
||||
: "no-petable";
|
||||
string nameStr = spawn.Name is { Length: > 0 } n ? $"\"{n}\"" : "no-name";
|
||||
string itemTypeStr = spawn.ItemType is { } it ? $"0x{it:X8}" : "no-itemtype";
|
||||
int animPartCount = spawn.AnimPartChanges?.Count ?? 0;
|
||||
|
|
@ -3503,7 +3506,8 @@ public sealed class GameWindow : IDisposable
|
|||
int subPalCount = spawn.SubPalettes?.Count ?? 0;
|
||||
Console.WriteLine(
|
||||
$"live: spawn guid=0x{spawn.Guid:X8} name={nameStr} setup={setupStr} pos={posStr} " +
|
||||
$"itemType={itemTypeStr} animParts={animPartCount} texChanges={texChangeCount} subPalettes={subPalCount}");
|
||||
$"petable={physicsTableStr} itemType={itemTypeStr} animParts={animPartCount} " +
|
||||
$"texChanges={texChangeCount} subPalettes={subPalCount}");
|
||||
}
|
||||
|
||||
// Target the statue specifically for full diagnostic dump: Name match
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -139,6 +139,9 @@ public sealed class ChatLog
|
|||
/// </remarks>
|
||||
public void OnWeenieError(uint errorId, string? param)
|
||||
{
|
||||
if (WeenieErrorMessages.IsSilentClientControlStatus(errorId))
|
||||
return;
|
||||
|
||||
// Phase I (post-launch fix): translate the wire code into the
|
||||
// retail-faithful template via WeenieErrorMessages. Many codes
|
||||
// are *informational* (e.g. 0x051B "You have entered the X
|
||||
|
|
|
|||
|
|
@ -32,6 +32,20 @@ namespace AcDream.Core.Chat;
|
|||
/// </summary>
|
||||
public static class WeenieErrorMessages
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true for internal client-control statuses that retail consumes
|
||||
/// without adding a line to the chat scroll.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>ClientCommunicationSystem::HandleFailureEvent</c>
|
||||
/// (<c>0x00571990</c>) has no display case for ILeftTheWorld
|
||||
/// (<c>0x003B</c>) or ITeleported (<c>0x003C</c>). ACE sends the latter
|
||||
/// after a successful portal use; presenting it as a failure is therefore
|
||||
/// an acdream-only artifact.
|
||||
/// </remarks>
|
||||
public static bool IsSilentClientControlStatus(uint errorCode) =>
|
||||
errorCode is 0x003Bu or 0x003Cu;
|
||||
|
||||
/// <summary>
|
||||
/// Format a WeenieError / WeenieErrorWithString into a human-readable
|
||||
/// system-message string.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue