Skip to content

Commit aea6382

Browse files
committed
Add terrain extender patches
1 parent 520641b commit aea6382

4 files changed

+201
-0
lines changed

TerrainPatcher.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
<Compile Include="src/FileLoading.cs"/>
5050
<Compile Include="src/Patches.cs"/>
5151
<Compile Include="src/Options.cs"/>
52+
<Compile Include="src\Array3Patches.cs" />
53+
<Compile Include="src\WorldStreamerPatches.cs" />
5254
</ItemGroup>
5355
<Import Project="$(MSBuildBinPath)/Microsoft.CSharp.targets"/>
5456
<Target Name="CopyToTargetDir" AfterTargets="Build">

src/Array3Patches.cs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Collections.Generic;
2+
using HarmonyLib;
3+
using Nautilus.Utility;
4+
5+
namespace TerrainPatcher
6+
{
7+
// Allows entities to save and exist on negative batches
8+
[HarmonyPatch(typeof(Array3<>))]
9+
internal static class Array3Patches
10+
{
11+
private class NegativeEntityCell
12+
{
13+
public Dictionary<Int3, EntityCell> EntityCells { get; }
14+
15+
public NegativeEntityCell(Dictionary<Int3, EntityCell> entityCells)
16+
{
17+
EntityCells = entityCells;
18+
}
19+
}
20+
21+
private static Dictionary<Array3<EntityCell>, NegativeEntityCell> _vanillaToNegativeArrays = new Dictionary<Array3<EntityCell>, NegativeEntityCell>();
22+
23+
internal static void Patch(Harmony harmony)
24+
{
25+
var type = typeof(Array3<>).MakeGenericType(typeof(EntityCell));
26+
var getMethod = AccessTools.Method(type, "Get");
27+
harmony.Patch(getMethod,
28+
prefix: new HarmonyMethod(AccessTools.Method(typeof(Array3Patches), nameof(GetPrefix))));
29+
var setMethod = AccessTools.Method(type, "Set");
30+
harmony.Patch(setMethod,
31+
prefix: new HarmonyMethod(AccessTools.Method(typeof(Array3Patches), nameof(SetPrefix))));
32+
33+
SaveUtils.RegisterOnQuitEvent(() => { _vanillaToNegativeArrays.Clear(); });
34+
}
35+
36+
private static bool GetPrefix(Array3<EntityCell> __instance, int x, int y, int z, ref EntityCell __result)
37+
{
38+
var index = __instance.GetIndex(x, y, z);
39+
if (index >= 0)
40+
{
41+
return true;
42+
}
43+
44+
// At this point the index is negative, so we'll handle it ourselves.
45+
if (_vanillaToNegativeArrays.TryGetValue(__instance, out var negativesArray) &&
46+
negativesArray.EntityCells.TryGetValue(new Int3(x, y, z), out var entityCell))
47+
{
48+
__result = entityCell;
49+
Mod.LogDebug($"Get negative entity cell for ({x},{y},{z})");
50+
return false;
51+
}
52+
53+
Mod.LogDebug($"Couldn't find negative entity cell for ({x},{y},{z})");
54+
return false;
55+
}
56+
57+
private static bool SetPrefix(Array3<EntityCell> __instance, int x, int y, int z, EntityCell value)
58+
{
59+
var index = __instance.GetIndex(x, y, z);
60+
if (index >= 0)
61+
{
62+
return true;
63+
}
64+
65+
// At this point the index is negative, so we'll set it to our collection.
66+
if (!_vanillaToNegativeArrays.TryGetValue(__instance, out var negativeEntityCell))
67+
{
68+
negativeEntityCell = new NegativeEntityCell(new Dictionary<Int3, EntityCell>(Int3.equalityComparer));
69+
_vanillaToNegativeArrays[__instance] = negativeEntityCell;
70+
}
71+
72+
negativeEntityCell.EntityCells[new Int3(x, y, z)] = value;
73+
Mod.LogDebug($"Set negative entity cell for ({x},{y},{z})");
74+
75+
return false;
76+
}
77+
}
78+
}

src/Patches.cs

+17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ internal static void Register()
1313
var harmony = new Harmony("Esper89.TerrainPatcher");
1414
harmony.PatchAll(typeof(LargeWorldStreamer_GetCompiledOctreesCachePath_Patch));
1515
harmony.PatchAll(typeof(BatchOctreesStreamer_GetPath_Patch));
16+
harmony.PatchAll(typeof(LargeWorldStreamer_CheckBatch_Patches));
17+
harmony.PatchAll(typeof(WorldStreamerPatches));
18+
Array3Patches.Patch(harmony);
1619
}
1720

