Skip to content

Commit f2e2d56

Browse files
committed
2 parents ebe1fbc + 778d0b5 commit f2e2d56

37 files changed

+1099
-435
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Timberborn Mod: Timberborn Commons
2+
// Author: igor.zavoychinskiy@gmail.com
3+
// License: Public Domain
4+
5+
using System.Reflection;
6+
using Bindito.Core;
7+
using TimberApi.ConfiguratorSystem;
8+
using TimberApi.SceneSystem;
9+
using Timberborn.PrefabOptimization;
10+
using UnityDev.Utils.LogUtilsLite;
11+
12+
namespace IgorZ.TimberCommons.Common {
13+
14+
[Configurator(SceneEntrypoint.All)]
15+
// ReSharper disable once UnusedType.Global
16+
sealed class Configurator : IConfigurator {
17+
public void Configure(IContainerDefinition containerDefinition) {
18+
if (Features.DebugExVerboseLogging && DebugEx.LoggingSettings.VerbosityLevel < 5) {
19+
DebugEx.LoggingSettings.VerbosityLevel = 5;
20+
}
21+
if (Features.PrefabOptimizerMaxExpectedRegistrySize != -1) {
22+
var fieldInfo = typeof(EnvironmentVertexColorPrefabOptimizer).GetField(
23+
"MaxExpectedRegistrySize", BindingFlags.Static | BindingFlags.NonPublic);
24+
if (fieldInfo != null) {
25+
fieldInfo.SetValue(null, Features.PrefabOptimizerMaxExpectedRegistrySize);
26+
} else {
27+
DebugEx.Warning("Cannot override EnvironmentVertexColorPrefabOptimizer.MaxExpectedRegistrySize");
28+
}
29+
}
30+
}
31+
}
32+
33+
}

