diff --git a/AutoRefillFires.csproj b/AutoRefillFires.csproj
index 78c024a..2ca9e7f 100644
--- a/AutoRefillFires.csproj
+++ b/AutoRefillFires.csproj
@@ -6,7 +6,7 @@
AutoRefillFires
latest
- 1.1.0
+ 1.2.0
D:\SteamLibrary\steamapps\common\Valheim
Default
@@ -60,11 +60,7 @@
-
+
@@ -84,21 +80,13 @@
-
+
-
+
-
+
-
+
diff --git a/Configuration/FuelSourcePriority.cs b/Configuration/FuelSourcePriority.cs
new file mode 100644
index 0000000..898c51f
--- /dev/null
+++ b/Configuration/FuelSourcePriority.cs
@@ -0,0 +1,8 @@
+namespace AutoRefillFires
+{
+ public enum FuelSourcePriority
+ {
+ PlayerFirst,
+ ContainersFirst
+ }
+}
diff --git a/Configuration/ModConfig.cs b/Configuration/ModConfig.cs
new file mode 100644
index 0000000..41bf103
--- /dev/null
+++ b/Configuration/ModConfig.cs
@@ -0,0 +1,61 @@
+using BepInEx.Configuration;
+using UnityEngine;
+
+namespace AutoRefillFires
+{
+ internal class ModConfig
+ {
+ public ConfigEntry Radius { get; }
+ public ConfigEntry CheckInterval { get; }
+ public ConfigEntry RefillBelowPercent { get; }
+
+ public ConfigEntry UseNearbyContainers { get; }
+ public ConfigEntry ContainerRadius { get; }
+
+ public ConfigEntry RefillToMax { get; }
+ public ConfigEntry RefillAmount { get; }
+
+ public ConfigEntry FillCampfires { get; }
+ public ConfigEntry FillHearths { get; }
+ public ConfigEntry FillStandingTorches { get; }
+ public ConfigEntry FillWallTorches { get; }
+ public ConfigEntry FillBraziers { get; }
+ public ConfigEntry FillBonfires { get; }
+ public ConfigEntry FillHotTubs { get; }
+ public ConfigEntry FillOtherFireplaces { get; }
+
+ public ConfigEntry KeepFuelReserve { get; }
+ public ConfigEntry FuelSourcePriority { get; }
+
+ public ConfigEntry OnlyRefillOwnPieces { get; }
+ public ConfigEntry ToggleHotkey { get; }
+ public ConfigEntry LogLevel { get; }
+
+ public ModConfig(ConfigFile config)
+ {
+ Radius = config.Bind("General", "Radius", 20f, "Refill fireplaces within this radius around the player.");
+ CheckInterval = config.Bind("General", "CheckInterval", 3f, "How often fireplaces are checked, in seconds.");
+ RefillBelowPercent = config.Bind("General", "RefillBelowPercent", 0.75f, "Refill when fuel drops below this percentage.");
+ RefillToMax = config.Bind("General", "RefillToMax", true, "If true, refill fireplaces to maximum fuel. If false, add only RefillAmount fuel.");
+ RefillAmount = config.Bind("General", "RefillAmount", 5, "Amount of fuel to add when RefillToMax is false.");
+
+ UseNearbyContainers = config.Bind("Containers", "UseNearbyContainers", false, "Allow plugin to take fuel from nearby containers.");
+ ContainerRadius = config.Bind("Containers", "ContainerRadius", 10f, "Maximum distance from the fireplace to search for containers.");
+
+ FillCampfires = config.Bind("Fireplace Types", "FillCampfires", true, "Automatically refill campfires.");
+ FillHearths = config.Bind("Fireplace Types", "FillHearths", true, "Automatically refill hearths.");
+ FillStandingTorches = config.Bind("Fireplace Types", "FillStandingTorches", true, "Automatically refill all standing torches, including colored variants.");
+ FillWallTorches = config.Bind("Fireplace Types", "FillWallTorches", true, "Automatically refill all wall torches, including colored variants.");
+ FillBraziers = config.Bind("Fireplace Types", "FillBraziers", true, "Automatically refill braziers.");
+ FillBonfires = config.Bind("Fireplace Types", "FillBonfires", true, "Automatically refill bonfires.");
+ FillHotTubs = config.Bind("Fireplace Types", "FillHotTubs", true, "Automatically refill hot tubs.");
+ FillOtherFireplaces = config.Bind("Fireplace Types", "FillOtherFireplaces", true, "Automatically refill unknown or modded Fireplace-based objects.");
+
+ ToggleHotkey = config.Bind("General", "ToggleHotkey", new KeyboardShortcut(KeyCode.F7), "Hotkey used to enable or disable automatic refilling.");
+ KeepFuelReserve = config.Bind("Fuel", "KeepFuelReserve", 0, "Minimum amount of fuel to keep in the source inventory. 10 means the last 10 Wood/Resin will not be used.");
+ FuelSourcePriority = config.Bind("Fuel", "FuelSourcePriority", AutoRefillFires.FuelSourcePriority.PlayerFirst, "Select whether player inventory or nearby containers should be used first.");
+ OnlyRefillOwnPieces = config.Bind("General", "OnlyRefillOwnPieces", false, "If enabled, only refill fireplaces and torches built by the local player.");
+ LogLevel = config.Bind("Logging", "LogLevel", ModLogLevel.Info, "Logging verbosity. Available values: None, Error, Warning, Info, Debug.");
+ }
+ }
+}
diff --git a/Configuration/ModLogLevel.cs b/Configuration/ModLogLevel.cs
new file mode 100644
index 0000000..30d1b67
--- /dev/null
+++ b/Configuration/ModLogLevel.cs
@@ -0,0 +1,11 @@
+namespace AutoRefillFires
+{
+ public enum ModLogLevel
+ {
+ None = 0,
+ Error = 1,
+ Warning = 2,
+ Info = 3,
+ Debug = 4
+ }
+}
diff --git a/Fuel/FuelManager.cs b/Fuel/FuelManager.cs
new file mode 100644
index 0000000..b2d6868
--- /dev/null
+++ b/Fuel/FuelManager.cs
@@ -0,0 +1,169 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace AutoRefillFires
+{
+ internal class FuelManager
+ {
+ private readonly ModConfig _config;
+ private readonly ModLogger _log;
+
+ public FuelManager(ModConfig config, ModLogger log)
+ {
+ _config = config;
+ _log = log;
+ }
+
+ public int ConsumeFuel(Player player, Transform target, string fuelName, int requestedAmount, Container[] containers)
+ {
+ if (requestedAmount <= 0)
+ return 0;
+
+ _log.Debug(
+ $"Fuel request: fuel='{fuelName}', " +
+ $"requested={requestedAmount}, " +
+ $"priority={_config.FuelSourcePriority.Value}, " +
+ $"containers={_config.UseNearbyContainers.Value}, " +
+ $"reserve={_config.KeepFuelReserve.Value}"
+ );
+
+ int consumed = 0;
+
+ if (_config.FuelSourcePriority.Value == FuelSourcePriority.ContainersFirst)
+ {
+ if (_config.UseNearbyContainers.Value && containers != null)
+ consumed += ConsumeFuelFromNearbyContainers(target, fuelName, requestedAmount - consumed, containers);
+
+ if (consumed < requestedAmount)
+ consumed += ConsumeFuelFromPlayer(player, fuelName, requestedAmount - consumed);
+ }
+ else
+ {
+ consumed += ConsumeFuelFromPlayer(player, fuelName, requestedAmount);
+
+ if (_config.UseNearbyContainers.Value && containers != null && consumed < requestedAmount)
+ consumed += ConsumeFuelFromNearbyContainers(target, fuelName, requestedAmount - consumed, containers);
+ }
+
+ _log.Debug(
+ $"Fuel request result: fuel='{fuelName}', " +
+ $"requested={requestedAmount}, " +
+ $"consumed={consumed}"
+ );
+
+ return consumed;
+ }
+
+ private int ConsumeFuelFromPlayer(Player player, string fuelName, int requestedAmount)
+ {
+ if (requestedAmount <= 0)
+ return 0;
+
+ Inventory inventory = player.GetInventory();
+
+ if (inventory == null)
+ {
+ _log.Debug("Player inventory is null.");
+ return 0;
+ }
+
+ int availableFuel = inventory.CountItems(fuelName);
+ int usableFuel = Mathf.Max(availableFuel - _config.KeepFuelReserve.Value, 0);
+ int amountToTake = Mathf.Min(requestedAmount, usableFuel);
+
+ if (amountToTake <= 0)
+ {
+ _log.Debug(
+ $"Player inventory: fuel='{fuelName}', " +
+ $"available={availableFuel}, " +
+ $"reserve={_config.KeepFuelReserve.Value}, " +
+ $"usable=0"
+ );
+
+ return 0;
+ }
+
+ inventory.RemoveItem(fuelName, amountToTake);
+
+ _log.Debug(
+ $"Player inventory: consumed={amountToTake}x {fuelName}, " +
+ $"before={availableFuel}, " +
+ $"remaining={availableFuel - amountToTake}"
+ );
+
+ return amountToTake;
+ }
+
+ private int ConsumeFuelFromNearbyContainers(Transform target, string fuelName, int requestedAmount, Container[] containers)
+ {
+ if (requestedAmount <= 0)
+ return 0;
+
+ if (containers == null || containers.Length == 0)
+ return 0;
+
+ List candidates = new List();
+
+ foreach (Container container in containers)
+ {
+ if (container == null)
+ continue;
+
+ float distance = Vector3.Distance(target.position, container.transform.position);
+
+ if (distance > _config.ContainerRadius.Value)
+ continue;
+
+ Inventory inventory = container.GetInventory();
+
+ if (inventory == null)
+ continue;
+
+ int availableFuel = inventory.CountItems(fuelName);
+ int usableFuel = Mathf.Max(availableFuel - _config.KeepFuelReserve.Value, 0);
+
+ if (usableFuel <= 0)
+ continue;
+
+ candidates.Add(new ContainerCandidate
+ {
+ Container = container,
+ Distance = distance
+ });
+ }
+
+ candidates.Sort((a, b) => a.Distance.CompareTo(b.Distance));
+
+ int consumed = 0;
+
+ foreach (ContainerCandidate candidate in candidates)
+ {
+ if (consumed >= requestedAmount)
+ break;
+
+ Inventory inventory = candidate.Container.GetInventory();
+
+ if (inventory == null)
+ continue;
+
+ int availableFuel = inventory.CountItems(fuelName);
+ int usableFuel = Mathf.Max(availableFuel - _config.KeepFuelReserve.Value, 0);
+
+ if (usableFuel <= 0)
+ continue;
+
+ int amountToTake = Mathf.Min(requestedAmount - consumed, usableFuel);
+
+ inventory.RemoveItem(fuelName, amountToTake);
+ consumed += amountToTake;
+
+ _log.Debug(
+ $"Container '{candidate.Container.name}' ({candidate.Distance:0.0}m): " +
+ $"consumed={amountToTake}x {fuelName}"
+ );
+ }
+
+ return consumed;
+ }
+ }
+}
diff --git a/Logging/ModLogger.cs b/Logging/ModLogger.cs
new file mode 100644
index 0000000..438980f
--- /dev/null
+++ b/Logging/ModLogger.cs
@@ -0,0 +1,40 @@
+using BepInEx.Logging;
+
+namespace AutoRefillFires
+{
+ internal class ModLogger
+ {
+ private readonly ModConfig _config;
+ private readonly ManualLogSource _logger;
+
+ public ModLogger(ModConfig config, ManualLogSource logger)
+ {
+ _config = config;
+ _logger = logger;
+ }
+
+ public void Debug(string message)
+ {
+ if (_config.LogLevel.Value >= ModLogLevel.Debug)
+ _logger.LogInfo($"[DEBUG] {message}");
+ }
+
+ public void Info(string message)
+ {
+ if (_config.LogLevel.Value >= ModLogLevel.Info)
+ _logger.LogInfo(message);
+ }
+
+ public void Warning(string message)
+ {
+ if (_config.LogLevel.Value >= ModLogLevel.Warning)
+ _logger.LogWarning(message);
+ }
+
+ public void Error(string message)
+ {
+ if (_config.LogLevel.Value >= ModLogLevel.Error)
+ _logger.LogError(message);
+ }
+ }
+}
diff --git a/Models/ContainerCandidate.cs b/Models/ContainerCandidate.cs
new file mode 100644
index 0000000..601e50c
--- /dev/null
+++ b/Models/ContainerCandidate.cs
@@ -0,0 +1,8 @@
+namespace AutoRefillFires
+{
+ internal class ContainerCandidate
+ {
+ public Container Container;
+ public float Distance;
+ }
+}
diff --git a/Models/FireplaceCandidate.cs b/Models/FireplaceCandidate.cs
new file mode 100644
index 0000000..9fb1a0c
--- /dev/null
+++ b/Models/FireplaceCandidate.cs
@@ -0,0 +1,9 @@
+namespace AutoRefillFires
+{
+ internal class FireplaceCandidate
+ {
+ public Fireplace Fireplace;
+ public float Distance;
+ public float FuelPercent;
+ }
+}
diff --git a/Plugin.cs b/Plugin.cs
index 5e6e2b9..942eb90 100644
--- a/Plugin.cs
+++ b/Plugin.cs
@@ -1,222 +1,34 @@
using BepInEx;
-using BepInEx.Configuration;
-using System.Collections.Generic;
using UnityEngine;
-using static UnityEngine.GraphicsBuffer;
namespace AutoRefillFires
{
- public enum ModLogLevel
- {
- None = 0,
- Error = 1,
- Warning = 2,
- Info = 3,
- Debug = 4
- }
- public enum FuelSourcePriority
- {
- PlayerFirst,
- ContainersFirst
- }
-
- [BepInPlugin(
- Plugin.PluginGuid,
- Plugin.PluginName,
- Plugin.PluginVersion
- )]
+ [BepInPlugin(Plugin.PluginGuid, Plugin.PluginName, Plugin.PluginVersion)]
public class Plugin : BaseUnityPlugin
{
public const string PluginGuid = "simplifydave.autorefillfires";
public const string PluginName = "Auto Refill Fires";
public const string PluginVersion = VersionInfo.Version;
- private ConfigEntry _radius;
- private ConfigEntry _checkInterval;
- private ConfigEntry _refillBelowPercent;
+ private ModConfig _config;
+ private ModLogger _log;
+ private RefillManager _refillManager;
- private ConfigEntry _useNearbyContainers;
- private ConfigEntry _containerRadius;
-
- private ConfigEntry _refillToMax;
- private ConfigEntry _refillAmount;
- private ConfigEntry _fillCampfires;
- private ConfigEntry _fillHearths;
- private ConfigEntry _fillStandingTorches;
- private ConfigEntry _fillWallTorches;
- private ConfigEntry _fillBraziers;
- private ConfigEntry _fillBonfires;
- private ConfigEntry _fillHotTubs;
- private ConfigEntry _fillOtherFireplaces;
- private ConfigEntry _keepFuelReserve;
- private ConfigEntry _fuelSourcePriority;
- private ConfigEntry _onlyRefillOwnPieces;
- private ConfigEntry _toggleHotkey;
- private ConfigEntry _logLevel;
private bool _modEnabled = true;
-
private float _nextCheckTime;
- private class ContainerCandidate
- {
- public Container Container;
- public float Distance;
- }
- private class FireplaceCandidate
- {
- public Fireplace Fireplace;
- public float Distance;
- public float FuelPercent;
- }
-
private void Awake()
{
- _radius = Config.Bind(
- "General",
- "Radius",
- 20f,
- "Refill fireplaces within this radius around the player."
- );
-
- _checkInterval = Config.Bind(
- "General",
- "CheckInterval",
- 3f,
- "How often fireplaces are checked, in seconds."
- );
-
- _refillBelowPercent = Config.Bind(
- "General",
- "RefillBelowPercent",
- 0.75f,
- "Refill when fuel drops below this percentage."
- );
-
- _refillToMax = Config.Bind(
- "General",
- "RefillToMax",
- true,
- "If true, refill fireplaces to maximum fuel. If false, add only RefillAmount fuel."
- );
-
- _refillAmount = Config.Bind(
- "General",
- "RefillAmount",
- 5,
- "Amount of fuel to add when RefillToMax is false."
- );
-
- _useNearbyContainers = Config.Bind(
- "Containers",
- "UseNearbyContainers",
- false,
- "Allow plugin to take fuel from nearby containers."
- );
-
- _containerRadius = Config.Bind(
- "Containers",
- "ContainerRadius",
- 10f,
- "Maximum distance from the fireplace to search for containers."
- );
-
- _fillCampfires = Config.Bind(
- "Fireplace Types",
- "FillCampfires",
- true,
- "Automatically refill campfires."
- );
-
- _fillHearths = Config.Bind(
- "Fireplace Types",
- "FillHearths",
- true,
- "Automatically refill hearths."
- );
-
- _fillStandingTorches = Config.Bind(
- "Fireplace Types",
- "FillStandingTorches",
- true,
- "Automatically refill all standing torches, including colored variants."
- );
-
- _fillWallTorches = Config.Bind(
- "Fireplace Types",
- "FillWallTorches",
- true,
- "Automatically refill all wall torches, including colored variants."
- );
-
- _fillBraziers = Config.Bind(
- "Fireplace Types",
- "FillBraziers",
- true,
- "Automatically refill braziers."
- );
-
- _fillBonfires = Config.Bind(
- "Fireplace Types",
- "FillBonfires",
- true,
- "Automatically refill bonfires."
- );
-
- _fillHotTubs = Config.Bind(
- "Fireplace Types",
- "FillHotTubs",
- true,
- "Automatically refill hot tubs."
- );
-
- _fillOtherFireplaces = Config.Bind(
- "Fireplace Types",
- "FillOtherFireplaces",
- true,
- "Automatically refill unknown or modded Fireplace-based objects."
- );
-
- _toggleHotkey = Config.Bind(
- "General",
- "ToggleHotkey",
- new KeyboardShortcut(KeyCode.F7),
- "Hotkey used to enable or disable automatic refilling."
- );
-
- _keepFuelReserve = Config.Bind(
- "Fuel",
- "KeepFuelReserve",
- 0,
- "Minimum amount of fuel to keep in the source inventory. 10 means the last 10 Wood/Resin will not be used."
- );
-
- _fuelSourcePriority = Config.Bind(
- "Fuel",
- "FuelSourcePriority",
- FuelSourcePriority.PlayerFirst,
- "Select whether player inventory or nearby containers should be used first."
- );
-
- _onlyRefillOwnPieces = Config.Bind(
- "General",
- "OnlyRefillOwnPieces",
- false,
- "If enabled, only refill fireplaces and torches built by the local player."
- );
-
- _logLevel = Config.Bind(
- "Logging",
- "LogLevel",
- ModLogLevel.Info,
- "Logging verbosity. Available values: None, Error, Warning, Info, Debug."
- );
+ _config = new ModConfig(Config);
+ _log = new ModLogger(_config, Logger);
+ _refillManager = new RefillManager(_config, _log);
Logger.LogInfo("Auto Refill Fires loaded!");
}
private void Update()
{
- if (_toggleHotkey.Value.IsDown())
+ if (_config.ToggleHotkey.Value.IsDown())
{
_modEnabled = !_modEnabled;
@@ -226,11 +38,11 @@ namespace AutoRefillFires
try
{
Config.Reload();
- LogInfo("Configuration reloaded.");
+ _log.Info("Configuration reloaded.");
}
catch (System.Exception ex)
{
- LogError($"Failed to reload configuration: {ex}");
+ _log.Error($"Failed to reload configuration: {ex}");
}
}
@@ -241,14 +53,9 @@ namespace AutoRefillFires
Player player = Player.m_localPlayer;
if (player != null)
- {
- player.Message(
- MessageHud.MessageType.Center,
- status
- );
- }
+ player.Message(MessageHud.MessageType.Center, status);
- LogInfo(status);
+ _log.Info(status);
}
if (!_modEnabled)
@@ -257,847 +64,14 @@ namespace AutoRefillFires
if (Time.time < _nextCheckTime)
return;
- _nextCheckTime = Time.time + _checkInterval.Value;
+ _nextCheckTime = Time.time + _config.CheckInterval.Value;
Player localPlayer = Player.m_localPlayer;
if (localPlayer == null)
return;
- RefillNearbyFireplaces(localPlayer);
- }
-
- private bool ShouldRefillFireplace(Fireplace fireplace)
- {
- string objectName = fireplace.gameObject.name
- .Replace("(Clone)", "")
- .ToLowerInvariant();
-
- bool result;
- string matchedRule;
-
- if (objectName.Contains("fire_pit") || objectName.Contains("firepit"))
- {
- result = _fillCampfires.Value;
- matchedRule = "FillCampfires";
- }
- else if (objectName.Contains("hearth"))
- {
- result = _fillHearths.Value;
- matchedRule = "FillHearths";
- }
- else if (objectName.Contains("groundtorch"))
- {
- result = _fillStandingTorches.Value;
- matchedRule = "FillStandingTorches";
- }
- else if (objectName.Contains("walltorch"))
- {
- result = _fillWallTorches.Value;
- matchedRule = "FillWallTorches";
- }
- else if (objectName.Contains("brazier"))
- {
- result = _fillBraziers.Value;
- matchedRule = "FillBraziers";
- }
- else if (objectName.Contains("bonfire"))
- {
- result = _fillBonfires.Value;
- matchedRule = "FillBonfires";
- }
- else if (objectName.Contains("hottub") || objectName.Contains("hot_tub"))
- {
- result = _fillHotTubs.Value;
- matchedRule = "FillHotTubs";
- }
- else
- {
- result = _fillOtherFireplaces.Value;
- matchedRule = "FillOtherFireplaces";
- }
-
- LogDebug(
- $"ShouldRefillFireplace: name='{fireplace.gameObject.name}', " +
- $"normalized='{objectName}', matchedRule={matchedRule}, result={result}"
- );
-
- return result;
- }
-
- private void RefillNearbyFireplaces(Player player)
- {
- Fireplace[] fireplaces =
- Object.FindObjectsByType(
- FindObjectsInactive.Exclude,
- FindObjectsSortMode.None
- );
-
- Smelter[] smelters = null;
-
- if (_fillHotTubs.Value)
- {
- smelters =
- Object.FindObjectsByType(
- FindObjectsInactive.Exclude,
- FindObjectsSortMode.None
- );
- }
-
- Container[] containers = null;
-
- if (_useNearbyContainers.Value)
- {
- containers =
- Object.FindObjectsByType(
- FindObjectsInactive.Exclude,
- FindObjectsSortMode.None
- );
- }
-
- List candidates =
- new List();
-
- foreach (Fireplace fireplace in fireplaces)
- {
- if (fireplace == null)
- continue;
-
- float distance = Vector3.Distance(
- player.transform.position,
- fireplace.transform.position
- );
-
- if (distance > _radius.Value)
- continue;
-
- ZNetView nview =
- fireplace.GetComponent();
-
- if (nview == null)
- nview = fireplace.GetComponentInParent();
-
- if (nview == null || !nview.IsValid())
- continue;
-
- ZDO zdo = nview.GetZDO();
-
- if (zdo == null)
- continue;
-
- float maxFuel =
- fireplace.m_maxFuel;
-
- if (maxFuel <= 0f)
- continue;
-
- float currentFuel =
- zdo.GetFloat(
- ZDOVars.s_fuel,
- 0f
- );
-
- float fuelPercent =
- currentFuel / maxFuel;
-
- candidates.Add(
- new FireplaceCandidate
- {
- Fireplace = fireplace,
- Distance = distance,
- FuelPercent = fuelPercent
- }
- );
- }
-
- /*
- * Priority:
- *
- * 1. Lowest fuel percentage first
- * 2. If equal, nearest fireplace first
- */
- candidates.Sort(
- (a, b) =>
- {
- int fuelCompare =
- a.FuelPercent.CompareTo(
- b.FuelPercent
- );
-
- if (fuelCompare != 0)
- return fuelCompare;
-
- return a.Distance.CompareTo(
- b.Distance
- );
- }
- );
-
- LogDebug(
- $"Scan start: " +
- $"loadedFireplaces={fireplaces.Length}, " +
- $"inRange={candidates.Count}, " +
- $"loadedContainers={(containers != null ? containers.Length : 0)}, " +
- $"radius={_radius.Value:0.0}m"
- );
-
- foreach (FireplaceCandidate candidate in candidates)
- {
- LogDebug(
- $"Priority: " +
- $"name='{candidate.Fireplace.gameObject.name}', " +
- $"fuel={candidate.FuelPercent * 100f:0.0}%, " +
- $"distance={candidate.Distance:0.0}m"
- );
-
- TryRefillFireplace(
- player,
- candidate.Fireplace,
- containers
- );
- }
-
- if (_fillHotTubs.Value && smelters != null)
- {
- RefillNearbyHotTubs(
- player,
- smelters,
- containers
- );
- }
-
- LogDebug("Scan end.");
- }
-
- private void RefillNearbyHotTubs(
- Player player,
- Smelter[] smelters,
- Container[] containers)
- {
- int hotTubsInRange = 0;
-
- foreach (Smelter smelter in smelters)
- {
- if (smelter == null)
- continue;
-
- string objectName =
- smelter.gameObject.name
- .Replace("(Clone)", "")
- .ToLowerInvariant();
-
- if (objectName != "piece_bathtub")
- continue;
-
- float distance = Vector3.Distance(
- player.transform.position,
- smelter.transform.position
- );
-
- if (distance > _radius.Value)
- continue;
-
- hotTubsInRange++;
-
- LogDebug(
- $"Hot tub in range: " +
- $"name='{smelter.gameObject.name}', " +
- $"distance={distance:0.0}m"
- );
-
- TryRefillHotTub(
- player,
- smelter,
- containers
- );
- }
-
- LogDebug(
- $"Hot tub scan end: inRange={hotTubsInRange}"
- );
- }
-
- private void TryRefillHotTub(
- Player player,
- Smelter smelter,
- Container[] containers)
- {
- if (smelter == null)
- return;
-
- ZNetView nview =
- smelter.GetComponent();
-
- if (nview == null)
- nview = smelter.GetComponentInParent();
-
- if (nview == null || !nview.IsValid())
- {
- LogDebug("Hot tub: invalid ZNetView.");
- return;
- }
-
- ZDO zdo = nview.GetZDO();
-
- if (zdo == null)
- {
- LogDebug("Hot tub: ZDO is null.");
- return;
- }
-
- if (
- smelter.m_fuelItem == null ||
- smelter.m_fuelItem.m_itemData == null ||
- smelter.m_fuelItem.m_itemData.m_shared == null
- )
- {
- LogDebug("Hot tub: fuel item is null.");
- return;
- }
-
- string fuelName =
- smelter.m_fuelItem.m_itemData.m_shared.m_name;
-
- float currentFuel =
- zdo.GetFloat(
- ZDOVars.s_fuel,
- 0f
- );
-
- float maxFuel =
- smelter.m_maxFuel;
-
- if (maxFuel <= 0f)
- return;
-
- float fuelPercent =
- currentFuel / maxFuel;
-
- LogDebug(
- $"Hot tub fuel state: " +
- $"fuel='{fuelName}', " +
- $"current={currentFuel:0.00}, " +
- $"max={maxFuel:0.00}, " +
- $"percent={fuelPercent * 100f:0.0}%, " +
- $"threshold={_refillBelowPercent.Value * 100f:0.0}%"
- );
-
- if (fuelPercent >= _refillBelowPercent.Value)
- {
- LogDebug(
- "Skipping hot tub - fuelPercent >= threshold."
- );
-
- return;
- }
-
- int missingFuel =
- Mathf.CeilToInt(
- maxFuel - currentFuel
- );
-
- int requestedFuel =
- _refillToMax.Value
- ? missingFuel
- : Mathf.Min(
- _refillAmount.Value,
- missingFuel
- );
-
- if (requestedFuel <= 0)
- return;
-
- LogDebug(
- $"Hot tub refill requested: " +
- $"missing={missingFuel}, " +
- $"requested={requestedFuel}"
- );
-
- int fuelConsumed =
- ConsumeFuel(
- player,
- smelter.transform,
- fuelName,
- requestedFuel,
- containers
- );
-
- if (fuelConsumed <= 0)
- {
- LogDebug(
- "Hot tub refill stopped: no usable fuel."
- );
-
- return;
- }
-
- for (int i = 0; i < fuelConsumed; i++)
- {
- nview.InvokeRPC(
- "RPC_AddFuel",
- new object[0]
- );
- }
-
- LogInfo(
- $"Refilled hot tub: " +
- $"{currentFuel:0.0}/{maxFuel:0.0}, " +
- $"fuel={fuelName}, " +
- $"added={fuelConsumed}, " +
- $"mode={(_refillToMax.Value ? "max" : "fixed")}"
- );
- }
-
- private bool IsOwnPiece(Player player, Fireplace fireplace)
- {
- Piece piece = fireplace.GetComponent();
-
- if (piece == null)
- piece = fireplace.GetComponentInParent();
-
- if (piece == null)
- {
- // Unknown/modded fireplace without a Piece component.
- // Don't block it.
- return true;
- }
-
- return piece.GetCreator() == player.GetPlayerID();
- }
-
- private void TryRefillFireplace(Player player, Fireplace fireplace, Container[] containers)
- {
- LogDebug($"TryRefillFireplace START: '{fireplace.gameObject.name}'");
-
- if (!ShouldRefillFireplace(fireplace))
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - type/config filter returned false."
- );
- return;
- }
-
- if (_onlyRefillOwnPieces.Value && !IsOwnPiece(player, fireplace))
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - not owned by local player."
- );
- return;
- }
-
- ZNetView nview = fireplace.GetComponent();
-
- if (nview == null)
- nview = fireplace.GetComponentInParent();
-
- if (nview == null)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - no ZNetView found."
- );
- return;
- }
-
- if (!nview.IsValid())
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - ZNetView is not valid."
- );
- return;
- }
-
- ZDO zdo = nview.GetZDO();
-
- if (zdo == null)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - ZDO is null."
- );
- return;
- }
-
- if (fireplace.m_fuelItem == null)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - m_fuelItem is null."
- );
- return;
- }
-
- float currentFuel = zdo.GetFloat(ZDOVars.s_fuel, 0f);
- float maxFuel = fireplace.m_maxFuel;
-
- if (maxFuel <= 0f)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - maxFuel <= 0 ({maxFuel})."
- );
- return;
- }
-
- float fuelPercent = currentFuel / maxFuel;
-
- LogDebug(
- $"Fuel state for '{fireplace.gameObject.name}': " +
- $"currentFuel={currentFuel:0.0}, maxFuel={maxFuel:0.0}, " +
- $"fuelPercent={fuelPercent:0.00}, threshold={_refillBelowPercent.Value:0.00}"
- );
-
- if (fuelPercent >= _refillBelowPercent.Value)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - fuelPercent >= threshold."
- );
- return;
- }
-
- string fuelName =
- fireplace.m_fuelItem.m_itemData.m_shared.m_name;
-
- int missingFuel =
- Mathf.CeilToInt(maxFuel - currentFuel);
-
- if (missingFuel <= 0)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - missingFuel <= 0 ({missingFuel})."
- );
- return;
- }
-
- int requestedFuel;
-
- if (_refillToMax.Value)
- {
- requestedFuel = missingFuel;
- }
- else
- {
- requestedFuel = Mathf.Min(
- Mathf.Max(_refillAmount.Value, 0),
- missingFuel
- );
- }
-
- LogDebug(
- $"Refill request for '{fireplace.gameObject.name}': " +
- $"fuel='{fuelName}', missingFuel={missingFuel}, requestedFuel={requestedFuel}, " +
- $"refillMode={(_refillToMax.Value ? "max" : "fixed")}"
- );
-
- if (requestedFuel <= 0)
- {
- LogDebug(
- $"Skipping '{fireplace.gameObject.name}' - requestedFuel <= 0."
- );
- return;
- }
-
- int fuelAdded = 0;
-
- int fuelConsumed = ConsumeFuel(
- player,
- fireplace.transform,
- fuelName,
- requestedFuel,
- containers
- );
-
- if (fuelConsumed <= 0)
- {
- LogDebug(
- $"No usable fuel found for '{fireplace.gameObject.name}'."
- );
-
- return;
- }
-
- for (int i = 0; i < fuelConsumed; i++)
- {
- nview.InvokeRPC(
- "RPC_AddFuel",
- new object[0]
- );
- }
-
- LogInfo(
- $"Refilled {fireplace.name}: " +
- $"{currentFuel:0.0}/{maxFuel:0.0}, " +
- $"fuel={fuelName}, " +
- $"added={fuelConsumed}, " +
- $"mode={(_refillToMax.Value ? "max" : "fixed")}"
- );
-
- if (fuelAdded > 0)
- {
- LogInfo(
- $"Refilled {fireplace.name}: " +
- $"{currentFuel:0.0}/{maxFuel:0.0}, " +
- $"fuel={fuelName}, " +
- $"added={fuelAdded}, " +
- $"mode={(_refillToMax.Value ? "max" : "fixed")}"
- );
- }
- }
-
- private int ConsumeFuelFromPlayer(Player player, string fuelName, int requestedAmount)
- {
- if (requestedAmount <= 0)
- return 0;
-
- Inventory inventory = player.GetInventory();
-
- if (inventory == null)
- {
- LogDebug("Player inventory is null.");
- return 0;
- }
-
- int availableFuel =
- inventory.CountItems(fuelName);
-
- int usableFuel =
- Mathf.Max(
- availableFuel - _keepFuelReserve.Value,
- 0
- );
-
- int amountToTake =
- Mathf.Min(
- requestedAmount,
- usableFuel
- );
-
- if (amountToTake <= 0)
- {
- LogDebug(
- $"Player inventory: fuel='{fuelName}', " +
- $"available={availableFuel}, " +
- $"reserve={_keepFuelReserve.Value}, " +
- $"usable=0"
- );
-
- return 0;
- }
-
- inventory.RemoveItem(
- fuelName,
- amountToTake
- );
-
- LogDebug(
- $"Player inventory: consumed={amountToTake}x {fuelName}, " +
- $"before={availableFuel}, " +
- $"remaining={availableFuel - amountToTake}"
- );
-
- return amountToTake;
- }
-
- private int ConsumeFuel(
- Player player,
- Transform target,
- string fuelName,
- int requestedAmount,
- Container[] containers)
- {
- if (requestedAmount <= 0)
- return 0;
-
- LogDebug(
- $"Fuel request: " +
- $"fuel='{fuelName}', " +
- $"requested={requestedAmount}, " +
- $"priority={_fuelSourcePriority.Value}, " +
- $"containers={_useNearbyContainers.Value}, " +
- $"reserve={_keepFuelReserve.Value}"
- );
-
- int consumed = 0;
-
- if (_fuelSourcePriority.Value == FuelSourcePriority.ContainersFirst)
- {
- if (_useNearbyContainers.Value && containers != null)
- {
- consumed += ConsumeFuelFromNearbyContainers(
- target,
- fuelName,
- requestedAmount - consumed,
- containers
- );
- }
-
- if (consumed < requestedAmount)
- {
- consumed += ConsumeFuelFromPlayer(
- player,
- fuelName,
- requestedAmount - consumed
- );
- }
- }
- else
- {
- consumed += ConsumeFuelFromPlayer(
- player,
- fuelName,
- requestedAmount
- );
-
- if (
- _useNearbyContainers.Value &&
- containers != null &&
- consumed < requestedAmount
- )
- {
- consumed += ConsumeFuelFromNearbyContainers(
- target,
- fuelName,
- requestedAmount - consumed,
- containers
- );
- }
- }
-
- LogDebug(
- $"Fuel request result: " +
- $"fuel='{fuelName}', " +
- $"requested={requestedAmount}, " +
- $"consumed={consumed}"
- );
-
- return consumed;
- }
-
- private int ConsumeFuelFromNearbyContainers(
- Transform target,
- string fuelName,
- int requestedAmount,
- Container[] containers)
- {
- if (requestedAmount <= 0)
- return 0;
-
- if (containers == null || containers.Length == 0)
- return 0;
-
- List candidates =
- new List();
-
- foreach (Container container in containers)
- {
- if (container == null)
- continue;
-
- float distance = Vector3.Distance(
- target.position,
- container.transform.position
- );
-
- if (distance > _containerRadius.Value)
- continue;
-
- Inventory inventory =
- container.GetInventory();
-
- if (inventory == null)
- continue;
-
- int availableFuel =
- inventory.CountItems(fuelName);
-
- int usableFuel =
- Mathf.Max(
- availableFuel - _keepFuelReserve.Value,
- 0
- );
-
- if (usableFuel <= 0)
- continue;
-
- candidates.Add(
- new ContainerCandidate
- {
- Container = container,
- Distance = distance
- }
- );
- }
-
- candidates.Sort(
- (a, b) => a.Distance.CompareTo(b.Distance)
- );
-
- int consumed = 0;
-
- foreach (ContainerCandidate candidate in candidates)
- {
- if (consumed >= requestedAmount)
- break;
-
- Inventory inventory =
- candidate.Container.GetInventory();
-
- if (inventory == null)
- continue;
-
- int availableFuel =
- inventory.CountItems(fuelName);
-
- int usableFuel =
- Mathf.Max(
- availableFuel - _keepFuelReserve.Value,
- 0
- );
-
- if (usableFuel <= 0)
- continue;
-
- int amountToTake =
- Mathf.Min(
- requestedAmount - consumed,
- usableFuel
- );
-
- inventory.RemoveItem(
- fuelName,
- amountToTake
- );
-
- consumed += amountToTake;
-
- LogDebug(
- $"Container '{candidate.Container.name}' " +
- $"({candidate.Distance:0.0}m): " +
- $"consumed={amountToTake}x {fuelName}"
- );
- }
-
- return consumed;
- }
-
- private void LogDebug(string message)
- {
- if (_logLevel.Value >= ModLogLevel.Debug)
- Logger.LogInfo($"[DEBUG] {message}");
- }
-
- private void LogInfo(string message)
- {
- if (_logLevel.Value >= ModLogLevel.Info)
- Logger.LogInfo(message);
- }
-
- private void LogWarning(string message)
- {
- if (_logLevel.Value >= ModLogLevel.Warning)
- Logger.LogWarning(message);
- }
-
- private void LogError(string message)
- {
- if (_logLevel.Value >= ModLogLevel.Error)
- Logger.LogError(message);
+ _refillManager.Run(localPlayer);
}
}
-}
\ No newline at end of file
+}
diff --git a/Refill/FireplaceRefiller.cs b/Refill/FireplaceRefiller.cs
new file mode 100644
index 0000000..dc6f3f4
--- /dev/null
+++ b/Refill/FireplaceRefiller.cs
@@ -0,0 +1,195 @@
+using UnityEngine;
+
+namespace AutoRefillFires
+{
+ internal class FireplaceRefiller
+ {
+ private readonly ModConfig _config;
+ private readonly FuelManager _fuelManager;
+ private readonly ModLogger _log;
+
+ public FireplaceRefiller(ModConfig config, FuelManager fuelManager, ModLogger log)
+ {
+ _config = config;
+ _fuelManager = fuelManager;
+ _log = log;
+ }
+
+ public void TryRefill(Player player, Fireplace fireplace, Container[] containers)
+ {
+ _log.Debug($"TryRefillFireplace START: '{fireplace.gameObject.name}'");
+
+ if (!ShouldRefillFireplace(fireplace))
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - type/config filter returned false.");
+ return;
+ }
+
+ if (_config.OnlyRefillOwnPieces.Value && !IsOwnPiece(player, fireplace))
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - not owned by local player.");
+ return;
+ }
+
+ ZNetView nview = fireplace.GetComponent();
+
+ if (nview == null)
+ nview = fireplace.GetComponentInParent();
+
+ if (nview == null)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - no ZNetView found.");
+ return;
+ }
+
+ if (!nview.IsValid())
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - ZNetView is not valid.");
+ return;
+ }
+
+ ZDO zdo = nview.GetZDO();
+
+ if (zdo == null)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - ZDO is null.");
+ return;
+ }
+
+ if (fireplace.m_fuelItem == null || fireplace.m_fuelItem.m_itemData == null || fireplace.m_fuelItem.m_itemData.m_shared == null)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - fuel item is null.");
+ return;
+ }
+
+ float currentFuel = zdo.GetFloat(ZDOVars.s_fuel, 0f);
+ float maxFuel = fireplace.m_maxFuel;
+
+ if (maxFuel <= 0f)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - maxFuel <= 0 ({maxFuel}).");
+ return;
+ }
+
+ float fuelPercent = currentFuel / maxFuel;
+
+ _log.Debug(
+ $"Fuel state for '{fireplace.gameObject.name}': " +
+ $"currentFuel={currentFuel:0.0}, maxFuel={maxFuel:0.0}, " +
+ $"fuelPercent={fuelPercent:0.00}, threshold={_config.RefillBelowPercent.Value:0.00}"
+ );
+
+ if (fuelPercent >= _config.RefillBelowPercent.Value)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - fuelPercent >= threshold.");
+ return;
+ }
+
+ string fuelName = fireplace.m_fuelItem.m_itemData.m_shared.m_name;
+ int missingFuel = Mathf.CeilToInt(maxFuel - currentFuel);
+
+ if (missingFuel <= 0)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - missingFuel <= 0 ({missingFuel}).");
+ return;
+ }
+
+ int requestedFuel = _config.RefillToMax.Value
+ ? missingFuel
+ : Mathf.Min(Mathf.Max(_config.RefillAmount.Value, 0), missingFuel);
+
+ _log.Debug(
+ $"Refill request for '{fireplace.gameObject.name}': " +
+ $"fuel='{fuelName}', missingFuel={missingFuel}, requestedFuel={requestedFuel}, " +
+ $"refillMode={(_config.RefillToMax.Value ? "max" : "fixed")}"
+ );
+
+ if (requestedFuel <= 0)
+ {
+ _log.Debug($"Skipping '{fireplace.gameObject.name}' - requestedFuel <= 0.");
+ return;
+ }
+
+ int fuelConsumed = _fuelManager.ConsumeFuel(player, fireplace.transform, fuelName, requestedFuel, containers);
+
+ if (fuelConsumed <= 0)
+ {
+ _log.Debug($"No usable fuel found for '{fireplace.gameObject.name}'.");
+ return;
+ }
+
+ for (int i = 0; i < fuelConsumed; i++)
+ nview.InvokeRPC("RPC_AddFuel", new object[0]);
+
+ _log.Info(
+ $"Refilled {fireplace.name}: {currentFuel:0.0}/{maxFuel:0.0}, " +
+ $"fuel={fuelName}, added={fuelConsumed}, " +
+ $"mode={(_config.RefillToMax.Value ? "max" : "fixed")}"
+ );
+ }
+
+ private bool ShouldRefillFireplace(Fireplace fireplace)
+ {
+ string objectName = fireplace.gameObject.name.Replace("(Clone)", "").ToLowerInvariant();
+
+ bool result;
+ string matchedRule;
+
+ if (objectName.Contains("fire_pit") || objectName.Contains("firepit"))
+ {
+ result = _config.FillCampfires.Value;
+ matchedRule = "FillCampfires";
+ }
+ else if (objectName.Contains("hearth"))
+ {
+ result = _config.FillHearths.Value;
+ matchedRule = "FillHearths";
+ }
+ else if (objectName.Contains("groundtorch"))
+ {
+ result = _config.FillStandingTorches.Value;
+ matchedRule = "FillStandingTorches";
+ }
+ else if (objectName.Contains("walltorch"))
+ {
+ result = _config.FillWallTorches.Value;
+ matchedRule = "FillWallTorches";
+ }
+ else if (objectName.Contains("brazier"))
+ {
+ result = _config.FillBraziers.Value;
+ matchedRule = "FillBraziers";
+ }
+ else if (objectName.Contains("bonfire"))
+ {
+ result = _config.FillBonfires.Value;
+ matchedRule = "FillBonfires";
+ }
+ else
+ {
+ result = _config.FillOtherFireplaces.Value;
+ matchedRule = "FillOtherFireplaces";
+ }
+
+ _log.Debug(
+ $"ShouldRefillFireplace: name='{fireplace.gameObject.name}', " +
+ $"normalized='{objectName}', matchedRule={matchedRule}, result={result}"
+ );
+
+ return result;
+ }
+
+ private bool IsOwnPiece(Player player, Fireplace fireplace)
+ {
+ Piece piece = fireplace.GetComponent();
+
+ if (piece == null)
+ piece = fireplace.GetComponentInParent();
+
+ if (piece == null)
+ return true;
+
+ return piece.GetCreator() == player.GetPlayerID();
+ }
+ }
+}
diff --git a/Refill/HotTubRefiller.cs b/Refill/HotTubRefiller.cs
new file mode 100644
index 0000000..8e7bdd3
--- /dev/null
+++ b/Refill/HotTubRefiller.cs
@@ -0,0 +1,127 @@
+using UnityEngine;
+
+namespace AutoRefillFires
+{
+ internal class HotTubRefiller
+ {
+ private readonly ModConfig _config;
+ private readonly FuelManager _fuelManager;
+ private readonly ModLogger _log;
+
+ public HotTubRefiller(ModConfig config, FuelManager fuelManager, ModLogger log)
+ {
+ _config = config;
+ _fuelManager = fuelManager;
+ _log = log;
+ }
+
+ public void RefillNearby(Player player, Smelter[] smelters, Container[] containers)
+ {
+ int hotTubsInRange = 0;
+
+ foreach (Smelter smelter in smelters)
+ {
+ if (smelter == null)
+ continue;
+
+ string objectName = smelter.gameObject.name.Replace("(Clone)", "").ToLowerInvariant();
+
+ if (objectName != "piece_bathtub")
+ continue;
+
+ float distance = Vector3.Distance(player.transform.position, smelter.transform.position);
+
+ if (distance > _config.Radius.Value)
+ continue;
+
+ hotTubsInRange++;
+
+ _log.Debug(
+ $"Hot tub in range: name='{smelter.gameObject.name}', " +
+ $"distance={distance:0.0}m"
+ );
+
+ TryRefillHotTub(player, smelter, containers);
+ }
+
+ _log.Debug($"Hot tub scan end: inRange={hotTubsInRange}");
+ }
+
+ private void TryRefillHotTub(Player player, Smelter smelter, Container[] containers)
+ {
+ if (smelter == null)
+ return;
+
+ ZNetView nview = smelter.GetComponent();
+
+ if (nview == null)
+ nview = smelter.GetComponentInParent();
+
+ if (nview == null || !nview.IsValid())
+ {
+ _log.Debug("Hot tub: invalid ZNetView.");
+ return;
+ }
+
+ ZDO zdo = nview.GetZDO();
+
+ if (zdo == null)
+ {
+ _log.Debug("Hot tub: ZDO is null.");
+ return;
+ }
+
+ if (smelter.m_fuelItem == null || smelter.m_fuelItem.m_itemData == null || smelter.m_fuelItem.m_itemData.m_shared == null)
+ {
+ _log.Debug("Hot tub: fuel item is null.");
+ return;
+ }
+
+ string fuelName = smelter.m_fuelItem.m_itemData.m_shared.m_name;
+ float currentFuel = zdo.GetFloat(ZDOVars.s_fuel, 0f);
+ float maxFuel = smelter.m_maxFuel;
+
+ if (maxFuel <= 0f)
+ return;
+
+ float fuelPercent = currentFuel / maxFuel;
+
+ _log.Debug(
+ $"Hot tub fuel state: fuel='{fuelName}', " +
+ $"current={currentFuel:0.00}, max={maxFuel:0.00}, " +
+ $"percent={fuelPercent * 100f:0.0}%, threshold={_config.RefillBelowPercent.Value * 100f:0.0}%"
+ );
+
+ if (fuelPercent >= _config.RefillBelowPercent.Value)
+ {
+ _log.Debug("Skipping hot tub - fuelPercent >= threshold.");
+ return;
+ }
+
+ int missingFuel = Mathf.CeilToInt(maxFuel - currentFuel);
+ int requestedFuel = _config.RefillToMax.Value ? missingFuel : Mathf.Min(_config.RefillAmount.Value, missingFuel);
+
+ if (requestedFuel <= 0)
+ return;
+
+ _log.Debug($"Hot tub refill requested: missing={missingFuel}, requested={requestedFuel}");
+
+ int fuelConsumed = _fuelManager.ConsumeFuel(player, smelter.transform, fuelName, requestedFuel, containers);
+
+ if (fuelConsumed <= 0)
+ {
+ _log.Debug("Hot tub refill stopped: no usable fuel.");
+ return;
+ }
+
+ for (int i = 0; i < fuelConsumed; i++)
+ nview.InvokeRPC("RPC_AddFuel", new object[0]);
+
+ _log.Info(
+ $"Refilled hot tub: {currentFuel:0.0}/{maxFuel:0.0}, " +
+ $"fuel={fuelName}, added={fuelConsumed}, " +
+ $"mode={(_config.RefillToMax.Value ? "max" : "fixed")}"
+ );
+ }
+ }
+}
diff --git a/Refill/RefillManager.cs b/Refill/RefillManager.cs
new file mode 100644
index 0000000..2b4aaf8
--- /dev/null
+++ b/Refill/RefillManager.cs
@@ -0,0 +1,112 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace AutoRefillFires
+{
+ internal class RefillManager
+ {
+ private readonly ModConfig _config;
+ private readonly FireplaceRefiller _fireplaceRefiller;
+ private readonly HotTubRefiller _hotTubRefiller;
+ private readonly ModLogger _log;
+
+ public RefillManager(ModConfig config, ModLogger log)
+ {
+ _config = config;
+ _log = log;
+
+ FuelManager fuelManager = new FuelManager(_config, _log);
+ _fireplaceRefiller = new FireplaceRefiller(_config, fuelManager, _log);
+ _hotTubRefiller = new HotTubRefiller(_config, fuelManager, _log);
+ }
+
+ public void Run(Player player)
+ {
+ Fireplace[] fireplaces = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
+ Smelter[] smelters = null;
+
+ if (_config.FillHotTubs.Value)
+ smelters = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
+
+ Container[] containers = null;
+
+ if (_config.UseNearbyContainers.Value)
+ containers = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
+
+ List candidates = new List();
+
+ foreach (Fireplace fireplace in fireplaces)
+ {
+ if (fireplace == null)
+ continue;
+
+ float distance = Vector3.Distance(player.transform.position, fireplace.transform.position);
+
+ if (distance > _config.Radius.Value)
+ continue;
+
+ ZNetView nview = fireplace.GetComponent();
+
+ if (nview == null)
+ nview = fireplace.GetComponentInParent();
+
+ if (nview == null || !nview.IsValid())
+ continue;
+
+ ZDO zdo = nview.GetZDO();
+
+ if (zdo == null)
+ continue;
+
+ float maxFuel = fireplace.m_maxFuel;
+
+ if (maxFuel <= 0f)
+ continue;
+
+ float currentFuel = zdo.GetFloat(ZDOVars.s_fuel, 0f);
+ float fuelPercent = currentFuel / maxFuel;
+
+ candidates.Add(new FireplaceCandidate
+ {
+ Fireplace = fireplace,
+ Distance = distance,
+ FuelPercent = fuelPercent
+ });
+ }
+
+ // Priority: lowest fuel percentage first, then nearest fireplace.
+ candidates.Sort((a, b) =>
+ {
+ int fuelCompare = a.FuelPercent.CompareTo(b.FuelPercent);
+
+ if (fuelCompare != 0)
+ return fuelCompare;
+
+ return a.Distance.CompareTo(b.Distance);
+ });
+
+ _log.Debug(
+ $"Scan start: loadedFireplaces={fireplaces.Length}, " +
+ $"inRange={candidates.Count}, " +
+ $"loadedContainers={(containers != null ? containers.Length : 0)}, " +
+ $"radius={_config.Radius.Value:0.0}m"
+ );
+
+ foreach (FireplaceCandidate candidate in candidates)
+ {
+ _log.Debug(
+ $"Priority: name='{candidate.Fireplace.gameObject.name}', " +
+ $"fuel={candidate.FuelPercent * 100f:0.0}%, " +
+ $"distance={candidate.Distance:0.0}m"
+ );
+
+ _fireplaceRefiller.TryRefill(player, candidate.Fireplace, containers);
+ }
+
+ if (_config.FillHotTubs.Value && smelters != null)
+ _hotTubRefiller.RefillNearby(player, smelters, containers);
+
+ _log.Debug("Scan end.");
+ }
+ }
+}