246 lines
10 KiB
C#
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();
|
|
}
|
|
}
|