Partial Manager class handling game time scaling and pause behavior. It computes TimeScale based on spectator state, player choosing/level-up timers, pause/gameover states, and manages prioritized time-scale requests that lerp over time.
using System;
public partial class Manager
{
[Sync] public bool IsPaused { get; private set; }
private Player GetDisplayedChoicePlayer()
{
if ( IsSpectator && SelectedPlayer.IsValid() )
return SelectedPlayer;
return LocalPlayer;
}
public bool IsPausedForChoosing
{
get
{
var player = GetDisplayedChoicePlayer();
return !IsUnpausedChoosing && player.IsValid() && player.IsChoosingLevelUpReward;
}
}
[Sync] public float TimeScaleSync { get; set; }
private float _timeScaleBeforePause;
private class TimeScaleRequest
{
public float StartTimeScale;
public float EndTimeScale;
public float Duration;
public int Priority;
public double RealStartTime;
}
private List<TimeScaleRequest> _timeScaleRequests = new();
private float GetDisplayedPerkCount( Player player )
{
if ( !player.IsValid() )
return 0f;
return player.IsProxy ? player.SyncPerks.Count : player.Perks.Count;
}
private float GetChoiceTimeScale( Player player )
{
if ( !player.IsValid() )
return TimeScaleSync;
if ( player.IsChoosingLevelUpReward )
return player.RealTimeSinceLvlUp > 0.5f ? 0f : Utils.Map( player.RealTimeSinceLvlUp, 0f, 0.5f, 0.7f, 0f, EasingType.SineOut );
return GetDisplayedPerkCount( player ) > 1
? (player.RealTimeSinceChosePerk > 0.5f ? 1f : Utils.Map( player.RealTimeSinceChosePerk, 0f, 0.5f, 0f, 1f, EasingType.SineOut ))
: TimeScaleSync;
}
void HandleTimeScale()
{
//if( Game.IsEditor && Networking.IsHost )
// TimeScaleSync = Input.Keyboard.Down( "G" ) ? 0.1f : 1f;
if ( IsSpectator )
{
var watchedPlayer = GetDisplayedChoicePlayer();
if ( IsPaused )
Scene.TimeScale = TimeScaleSync;
else if ( IsGameOver )
Scene.TimeScale = Utils.DynamicEaseTo( Scene.TimeScale, 0.1f, 0.1f, Time.Delta );
else if ( !IsUnpausedChoosing )
Scene.TimeScale = GetChoiceTimeScale( watchedPlayer );
else
Scene.TimeScale = TimeScaleSync;
}
else if ( !IsUnpausedChoosing && !IsPaused && !IsGameOver && LocalPlayer.IsValid() )
{
Scene.TimeScale = GetChoiceTimeScale( LocalPlayer );
}
else if ( IsPaused )
{
if ( !Networking.IsHost )
{
Scene.TimeScale = TimeScaleSync;
}
}
else if( IsGameOver )
{
Scene.TimeScale = Utils.DynamicEaseTo( Scene.TimeScale, 0.1f, 0.1f, Time.Delta );
}
else
{
HandleTimeScaleRequests();
Scene.TimeScale = TimeScaleSync;
}
}
/// <summary>
/// Request a change to TimeScaleSync over a duration, with a priority.
/// If isInstant is true, sets the value immediately (no lerp).
/// You can specify the initial value for lerping.
/// </summary>
public void RequestTimeScale( float startTimeScale, float endTimeScale, float duration, int priority )
{
if ( !Networking.IsHost )
return;
_timeScaleRequests.Add( new TimeScaleRequest
{
StartTimeScale = startTimeScale,
EndTimeScale = endTimeScale,
Duration = duration,
Priority = priority,
RealStartTime = RealTime.Now
} );
}
private void HandleTimeScaleRequests()
{
if ( _timeScaleRequests.Count == 0 )
{
if ( !IsGameOver )
TimeScaleSync = 1f;
return;
}
var activeRequests = _timeScaleRequests
.Where( r => (RealTime.Now - r.RealStartTime) < r.Duration )
.ToList();
_timeScaleRequests.RemoveAll( r => (RealTime.Now - r.RealStartTime) >= r.Duration );
if ( activeRequests.Count == 0 )
{
TimeScaleSync = 1f;
return;
}
var request = activeRequests
.OrderByDescending( r => r.Priority )
.ThenByDescending( r => r.RealStartTime )
.First();
float t = Math.Clamp( (float)((RealTime.Now - request.RealStartTime) / request.Duration), 0f, 1f );
TimeScaleSync = Utils.Map( t, 0f, 1f, request.StartTimeScale, request.EndTimeScale, EasingType.SineIn );
}
}