OneMoreRoundWeapon.CyclePresentation.cs
using Sandbox;
using System;
/// <summary>
/// Peer-local cycle presentation scheduler.
///
/// Mechanical cycling remains owner/gameplay state. Presentation is derived
/// from ShotFired, which S&box already distributes to every peer that should see
/// the shot. That gives remote players bolt/pump/lever presentation without a
/// second custom RPC stream.
/// </summary>
public partial class OneMoreRoundWeapon
{
private bool _presentationCyclePending;
private bool _presentationCycleActive;
private float _presentationCycleStartAt = -1f;
private float _presentationCycleCompleteAt = -1f;
public bool IsCyclePresentationActive =>
_presentationCycleActive;
public float CyclePresentationProgress
{
get
{
if ( !_presentationCyclePending )
return 0f;
if ( !HasCycleProfile )
return 0f;
float duration = MathF.Max( CycleProfile.Duration, 0f );
if ( duration <= 0f )
return _presentationCycleActive ? 1f : 0f;
return MathX.Clamp(
(RealTime.Now - _presentationCycleStartAt) / duration,
0f,
1f
);
}
}
private void BindCyclePresentationEvents()
{
PresentationEvent -= HandleCyclePresentationSourceEvent;
PresentationEvent += HandleCyclePresentationSourceEvent;
}
private void HandleCyclePresentationSourceEvent(
WeaponPresentationEvent presentationEvent
)
{
if (
presentationEvent.Kind == WeaponPresentationEventKind.ShotFired &&
HasCycleProfile &&
CycleProfile.AutoCycleAfterPrimaryShot
)
{
ScheduleCyclePresentation( presentationEvent.Time );
return;
}
if (
presentationEvent.Kind == WeaponPresentationEventKind.Holstered
)
{
ResetCyclePresentationState();
}
}
private void ScheduleCyclePresentation( float shotTime )
{
float startDelay = MathF.Max( CycleProfile.StartDelay, 0f );
float duration = MathF.Max( CycleProfile.Duration, 0f );
_presentationCyclePending = true;
_presentationCycleActive = false;
_presentationCycleStartAt = shotTime + startDelay;
_presentationCycleCompleteAt = _presentationCycleStartAt + duration;
if ( startDelay <= 0f )
{
StartScheduledCyclePresentation();
}
}
private void UpdateCyclePresentationState()
{
if ( !_presentationCyclePending )
return;
if ( !HasCycleProfile )
{
ResetCyclePresentationState();
return;
}
float now = RealTime.Now;
if (
!_presentationCycleActive &&
now >= _presentationCycleStartAt
)
{
StartScheduledCyclePresentation();
}
if (
_presentationCycleActive &&
now >= _presentationCycleCompleteAt
)
{
CompleteScheduledCyclePresentation();
}
}
private void StartScheduledCyclePresentation()
{
if ( _presentationCycleActive )
return;
_presentationCycleActive = true;
RaiseCycleStartedPresentation();
}
private void CompleteScheduledCyclePresentation()
{
if ( !_presentationCyclePending )
return;
bool wasActive = _presentationCycleActive;
_presentationCyclePending = false;
_presentationCycleActive = false;
_presentationCycleStartAt = -1f;
_presentationCycleCompleteAt = -1f;
if ( wasActive )
{
RaiseCycleCompletedPresentation();
}
}
private void ResetCyclePresentationState()
{
_presentationCyclePending = false;
_presentationCycleActive = false;
_presentationCycleStartAt = -1f;
_presentationCycleCompleteAt = -1f;
}
}