Line endings: LF everywhere

.gitattributes forces LF (CRLF only for .bat/.cmd), core.autocrlf is off;
files that differed only by CRLF are normalized.
This commit is contained in:
cthangover committed 2026-10-21 13:29:55 +02:00
1 parent bd7b472dff
commit 8d3f7445f3
6 files changed
+884 -854

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
* text=auto eol=lf
# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf
# Binaries: never converted, never diffed as text.
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
*.svg text
*.ogg binary
*.wav binary
*.mp3 binary
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary
*.dll binary
*.so binary
*.dylib binary
*.pdb binary
*.exe binary
*.zip binary
*.pck binary
*.moc3 binary
*.res binary
*.scn binary
+37 -37
View File
@@ -1,37 +1,37 @@
# Godot 4.x
.godot/
.idea/
sdk/
bin/
obj/
#ai
.omo/
.opencode/
gen/
obj/
bin/
*.tres~
*.tscn~
# GDScript LSP
.godot/
# Export
*.exe
*.dmg
*.apk
*.aab
*.pck
*.zip
*.log
# OS
.DS_Store
Thumbs.db
# Editor
*.import
*.godot.uid
# Godot 4.x
.godot/
.idea/
sdk/
bin/
obj/
#ai
.omo/
.opencode/
gen/
obj/
bin/
*.tres~
*.tscn~
# GDScript LSP
.godot/
# Export
*.exe
*.dmg
*.apk
*.aab
*.pck
*.zip
*.log
# OS
.DS_Store
Thumbs.db
# Editor
*.import
*.godot.uid
+146 -146
View File
@@ -1,147 +1,147 @@
using Cthangover.Core.Scenes;
using Cthangover.Core.Settings;
using Cthangover.Core.UI.Tool;
using Cthangover.Core.Utils;
using Godot;
using Cthangover.Core.Scenes;
using Cthangover.Core.Settings;
using Cthangover.Core.UI.Tool;
using Cthangover.Core.Utils;
using Godot;
using Cthangover.Mods.Interface.Tool;
namespace Cthangover.Mods.Time
{
/// <summary>
/// Draggable lamp UI element for the player's portable light source.
/// Input handling exists in both _GuiInput (bubbled) and _Input (global)
/// to catch drags that start on the lamp but continue outside its rect.
/// When the lamp is hidden, it resets position to offscreen (-1000,-1000)
/// to disable the light in the shader. GetLightParams() is a static helper
/// so the light controller can query lamp parameters without coupling to
/// the behaviour instance. Inherits from TextureRectModIconLoader for
/// mod-provided lamp icon textures.
/// </summary>
public partial class LampBehaviour : TextureRectModIconLoader
{
private UiLightController controller;
private Vector2 dragPosition;
private bool isPlaced;
private bool pressingOnLamp;
/// <summary>
/// Queries lamp parameters (color, radius, influence) from
/// <see cref="GameData.Instance.Runtime.LampData"/>. Static so the
/// <see cref="UiLightController"/> can query without a direct reference.
/// </summary>
public static (Color, float, float) GetLightParams()
{
var lampData = GameData.Instance.Runtime.LampData;
var radius = lampData.Radius;
var influence = lampData.Influence;
return (Colors.Yellow, radius, influence);
}
/// <summary>
/// Visibility toggle for the lamp. Setting to <c>false</c> resets the
/// placed state and sends the light position offscreen to disable
/// the lamp light in the shader. Uses <c>new</c> to log transitions.
/// </summary>
public new bool Visible
{
get => base.Visible;
set
{
if (base.Visible != value)
GameLogger.Log("LIGHT", $"Lamp.Visible: {base.Visible} -> {value}, isPlaced={isPlaced}", LogLevel.Debug);
base.Visible = value;
if (!value)
{
pressingOnLamp = false;
if (isPlaced)
{
isPlaced = false;
controller?.UpdateLightPos(new Vector2(-1000f, -1000f));
}
}
}
}
/// <summary>Finds the shared <see cref="UiLightController"/> this lamp pushes positions to.</summary>
public override void _Ready()
{
base._Ready();
MouseFilter = MouseFilterEnum.Stop;
controller = SceneContextNode.FindNode<UiLightController>("Lights");
GameLogger.Log("LIGHT", $"Lamp._Ready: controller={(controller != null ? "OK" : "NULL")}", LogLevel.Debug);
}
/// <summary>Click to place the light at the cursor, drag to move it, and click a placed lamp to pick it back up.</summary>
public override void _GuiInput(InputEvent @event)
{
if (@event is InputEventMouseButton mouseButton)
{
if (mouseButton.ButtonIndex == MouseButton.Left)
{
if (mouseButton.Pressed)
{
pressingOnLamp = true;
dragPosition = GetGlobalMousePosition();
if (isPlaced)
{
dragPosition = new Vector2(-1000f, -1000f);
controller?.UpdateLightPos(dragPosition);
isPlaced = false;
}
}
else
{
if (pressingOnLamp && !isPlaced)
{
isPlaced = true;
controller?.UpdateLightPos(dragPosition);
}
pressingOnLamp = false;
}
}
AcceptEvent();
}
else if (@event is InputEventMouseMotion mouseMotion)
{
if (Input.IsMouseButtonPressed(MouseButton.Left))
{
dragPosition = GetGlobalMousePosition();
controller?.UpdateLightPos(dragPosition);
isPlaced = false;
AcceptEvent();
}
}
}
/// <summary>Keeps dragging the placed light while the pointer moves outside the lamp's own control rect.</summary>
public override void _Input(InputEvent @event)
{
if (!Visible)
return;
if (@event is InputEventMouseMotion mouseMotion
&& Input.IsMouseButtonPressed(MouseButton.Left)
&& pressingOnLamp)
{
dragPosition = GetGlobalMousePosition();
controller?.UpdateLightPos(dragPosition);
isPlaced = false;
}
if (@event is InputEventMouseButton mouseButton
&& mouseButton.ButtonIndex == MouseButton.Left
&& !mouseButton.Pressed
&& pressingOnLamp)
{
if (!isPlaced)
{
isPlaced = true;
controller?.UpdateLightPos(dragPosition);
}
pressingOnLamp = false;
}
}
}
}
namespace Cthangover.Mods.Time
{
/// <summary>
/// Draggable lamp UI element for the player's portable light source.
/// Input handling exists in both _GuiInput (bubbled) and _Input (global)
/// to catch drags that start on the lamp but continue outside its rect.
/// When the lamp is hidden, it resets position to offscreen (-1000,-1000)
/// to disable the light in the shader. GetLightParams() is a static helper
/// so the light controller can query lamp parameters without coupling to
/// the behaviour instance. Inherits from TextureRectModIconLoader for
/// mod-provided lamp icon textures.
/// </summary>
public partial class LampBehaviour : TextureRectModIconLoader
{
private UiLightController controller;
private Vector2 dragPosition;
private bool isPlaced;
private bool pressingOnLamp;
/// <summary>
/// Queries lamp parameters (color, radius, influence) from
/// <see cref="GameData.Instance.Runtime.LampData"/>. Static so the
/// <see cref="UiLightController"/> can query without a direct reference.
/// </summary>
public static (Color, float, float) GetLightParams()
{
var lampData = GameData.Instance.Runtime.LampData;
var radius = lampData.Radius;
var influence = lampData.Influence;
return (Colors.Yellow, radius, influence);
}
/// <summary>
/// Visibility toggle for the lamp. Setting to <c>false</c> resets the
/// placed state and sends the light position offscreen to disable
/// the lamp light in the shader. Uses <c>new</c> to log transitions.
/// </summary>
public new bool Visible
{
get => base.Visible;
set
{
if (base.Visible != value)
GameLogger.Log("LIGHT", $"Lamp.Visible: {base.Visible} -> {value}, isPlaced={isPlaced}", LogLevel.Debug);
base.Visible = value;
if (!value)
{
pressingOnLamp = false;
if (isPlaced)
{
isPlaced = false;
controller?.UpdateLightPos(new Vector2(-1000f, -1000f));
}
}
}
}
/// <summary>Finds the shared <see cref="UiLightController"/> this lamp pushes positions to.</summary>
public override void _Ready()
{
base._Ready();
MouseFilter = MouseFilterEnum.Stop;
controller = SceneContextNode.FindNode<UiLightController>("Lights");
GameLogger.Log("LIGHT", $"Lamp._Ready: controller={(controller != null ? "OK" : "NULL")}", LogLevel.Debug);
}
/// <summary>Click to place the light at the cursor, drag to move it, and click a placed lamp to pick it back up.</summary>
public override void _GuiInput(InputEvent @event)
{
if (@event is InputEventMouseButton mouseButton)
{
if (mouseButton.ButtonIndex == MouseButton.Left)
{
if (mouseButton.Pressed)
{
pressingOnLamp = true;
dragPosition = GetGlobalMousePosition();
if (isPlaced)
{
dragPosition = new Vector2(-1000f, -1000f);
controller?.UpdateLightPos(dragPosition);
isPlaced = false;
}
}
else
{
if (pressingOnLamp && !isPlaced)
{
isPlaced = true;
controller?.UpdateLightPos(dragPosition);
}
pressingOnLamp = false;
}
}
AcceptEvent();
}
else if (@event is InputEventMouseMotion mouseMotion)
{
if (Input.IsMouseButtonPressed(MouseButton.Left))
{
dragPosition = GetGlobalMousePosition();
controller?.UpdateLightPos(dragPosition);
isPlaced = false;
AcceptEvent();
}
}
}
/// <summary>Keeps dragging the placed light while the pointer moves outside the lamp's own control rect.</summary>
public override void _Input(InputEvent @event)
{
if (!Visible)
return;
if (@event is InputEventMouseMotion mouseMotion
&& Input.IsMouseButtonPressed(MouseButton.Left)
&& pressingOnLamp)
{
dragPosition = GetGlobalMousePosition();
controller?.UpdateLightPos(dragPosition);
isPlaced = false;
}
if (@event is InputEventMouseButton mouseButton
&& mouseButton.ButtonIndex == MouseButton.Left
&& !mouseButton.Pressed
&& pressingOnLamp)
{
if (!isPlaced)
{
isPlaced = true;
controller?.UpdateLightPos(dragPosition);
}
pressingOnLamp = false;
}
}
}
}
+79 -79
View File
@@ -1,79 +1,79 @@
using System.Text.Json.Serialization;
using Godot;
namespace Cthangover.Mods.Time
{
/// <summary>
/// Serializable light definition for JSON scene lighting. Coordinates are
/// viewport-relative (0.0–1.0) so scenes adapt to different resolutions.
/// ToPixelPos/FromPixelPos convert between normalized space and pixel space
/// using a reference viewport size. Color is stored as a hex string for
/// human-readable JSON editing.
/// </summary>
public class LightDef
{
/// <summary>
/// Normalized horizontal position (0.0–1.0) relative to viewport width.
/// </summary>
[JsonPropertyName("x")]
public float X { get; set; }
/// <summary>
/// Normalized vertical position (0.0–1.0) relative to viewport height.
/// </summary>
[JsonPropertyName("y")]
public float Y { get; set; }
/// <summary>
/// Light radius in pixel units. Determines the falloff distance in the
/// lighting shader.
/// </summary>
[JsonPropertyName("radius")]
public float Radius { get; set; }
/// <summary>
/// Light intensity (0.0–1.0). At <c>1.0</c>, the light fully brightens
/// pixels within its radius. Lower values create subtler effects.
/// </summary>
[JsonPropertyName("influence")]
public float Influence { get; set; } = 1f;
/// <summary>
/// Light color as a hex string (e.g. "#ff8800"). Parsed by
/// <see cref="ToColor"/> for shader upload.
/// </summary>
[JsonPropertyName("color")]
public string ColorHex { get; set; } = "#ffff00";
/// <summary>
/// Converts <see cref="ColorHex"/> to a Godot <c>Color</c> struct.
/// </summary>
public Color ToColor()
{
return new Color(ColorHex);
}
/// <summary>
/// Converts normalized coordinates (<see cref="X"/>, <see cref="Y"/>) to
/// pixel coordinates based on <paramref name="viewportSize"/>.
/// </summary>
/// <param name="viewportSize">Current viewport dimensions in pixels.</param>
public Vector2 ToPixelPos(Vector2 viewportSize)
{
return new Vector2(X * viewportSize.X, Y * viewportSize.Y);
}
/// <summary>
/// Computes normalized coordinates from pixel coordinates by dividing by
/// <paramref name="referenceSize"/>. Enables saving light positions
/// across different resolutions.
/// </summary>
/// <param name="pixelPos">Light position in pixel space.</param>
/// <param name="referenceSize">Viewport dimensions used as reference.</param>
public void FromPixelPos(Vector2 pixelPos, Vector2 referenceSize)
{
X = pixelPos.X / referenceSize.X;
Y = pixelPos.Y / referenceSize.Y;
}
}
}
using System.Text.Json.Serialization;
using Godot;
namespace Cthangover.Mods.Time
{
/// <summary>
/// Serializable light definition for JSON scene lighting. Coordinates are
/// viewport-relative (0.0–1.0) so scenes adapt to different resolutions.
/// ToPixelPos/FromPixelPos convert between normalized space and pixel space
/// using a reference viewport size. Color is stored as a hex string for
/// human-readable JSON editing.
/// </summary>
public class LightDef
{
/// <summary>
/// Normalized horizontal position (0.0–1.0) relative to viewport width.
/// </summary>
[JsonPropertyName("x")]
public float X { get; set; }
/// <summary>
/// Normalized vertical position (0.0–1.0) relative to viewport height.
/// </summary>
[JsonPropertyName("y")]
public float Y { get; set; }
/// <summary>
/// Light radius in pixel units. Determines the falloff distance in the
/// lighting shader.
/// </summary>
[JsonPropertyName("radius")]
public float Radius { get; set; }
/// <summary>
/// Light intensity (0.0–1.0). At <c>1.0</c>, the light fully brightens
/// pixels within its radius. Lower values create subtler effects.
/// </summary>
[JsonPropertyName("influence")]
public float Influence { get; set; } = 1f;
/// <summary>
/// Light color as a hex string (e.g. "#ff8800"). Parsed by
/// <see cref="ToColor"/> for shader upload.
/// </summary>
[JsonPropertyName("color")]
public string ColorHex { get; set; } = "#ffff00";
/// <summary>
/// Converts <see cref="ColorHex"/> to a Godot <c>Color</c> struct.
/// </summary>
public Color ToColor()
{
return new Color(ColorHex);
}
/// <summary>
/// Converts normalized coordinates (<see cref="X"/>, <see cref="Y"/>) to
/// pixel coordinates based on <paramref name="viewportSize"/>.
/// </summary>
/// <param name="viewportSize">Current viewport dimensions in pixels.</param>
public Vector2 ToPixelPos(Vector2 viewportSize)
{
return new Vector2(X * viewportSize.X, Y * viewportSize.Y);
}
/// <summary>
/// Computes normalized coordinates from pixel coordinates by dividing by
/// <paramref name="referenceSize"/>. Enables saving light positions
/// across different resolutions.
/// </summary>
/// <param name="pixelPos">Light position in pixel space.</param>
/// <param name="referenceSize">Viewport dimensions used as reference.</param>
public void FromPixelPos(Vector2 pixelPos, Vector2 referenceSize)
{
X = pixelPos.X / referenceSize.X;
Y = pixelPos.Y / referenceSize.Y;
}
}
}
File diff suppressed because it is too large. Load diff
+73 -73
View File
@@ -1,73 +1,73 @@
using Cthangover.Core.Bootstrap;
using Cthangover.Core.Settings;
using Cthangover.Core.Events;
using Cthangover.Core.Mods;
using Cthangover.Core.UI.Event;
using Godot;
namespace Cthangover.Mods.Time
{
/// <summary>
/// Bridges the scene-event timer system to the in-game clock.
/// Implements <see cref="Cthangover.Core.UI.Event.IOnTimeEvent"/>
/// with priority 0 so that the tick is processed before most
/// other listeners. The controller must be explicitly registered
/// via <see cref="EnsureRegistered"/>, which finds the
/// <see cref="Cthangover.Core.UI.Event.SceneEventController"/> in
/// the scene tree and subscribes <see cref="OnTimerTick"/> to it.
/// Registration is idempotent — subsequent calls are no-ops.
/// </summary>
public class TimeTickController : IOnTimeEvent
{
/// <summary>Executes before default-priority listeners so time
/// updates are visible to all other tick handlers.</summary>
public int Priority => 0;
private bool _registered;
/// <summary>
/// Locates the <see cref="Cthangover.Core.UI.Event.SceneEventController"/>
/// in the scene tree and registers this controller as a timer-tick
/// listener. Safe to call multiple times — only the first call
/// does work.
/// </summary>
public void EnsureRegistered(Node node)
{
if (_registered)
return;
var eventController = node.GetTree()?.Root?.FindChild("EventController", true, false) as SceneEventController;
if (eventController == null)
return;
eventController.AddTimerTickEventListener(this);
_registered = true;
}
/// <summary>
/// Called by the event system on each timer pulse. Advances
/// the in-game clock by one minute via
/// <see cref="TimeData.AddTick"/>.
/// </summary>
public void OnTimerTick()
{
// The SceneEventController keeps ticking (1s interval, ProcessMode
// Always) while the launcher boot compiles mods in the background —
// registration in GameData._Ready succeeds because the engine puts
// every autoload into the tree before any _Ready fires. Touching
// Runtime here would create it before the starter inventory can be
// seeded through loaded mod factories: the seed then runs against an
// empty registry, logs "Initialize refused" spam and permanently
// skips the starter items. Skip boot-window ticks instead — the same
// readiness contract GameData.Runtime enforces.
if (!Boot.ModsReady)
return;
GameData.Instance.Runtime.Time.AddTick();
ModEventBus.Publish(new TimeTickEvent
{
TimeTick = GameData.Instance.Runtime.Time.Tick,
NormalizedTime = GameData.Instance.Runtime.Time.Normalized,
});
}
}
}
using Cthangover.Core.Bootstrap;
using Cthangover.Core.Settings;
using Cthangover.Core.Events;
using Cthangover.Core.Mods;
using Cthangover.Core.UI.Event;
using Godot;
namespace Cthangover.Mods.Time
{
/// <summary>
/// Bridges the scene-event timer system to the in-game clock.
/// Implements <see cref="Cthangover.Core.UI.Event.IOnTimeEvent"/>
/// with priority 0 so that the tick is processed before most
/// other listeners. The controller must be explicitly registered
/// via <see cref="EnsureRegistered"/>, which finds the
/// <see cref="Cthangover.Core.UI.Event.SceneEventController"/> in
/// the scene tree and subscribes <see cref="OnTimerTick"/> to it.
/// Registration is idempotent — subsequent calls are no-ops.
/// </summary>
public class TimeTickController : IOnTimeEvent
{
/// <summary>Executes before default-priority listeners so time
/// updates are visible to all other tick handlers.</summary>
public int Priority => 0;
private bool _registered;
/// <summary>
/// Locates the <see cref="Cthangover.Core.UI.Event.SceneEventController"/>
/// in the scene tree and registers this controller as a timer-tick
/// listener. Safe to call multiple times — only the first call
/// does work.
/// </summary>
public void EnsureRegistered(Node node)
{
if (_registered)
return;
var eventController = node.GetTree()?.Root?.FindChild("EventController", true, false) as SceneEventController;
if (eventController == null)
return;
eventController.AddTimerTickEventListener(this);
_registered = true;
}
/// <summary>
/// Called by the event system on each timer pulse. Advances
/// the in-game clock by one minute via
/// <see cref="TimeData.AddTick"/>.
/// </summary>
public void OnTimerTick()
{
// The SceneEventController keeps ticking (1s interval, ProcessMode
// Always) while the launcher boot compiles mods in the background —
// registration in GameData._Ready succeeds because the engine puts
// every autoload into the tree before any _Ready fires. Touching
// Runtime here would create it before the starter inventory can be
// seeded through loaded mod factories: the seed then runs against an
// empty registry, logs "Initialize refused" spam and permanently
// skips the starter items. Skip boot-window ticks instead — the same
// readiness contract GameData.Runtime enforces.
if (!Boot.ModsReady)
return;
GameData.Instance.Runtime.Time.AddTick();
ModEventBus.Publish(new TimeTickEvent
{
TimeTick = GameData.Instance.Runtime.Time.Tick,
NormalizedTime = GameData.Instance.Runtime.Time.Normalized,
});
}
}
}