diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3030c1a
--- /dev/null
+++ b/.gitattributes
@@ -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
diff --git a/.gitignore b/.gitignore
index bb88804..c301ca8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/src/ItemActionFactory.cs b/src/ItemActionFactory.cs
index 090772b..f3e50d2 100644
--- a/src/ItemActionFactory.cs
+++ b/src/ItemActionFactory.cs
@@ -1,63 +1,63 @@
-using System.Collections.Generic;
-using Cthangover.Core.Items;
-
-namespace Cthangover.Core.Factories.Impls
-{
- ///
- /// Registry of item action behaviours, indexed by ID. It discovers
- /// nothing itself: C# actions of every mod arrive through
- /// ItemActionConsumer as the kernel registers their assembly and
- /// leave with their mod, and GDScript-declared actions arrive through
- /// ItemActionBridge. A mod therefore introduces a new item action
- /// simply by containing a public class that implements the interface.
- ///
- /// The "null" ID string is treated as a no-op action (the
- /// fallback for items that don't have an on-use effect), avoiding
- /// the need for a sentinel NullItemAction class.
- ///
- /// The single instance lives in this mod assembly as a static lazy
- /// singleton; the static facade stays as the public API.
- ///
- public class ItemActionFactory
- {
- private static ItemActionFactory _instance;
-
- /// Static lazy singleton; kept as the public API.
- public static ItemActionFactory Instance => _instance ??= new ItemActionFactory();
-
- private readonly IDictionary data = new Dictionary();
-
- internal ItemActionFactory()
- {
- }
-
- /// Registers a single action under its ; the first action of an id wins, null/empty ids are ignored.
- public void Register(IItemAction action)
- {
- if (action != null && !string.IsNullOrEmpty(action.ID) && !data.ContainsKey(action.ID))
- data[action.ID] = action;
- }
-
- ///
- /// Removes exactly (its mod is being unloaded);
- /// an id owned by another action instance is left alone.
- ///
- public void Unregister(IItemAction action)
- {
- if (action != null && !string.IsNullOrEmpty(action.ID) && data.TryGetValue(action.ID, out var registered) && registered == action)
- data.Remove(action.ID);
- }
-
- ///
- /// Resolves the behaviour registered under .
- /// The special id "null" (or an empty value) is an explicit
- /// no-action marker and returns null, as does an unknown id.
- ///
- public IItemAction Get(string id)
- {
- if (string.IsNullOrWhiteSpace(id) || id == "null")
- return null;
- return data[id];
- }
- }
-}
+using System.Collections.Generic;
+using Cthangover.Core.Items;
+
+namespace Cthangover.Core.Factories.Impls
+{
+ ///
+ /// Registry of item action behaviours, indexed by ID. It discovers
+ /// nothing itself: C# actions of every mod arrive through
+ /// ItemActionConsumer as the kernel registers their assembly and
+ /// leave with their mod, and GDScript-declared actions arrive through
+ /// ItemActionBridge. A mod therefore introduces a new item action
+ /// simply by containing a public class that implements the interface.
+ ///
+ /// The "null" ID string is treated as a no-op action (the
+ /// fallback for items that don't have an on-use effect), avoiding
+ /// the need for a sentinel NullItemAction class.
+ ///
+ /// The single instance lives in this mod assembly as a static lazy
+ /// singleton; the static facade stays as the public API.
+ ///
+ public class ItemActionFactory
+ {
+ private static ItemActionFactory _instance;
+
+ /// Static lazy singleton; kept as the public API.
+ public static ItemActionFactory Instance => _instance ??= new ItemActionFactory();
+
+ private readonly IDictionary data = new Dictionary();
+
+ internal ItemActionFactory()
+ {
+ }
+
+ /// Registers a single action under its ; the first action of an id wins, null/empty ids are ignored.
+ public void Register(IItemAction action)
+ {
+ if (action != null && !string.IsNullOrEmpty(action.ID) && !data.ContainsKey(action.ID))
+ data[action.ID] = action;
+ }
+
+ ///
+ /// Removes exactly (its mod is being unloaded);
+ /// an id owned by another action instance is left alone.
+ ///
+ public void Unregister(IItemAction action)
+ {
+ if (action != null && !string.IsNullOrEmpty(action.ID) && data.TryGetValue(action.ID, out var registered) && registered == action)
+ data.Remove(action.ID);
+ }
+
+ ///
+ /// Resolves the behaviour registered under .
+ /// The special id "null" (or an empty value) is an explicit
+ /// no-action marker and returns null, as does an unknown id.
+ ///
+ public IItemAction Get(string id)
+ {
+ if (string.IsNullOrWhiteSpace(id) || id == "null")
+ return null;
+ return data[id];
+ }
+ }
+}
diff --git a/src/RecipeData.cs b/src/RecipeData.cs
index d3dbf61..d2dfdc2 100644
--- a/src/RecipeData.cs
+++ b/src/RecipeData.cs
@@ -1,50 +1,50 @@
-using System.Collections.Generic;
-using System.Linq;
-using Cthangover.Core.Factories.Impls;
-using Cthangover.Core.Items;
-
-namespace Cthangover.Core.Settings
-{
- ///
- /// Tracks which crafting recipes the player has unlocked. Internally
- /// a of recipe IDs that provides O(1)
- /// lookup. Recipes can be queried by
- /// via , which resolves each ID through
- /// and
- /// filters the resulting objects.
- ///
- public class RecipeData : IRecipeData
- {
- /// Set of known recipe IDs (duplicate insertion is silently ignored).
- public HashSet Data { get; set; } = new();
-
- ///
- /// Unlocks a recipe by ID. Returns false if the recipe
- /// was already known (duplicate).
- ///
- public bool Add(string id)
- {
- return Data.Add(id);
- }
-
- /// true if the recipe with the given ID is already unlocked.
- public bool Has(string id)
- {
- return Data.Contains(id);
- }
-
- ///
- /// Returns all known recipes that belong to the specified
- /// . Each ID is resolved via
- ///
- /// and filtered; IDs that resolve to null are silently
- /// dropped.
- ///
- public List GetRecipesByType(WorkbenchType type)
- {
- return Data.Select(o => RecipeFactory.Instance.Get(o))
- .Where(o => o.WorkbenchType == type)
- .ToList();
- }
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.Items;
+
+namespace Cthangover.Core.Settings
+{
+ ///
+ /// Tracks which crafting recipes the player has unlocked. Internally
+ /// a of recipe IDs that provides O(1)
+ /// lookup. Recipes can be queried by
+ /// via , which resolves each ID through
+ /// and
+ /// filters the resulting objects.
+ ///
+ public class RecipeData : IRecipeData
+ {
+ /// Set of known recipe IDs (duplicate insertion is silently ignored).
+ public HashSet Data { get; set; } = new();
+
+ ///
+ /// Unlocks a recipe by ID. Returns false if the recipe
+ /// was already known (duplicate).
+ ///
+ public bool Add(string id)
+ {
+ return Data.Add(id);
+ }
+
+ /// true if the recipe with the given ID is already unlocked.
+ public bool Has(string id)
+ {
+ return Data.Contains(id);
+ }
+
+ ///
+ /// Returns all known recipes that belong to the specified
+ /// . Each ID is resolved via
+ ///
+ /// and filtered; IDs that resolve to null are silently
+ /// dropped.
+ ///
+ public List GetRecipesByType(WorkbenchType type)
+ {
+ return Data.Select(o => RecipeFactory.Instance.Get(o))
+ .Where(o => o.WorkbenchType == type)
+ .ToList();
+ }
+ }
+}
diff --git a/src/ui/InventoryBagBehaviour.cs b/src/ui/InventoryBagBehaviour.cs
index 9462443..c3fd2d0 100644
--- a/src/ui/InventoryBagBehaviour.cs
+++ b/src/ui/InventoryBagBehaviour.cs
@@ -1,41 +1,41 @@
-using System.Collections.Generic;
-using Cthangover.Core.Items;
-using Cthangover.Mods.Interface.Base.Lists.Impls;
-
-namespace Cthangover.Core.UI.Inventory
-{
- ///
- /// Grid-based inventory display using ColumnCellListWidget. Binds an
- /// external List<IItemContainer>. The List property setter auto-refreshes
- /// the UI — intended for data-driven updates where the inventory source
- /// pushes a new list reference rather than items mutating in place.
- ///
- public partial class InventoryBagBehaviour : ColumnCellListWidget
- {
- private List list;
-
- ///
- /// Bound inventory data source. Setting this property immediately rebuilds
- /// the grid display via . The entire grid is
- /// reconstructed from the new list reference.
- ///
- public List List
- {
- get => list;
- set
- {
- list = value;
- Refresh();
- }
- }
-
- ///
- /// Provides the current as the data collection for the
- /// base.
- ///
- public override ICollection CreateModels()
- {
- return list;
- }
- }
-}
+using System.Collections.Generic;
+using Cthangover.Core.Items;
+using Cthangover.Mods.Interface.Base.Lists.Impls;
+
+namespace Cthangover.Core.UI.Inventory
+{
+ ///
+ /// Grid-based inventory display using ColumnCellListWidget. Binds an
+ /// external List<IItemContainer>. The List property setter auto-refreshes
+ /// the UI — intended for data-driven updates where the inventory source
+ /// pushes a new list reference rather than items mutating in place.
+ ///
+ public partial class InventoryBagBehaviour : ColumnCellListWidget
+ {
+ private List list;
+
+ ///
+ /// Bound inventory data source. Setting this property immediately rebuilds
+ /// the grid display via . The entire grid is
+ /// reconstructed from the new list reference.
+ ///
+ public List List
+ {
+ get => list;
+ set
+ {
+ list = value;
+ Refresh();
+ }
+ }
+
+ ///
+ /// Provides the current as the data collection for the
+ /// base.
+ ///
+ public override ICollection CreateModels()
+ {
+ return list;
+ }
+ }
+}