Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions BuildingBlueprints/Services/BlueprintPlacementValidator.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
namespace BuildingBlueprints.Services;

#if TIMBERV11
using Timberborn.ConstructionSites;
#endif

[BindSingleton]
public class BlueprintPlacementValidator(
IEnumerable<IBlockObjectPlacer> 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<HashSet<Preview>> ValidatePreviewsAsync(ImmutableArray<Preview> previews)
=> (await ValidatePreviews(previews, true)).ValidPreviews;
Expand Down Expand Up @@ -88,9 +98,20 @@ public async Task<List<ValueTuple<Preview, BaseComponent>>> BuildPreviewsAsync(I

Task<BaseComponent> 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<BuildingSpec>().PlaceFinished
? constructionFactory.CreateAsFinished(builder, placement)
: constructionFactory.CreateAsUnfinished(builder, placement);
return Task.FromResult<BaseComponent>(placed);
#else
TaskCompletionSource<BaseComponent> tcs = new();
buildingPlacer.Place(bos, placement, result => tcs.SetResult(result));
return tcs.Task;
#endif
}

bool AlreadyHasPath(Vector3Int position) =>
Expand Down
16 changes: 14 additions & 2 deletions GameAssemblyPublicizer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Collections.Immutable;

ImmutableArray<string> 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<string> SpecialFolders = [
@"D:\Software\SteamLibrary\steamapps\workshop\content\1062090\3283831040\version-1.0\Scripts", // Mod Settings
];
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<BonusTrackerItem> CurrentBonusesKey = new("CurrentBonuses");
Expand All @@ -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; }

Expand Down
Original file line number Diff line number Diff line change
@@ -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<AutomatorConnection> _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<Relay, RelaySettingsModel>(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<Automator>(out var automator) ? automator : null;
}
}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ModdableTimberborn/ModdableTimberborn.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<Import Project="../Targets/Common.targets" />
<Import Project="../Targets/CommonTimberUi.targets" />
<Import Project="../Targets/CommonTimberUI.targets" />
<Import Project="../Targets/CommonHarmony.targets" />

<ItemGroup>
Expand Down
34 changes: 32 additions & 2 deletions ModdableTimberborn/Services/BlockObjectSpawningHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
namespace ModdableTimberborn.Services;

#if TIMBERV11
using Timberborn.ConstructionSites;
#endif

[BindSingleton(Contexts = BindAttributeContext.NonMenu)]
public class BlockObjectSpawningHelper(
BlockValidator blockValidator,
Expand All @@ -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<Orientation> Orientations = TimberUiUtils.GetSortedEnumValues<Orientation>();
Expand Down Expand Up @@ -54,8 +64,27 @@ public bool TryGetBlockObjectSpec(IEnumerable<string> templateNames, [NotNullWhe
public bool IsPlacementValid(BlockObjectSpec template, Placement placement)
=> blockValidator.BlocksValid(template, placement);

public async Task<BaseComponent> PlaceObject(BlockObjectSpec template, Placement placement)
public Task<BaseComponent> 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<BuildingSpec>())
{
placed = template.Blueprint.GetSpec<BuildingSpec>().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<BaseComponent> tcs = new();
Expand All @@ -69,7 +98,8 @@ public async Task<BaseComponent> PlaceObject(BlockObjectSpec template, Placement
tcs.TrySetException(ex);
}

return await tcs.Task;
return tcs.Task;
#endif
}

public async Task<BaseComponent?> TryPlacingWithDestruction(BlockObjectSpec template, Placement placement)
Expand Down
8 changes: 8 additions & 0 deletions ModdableTimberborn/SoakEffect/ModdableSoakEffectComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
/// Component that tracks if a character is in water and raises events accordingly.
/// </summary>
public class ModdableSoakEffectComponent : TickableComponent, IAwakableComponent
#if TIMBERV11
// 1.1 removed TickableComponent.StartTickable(); pre-tick init moves to IPostInitializableEntity.
, IPostInitializableEntity
#endif
{

/// <summary>
Expand Down Expand Up @@ -38,7 +42,11 @@ public void Awake()
GetComponents(waterResistors);
}

#if TIMBERV11
public void PostInitializeEntity()
#else
public override void StartTickable()
#endif
{
UpdateState();
}
Expand Down
18 changes: 17 additions & 1 deletion Targets/Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,23 @@
<None Include="**\V1\**" />
<None Include="**\*.V1.cs" />
</ItemGroup>


<!-- 1.1 reuses the V1 lineage but overrides the few files that diverge via *.V11.cs / V11\.
Drop V11 sources on every stream except 1.1; on 1.1 drop the *.V1.cs they replace. -->
<ItemGroup Condition="!$(IsV11)">
<Compile Remove="**\V11\**" />
<Compile Remove="**\*.V11.cs" />
<None Include="**\V11\**" />
<None Include="**\*.V11.cs" />
</ItemGroup>

<ItemGroup Condition="$(IsV11)">
<Compile Remove="**\V1\**" />
<Compile Remove="**\*.V1.cs" />
<None Include="**\V1\**" />
<None Include="**\*.V1.cs" />
</ItemGroup>

<Target Name="Validate" Condition="!$(IsTestProject)">
<Error Condition="'$(OutputModFolderName)'==''" Text="OutputModFolderName was not specified" />
</Target>
Expand Down
8 changes: 8 additions & 0 deletions Targets/CommonProperties.targets
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@
<TimberbornVersion>version-1.0</TimberbornVersion>
<IsU7>false</IsU7>
<IsV1>false</IsV1>
<IsV11>false</IsV11>

<IsU7 Condition="$(TimberbornVersion)=='version-0.7'">true</IsU7>
<IsV1 Condition="$(TimberbornVersion)=='version-1.0'">true</IsV1>
<IsV11 Condition="$(TimberbornVersion)=='version-1.1'">true</IsV11>

<!-- 1.1 (experimental) is an incremental update over 1.0: it reuses the V1 source
lineage (shared files, global usings, ModAnalyzers). IsV1 therefore stays true
for 1.1; TIMBERV11 marks the few spots that need 1.1-specific code via #if. -->
<IsV1 Condition="$(IsV11)">true</IsV1>

<AssemblyPath>D:\Software\SteamLibrary\steamapps\common\Timberborn\Timberborn_Data\Managed</AssemblyPath>

<DefineConstants Condition="$(IsU7)">$(DefineConstants);TIMBER7</DefineConstants>
<DefineConstants Condition="$(IsV1)">$(DefineConstants);TIMBERV1</DefineConstants>
<DefineConstants Condition="$(IsV11)">$(DefineConstants);TIMBERV11</DefineConstants>

<GameSolutionFolder Condition="'$(GameSolutionFolder)'==''">..\</GameSolutionFolder>
<GameModsFolder>C:\Users\lukev\OneDrive\Documents\Timberborn\Mods\</GameModsFolder>
Expand Down