s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
link Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=fss.causal&take=20
Showing code results for query:
*
(15 total matches found)
Game
game
global using Sandbox; global using System.Collections.Generic;
Game
game
namespace Causal;
public interface IMovieCondition
{
bool HasFailed();
}
Game
game
using System;
namespace Causal;
public sealed class Hover : Component, Component.ExecuteInEditor
{
[Property] public float HoverHeight { get; set; } = 8f;
[Property] public float HoverSpeed { get; set; } = 1.5f;
[Property] public float SpinSpeed { get; set; }
private bool _hasBase;
private Vector3 _basePosition;
private Rotation _baseRotation;
private Vector3 _lastWrittenPosition;
private Rotation _lastWrittenRotation;
private bool _hasLastWritten;
protected override void OnUpdate()
{
float offset = MathF.Sin( Time.Now * HoverSpeed ) * HoverHeight;
if ( !_hasBase || (_hasLastWritten && GameObject.WorldPosition != _lastWrittenPosition) )
{
_basePosition = GameObject.WorldPosition - Vector3.Up * offset;
_baseRotation = GameObject.WorldRotation;
_hasBase = true;
}
else if ( _hasLastWritten && GameObject.WorldRotation != _lastWrittenRotation )
{
_baseRotation = GameObject.WorldRotation * Rotation.FromYaw( -SpinSpeed * Time.Delta );
}
_lastWrittenPosition = _basePosition + Vector3.Up * offset;
GameObject.WorldPosition = _lastWrittenPosition;
if ( SpinSpeed != 0f )
{
GameObject.WorldRotation *= Rotation.FromYaw( SpinSpeed * Time.Delta );
}
_lastWrittenRotation = GameObject.WorldRotation;
_hasLastWritten = true;
}
protected override void OnDisabled()
{
RestoreAuthoredTransform();
}
protected override void OnDestroy()
{
RestoreAuthoredTransform();
}
private void RestoreAuthoredTransform()
{
if ( !_hasBase )
{
return;
}
GameObject.WorldPosition = _basePosition;
GameObject.WorldRotation = _baseRotation;
_hasLastWritten = false;
}
}
Game
game
namespace Causal;
public sealed class ManualMovieCondition : Component, IMovieCondition
{
[Property] public bool Failed { get; set; }
public bool HasFailed()
{
return Failed;
}
}
Game
game
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;
}
}
}
}
Game
game
using System;
using Sandbox.MovieMaker;
namespace Causal;
public sealed class BlastDoor : Component
{
[Property] public MovieResource OpenMovie { get; set; }
[Property] public MovieResource CloseMovie { get; set; }
[Property] public float ActionCooldown { get; set; } = 2f;
[Property] public GameObject VfxObject { get; set; }
[Property] public float VfxSeconds { get; set; } = 2f;
[Property] public bool StartOpen { get; set; }
[Property] public bool AllowInterrupt { get; set; } = false;
[Property] public bool AdvanceWhileHidden { get; set; } = true;
public bool IsOpen { get; private set; }
public bool IsAnimating => _player.IsValid() && _player.IsPlaying;
public bool CanClose => CloseMovie is not null && CloseMovie.IsValid();
private MoviePlayer _player;
private GameObject _vfx;
private TimeSince _timeSinceAction = 99f;
private TimeSince _timeSinceVfx = 99f;
private bool _vfxShowing;
private float _storedPosition;
private bool _wasPlaying;
private MovieResource _savedMovie;
private float _savedPosition;
private float _savedTimeScale = 1f;
private bool _savedWasPlaying;
private bool _hasSaved;
private TimeSince _timeSinceHidden;
protected override void OnStart()
{
_player = GetComponent<MoviePlayer>();
if ( !_player.IsValid() )
{
Log.Warning( $"BlastDoor on '{GameObject.Name}' needs a MoviePlayer on the same GameObject." );
return;
}
_player.CreateTargets = false;
IsOpen = StartOpen;
_timeSinceAction = 99f;
ResolveVfx();
}
public void Open()
{
Play( OpenMovie, true );
}
public void Close()
{
Play( CloseMovie, false );
}
public void Toggle()
{
if ( IsOpen )
{
Close();
}
else
{
Open();
}
}
protected override void OnDisabled()
{
if ( !_player.IsValid() )
{
return;
}
_savedMovie = _player.Resource as MovieResource;
if ( !_savedMovie.IsValid() )
{
_savedMovie = IsOpen ? OpenMovie : CloseMovie;
}
_savedPosition = _player.PositionSeconds;
_savedTimeScale = _player.TimeScale;
_savedWasPlaying = _player.IsPlaying || _storedPosition > 0.05f;
_timeSinceHidden = 0f;
_hasSaved = true;
}
protected override void OnEnabled()
{
if ( !_hasSaved )
{
return;
}
_hasSaved = false;
if ( !_savedWasPlaying || !_savedMovie.IsValid() || !_player.IsValid() )
{
return;
}
float end = MovieGate.MovieDurationSeconds( _savedMovie );
float target = AdvanceWhileHidden ? _savedPosition + (float)_timeSinceHidden * _savedTimeScale : _savedPosition;
target = end > 0f ? Math.Clamp( target, 0f, Math.Max( 0f, end - 0.05f ) ) : 0f;
_player.Play( _savedMovie );
_player.TimeScale = _savedTimeScale;
_player.PositionSeconds = target;
_player.IsPlaying = true;
_storedPosition = target;
_wasPlaying = true;
}
private void Play( MovieResource movie, bool targetState )
{
if ( !GameObject.IsValid() )
{
return;
}
if ( !_player.IsValid() )
{
Log.Warning( $"BlastDoor on '{GameObject.Name}' has no MoviePlayer." );
return;
}
if ( movie is null || !movie.IsValid() )
{
Log.Warning( $"BlastDoor on '{GameObject.Name}' is missing its {(targetState ? "OpenMovie" : "CloseMovie")}." );
return;
}
if ( IsOpen == targetState )
{
return;
}
if ( _timeSinceAction < ActionCooldown )
{
return;
}
if ( !AllowInterrupt && _player.IsPlaying )
{
return;
}
_player.Play( movie );
_timeSinceAction = 0f;
_storedPosition = 0f;
IsOpen = targetState;
if ( targetState )
{
ShowVfx();
}
}
protected override void OnUpdate()
{
TickVfx();
if ( !_player.IsValid() )
{
return;
}
if ( _player.IsPlaying )
{
_storedPosition = _player.PositionSeconds;
_wasPlaying = true;
return;
}
if ( !_wasPlaying )
{
return;
}
_wasPlaying = false;
float pos = _player.PositionSeconds;
float end = ClipEndSeconds();
bool finished = end > 0f ? pos >= end - 0.05f : pos >= _storedPosition - 0.05f;
if ( _storedPosition > 0.05f && !finished )
{
_player.PositionSeconds = _storedPosition;
_player.IsPlaying = true;
}
else
{
_storedPosition = 0f;
}
}
private float ClipEndSeconds()
{
var clip = _player.Clip;
if ( clip is null )
{
return 0f;
}
return (float)clip.Duration.TotalSeconds;
}
private void ResolveVfx()
{
_vfx = VfxObject;
if ( !_vfx.IsValid() )
{
Log.Warning( $"BlastDoor on '{GameObject.Name}' has no VfxObject assigned." );
return;
}
_vfx.Enabled = false;
}
private void ShowVfx()
{
if ( !_vfx.IsValid() )
{
return;
}
_vfx.Enabled = true;
_timeSinceVfx = 0f;
_vfxShowing = true;
}
private void TickVfx()
{
if ( !_vfxShowing )
{
return;
}
if ( !_vfx.IsValid() )
{
_vfxShowing = false;
return;
}
if ( _timeSinceVfx >= VfxSeconds )
{
_vfx.Enabled = false;
_vfxShowing = false;
}
}
}
Game
game
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Causal;
public sealed class FirstPersonCosmetics : Component
{
[Property] public SkinnedModelRenderer ArmsRenderer { get; set; }
[Property] public SkinnedModelRenderer ClothingRenderer { get; set; }
[Property] public ulong ClothingBodyGroups { get; set; } = 1088;
[Property] public List<string> ExcludedCategories { get; set; } = new() { "Gloves" };
[Property] public List<string> RemovedClothingNames { get; set; } = new() { "y_front_pants" };
[Property] public float CosmeticSettleTimeout { get; set; } = 30f;
public static bool IsLoadingClothing { get; private set; }
private const ulong BareBodyGroups = 21;
private const ulong ClothedBodyGroups = 20;
private int _generation;
private CancellationTokenSource _cts;
protected override void OnStart()
{
if ( !ArmsRenderer.IsValid() || !ClothingRenderer.IsValid() )
{
Log.Warning( "FirstPersonCosmetics has no valid arms/clothing renderer, cosmetics disabled." );
return;
}
_cts = new CancellationTokenSource();
int generation = ++_generation;
_ = ApplyCosmeticsAsync( generation, _cts.Token );
}
protected override void OnDestroy()
{
_generation++;
if ( _cts is not null )
{
_cts.Cancel();
_cts.Dispose();
_cts = null;
}
}
private async Task ApplyCosmeticsAsync( int generation, CancellationToken token )
{
try
{
var container = ClothingContainer.CreateFromLocalUser();
if ( !IsCurrent( generation, token ) )
{
return;
}
StripExcludedCategories( container );
IsLoadingClothing = true;
await container.ApplyAsync( ClothingRenderer, token );
if ( !IsCurrent( generation, token ) )
{
return;
}
await WaitForSettled( container, generation, token );
if ( !IsCurrent( generation, token ) )
{
return;
}
SetLoadingForGeneration( generation, false );
if ( StripExcludedCategories( container ) > 0 )
{
container.Apply( ClothingRenderer );
}
RemoveNamedClothingObjects();
ApplyClothingRenderOptions();
ApplyClothingBodyGroups();
SetArmsBodyGroups( CountEntries( container ) > 0 ? ClothedBodyGroups : BareBodyGroups );
}
catch ( OperationCanceledException )
{
SetLoadingForGeneration( generation, false );
SetArmsBodyGroupsForGeneration( generation, BareBodyGroups );
}
catch ( Exception exception )
{
SetLoadingForGeneration( generation, false );
SetArmsBodyGroupsForGeneration( generation, BareBodyGroups );
Log.Warning( $"FirstPersonCosmetics failed: {exception.Message}" );
}
}
private bool IsCurrent( int generation, CancellationToken token )
{
return generation == _generation && !token.IsCancellationRequested;
}
private void SetArmsBodyGroupsForGeneration( int generation, ulong groups )
{
if ( generation == _generation )
{
SetArmsBodyGroups( groups );
}
}
private void SetArmsBodyGroups( ulong groups )
{
if ( ArmsRenderer.IsValid() )
{
ArmsRenderer.BodyGroups = groups;
}
}
private void ApplyClothingBodyGroups()
{
if ( ClothingRenderer.IsValid() )
{
ClothingRenderer.BodyGroups = ClothingBodyGroups;
}
}
private void SetLoadingForGeneration( int generation, bool loading )
{
if ( generation == _generation )
{
IsLoadingClothing = loading;
}
}
private void ApplyClothingRenderOptions()
{
if ( !ClothingRenderer.IsValid() )
{
return;
}
ApplyClothingRenderOptions( ClothingRenderer.GameObject );
}
private static void ApplyClothingRenderOptions( GameObject root )
{
if ( !root.IsValid() || root.IsDestroyed )
{
return;
}
var renderer = root.GetComponent<SkinnedModelRenderer>();
if ( renderer.IsValid() )
{
renderer.RenderType = ModelRenderer.ShadowRenderType.Off;
var options = renderer.RenderOptions;
options.Game = false;
options.Overlay = true;
options.Bloom = false;
options.AfterUI = false;
}
foreach ( GameObject child in root.Children )
{
ApplyClothingRenderOptions( child );
}
}
private void RemoveNamedClothingObjects()
{
if ( RemovedClothingNames is null || RemovedClothingNames.Count == 0 )
{
return;
}
if ( !ClothingRenderer.IsValid() )
{
return;
}
int removed = 0;
RemoveNamedClothingObjects( ClothingRenderer.GameObject, ref removed );
if ( removed > 0 )
{
Log.Info( $"FirstPersonCosmetics removed {removed} unwanted clothing objects." );
}
}
private void RemoveNamedClothingObjects( GameObject root, ref int removed )
{
if ( !root.IsValid() || root.IsDestroyed )
{
return;
}
foreach ( GameObject child in root.Children )
{
if ( !child.IsValid() || child.IsDestroyed )
{
continue;
}
if ( IsRemovedClothingName( child.Name ) )
{
child.Destroy();
removed++;
continue;
}
RemoveNamedClothingObjects( child, ref removed );
}
}
private bool IsRemovedClothingName( string name )
{
if ( string.IsNullOrEmpty( name ) || RemovedClothingNames is null )
{
return false;
}
foreach ( string removed in RemovedClothingNames )
{
if ( !string.IsNullOrEmpty( removed ) && name.IndexOf( removed, StringComparison.OrdinalIgnoreCase ) >= 0 )
{
return true;
}
}
return false;
}
private static int CountEntries( ClothingContainer container )
{
if ( container?.Clothing is null )
{
return 0;
}
int count = 0;
foreach ( var entry in container.Clothing )
{
if ( entry is not null )
{
count++;
}
}
return count;
}
private int StripExcludedCategories( ClothingContainer container )
{
if ( container?.Clothing is null || ExcludedCategories is null || ExcludedCategories.Count == 0 )
{
return 0;
}
int stripped = 0;
var seen = new List<string>();
var entries = new List<ClothingContainer.ClothingEntry>( container.Clothing );
foreach ( var entry in entries )
{
string category = entry?.Clothing?.Category.ToString();
if ( string.IsNullOrEmpty( category ) )
{
continue;
}
if ( !seen.Contains( category ) )
{
seen.Add( category );
}
foreach ( string excluded in ExcludedCategories )
{
if ( string.Equals( category, excluded, StringComparison.OrdinalIgnoreCase ) )
{
container.Clothing.Remove( entry );
stripped++;
break;
}
}
}
if ( stripped > 0 )
{
Log.Info( $"FirstPersonCosmetics excluded {stripped} clothing entries." );
}
else if ( entries.Count > 0 )
{
Log.Info( $"FirstPersonCosmetics excluded nothing, categories present: {string.Join( ", ", seen )}" );
}
return stripped;
}
private async Task WaitForSettled( ClothingContainer container, int generation, CancellationToken token )
{
TimeSince elapsed = 0;
while ( HasPendingEntries( container ) )
{
if ( elapsed > CosmeticSettleTimeout )
{
Log.Warning( $"FirstPersonCosmetics timed out after {CosmeticSettleTimeout:0}s waiting for downloads, applying what is loaded." );
return;
}
await GameTask.DelaySeconds( 0.5f );
if ( !IsCurrent( generation, token ) )
{
return;
}
}
}
private static bool HasPendingEntries( ClothingContainer container )
{
if ( container?.Clothing is null )
{
return false;
}
foreach ( var entry in container.Clothing )
{
if ( entry is not null && entry.Clothing is null && entry.ItemDefinitionId != 0 )
{
return true;
}
}
return false;
}
}
Game
game
CausalMenu {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: all;
background-color: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(8px);
z-index: 1;
// Keep in sync with CausalGameManager.MenuFadeSeconds.
transition: opacity 0.6s ease;
&.fading {
opacity: 0;
pointer-events: none;
}
&.hidden {
display: none;
opacity: 0;
pointer-events: none;
}
}
.menu-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 64px;
}
.logo-section {
display: flex;
flex-direction: column;
align-items: center;
animation: menu-enter 0.7s ease both;
}
.logo-image {
width: 520px;
flex-shrink: 0;
filter: drop-shadow(0 4px 24px rgba(255, 180, 80, 0.2));
}
.buttons-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
animation: menu-enter 0.7s ease 0.15s both;
}
.menu-item {
color: rgba(255, 255, 255, 0.75);
font-size: 34px;
font-weight: 500;
letter-spacing: 6px;
text-transform: uppercase;
text-align: center;
cursor: pointer;
text-shadow: 0 0 8px rgba(120, 200, 255, 0.35);
transition: color 0.2s ease, transform 0.18s ease;
&:hover {
color: #fff;
text-shadow: 0 0 18px rgba(170, 220, 255, 0.95);
transform: scale(1.06);
}
&:active {
transform: scale(0.98);
}
}
.menu-item.primary {
animation-name: menu-pulse;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
.newgame-row {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
}
.menu-item.disabled {
color: rgba(255, 255, 255, 0.25);
text-shadow: none;
cursor: default;
animation: none;
&:hover {
color: rgba(255, 255, 255, 0.25);
text-shadow: none;
transform: none;
}
&:active {
transform: none;
}
}
.avatar-status {
position: absolute;
left: 100%;
top: 0;
bottom: 0;
margin-left: 24px;
display: flex;
align-items: center;
font-size: 20px;
letter-spacing: 2px;
color: rgba(180, 230, 255, 0.8);
text-shadow: 0 0 8px rgba(120, 200, 255, 0.35);
white-space: nowrap;
}
@keyframes menu-enter {
0% {
opacity: 0;
transform: translateY(14px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes menu-pulse {
0% {
color: rgba(255, 255, 255, 0.75);
text-shadow: 0 0 8px rgba(120, 200, 255, 0.4);
}
50% {
color: #fff;
text-shadow: 0 0 20px rgba(170, 220, 255, 0.95);
}
100% {
color: rgba(255, 255, 255, 0.75);
text-shadow: 0 0 8px rgba(120, 200, 255, 0.4);
}
}
.about-link {
position: absolute;
top: 32px;
right: 40px;
display: flex;
flex-direction: row;
align-items: center;
height: 48px;
padding-left: 0;
padding-right: 0;
background-color: rgba(8, 18, 26, 0.55);
backdrop-filter: blur(4px);
border: 1px solid rgba(140, 210, 255, 0.35);
border-radius: 24px;
box-shadow: 0 0 16px rgba(120, 200, 255, 0.08);
color: rgba(190, 225, 255, 0.8);
cursor: pointer;
overflow: hidden;
transition: border-color 0.2s ease, box-shadow 0.2s ease, padding-left 0.25s ease, padding-right 0.25s ease;
z-index: 150;
pointer-events: all;
&:hover {
border-color: rgba(170, 220, 255, 0.9);
box-shadow: 0 0 24px rgba(140, 210, 255, 0.3);
padding-left: 4px;
padding-right: 20px;
}
&:active {
transform: scale(0.97);
}
}
.about-glyph {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 46px;
height: 46px;
font-size: 22px;
font-weight: 600;
text-shadow: 0 0 8px rgba(120, 200, 255, 0.35);
}
.about-expand {
max-width: 0;
opacity: 0;
overflow: hidden;
white-space: nowrap;
color: #fff;
font-size: 17px;
font-weight: 500;
letter-spacing: 3px;
text-transform: uppercase;
text-shadow: 0 0 14px rgba(170, 220, 255, 0.95);
transition: max-width 0.25s ease, opacity 0.2s ease;
}
.about-link:hover .about-expand {
max-width: 240px;
opacity: 1;
}
.about-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 1);
opacity: 0;
pointer-events: none;
z-index: 200;
&.open {
pointer-events: all;
}
}
.about-panel {
position: absolute;
top: 0;
right: 0;
width: min(520px, 92vw);
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(270deg, rgba(8, 18, 26, 0.97) 0%, rgba(8, 16, 24, 0.92) 100%);
border-left: 1px solid rgba(140, 210, 255, 0.5);
box-shadow: -8px 0 48px rgba(120, 200, 255, 0.12);
padding: 48px 40px 32px;
z-index: 201;
pointer-events: all;
}
.about-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
margin-bottom: 0;
}
.about-title {
color: rgba(190, 225, 255, 0.95);
font-size: 28px;
font-weight: 600;
letter-spacing: 4px;
text-transform: uppercase;
text-shadow: 0 0 14px rgba(140, 210, 255, 0.6);
}
.about-close {
color: rgba(190, 225, 255, 0.6);
font-size: 24px;
cursor: pointer;
padding: 8px;
transition: color 0.2s ease, transform 0.18s ease;
&:hover {
color: #fff;
text-shadow: 0 0 14px rgba(170, 220, 255, 0.95);
transform: scale(1.1);
}
}
.about-body {
flex-shrink: 1;
flex-grow: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 26px;
padding-top: 8px;
padding-right: 12px;
label {
margin: 0;
}
.about-para {
color: rgba(220, 235, 245, 0.88);
font-size: 23px;
line-height: 1.75;
}
.about-quote {
color: rgba(190, 225, 255, 0.95);
font-style: italic;
font-size: 23px;
border-left: 2px solid rgba(140, 210, 255, 0.5);
padding-left: 16px;
text-shadow: 0 0 10px rgba(140, 210, 255, 0.4);
}
}
.about-footer {
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid rgba(140, 210, 255, 0.2);
color: rgba(170, 210, 240, 0.5);
font-size: 15px;
letter-spacing: 2px;
text-transform: uppercase;
}
Game
game
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits PanelComponent
@namespace Causal
<root class="@MenuClass">
<div class="menu-container">
<div class="logo-section">
<img class="logo-image" src="textures/causal-logo.png" alt="CAUSAL" />
</div>
<div class="buttons-section">
<div class="newgame-row">
<div class="menu-item primary @NewGameClass" @onclick=@EnterGame onmouseover=@PlayHoverSound>
<span>New Game</span>
</div>
@if ( IsApplyingClothing )
{
<label class="avatar-status">Applying avatar clothing...</label>
}
</div>
<div class="menu-item" @onclick=@QuitGame onmouseover=@PlayHoverSound>
<span>Quit</span>
</div>
</div>
</div>
<div class="about-link" @onclick=@ShowAbout onmouseover=@PlayHoverSound>
<span class="about-glyph">?</span>
<span class="about-expand">About Causal</span>
</div>
<div class="about-backdrop" @ref="_aboutBackdrop" @onclick=@HideAbout></div>
<aside class="about-panel" @ref="_aboutPanel">
<div class="about-header">
<div class="about-close" @onclick=@HideAbout onmouseover=@PlayHoverSound>
<span>✕</span>
</div>
</div>
<div class="about-body">
<label class="about-para">You have been chosen to be a Causal Agent.</label>
<label class="about-para">The Paralix Research Facility, a remote research station, has sent you there.</label>
<label class="about-para">The facility has been left abandoned with its systems becoming unstable since something went wrong, but the deeper you investigate the more you come to realize that the facility exists in more than one state.</label>
<label class="about-para">A construct located here is capable of slipgating time. In order to get to it, you will have to explore the facility, discover histories, and with some hope; to stop it from operating.</label>
<label class="about-quote">"Playing with causality comes at a price..."</label>
</div>
<div class="about-footer">
<span>Good luck!</span>
</div>
</aside>
</root>
@code
{
[Property] public float AboutSlideSeconds { get; set; } = 0.25f;
private bool _showAbout;
private bool _aboutClassesOpen;
private float _slideT;
private float _lastAppliedRight = float.MaxValue;
private float _lastAppliedOpacity = -1f;
private Panel _aboutBackdrop;
private Panel _aboutPanel;
private TimeSince _timeSinceHover = 10f;
private bool IsMenuVisible => !CausalGameManager.Instance.IsValid() || !CausalGameManager.Instance.IsActive;
private bool IsApplyingClothing => FirstPersonCosmetics.IsLoadingClothing;
private string NewGameClass => IsApplyingClothing ? "disabled" : "";
private bool IsFading => CausalGameManager.Instance.IsValid() && CausalGameManager.Instance.IsInIntro;
private string MenuClass => !IsMenuVisible ? "hidden" : (IsFading ? "fading" : "");
protected override void OnStart()
{
TickAboutSlide();
}
protected override void OnUpdate()
{
TickAboutSlide();
if ( _showAbout && Input.EscapePressed )
{
HideAbout();
}
}
private void TickAboutSlide()
{
float duration = AboutSlideSeconds <= 0.01f ? 0.01f : AboutSlideSeconds;
_slideT = MathX.Clamp( _slideT + ( _showAbout ? Time.Delta : -Time.Delta ) / duration, 0f, 1f );
float eased = _slideT * _slideT * ( 3f - 2f * _slideT );
if ( _aboutPanel.IsValid() )
{
// Percent of screen width: the panel can never exceed 92vw, so
// -120% is always fully off-screen with room for the border and
// shadow tail. No measurement, nothing to go stale on resize.
float offset = -120f * ( 1f - eased );
if ( MathF.Abs( offset - _lastAppliedRight ) > 0.1f )
{
_aboutPanel.Style.Right = Length.Percent( offset );
_lastAppliedRight = offset;
}
}
if ( _aboutBackdrop.IsValid() )
{
float opacity = 0.45f * eased;
if ( MathF.Abs( opacity - _lastAppliedOpacity ) > 0.005f )
{
_aboutBackdrop.Style.Opacity = opacity;
_lastAppliedOpacity = opacity;
}
}
if ( _aboutClassesOpen != _showAbout )
{
ApplyAboutClasses();
}
}
private void EnterGame()
{
if ( IsApplyingClothing )
{
return;
}
var manager = CausalGameManager.Instance;
if ( !manager.IsValid() )
{
Log.Warning( "CausalMenu found no CausalGameManager." );
return;
}
_showAbout = false;
ApplyAboutClasses();
manager.BeginIntro();
StateHasChanged();
}
private void ShowAbout()
{
_showAbout = true;
ApplyAboutClasses();
}
private void HideAbout()
{
if ( !_showAbout )
{
return;
}
_showAbout = false;
ApplyAboutClasses();
}
private void ApplyAboutClasses()
{
_aboutBackdrop?.SetClass( "open", _showAbout );
_aboutClassesOpen = _showAbout;
}
private void QuitGame()
{
Game.Close();
}
private void PlayHoverSound()
{
// onmouseover refires as the cursor crosses child elements and panel
// rebuilds, so debounce to a single tick per hover.
if ( _timeSinceHover < 0.15f )
{
return;
}
_timeSinceHover = 0f;
Sound.Play( "audio/ui/menuhover.sound" );
}
protected override int BuildHash() => HashCode.Combine( IsMenuVisible, IsFading, IsApplyingClothing );
}
Game
game
using Sandbox.MovieMaker;
namespace Causal;
public sealed class ShiftController : Component
{
[Property] public MoviePlayer ShiftPlayer { get; set; }
[Property] public float ShiftAtSeconds { get; set; } = 0.5f;
private MoviePlayer _shiftPlayer;
private TimeShiftManager _manager;
private TimeSince _timeSinceShiftStart;
private bool _shiftPending;
private bool _wasPlaying;
protected override void OnStart()
{
_shiftPlayer = ShiftPlayer.IsValid() ? ShiftPlayer : GetComponentInChildren<MoviePlayer>();
_manager = Game.ActiveScene?.GetSystem<TimeShiftManager>();
if ( _shiftPlayer.IsValid() )
{
var clip = _shiftPlayer.Clip;
if ( clip is not null )
{
_ = clip.Duration;
}
}
}
protected override void OnUpdate()
{
if ( Input.Pressed( "timeshift" ) )
{
TryBeginShift();
}
PollShiftTrigger();
}
private void TryBeginShift()
{
if ( CausalGameManager.Instance.IsValid() && !CausalGameManager.Instance.IsActive )
{
return;
}
var manager = GetManager();
if ( manager is not null && !manager.ShiftUnlocked )
{
return;
}
if ( !_shiftPlayer.IsValid() )
{
manager?.RequestShift();
return;
}
if ( _shiftPlayer.IsPlaying )
{
return;
}
_shiftPlayer.Play();
_timeSinceShiftStart = 0;
_shiftPending = true;
}
private void PollShiftTrigger()
{
if ( !_shiftPlayer.IsValid() )
{
return;
}
bool playing = _shiftPlayer.IsPlaying;
if ( !_shiftPending )
{
_wasPlaying = playing;
return;
}
if ( _timeSinceShiftStart >= ShiftAtSeconds )
{
FireShift();
}
else if ( _wasPlaying && !playing )
{
FireShift();
}
_wasPlaying = playing;
}
private void FireShift()
{
_shiftPending = false;
GetManager()?.RequestShift();
}
private TimeShiftManager GetManager()
{
if ( _manager is not null )
{
return _manager;
}
_manager = Game.ActiveScene?.GetSystem<TimeShiftManager>();
return _manager;
}
}
Game
game
using System;
using Sandbox.MovieMaker;
namespace Causal;
public sealed class MovieGate : Component
{
public enum HandoffMode
{
Absolute,
Proportional,
Restart
}
[Property] public MoviePlayer Player { get; set; }
[Property] public MovieResource BaseMovie { get; set; }
[Property] public MovieResource FailMovie { get; set; }
[Property] public HandoffMode Handoff { get; set; } = HandoffMode.Absolute;
[Property] public float FailOffset { get; set; }
[Property] public float FailCheckFromSeconds { get; set; } = 1f;
[Property] public float FailCheckToSeconds { get; set; } = 3f;
[Property] public bool AllowInterrupt { get; set; } = true;
[Property] public float ActionCooldown { get; set; } = 2f;
[Property] public bool AdvanceWhileHidden { get; set; } = true;
public bool IsPlaying => _player.IsValid() && _player.IsPlaying;
public bool HasBranched => _hasBranched;
private MoviePlayer _player;
private IMovieCondition _condition;
private TimeSince _timeSinceAction = 99f;
private TimeSince _timeSinceHidden;
private float _storedPosition;
private bool _wasPlaying;
private bool _hasBranched;
private MovieResource _savedMovie;
private float _savedPosition;
private float _savedTimeScale = 1f;
private bool _savedWasPlaying;
private bool _hasSaved;
protected override void OnStart()
{
ResolvePlayer();
ResolveCondition();
}
public void PlayBase()
{
if ( !GameObject.IsValid() )
{
return;
}
if ( !_player.IsValid() )
{
Log.Warning( $"MovieGate on '{GameObject.Name}' has no MoviePlayer." );
return;
}
if ( BaseMovie is null || !BaseMovie.IsValid() )
{
Log.Warning( $"MovieGate on '{GameObject.Name}' is missing its BaseMovie." );
return;
}
if ( _timeSinceAction < ActionCooldown )
{
return;
}
if ( !AllowInterrupt && _player.IsPlaying )
{
return;
}
MovieResource selected = BaseMovie;
if ( FailMovie.IsValid() && HasFailed() )
{
selected = FailMovie;
}
PlayAt( selected, 0f, true, _player.TimeScale );
_hasBranched = selected == FailMovie;
_timeSinceAction = 0f;
}
protected override void OnUpdate()
{
if ( !_player.IsValid() )
{
return;
}
if ( _player.IsPlaying )
{
_storedPosition = _player.PositionSeconds;
_wasPlaying = true;
TickBranch();
return;
}
if ( !_wasPlaying )
{
return;
}
_wasPlaying = false;
float pos = _player.PositionSeconds;
float end = ClipDurationSeconds( _player.Clip );
bool finished = end > 0f ? pos >= end - 0.05f : pos >= _storedPosition - 0.05f;
if ( _storedPosition > 0.05f && !finished )
{
_player.PositionSeconds = _storedPosition;
_player.IsPlaying = true;
}
else
{
_storedPosition = 0f;
}
}
protected override void OnDisabled()
{
if ( !_player.IsValid() )
{
return;
}
_savedMovie = CurrentMovie();
_savedPosition = _player.PositionSeconds;
_savedTimeScale = _player.TimeScale;
_savedWasPlaying = _player.IsPlaying || _storedPosition > 0.05f;
_timeSinceHidden = 0f;
_hasSaved = true;
}
protected override void OnEnabled()
{
if ( !_hasSaved )
{
return;
}
_hasSaved = false;
ResolvePlayer();
if ( !_savedWasPlaying || !_savedMovie.IsValid() || !_player.IsValid() )
{
return;
}
float end = MovieDurationSeconds( _savedMovie );
float target = AdvanceWhileHidden ? _savedPosition + (float)_timeSinceHidden * _savedTimeScale : _savedPosition;
target = end > 0f ? Math.Clamp( target, 0f, Math.Max( 0f, end - 0.05f ) ) : 0f;
PlayAt( _savedMovie, target, true, _savedTimeScale );
}
private void TickBranch()
{
if ( _hasBranched || !FailMovie.IsValid() )
{
return;
}
float pos = _storedPosition;
if ( pos < FailCheckFromSeconds || pos > FailCheckToSeconds )
{
return;
}
if ( !HasFailed() )
{
return;
}
float target = MapPosition( pos, ClipDurationSeconds( _player.Clip ), MovieDurationSeconds( FailMovie ), Handoff, FailOffset );
PlayAt( FailMovie, target, true, _player.TimeScale );
_hasBranched = true;
_timeSinceAction = 0f;
}
private void PlayAt( MovieResource movie, float position, bool resumePlaying, float timeScale )
{
if ( !_player.IsValid() || !movie.IsValid() )
{
return;
}
_player.Play( movie );
_player.TimeScale = timeScale;
_player.PositionSeconds = position;
_player.IsPlaying = resumePlaying;
_storedPosition = resumePlaying ? position : 0f;
_wasPlaying = resumePlaying;
}
private bool HasFailed()
{
return _condition is not null && _condition.HasFailed();
}
private MovieResource CurrentMovie()
{
var resource = _player.Resource as MovieResource;
if ( resource.IsValid() )
{
return resource;
}
return _hasBranched ? FailMovie : BaseMovie;
}
private void ResolvePlayer()
{
_player = Player.IsValid() ? Player : GetComponent<MoviePlayer>();
if ( !_player.IsValid() )
{
Log.Warning( $"MovieGate on '{GameObject.Name}' needs a MoviePlayer on the same GameObject." );
return;
}
_player.CreateTargets = false;
}
private void ResolveCondition()
{
_condition = null;
foreach ( var component in Components.GetAll() )
{
if ( component is IMovieCondition condition )
{
_condition = condition;
break;
}
}
if ( _condition is null )
{
Log.Warning( $"MovieGate on '{GameObject.Name}' found no IMovieCondition." );
}
}
private static float MapPosition( float basePosition, float baseDuration, float failDuration, HandoffMode mode, float offset )
{
float mapped = mode switch
{
HandoffMode.Absolute => basePosition + offset,
HandoffMode.Proportional => baseDuration > 0.001f ? basePosition / baseDuration * failDuration : 0f,
_ => 0f,
};
return failDuration > 0f ? Math.Clamp( mapped, 0f, Math.Max( 0f, failDuration - 0.05f ) ) : 0f;
}
private static float ClipDurationSeconds( IMovieClip clip )
{
if ( clip is null )
{
return 0f;
}
return (float)clip.Duration.TotalSeconds;
}
internal static float MovieDurationSeconds( MovieResource movie )
{
if ( !movie.IsValid() || movie.Compiled is null )
{
return 0f;
}
return (float)movie.Compiled.Duration.TotalSeconds;
}
}
Game
game
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Causal" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "causal" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "fss" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "fss.causal" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "29" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]
[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-09-16T20:39:46.7421903Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.115.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.115.0")]
Game
game
namespace Causal;
public sealed class CausalGameManager : Component
{
[Property] public GameObject Player { get; set; }
[Property] public float MenuFadeSeconds { get; set; } = 0.6f;
[Property] public float CameraFlightSeconds { get; set; } = 2.2f;
public static CausalGameManager Instance { get; private set; }
public GameState State { get; private set; } = GameState.Menu;
public bool IsActive => State == GameState.Active;
public bool IsInIntro => State == GameState.Intro;
private PlayerController _controller;
private TimeSince _timeSinceIntro;
private Vector3 _flightFromPosition;
private Rotation _flightFromRotation;
protected override void OnAwake()
{
Instance = this;
}
protected override void OnStart()
{
ResolvePlayer();
EnterMenu();
}
protected override void OnUpdate()
{
if ( State != GameState.Intro )
{
return;
}
TickIntro();
}
protected override void OnDestroy()
{
if ( Instance == this )
{
Instance = null;
}
}
public void EnterMenu()
{
State = GameState.Menu;
ApplyInputState();
}
public void BeginIntro()
{
if ( State != GameState.Menu )
{
return;
}
if ( !_controller.IsValid() )
{
Log.Warning( $"CausalGameManager on '{GameObject.Name}' found no PlayerController." );
return;
}
if ( !Scene.Camera.IsValid() )
{
EnterGame();
return;
}
_flightFromPosition = Scene.Camera.WorldPosition;
_flightFromRotation = Scene.Camera.WorldRotation;
_timeSinceIntro = 0;
State = GameState.Intro;
ApplyInputState();
}
public void EnterGame()
{
if ( State == GameState.Active )
{
return;
}
State = GameState.Active;
ApplyInputState();
}
private void TickIntro()
{
if ( !_controller.IsValid() || !Scene.Camera.IsValid() )
{
EnterGame();
return;
}
float flightDuration = CameraFlightSeconds <= 0.01f ? 0.01f : CameraFlightSeconds;
float flightTime = _timeSinceIntro - MenuFadeSeconds;
float t = flightTime / flightDuration;
if ( t < 0f )
{
t = 0f;
}
if ( t > 1f )
{
t = 1f;
}
float eased = t * t * (3f - 2f * t);
Transform target = _controller.EyeTransform;
Scene.Camera.WorldPosition = Vector3.Lerp( _flightFromPosition, target.Position, eased );
Scene.Camera.WorldRotation = Rotation.Slerp( _flightFromRotation, target.Rotation, eased );
if ( t >= 1f )
{
EnterGame();
}
}
private void ResolvePlayer()
{
if ( Player.IsValid() )
{
_controller = Player.GetComponent<PlayerController>();
}
if ( !_controller.IsValid() )
{
foreach ( var controller in Scene.GetAllComponents<PlayerController>() )
{
_controller = controller;
Player = controller.GameObject;
break;
}
}
if ( !_controller.IsValid() )
{
Log.Warning( $"CausalGameManager on '{GameObject.Name}' found no PlayerController." );
}
}
private void ApplyInputState()
{
if ( !_controller.IsValid() )
{
return;
}
bool active = State == GameState.Active;
_controller.WishVelocity = 0;
_controller.UseInputControls = active;
_controller.UseCameraControls = active;
_controller.UseLookControls = active;
}
public enum GameState
{
Menu,
Intro,
Active
}
}
Game
game
using System;
using Sandbox.Movement;
namespace Causal;
[Icon( "directions_walk" )]
[Group( "Movement" )]
[Title( "MoveMode - Walk-C" )]
[Description( "Walk and sprint with strafe and backpedal speed penalties" )]
public sealed class CausalMoveModeWalk : MoveModeWalk
{
[Property] public float SideSpeedMultiplier { get; set; } = 0.65f;
[Property] public float BackwardSpeedMultiplier { get; set; } = 0.65f;
private Vector3.SmoothDamped _smoothedMovement;
public override int Score( PlayerController controller )
{
return base.Score( controller ) + 1;
}
public override Vector3 UpdateMove( Rotation eyes, Vector3 input )
{
eyes = eyes.Angles() with { pitch = 0 };
input = input.ClampLength( 1 );
var direction = eyes * input;
var velocity = GetBalancedSpeed( input );
if ( direction.IsNearlyZero( 0.1f ) )
{
direction = 0;
}
else
{
_smoothedMovement.Current = direction.Normal * _smoothedMovement.Current.Length;
}
_smoothedMovement.Target = direction * velocity;
_smoothedMovement.SmoothTime = _smoothedMovement.Target.Length < _smoothedMovement.Current.Length
? Controller.DeaccelerationTime
: Controller.AccelerationTime;
_smoothedMovement.Update( Time.Delta );
if ( _smoothedMovement.Current.IsNearlyZero( 0.01f ) )
{
_smoothedMovement.Current = 0;
}
return _smoothedMovement.Current;
}
private float GetBalancedSpeed( Vector3 input )
{
var run = Input.Down( Controller.AltMoveButton );
if ( Controller.RunByDefault )
{
run = !run;
}
var velocity = run ? Controller.RunSpeed : Controller.WalkSpeed;
if ( Controller.IsDucking )
{
velocity = Controller.DuckedSpeed;
}
return velocity * GetDirectionSpeedMultiplier( input );
}
private float GetDirectionSpeedMultiplier( Vector3 input )
{
var speedMultiplier = 1f;
if ( input.x < 0f )
{
speedMultiplier *= BackwardSpeedMultiplier;
}
if ( MathF.Abs( input.y ) > 0f )
{
speedMultiplier *= MathX.Lerp( 1f, SideSpeedMultiplier, MathF.Abs( input.y ) );
}
return speedMultiplier;
}
}
Game
game
namespace Causal;
public sealed class DoorButton : Component, Component.IPressable
{
[Property] public GameObject DoorObject { get; set; }
private HighlightOutline _highlight;
private BlastDoor _door;
protected override void OnStart()
{
Component visual = (Component)GetComponent<ModelRenderer>() ?? GetComponent<MeshComponent>();
if ( !visual.IsValid() )
{
Log.Warning( $"DoorButton on '{GameObject.Name}' found no ModelRenderer or MeshComponent." );
}
if ( !DoorObject.IsValid() )
{
Log.Warning( $"DoorButton on '{GameObject.Name}' has no door GameObject assigned." );
}
else
{
_door = DoorObject.GetComponent<BlastDoor>() ?? DoorObject.GetComponentInChildren<BlastDoor>();
if ( !_door.IsValid() )
{
Log.Warning( $"DoorButton on '{GameObject.Name}' found no BlastDoor on '{DoorObject.Name}' or its children." );
}
}
_highlight = GetComponent<HighlightOutline>();
if ( !_highlight.IsValid() )
{
Log.Warning( $"DoorButton on '{GameObject.Name}' needs a HighlightOutline for hover feedback." );
}
else
{
_highlight.Enabled = false;
}
}
public bool CanPress( Component.IPressable.Event e )
{
return _door.IsValid() && (!_door.IsOpen || _door.CanClose) && (_door.AllowInterrupt || !_door.IsAnimating);
}
public bool Press( Component.IPressable.Event e )
{
if ( !CanPress( e ) )
{
return false;
}
_door.Toggle();
return true;
}
public bool Pressing( Component.IPressable.Event e )
{
return true;
}
public void Release( Component.IPressable.Event e )
{
}
public void Hover( Component.IPressable.Event e )
{
if ( _highlight.IsValid() )
{
_highlight.Enabled = true;
}
}
public void Look( Component.IPressable.Event e )
{
}
public void Blur( Component.IPressable.Event e )
{
if ( _highlight.IsValid() )
{
_highlight.Enabled = false;
}
}
public Component.IPressable.Tooltip? GetTooltip( Component.IPressable.Event e )
{
if ( !_door.IsValid() )
{
return null;
}
string title = _door.IsOpen && _door.CanClose ? "Close" : "Open";
return new Component.IPressable.Tooltip( title, "", "", true, this );
}
}
Debug: View Raw JSON Response
{
"TotalCount": 15,
"Files": [
{
"Ident": "fss.causal",
"Path": "Assembly.cs",
"FileName": "Assembly.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "global using Sandbox;\r\nglobal using System.Collections.Generic;\r\n"
},
{
"Ident": "fss.causal",
"Path": "World/IMovieCondition.cs",
"FileName": "IMovieCondition.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "namespace Causal;\r\n\r\npublic interface IMovieCondition\r\n{\r\n\tbool HasFailed();\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "World/Hover.cs",
"FileName": "Hover.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "using System;\r\n\r\nnamespace Causal;\r\n\r\npublic sealed class Hover : Component, Component.ExecuteInEditor\r\n{\r\n\t[Property] public float HoverHeight { get; set; } = 8f;\r\n\t[Property] public float HoverSpeed { get; set; } = 1.5f;\r\n\t[Property] public float SpinSpeed { get; set; }\r\n\r\n\tprivate bool _hasBase;\r\n\tprivate Vector3 _basePosition;\r\n\tprivate Rotation _baseRotation;\r\n\tprivate Vector3 _lastWrittenPosition;\r\n\tprivate Rotation _lastWrittenRotation;\r\n\tprivate bool _hasLastWritten;\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tfloat offset = MathF.Sin( Time.Now * HoverSpeed ) * HoverHeight;\r\n\r\n\t\tif ( !_hasBase || (_hasLastWritten && GameObject.WorldPosition != _lastWrittenPosition) )\r\n\t\t{\r\n\t\t\t_basePosition = GameObject.WorldPosition - Vector3.Up * offset;\r\n\t\t\t_baseRotation = GameObject.WorldRotation;\r\n\t\t\t_hasBase = true;\r\n\t\t}\r\n\t\telse if ( _hasLastWritten && GameObject.WorldRotation != _lastWrittenRotation )\r\n\t\t{\r\n\t\t\t_baseRotation = GameObject.WorldRotation * Rotation.FromYaw( -SpinSpeed * Time.Delta );\r\n\t\t}\r\n\r\n\t\t_lastWrittenPosition = _basePosition + Vector3.Up * offset;\r\n\t\tGameObject.WorldPosition = _lastWrittenPosition;\r\n\r\n\t\tif ( SpinSpeed != 0f )\r\n\t\t{\r\n\t\t\tGameObject.WorldRotation *= Rotation.FromYaw( SpinSpeed * Time.Delta );\r\n\t\t}\r\n\r\n\t\t_lastWrittenRotation = GameObject.WorldRotation;\r\n\t\t_hasLastWritten = true;\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\tRestoreAuthoredTransform();\r\n\t}\r\n\r\n\tprotected override void OnDestroy()\r\n\t{\r\n\t\tRestoreAuthoredTransform();\r\n\t}\r\n\r\n\tprivate void RestoreAuthoredTransform()\r\n\t{\r\n\t\tif ( !_hasBase )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tGameObject.WorldPosition = _basePosition;\r\n\t\tGameObject.WorldRotation = _baseRotation;\r\n\t\t_hasLastWritten = false;\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "World/ManualMovieCondition.cs",
"FileName": "ManualMovieCondition.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "namespace Causal;\r\n\r\npublic sealed class ManualMovieCondition : Component, IMovieCondition\r\n{\r\n\t[Property] public bool Failed { get; set; }\r\n\r\n\tpublic bool HasFailed()\r\n\t{\r\n\t\treturn Failed;\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "Game/TimeShiftManager.cs",
"FileName": "TimeShiftManager.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "namespace Causal;\r\n\r\ninternal interface ITimeShiftEvent : ISceneEvent<ITimeShiftEvent>\r\n{\r\n\tvoid OnTimeShiftRequested() { }\r\n\tvoid OnTimeShifted( bool isCause ) { }\r\n}\r\n\r\npublic sealed class TimeShiftManager : GameObjectSystem<TimeShiftManager>, ITimeShiftEvent, ISceneStartup\r\n{\r\n\tpublic const string CauseTag = \"cause\";\r\n\tpublic const string EffectTag = \"effect\";\r\n\r\n\t[Property] public float SwitchCooldown { get; set; } = 2f;\r\n\t[Property] public bool StartInCause { get; set; } = true;\r\n\t[Property] public bool StartShiftUnlocked { get; set; } = false;\r\n\r\n\tpublic bool IsCause { get; private set; } = true;\r\n\tpublic bool ShiftUnlocked { get; private set; }\r\n\r\n\tprivate readonly List<GameObject> _causeRoots = new();\r\n\tprivate readonly List<GameObject> _effectRoots = new();\r\n\tprivate TimeSince _timeSinceShift = 99f;\r\n\r\n\tpublic TimeShiftManager( Scene scene ) : base( scene )\r\n\t{\r\n\t}\r\n\r\n\tvoid ISceneStartup.OnHostInitialize()\r\n\t{\r\n\t\tRefreshRoots();\r\n\t\tIsCause = StartInCause;\r\n\t\tShiftUnlocked = StartShiftUnlocked;\r\n\t\t_timeSinceShift = 99f;\r\n\t\tWarmRoots();\r\n\t\tApplyState();\r\n\t}\r\n\r\n\tvoid ITimeShiftEvent.OnTimeShiftRequested()\r\n\t{\r\n\t\tRequestShift();\r\n\t}\r\n\r\n\tpublic void RequestShift()\r\n\t{\r\n\t\tif ( !ShiftUnlocked )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfloat elapsed = _timeSinceShift;\r\n\t\tif ( elapsed >= 0f && elapsed <= SwitchCooldown )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_timeSinceShift = 0f;\r\n\t\tIsCause = !IsCause;\r\n\t\tApplyState();\r\n\t\tITimeShiftEvent.Post( x => x.OnTimeShifted( IsCause ) );\r\n\t}\r\n\r\n\tpublic void UnlockShift()\r\n\t{\r\n\t\tShiftUnlocked = true;\r\n\t}\r\n\r\n\t[ConCmd( \"unlock_shift\", Help = \"Debug: unlock time shifting before the device pickup exists.\" )]\r\n\tpublic static void UnlockShiftCommand()\r\n\t{\r\n\t\tvar manager = Game.ActiveScene?.GetSystem<TimeShiftManager>();\r\n\t\tif ( manager is null )\r\n\t\t{\r\n\t\t\tLog.Warning( \"causal_unlock_shift found no TimeShiftManager.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tmanager.UnlockShift();\r\n\t}\r\n\r\n\tprivate void RefreshRoots()\r\n\t{\r\n\t\t_causeRoots.Clear();\r\n\t\t_effectRoots.Clear();\r\n\r\n\t\tforeach ( var go in Scene.FindAllWithTag( CauseTag ) )\r\n\t\t{\r\n\t\t\tif ( IsTopmostTagged( go, CauseTag ) )\r\n\t\t\t{\r\n\t\t\t\t_causeRoots.Add( go );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tforeach ( var go in Scene.FindAllWithTag( EffectTag ) )\r\n\t\t{\r\n\t\t\tif ( IsTopmostTagged( go, EffectTag ) )\r\n\t\t\t{\r\n\t\t\t\t_effectRoots.Add( go );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static bool IsTopmostTagged( GameObject go, string tag )\r\n\t{\r\n\t\tvar parent = go.Parent;\r\n\t\treturn !parent.IsValid() || !parent.Tags.Has( tag );\r\n\t}\r\n\r\n\tprivate void WarmRoots()\r\n\t{\r\n\t\tforeach ( var go in _causeRoots )\r\n\t\t{\r\n\t\t\tif ( go.IsValid() )\r\n\t\t\t{\r\n\t\t\t\tgo.Enabled = true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tforeach ( var go in _effectRoots )\r\n\t\t{\r\n\t\t\tif ( go.IsValid() )\r\n\t\t\t{\r\n\t\t\t\tgo.Enabled = true;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ApplyState()\r\n\t{\r\n\t\tif ( _causeRoots.Count == 0 || _effectRoots.Count == 0 )\r\n\t\t{\r\n\t\t\tRefreshRoots();\r\n\t\t}\r\n\r\n\t\tforeach ( var go in _causeRoots )\r\n\t\t{\r\n\t\t\tif ( go.IsValid() )\r\n\t\t\t{\r\n\t\t\t\tgo.Enabled = IsCause;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tforeach ( var go in _effectRoots )\r\n\t\t{\r\n\t\t\tif ( go.IsValid() )\r\n\t\t\t{\r\n\t\t\t\tgo.Enabled = !IsCause;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "World/BlastDoor.cs",
"FileName": "BlastDoor.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "using System;\r\nusing Sandbox.MovieMaker;\r\n\r\nnamespace Causal;\r\n\r\npublic sealed class BlastDoor : Component\r\n{\r\n\t[Property] public MovieResource OpenMovie { get; set; }\r\n\t[Property] public MovieResource CloseMovie { get; set; }\r\n\t[Property] public float ActionCooldown { get; set; } = 2f;\r\n\t[Property] public GameObject VfxObject { get; set; }\r\n\t[Property] public float VfxSeconds { get; set; } = 2f;\r\n\t[Property] public bool StartOpen { get; set; }\r\n\t[Property] public bool AllowInterrupt { get; set; } = false;\r\n\t[Property] public bool AdvanceWhileHidden { get; set; } = true;\r\n\r\n\tpublic bool IsOpen { get; private set; }\r\n\r\n\tpublic bool IsAnimating => _player.IsValid() && _player.IsPlaying;\r\n\r\n\tpublic bool CanClose => CloseMovie is not null && CloseMovie.IsValid();\r\n\r\n\tprivate MoviePlayer _player;\r\n\tprivate GameObject _vfx;\r\n\tprivate TimeSince _timeSinceAction = 99f;\r\n\tprivate TimeSince _timeSinceVfx = 99f;\r\n\tprivate bool _vfxShowing;\r\n\tprivate float _storedPosition;\r\n\tprivate bool _wasPlaying;\r\n\tprivate MovieResource _savedMovie;\r\n\tprivate float _savedPosition;\r\n\tprivate float _savedTimeScale = 1f;\r\n\tprivate bool _savedWasPlaying;\r\n\tprivate bool _hasSaved;\r\n\tprivate TimeSince _timeSinceHidden;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\t_player = GetComponent<MoviePlayer>();\r\n\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"BlastDoor on '{GameObject.Name}' needs a MoviePlayer on the same GameObject.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_player.CreateTargets = false;\r\n\t\tIsOpen = StartOpen;\r\n\t\t_timeSinceAction = 99f;\r\n\t\tResolveVfx();\r\n\t}\r\n\r\n\tpublic void Open()\r\n\t{\r\n\t\tPlay( OpenMovie, true );\r\n\t}\r\n\r\n\tpublic void Close()\r\n\t{\r\n\t\tPlay( CloseMovie, false );\r\n\t}\r\n\r\n\tpublic void Toggle()\r\n\t{\r\n\t\tif ( IsOpen )\r\n\t\t{\r\n\t\t\tClose();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tOpen();\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_savedMovie = _player.Resource as MovieResource;\r\n\t\tif ( !_savedMovie.IsValid() )\r\n\t\t{\r\n\t\t\t_savedMovie = IsOpen ? OpenMovie : CloseMovie;\r\n\t\t}\r\n\r\n\t\t_savedPosition = _player.PositionSeconds;\r\n\t\t_savedTimeScale = _player.TimeScale;\r\n\t\t_savedWasPlaying = _player.IsPlaying || _storedPosition > 0.05f;\r\n\t\t_timeSinceHidden = 0f;\r\n\t\t_hasSaved = true;\r\n\t}\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tif ( !_hasSaved )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_hasSaved = false;\r\n\r\n\t\tif ( !_savedWasPlaying || !_savedMovie.IsValid() || !_player.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfloat end = MovieGate.MovieDurationSeconds( _savedMovie );\r\n\t\tfloat target = AdvanceWhileHidden ? _savedPosition + (float)_timeSinceHidden * _savedTimeScale : _savedPosition;\r\n\t\ttarget = end > 0f ? Math.Clamp( target, 0f, Math.Max( 0f, end - 0.05f ) ) : 0f;\r\n\t\t_player.Play( _savedMovie );\r\n\t\t_player.TimeScale = _savedTimeScale;\r\n\t\t_player.PositionSeconds = target;\r\n\t\t_player.IsPlaying = true;\r\n\t\t_storedPosition = target;\r\n\t\t_wasPlaying = true;\r\n\t}\r\n\r\n\tprivate void Play( MovieResource movie, bool targetState )\r\n\t{\r\n\t\tif ( !GameObject.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"BlastDoor on '{GameObject.Name}' has no MoviePlayer.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( movie is null || !movie.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"BlastDoor on '{GameObject.Name}' is missing its {(targetState ? \"OpenMovie\" : \"CloseMovie\")}.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( IsOpen == targetState )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _timeSinceAction < ActionCooldown )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !AllowInterrupt && _player.IsPlaying )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_player.Play( movie );\r\n\t\t_timeSinceAction = 0f;\r\n\t\t_storedPosition = 0f;\r\n\t\tIsOpen = targetState;\r\n\t\tif ( targetState )\r\n\t\t{\r\n\t\t\tShowVfx();\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tTickVfx();\r\n\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _player.IsPlaying )\r\n\t\t{\r\n\t\t\t_storedPosition = _player.PositionSeconds;\r\n\t\t\t_wasPlaying = true;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_wasPlaying )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_wasPlaying = false;\r\n\t\tfloat pos = _player.PositionSeconds;\r\n\t\tfloat end = ClipEndSeconds();\r\n\t\tbool finished = end > 0f ? pos >= end - 0.05f : pos >= _storedPosition - 0.05f;\r\n\t\tif ( _storedPosition > 0.05f && !finished )\r\n\t\t{\r\n\t\t\t_player.PositionSeconds = _storedPosition;\r\n\t\t\t_player.IsPlaying = true;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_storedPosition = 0f;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate float ClipEndSeconds()\r\n\t{\r\n\t\tvar clip = _player.Clip;\r\n\t\tif ( clip is null )\r\n\t\t{\r\n\t\t\treturn 0f;\r\n\t\t}\r\n\r\n\t\treturn (float)clip.Duration.TotalSeconds;\r\n\t}\r\n\r\n\tprivate void ResolveVfx()\r\n\t{\r\n\t\t_vfx = VfxObject;\r\n\r\n\t\tif ( !_vfx.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"BlastDoor on '{GameObject.Name}' has no VfxObject assigned.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_vfx.Enabled = false;\r\n\t}\r\n\r\n\tprivate void ShowVfx()\r\n\t{\r\n\t\tif ( !_vfx.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_vfx.Enabled = true;\r\n\t\t_timeSinceVfx = 0f;\r\n\t\t_vfxShowing = true;\r\n\t}\r\n\r\n\tprivate void TickVfx()\r\n\t{\r\n\t\tif ( !_vfxShowing )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_vfx.IsValid() )\r\n\t\t{\r\n\t\t\t_vfxShowing = false;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _timeSinceVfx >= VfxSeconds )\r\n\t\t{\r\n\t\t\t_vfx.Enabled = false;\r\n\t\t\t_vfxShowing = false;\r\n\t\t}\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "Player/FirstPersonCosmetics.cs",
"FileName": "FirstPersonCosmetics.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "using System;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\n\r\nnamespace Causal;\r\n\r\npublic sealed class FirstPersonCosmetics : Component\r\n{\r\n\t[Property] public SkinnedModelRenderer ArmsRenderer { get; set; }\r\n\t[Property] public SkinnedModelRenderer ClothingRenderer { get; set; }\r\n\t[Property] public ulong ClothingBodyGroups { get; set; } = 1088;\r\n\t[Property] public List<string> ExcludedCategories { get; set; } = new() { \"Gloves\" };\r\n\t[Property] public List<string> RemovedClothingNames { get; set; } = new() { \"y_front_pants\" };\r\n\t[Property] public float CosmeticSettleTimeout { get; set; } = 30f;\r\n\r\n\tpublic static bool IsLoadingClothing { get; private set; }\r\n\r\n\tprivate const ulong BareBodyGroups = 21;\r\n\tprivate const ulong ClothedBodyGroups = 20;\r\n\tprivate int _generation;\r\n\tprivate CancellationTokenSource _cts;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tif ( !ArmsRenderer.IsValid() || !ClothingRenderer.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( \"FirstPersonCosmetics has no valid arms/clothing renderer, cosmetics disabled.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_cts = new CancellationTokenSource();\r\n\t\tint generation = ++_generation;\r\n\t\t_ = ApplyCosmeticsAsync( generation, _cts.Token );\r\n\t}\r\n\r\n\tprotected override void OnDestroy()\r\n\t{\r\n\t\t_generation++;\r\n\t\tif ( _cts is not null )\r\n\t\t{\r\n\t\t\t_cts.Cancel();\r\n\t\t\t_cts.Dispose();\r\n\t\t\t_cts = null;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate async Task ApplyCosmeticsAsync( int generation, CancellationToken token )\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar container = ClothingContainer.CreateFromLocalUser();\r\n\t\t\tif ( !IsCurrent( generation, token ) )\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tStripExcludedCategories( container );\r\n\r\n\t\t\tIsLoadingClothing = true;\r\n\r\n\t\t\tawait container.ApplyAsync( ClothingRenderer, token );\r\n\t\t\tif ( !IsCurrent( generation, token ) )\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tawait WaitForSettled( container, generation, token );\r\n\t\t\tif ( !IsCurrent( generation, token ) )\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tSetLoadingForGeneration( generation, false );\r\n\r\n\t\t\tif ( StripExcludedCategories( container ) > 0 )\r\n\t\t\t{\r\n\t\t\t\tcontainer.Apply( ClothingRenderer );\r\n\t\t\t}\r\n\r\n\t\t\tRemoveNamedClothingObjects();\r\n\t\t\tApplyClothingRenderOptions();\r\n\t\t\tApplyClothingBodyGroups();\r\n\t\t\tSetArmsBodyGroups( CountEntries( container ) > 0 ? ClothedBodyGroups : BareBodyGroups );\r\n\t\t}\r\n\t\tcatch ( OperationCanceledException )\r\n\t\t{\r\n\t\t\tSetLoadingForGeneration( generation, false );\r\n\t\t\tSetArmsBodyGroupsForGeneration( generation, BareBodyGroups );\r\n\t\t}\r\n\t\tcatch ( Exception exception )\r\n\t\t{\r\n\t\t\tSetLoadingForGeneration( generation, false );\r\n\t\t\tSetArmsBodyGroupsForGeneration( generation, BareBodyGroups );\r\n\t\t\tLog.Warning( $\"FirstPersonCosmetics failed: {exception.Message}\" );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate bool IsCurrent( int generation, CancellationToken token )\r\n\t{\r\n\t\treturn generation == _generation && !token.IsCancellationRequested;\r\n\t}\r\n\r\n\tprivate void SetArmsBodyGroupsForGeneration( int generation, ulong groups )\r\n\t{\r\n\t\tif ( generation == _generation )\r\n\t\t{\r\n\t\t\tSetArmsBodyGroups( groups );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void SetArmsBodyGroups( ulong groups )\r\n\t{\r\n\t\tif ( ArmsRenderer.IsValid() )\r\n\t\t{\r\n\t\t\tArmsRenderer.BodyGroups = groups;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ApplyClothingBodyGroups()\r\n\t{\r\n\t\tif ( ClothingRenderer.IsValid() )\r\n\t\t{\r\n\t\t\tClothingRenderer.BodyGroups = ClothingBodyGroups;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void SetLoadingForGeneration( int generation, bool loading )\r\n\t{\r\n\t\tif ( generation == _generation )\r\n\t\t{\r\n\t\t\tIsLoadingClothing = loading;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ApplyClothingRenderOptions()\r\n\t{\r\n\t\tif ( !ClothingRenderer.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tApplyClothingRenderOptions( ClothingRenderer.GameObject );\r\n\t}\r\n\r\n\tprivate static void ApplyClothingRenderOptions( GameObject root )\r\n\t{\r\n\t\tif ( !root.IsValid() || root.IsDestroyed )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar renderer = root.GetComponent<SkinnedModelRenderer>();\r\n\t\tif ( renderer.IsValid() )\r\n\t\t{\r\n\t\t\trenderer.RenderType = ModelRenderer.ShadowRenderType.Off;\r\n\t\t\tvar options = renderer.RenderOptions;\r\n\t\t\toptions.Game = false;\r\n\t\t\toptions.Overlay = true;\r\n\t\t\toptions.Bloom = false;\r\n\t\t\toptions.AfterUI = false;\r\n\t\t}\r\n\r\n\t\tforeach ( GameObject child in root.Children )\r\n\t\t{\r\n\t\t\tApplyClothingRenderOptions( child );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void RemoveNamedClothingObjects()\r\n\t{\r\n\t\tif ( RemovedClothingNames is null || RemovedClothingNames.Count == 0 )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !ClothingRenderer.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tint removed = 0;\r\n\t\tRemoveNamedClothingObjects( ClothingRenderer.GameObject, ref removed );\r\n\t\tif ( removed > 0 )\r\n\t\t{\r\n\t\t\tLog.Info( $\"FirstPersonCosmetics removed {removed} unwanted clothing objects.\" );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void RemoveNamedClothingObjects( GameObject root, ref int removed )\r\n\t{\r\n\t\tif ( !root.IsValid() || root.IsDestroyed )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tforeach ( GameObject child in root.Children )\r\n\t\t{\r\n\t\t\tif ( !child.IsValid() || child.IsDestroyed )\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ( IsRemovedClothingName( child.Name ) )\r\n\t\t\t{\r\n\t\t\t\tchild.Destroy();\r\n\t\t\t\tremoved++;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tRemoveNamedClothingObjects( child, ref removed );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate bool IsRemovedClothingName( string name )\r\n\t{\r\n\t\tif ( string.IsNullOrEmpty( name ) || RemovedClothingNames is null )\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tforeach ( string removed in RemovedClothingNames )\r\n\t\t{\r\n\t\t\tif ( !string.IsNullOrEmpty( removed ) && name.IndexOf( removed, StringComparison.OrdinalIgnoreCase ) >= 0 )\r\n\t\t\t{\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t}\r\n\r\n\tprivate static int CountEntries( ClothingContainer container )\r\n\t{\r\n\t\tif ( container?.Clothing is null )\r\n\t\t{\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tint count = 0;\r\n\t\tforeach ( var entry in container.Clothing )\r\n\t\t{\r\n\t\t\tif ( entry is not null )\r\n\t\t\t{\r\n\t\t\t\tcount++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn count;\r\n\t}\r\n\r\n\tprivate int StripExcludedCategories( ClothingContainer container )\r\n\t{\r\n\t\tif ( container?.Clothing is null || ExcludedCategories is null || ExcludedCategories.Count == 0 )\r\n\t\t{\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tint stripped = 0;\r\n\t\tvar seen = new List<string>();\r\n\t\tvar entries = new List<ClothingContainer.ClothingEntry>( container.Clothing );\r\n\t\tforeach ( var entry in entries )\r\n\t\t{\r\n\t\t\tstring category = entry?.Clothing?.Category.ToString();\r\n\t\t\tif ( string.IsNullOrEmpty( category ) )\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ( !seen.Contains( category ) )\r\n\t\t\t{\r\n\t\t\t\tseen.Add( category );\r\n\t\t\t}\r\n\r\n\t\t\tforeach ( string excluded in ExcludedCategories )\r\n\t\t\t{\r\n\t\t\t\tif ( string.Equals( category, excluded, StringComparison.OrdinalIgnoreCase ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tcontainer.Clothing.Remove( entry );\r\n\t\t\t\t\tstripped++;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( stripped > 0 )\r\n\t\t{\r\n\t\t\tLog.Info( $\"FirstPersonCosmetics excluded {stripped} clothing entries.\" );\r\n\t\t}\r\n\t\telse if ( entries.Count > 0 )\r\n\t\t{\r\n\t\t\tLog.Info( $\"FirstPersonCosmetics excluded nothing, categories present: {string.Join( \", \", seen )}\" );\r\n\t\t}\r\n\r\n\t\treturn stripped;\r\n\t}\r\n\r\n\tprivate async Task WaitForSettled( ClothingContainer container, int generation, CancellationToken token )\r\n\t{\r\n\t\tTimeSince elapsed = 0;\r\n\t\twhile ( HasPendingEntries( container ) )\r\n\t\t{\r\n\t\t\tif ( elapsed > CosmeticSettleTimeout )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"FirstPersonCosmetics timed out after {CosmeticSettleTimeout:0}s waiting for downloads, applying what is loaded.\" );\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tawait GameTask.DelaySeconds( 0.5f );\r\n\t\t\tif ( !IsCurrent( generation, token ) )\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static bool HasPendingEntries( ClothingContainer container )\r\n\t{\r\n\t\tif ( container?.Clothing is null )\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tforeach ( var entry in container.Clothing )\r\n\t\t{\r\n\t\t\tif ( entry is not null && entry.Clothing is null && entry.ItemDefinitionId != 0 )\r\n\t\t\t{\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "ui/causalmenu.razor.scss",
"FileName": "causalmenu.razor.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "CausalMenu {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpointer-events: all;\n\tbackground-color: rgba(0, 0, 0, 0.55);\n\tbackdrop-filter: blur(8px);\n\tz-index: 1;\n\t// Keep in sync with CausalGameManager.MenuFadeSeconds.\n\ttransition: opacity 0.6s ease;\n\n\t&.fading {\n\t\topacity: 0;\n\t\tpointer-events: none;\n\t}\n\n\t&.hidden {\n\t\tdisplay: none;\n\t\topacity: 0;\n\t\tpointer-events: none;\n\t}\n}\n\n.menu-container {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 64px;\n}\n\n.logo-section {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tanimation: menu-enter 0.7s ease both;\n}\n\n.logo-image {\n\twidth: 520px;\n\tflex-shrink: 0;\n\tfilter: drop-shadow(0 4px 24px rgba(255, 180, 80, 0.2));\n}\n\n.buttons-section {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 24px;\n\tanimation: menu-enter 0.7s ease 0.15s both;\n}\n\n.menu-item {\n\tcolor: rgba(255, 255, 255, 0.75);\n\tfont-size: 34px;\n\tfont-weight: 500;\n\tletter-spacing: 6px;\n\ttext-transform: uppercase;\n\ttext-align: center;\n\tcursor: pointer;\n\ttext-shadow: 0 0 8px rgba(120, 200, 255, 0.35);\n\ttransition: color 0.2s ease, transform 0.18s ease;\n\n\t&:hover {\n\t\tcolor: #fff;\n\t\ttext-shadow: 0 0 18px rgba(170, 220, 255, 0.95);\n\t\ttransform: scale(1.06);\n\t}\n\n\t&:active {\n\t\ttransform: scale(0.98);\n\t}\n}\n\n.menu-item.primary {\n\tanimation-name: menu-pulse;\n\tanimation-duration: 2s;\n\tanimation-timing-function: ease-in-out;\n\tanimation-iteration-count: infinite;\n}\n\n.newgame-row {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n}\n\n.menu-item.disabled {\n\tcolor: rgba(255, 255, 255, 0.25);\n\ttext-shadow: none;\n\tcursor: default;\n\tanimation: none;\n\n\t&:hover {\n\t\tcolor: rgba(255, 255, 255, 0.25);\n\t\ttext-shadow: none;\n\t\ttransform: none;\n\t}\n\n\t&:active {\n\t\ttransform: none;\n\t}\n}\n\n.avatar-status {\n\tposition: absolute;\n\tleft: 100%;\n\ttop: 0;\n\tbottom: 0;\n\tmargin-left: 24px;\n\tdisplay: flex;\n\talign-items: center;\n\tfont-size: 20px;\n\tletter-spacing: 2px;\n\tcolor: rgba(180, 230, 255, 0.8);\n\ttext-shadow: 0 0 8px rgba(120, 200, 255, 0.35);\n\twhite-space: nowrap;\n}\n\n@keyframes menu-enter {\n\t0% {\n\t\topacity: 0;\n\t\ttransform: translateY(14px);\n\t}\n\t100% {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n@keyframes menu-pulse {\n\t0% {\n\t\tcolor: rgba(255, 255, 255, 0.75);\n\t\ttext-shadow: 0 0 8px rgba(120, 200, 255, 0.4);\n\t}\n\t50% {\n\t\tcolor: #fff;\n\t\ttext-shadow: 0 0 20px rgba(170, 220, 255, 0.95);\n\t}\n\t100% {\n\t\tcolor: rgba(255, 255, 255, 0.75);\n\t\ttext-shadow: 0 0 8px rgba(120, 200, 255, 0.4);\n\t}\n}\n\n.about-link {\n\tposition: absolute;\n\ttop: 32px;\n\tright: 40px;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\theight: 48px;\n\tpadding-left: 0;\n\tpadding-right: 0;\n\tbackground-color: rgba(8, 18, 26, 0.55);\n\tbackdrop-filter: blur(4px);\n\tborder: 1px solid rgba(140, 210, 255, 0.35);\n\tborder-radius: 24px;\n\tbox-shadow: 0 0 16px rgba(120, 200, 255, 0.08);\n\tcolor: rgba(190, 225, 255, 0.8);\n\tcursor: pointer;\n\toverflow: hidden;\n\ttransition: border-color 0.2s ease, box-shadow 0.2s ease, padding-left 0.25s ease, padding-right 0.25s ease;\n\tz-index: 150;\n\tpointer-events: all;\n\n\t&:hover {\n\t\tborder-color: rgba(170, 220, 255, 0.9);\n\t\tbox-shadow: 0 0 24px rgba(140, 210, 255, 0.3);\n\t\tpadding-left: 4px;\n\t\tpadding-right: 20px;\n\t}\n\n\t&:active {\n\t\ttransform: scale(0.97);\n\t}\n}\n\n.about-glyph {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\twidth: 46px;\n\theight: 46px;\n\tfont-size: 22px;\n\tfont-weight: 600;\n\ttext-shadow: 0 0 8px rgba(120, 200, 255, 0.35);\n}\n\n.about-expand {\n\tmax-width: 0;\n\topacity: 0;\n\toverflow: hidden;\n\twhite-space: nowrap;\n\tcolor: #fff;\n\tfont-size: 17px;\n\tfont-weight: 500;\n\tletter-spacing: 3px;\n\ttext-transform: uppercase;\n\ttext-shadow: 0 0 14px rgba(170, 220, 255, 0.95);\n\ttransition: max-width 0.25s ease, opacity 0.2s ease;\n}\n\n.about-link:hover .about-expand {\n\tmax-width: 240px;\n\topacity: 1;\n}\n\n.about-backdrop {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 1);\n\topacity: 0;\n\tpointer-events: none;\n\tz-index: 200;\n\n\t&.open {\n\t\tpointer-events: all;\n\t}\n}\n\n.about-panel {\n\tposition: absolute;\n\ttop: 0;\n\tright: 0;\n\twidth: min(520px, 92vw);\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: linear-gradient(270deg, rgba(8, 18, 26, 0.97) 0%, rgba(8, 16, 24, 0.92) 100%);\n\tborder-left: 1px solid rgba(140, 210, 255, 0.5);\n\tbox-shadow: -8px 0 48px rgba(120, 200, 255, 0.12);\n\tpadding: 48px 40px 32px;\n\tz-index: 201;\n\tpointer-events: all;\n}\n\n.about-header {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tmargin-bottom: 0;\n}\n\n.about-title {\n\tcolor: rgba(190, 225, 255, 0.95);\n\tfont-size: 28px;\n\tfont-weight: 600;\n\tletter-spacing: 4px;\n\ttext-transform: uppercase;\n\ttext-shadow: 0 0 14px rgba(140, 210, 255, 0.6);\n}\n\n.about-close {\n\tcolor: rgba(190, 225, 255, 0.6);\n\tfont-size: 24px;\n\tcursor: pointer;\n\tpadding: 8px;\n\ttransition: color 0.2s ease, transform 0.18s ease;\n\n\t&:hover {\n\t\tcolor: #fff;\n\t\ttext-shadow: 0 0 14px rgba(170, 220, 255, 0.95);\n\t\ttransform: scale(1.1);\n\t}\n}\n\n.about-body {\n\tflex-shrink: 1;\n\tflex-grow: 1;\n\toverflow-y: auto;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: flex-start;\n\tgap: 26px;\n\tpadding-top: 8px;\n\tpadding-right: 12px;\n\n\tlabel {\n\t\tmargin: 0;\n\t}\n\n\t.about-para {\n\t\tcolor: rgba(220, 235, 245, 0.88);\n\t\tfont-size: 23px;\n\t\tline-height: 1.75;\n\t}\n\n\t.about-quote {\n\t\tcolor: rgba(190, 225, 255, 0.95);\n\t\tfont-style: italic;\n\t\tfont-size: 23px;\n\t\tborder-left: 2px solid rgba(140, 210, 255, 0.5);\n\t\tpadding-left: 16px;\n\t\ttext-shadow: 0 0 10px rgba(140, 210, 255, 0.4);\n\t}\n}\n\n.about-footer {\n\tmargin-top: 24px;\n\tpadding-top: 20px;\n\tborder-top: 1px solid rgba(140, 210, 255, 0.2);\n\tcolor: rgba(170, 210, 240, 0.5);\n\tfont-size: 15px;\n\tletter-spacing: 2px;\n\ttext-transform: uppercase;\n}\n"
},
{
"Ident": "fss.causal",
"Path": "UI/CausalMenu.razor",
"FileName": "CausalMenu.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "@using Sandbox;\r\n@using Sandbox.UI;\r\n@using System;\r\n@inherits PanelComponent\r\n@namespace Causal\r\n\r\n<root class=\"@MenuClass\">\r\n\t<div class=\"menu-container\">\r\n\t\t<div class=\"logo-section\">\r\n\t\t\t<img class=\"logo-image\" src=\"textures/causal-logo.png\" alt=\"CAUSAL\" />\r\n\t\t</div>\r\n\t\t<div class=\"buttons-section\">\r\n\t\t\t<div class=\"newgame-row\">\r\n\t\t\t\t<div class=\"menu-item primary @NewGameClass\" @onclick=@EnterGame onmouseover=@PlayHoverSound>\r\n\t\t\t\t\t<span>New Game</span>\r\n\t\t\t\t</div>\r\n\t\t\t\t@if ( IsApplyingClothing )\r\n\t\t\t\t{\r\n\t\t\t\t\t<label class=\"avatar-status\">Applying avatar clothing...</label>\r\n\t\t\t\t}\r\n\t\t\t</div>\r\n\t\t\t<div class=\"menu-item\" @onclick=@QuitGame onmouseover=@PlayHoverSound>\r\n\t\t\t\t<span>Quit</span>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n\t<div class=\"about-link\" @onclick=@ShowAbout onmouseover=@PlayHoverSound>\r\n\t\t<span class=\"about-glyph\">?</span>\r\n\t\t<span class=\"about-expand\">About Causal</span>\r\n\t</div>\r\n\t<div class=\"about-backdrop\" @ref=\"_aboutBackdrop\" @onclick=@HideAbout></div>\r\n\t<aside class=\"about-panel\" @ref=\"_aboutPanel\">\r\n\t\t<div class=\"about-header\">\r\n\t\t\t<div class=\"about-close\" @onclick=@HideAbout onmouseover=@PlayHoverSound>\r\n\t\t\t\t<span>✕</span>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t\t<div class=\"about-body\">\r\n\t\t\t<label class=\"about-para\">You have been chosen to be a Causal Agent.</label>\r\n\t\t\t<label class=\"about-para\">The Paralix Research Facility, a remote research station, has sent you there.</label>\r\n\t\t\t<label class=\"about-para\">The facility has been left abandoned with its systems becoming unstable since something went wrong, but the deeper you investigate the more you come to realize that the facility exists in more than one state.</label>\r\n\t\t\t<label class=\"about-para\">A construct located here is capable of slipgating time. In order to get to it, you will have to explore the facility, discover histories, and with some hope; to stop it from operating.</label>\r\n\t\t\t<label class=\"about-quote\">\"Playing with causality comes at a price...\"</label>\r\n\t\t</div>\r\n\t\t<div class=\"about-footer\">\r\n\t\t\t<span>Good luck!</span>\r\n\t\t</div>\r\n\t</aside>\r\n</root>\r\n\r\n@code\r\n{\r\n\t[Property] public float AboutSlideSeconds { get; set; } = 0.25f;\r\n\r\n\tprivate bool _showAbout;\r\n\tprivate bool _aboutClassesOpen;\r\n\tprivate float _slideT;\r\n\tprivate float _lastAppliedRight = float.MaxValue;\r\n\tprivate float _lastAppliedOpacity = -1f;\r\n\r\n\tprivate Panel _aboutBackdrop;\r\n\tprivate Panel _aboutPanel;\r\n\r\n\tprivate TimeSince _timeSinceHover = 10f;\r\n\r\n\tprivate bool IsMenuVisible => !CausalGameManager.Instance.IsValid() || !CausalGameManager.Instance.IsActive;\r\n\r\n\tprivate bool IsApplyingClothing => FirstPersonCosmetics.IsLoadingClothing;\r\n\r\n\tprivate string NewGameClass => IsApplyingClothing ? \"disabled\" : \"\";\r\n\r\n\tprivate bool IsFading => CausalGameManager.Instance.IsValid() && CausalGameManager.Instance.IsInIntro;\r\n\r\n\tprivate string MenuClass => !IsMenuVisible ? \"hidden\" : (IsFading ? \"fading\" : \"\");\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tTickAboutSlide();\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tTickAboutSlide();\r\n\r\n\t\tif ( _showAbout && Input.EscapePressed )\r\n\t\t{\r\n\t\t\tHideAbout();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void TickAboutSlide()\r\n\t{\r\n\t\tfloat duration = AboutSlideSeconds <= 0.01f ? 0.01f : AboutSlideSeconds;\r\n\t\t_slideT = MathX.Clamp( _slideT + ( _showAbout ? Time.Delta : -Time.Delta ) / duration, 0f, 1f );\r\n\t\tfloat eased = _slideT * _slideT * ( 3f - 2f * _slideT );\r\n\r\n\t\tif ( _aboutPanel.IsValid() )\r\n\t\t{\r\n\t\t\t// Percent of screen width: the panel can never exceed 92vw, so\r\n\t\t\t// -120% is always fully off-screen with room for the border and\r\n\t\t\t// shadow tail. No measurement, nothing to go stale on resize.\r\n\t\t\tfloat offset = -120f * ( 1f - eased );\r\n\t\t\tif ( MathF.Abs( offset - _lastAppliedRight ) > 0.1f )\r\n\t\t\t{\r\n\t\t\t\t_aboutPanel.Style.Right = Length.Percent( offset );\r\n\t\t\t\t_lastAppliedRight = offset;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( _aboutBackdrop.IsValid() )\r\n\t\t{\r\n\t\t\tfloat opacity = 0.45f * eased;\r\n\t\t\tif ( MathF.Abs( opacity - _lastAppliedOpacity ) > 0.005f )\r\n\t\t\t{\r\n\t\t\t\t_aboutBackdrop.Style.Opacity = opacity;\r\n\t\t\t\t_lastAppliedOpacity = opacity;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( _aboutClassesOpen != _showAbout )\r\n\t\t{\r\n\t\t\tApplyAboutClasses();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void EnterGame()\r\n\t{\r\n\t\tif ( IsApplyingClothing )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar manager = CausalGameManager.Instance;\r\n\t\tif ( !manager.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( \"CausalMenu found no CausalGameManager.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_showAbout = false;\r\n\t\tApplyAboutClasses();\r\n\t\tmanager.BeginIntro();\r\n\t\tStateHasChanged();\r\n\t}\r\n\r\n\tprivate void ShowAbout()\r\n\t{\r\n\t\t_showAbout = true;\r\n\t\tApplyAboutClasses();\r\n\t}\r\n\r\n\tprivate void HideAbout()\r\n\t{\r\n\t\tif ( !_showAbout )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_showAbout = false;\r\n\t\tApplyAboutClasses();\r\n\t}\r\n\r\n\tprivate void ApplyAboutClasses()\r\n\t{\r\n\t\t_aboutBackdrop?.SetClass( \"open\", _showAbout );\r\n\t\t_aboutClassesOpen = _showAbout;\r\n\t}\r\n\r\n\tprivate void QuitGame()\r\n\t{\r\n\t\tGame.Close();\r\n\t}\r\n\r\n\tprivate void PlayHoverSound()\r\n\t{\r\n\t\t// onmouseover refires as the cursor crosses child elements and panel\r\n\t\t// rebuilds, so debounce to a single tick per hover.\r\n\t\tif ( _timeSinceHover < 0.15f )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_timeSinceHover = 0f;\r\n\t\tSound.Play( \"audio/ui/menuhover.sound\" );\r\n\t}\r\n\r\n\tprotected override int BuildHash() => HashCode.Combine( IsMenuVisible, IsFading, IsApplyingClothing );\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "Player/ShiftController.cs",
"FileName": "ShiftController.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "using Sandbox.MovieMaker;\r\n\r\nnamespace Causal;\r\n\r\npublic sealed class ShiftController : Component\r\n{\r\n\t[Property] public MoviePlayer ShiftPlayer { get; set; }\r\n\t[Property] public float ShiftAtSeconds { get; set; } = 0.5f;\r\n\r\n\tprivate MoviePlayer _shiftPlayer;\r\n\tprivate TimeShiftManager _manager;\r\n\tprivate TimeSince _timeSinceShiftStart;\r\n\tprivate bool _shiftPending;\r\n\tprivate bool _wasPlaying;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\t_shiftPlayer = ShiftPlayer.IsValid() ? ShiftPlayer : GetComponentInChildren<MoviePlayer>();\r\n\t\t_manager = Game.ActiveScene?.GetSystem<TimeShiftManager>();\r\n\t\tif ( _shiftPlayer.IsValid() )\r\n\t\t{\r\n\t\t\tvar clip = _shiftPlayer.Clip;\r\n\t\t\tif ( clip is not null )\r\n\t\t\t{\r\n\t\t\t\t_ = clip.Duration;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( Input.Pressed( \"timeshift\" ) )\r\n\t\t{\r\n\t\t\tTryBeginShift();\r\n\t\t}\r\n\r\n\t\tPollShiftTrigger();\r\n\t}\r\n\r\n\tprivate void TryBeginShift()\r\n\t{\r\n\t\tif ( CausalGameManager.Instance.IsValid() && !CausalGameManager.Instance.IsActive )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar manager = GetManager();\r\n\t\tif ( manager is not null && !manager.ShiftUnlocked )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_shiftPlayer.IsValid() )\r\n\t\t{\r\n\t\t\tmanager?.RequestShift();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _shiftPlayer.IsPlaying )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_shiftPlayer.Play();\r\n\t\t_timeSinceShiftStart = 0;\r\n\t\t_shiftPending = true;\r\n\t}\r\n\r\n\tprivate void PollShiftTrigger()\r\n\t{\r\n\t\tif ( !_shiftPlayer.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tbool playing = _shiftPlayer.IsPlaying;\r\n\r\n\t\tif ( !_shiftPending )\r\n\t\t{\r\n\t\t\t_wasPlaying = playing;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _timeSinceShiftStart >= ShiftAtSeconds )\r\n\t\t{\r\n\t\t\tFireShift();\r\n\t\t}\r\n\t\telse if ( _wasPlaying && !playing )\r\n\t\t{\r\n\t\t\tFireShift();\r\n\t\t}\r\n\r\n\t\t_wasPlaying = playing;\r\n\t}\r\n\r\n\tprivate void FireShift()\r\n\t{\r\n\t\t_shiftPending = false;\r\n\t\tGetManager()?.RequestShift();\r\n\t}\r\n\r\n\tprivate TimeShiftManager GetManager()\r\n\t{\r\n\t\tif ( _manager is not null )\r\n\t\t{\r\n\t\t\treturn _manager;\r\n\t\t}\r\n\r\n\t\t_manager = Game.ActiveScene?.GetSystem<TimeShiftManager>();\r\n\t\treturn _manager;\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "World/MovieGate.cs",
"FileName": "MovieGate.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "using System;\r\nusing Sandbox.MovieMaker;\r\n\r\nnamespace Causal;\r\n\r\npublic sealed class MovieGate : Component\r\n{\r\n\tpublic enum HandoffMode\r\n\t{\r\n\t\tAbsolute,\r\n\t\tProportional,\r\n\t\tRestart\r\n\t}\r\n\r\n\t[Property] public MoviePlayer Player { get; set; }\r\n\t[Property] public MovieResource BaseMovie { get; set; }\r\n\t[Property] public MovieResource FailMovie { get; set; }\r\n\t[Property] public HandoffMode Handoff { get; set; } = HandoffMode.Absolute;\r\n\t[Property] public float FailOffset { get; set; }\r\n\t[Property] public float FailCheckFromSeconds { get; set; } = 1f;\r\n\t[Property] public float FailCheckToSeconds { get; set; } = 3f;\r\n\t[Property] public bool AllowInterrupt { get; set; } = true;\r\n\t[Property] public float ActionCooldown { get; set; } = 2f;\r\n\t[Property] public bool AdvanceWhileHidden { get; set; } = true;\r\n\r\n\tpublic bool IsPlaying => _player.IsValid() && _player.IsPlaying;\r\n\tpublic bool HasBranched => _hasBranched;\r\n\r\n\tprivate MoviePlayer _player;\r\n\tprivate IMovieCondition _condition;\r\n\tprivate TimeSince _timeSinceAction = 99f;\r\n\tprivate TimeSince _timeSinceHidden;\r\n\tprivate float _storedPosition;\r\n\tprivate bool _wasPlaying;\r\n\tprivate bool _hasBranched;\r\n\tprivate MovieResource _savedMovie;\r\n\tprivate float _savedPosition;\r\n\tprivate float _savedTimeScale = 1f;\r\n\tprivate bool _savedWasPlaying;\r\n\tprivate bool _hasSaved;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tResolvePlayer();\r\n\t\tResolveCondition();\r\n\t}\r\n\r\n\tpublic void PlayBase()\r\n\t{\r\n\t\tif ( !GameObject.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"MovieGate on '{GameObject.Name}' has no MoviePlayer.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( BaseMovie is null || !BaseMovie.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"MovieGate on '{GameObject.Name}' is missing its BaseMovie.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _timeSinceAction < ActionCooldown )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !AllowInterrupt && _player.IsPlaying )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tMovieResource selected = BaseMovie;\r\n\t\tif ( FailMovie.IsValid() && HasFailed() )\r\n\t\t{\r\n\t\t\tselected = FailMovie;\r\n\t\t}\r\n\r\n\t\tPlayAt( selected, 0f, true, _player.TimeScale );\r\n\t\t_hasBranched = selected == FailMovie;\r\n\t\t_timeSinceAction = 0f;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( _player.IsPlaying )\r\n\t\t{\r\n\t\t\t_storedPosition = _player.PositionSeconds;\r\n\t\t\t_wasPlaying = true;\r\n\t\t\tTickBranch();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_wasPlaying )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_wasPlaying = false;\r\n\t\tfloat pos = _player.PositionSeconds;\r\n\t\tfloat end = ClipDurationSeconds( _player.Clip );\r\n\t\tbool finished = end > 0f ? pos >= end - 0.05f : pos >= _storedPosition - 0.05f;\r\n\t\tif ( _storedPosition > 0.05f && !finished )\r\n\t\t{\r\n\t\t\t_player.PositionSeconds = _storedPosition;\r\n\t\t\t_player.IsPlaying = true;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_storedPosition = 0f;\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_savedMovie = CurrentMovie();\r\n\t\t_savedPosition = _player.PositionSeconds;\r\n\t\t_savedTimeScale = _player.TimeScale;\r\n\t\t_savedWasPlaying = _player.IsPlaying || _storedPosition > 0.05f;\r\n\t\t_timeSinceHidden = 0f;\r\n\t\t_hasSaved = true;\r\n\t}\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tif ( !_hasSaved )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_hasSaved = false;\r\n\r\n\t\tResolvePlayer();\r\n\r\n\t\tif ( !_savedWasPlaying || !_savedMovie.IsValid() || !_player.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfloat end = MovieDurationSeconds( _savedMovie );\r\n\t\tfloat target = AdvanceWhileHidden ? _savedPosition + (float)_timeSinceHidden * _savedTimeScale : _savedPosition;\r\n\t\ttarget = end > 0f ? Math.Clamp( target, 0f, Math.Max( 0f, end - 0.05f ) ) : 0f;\r\n\t\tPlayAt( _savedMovie, target, true, _savedTimeScale );\r\n\t}\r\n\r\n\tprivate void TickBranch()\r\n\t{\r\n\t\tif ( _hasBranched || !FailMovie.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfloat pos = _storedPosition;\r\n\t\tif ( pos < FailCheckFromSeconds || pos > FailCheckToSeconds )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !HasFailed() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfloat target = MapPosition( pos, ClipDurationSeconds( _player.Clip ), MovieDurationSeconds( FailMovie ), Handoff, FailOffset );\r\n\t\tPlayAt( FailMovie, target, true, _player.TimeScale );\r\n\t\t_hasBranched = true;\r\n\t\t_timeSinceAction = 0f;\r\n\t}\r\n\r\n\tprivate void PlayAt( MovieResource movie, float position, bool resumePlaying, float timeScale )\r\n\t{\r\n\t\tif ( !_player.IsValid() || !movie.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_player.Play( movie );\r\n\t\t_player.TimeScale = timeScale;\r\n\t\t_player.PositionSeconds = position;\r\n\t\t_player.IsPlaying = resumePlaying;\r\n\t\t_storedPosition = resumePlaying ? position : 0f;\r\n\t\t_wasPlaying = resumePlaying;\r\n\t}\r\n\r\n\tprivate bool HasFailed()\r\n\t{\r\n\t\treturn _condition is not null && _condition.HasFailed();\r\n\t}\r\n\r\n\tprivate MovieResource CurrentMovie()\r\n\t{\r\n\t\tvar resource = _player.Resource as MovieResource;\r\n\t\tif ( resource.IsValid() )\r\n\t\t{\r\n\t\t\treturn resource;\r\n\t\t}\r\n\r\n\t\treturn _hasBranched ? FailMovie : BaseMovie;\r\n\t}\r\n\r\n\tprivate void ResolvePlayer()\r\n\t{\r\n\t\t_player = Player.IsValid() ? Player : GetComponent<MoviePlayer>();\r\n\r\n\t\tif ( !_player.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"MovieGate on '{GameObject.Name}' needs a MoviePlayer on the same GameObject.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_player.CreateTargets = false;\r\n\t}\r\n\r\n\tprivate void ResolveCondition()\r\n\t{\r\n\t\t_condition = null;\r\n\r\n\t\tforeach ( var component in Components.GetAll() )\r\n\t\t{\r\n\t\t\tif ( component is IMovieCondition condition )\r\n\t\t\t{\r\n\t\t\t\t_condition = condition;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( _condition is null )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"MovieGate on '{GameObject.Name}' found no IMovieCondition.\" );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static float MapPosition( float basePosition, float baseDuration, float failDuration, HandoffMode mode, float offset )\r\n\t{\r\n\t\tfloat mapped = mode switch\r\n\t\t{\r\n\t\t\tHandoffMode.Absolute => basePosition + offset,\r\n\t\t\tHandoffMode.Proportional => baseDuration > 0.001f ? basePosition / baseDuration * failDuration : 0f,\r\n\t\t\t_ => 0f,\r\n\t\t};\r\n\r\n\t\treturn failDuration > 0f ? Math.Clamp( mapped, 0f, Math.Max( 0f, failDuration - 0.05f ) ) : 0f;\r\n\t}\r\n\r\n\tprivate static float ClipDurationSeconds( IMovieClip clip )\r\n\t{\r\n\t\tif ( clip is null )\r\n\t\t{\r\n\t\t\treturn 0f;\r\n\t\t}\r\n\r\n\t\treturn (float)clip.Duration.TotalSeconds;\r\n\t}\r\n\r\n\tinternal static float MovieDurationSeconds( MovieResource movie )\r\n\t{\r\n\t\tif ( !movie.IsValid() || movie.Compiled is null )\r\n\t\t{\r\n\t\t\treturn 0f;\r\n\t\t}\r\n\r\n\t\treturn (float)movie.Compiled.Duration.TotalSeconds;\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": ".obj/__compiler_extra.cs",
"FileName": "__compiler_extra.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"Causal\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"causal\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"fss\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"fss.causal\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"29\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v9.0\", FrameworkDisplayName = \".NET 9.0\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"2026-09-16T20:39:46.7421903Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.115.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.115.0\")]"
},
{
"Ident": "fss.causal",
"Path": "Game/CausalGameManager.cs",
"FileName": "CausalGameManager.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "namespace Causal;\r\n\r\npublic sealed class CausalGameManager : Component\r\n{\r\n\t[Property] public GameObject Player { get; set; }\r\n\t[Property] public float MenuFadeSeconds { get; set; } = 0.6f;\r\n\t[Property] public float CameraFlightSeconds { get; set; } = 2.2f;\r\n\r\n\tpublic static CausalGameManager Instance { get; private set; }\r\n\r\n\tpublic GameState State { get; private set; } = GameState.Menu;\r\n\r\n\tpublic bool IsActive => State == GameState.Active;\r\n\tpublic bool IsInIntro => State == GameState.Intro;\r\n\r\n\tprivate PlayerController _controller;\r\n\tprivate TimeSince _timeSinceIntro;\r\n\tprivate Vector3 _flightFromPosition;\r\n\tprivate Rotation _flightFromRotation;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tInstance = this;\r\n\t}\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tResolvePlayer();\r\n\t\tEnterMenu();\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( State != GameState.Intro )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tTickIntro();\r\n\t}\r\n\r\n\tprotected override void OnDestroy()\r\n\t{\r\n\t\tif ( Instance == this )\r\n\t\t{\r\n\t\t\tInstance = null;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void EnterMenu()\r\n\t{\r\n\t\tState = GameState.Menu;\r\n\t\tApplyInputState();\r\n\t}\r\n\r\n\tpublic void BeginIntro()\r\n\t{\r\n\t\tif ( State != GameState.Menu )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !_controller.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"CausalGameManager on '{GameObject.Name}' found no PlayerController.\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( !Scene.Camera.IsValid() )\r\n\t\t{\r\n\t\t\tEnterGame();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_flightFromPosition = Scene.Camera.WorldPosition;\r\n\t\t_flightFromRotation = Scene.Camera.WorldRotation;\r\n\t\t_timeSinceIntro = 0;\r\n\t\tState = GameState.Intro;\r\n\t\tApplyInputState();\r\n\t}\r\n\r\n\tpublic void EnterGame()\r\n\t{\r\n\t\tif ( State == GameState.Active )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tState = GameState.Active;\r\n\t\tApplyInputState();\r\n\t}\r\n\r\n\tprivate void TickIntro()\r\n\t{\r\n\t\tif ( !_controller.IsValid() || !Scene.Camera.IsValid() )\r\n\t\t{\r\n\t\t\tEnterGame();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfloat flightDuration = CameraFlightSeconds <= 0.01f ? 0.01f : CameraFlightSeconds;\r\n\t\tfloat flightTime = _timeSinceIntro - MenuFadeSeconds;\r\n\t\tfloat t = flightTime / flightDuration;\r\n\t\tif ( t < 0f )\r\n\t\t{\r\n\t\t\tt = 0f;\r\n\t\t}\r\n\t\tif ( t > 1f )\r\n\t\t{\r\n\t\t\tt = 1f;\r\n\t\t}\r\n\r\n\t\tfloat eased = t * t * (3f - 2f * t);\r\n\r\n\t\tTransform target = _controller.EyeTransform;\r\n\t\tScene.Camera.WorldPosition = Vector3.Lerp( _flightFromPosition, target.Position, eased );\r\n\t\tScene.Camera.WorldRotation = Rotation.Slerp( _flightFromRotation, target.Rotation, eased );\r\n\r\n\t\tif ( t >= 1f )\r\n\t\t{\r\n\t\t\tEnterGame();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ResolvePlayer()\r\n\t{\r\n\t\tif ( Player.IsValid() )\r\n\t\t{\r\n\t\t\t_controller = Player.GetComponent<PlayerController>();\r\n\t\t}\r\n\r\n\t\tif ( !_controller.IsValid() )\r\n\t\t{\r\n\t\t\tforeach ( var controller in Scene.GetAllComponents<PlayerController>() )\r\n\t\t\t{\r\n\t\t\t\t_controller = controller;\r\n\t\t\t\tPlayer = controller.GameObject;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !_controller.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"CausalGameManager on '{GameObject.Name}' found no PlayerController.\" );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ApplyInputState()\r\n\t{\r\n\t\tif ( !_controller.IsValid() )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tbool active = State == GameState.Active;\r\n\t\t_controller.WishVelocity = 0;\r\n\t\t_controller.UseInputControls = active;\r\n\t\t_controller.UseCameraControls = active;\r\n\t\t_controller.UseLookControls = active;\r\n\t}\r\n\r\n\tpublic enum GameState\r\n\t{\r\n\t\tMenu,\r\n\t\tIntro,\r\n\t\tActive\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "Player/CausalMoveModeWalk.cs",
"FileName": "CausalMoveModeWalk.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "using System;\r\nusing Sandbox.Movement;\r\n\r\nnamespace Causal;\r\n\r\n[Icon( \"directions_walk\" )]\r\n[Group( \"Movement\" )]\r\n[Title( \"MoveMode - Walk-C\" )]\r\n[Description( \"Walk and sprint with strafe and backpedal speed penalties\" )]\r\npublic sealed class CausalMoveModeWalk : MoveModeWalk\r\n{\r\n\t[Property] public float SideSpeedMultiplier { get; set; } = 0.65f;\r\n\t[Property] public float BackwardSpeedMultiplier { get; set; } = 0.65f;\r\n\r\n\tprivate Vector3.SmoothDamped _smoothedMovement;\r\n\r\n\tpublic override int Score( PlayerController controller )\r\n\t{\r\n\t\treturn base.Score( controller ) + 1;\r\n\t}\r\n\r\n\tpublic override Vector3 UpdateMove( Rotation eyes, Vector3 input )\r\n\t{\r\n\t\teyes = eyes.Angles() with { pitch = 0 };\r\n\r\n\t\tinput = input.ClampLength( 1 );\r\n\r\n\t\tvar direction = eyes * input;\r\n\t\tvar velocity = GetBalancedSpeed( input );\r\n\r\n\t\tif ( direction.IsNearlyZero( 0.1f ) )\r\n\t\t{\r\n\t\t\tdirection = 0;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_smoothedMovement.Current = direction.Normal * _smoothedMovement.Current.Length;\r\n\t\t}\r\n\r\n\t\t_smoothedMovement.Target = direction * velocity;\r\n\t\t_smoothedMovement.SmoothTime = _smoothedMovement.Target.Length < _smoothedMovement.Current.Length\r\n\t\t\t? Controller.DeaccelerationTime\r\n\t\t\t: Controller.AccelerationTime;\r\n\t\t_smoothedMovement.Update( Time.Delta );\r\n\r\n\t\tif ( _smoothedMovement.Current.IsNearlyZero( 0.01f ) )\r\n\t\t{\r\n\t\t\t_smoothedMovement.Current = 0;\r\n\t\t}\r\n\r\n\t\treturn _smoothedMovement.Current;\r\n\t}\r\n\r\n\tprivate float GetBalancedSpeed( Vector3 input )\r\n\t{\r\n\t\tvar run = Input.Down( Controller.AltMoveButton );\r\n\r\n\t\tif ( Controller.RunByDefault )\r\n\t\t{\r\n\t\t\trun = !run;\r\n\t\t}\r\n\r\n\t\tvar velocity = run ? Controller.RunSpeed : Controller.WalkSpeed;\r\n\r\n\t\tif ( Controller.IsDucking )\r\n\t\t{\r\n\t\t\tvelocity = Controller.DuckedSpeed;\r\n\t\t}\r\n\r\n\t\treturn velocity * GetDirectionSpeedMultiplier( input );\r\n\t}\r\n\r\n\tprivate float GetDirectionSpeedMultiplier( Vector3 input )\r\n\t{\r\n\t\tvar speedMultiplier = 1f;\r\n\r\n\t\tif ( input.x < 0f )\r\n\t\t{\r\n\t\t\tspeedMultiplier *= BackwardSpeedMultiplier;\r\n\t\t}\r\n\r\n\t\tif ( MathF.Abs( input.y ) > 0f )\r\n\t\t{\r\n\t\t\tspeedMultiplier *= MathX.Lerp( 1f, SideSpeedMultiplier, MathF.Abs( input.y ) );\r\n\t\t}\r\n\r\n\t\treturn speedMultiplier;\r\n\t}\r\n}\r\n"
},
{
"Ident": "fss.causal",
"Path": "World/DoorButton.cs",
"FileName": "DoorButton.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 381941,
"Code": "namespace Causal;\r\n\r\npublic sealed class DoorButton : Component, Component.IPressable\r\n{\r\n\t[Property] public GameObject DoorObject { get; set; }\r\n\r\n\tprivate HighlightOutline _highlight;\r\n\tprivate BlastDoor _door;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tComponent visual = (Component)GetComponent<ModelRenderer>() ?? GetComponent<MeshComponent>();\r\n\t\tif ( !visual.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"DoorButton on '{GameObject.Name}' found no ModelRenderer or MeshComponent.\" );\r\n\t\t}\r\n\r\n\t\tif ( !DoorObject.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"DoorButton on '{GameObject.Name}' has no door GameObject assigned.\" );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_door = DoorObject.GetComponent<BlastDoor>() ?? DoorObject.GetComponentInChildren<BlastDoor>();\r\n\t\t\tif ( !_door.IsValid() )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"DoorButton on '{GameObject.Name}' found no BlastDoor on '{DoorObject.Name}' or its children.\" );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_highlight = GetComponent<HighlightOutline>();\r\n\t\tif ( !_highlight.IsValid() )\r\n\t\t{\r\n\t\t\tLog.Warning( $\"DoorButton on '{GameObject.Name}' needs a HighlightOutline for hover feedback.\" );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_highlight.Enabled = false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic bool CanPress( Component.IPressable.Event e )\r\n\t{\r\n\t\treturn _door.IsValid() && (!_door.IsOpen || _door.CanClose) && (_door.AllowInterrupt || !_door.IsAnimating);\r\n\t}\r\n\r\n\tpublic bool Press( Component.IPressable.Event e )\r\n\t{\r\n\t\tif ( !CanPress( e ) )\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t_door.Toggle();\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic bool Pressing( Component.IPressable.Event e )\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic void Release( Component.IPressable.Event e )\r\n\t{\r\n\t}\r\n\r\n\tpublic void Hover( Component.IPressable.Event e )\r\n\t{\r\n\t\tif ( _highlight.IsValid() )\r\n\t\t{\r\n\t\t\t_highlight.Enabled = true;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Look( Component.IPressable.Event e )\r\n\t{\r\n\t}\r\n\r\n\tpublic void Blur( Component.IPressable.Event e )\r\n\t{\r\n\t\tif ( _highlight.IsValid() )\r\n\t\t{\r\n\t\t\t_highlight.Enabled = false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic Component.IPressable.Tooltip? GetTooltip( Component.IPressable.Event e )\r\n\t{\r\n\t\tif ( !_door.IsValid() )\r\n\t\t{\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tstring title = _door.IsOpen && _door.CanClose ? \"Close\" : \"Open\";\r\n\t\treturn new Component.IPressable.Tooltip( title, \"\", \"\", true, this );\r\n\t}\r\n}\r\n"
}
]
}