Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DayNight Cycle #240

Merged
merged 3 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Content.Server/Shuttles/Systems/ArrivalsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Content.Server.Spawners.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared._NC14.DayNightCycle;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.DeviceNetwork;
Expand Down Expand Up @@ -485,6 +486,12 @@ private void SetupArrivalsStation()
Range = 32f
};
AddComp(mapUid, restricted);
var dayNight = new DayNightCycleComponent
{
CurrentCycleTime = 0.5f,
CycleDurationMinutes = 60,
};
AddComp(mapUid, dayNight);
}

_mapManager.DoMapInitialize(mapId);
Expand Down
50 changes: 50 additions & 0 deletions Content.Shared/_NC/DayNightCycle/DayNightCycleComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;

namespace Content.Shared._NC14.DayNightCycle
{
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class DayNightCycleComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("cycleDuration")]
public float CycleDurationMinutes { get; set; } = 60f; // Default cycle duration is 60 minutes

[DataField("timeEntries")]
public List<TimeEntry> TimeEntries { get; set; } = new()
{
new() { Time = 0.00f, ColorHex = "#000000" }, // Midnight
new() { Time = 0.04f, ColorHex = "#02020b" }, // Very early morning
new() { Time = 0.08f, ColorHex = "#312716" }, // Early dawn
new() { Time = 0.17f, ColorHex = "#4E3D23" }, // Dawn
new() { Time = 0.25f, ColorHex = "#58372D" }, // Sunrise
new() { Time = 0.33f, ColorHex = "#876A42" }, // Early morning
new() { Time = 0.42f, ColorHex = "#A08042" }, // Mid-morning
new() { Time = 0.50f, ColorHex = "#A88F73" }, // Noon
new() { Time = 0.58f, ColorHex = "#C1A78A" }, // Early afternoon
new() { Time = 0.67f, ColorHex = "#7D6244" }, // Late afternoon
new() { Time = 0.75f, ColorHex = "#8C6130" }, // Sunset
new() { Time = 0.83f, ColorHex = "#543521" }, // Dusk
new() { Time = 0.92f, ColorHex = "#02020b" }, // Early night
new() { Time = 1.00f, ColorHex = "#000000" } // Back to Midnight
};

[ViewVariables(VVAccess.ReadWrite)]
[AutoNetworkedField]
public int CurrentTimeEntryIndex { get; set; }

[ViewVariables(VVAccess.ReadWrite)]
[AutoNetworkedField]
public float CurrentCycleTime { get; set; }
}

[DataDefinition, NetSerializable, Serializable]
public sealed partial class TimeEntry
{
[DataField("colorHex")]
public string ColorHex { get; set; } = "#FFFFFF";

[DataField("time")]
public float Time { get; set; } // Normalized time (0-1)
}
}
117 changes: 117 additions & 0 deletions Content.Shared/_NC/DayNightCycle/DayNightCycleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System.Linq;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;

namespace Content.Shared._NC14.DayNightCycle
{
public sealed class DayNightCycleSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;

private const float EARLY_MORNING_TIME = 0.2f; // This represents 20% into the cycle, which is early morning

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DayNightCycleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DayNightCycleComponent, ComponentStartup>(OnComponentStartup);
}

private void OnMapInit(EntityUid uid, DayNightCycleComponent component, MapInitEvent args)
{
if (component.TimeEntries.Count < 2)
{
// Default Fallout-inspired color cycle with more variants
component.TimeEntries = new List<TimeEntry>
{
new() { Time = 0.00f, ColorHex = "#000000" }, // Midnight
new() { Time = 0.04f, ColorHex = "#02020b" }, // Very early morning
new() { Time = 0.08f, ColorHex = "#312716" }, // Early dawn
new() { Time = 0.17f, ColorHex = "#4E3D23" }, // Dawn
new() { Time = 0.25f, ColorHex = "#58372D" }, // Sunrise
new() { Time = 0.33f, ColorHex = "#876A42" }, // Early morning
new() { Time = 0.42f, ColorHex = "#A08042" }, // Mid-morning
new() { Time = 0.50f, ColorHex = "#A88F73" }, // Noon
new() { Time = 0.58f, ColorHex = "#C1A78A" }, // Early afternoon
new() { Time = 0.67f, ColorHex = "#7D6244" }, // Late afternoon
new() { Time = 0.75f, ColorHex = "#8C6130" }, // Sunset
new() { Time = 0.83f, ColorHex = "#543521" }, // Dusk
new() { Time = 0.92f, ColorHex = "#02020b" }, // Early night
new() { Time = 1.00f, ColorHex = "#000000" } // Back to Midnight
};
}

InitializeEarlyMorning(component);
}

private void OnComponentStartup(EntityUid uid, DayNightCycleComponent component, ComponentStartup args)
{
InitializeEarlyMorning(component);
}

private void InitializeEarlyMorning(DayNightCycleComponent component)
{
component.CurrentCycleTime = EARLY_MORNING_TIME;
UpdateLightColor(component);
}

public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<DayNightCycleComponent, MapLightComponent>();
while (query.MoveNext(out var uid, out var dayNight, out var mapLight))
{
dayNight.CurrentCycleTime += frameTime / (dayNight.CycleDurationMinutes * 60f);
dayNight.CurrentCycleTime %= 1f; // Keep it between 0 and 1

UpdateLightColor(dayNight, mapLight, uid);
}
}

private void UpdateLightColor(DayNightCycleComponent dayNight, MapLightComponent? mapLight = null, EntityUid? uid = null)
{
var color = GetInterpolatedColor(dayNight);

if (mapLight != null && uid.HasValue)
{
mapLight.AmbientLightColor = color;
Dirty(uid.Value, mapLight);
Dirty(uid.Value, dayNight);
}
}

private Color GetInterpolatedColor(DayNightCycleComponent component)
{
var entries = component.TimeEntries;
var time = component.CurrentCycleTime;

for (int i = 0; i < entries.Count - 1; i++)
{
if (time >= entries[i].Time && time <= entries[i + 1].Time)
{
var t = (time - entries[i].Time) / (entries[i + 1].Time - entries[i].Time);
return InterpolateHexColors(entries[i].ColorHex, entries[i + 1].ColorHex, t);
}
}

// If we're here, we're between the last and first entry
var lastEntry = entries.Last();
var firstEntry = entries.First();
var wrappedT = (time - lastEntry.Time) / (1f + firstEntry.Time - lastEntry.Time);
return InterpolateHexColors(lastEntry.ColorHex, firstEntry.ColorHex, wrappedT);
}

private Color InterpolateHexColors(string hexColor1, string hexColor2, float t)
{
Color color1 = Color.FromHex(hexColor1);
Color color2 = Color.FromHex(hexColor2);

float r = color1.R + (color2.R - color1.R) * t;
float g = color1.G + (color2.G - color1.G) * t;
float b = color1.B + (color2.B - color1.B) * t;

return new Color(r, g, b);
}
}
}
3 changes: 3 additions & 0 deletions Resources/Maps/glacier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ entities:
- uid: 1
components:
- type: MetaData
- type: MapLight
ambientLightColor: '#030305FF'
- type: DayNightCycle
- type: Transform
- type: Map
mapPaused: True
Expand Down
Loading