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:49 +02:00
1 parent f4dc0c0b04
commit 5f59b01a51
6 files changed
+245 -215

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
+48 -48
View File
@@ -1,48 +1,48 @@
using Cthangover.Core.Settings;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Per-recruit behaviour plugin discovered via reflection from mod
/// assemblies. Each implementation is registered into
/// <see cref="RecruitBehaviourRegistry"/> by
/// <see cref="Mods.ModAssemblyLoader.RegisterAssembly"/> and then
/// invoked for <b>every</b> active recruit — not just those from the
/// owning mod — during tick, configure, and removal lifecycle events.
///
/// This cross-recruit dispatch pattern is intentional: a behaviour
/// might want to introspect all recruits (e.g. a "rivalry" system
/// that checks for conflicting personalities), so the registry passes
/// the full recruit list to every behaviour rather than filtering by
/// ownership.
/// </summary>
public interface IRecruitBehaviour
{
/// <summary>Unique identifier for lookup via <c>RecruitBehaviourRegistry.Get</c>.</summary>
string Id { get; }
/// <summary>
/// Called once when a recruit is added to the party
/// (via <c>RecruitingData.Add</c>). Use for initial stat
/// setup, buff application, or spawning companion nodes.
/// </summary>
void ConfigureRecruit(Recruit recruit, RuntimeData runtime);
/// <summary>
/// Called every game tick for each active recruit. The
/// <paramref name="currentTick"/> value is the global
/// <c>RuntimeData.Time.Tick</c> counter — use it for
/// cooldown-based or periodic logic rather than maintaining
/// a private timer.
/// </summary>
void OnTick(Recruit recruit, RuntimeData runtime, long currentTick);
/// <summary>
/// Called when a recruit is removed from the party
/// (via <c>RecruitingData.Remove</c>). Clean up any
/// persisted state, buffs, or companion nodes.
/// </summary>
void OnRemove(Recruit recruit, RuntimeData runtime);
}
}
using Cthangover.Core.Settings;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Per-recruit behaviour plugin discovered via reflection from mod
/// assemblies. Each implementation is registered into
/// <see cref="RecruitBehaviourRegistry"/> by
/// <see cref="Mods.ModAssemblyLoader.RegisterAssembly"/> and then
/// invoked for <b>every</b> active recruit — not just those from the
/// owning mod — during tick, configure, and removal lifecycle events.
///
/// This cross-recruit dispatch pattern is intentional: a behaviour
/// might want to introspect all recruits (e.g. a "rivalry" system
/// that checks for conflicting personalities), so the registry passes
/// the full recruit list to every behaviour rather than filtering by
/// ownership.
/// </summary>
public interface IRecruitBehaviour
{
/// <summary>Unique identifier for lookup via <c>RecruitBehaviourRegistry.Get</c>.</summary>
string Id { get; }
/// <summary>
/// Called once when a recruit is added to the party
/// (via <c>RecruitingData.Add</c>). Use for initial stat
/// setup, buff application, or spawning companion nodes.
/// </summary>
void ConfigureRecruit(Recruit recruit, RuntimeData runtime);
/// <summary>
/// Called every game tick for each active recruit. The
/// <paramref name="currentTick"/> value is the global
/// <c>RuntimeData.Time.Tick</c> counter — use it for
/// cooldown-based or periodic logic rather than maintaining
/// a private timer.
/// </summary>
void OnTick(Recruit recruit, RuntimeData runtime, long currentTick);
/// <summary>
/// Called when a recruit is removed from the party
/// (via <c>RecruitingData.Remove</c>). Clean up any
/// persisted state, buffs, or companion nodes.
/// </summary>
void OnRemove(Recruit recruit, RuntimeData runtime);
}
}
+33 -33
View File
@@ -1,33 +1,33 @@
using Cthangover.Core.Characters;
using Cthangover.Core.Settings;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Gating predicate for the recruit system. Implementations are
/// auto-discovered from mod assemblies by
/// <see cref="RecruitBehaviourRegistry.RegisterAssembly"/> and
/// evaluated conjunctively — <b>all</b> conditions must return
/// <c>true</c> for recruitment to succeed.
///
/// Conditions receive the full <see cref="Character"/> and
/// <see cref="RuntimeData"/> so they can inspect character stats,
/// inventory, relationship flags, quest state, or any other
/// runtime-accessible data when deciding whether a given enemy is
/// recruitable.
/// </summary>
public interface IRecruitCondition
{
/// <summary>Unique identifier for debugging and override lookups.</summary>
string Id { get; }
/// <summary>
/// Returns <c>true</c> if the given <paramref name="enemy"/>
/// can be recruited under current game state. Called from
/// <c>RecruitBehaviourRegistry.CanRecruit</c> as part of a
/// conjunctive gate — a single <c>false</c> blocks recruitment.
/// </summary>
bool CanRecruit(Character enemy, RuntimeData runtime);
}
}
using Cthangover.Core.Characters;
using Cthangover.Core.Settings;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Gating predicate for the recruit system. Implementations are
/// auto-discovered from mod assemblies by
/// <see cref="RecruitBehaviourRegistry.RegisterAssembly"/> and
/// evaluated conjunctively — <b>all</b> conditions must return
/// <c>true</c> for recruitment to succeed.
///
/// Conditions receive the full <see cref="Character"/> and
/// <see cref="RuntimeData"/> so they can inspect character stats,
/// inventory, relationship flags, quest state, or any other
/// runtime-accessible data when deciding whether a given enemy is
/// recruitable.
/// </summary>
public interface IRecruitCondition
{
/// <summary>Unique identifier for debugging and override lookups.</summary>
string Id { get; }
/// <summary>
/// Returns <c>true</c> if the given <paramref name="enemy"/>
/// can be recruited under current game state. Called from
/// <c>RecruitBehaviourRegistry.CanRecruit</c> as part of a
/// conjunctive gate — a single <c>false</c> blocks recruitment.
/// </summary>
bool CanRecruit(Character enemy, RuntimeData runtime);
}
}
+35 -35
View File
@@ -1,35 +1,35 @@
using System;
using Cthangover.Core.Utils;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Runtime model for a recruited party member. Stores the recruit's
/// unique ID, the character template it was created from, and a
/// modifiable <see cref="Utility.PropertyData"/> bag that behaviours
/// read and mutate during tick/configure events.
///
/// Marked <c>[Serializable]</c> so the recruiting roster survives
/// save/load — <c>PropertyData</c> serialises its nested values
/// automatically. The <c>PROP_HEALTH</c> constant is the conventional
/// key for health tracking; behaviours that need custom state
/// (e.g. morale, loyalty timers) add their own keys to the
/// <c>Properties</c> bag at configure time.
/// </summary>
[Serializable]
public class Recruit
{
/// <summary>Conventional key for the recruit's health value in the <c>Properties</c> bag.</summary>
public const string PROP_HEALTH = "Health";
/// <summary>Unique instance ID (auto-generated via <c>RecruitingData.GenID</c>).</summary>
public string ID { get; set; }
/// <summary>Character template ID this recruit was spawned from.</summary>
public string CharacterID { get; set; }
/// <summary>Mutable property bag — behaviours read/write custom state here.</summary>
public PropertyData Properties { get; set; } = new();
}
}
using System;
using Cthangover.Core.Utils;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Runtime model for a recruited party member. Stores the recruit's
/// unique ID, the character template it was created from, and a
/// modifiable <see cref="Utility.PropertyData"/> bag that behaviours
/// read and mutate during tick/configure events.
///
/// Marked <c>[Serializable]</c> so the recruiting roster survives
/// save/load — <c>PropertyData</c> serialises its nested values
/// automatically. The <c>PROP_HEALTH</c> constant is the conventional
/// key for health tracking; behaviours that need custom state
/// (e.g. morale, loyalty timers) add their own keys to the
/// <c>Properties</c> bag at configure time.
/// </summary>
[Serializable]
public class Recruit
{
/// <summary>Conventional key for the recruit's health value in the <c>Properties</c> bag.</summary>
public const string PROP_HEALTH = "Health";
/// <summary>Unique instance ID (auto-generated via <c>RecruitingData.GenID</c>).</summary>
public string ID { get; set; }
/// <summary>Character template ID this recruit was spawned from.</summary>
public string CharacterID { get; set; }
/// <summary>Mutable property bag — behaviours read/write custom state here.</summary>
public PropertyData Properties { get; set; } = new();
}
}
+62 -62
View File
@@ -1,62 +1,62 @@
using Cthangover.Core.Scenes;
using Cthangover.Core.UI.Event;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Lazy bridge between the scene timer system and the recruit
/// behaviour tick loop. Implements <see cref="IOnTimeEvent"/>
/// so it can be registered with <see cref="SceneEventController"/>
/// via <c>AddTimerTickEventListener</c>.
///
/// Registration is lazy (<see cref="EnsureRegistered"/> is a no-op
/// after the first call) because the recruit system should not
/// force the timer subsystem to activate if no behaviours are
/// actually loaded. The first behaviour lifecycle method in
/// <see cref="RecruitBehaviourRegistry"/> calls
/// <c>EnsureRegistered</c>, which finds the scene's
/// <c>EventController</c> node and subscribes.
///
/// <c>Priority = 0</c> so recruit ticks fire at a neutral point in
/// the timer event chain — before or after other listeners as
/// determined by their relative priorities.
/// </summary>
public class RecruitTickController : IOnTimeEvent
{
/// <summary>Neutral priority — doesn't preempt or follow any specific listener.</summary>
public int Priority => 0;
private bool _registered;
/// <summary>
/// Finds the scene's <c>EventController</c> node and subscribes
/// this controller as a timer-tick listener. Idempotent —
/// subsequent calls do nothing.
/// </summary>
public void EnsureRegistered()
{
if (_registered)
return;
var eventController = SceneContextNode.FindNode<SceneEventController>("EventController");
if (eventController == null)
return;
eventController.AddTimerTickEventListener(this);
_registered = true;
}
/// <summary>
/// Called by <see cref="SceneEventController"/> on every
/// timer tick. Delegates to
/// <see cref="RecruitBehaviourRegistry.OnTick"/>.
/// Ticks also fire during the launcher boot window, so the registry
/// side must not touch <c>GameData.Instance.Runtime</c> or mod data
/// until <c>Boot.ModsReady</c> (see the boot-window contract on
/// <c>IOnTimeEvent</c>; core-side reference: TimeTickController).
/// </summary>
public void OnTimerTick()
{
RecruitBehaviourRegistry.Instance.OnTick();
}
}
}
using Cthangover.Core.Scenes;
using Cthangover.Core.UI.Event;
namespace Cthangover.Core.Relationship
{
/// <summary>
/// Lazy bridge between the scene timer system and the recruit
/// behaviour tick loop. Implements <see cref="IOnTimeEvent"/>
/// so it can be registered with <see cref="SceneEventController"/>
/// via <c>AddTimerTickEventListener</c>.
///
/// Registration is lazy (<see cref="EnsureRegistered"/> is a no-op
/// after the first call) because the recruit system should not
/// force the timer subsystem to activate if no behaviours are
/// actually loaded. The first behaviour lifecycle method in
/// <see cref="RecruitBehaviourRegistry"/> calls
/// <c>EnsureRegistered</c>, which finds the scene's
/// <c>EventController</c> node and subscribes.
///
/// <c>Priority = 0</c> so recruit ticks fire at a neutral point in
/// the timer event chain — before or after other listeners as
/// determined by their relative priorities.
/// </summary>
public class RecruitTickController : IOnTimeEvent
{
/// <summary>Neutral priority — doesn't preempt or follow any specific listener.</summary>
public int Priority => 0;
private bool _registered;
/// <summary>
/// Finds the scene's <c>EventController</c> node and subscribes
/// this controller as a timer-tick listener. Idempotent —
/// subsequent calls do nothing.
/// </summary>
public void EnsureRegistered()
{
if (_registered)
return;
var eventController = SceneContextNode.FindNode<SceneEventController>("EventController");
if (eventController == null)
return;
eventController.AddTimerTickEventListener(this);
_registered = true;
}
/// <summary>
/// Called by <see cref="SceneEventController"/> on every
/// timer tick. Delegates to
/// <see cref="RecruitBehaviourRegistry.OnTick"/>.
/// Ticks also fire during the launcher boot window, so the registry
/// side must not touch <c>GameData.Instance.Runtime</c> or mod data
/// until <c>Boot.ModsReady</c> (see the boot-window contract on
/// <c>IOnTimeEvent</c>; core-side reference: TimeTickController).
/// </summary>
public void OnTimerTick()
{
RecruitBehaviourRegistry.Instance.OnTick();
}
}
}