acdream/src/AcDream.App/Update/LiveObjectFrameController.cs

246 lines
10 KiB
C#

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;
}
/// <summary>
/// Owns the shared effect-manager tail that follows ordinary and static
/// object worksets in acdream's registered TS-50/TS-51 adaptation.
/// </summary>
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);
}
}
/// <summary>
/// Owns the complete pre-network local/ordinary/static live-object phase.
/// </summary>
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<LiveEntityAnimationState>
_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<LiveEntityAnimationState> 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<uint, LiveEntityAnimationSchedule> 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);
}
}
/// <summary>
/// Re-composes spatial derivatives after authoritative root mutations without
/// advancing animation, physics, effects, particles, scripts, or fades.
/// </summary>
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();
}
}