1821
[HarmonyPatch(
@@ -73,5 +76,19 @@ private static bool SetResult(Int3 batchId, ref string result, bool runOriginal)
7376
return true;
7477
}
7578
}
79+
80+
// Permits any batch location
81+
[HarmonyPatch(typeof(LargeWorldStreamer))]
82+
internal static class LargeWorldStreamer_CheckBatch_Patches
83+
{
84+
[HarmonyPatch(nameof(LargeWorldStreamer.CheckBatch))]
85+
[HarmonyPatch(nameof(LargeWorldStreamer.CheckRoot), typeof(int), typeof(int), typeof(int))]
86+
[HarmonyPrefix]
87+
private static bool AllowOutOfBounds(ref bool __result)
88+
{
89+
__result = true;
90+
return false;
91+
}
92+
}
7693
}
7794
}

src/WorldStreamerPatches.cs

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System.Collections.Generic;
2+
using System.Reflection.Emit;
3+
using HarmonyLib;
4+
using UnityEngine;
5+
using WorldStreaming;
6+
7+
namespace TerrainPatcher
8+
{
9+
[HarmonyPatch(typeof(WorldStreamer))]
10+
internal static class WorldStreamerPatches
11+
{
12+
[HarmonyPatch(nameof(WorldStreamer.CreateStreamers))]
13+
[HarmonyPrefix]
14+
private static void CreateStreamersPrefix(WorldStreamer __instance)
15+
{
16+
// allows the streamer to stream octrees as far as the patched batches go
17+
18+
var biggestBatch = BiggestBatch(TerrainRegistry.patchedBatches.Keys, __instance.settings.numOctrees) +
19+
__instance.settings.numOctreesPerBatch * (__instance.settings.numOctreesPerBatch * 2);
20+
__instance.settings.numOctrees = biggestBatch;
21+
}
22+
23+
[HarmonyPatch(nameof(WorldStreamer.CreateStreamers))]
24+
[HarmonyTranspiler]
25+
private static IEnumerable<CodeInstruction> CreateStreamersTranspiler(IEnumerable<CodeInstruction> instructions)
26+
{
27+
// Changes minimum and maximum center positions. Also necessary to stream batches
28+
29+
var int3Zero = AccessTools.Field(typeof(Int3), nameof(Int3.zero));
30+
var found = false;
31+
var found2 = false;
32+
foreach (var instruction in instructions)
33+
{
34+
if (instruction.Is(OpCodes.Ldsfld, int3Zero))
35+
{
36+
yield return CodeInstruction.Call(typeof(WorldStreamerPatches), nameof(MinimumBoundary));
37+
found = true;
38+
}
39+
else if (instruction.Calls(AccessTools.Method(typeof(WorldStreamer),
40+
nameof(WorldStreamer.ParseStreamingSettings))))
41+
{
42+
yield return instruction;
43+
yield return CodeInstruction.Call(typeof(WorldStreamerPatches), nameof(ParseStreamingSettings));
44+
found2 = true;
45+
}
46+
else
47+
{
48+
yield return instruction;
49+
}
50+
}
51+
52+
if (found && found2)
53+
{
54+
Mod.LogDebug($"{nameof(CreateStreamersTranspiler)} has been patched successfully.");
55+
}
56+
else
57+
{
58+
Mod.LogError($"{nameof(CreateStreamersTranspiler)} failed patching.");
59+
}
60+
}
61+
62+
private static LargeWorldStreamer.Settings ParseStreamingSettings(LargeWorldStreamer.Settings settings)
63+
{
64+
var patchedBatches = TerrainRegistry.patchedBatches.Keys;
65+
settings.octreesSettings.centerMin = SmallestBatch(patchedBatches) * 5 - 10;
66+
settings.octreesSettings.centerMax = BiggestBatch(patchedBatches, settings.octreesSettings.centerMax) * 5 + 15;
67+
return settings;
68+
}
69+
70+
private static Int3 MinimumBoundary()
71+
{
72+
return SmallestBatch(TerrainRegistry.patchedBatches.Keys) * 5 - 10;
73+
}
74+
75+
private static Int3 SmallestBatch(IEnumerable<Int3> batches)
76+
{
77+
var result = Int3.zero;
78+
79+
foreach (var batch in batches)
80+
{
81+
result = Int3.Min(result, batch);
82+
}
83+
84+
var horizontalMin = Mathf.Min(result.x, result.z);
85+
86+
return new Int3(horizontalMin, result.y, horizontalMin);
87+
}
88+
89+
private static Int3 BiggestBatch(IEnumerable<Int3> batches, Int3 minimumBatch)
90+
{
91+
var result = minimumBatch;
92+
93+
foreach (var batch in batches)
94+
{
95+
var batchSize = batch;
96+
result = Int3.Max(result, batchSize);
97+
}
98+
99+
var horizontalMax = Mathf.Max(result.x, result.z);
100+
101+
return new Int3(horizontalMax, result.y, horizontalMax);
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)