Lots of new optimizations utilizing prepatcher
To start, this mod now requires Prepatcher and Fishery. New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
@@ -1,30 +1,44 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using RoomOwnersCache = PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.FishRoomOwnersInfo>;
|
||||
using RoomOwnersCache
|
||||
= PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.CacheValue>;
|
||||
using RoomRoleCache
|
||||
= PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Role_Patch.CacheValue>;
|
||||
using ContainedAndAdjacentThingsCache
|
||||
= PerformanceFish.Cache.ByIndex<Verse.Room,
|
||||
PerformanceFish.RoomOptimizations.ContainedAndAdjacentThings_Patch.CacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
{
|
||||
public string Description => "Room stats related optimizations";
|
||||
public string Description { get; } = "Room stats related optimizations";
|
||||
|
||||
public class Role_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Throttles room stats and role updating to happen at most once every 512 or so ticks per room, instead of every time anything at all changes or moves";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Role));
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
public override string Description { get; }
|
||||
= "Throttles room stats and role updating to happen at most once every 512 or so ticks per room, "
|
||||
+ "instead of every time anything at all changes or moves";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.Role));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions instructions)
|
||||
{
|
||||
var this_statsAndRoleDirty = FishTranspiler.Field(typeof(Room), nameof(Room.statsAndRoleDirty));
|
||||
|
||||
try
|
||||
{
|
||||
return codes.ReplaceAt((codes, i)
|
||||
=> i - 1 > 0 && codes[i - 1] == this_statsAndRoleDirty
|
||||
&& codes[i].opcode == OpCodes.Brfalse_S && codes[i].operand is Label,
|
||||
code => new[]
|
||||
return instructions.ReplaceAt((codes, i)
|
||||
=> i - 1 > 0
|
||||
&& codes[i - 1] == this_statsAndRoleDirty
|
||||
&& codes[i].opcode == OpCodes.Brfalse_S
|
||||
&& codes[i].operand is Label, static code => new[]
|
||||
{
|
||||
code,
|
||||
FishTranspiler.This,
|
||||
@@ -34,93 +48,133 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish failed to patch Room.Role. This just means that specific patch will be doing nothing.\n{ex}");
|
||||
Log.Error("Performance Fish failed to patch Room.Role. This just means that specific patch "
|
||||
+ $"will be doing nothing.\n{ex}");
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShouldUpdate(Room __instance)
|
||||
{
|
||||
if (__instance.role is null || !RoomThrottleInfo.TryGetValue(__instance, out var value) || value < Current.Game.tickManager.TicksGame)
|
||||
if (__instance.role is not null
|
||||
|| !RoomRoleCache.Get[__instance.ID].Dirty)
|
||||
{
|
||||
RoomThrottleInfo[__instance] = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
UpdateCache(__instance);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private static void UpdateCache(Room __instance)
|
||||
=> RoomRoleCache.Get.GetReference(__instance).SetDirty(__instance);
|
||||
|
||||
public record struct CacheValue
|
||||
{
|
||||
private int _nextUpdateTick;
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => TickHelper.Past(_nextUpdateTick);
|
||||
}
|
||||
public void SetDirty(Room room)
|
||||
=> _nextUpdateTick = TickHelper.Add(384, room.ID, 256);
|
||||
}
|
||||
public static Dictionary<Room, int> RoomThrottleInfo { get; } = new();
|
||||
static Role_Patch() => Cache.Utility.All.Add(RoomThrottleInfo);
|
||||
}
|
||||
|
||||
public class Owners_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches room owner data and throttles them to only update at most once every ~512 or so ticks. RimWorld calculates owners by checking ownership of all items contained in a room, which can get quite expensive";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Owners));
|
||||
public override string Description { get; }
|
||||
= "Caches room owner data and throttles them to only update at most once every ~512 or so ticks. "
|
||||
+ "RimWorld calculates owners by checking ownership of all items contained in a room, which can get "
|
||||
+ "quite expensive";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.Owners));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Room __instance, ref IEnumerable<Pawn> __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref RoomOwnersCache.Get.GetReference(__instance);
|
||||
ref var cache = ref RoomOwnersCache.Get.GetReference(__instance.ID);
|
||||
|
||||
if (cache.ShouldRefreshNow)
|
||||
if (cache.Dirty)
|
||||
{
|
||||
if (__instance.TouchesMapEdge || __instance.IsHuge || (__instance.statsAndRoleDirty && !__instance.ContainedBeds.Any()))
|
||||
{
|
||||
cache.ShouldRefreshNow = false;
|
||||
cache.owners = Array.Empty<Pawn>();
|
||||
RoomOwnersCache.Get[__instance] = cache;
|
||||
__result = cache.owners;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
return __state = true;
|
||||
if (CanCache(__instance))
|
||||
return __state = true;
|
||||
else
|
||||
cache.Update(__instance, null);
|
||||
}
|
||||
|
||||
__result = cache.owners;
|
||||
__result = cache.Owners;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
private static bool CanCache(Room __instance)
|
||||
=> __instance is { TouchesMapEdge: false, IsHuge: false }
|
||||
&& (!__instance.statsAndRoleDirty || __instance.ContainedBeds.Any());
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Room __instance, bool __state, ref IEnumerable<Pawn> __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
if (RoomOwnersCache.Get.TryGetValue(__instance, out var cache) && cache.owners is List<Pawn> list)
|
||||
{
|
||||
list.Clear();
|
||||
foreach (var pawn in __result)
|
||||
list.Add(pawn);
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.owners = new List<Pawn>(__result);
|
||||
}
|
||||
|
||||
cache.ShouldRefreshNow = false;
|
||||
RoomOwnersCache.Get[__instance] = cache;
|
||||
__result = cache.owners;
|
||||
UpdateCache(__instance, ref __result);
|
||||
}
|
||||
|
||||
public struct FishRoomOwnersInfo
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(Room __instance, ref IEnumerable<Pawn> __result)
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public IEnumerable<Pawn> owners;
|
||||
ref var cache = ref RoomOwnersCache.Get.GetReference(__instance);
|
||||
|
||||
cache.Update(__instance, __result);
|
||||
|
||||
__result = cache.Owners;
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public record struct CacheValue
|
||||
{
|
||||
private int _nextRefreshTick;
|
||||
public List<Pawn> Owners;
|
||||
|
||||
public void Update(Room room, IEnumerable<Pawn>? result)
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
(Owners ??= new(0)).Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Owners != null)
|
||||
{
|
||||
Owners.Clear();
|
||||
Owners.AddRange(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Owners = new(result);
|
||||
}
|
||||
}
|
||||
|
||||
_nextRefreshTick = TickHelper.Add(384, room.ID, 256);
|
||||
}
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Regions_Patch : FishPatch
|
||||
public class Regions_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Minor optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Regions));
|
||||
public override string Description { get; } = "Minor optimization";
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.Regions));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Regions_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -128,25 +182,27 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
{
|
||||
var tmpRegions = instance.tmpRegions;
|
||||
var districts = instance.districts;
|
||||
lock (_lock)
|
||||
{
|
||||
tmpRegions.Clear();
|
||||
for (var i = 0; i < districts.Count; i++)
|
||||
tmpRegions.AddRange(districts[i].Regions);
|
||||
}
|
||||
|
||||
tmpRegions.Clear();
|
||||
for (var i = 0; i < districts.Count; i++)
|
||||
tmpRegions.AddRangeFast(districts[i].Regions);
|
||||
|
||||
return tmpRegions;
|
||||
}
|
||||
|
||||
private static readonly object _lock = new();
|
||||
}
|
||||
|
||||
public class ContainedBeds_Patch : FishPatch
|
||||
public class ContainedBeds_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "So RimWorld has a list of beds and a list of all things. For some reason ludeon chose to have ContainedBeds retrieve its beds by looping through all things. This fixes that. Helps a lot against spikes";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedBeds));
|
||||
public override string Description { get; }
|
||||
= "So RimWorld has a list of beds and a list of all things. For some reason Ludeon chose to have "
|
||||
+ "ContainedBeds retrieve its beds by looping through all things. This fixes that. Helps a lot "
|
||||
+ "against spikes";
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ContainedBeds_Replacement);
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedBeds));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
|
||||
=> Reflection.GetCodeInstructions(ContainedBeds_Replacement, generator);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IEnumerable<Building_Bed> ContainedBeds_Replacement(Room instance)
|
||||
@@ -157,92 +213,99 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
var beds = regions[i].ListerThings.listsByGroup[(int)ThingRequestGroup.Bed];
|
||||
if (beds is null)
|
||||
continue;
|
||||
|
||||
for (var j = 0; j < beds.Count; j++)
|
||||
yield return (Building_Bed)beds[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ContainedAndAdjacentThings_Patch : FishPatch
|
||||
public class ContainedAndAdjacentThings_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caching of room content info. Variable impact";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedAndAdjacentThings));
|
||||
public override string Description { get; } = "Caching of room content info. Variable impact";
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedAndAdjacentThings));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ContainedAndAdjacentThings_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Thing> ContainedAndAdjacentThings_Replacement(Room instance)
|
||||
{
|
||||
lock (_lock)
|
||||
instance.uniqueContainedThings.Clear();
|
||||
|
||||
var regions = instance.Regions;
|
||||
|
||||
ref var cache = ref ContainedAndAdjacentThingsCache.Get.GetReference(instance.ID);
|
||||
|
||||
if (cache.ListVersions != null
|
||||
&& cache.ListVersions.Count == regions.Count
|
||||
&& !cache.Dirty)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
uniqueContainedThings.Clear();
|
||||
|
||||
var regions = instance.Regions;
|
||||
|
||||
if (CachedThingLists.TryGetValue(instance, out var cache) && cache.listVersions.Count == regions.Count && !cache.ShouldRefreshNow)
|
||||
var cacheIsAccurate = true;
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var cacheIsAccurate = true;
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
var allThings = regions[i].listerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
var allThings = regions[i].listerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
if (cache.listVersions[i] != -1)
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
else if (cache.listVersions[i] != allThings.Version())
|
||||
{
|
||||
if (cache.ListVersions[i] != -1)
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
}
|
||||
if (cacheIsAccurate)
|
||||
else if (cache.ListVersions[i] != allThings._version)
|
||||
{
|
||||
uniqueContainedThings.AddRange(cache.things);
|
||||
return uniqueContainedThings;
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
}
|
||||
|
||||
return RefreshContainedAndAdjacentThings(instance, regions, cache);
|
||||
if (cacheIsAccurate)
|
||||
{
|
||||
instance.uniqueContainedThings.AddRangeFast(cache.Things);
|
||||
return instance.uniqueContainedThings;
|
||||
}
|
||||
}
|
||||
|
||||
return RefreshContainedAndAdjacentThings(instance, regions, ref cache);
|
||||
}
|
||||
|
||||
private static List<Thing> RefreshContainedAndAdjacentThings(Room instance, List<Region> regions, FishThingsInfo cache)
|
||||
private static List<Thing> RefreshContainedAndAdjacentThings(Room instance, List<Region> regions,
|
||||
ref CacheValue cache)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
var uniqueContainedThingsSet = instance.uniqueContainedThingsSet;
|
||||
uniqueContainedThingsSet.Clear();
|
||||
(cache.listVersions ??= new()).Clear();
|
||||
(cache.ListVersions ??= new()).Clear();
|
||||
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var allThings = regions[i].ListerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
cache.listVersions.Add(-1);
|
||||
cache.ListVersions.Add(-1);
|
||||
continue;
|
||||
}
|
||||
|
||||
var count = allThings.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
uniqueContainedThingsSet.Add(allThings[j]);
|
||||
|
||||
cache.listVersions.Add(allThings.Version());
|
||||
|
||||
cache.ListVersions.Add(allThings.Version());
|
||||
}
|
||||
|
||||
uniqueContainedThings.AddRange(uniqueContainedThingsSet);
|
||||
if (cache.things is List<Thing> list)
|
||||
if (cache.Things is { } list)
|
||||
list.ReplaceContentsWith(uniqueContainedThings);
|
||||
else
|
||||
cache.things = new List<Thing>(uniqueContainedThings);
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
CachedThingLists[instance] = cache;
|
||||
cache.Things = new(uniqueContainedThings);
|
||||
|
||||
cache.SetDirty(false, instance);
|
||||
|
||||
uniqueContainedThingsSet.Clear();
|
||||
return uniqueContainedThings;
|
||||
}
|
||||
|
||||
/*private static List<Thing> RefreshContainedAndAdjacentThingsAlternative(Room instance, List<Region> regions, FishThingsInfo cache)
|
||||
/*private static List<Thing> RefreshContainedAndAdjacentThingsAlternative(Room instance, List<Region> regions,
|
||||
FishThingsInfo cache)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
(cache.listVersions ??= new()).Clear();
|
||||
@@ -261,6 +324,7 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
cache.listVersions.Add(-1);
|
||||
continue;
|
||||
}
|
||||
|
||||
var count = allThings.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
@@ -270,7 +334,7 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
adjacentThings.Add(allThings[j]);
|
||||
}
|
||||
|
||||
cache.listVersions.Add(Utility<Thing>.getListVersion(allThings));
|
||||
cache.listVersions.Add(allThings._version);
|
||||
}
|
||||
|
||||
for (var i = 0; i < adjacentThings.Count; i++)
|
||||
@@ -290,31 +354,30 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
return uniqueContainedThings;
|
||||
}*/
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static Dictionary<Room, FishThingsInfo> CachedThingLists { get; } = new();
|
||||
static ContainedAndAdjacentThings_Patch() => Cache.Utility.All.Add(CachedThingLists);
|
||||
|
||||
public struct FishThingsInfo
|
||||
public record struct CacheValue
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public List<int> listVersions;
|
||||
public IEnumerable<Thing> things;
|
||||
private int _nextRefreshTick;
|
||||
public List<int>? ListVersions;
|
||||
public List<Thing> Things;
|
||||
|
||||
public void SetDirty(bool value, Room room)
|
||||
=> _nextRefreshTick = value ? 0 : TickHelper.Add(3072, room.ID, 2048);
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RoomAt_Patch : FishPatch
|
||||
public class RoomAt_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string? Description => "Minor optimization through inlining ond cleaning up of duplicate calls in the method. About 1/4 faster, which isn't much, but it likely won't break anything either.";
|
||||
public override string? Description { get; }
|
||||
= "Minor optimization through inlining ond cleaning up of duplicate calls in the method. About 1/4 "
|
||||
+ "faster, which isn't much, but it likely won't break anything either.";
|
||||
|
||||
public override Delegate? TargetMethodGroup => RegionAndRoomQuery.RoomAt;
|
||||
public override Delegate TargetMethodGroup { get; } = RegionAndRoomQuery.RoomAt;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(RoomAt);
|
||||
@@ -323,17 +386,23 @@ public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All)
|
||||
{
|
||||
var mapSize = map.info.Size;
|
||||
if ((uint)c.x >= mapSize.x || (uint)c.z >= mapSize.z)
|
||||
if ((uint)c.x >= (uint)mapSize.x || (uint)c.z >= (uint)mapSize.z)
|
||||
return null;
|
||||
|
||||
var regionAndRoomUpdater = map.regionAndRoomUpdater;
|
||||
if (regionAndRoomUpdater.Enabled)
|
||||
{
|
||||
regionAndRoomUpdater.TryRebuildDirtyRegionsAndRooms();
|
||||
}
|
||||
else if (regionAndRoomUpdater.AnythingToRebuild)
|
||||
Log.Warning($"Trying to get valid region at {c} but RegionAndRoomUpdater is disabled. The result may be incorrect.");
|
||||
{
|
||||
Log.Warning($"Trying to get valid region at {
|
||||
c.ToString()} but RegionAndRoomUpdater is disabled. The result may be incorrect.");
|
||||
}
|
||||
|
||||
var region = map.regionGrid.regionGrid[(c.z * mapSize.x) + c.x];
|
||||
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0 ? null
|
||||
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0
|
||||
? null
|
||||
: region.District?.Room;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user