Game/TimeShiftManager.cs
namespace Causal;
internal interface ITimeShiftEvent : ISceneEvent<ITimeShiftEvent>
{
void OnTimeShiftRequested() { }
void OnTimeShifted( bool isCause ) { }
}
public sealed class TimeShiftManager : GameObjectSystem<TimeShiftManager>, ITimeShiftEvent, ISceneStartup
{
public const string CauseTag = "cause";
public const string EffectTag = "effect";
[Property] public float SwitchCooldown { get; set; } = 2f;
[Property] public bool StartInCause { get; set; } = true;
[Property] public bool StartShiftUnlocked { get; set; } = false;
public bool IsCause { get; private set; } = true;
public bool ShiftUnlocked { get; private set; }
private readonly List<GameObject> _causeRoots = new();
private readonly List<GameObject> _effectRoots = new();
private TimeSince _timeSinceShift = 99f;
public TimeShiftManager( Scene scene ) : base( scene )
{
}
void ISceneStartup.OnHostInitialize()
{
RefreshRoots();
IsCause = StartInCause;
ShiftUnlocked = StartShiftUnlocked;
_timeSinceShift = 99f;
WarmRoots();
ApplyState();
}
void ITimeShiftEvent.OnTimeShiftRequested()
{
RequestShift();
}
public void RequestShift()
{
if ( !ShiftUnlocked )
{
return;
}
float elapsed = _timeSinceShift;
if ( elapsed >= 0f && elapsed <= SwitchCooldown )
{
return;
}
_timeSinceShift = 0f;
IsCause = !IsCause;
ApplyState();
ITimeShiftEvent.Post( x => x.OnTimeShifted( IsCause ) );
}
public void UnlockShift()
{
ShiftUnlocked = true;
}
[ConCmd( "unlock_shift", Help = "Debug: unlock time shifting before the device pickup exists." )]
public static void UnlockShiftCommand()
{
var manager = Game.ActiveScene?.GetSystem<TimeShiftManager>();
if ( manager is null )
{
Log.Warning( "causal_unlock_shift found no TimeShiftManager." );
return;
}
manager.UnlockShift();
}
private void RefreshRoots()
{
_causeRoots.Clear();
_effectRoots.Clear();
foreach ( var go in Scene.FindAllWithTag( CauseTag ) )
{
if ( IsTopmostTagged( go, CauseTag ) )
{
_causeRoots.Add( go );
}
}
foreach ( var go in Scene.FindAllWithTag( EffectTag ) )
{
if ( IsTopmostTagged( go, EffectTag ) )
{
_effectRoots.Add( go );
}
}
}
private static bool IsTopmostTagged( GameObject go, string tag )
{
var parent = go.Parent;
return !parent.IsValid() || !parent.Tags.Has( tag );
}
private void WarmRoots()
{
foreach ( var go in _causeRoots )
{
if ( go.IsValid() )
{
go.Enabled = true;
}
}
foreach ( var go in _effectRoots )
{
if ( go.IsValid() )
{
go.Enabled = true;
}
}
}
private void ApplyState()
{
if ( _causeRoots.Count == 0 || _effectRoots.Count == 0 )
{
RefreshRoots();
}
foreach ( var go in _causeRoots )
{
if ( go.IsValid() )
{
go.Enabled = IsCause;
}
}
foreach ( var go in _effectRoots )
{
if ( go.IsValid() )
{
go.Enabled = !IsCause;
}
}
}
}