TimberCommons/Common/Features.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Timberborn Mod: Timberborn Commons
2+
// Author: igor.zavoychinskiy@gmail.com
3+
// License: Public Domain
4+
5+
using IgorZ.TimberDev.UI;
6+
using IgorZ.TimberDev.Utils;
7+
using Timberborn.GoodConsumingBuildingSystem;
8+
using UnityDev.Utils.LogUtilsLite;
9+
10+
namespace IgorZ.TimberCommons.Common {
11+
12+
static class Features {
13+
/// <summary>Indicates that <see cref="DebugEx.Fine"/> methods should emit record to the log.</summary>
14+
public static bool DebugExVerboseLogging;
15+
16+
/// <summary>
17+
/// Indicates that duration in the "supply lasts for" message on <see cref="GoodConsumingBuilding"/> UI should be
18+
/// formatted as "Xd Yh" instead of "XX hours".
19+
/// </summary>
20+
/// <seealso cref="CommonFormats.DaysHoursFormat"/>
21+
public static bool GoodConsumingBuildingUIDaysHoursForAll;
22+
23+
/// <summary>
24+
/// Indicates that durations for the growth time for all growables should be formatted as "Xd Yh" instead of rounding
25+
/// to days.
26+
/// </summary>
27+
/// <seealso cref="CommonFormats.DaysHoursFormat"/>
28+
public static bool GrowableGrowthTimeUIDaysHoursViewForAll;
29+
30+
/// <summary>Indicates whether recipe durations exceeding 24 hours should be displayed in days/hours format.</summary>
31+
/// <seealso cref="CommonFormats.DaysHoursFormat"/>
32+
public static bool ShowDaysHoursForSlowRecipes;
33+
34+
/// <summary>
35+
/// Specifies whether fuel rates below 0.1 should be displayed with increased precision in the recipe UI.
36+
/// </summary>
37+
/// <seealso cref="CommonFormats.FormatSmallValue"/>
38+
public static bool ShowLongValueForLowFuelConsumptionRecipes;
39+
40+
/// <summary>Overrides the maximum registry size for PrefabOptimizer to suppress log complaints.</summary>
41+
public static int PrefabOptimizerMaxExpectedRegistrySize = -1;
42+
43+
static Features() {
44+
FeatureController.ReadFeatures(Consume);
45+
}
46+
47+
static bool Consume(string name, bool enabled, string value) {
48+
return name switch {
49+
"DebugEx.VerboseLogging" =>
50+
FeatureController.SetFlag(ref DebugExVerboseLogging, name, enabled, value),
51+
"GoodConsumingBuildingUI.DaysHoursViewForAllBuildings" =>
52+
FeatureController.SetFlag(ref GoodConsumingBuildingUIDaysHoursForAll, name, enabled, value),
53+
"GrowableGrowthTimeUI.DaysHoursViewForAllGrowables" =>
54+
FeatureController.SetFlag(ref GrowableGrowthTimeUIDaysHoursViewForAll, name, enabled, value),
55+
"RecipesUI.ShowDaysHoursForSlowRecipes" =>
56+
FeatureController.SetFlag(ref ShowDaysHoursForSlowRecipes, name, enabled, value),
57+
"RecipesUI.ShowLongValueForLowFuelConsumptionRecipes" =>
58+
FeatureController.SetFlag(ref ShowLongValueForLowFuelConsumptionRecipes, name, enabled, value),
59+
"PrefabOptimizer.MaxExpectedRegistrySize" =>
60+
FeatureController.SetValue(ref PrefabOptimizerMaxExpectedRegistrySize, name, enabled, value),
61+
_ => false
62+
};
63+
}
64+
}
65+
66+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Timberborn Mod: Timberborn Commons
2+
// Author: igor.zavoychinskiy@gmail.com
3+
// License: Public Domain
4+
5+
namespace IgorZ.TimberCommons.Common {
6+
7+
/// <summary>Interface that enables "supply lasts for XX" progress bar in UI for manufactories.</summary>
8+
/// <remarks>
9+
/// It's expected that only one component on the building will implement it. If there is such a component and it returns
10+
/// non "null" values, then the UI fragment gets a "progress bar" that tells player how long the buildings would work on
11+
/// the existing inventory reserves.
12+
/// </remarks>
13+
public interface ISupplyLeftProvider {
14+
/// <summary>Returns the stats or indicates that the progress bar should be hidden.</summary>
15+
/// <remarks>
16+
/// This method will be called from the UI fragment code, which happens every frame. Avoid doing expensive stuff in
17+
/// this method.
18+
/// </remarks>
19+
/// <returns>
20+
/// The progress as a value in range [0; 1] and the message to show on the progress bar. The progress must be greater
21+
/// than zero and the message must not be <c>null</c>. If any of these conditions is not met, the progress bar will be
22+
/// hidden in the UI.
23+
/// </returns>
24+
public (float progress, string progressBarMsg) GetStats();
25+
}
26+
27+
}

TimberCommons/IrrigationSystem/BlockContaminationRangeEffect.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Author: igor.zavoychinskiy@gmail.com
33
// License: Public Domain
44

5-
using System.Collections.Generic;
5+
using System.Collections.Immutable;
66
using Bindito.Core;
77
using IgorZ.TimberCommons.WaterService;
88
using Timberborn.BaseComponentSystem;
@@ -24,6 +24,8 @@ public sealed class BlockContaminationRangeEffect : BaseComponent, IRangeEffect
2424

2525
/// <inheritdoc cref="EffectGroup"/>
2626
[SerializeField]
27+
[Tooltip(
28+
"The name by which this effect can be found by the other components. Multiple effects can have the same name.")]
2729
string _effectGroupName = "BlockContamination";
2830

2931
// ReSharper restore InconsistentNaming
@@ -35,7 +37,7 @@ public sealed class BlockContaminationRangeEffect : BaseComponent, IRangeEffect
3537
public string EffectGroup => _effectGroupName;
3638

3739
/// <inheritdoc/>
38-
public void ApplyEffect(IEnumerable<Vector2Int> tiles) {
40+
public void ApplyEffect(ImmutableHashSet<Vector2Int> tiles) {
3941
ResetEffect();
4042
_contaminationOverrideIndex = _directSoilMoistureSystemAccessor.AddContaminationOverride(tiles);
4143
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Timberborn Mod: Timberborn Commons
2+
// Author: igor.zavoychinskiy@gmail.com
3+
// License: Public Domain
4+
5+
using Bindito.Core;
6+
using TimberApi.ConfiguratorSystem;
7+
using TimberApi.SceneSystem;
8+
using Timberborn.Growing;
9+
using Timberborn.TemplateSystem;
10+
11+
namespace IgorZ.TimberCommons.IrrigationSystem {
12+
13+
[Configurator(SceneEntrypoint.InGame)]
14+
// ReSharper disable once UnusedType.Global
15+
sealed class Configurator : IConfigurator {
16+
public void Configure(IContainerDefinition containerDefinition) {
17+
containerDefinition.MultiBind<TemplateModule>().ToProvider(ProvideTemplateModule).AsSingleton();
18+
}
19+
20+
static TemplateModule ProvideTemplateModule() {
21+
var builder = new TemplateModule.Builder();
22+
builder.AddDecorator<Growable, GrowthRateModifier>();
23+
return builder.Build();
24+
}
25+
}
26+
27+
}

TimberCommons/IrrigationSystem/GoodConsumingIrrigationTower.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Timberborn.Buildings;
99
using Timberborn.GoodConsumingBuildingSystem;
1010
using Timberborn.Localization;
11-
using UnityEngine;
1211

1312
namespace IgorZ.TimberCommons.IrrigationSystem {
1413

@@ -50,8 +49,8 @@ protected override bool CanMoisturize() {
5049
}
5150

5251
/// <inheritdoc/>
53-
protected override void IrrigationStarted(IEnumerable<Vector2Int> tiles) {
54-
_rangeEffects.ForEach(x => x.ApplyEffect(tiles));
52+
protected override void IrrigationStarted() {
53+
_rangeEffects.ForEach(x => x.ApplyEffect(ReachableTiles));
5554
}
5655

5756
/// <inheritdoc/>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Timberborn Mod: Timberborn Commons
2+
// Author: igor.zavoychinskiy@gmail.com
3+
// License: Public Domain
4+
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using Bindito.Core;
8+
using Timberborn.BaseComponentSystem;
9+
using Timberborn.Growing;
10+
using Timberborn.TimeSystem;
11+
using UnityDev.Utils.LogUtilsLite;
12+
using UnityEngine;
13+
14+
namespace IgorZ.TimberCommons.IrrigationSystem {
15+
16+
/// <summary>Component that can change the growth rate on the "growable" entities.</summary>
17+
/// <remarks>
18+
/// It's added to every growable entity in the game. Any code can access it and register its own modifications rates.
19+
/// However, only the worst "moderator" and the best "boost" will be considered. The effects are not adding up.
20+
/// </remarks>
21+
/// <seealso cref="ModifyGrowableGrowthRangeEffect"/>
22+
public sealed class GrowthRateModifier : BaseComponent {
23+
24+
#region API properties
25+
// ReSharper disable InconsistentNaming
26+
// ReSharper disable MemberCanBePrivate.Global
27+
28+
/// <summary>The actual modifier to the growth rate as a positive or negative percent value.</summary>
29+
/// <seealso cref="WorstModerator"/>
30+
/// <seealso cref="BestBooster"/>
31+
public float EffectiveModifier { get; private set; }
32+
33+
/// <summary>Indicates that growable is actively growing, so the rate modifier makes sense.</summary>
34+
public bool IsLiveAndGrowing => !_growable.IsGrown && !_growable.IsDead;
35+
36+
/// <summary>Specifies if the growable is growing or has grown at the non-stock rate.</summary>
37+
public bool RateIsModified { get; private set; }
38+
39+
/// <summary>The <i>worst</i> moderator rate modifier.</summary>
40+
public float WorstModerator { get; private set; }
41+
42+
/// <summary>The <i>best</i> boost rate modifier.</summary>
43+
public float BestBooster { get; private set; }
44+
45+
// ReSharper restore InconsistentNaming
46+
// ReSharper restore MemberCanBePrivate.Global
47+
#endregion
48+
49+
#region API methods
50+
51+
/// <summary>Records the owner's request for the growth modification.</summary>
52+
/// <remarks>The final rate applied may be different if there are more than one owners registered.</remarks>
53+
/// <param name="ownerId">A unique ID of the caller that applies the modifier.</param>
54+
/// <param name="modifier">
55+
/// The rate modifer as a positive or negative value in percents. E.g. <c>15.5</c> (+15.5%) or <c>-8.5</c> (-8.5%).
56+
/// </param>
57+
public void RegisterModifier(string ownerId, float modifier) {
58+
if (!IsLiveAndGrowing) {
59+
return;
60+
}
61+
if (modifier > 0) {
62+
_registeredBoosts[ownerId] = modifier;
63+
} else if (modifier < 0) {
64+
_registeredModerators[ownerId] = modifier;
65+
} else {
66+
HostedDebugLog.Warning(this, "Ignoring zero modifier from owner: {0}", ownerId);
67+
}
68+
UpdateRate();
69+
}
70+
71+
/// <summary>Removes any modifications from the owner.</summary>
72+
/// <param name="ownerId">
73+
/// The ID of the owner that applied the modifiers. Only the modifiers applied by this owner will be deleted.
74+
/// </param>
75+
public void UnregisterModifier(string ownerId) {
76+
_registeredBoosts.Remove(ownerId);
77+
_registeredModerators.Remove(ownerId);
78+
UpdateRate();
79+
}
80+
81+
#endregion
82+
83+
#region Implementation
84+
85+
ITimeTriggerFactory _timeTriggerFactory;
86+
Growable _growable;
87+
readonly Dictionary<string, float> _registeredBoosts = new();
88+
readonly Dictionary<string, float> _registeredModerators = new();
89+
float _originalGrowthTimeInDays;
90+
91+
/// <summary>It must be public for the injection logic to work.</summary>
92+
[Inject]
93+
public void InjectDependencies(ITimeTriggerFactory timeTriggerFactory) {
94+
_timeTriggerFactory = timeTriggerFactory;
95+
}
96+
97+
void Awake() {
98+
_growable = GetComponentFast<Growable>();
99+
_originalGrowthTimeInDays = _growable._growthTimeInDays;
100+
}
101+
102+
/// <summary>Calculates the effective multiplier and updates the growable settings.</summary>
103+
/// <seealso cref="EffectiveModifier"/>
104+
void UpdateRate() {
105+
BestBooster = _registeredBoosts.Count > 0 ? _registeredBoosts.Values.Max() : 0f;
106+
WorstModerator = _registeredModerators.Count > 0 ? _registeredModerators.Values.Min() : 0f;
107+
EffectiveModifier = BestBooster + WorstModerator;
108+
if (!IsLiveAndGrowing) {
109+
return; // The growable is in the invalid state.
110+
}
111+
var newGrowthTime = _originalGrowthTimeInDays / (1f + EffectiveModifier / 100f);
112+
var progressDone = _growable.GrowthProgress;
113+
var newTrigger = _timeTriggerFactory.Create(() => _growable.Grow(), newGrowthTime);
114+
newTrigger.FastForwardProgress(progressDone);
115+
_growable._timeTrigger.Reset();
116+
_growable._timeTrigger = newTrigger;
117+
_growable._timeTrigger.Resume();
118+
RateIsModified = Mathf.Abs(_originalGrowthTimeInDays - newGrowthTime) > float.Epsilon;
119+
_growable._growthTimeInDays = newGrowthTime;
120+
}
121+
122+
#endregion
123+
}
124+
125+
}

TimberCommons/IrrigationSystem/IRangeEffect.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Author: igor.zavoychinskiy@gmail.com
33
// License: Public Domain
44

5-
using System.Collections.Generic;
5+
using System.Collections.Immutable;
66
using UnityEngine;
77

88
namespace IgorZ.TimberCommons.IrrigationSystem {
@@ -22,7 +22,7 @@ public interface IRangeEffect {
2222

2323
/// <summary>Applies affect to the specified tiles.</summary>
2424
/// <param name="tiles">The tiles to apply effect to.</param>
25-
public void ApplyEffect(IEnumerable<Vector2Int> tiles);
25+
public void ApplyEffect(ImmutableHashSet<Vector2Int> tiles);
2626

2727
/// <summary>Resets all effects that were applied in the last call to <see cref="ApplyEffect"/></summary>
2828
public void ResetEffect();

0 commit comments

Comments
 (0)