diff --git a/BuildingBlueprints/Services/BlueprintPlacementValidator.cs b/BuildingBlueprints/Services/BlueprintPlacementValidator.cs index 48d1c557..2a76226c 100644 --- a/BuildingBlueprints/Services/BlueprintPlacementValidator.cs +++ b/BuildingBlueprints/Services/BlueprintPlacementValidator.cs @@ -1,13 +1,23 @@ namespace BuildingBlueprints.Services; +#if TIMBERV11 +using Timberborn.ConstructionSites; +#endif + [BindSingleton] public class BlueprintPlacementValidator( IEnumerable placers, EntityService entityService, IBlockService blockService +#if TIMBERV11 + // 1.1: BuildingPlacer.Place lost its result callback; place finished buildings via the factory instead. + , ConstructionFactory constructionFactory +#endif ) { +#if !TIMBERV11 readonly BuildingPlacer buildingPlacer = (BuildingPlacer)placers.First(p => p is BuildingPlacer); +#endif public async Task> ValidatePreviewsAsync(ImmutableArray previews) => (await ValidatePreviews(previews, true)).ValidPreviews; @@ -88,9 +98,20 @@ public async Task>> BuildPreviewsAsync(I Task PlaceAsync(BlockObjectSpec bos, Placement placement) { +#if TIMBERV11 + // Mirror BuildingPlacer's ShouldBePlacedFinished: a normal building is placed UNFINISHED (a + // construction site beavers build); only specs flagged PlaceFinished spawn already-built. + // (Forcing CreateAsFinished here made every pasted blueprint instantly complete — wrong.) + var builder = new EntitySetup.Builder(bos.Blueprint); + var placed = bos.Blueprint.GetSpec().PlaceFinished + ? constructionFactory.CreateAsFinished(builder, placement) + : constructionFactory.CreateAsUnfinished(builder, placement); + return Task.FromResult(placed); +#else TaskCompletionSource tcs = new(); buildingPlacer.Place(bos, placement, result => tcs.SetResult(result)); return tcs.Task; +#endif } bool AlreadyHasPath(Vector3Int position) => diff --git a/GameAssemblyPublicizer/Program.cs b/GameAssemblyPublicizer/Program.cs index bd0c98ba..06aedb82 100644 --- a/GameAssemblyPublicizer/Program.cs +++ b/GameAssemblyPublicizer/Program.cs @@ -2,7 +2,9 @@ using System.Collections.Immutable; ImmutableArray Prefixes = ["Bindito.", "Timberborn.", "Unity"]; -const string GameAssembliesPath = @"D:\Software\SteamLibrary\steamapps\common\Timberborn\Timberborn_Data\Managed"; +// Container/CI-friendly: override via GAME_MANAGED_PATH; falls back to the original local-dev Windows path. +string GameAssembliesPath = Environment.GetEnvironmentVariable("GAME_MANAGED_PATH") + ?? @"D:\Software\SteamLibrary\steamapps\common\Timberborn\Timberborn_Data\Managed"; ImmutableArray SpecialFolders = [ @"D:\Software\SteamLibrary\steamapps\workshop\content\1062090\3283831040\version-1.0\Scripts", // Mod Settings ]; @@ -31,8 +33,18 @@ } } -foreach (var folder in SpecialFolders) +// Extra special folders from env (container/CI), PathSeparator-delimited — e.g. the Mod Settings 1.1 Scripts dir. +var extraDirs = (Environment.GetEnvironmentVariable("EXTRA_PUBLICIZE_DIRS") ?? "") + .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + +foreach (var folder in SpecialFolders.Concat(extraDirs)) { + // Skip missing folders so cross-platform / container runs don't crash on local-dev-only paths. + if (!Directory.Exists(folder)) + { + Console.WriteLine($"Skipping missing special folder: {folder}"); + continue; + } foreach (var file in Directory.EnumerateFiles(folder, "*.dll", SearchOption.AllDirectories)) { PublicizeFile(file, commonOutput); diff --git a/ModdableTimberborn/BonusSystem/PersistentBonusTrackerComponent.cs b/ModdableTimberborn/BonusSystem/PersistentBonusTrackerComponent.cs index d3975278..667cf5d2 100644 --- a/ModdableTimberborn/BonusSystem/PersistentBonusTrackerComponent.cs +++ b/ModdableTimberborn/BonusSystem/PersistentBonusTrackerComponent.cs @@ -1,6 +1,14 @@ namespace ModdableTimberborn.BonusSystem; -public class PersistentBonusTrackerComponent : BaseComponent, IPersistentEntity, IBonusTrackerComponent, IAwakableComponent, IStartableComponent +// 1.1 removed IStartableComponent. This component's only start-time work is replaying bonuses loaded from +// the save (the 1.0 Start() early-returned when nothing was loaded), so the precise 1.1 hook is +// IPostLoadableEntity.PostLoadEntity() — guaranteed to run after Load(), unlike the init phases. +public class PersistentBonusTrackerComponent : BaseComponent, IPersistentEntity, IBonusTrackerComponent, IAwakableComponent, +#if TIMBERV11 + IPostLoadableEntity +#else + IStartableComponent +#endif { static readonly ComponentKey SaveKey = new(nameof(PersistentBonusTrackerComponent)); static readonly ListKey CurrentBonusesKey = new("CurrentBonuses"); @@ -14,7 +22,11 @@ public void Awake() BonusTracker = new(bm); } +#if TIMBERV11 + public void PostLoadEntity() +#else public void Start() +#endif { if (pending is null) { return; } diff --git a/ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.cs b/ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.V1.cs similarity index 100% rename from ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.cs rename to ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.V1.cs diff --git a/ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.V11.cs b/ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.V11.cs new file mode 100644 index 00000000..fbde0cf4 --- /dev/null +++ b/ModdableTimberborn/BuildingSettings/BuiltInSettings/RelaySettings.V11.cs @@ -0,0 +1,66 @@ +using Timberborn.Automation; + +namespace ModdableTimberborn.BuildingSettings.BuiltInSettings; + +// 1.1 reworked Relay from two fixed inputs (InputA/InputB) to a mode-dependent list of N inputs +// (List _inputs; SupportsMultipleInputs per mode). The setting model now carries +// a variable-length Guid array — one entry per input slot — instead of a fixed InputA/InputB pair. +public record RelaySettingsModel( + RelayMode Mode, + Guid?[] Inputs +) : EntityIdModelBase(Inputs); + +public class RelaySettings( + EntityRegistry entityRegistry, + ILoc t +) : EntityIdBuildingSettingsBase(t) +{ + public override string DescribeModel(RelaySettingsModel model) + { + var inputs = model.Inputs.Select(id => entityRegistry.DescribeEntity(id, t)); + var separator = $" {t.T("Building.Relay.Mode." + model.Mode)} "; + + return string.Join(separator, inputs); + } + + protected override bool ApplyModel(RelaySettingsModel model, Relay target) + { + target.SetMode(model.Mode); + + // Grow toward the model's input count, but stop if IncreaseInputs() can't add another slot: + // the current mode caps inputs (e.g. Not/Passthrough allow one), so SetInput(null, index>=1) + // no-ops and the list never grows — without this no-progress break the loop would hang. + while (target._inputs.Count < model.Inputs.Length) + { + var before = target._inputs.Count; + target.IncreaseInputs(); + if (target._inputs.Count == before) { break; } + } + while (target._inputs.Count > model.Inputs.Length) { target.RemoveInput(target._inputs.Count - 1); } + + // Reconnect every existing slot. SetInput(null) is a no-op (Connect ignores null), so a cleared + // input is disconnected explicitly. Slots beyond the model (mode couldn't shrink) are cleared. + for (var i = 0; i < target._inputs.Count; i++) + { + var automator = i < model.Inputs.Length ? ResolveAutomator(model.Inputs[i]) : null; + + if (automator is not null) { target.SetInput(automator, i); } + else { target._inputs[i].Disconnect(); } + } + + target.Evaluate(); + + return true; + } + + protected override RelaySettingsModel GetModel(Relay target) + => new(target.Mode, [.. target._inputs.Select(connection => connection.Transmitter?.GetEntityId())]); + + Automator? ResolveAutomator(Guid? entityId) + { + if (!entityId.HasValue) { return null; } + + var entity = entityRegistry.GetEntity(entityId.Value); + return entity != null && entity.TryGetComponent(out var automator) ? automator : null; + } +} \ No newline at end of file diff --git a/ModdableTimberborn/BuildingSettings/BuiltInSettings/ValveSettings.cs b/ModdableTimberborn/BuildingSettings/BuiltInSettings/ValveSettings.cs index 0ef2e21d..f7a42bfc 100644 --- a/ModdableTimberborn/BuildingSettings/BuiltInSettings/ValveSettings.cs +++ b/ModdableTimberborn/BuildingSettings/BuiltInSettings/ValveSettings.cs @@ -1,5 +1,10 @@ namespace ModdableTimberborn.BuildingSettings.BuiltInSettings; +#if TIMBERV11 +// 1.1 renamed the throttle-style Valve to ThrottlingValve (water rework). Alias keeps the body version-agnostic. +using Valve = Timberborn.WaterBuildings.ThrottlingValve; +#endif + public record ValveSettingsModel( bool IsSynchronized, float OutflowLimit, diff --git a/ModdableTimberborn/BuildingSettings/BuiltInSettings/WaterInputCoordinatesSettings.cs b/ModdableTimberborn/BuildingSettings/BuiltInSettings/WaterInputCoordinatesSettings.cs index 1637c603..4e1d08fb 100644 --- a/ModdableTimberborn/BuildingSettings/BuiltInSettings/WaterInputCoordinatesSettings.cs +++ b/ModdableTimberborn/BuildingSettings/BuiltInSettings/WaterInputCoordinatesSettings.cs @@ -1,5 +1,10 @@ namespace ModdableTimberborn.BuildingSettings.BuiltInSettings; +#if TIMBERV11 +// 1.1 moved the depth-limit fields onto WaterInputPipeCoordinates (water rework). Alias keeps the body version-agnostic. +using WaterInputCoordinates = Timberborn.WaterBuildings.WaterInputPipeCoordinates; +#endif + public record WaterInputCoordinatesSettingsModel( bool UseDepthLimit, int DepthLimit @@ -13,7 +18,11 @@ public override string DescribeModel(WaterInputCoordinatesSettingsModel model) protected override bool ApplyModel(WaterInputCoordinatesSettingsModel model, WaterInputCoordinates target) { target.UseDepthLimit = model.UseDepthLimit; +#if TIMBERV11 + target.DepthLimit = Math.Min(model.DepthLimit, target._waterInputPipeSpec.MaxDepth); +#else target.DepthLimit = Math.Min(model.DepthLimit, target._waterInputSpec.MaxDepth); +#endif target.UpdateCoordinatesAndDepth(); return true; diff --git a/ModdableTimberborn/ModdableTimberborn.csproj b/ModdableTimberborn/ModdableTimberborn.csproj index fed069d0..9291546f 100644 --- a/ModdableTimberborn/ModdableTimberborn.csproj +++ b/ModdableTimberborn/ModdableTimberborn.csproj @@ -5,7 +5,7 @@ - + diff --git a/ModdableTimberborn/Services/BlockObjectSpawningHelper.cs b/ModdableTimberborn/Services/BlockObjectSpawningHelper.cs index 9a582de7..5d8105c3 100644 --- a/ModdableTimberborn/Services/BlockObjectSpawningHelper.cs +++ b/ModdableTimberborn/Services/BlockObjectSpawningHelper.cs @@ -1,5 +1,9 @@ namespace ModdableTimberborn.Services; +#if TIMBERV11 +using Timberborn.ConstructionSites; +#endif + [BindSingleton(Contexts = BindAttributeContext.NonMenu)] public class BlockObjectSpawningHelper( BlockValidator blockValidator, @@ -11,6 +15,12 @@ public class BlockObjectSpawningHelper( TemplateNameMapper templateNameMapper, EntityRegistry entityRegistry, MapIndexService mapIndexService +#if TIMBERV11 + // 1.1: placement returns the entity via the factories instead of the removed Place(..., callback) overload. + // Buildings go through ConstructionFactory (building-aware finished wiring); other block objects through BlockObjectFactory. + , BlockObjectFactory blockObjectFactory + , ConstructionFactory constructionFactory +#endif ) { static readonly ImmutableArray Orientations = TimberUiUtils.GetSortedEnumValues(); @@ -54,8 +64,27 @@ public bool TryGetBlockObjectSpec(IEnumerable templateNames, [NotNullWhe public bool IsPlacementValid(BlockObjectSpec template, Placement placement) => blockValidator.BlocksValid(template, placement); - public async Task PlaceObject(BlockObjectSpec template, Placement placement) + public Task PlaceObject(BlockObjectSpec template, Placement placement) { +#if TIMBERV11 + // 1.1 reworked placement: the placer's Place() lost its result callback and now takes an + // EntitySetup.Builder. Mirror what GetMatchingPlacer did: buildings go through ConstructionFactory + // following the spec's PlaceFinished flag (normal buildings → unfinished construction site, not + // instantly built); non-building block objects are placed finished. All paths return the BlockObject. + var builder = new EntitySetup.Builder(template.Blueprint); + BaseComponent placed; + if (template.Blueprint.HasSpec()) + { + placed = template.Blueprint.GetSpec().PlaceFinished + ? constructionFactory.CreateAsFinished(builder, placement) + : constructionFactory.CreateAsUnfinished(builder, placement); + } + else + { + placed = blockObjectFactory.CreateFinished(builder, placement); + } + return Task.FromResult(placed); +#else var placer = blockObjectPlacerService.GetMatchingPlacer(template); TaskCompletionSource tcs = new(); @@ -69,7 +98,8 @@ public async Task PlaceObject(BlockObjectSpec template, Placement tcs.TrySetException(ex); } - return await tcs.Task; + return tcs.Task; +#endif } public async Task TryPlacingWithDestruction(BlockObjectSpec template, Placement placement) diff --git a/ModdableTimberborn/SoakEffect/ModdableSoakEffectComponent.cs b/ModdableTimberborn/SoakEffect/ModdableSoakEffectComponent.cs index f9d0d7e2..9108812f 100644 --- a/ModdableTimberborn/SoakEffect/ModdableSoakEffectComponent.cs +++ b/ModdableTimberborn/SoakEffect/ModdableSoakEffectComponent.cs @@ -4,6 +4,10 @@ /// Component that tracks if a character is in water and raises events accordingly. /// public class ModdableSoakEffectComponent : TickableComponent, IAwakableComponent +#if TIMBERV11 + // 1.1 removed TickableComponent.StartTickable(); pre-tick init moves to IPostInitializableEntity. + , IPostInitializableEntity +#endif { /// @@ -38,7 +42,11 @@ public void Awake() GetComponents(waterResistors); } +#if TIMBERV11 + public void PostInitializeEntity() +#else public override void StartTickable() +#endif { UpdateState(); } diff --git a/Targets/Common.targets b/Targets/Common.targets index 66917895..5f6f4c36 100644 --- a/Targets/Common.targets +++ b/Targets/Common.targets @@ -17,7 +17,23 @@ - + + + + + + + + + + + + + + + + diff --git a/Targets/CommonProperties.targets b/Targets/CommonProperties.targets index 6446a927..de6f08e5 100644 --- a/Targets/CommonProperties.targets +++ b/Targets/CommonProperties.targets @@ -6,14 +6,22 @@ version-1.0 false false + false true true + true + + + true D:\Software\SteamLibrary\steamapps\common\Timberborn\Timberborn_Data\Managed $(DefineConstants);TIMBER7 $(DefineConstants);TIMBERV1 + $(DefineConstants);TIMBERV11 ..\ C:\Users\lukev\OneDrive\Documents\Timberborn\Mods\