using System.Numerics; using AcDream.App.Input; using AcDream.App.Interaction; using AcDream.App.Rendering; using AcDream.App.Rendering.Vfx; using AcDream.App.World; using AcDream.Core.Physics; using AcDream.Core.Rendering; using AcDream.Core.Vfx; using AcDream.Core.World; using AcDream.UI.Abstractions.Panels.Settings; namespace AcDream.App.Update; internal interface ILiveObjectFramePhase { void Tick(float deltaSeconds); } internal interface ILiveSessionFramePhase { void Tick(); } internal interface IPostNetworkCommandFramePhase { void RunPostNetworkCommandPhase(); } internal interface ILiveSpatialReconcilePhase { void Reconcile(); } internal interface IParticleRangeSource { float RangeMultiplier { get; } } internal sealed class SettingsParticleRangeSource : IParticleRangeSource { private readonly SettingsVM? _settings; private readonly ParticleRange _fallback; public SettingsParticleRangeSource(SettingsVM? settings, ParticleRange fallback) { _settings = settings; _fallback = fallback; } public float RangeMultiplier => (_settings?.DisplayDraft.ParticleRange ?? _fallback) == ParticleRange.Extended ? ParticleVisibilityController.ExtendedRangeMultiplier : 1f; } /// /// Owns the shared effect-manager tail that follows ordinary and static /// object worksets in acdream's registered TS-50/TS-51 adaptation. /// internal sealed class LiveEffectFrameController { private readonly TranslucencyFadeManager _translucencyFades; private readonly AnimationHookFrameQueue _animationHooks; private readonly EntityEffectController _entityEffects; private readonly ParticleHookSink _particleSink; private readonly LiveEntityLightController _lights; private readonly ParticleVisibilityController _particleVisibility; private readonly ParticleSystem _particles; private readonly PhysicsScriptRunner _scripts; private readonly IPhysicsScriptTimeSource _scriptTime; private readonly IParticleRangeSource _particleRange; public LiveEffectFrameController( TranslucencyFadeManager translucencyFades, AnimationHookFrameQueue animationHooks, EntityEffectController entityEffects, ParticleHookSink particleSink, LiveEntityLightController lights, ParticleVisibilityController particleVisibility, ParticleSystem particles, PhysicsScriptRunner scripts, IPhysicsScriptTimeSource scriptTime, IParticleRangeSource particleRange) { _translucencyFades = translucencyFades ?? throw new ArgumentNullException(nameof(translucencyFades)); _animationHooks = animationHooks ?? throw new ArgumentNullException(nameof(animationHooks)); _entityEffects = entityEffects ?? throw new ArgumentNullException(nameof(entityEffects)); _particleSink = particleSink ?? throw new ArgumentNullException(nameof(particleSink)); _lights = lights ?? throw new ArgumentNullException(nameof(lights)); _particleVisibility = particleVisibility ?? throw new ArgumentNullException(nameof(particleVisibility)); _particles = particles ?? throw new ArgumentNullException(nameof(particles)); _scripts = scripts ?? throw new ArgumentNullException(nameof(scripts)); _scriptTime = scriptTime ?? throw new ArgumentNullException(nameof(scriptTime)); _particleRange = particleRange ?? throw new ArgumentNullException(nameof(particleRange)); } public void Tick(float deltaSeconds) { // Retail's ordinary-object UpdatePositionInternal @ 0x00512C30 calls // CPhysicsObj::process_hooks @ 0x00511550 before its manager tail. // acdream currently keeps non-AnimationDone hooks at this deferred // shared boundary under TS-50; this extraction must not silently // claim that adaptation is gone. _translucencyFades.AdvanceAll(deltaSeconds); _animationHooks.Drain(); _entityEffects.RefreshLiveOwnerPoses(); _particleSink.RefreshAttachedEmitters(); _lights.Refresh(); _particleVisibility.Apply(_particles, _particleRange.RangeMultiplier); // Retail CPhysicsObj::UpdateObjectInternal @ 0x005156B0 advances the // ordinary-object Particle manager before Script. acdream's shared // once-per-host tail and its static-order difference remain TS-51. _particles.Tick(deltaSeconds); _scripts.Tick(_scriptTime.CurrentScriptTime); } } /// /// Owns the complete pre-network local/ordinary/static live-object phase. /// internal sealed class LiveObjectFrameController : ILiveObjectFramePhase { private readonly RetailInboundEventDispatcher _inboundEvents; private readonly RetailLocalPlayerFrameController _localPlayerFrame; private readonly SelectionInteractionController? _selectionInteractions; private readonly LiveEntityRuntime _liveEntities; private readonly ILocalPlayerIdentitySource _localPlayer; private readonly LiveWorldOriginState _origin; private readonly LiveEntityAnimationScheduler _animations; private readonly RetailStaticAnimatingObjectScheduler _staticAnimations; private readonly LiveEntityAnimationPresenter _animationPresenter; private readonly LiveEntityAnimationRuntimeView _animatedEntities; private readonly EquippedChildRenderController _equippedChildren; private readonly LiveEffectFrameController _effects; public LiveObjectFrameController( RetailInboundEventDispatcher inboundEvents, RetailLocalPlayerFrameController localPlayerFrame, SelectionInteractionController? selectionInteractions, LiveEntityRuntime liveEntities, ILocalPlayerIdentitySource localPlayer, LiveWorldOriginState origin, LiveEntityAnimationScheduler animations, RetailStaticAnimatingObjectScheduler staticAnimations, LiveEntityAnimationPresenter animationPresenter, LiveEntityAnimationRuntimeView animatedEntities, EquippedChildRenderController equippedChildren, LiveEffectFrameController effects) { _inboundEvents = inboundEvents ?? throw new ArgumentNullException(nameof(inboundEvents)); _localPlayerFrame = localPlayerFrame ?? throw new ArgumentNullException(nameof(localPlayerFrame)); _selectionInteractions = selectionInteractions; _liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities)); _localPlayer = localPlayer ?? throw new ArgumentNullException(nameof(localPlayer)); _origin = origin ?? throw new ArgumentNullException(nameof(origin)); _animations = animations ?? throw new ArgumentNullException(nameof(animations)); _staticAnimations = staticAnimations ?? throw new ArgumentNullException(nameof(staticAnimations)); _animationPresenter = animationPresenter ?? throw new ArgumentNullException(nameof(animationPresenter)); _animatedEntities = animatedEntities ?? throw new ArgumentNullException(nameof(animatedEntities)); _equippedChildren = equippedChildren ?? throw new ArgumentNullException(nameof(equippedChildren)); _effects = effects ?? throw new ArgumentNullException(nameof(effects)); } public void Tick(float deltaSeconds) => _inboundEvents.Run( this, deltaSeconds, static (controller, elapsed) => controller.TickCore(elapsed)); private void TickCore(float deltaSeconds) { _localPlayerFrame.AdvanceBeforeNetwork(deltaSeconds); _selectionInteractions?.DrainOutbound(); Vector3? playerPosition = _liveEntities.MaterializedWorldEntities.TryGetValue( _localPlayer.ServerGuid, out WorldEntity? player) ? player.Position : null; IReadOnlyDictionary schedules = _animations.Tick( deltaSeconds, playerPosition, _localPlayerFrame.HiddenPartPoseDirty, _origin.CenterX, _origin.CenterY, _animationPresenter.PrepareAnimation); // CPhysics::UseTime @ 0x00509950 walks ordinary objects before the // distinct static_animating_objects workset. _staticAnimations.Tick(deltaSeconds); if (_animatedEntities.Count > 0) _animationPresenter.Present(schedules); _equippedChildren.Tick(); // Retail CPhysicsObj::animate_static_object @ 0x00513DF0 runs // Script -> Particle -> process_hooks. acdream intentionally retains // the TS-51 static-order difference here: ProcessHooks precedes the // shared once-per-host Particle -> Script tail below. _staticAnimations.ProcessHooks(); _effects.Tick(deltaSeconds); } } /// /// Re-composes spatial derivatives after authoritative root mutations without /// advancing animation, physics, effects, particles, scripts, or fades. /// internal sealed class LiveSpatialPresentationReconciler : ILiveSpatialReconcilePhase { private readonly EntityEffectController _entityEffects; private readonly EquippedChildRenderController _equippedChildren; private readonly ParticleHookSink _particleSink; private readonly LiveEntityLightController _lights; public LiveSpatialPresentationReconciler( EntityEffectController entityEffects, EquippedChildRenderController equippedChildren, ParticleHookSink particleSink, LiveEntityLightController lights) { _entityEffects = entityEffects ?? throw new ArgumentNullException(nameof(entityEffects)); _equippedChildren = equippedChildren ?? throw new ArgumentNullException(nameof(equippedChildren)); _particleSink = particleSink ?? throw new ArgumentNullException(nameof(particleSink)); _lights = lights ?? throw new ArgumentNullException(nameof(lights)); } public void Reconcile() { _entityEffects.RefreshLiveOwnerPoses(); _equippedChildren.Tick(); _particleSink.RefreshAttachedEmitters(); _lights.Refresh(); } }