diff --git a/Content.Server/_CP14/DayCycle/CP14AddTimeEntryCommand.cs b/Content.Server/_CP14/DayCycle/CP14AddTimeEntryCommand.cs new file mode 100644 index 0000000000..9111f46334 --- /dev/null +++ b/Content.Server/_CP14/DayCycle/CP14AddTimeEntryCommand.cs @@ -0,0 +1,79 @@ +using Content.Server.Administration; +using Content.Shared._CP14.DayCycle; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server._CP14.DayCycle; + +[AdminCommand(AdminFlags.VarEdit)] +public sealed class CP14AddTimeEntryCommand : LocalizedCommands +{ + private const string Name = "add_time_entry"; + private const int ArgumentCount = 4; + + public override string Command => Name; + public override string Description => "Allows you to add a new time entry to the map list"; + public override string Help => $"{Name} "; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != ArgumentCount) + { + shell.WriteError($"{Loc.GetString("shell-wrong-arguments-number")}\n{Help}"); + return; + } + + if (!NetEntity.TryParse(args[0], out var netEntity)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var entityManager = IoCManager.Resolve(); + var dayCycleSystem = entityManager.System(); + var entity = entityManager.GetEntity(netEntity); + + if (!entityManager.TryGetComponent(entity, out var dayCycle)) + { + shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component", ("uid", entity), ("componentName", nameof(CP14DayCycleComponent)))); + return; + } + + if (!Color.TryParse(args[1], out var color)) + { + shell.WriteError(Loc.GetString("parse-color-fail", ("args", args[1]))); + return; + } + + if (!float.TryParse(args[2], out var duration)) + { + shell.WriteError(Loc.GetString("parse-float-fail", ("args", args[2]))); + return; + } + + if (!bool.TryParse(args[3], out var isNight)) + { + shell.WriteError(Loc.GetString("parse-bool-fail", ("args", args[3]))); + return; + } + + var entry = new DayCycleEntry + { + Color = color, + Duration = TimeSpan.FromSeconds(duration), + IsNight = isNight + }; + + dayCycleSystem.AddTimeEntry((entity, dayCycle), entry); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + return args.Length switch + { + 1 => CompletionResult.FromOptions(CompletionHelper.Components(args[0])), + 4 => CompletionResult.FromOptions(CompletionHelper.Booleans), + _ => CompletionResult.Empty, + }; + } +} diff --git a/Content.Server/_CP14/DayCycle/CP14InitDayCycleCommand.cs b/Content.Server/_CP14/DayCycle/CP14InitDayCycleCommand.cs new file mode 100644 index 0000000000..31ce7c4ada --- /dev/null +++ b/Content.Server/_CP14/DayCycle/CP14InitDayCycleCommand.cs @@ -0,0 +1,60 @@ +using Content.Server.Administration; +using Content.Shared._CP14.DayCycle; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server._CP14.DayCycle; + +[AdminCommand(AdminFlags.VarEdit)] +public sealed class CP14InitDayCycleCommand : LocalizedCommands +{ + private const string Name = "init_day_cycle"; + private const int ArgumentCount = 1; + + public override string Command => Name; + public override string Description => + "Re-initializes the day and night system, but reset the current time entry stage"; + public override string Help => $"{Name} "; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != ArgumentCount) + { + shell.WriteError($"{Loc.GetString("shell-wrong-arguments-number")}\n{Help}"); + return; + } + + if (!NetEntity.TryParse(args[0], out var netEntity)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var entityManager = IoCManager.Resolve(); + var dayCycleSystem = entityManager.System(); + var entity = entityManager.GetEntity(netEntity); + + if (!entityManager.TryGetComponent(entity, out var dayCycle)) + { + shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component", ("uid", entity), ("componentName", nameof(CP14DayCycleComponent)))); + return; + } + + if (dayCycle.TimeEntries.Count < CP14DayCycleSystem.MinTimeEntryCount) + { + shell.WriteError($"Attempting to init a daily cycle with the number of time entries less than {CP14DayCycleSystem.MinTimeEntryCount}"); + return; + } + + dayCycleSystem.Init((entity, dayCycle)); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + return args.Length switch + { + 1 => CompletionResult.FromOptions(CompletionHelper.Components(args[0])), + _ => CompletionResult.Empty, + }; + } +} diff --git a/Content.Server/_CP14/DayCycle/CP14SetTimeEntryCommand.cs b/Content.Server/_CP14/DayCycle/CP14SetTimeEntryCommand.cs new file mode 100644 index 0000000000..255fa270fb --- /dev/null +++ b/Content.Server/_CP14/DayCycle/CP14SetTimeEntryCommand.cs @@ -0,0 +1,81 @@ +using Content.Server.Administration; +using Content.Shared._CP14.DayCycle; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server._CP14.DayCycle; + +[AdminCommand(AdminFlags.VarEdit)] +public sealed class CP14SetTimeEntryCommand : LocalizedCommands +{ + private const string Name = "set_time_entry"; + private const int ArgumentCount = 2; + + public override string Command => Name; + public override string Description => "Sets a new entry at the specified index"; + public override string Help => $"{Name} "; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != ArgumentCount) + { + shell.WriteError($"{Loc.GetString("shell-wrong-arguments-number")}\n{Help}"); + return; + } + + if (!NetEntity.TryParse(args[0], out var netEntity)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var entityManager = IoCManager.Resolve(); + var dayCycleSystem = entityManager.System(); + var entity = entityManager.GetEntity(netEntity); + + if (!entityManager.TryGetComponent(entity, out var dayCycle)) + { + shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component", ("uid", entity), ("componentName", nameof(CP14DayCycleComponent)))); + return; + } + + if (!int.TryParse(args[1], out var timeEntry)) + { + shell.WriteError(Loc.GetString("parse-int-fail", ("args", args[1]))); + return; + } + + dayCycleSystem.SetTimeEntry((entity, dayCycle), timeEntry); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + var entityManager = IoCManager.Resolve(); + + switch (args.Length) + { + case 1: + return CompletionResult.FromOptions(CompletionHelper.Components(args[0], entityManager)); + + case 2: + if (!NetEntity.TryParse(args[0], out var mapUid)) + return CompletionResult.Empty; + + if (!entityManager.TryGetComponent(entityManager.GetEntity(mapUid), out var component)) + return CompletionResult.Empty; + + if (component.TimeEntries.Count - 1 < 0) + return CompletionResult.Empty; + + var indices = new string[component.TimeEntries.Count - 1]; + for (var i = 0; i < indices.Length; i++) + { + indices[i] = i.ToString(); + } + + return CompletionResult.FromOptions(indices); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Shared/_CP14/DayCycle/CP14DayCycleComponent.cs b/Content.Shared/_CP14/DayCycle/CP14DayCycleComponent.cs new file mode 100644 index 0000000000..df7838667a --- /dev/null +++ b/Content.Shared/_CP14/DayCycle/CP14DayCycleComponent.cs @@ -0,0 +1,73 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared._CP14.DayCycle; + +/// +/// Stores all the necessary data for the day and night cycle system to work +/// + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(CP14DayCycleSystem))] +public sealed partial class CP14DayCycleComponent : Component +{ + [ViewVariables] + public int NextTimeEntryIndex => CurrentTimeEntryIndex + 1 >= TimeEntries.Count ? 0 : CurrentTimeEntryIndex + 1; + + [ViewVariables] + public DayCycleEntry CurrentTimeEntry => TimeEntries[CurrentTimeEntryIndex]; + + [ViewVariables] + public DayCycleEntry NextCurrentTimeEntry => TimeEntries[NextTimeEntryIndex]; + + [ViewVariables] + public Color StartColor => CurrentTimeEntry.Color; + + [ViewVariables] + public Color EndColor => NextCurrentTimeEntry.Color; + + [DataField(required: true), ViewVariables, AutoNetworkedField] + public List TimeEntries = new(); + + [DataField, ViewVariables, AutoNetworkedField] + public bool IsNight; // TODO: Rewrite this shit + + [DataField, ViewVariables, AutoNetworkedField] + public int CurrentTimeEntryIndex; + + [DataField, ViewVariables, AutoNetworkedField] + public TimeSpan EntryStartTime; + + [DataField, ViewVariables, AutoNetworkedField] + public TimeSpan EntryEndTime; +} + +[DataDefinition, NetSerializable, Serializable] +public readonly partial record struct DayCycleEntry() +{ + /// + /// The color of the world's lights at the beginning of this time of day + /// + [DataField] + public Color Color { get; init; } = Color.White; + + /// + /// Duration of color shift to the next time of day + /// + [DataField] + public TimeSpan Duration { get; init; } = TimeSpan.FromSeconds(60); + + [DataField] + public bool IsNight { get; init; } = false; +} + +/// +/// Event raised on map entity, wen night is started +/// +[ByRefEvent] +public readonly record struct DayCycleNightStartedEvent(EntityUid Map); + +/// +/// Event raised on map entity, wen night is started +/// +[ByRefEvent] +public readonly record struct DayCycleDayStartedEvent(EntityUid Map); diff --git a/Content.Shared/_CP14/DayCycle/CP14DayCycleSystem.cs b/Content.Shared/_CP14/DayCycle/CP14DayCycleSystem.cs new file mode 100644 index 0000000000..4d7cd39b6d --- /dev/null +++ b/Content.Shared/_CP14/DayCycle/CP14DayCycleSystem.cs @@ -0,0 +1,135 @@ +using Robust.Shared.Map.Components; +using Robust.Shared.Timing; + +namespace Content.Shared._CP14.DayCycle; + +public sealed partial class CP14DayCycleSystem : EntitySystem +{ + public const int MinTimeEntryCount = 2; + private const float MaxTimeDiff = 0.05f; + + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInitDayCycle); + SubscribeLocalEvent(OnDayStarted); + SubscribeLocalEvent(OnNightStarted); + } + + private void OnDayStarted(Entity dayCycle, ref DayCycleDayStartedEvent args) + { + } + + private void OnNightStarted(Entity dayCycle, ref DayCycleNightStartedEvent args) + { + } + + private void OnMapInitDayCycle(Entity dayCycle, ref MapInitEvent args) + { + Init(dayCycle); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var dayCycleQuery = EntityQueryEnumerator(); + while (dayCycleQuery.MoveNext(out var uid, out var dayCycle, out var mapLight)) + { + var entity = new Entity(uid, dayCycle, mapLight); + + if (dayCycle.TimeEntries.Count < MinTimeEntryCount) + continue; + + SetAmbientColor((entity, entity), GetCurrentColor(entity, _timing.CurTime.TotalSeconds)); + + if (_timing.CurTime <= dayCycle.EntryEndTime) + continue; + + SetTimeEntry((uid, dayCycle), dayCycle.NextTimeEntryIndex); + } + } + + public void Init(Entity dayCycle) + { + if (dayCycle.Comp.TimeEntries.Count < MinTimeEntryCount) + { + Log.Warning($"Attempting to init a daily cycle with the number of time entries less than {MinTimeEntryCount}"); + return; + } + + dayCycle.Comp.CurrentTimeEntryIndex = 0; + dayCycle.Comp.EntryStartTime = _timing.CurTime; + dayCycle.Comp.EntryEndTime = _timing.CurTime + dayCycle.Comp.CurrentTimeEntry.Duration; + + Dirty(dayCycle); + } + + public void AddTimeEntry(Entity dayCycle, DayCycleEntry entry) + { + dayCycle.Comp.TimeEntries.Add(entry); + Dirty(dayCycle); + } + + public void SetTimeEntry(Entity dayCycle, int nextEntry) + { + nextEntry = Math.Clamp(nextEntry, 0, dayCycle.Comp.TimeEntries.Count - 1); + + dayCycle.Comp.CurrentTimeEntryIndex = nextEntry; + dayCycle.Comp.EntryStartTime = dayCycle.Comp.EntryEndTime; + dayCycle.Comp.EntryEndTime += dayCycle.Comp.CurrentTimeEntry.Duration; + + // TODO: Made with states,we might need an evening or something, and besides, it's too much hardcore + if (dayCycle.Comp.IsNight && !dayCycle.Comp.CurrentTimeEntry.IsNight) // Day started + { + dayCycle.Comp.IsNight = false; + + var ev = new DayCycleDayStartedEvent(dayCycle); + RaiseLocalEvent(dayCycle, ref ev, true); + } + + if (!dayCycle.Comp.IsNight && dayCycle.Comp.CurrentTimeEntry.IsNight) // Night started + { + dayCycle.Comp.IsNight = true; + + var ev = new DayCycleNightStartedEvent(dayCycle); + RaiseLocalEvent(dayCycle, ref ev, true); + } + + Dirty(dayCycle); + } + + private void SetAmbientColor(Entity light, Color color) + { + if (color == light.Comp.AmbientLightColor) + return; + + light.Comp.AmbientLightColor = color; + Dirty(light); + } + + private Color GetCurrentColor(Entity dayCycle, double totalSeconds) + { + var timeScale = GetTimeScale(dayCycle, totalSeconds); + return Color.InterpolateBetween(dayCycle.Comp.StartColor, dayCycle.Comp.EndColor, timeScale); + } + + private float GetTimeScale(Entity dayCycle, double totalSeconds) + { + return GetLerpValue(dayCycle.Comp.EntryStartTime.TotalSeconds, dayCycle.Comp.EntryEndTime.TotalSeconds, totalSeconds); + } + + private static float GetLerpValue(double start, double end, double current) + { + if (Math.Abs(start - end) < MaxTimeDiff) + return 0f; + + var distanceFromStart = current - start; + var totalDistance = end - start; + + return MathHelper.Clamp01((float)(distanceFromStart / totalDistance)); + } +} diff --git a/Content.Shared/_CP14/DayCycle/DayCycleComponent.cs b/Content.Shared/_CP14/DayCycle/DayCycleComponent.cs deleted file mode 100644 index 8203f24bee..0000000000 --- a/Content.Shared/_CP14/DayCycle/DayCycleComponent.cs +++ /dev/null @@ -1,58 +0,0 @@ - -using Robust.Shared.Serialization; - -namespace Content.Shared._CP14.DayCycle; - -/// -/// Stores all the necessary data for the day and night cycle system to work -/// - -[RegisterComponent, Access(typeof(DayCycleSystem))] -public sealed partial class DayCycleComponent : Component -{ - [DataField(required: true)] - public List TimeEntries = new(); - - [DataField] - public bool IsNight = false; - - [DataField] - public int CurrentTimeEntry = 0; - - [DataField] - public TimeSpan EntryStartTime; - - [DataField] - public TimeSpan EntryEndTime; -} - -[DataDefinition, NetSerializable, Serializable] -public readonly partial record struct DayCycleEntry() -{ - /// - /// the color of the world's lights at the beginning of this time of day - /// - [DataField] - public Color StartColor { get; init; } = Color.White; - - /// - /// duration of color shift to the next time of day - /// - [DataField] - public TimeSpan Duration { get; init; } = TimeSpan.FromSeconds(60); - - [DataField] - public bool IsNight { get; init; } = false; -} - -/// -/// Event raised on map entity, wen night is started -/// -[ByRefEvent] -public readonly record struct DayCycleNightStartedEvent(EntityUid Map); - -/// -/// Event raised on map entity, wen night is started -/// -[ByRefEvent] -public readonly record struct DayCycleDayStartedEvent(EntityUid Map); diff --git a/Content.Shared/_CP14/DayCycle/DayCycleSystem.cs b/Content.Shared/_CP14/DayCycle/DayCycleSystem.cs deleted file mode 100644 index f1ca774d8a..0000000000 --- a/Content.Shared/_CP14/DayCycle/DayCycleSystem.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Robust.Shared.Map.Components; -using Robust.Shared.Timing; - -namespace Content.Shared._CP14.DayCycle; -public sealed partial class DayCycleSystem : EntitySystem -{ - - [Dependency] private readonly IGameTiming _timing = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInitDayCycle); - SubscribeLocalEvent(OnDayStarted); - SubscribeLocalEvent(OnNightStarted); - } - - private void OnDayStarted(Entity dayCycle, ref DayCycleDayStartedEvent args) - { - } - - private void OnNightStarted(Entity dayCycle, ref DayCycleNightStartedEvent args) - { - } - - private void OnMapInitDayCycle(Entity dayCycle, ref MapInitEvent args) - { - if (dayCycle.Comp.TimeEntries.Count == 0) - return; - - var currentEntry = dayCycle.Comp.TimeEntries[0]; - - dayCycle.Comp.EntryStartTime = _timing.CurTime; - dayCycle.Comp.EntryEndTime = _timing.CurTime + currentEntry.Duration; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var dayCycleQuery = EntityQueryEnumerator(); - while (dayCycleQuery.MoveNext(out var uid, out var dayCycle, out var mapLight)) - { - if (dayCycle.TimeEntries.Count <= 1) - continue; - - var curEntry = dayCycle.CurrentTimeEntry; - var nextEntry = (curEntry + 1 >= dayCycle.TimeEntries.Count) ? 0 : (curEntry + 1); - - var start = dayCycle.EntryStartTime; - var end = dayCycle.EntryEndTime; - - var lerpValue = GetLerpValue((float) start.TotalSeconds, (float) end.TotalSeconds, (float) _timing.CurTime.TotalSeconds); - - var startColor = dayCycle.TimeEntries[curEntry].StartColor; - var endColor = dayCycle.TimeEntries[nextEntry].StartColor; - - mapLight.AmbientLightColor = Color.InterpolateBetween(startColor, endColor, lerpValue); - Dirty(uid, mapLight); - - - if (_timing.CurTime > dayCycle.EntryEndTime) - { - dayCycle.CurrentTimeEntry = nextEntry; - dayCycle.EntryStartTime = dayCycle.EntryEndTime; - dayCycle.EntryEndTime += dayCycle.TimeEntries[nextEntry].Duration; - - if (dayCycle.IsNight && !dayCycle.TimeEntries[curEntry].IsNight) // Day started - { - dayCycle.IsNight = false; - var ev = new DayCycleDayStartedEvent(uid); - RaiseLocalEvent(uid, ref ev, true); - } - if (!dayCycle.IsNight && dayCycle.TimeEntries[curEntry].IsNight) // Night started - { - dayCycle.IsNight = true; - var ev = new DayCycleNightStartedEvent(uid); - RaiseLocalEvent(uid, ref ev, true); - } - } - } - } - - public static float GetLerpValue(float start, float end, float current) - { - if (Math.Abs(start - end) < 0.05f) - return 0f; - - var distanceFromStart = current - start; - var totalDistance = end - start; - - return MathHelper.Clamp01(distanceFromStart / totalDistance); - } -} diff --git a/Resources/Maps/_CP14/alchemy_test.yml b/Resources/Maps/_CP14/alchemy_test.yml index e5881afbf6..1b0cba398f 100644 --- a/Resources/Maps/_CP14/alchemy_test.yml +++ b/Resources/Maps/_CP14/alchemy_test.yml @@ -31,25 +31,28 @@ entities: - type: OccluderTree - type: LoadedMap - type: MapLight - - type: DayCycle + - type: CP14DayCycle timeEntries: - - duration: 30 - startColor: '#754A4AFF' - - duration: 30 - startColor: '#E0BA87FF' - - duration: 30 - startColor: '#BFEEFFFF' + - duration: 80 + color: '#754A4AFF' + - duration: 80 + color: '#E0BA87FF' + - duration: 80 + color: '#BFEEFFFF' - isNight: True - duration: 30 - startColor: '#385163FF' + duration: 80 + color: '#385163FF' - isNight: True - duration: 30 - startColor: '#060D12FF' + duration: 80 + color: '#060D12FF' - isNight: True - duration: 90 - startColor: '#000000FF' - - duration: 30 - startColor: '#120906FF' + duration: 80 + color: '#000000FF' + - isNight: True + duration: 80 + color: '#000000FF' + - duration: 80 + color: '#120906FF' - uid: 2 components: - type: MetaData diff --git a/Resources/Maps/_CP14/cave-arena.yml b/Resources/Maps/_CP14/cave-arena.yml index 6d9189c216..6044e7b53b 100644 --- a/Resources/Maps/_CP14/cave-arena.yml +++ b/Resources/Maps/_CP14/cave-arena.yml @@ -20,7 +20,7 @@ entities: - type: MovedGrids - type: Broadphase - type: MapLight - - type: DayCycle + - type: CP14DayCycle timeEntries: - duration: 30 startColor: '#0A2136FF' diff --git a/Resources/Maps/_CP14/dev_map.yml b/Resources/Maps/_CP14/dev_map.yml index 394789a50e..1301d92b97 100644 --- a/Resources/Maps/_CP14/dev_map.yml +++ b/Resources/Maps/_CP14/dev_map.yml @@ -391,27 +391,27 @@ entities: - type: GridTree - type: MovedGrids - type: MapLight - - type: DayCycle + - type: CP14DayCycle timeEntries: - - startColor: "#754a4a" #Рассвет + - color: "#754a4a" #Рассвет duration: 30 isNight: false - - startColor: "#e0ba87" #Полдень + - color: "#e0ba87" #Полдень duration: 30 isNight: false - - startColor: "#bfeeff" #Полдень + - color: "#bfeeff" #Полдень duration: 30 isNight: false - - startColor: "#385163" #Вечер + - color: "#385163" #Вечер duration: 30 isNight: true - - startColor: "#060d12" #Ночь + - color: "#060d12" #Ночь duration: 30 isNight: true - - startColor: "#000000" #Ночь + - color: "#000000" #Ночь duration: 30 isNight: true - - startColor: "#120906" #Ночь + - color: "#120906" #Ночь duration: 30 isNight: false - proto: AirAlarm