Decompiled source of BetterCarts v1.6.0
BetterCarts.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.NET.Common; using BetterCarts.Patches; using Candide; using Candide.CandideUI.ChatWindow; using Candide.Entities.Controllers.Legacy; using Candide.Entities.Controllers.Other; using Candide.Entities.PlayerState; using Candide.GameModels; using Candide.GameModels.Controllers; using Candide.GameModels.Managers; using Candide.Graphics; using Candide.Graphics.Fonts; using Candide.LegacyUI; using Candide.Multiplayer.Chat; using Candide.Multiplayer.Models; using Candide.Sound; using Candide.World; using CandideCreator.Shared.Algorithms; using CandideCreator.Shared.Graphics; using CandideCreator.Shared.Helpers; using CandideCreator.Shared.Models; using CandideServer; using CandideServer.Entities; using CandideServer.Entities.ControllerSets.MaterialStorage; using CandideServer.Entities.Controllers; using CandideServer.Helpers; using CandideServer.MessageModels; using CandideServer.MessageModels.Entities; using CandideServer.Models; using CandideServer.Models.Worlds; using CandideServer.Saving; using CandideServer.ServerManagers; using CandideServer.SimulationModels; using CandideServer.SyncStrategies; using CandideServer.World; using FontStashSharp; using HarmonyLib; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Shared.Collision; using Shared.Data; using Shared.Entity; using Shared.Entity.Base; using Shared.Entity.Components; using Shared.Helpers; using Shared.Models.Construction; using Shared.Models.Interaction; using Shared.Models.Items; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: RequiresPreviewFeatures] [assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] [assembly: AssemblyCompany("BetterCarts")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.6.0.0")] [assembly: AssemblyInformationalVersion("1.6.0+5ae0069b25cbe0baa73f9bd20c4ae2468f669a33")] [assembly: AssemblyProduct("BetterCarts")] [assembly: AssemblyTitle("BetterCarts")] [assembly: TargetPlatform("Windows7.0")] [assembly: SupportedOSPlatform("Windows7.0")] [assembly: AssemblyVersion("1.6.0.0")] [module: RefSafetyRules(11)] namespace BetterCarts { internal static class CartAccess { internal static readonly Func<ServerCart2Controller, EntityWrapper, bool> PickupEntity = AccessTools.MethodDelegate<Func<ServerCart2Controller, EntityWrapper, bool>>(AccessTools.Method(typeof(ServerCart2Controller), "PickupEntity", (Type[])null, (Type[])null), (object)null, true, (Type[])null); } internal sealed class CartTypeRecord { internal Guid Id; internal string Name = string.Empty; internal ConfigEntry<int> Setting; } internal static class CartCapacity { internal const int VanillaBase = 4; internal const int VanillaBlessed = 5; private const int DefaultCapacity = 4; private const int SliderMax = 64; private const string SectionName = "Cart Capacity"; private const string EntryDescription = "The default capacity of this Cart is 4."; private static readonly CartTypeRecord[] Types = new CartTypeRecord[2] { new CartTypeRecord { Id = new Guid("5af0ea10-21de-404a-a869-b0079653ee0b"), Name = "Wooden Cart" }, new CartTypeRecord { Id = new Guid("4f26d74b-6fea-4ce9-b369-3bd4507dfff6"), Name = "Bronze Cart" } }; private static readonly Dictionary<Guid, CartTypeRecord> Records = new Dictionary<Guid, CartTypeRecord>(); private static bool _flagsLogged; internal static bool Enforcing { get { if (ModConfig.LoadCartCapacity.Value && ModConfig.Enabled.Value) { return ModConfig.CartCapacityEnabled.Value; } return false; } } internal static bool Ejecting { get { if (Enforcing) { if (ModConfig.CartCapacityEjectOverflow != null) { return ModConfig.CartCapacityEjectOverflow.Value; } return true; } return false; } } internal static bool Blessed => WorldFlagsHelper.HasFlag(ServerGameState.WorldFlags, "worship_flag:cart_capacity"); private static int BlessingBonus { get { if (ModConfig.CartCapacityBlessingBonus != null) { return ModConfig.CartCapacityBlessingBonus.Value; } return 1; } } internal static void BindTypeEntries(ConfigFile config) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown bool hidden = !ModConfig.CartCapacityEnabled.Value; Records.Clear(); int num = 3; CartTypeRecord[] types = Types; foreach (CartTypeRecord cartTypeRecord in types) { cartTypeRecord.Setting = config.Bind<int>("Cart Capacity", cartTypeRecord.Id.ToString(), 4, new ConfigDescription("The default capacity of this Cart is 4.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64), new object[1] { ModConfig.EntryTag(cartTypeRecord.Name + " Capacity", num, hidden) })); Records[cartTypeRecord.Id] = cartTypeRecord; num++; } } internal static void LogStartup() { ModLog.Info("=== STARTUP ==="); ModLog.Info("Enabled=" + ModConfig.Enabled.Value + " CartCapacityEnabled=" + ModConfig.CartCapacityEnabled.Value + " bonus=" + BlessingBonus + " types=" + Records.Count); CartTypeRecord[] types = Types; foreach (CartTypeRecord cartTypeRecord in types) { ModLog.Info(" type " + cartTypeRecord.Id.ToString() + " name=\"" + cartTypeRecord.Name + "\" slider=" + ((cartTypeRecord.Setting == null) ? "UNBOUND" : cartTypeRecord.Setting.Value.ToString(CultureInfo.InvariantCulture))); } ModLog.Info("=== END STARTUP ==="); } internal static void NoteWorldLoaded(string origin) { if (!_flagsLogged && ModLog.Enabled) { _flagsLogged = true; LogWorldFlags(origin); } } private static void LogWorldFlags(string origin) { object worldFlags = ServerGameState.WorldFlags; string text = ((worldFlags == null) ? "null" : ((!(worldFlags is IEnumerable source) || worldFlags is string) ? worldFlags.ToString() : string.Join(",", from object o in source select (o != null) ? o.ToString() : "null"))); ModLog.Info("WorldFlags(" + origin + ") key=\"worship_flag:cart_capacity\" HasFlag=" + Blessed + " raw=[" + text + "]"); } internal static bool TryGetEnforcedCapacity(EntityWrapper cartEntity, out int capacity) { capacity = 0; if (cartEntity == null || !Enforcing) { return false; } if (!Records.TryGetValue(cartEntity.BaseGuid, out var value) || value.Setting == null) { if (ModLog.AdvancedEnabled) { ModLog.AdvancedOnChange("cap:" + cartEntity.BaseGuid, "CAPACITY type=" + cartEntity.BaseGuid.ToString() + " is not a vanilla Cart type - Cart Capacity does not apply to it"); } return false; } int value2 = value.Setting.Value; int num = (Blessed ? BlessingBonus : 0); capacity = value2 + num; if (ModLog.AdvancedEnabled) { ModLog.AdvancedOnChange("cap:" + value.Id, "CAPACITY " + value.Name + " base=" + value2 + " bonus=" + num + " blessed=" + Blessed + " -> " + capacity); } return true; } internal static int GetKnownCapacity(EntityWrapper cartEntity) { if (TryGetEnforcedCapacity(cartEntity, out var capacity)) { return capacity; } if (!Blessed) { return 4; } return 5; } } internal static class CartCargo { private sealed class CartState { internal readonly List<Guid> Extras = new List<Guid>(); internal long NextSweepTick; internal bool Adopted; internal string Written; internal readonly List<Guid> WrittenIds = new List<Guid>(); internal long OccupiedStamp = -1L; internal int Occupied; internal bool EjectDone; } private const int SweepIntervalMs = 100; private const float StackHeight = 6f; private const float ScanTiles = 2f; private const float EjectTiles = 1f; private static readonly string[] SlotKeys = new string[5] { "c1", "c2", "c3", "c4", "c5" }; private static readonly ConditionalWeakTable<ServerCart2Controller, CartState> States = new ConditionalWeakTable<ServerCart2Controller, CartState>(); private static readonly List<EntityWrapper> ReuseCount = new List<EntityWrapper>(); private static readonly List<EntityWrapper> ReuseSweep = new List<EntityWrapper>(); private static readonly List<EntityWrapper> ReuseUnslotted = new List<EntityWrapper>(); private static readonly List<EntityWrapper> ReuseDrop = new List<EntityWrapper>(); private static readonly List<Guid> ReuseOrder = new List<Guid>(); private static readonly List<Guid> ReuseAdopt = new List<Guid>(); private static readonly HashSet<Guid> ReuseAdoptSeen = new HashSet<Guid>(); private static readonly HashSet<Guid> ReuseSlotted = new HashSet<Guid>(); private static readonly HashSet<Guid> ReuseSweepIds = new HashSet<Guid>(); private static readonly HashSet<Guid> ReuseItemIds = new HashSet<Guid>(); private static readonly HashSet<Guid> ReuseOrderIds = new HashSet<Guid>(); internal static bool HasFreeSlot(ServerCart2Controller cart) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed) { return false; } return GetOccupied(cart) < CartCapacity.GetKnownCapacity(entity); } internal static int GetOccupied(ServerCart2Controller cart) { CartState orCreateValue = States.GetOrCreateValue(cart); Adopt(cart, orCreateValue); long tickCount = Environment.TickCount64; if (orCreateValue.OccupiedStamp == tickCount) { return orCreateValue.Occupied; } CollectCarried(cart, ReuseCount); orCreateValue.Occupied = Math.Max(ReuseCount.Count, SlottedCount(cart) + orCreateValue.Extras.Count); orCreateValue.OccupiedStamp = tickCount; return orCreateValue.Occupied; } internal static void Invalidate(ServerCart2Controller cart) { States.GetOrCreateValue(cart).OccupiedStamp = -1L; } private static int SlottedCount(ServerCart2Controller cart) { EntityWrapper entity = ((AbstractController)cart).Entity; Parameters parameters = ((AbstractController)cart).Parameters; Dictionary<string, string> dictionary = ((parameters == null) ? null : parameters.Dictionary); if (entity == null || dictionary == null) { return 0; } ReuseSlotted.Clear(); foreach (KeyValuePair<string, string> item in dictionary) { if (!string.Equals(item.Key, "bc_cargo", StringComparison.Ordinal) && item.Value != null && item.Value.Length == 36 && Guid.TryParse(item.Value, out var result) && !(result == Guid.Empty)) { EntityWrapper entityById = entity.System.GetEntityById(result); if (entityById != null && !entityById.Removed && entityById.Carriable && entityById.CarrierId == entity.Id) { ReuseSlotted.Add(result); } } } return ReuseSlotted.Count; } internal static bool CanTakeExtra(ServerCart2Controller cart) { CartState orCreateValue = States.GetOrCreateValue(cart); Adopt(cart, orCreateValue); return orCreateValue.Extras.Count < 128; } internal static void PinExtra(ServerCart2Controller cart, EntityWrapper item) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed || item == null || item.Removed || (item.CarrierId.HasValue && item.CarrierId != entity.Id)) { return; } CartState orCreateValue = States.GetOrCreateValue(cart); Adopt(cart, orCreateValue); ClearThrowState(item); Pin(entity, item); if (!orCreateValue.Extras.Contains(item.Id)) { orCreateValue.Extras.Add(item.Id); if (ModLog.AdvancedEnabled) { ModLog.Advanced("PIN extra " + Short(item.Id) + " on cart " + Short(entity.Id) + " (extras=" + orCreateValue.Extras.Count + ")"); } } orCreateValue.OccupiedStamp = -1L; Publish(entity, orCreateValue); } internal static void Tick(ServerCart2Controller cart) { CartState orCreateValue = States.GetOrCreateValue(cart); Adopt(cart, orCreateValue); HoldExtras(cart, orCreateValue); long tickCount = Environment.TickCount64; if (tickCount >= orCreateValue.NextSweepTick) { orCreateValue.NextSweepTick = tickCount + 100; Sweep(cart, orCreateValue); } } internal static void ReleaseAll(ServerCart2Controller cart, bool clearStoredCargo = true) { CartState orCreateValue = States.GetOrCreateValue(cart); orCreateValue.Extras.Clear(); orCreateValue.OccupiedStamp = -1L; EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null) { return; } CollectCarried(cart, ReuseSweep); foreach (EntityWrapper item in ReuseSweep) { if (!IsSlotted(cart, item.Id)) { Release(entity, item); } } if (clearStoredCargo) { Publish(entity, orCreateValue); } } private static void Adopt(ServerCart2Controller cart, CartState state) { if (state.Adopted) { return; } Parameters parameters = ((AbstractController)cart).Parameters; if (parameters == null || ((AbstractController)cart).Entity == null || ((AbstractController)cart).Entity.Removed) { return; } state.Adopted = true; string text = parameters.GetString("bc_cargo", string.Empty); if (!string.IsNullOrEmpty(text) && ModLog.AdvancedEnabled) { ModLog.Advanced("ADOPT cart=" + Short(((AbstractController)cart).Entity.Id) + " bc_cargo=\"" + text + "\""); } CartCargoSync.Unpack(text, ReuseAdopt, ReuseAdoptSeen); state.Extras.Clear(); foreach (Guid item in ReuseAdopt) { state.Extras.Add(item); } state.Written = CartCargoSync.Pack(state.Extras); state.WrittenIds.Clear(); state.WrittenIds.AddRange(state.Extras); state.OccupiedStamp = -1L; } private static void HoldExtras(ServerCart2Controller cart, CartState state) { if (state.Extras.Count == 0) { return; } EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed) { return; } bool flag = false; for (int num = state.Extras.Count - 1; num >= 0; num--) { EntityWrapper entityById = entity.System.GetEntityById(state.Extras[num]); if (entityById == null || entityById.Removed) { if (!ServerGameState.Entities.ContainsKey(state.Extras[num])) { state.Extras.RemoveAt(num); flag = true; } } else if (entityById.CarrierId.HasValue && entityById.CarrierId != entity.Id) { if (ModLog.AdvancedEnabled) { ModLog.Advanced("UNPIN extra " + Short(entityById.Id) + " - carrier changed to " + Short(entityById.CarrierId.Value)); } entityById.NoEntityCollision = false; entityById.NoTerrainCollision = false; state.Extras.RemoveAt(num); flag = true; } else { if (entityById.IsThrown || entityById.ThrowerId.HasValue) { ClearThrowState(entityById); } Pin(entity, entityById); Stack(entity, entityById); } } if (flag) { state.OccupiedStamp = -1L; Publish(entity, state); } } private static void Sweep(ServerCart2Controller cart, CartState state) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed) { return; } CollectCarried(cart, ReuseSweep); ReuseSweepIds.Clear(); foreach (EntityWrapper item in ReuseSweep) { ReuseSweepIds.Add(item.Id); } foreach (Guid extra in state.Extras) { EntityWrapper entityById = entity.System.GetEntityById(extra); if (entityById == null || entityById.Removed) { return; } Guid? carrierId = entityById.CarrierId; Guid id = entity.Id; if (carrierId.HasValue && carrierId.GetValueOrDefault() == id && ReuseSweepIds.Add(extra)) { ReuseSweep.Add(entityById); } } DumpSweep(cart, entity); ReuseUnslotted.Clear(); foreach (EntityWrapper item2 in ReuseSweep) { if (!IsSlotted(cart, item2.Id)) { ReuseUnslotted.Add(item2); } } int capacity; bool flag = CartCapacity.TryGetEnforcedCapacity(entity, out capacity); int num = ReuseSweep.Count - ReuseUnslotted.Count; bool flag2 = !state.EjectDone && flag && CartCapacity.Ejecting; state.EjectDone = true; int num2 = ReuseUnslotted.Count; if (flag2) { num2 = Math.Max(0, Math.Min(ReuseUnslotted.Count, capacity - num)); } StableOrder(state, ReuseUnslotted); state.Extras.Clear(); ReuseDrop.Clear(); for (int i = 0; i < ReuseOrder.Count; i++) { if (i < num2) { state.Extras.Add(ReuseOrder[i]); continue; } EntityWrapper val = Find(ReuseUnslotted, ReuseOrder[i]); if (val != null) { ReuseDrop.Add(val); } } if (flag2) { EjectSurplus(cart, entity, capacity, num); } state.OccupiedStamp = -1L; Publish(entity, state); } private static void EjectSurplus(ServerCart2Controller cart, EntityWrapper cartEntity, int capacity, int slotted) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) int count = ReuseDrop.Count; int num = slotted - capacity; int num2 = SlotKeys.Length - 1; while (num2 >= 0 && ReuseDrop.Count - count < num) { ref Guid? reference = ref SlotRef(cart, num2); if (reference.HasValue) { EntityWrapper entityById = cartEntity.System.GetEntityById(reference.Value); if (entityById != null && !entityById.Removed && !(entityById.CarrierId != cartEntity.Id)) { reference = null; ServerEntitySystemManager.UpdateEntityParameter(cartEntity, SlotKeys[num2], string.Empty, SyncStrategy.Everyone(), true); ReuseDrop.Add(entityById); } } num2--; } for (int i = 0; i < ReuseDrop.Count; i++) { if (ModLog.AdvancedEnabled) { ModLog.Advanced("DROP " + Short(ReuseDrop[i].Id) + " from cart " + Short(cartEntity.Id) + " (slotted=" + slotted + " extras=" + count + " cap=" + capacity + " dropped=" + ReuseDrop.Count + ")"); } Drop(cartEntity, ReuseDrop[i], i, ReuseDrop.Count); } ReuseDrop.Clear(); } private static ref Guid? SlotRef(ServerCart2Controller cart, int index) { return index switch { 0 => ref cart.Carried1, 1 => ref cart.Carried2, 2 => ref cart.Carried3, 3 => ref cart.Carried4, _ => ref cart.Carried5, }; } private static void Drop(EntityWrapper cartEntity, EntityWrapper item, int index, int count) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) Release(cartEntity, item); double num = Math.PI * 2.0 * (double)index / (double)count; float num2 = WorldInfo.TileSize * 1f; item.Position = cartEntity.Position + new Vector3((float)Math.Cos(num) * num2, (float)Math.Sin(num) * num2, 6f); item.Velocity = Vector3.Zero; item.System.CollisionGroup.UpdatePositionAndVelocity(item); } private static void DumpSweep(ServerCart2Controller cart, EntityWrapper cartEntity) { if (!ModLog.AdvancedEnabled) { return; } Parameters parameters = ((AbstractController)cart).Parameters; Dictionary<string, string> dictionary = ((parameters == null) ? null : parameters.Dictionary); int capacity; bool flag = CartCapacity.TryGetEnforcedCapacity(cartEntity, out capacity); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("SWEEP cart=").Append(cartEntity.Id).Append(" type=") .Append(cartEntity.BaseGuid) .Append(" carried=") .Append(ReuseSweep.Count) .Append(" slotted=") .Append(SlottedCount(cart)) .Append(" cap=") .Append(flag ? capacity.ToString() : "none") .Append(" blessed=") .Append(CartCapacity.Blessed); stringBuilder.Append(" | fields c1=").Append(Short(cart.Carried1)).Append(" c2=") .Append(Short(cart.Carried2)) .Append(" c3=") .Append(Short(cart.Carried3)) .Append(" c4=") .Append(Short(cart.Carried4)) .Append(" c5=") .Append(Short(cart.Carried5)); stringBuilder.Append(" | items"); foreach (EntityWrapper item in ReuseSweep) { stringBuilder.Append(' ').Append(Short(item.Id)).Append(MatchedKey(dictionary, item.Id)); } stringBuilder.Append(" | params"); if (dictionary == null || dictionary.Count == 0) { stringBuilder.Append(" <none>"); } else { foreach (KeyValuePair<string, string> item2 in dictionary) { stringBuilder.Append(' ').Append(item2.Key).Append('=') .Append((item2.Value.Length == 36) ? Short(item2.Value) : item2.Value); } } ModLog.AdvancedOnChange("sweep:" + cartEntity.Id, stringBuilder.ToString()); } private static string MatchedKey(IDictionary<string, string> dictionary, Guid itemId) { if (dictionary == null) { return "(?)"; } string b = itemId.ToString(); foreach (KeyValuePair<string, string> item in dictionary) { if (!string.Equals(item.Key, "bc_cargo", StringComparison.Ordinal) && string.Equals(item.Value, b, StringComparison.OrdinalIgnoreCase)) { return "(" + item.Key + ")"; } } return "(UNSLOTTED)"; } private static string Short(Guid? id) { if (!id.HasValue) { return "-"; } return Short(id.Value.ToString()); } private static string Short(Guid id) { return Short(id.ToString()); } private static string Short(string id) { if (id.Length < 8) { return id; } return id.Substring(0, 8); } private static void Publish(EntityWrapper cartEntity, CartState state) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (state.Written != null && SameOrder(state.Extras, state.WrittenIds)) { return; } string text = CartCargoSync.Pack(state.Extras); if (!string.Equals(text, state.Written, StringComparison.Ordinal)) { state.Written = text; state.WrittenIds.Clear(); state.WrittenIds.AddRange(state.Extras); if (ModLog.AdvancedEnabled) { ModLog.Advanced("PUBLISH cart=" + Short(cartEntity.Id) + " bc_cargo=\"" + text + "\""); } ServerEntitySystemManager.UpdateEntityParameter(cartEntity, "bc_cargo", text, SyncStrategy.Everyone(), true); } } private static bool SameOrder(List<Guid> current, List<Guid> written) { if (current.Count != written.Count) { return false; } for (int i = 0; i < current.Count; i++) { if (current[i] != written[i]) { return false; } } return true; } private static void StableOrder(CartState state, List<EntityWrapper> items) { ReuseOrder.Clear(); ReuseOrderIds.Clear(); ReuseItemIds.Clear(); foreach (EntityWrapper item in items) { ReuseItemIds.Add(item.Id); } foreach (Guid extra in state.Extras) { if (ReuseItemIds.Contains(extra)) { ReuseOrder.Add(extra); ReuseOrderIds.Add(extra); } } foreach (EntityWrapper item2 in items) { if (ReuseOrderIds.Add(item2.Id)) { ReuseOrder.Add(item2.Id); } } } private static EntityWrapper Find(List<EntityWrapper> items, Guid id) { foreach (EntityWrapper item in items) { if (item.Id == id) { return item; } } return null; } private static void CollectCarried(ServerCart2Controller cart, List<EntityWrapper> into) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) into.Clear(); EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null) { return; } CollisionSpecialGroup entityCollisionsOrNull = ServerWorldHandler.GetEntityCollisionsOrNull(entity.WorldId); if (entityCollisionsOrNull == null) { return; } int num = (int)(WorldInfo.TileSize * 2f); Rectangle val = default(Rectangle); ((Rectangle)(ref val))..ctor((int)entity.Position2.X - num, (int)entity.Position2.Y - num, num * 2, num * 2); entityCollisionsOrNull.GetEntitiesInRectangleArea(RectangleF.op_Implicit(val), into); for (int num2 = into.Count - 1; num2 >= 0; num2--) { EntityWrapper val2 = into[num2]; if (val2 == null || val2.Removed || !ComponentExtension.HasFlags(val2.Mask, (Component)1024) || !val2.Carriable || val2.CarrierId != entity.Id) { into.RemoveAt(num2); } } } private static bool IsSlotted(ServerCart2Controller cart, Guid itemId) { Parameters parameters = ((AbstractController)cart).Parameters; if (parameters == null) { return false; } Dictionary<string, string> dictionary = parameters.Dictionary; if (dictionary == null || dictionary.Count == 0) { return false; } Span<char> span = stackalloc char[36]; itemId.TryFormat(span, out var _); foreach (KeyValuePair<string, string> item in dictionary) { if (!string.Equals(item.Key, "bc_cargo", StringComparison.Ordinal) && MemoryExtensions.Equals(item.Value.AsSpan(), span, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static void Pin(EntityWrapper cartEntity, EntityWrapper item) { item.NoEntityCollision = true; item.NoTerrainCollision = true; item.CarrierId = cartEntity.Id; } private static void ClearThrowState(EntityWrapper item) { item.IsThrown = false; item.ThrowerId = null; ServerEntityModel val = ServerEntityHelper.ToServerEntity(item); if (val != null) { val.IsThrown = false; val.ThrowerId = null; val.ThrownCastSpellArgs = null; val.KillWhenThrowHit = false; } } private static void Release(EntityWrapper cartEntity, EntityWrapper item) { if (item.CarrierId == cartEntity.Id) { item.CarrierId = null; } item.NoEntityCollision = false; item.NoTerrainCollision = false; } private static void Stack(EntityWrapper cartEntity, EntityWrapper item) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) item.Position = cartEntity.Position + new Vector3(0f, 0f, 6f); item.Velocity = cartEntity.Velocity; item.System.CollisionGroup.UpdatePositionAndVelocity(item); } } internal static class CartCargoClient { private sealed class ClientState { internal readonly List<Guid> Slots = new List<Guid>(); internal string LastRaw; } private const float RingRadius = 2.5f; private const float RingHeight = 6f; private const float RingJitter = 0.02f; private const int SeatsPerLayer = 4; private const float LayerStep = 6f; private const float DepthNudge = 0.02f; private const string PickupSound = "event:/hits/impact/impact_storage"; private static readonly ConditionalWeakTable<Cart2Controller, ClientState> States = new ConditionalWeakTable<Cart2Controller, ClientState>(); private static readonly List<Guid> ReuseIncoming = new List<Guid>(); private static readonly HashSet<Guid> ReuseIncomingSeen = new HashSet<Guid>(); internal static void SyncSlots(Cart2Controller cart) { SyncSlots(cart, States.GetOrCreateValue(cart)); } private static void SyncSlots(Cart2Controller cart, ClientState state) { List<Guid> slots = state.Slots; Parameters parameters = ((AbstractController)cart).Parameters; if (parameters == null) { return; } string text = parameters.GetString("bc_cargo", string.Empty); if (string.Equals(state.LastRaw, text, StringComparison.Ordinal)) { return; } bool flag = state.LastRaw == null; state.LastRaw = text; if (ModLog.AdvancedEnabled) { ModLog.Advanced("CLIENT SYNC cart=" + ((AbstractController)cart).Entity.Id.ToString() + " bc_cargo=\"" + text + "\" had=" + slots.Count); } CartCargoSync.Unpack(text, ReuseIncoming, ReuseIncomingSeen); foreach (Guid item in slots) { if (!ReuseIncoming.Contains(item)) { ReleaseOne(cart, item); } } bool flag2 = false; foreach (Guid item2 in ReuseIncoming) { if (!slots.Contains(item2)) { flag2 = true; break; } } slots.Clear(); foreach (Guid item3 in ReuseIncoming) { slots.Add(item3); } if (flag2 && !flag) { SoundExtensions.EmitSoundOneShot(((AbstractController)cart).Entity, "event:/hits/impact/impact_storage", 1f, 1f); } } internal static void UpdateSlots(Cart2Controller cart) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed) { return; } ClientState orCreateValue = States.GetOrCreateValue(cart); SyncSlots(cart, orCreateValue); List<Guid> slots = orCreateValue.Slots; for (int i = 0; i < slots.Count; i++) { if (GameState.Entities.TryGetValue(slots[i], out var value) && value != null && !value.Removed) { if (value.CarrierId == GameState.LocalPlayer.EntityId) { value.NoEntityCollision = false; value.NoTerrainCollision = false; } else if (!value.CarrierId.HasValue || !(value.CarrierId != entity.Id)) { value.IsThrown = false; value.ThrowerId = null; value.NoEntityCollision = true; value.NoTerrainCollision = true; value.CarrierId = entity.Id; Place(cart, value, i); } } } } internal static void ReleaseAll(Cart2Controller cart) { List<Guid> slots = States.GetOrCreateValue(cart).Slots; foreach (Guid item in slots) { ReleaseOne(cart, item); } slots.Clear(); } private static void ReleaseOne(Cart2Controller cart, Guid id) { if (GameState.Entities.TryGetValue(id, out var value)) { value.NoEntityCollision = false; value.NoTerrainCollision = false; if (value.CarrierId == ((AbstractController)cart).Entity.Id) { value.CarrierId = null; } } } private static void Place(Cart2Controller cart, EntityWrapper item, int index) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) int num = index / 4; Vector3 val = DirectionToVector3(index % 4) * (2.5f + (float)index * 0.02f); val.Z = 6f + (float)num * 6f; val = VectorExtension.ToXzy(val); Vector3 val2 = default(Vector3); Vector3.Transform(ref val, ref ((AbstractController)cart).Entity.MeshTransformMatrixRef, ref val2); item.Position = VectorExtension.ToXzy(val2) + ((AbstractController)cart).Entity.Position + ((AbstractController)cart).Entity.Velocity * ((AbstractController)cart).Entity.Fdt + new Vector3(0f, (float)index * 0.02f, 0f); item.Velocity = ((AbstractController)cart).Entity.Velocity; item.System.CollisionGroup.UpdatePositionAndVelocity(item); } private static Vector3 DirectionToVector3(float direction) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) float num = direction * ((float)Math.PI / 2f); return new Vector3((float)Math.Cos(num), (float)Math.Sin(num), 0f); } } internal static class CartCargoSync { internal const string CargoKey = "bc_cargo"; internal const int MaxExtras = 128; private const char Separator = ','; private const int GuidLength = 36; internal static string Pack(List<Guid> extras) { if (extras == null || extras.Count == 0) { return string.Empty; } return string.Create(extras.Count * 37 - 1, extras, delegate(Span<char> chars, List<Guid> ids) { int num = 0; for (int i = 0; i < ids.Count; i++) { if (i > 0) { chars[num++] = ','; } ids[i].TryFormat(chars.Slice(num), out var charsWritten); num += charsWritten; } }); } internal static void Unpack(string raw, List<Guid> into, HashSet<Guid> seen) { into.Clear(); seen.Clear(); if (string.IsNullOrEmpty(raw)) { return; } ReadOnlySpan<char> readOnlySpan = raw.AsSpan(); while (!readOnlySpan.IsEmpty) { int num = readOnlySpan.IndexOf(','); if (Guid.TryParse((num >= 0) ? readOnlySpan.Slice(0, num) : readOnlySpan, out var result) && seen.Add(result)) { into.Add(result); } if (num >= 0) { readOnlySpan = readOnlySpan.Slice(num + 1); continue; } break; } } } internal static class CartDisconnect { internal static void NoteClientDetach(Cart2Controller cart, Guid? previousTarget) { if (cart != null && OverlayText.CanShow(OverlayText.Disconnect) && previousTarget.HasValue && !cart.FollowingId.HasValue) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity != null && !entity.Removed && OverlayText.PulledByLocalPlayer(previousTarget.Value)) { OverlayText.Show(OverlayText.Disconnect, entity); } } } } internal static class CartOverlay { private struct ParsedParameter { internal string Raw; internal Guid? Id; } private sealed class OverlayState { internal readonly WeakReference<Cart2Controller> Owner; internal int Count = -1; internal string Text = string.Empty; internal string MeasuredText; internal StaticSpriteFont MeasuredFont; internal float MeasuredScale; internal float TextWidth; internal float TextHeight; internal long NextRefreshTick; internal string CargoRaw; internal readonly List<Guid> Extras = new List<Guid>(); internal readonly HashSet<Guid> ExtrasSeen = new HashSet<Guid>(); internal readonly Dictionary<string, ParsedParameter> Parameters = new Dictionary<string, ParsedParameter>(StringComparer.Ordinal); internal readonly HashSet<string> SeenKeys = new HashSet<string>(StringComparer.Ordinal); internal readonly List<string> RemovedKeys = new List<string>(); internal readonly HashSet<Guid> Candidates = new HashSet<Guid>(); internal OverlayState(Cart2Controller owner) { Owner = new WeakReference<Cart2Controller>(owner); } } private const int RefreshIntervalMs = 150; private static readonly ConditionalWeakTable<Cart2Controller, OverlayState> States = new ConditionalWeakTable<Cart2Controller, OverlayState>(); private static readonly List<OverlayState> Ordered = new List<OverlayState>(); internal static bool Showing { get { if (ModConfig.Enabled == null || !ModConfig.Enabled.Value) { return false; } if (ModConfig.CartOverlaysEnabled == null || !ModConfig.CartOverlaysEnabled.Value) { return false; } if (!Flag(ModConfig.CartOverlayShowAboveVanilla) && !Flag(ModConfig.CartOverlayShowVanilla)) { return Flag(ModConfig.CartOverlayShowEmpty); } return true; } } internal static void Track(Cart2Controller cart) { if (cart == null) { return; } if (!Showing) { if (States.TryGetValue(cart, out var value)) { value.NextRefreshTick = 0L; value.Count = -1; } return; } EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed) { Forget(cart); return; } if (!States.TryGetValue(cart, out var value2)) { value2 = new OverlayState(cart); States.Add(cart, value2); PruneCollected(); Ordered.Add(value2); } long tickCount = Environment.TickCount64; if (tickCount >= value2.NextRefreshTick) { value2.NextRefreshTick = tickCount + 150; int num = CountCargo(cart, entity, value2); if (num != value2.Count) { value2.Count = num; value2.Text = num.ToString(CultureInfo.InvariantCulture); } } } internal static void MarkDirty(Cart2Controller cart) { if (cart != null && States.TryGetValue(cart, out var value)) { value.NextRefreshTick = 0L; } } internal static void Forget(Cart2Controller cart) { if (cart != null && States.TryGetValue(cart, out var value)) { States.Remove(cart); Ordered.Remove(value); } } private static void PruneCollected() { for (int num = Ordered.Count - 1; num >= 0; num--) { if (!Ordered[num].Owner.TryGetTarget(out var _)) { Ordered.RemoveAt(num); } } } internal static void Draw(SpriteBatch batch, Matrix cameraMatrix) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) if (batch == null || !Showing) { return; } Vector2 currentPositionCenter = Globals.Game.Camera.CurrentPositionCenter; Viewport viewport = ((GraphicsResource)batch).GraphicsDevice.Viewport; float num = (float)((Viewport)(ref viewport)).Width * 0.8f; num *= num; float num2 = (float)Globals.InterfaceScale * 1f; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(num2, num2); Vector2? scale = val; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0f, -16f); for (int i = 0; i < Ordered.Count; i++) { OverlayState overlayState = Ordered[i]; if (overlayState.Count < 0 || !Visible(overlayState.Count) || !overlayState.Owner.TryGetTarget(out var target) || target == null) { continue; } EntityWrapper entity = ((AbstractController)target).Entity; if (entity == null || entity.Removed || !GameState.Entities.ContainsKey(entity.Id)) { continue; } Vector2 val3 = VectorExtension.ToScreenSpace(entity.Position) + val2; if (!(Vector2.DistanceSquared(val3, currentPositionCenter) > num) && (!OverlayText.CullWhenZoomedOut || !DeferredRenderer.IsOutsideFoW(entity))) { Vector2 val4 = Vector2.Transform(val3, cameraMatrix); val4.X = MathF.Round(val4.X); val4.Y = MathF.Round(val4.Y); Measure(overlayState, num2, val); float halfWidth = overlayState.TextWidth / 2f; if (OverlayText.DrawPlate) { float num3 = overlayState.TextWidth + 4f; float num4 = overlayState.TextHeight + 2f; PrimitiveRenderer.DrawRect(batch, val4 - new Vector2(num3 / 2f, 1f), num3, num4, OverlayText.PlateColor, 0f); } if (OverlayText.DrawShadow) { DrawCentered(batch, overlayState.Text, val4 + new Vector2(OverlayText.ShadowOffset, OverlayText.ShadowOffset), halfWidth, scale, OverlayText.ShadowColor); DrawCentered(batch, overlayState.Text, val4 + new Vector2(OverlayText.ShadowOffset, 0f - OverlayText.ShadowOffset), halfWidth, scale, OverlayText.ShadowColor); DrawCentered(batch, overlayState.Text, val4 + new Vector2(0f - OverlayText.ShadowOffset, OverlayText.ShadowOffset), halfWidth, scale, OverlayText.ShadowColor); DrawCentered(batch, overlayState.Text, val4 + new Vector2(0f - OverlayText.ShadowOffset, 0f - OverlayText.ShadowOffset), halfWidth, scale, OverlayText.ShadowColor); } DrawCentered(batch, overlayState.Text, val4, halfWidth, scale, OverlayText.TextColor); } } } private static void Measure(OverlayState state, float scale, Vector2 scaleVector) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if ((object)state.MeasuredText != state.Text || state.MeasuredFont != OverlayText.Font || state.MeasuredScale != scale) { Bounds val = ((SpriteFontBase)OverlayText.Font).TextBounds(state.Text, Vector2.Zero, (Vector2?)scaleVector, 0f, 0f, (FontSystemEffect)0, 0); state.TextWidth = val.X2; state.TextHeight = val.Y2; state.MeasuredText = state.Text; state.MeasuredFont = OverlayText.Font; state.MeasuredScale = scale; } } private static void DrawCentered(SpriteBatch batch, string text, Vector2 position, float halfWidth, Vector2? scale, Color color) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) ((SpriteFontBase)OverlayText.Font).DrawText(batch, text, new Vector2(position.X - halfWidth, position.Y), color, 0f, default(Vector2), scale, 0f, 0f, 0f, (TextStyle)0, (FontSystemEffect)0, 0, int.MaxValue); } private static bool Visible(int count) { if (count == 0) { return Flag(ModConfig.CartOverlayShowEmpty); } if (count > 5) { return Flag(ModConfig.CartOverlayShowAboveVanilla); } return Flag(ModConfig.CartOverlayShowVanilla); } private static int CountCargo(Cart2Controller cart, EntityWrapper cartEntity, OverlayState state) { Parameters parameters = ((AbstractController)cart).Parameters; if (parameters == null) { return 0; } string text = parameters.GetString("bc_cargo", string.Empty); if (!string.Equals(state.CargoRaw, text, StringComparison.Ordinal)) { CartCargoSync.Unpack(text, state.Extras, state.ExtrasSeen); state.CargoRaw = text; } state.SeenKeys.Clear(); Dictionary<string, string> dictionary = parameters.Dictionary; if (dictionary != null) { foreach (KeyValuePair<string, string> item in dictionary) { if (!string.Equals(item.Key, "bc_cargo", StringComparison.Ordinal)) { state.SeenKeys.Add(item.Key); if (!state.Parameters.TryGetValue(item.Key, out var value) || !string.Equals(value.Raw, item.Value, StringComparison.Ordinal)) { value.Raw = item.Value; value.Id = (Guid.TryParse(item.Value, out var result) ? new Guid?(result) : ((Guid?)null)); state.Parameters[item.Key] = value; } } } } state.RemovedKeys.Clear(); state.Candidates.Clear(); foreach (KeyValuePair<string, ParsedParameter> parameter in state.Parameters) { if (!state.SeenKeys.Contains(parameter.Key)) { state.RemovedKeys.Add(parameter.Key); } else if (parameter.Value.Id.HasValue) { state.Candidates.Add(parameter.Value.Id.Value); } } foreach (string removedKey in state.RemovedKeys) { state.Parameters.Remove(removedKey); } state.RemovedKeys.Clear(); foreach (Guid extra in state.Extras) { state.Candidates.Add(extra); } int num = 0; foreach (Guid candidate in state.Candidates) { if (GameState.Entities.TryGetValue(candidate, out var value2) && value2 != null && !value2.Removed && value2.Carriable && value2.CarrierId == cartEntity.Id) { num++; } } return num; } private static bool Flag(ConfigEntry<bool> entry) { return entry?.Value ?? false; } } internal static class ConstructionSiteMessage { private sealed class SendState { public long NextSendTick; } private const byte KeptEffectId = 201; private const byte StockpileEffectId = 202; private const int SendIntervalMs = 3000; private const int MaxChainWalk = 32; private static readonly ConditionalWeakTable<ServerCart2Controller, SendState> KeptStates = new ConditionalWeakTable<ServerCart2Controller, SendState>(); private static readonly ConditionalWeakTable<ServerCart2Controller, SendState> StockpileStates = new ConditionalWeakTable<ServerCart2Controller, SendState>(); internal static void NoteRefused(ServerCart2Controller cart) { Send(cart, KeptStates, 201); } internal static void NoteStockpileSkipped(ServerCart2Controller cart) { Send(cart, StockpileStates, 202); } internal static void NoteEffect(Cart2Controller cart, byte vfx) { OverlayText.Message message; switch (vfx) { case 201: message = OverlayText.ProtectionKept; break; case 202: message = OverlayText.StockpileSkipped; break; default: return; } if (cart != null && OverlayText.CanShow(message)) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity != null && !entity.Removed && cart.FollowingId.HasValue && OverlayText.PulledByLocalPlayer(cart.FollowingId.Value)) { OverlayText.Show(message, entity); } } } private static void Send(ServerCart2Controller cart, ConditionalWeakTable<ServerCart2Controller, SendState> states, byte effectId) { EntityWrapper entity = ((AbstractController)cart).Entity; if (entity == null || entity.Removed) { return; } SendState orCreateValue = states.GetOrCreateValue(cart); long tickCount = Environment.TickCount64; if (tickCount >= orCreateValue.NextSendTick) { orCreateValue.NextSendTick = tickCount + 3000; if (PulledByPlayer(cart)) { EntityServerManager.SendEntityVfxMessage(entity.Id, effectId); } } } private static bool PulledByPlayer(ServerCart2Controller cart) { ServerCart2Controller val = cart; for (int i = 0; i < 32; i++) { Guid? followingId = val.FollowingId; if (!followingId.HasValue || !ServerGameState.Entities.TryGetValue(followingId.Value, out var value)) { return false; } EntityWrapper entityWrapper = value.EntityWrapper; if (entityWrapper == null) { return false; } AbstractController controller = entityWrapper.Controller; ServerCart2Controller val2 = (ServerCart2Controller)(object)((controller is ServerCart2Controller) ? controller : null); if (val2 != null) { val = val2; continue; } return true; } return false; } } internal static class ConstructionSiteProtection { private static readonly List<EntityWrapper> ReuseArea = new List<EntityWrapper>(); internal static bool Active { get { if (ModConfig.LoadConstructionSiteProtection.Value && ModConfig.Enabled.Value) { return ModConfig.ConstructionSiteProtectionEnabled.Value; } return false; } } internal static bool IsOnSite(EntityWrapper entity) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return FindSite(entity.WorldId, entity.Position2) != null; } internal static bool IsProtected(EntityWrapper item) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) ConstructionMaterialsAggregate constructionMaterials = item.ConstructionMaterials; if (constructionMaterials == null || constructionMaterials.IsEmpty || ServerGameState.Config.DisableConstructionMaterialRequirements) { return false; } ConstructionSiteModel val = FindSite(item.WorldId, item.Position2); if (val == null) { return false; } ConstructionModel constructionOrNull = ConstructionDataBase.GetConstructionOrNull(val.ConstructionId); if (constructionOrNull == null) { return false; } Dictionary<string, int> materialAmounts = constructionOrNull.ConstructionMaterialsRequirement.MaterialAmounts; if (!Supplies(materialAmounts, constructionMaterials)) { return false; } CollectArea(val); foreach (KeyValuePair<string, int> item2 in materialAmounts) { if (item2.Value > 0) { int amountForResource = constructionMaterials.GetAmountForResource(item2.Key); if (amountForResource > 0 && LooseAmount(val, item2.Key) - amountForResource < item2.Value) { return true; } } } return false; } private static bool Supplies(Dictionary<string, int> required, ConstructionMaterialsAggregate materials) { foreach (KeyValuePair<string, int> item in required) { if (item.Value > 0 && materials.GetAmountForResource(item.Key) > 0) { return true; } } return false; } private static ConstructionSiteModel FindSite(Guid worldId, Vector2 position) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!ServerRunState.WorldToConstructionSitesTree.TryGetValue(worldId, out var value)) { return null; } Point val = ToTile(position); QueryRect<ConstructionSiteModel> enumerator = value.QueryFast(RectangleF.op_Implicit(new Rectangle(val.X, val.Y, 1, 1))).GetEnumerator(); try { while (enumerator.MoveNext()) { ConstructionSiteModel current = enumerator.Current; if (((Rectangle)(ref current.TileBounds)).Contains(val)) { return current; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return null; } private static void CollectArea(ConstructionSiteModel site) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) ReuseArea.Clear(); CollisionSpecialGroup entityCollisionsOrNull = ServerWorldHandler.GetEntityCollisionsOrNull(site.WorldId); if (entityCollisionsOrNull != null) { float tileSize = WorldInfo.TileSize; Rectangle tileBounds = site.TileBounds; Rectangle val = default(Rectangle); ((Rectangle)(ref val))..ctor((int)((float)tileBounds.X * tileSize), (int)((float)tileBounds.Y * tileSize), (int)((float)tileBounds.Width * tileSize), (int)((float)tileBounds.Height * tileSize)); entityCollisionsOrNull.GetEntitiesInRectangleArea(RectangleF.op_Implicit(val), ReuseArea); } } private static int LooseAmount(ConstructionSiteModel site, string resourceId) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) int num = 0; foreach (EntityWrapper item in ReuseArea) { if (!item.Removed && !item.CarrierId.HasValue && item.ConstructionMaterials != null && ((Rectangle)(ref site.TileBounds)).Contains(ToTile(item.Position2))) { num += item.ConstructionMaterials.GetAmountForResource(resourceId); } } return num; } private static Point ToTile(Vector2 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) float tileSize = WorldInfo.TileSize; return new Point((int)(position.X / tileSize), (int)(position.Y / tileSize)); } } internal static class OverlayText { internal sealed class Message { internal readonly string Text; internal readonly Color Color; internal readonly float Duration; internal readonly float Scale; internal readonly bool OncePerWorldLoad; internal readonly Func<ConfigEntry<bool>> Setting; internal bool Shown; internal Message(string text, Color color, float duration, float scale, bool oncePerWorldLoad, Func<ConfigEntry<bool>> setting) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Text = text; Color = color; Duration = duration; Scale = scale; OncePerWorldLoad = oncePerWorldLoad; Setting = setting; } } internal static readonly StaticSpriteFont Font = PixelSpriteFont.ArialPixel; internal const float TextScale = 1f; internal const float AnchorHeight = 16f; internal static readonly Color TextColor = StyleHelper.TextNormalColor; internal static readonly bool DrawShadow = true; internal static readonly float ShadowOffset = 1f; internal static readonly Color ShadowColor = Color.Black; internal static readonly bool DrawPlate = false; internal const float PlatePaddingX = 4f; internal const float PlatePaddingY = 2f; internal static readonly Color PlateColor = new Color(Color.DimGray, 0.3f); internal static readonly bool CullWhenZoomedOut = true; internal const float ViewportCullFactor = 0.8f; private const float MessageHeight = 40f; internal static readonly Message Disconnect = new Message("Cart disconnected!", StyleHelper.TextWarningColor, 2f, 1f, oncePerWorldLoad: false, () => ModConfig.CartOverlayDisconnectMessage); internal static readonly Message ProtectionKept = new Message("Construction Site Protection is ON,\ncan't pick up this item", StyleHelper.TextGreenColor, 5f, 1f, oncePerWorldLoad: true, () => ModConfig.CartOverlayProtectionMessage); internal static readonly Message StockpileSkipped = new Message("Construction Site Protection is ON,\ncan't pick items from Stockpiles\nwhen the Cart is on a Construction Site", StyleHelper.TextGreenColor, 6f, 1f, oncePerWorldLoad: true, () => ModConfig.CartOverlayProtectionMessage); private static readonly Message[] Messages = new Message[3] { Disconnect, ProtectionKept, StockpileSkipped }; private const int MaxChainWalk = 32; internal static bool CanShow(Message message) { if (ModConfig.Enabled == null || !ModConfig.Enabled.Value) { return false; } if (ModConfig.CartOverlaysEnabled == null || !ModConfig.CartOverlaysEnabled.Value) { return false; } ConfigEntry<bool> val = message.Setting(); if (val == null || !val.Value) { return false; } if (message.OncePerWorldLoad) { return !message.Shown; } return true; } internal static void Show(Message message, EntityWrapper cartEntity) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (cartEntity == null || cartEntity.Removed || !CanShow(message)) { return; } message.Shown = true; Vector2 val = VectorExtension.ToScreenSpace(cartEntity.Position) + new Vector2(0f, -40f); lock (FloatingTextSystem.Texts) { FloatingTextSystem.AddNewFloatText(message.Text, val, message.Color, (string)null, 1, 0f, (string)null, message.Scale, false); foreach (FloatText text in FloatingTextSystem.Texts) { if ((object)text.PrefixText == message.Text) { text.LifeLength = message.Duration; } } } } internal static void ResetForWorldLoad() { Message[] messages = Messages; for (int i = 0; i < messages.Length; i++) { messages[i].Shown = false; } } internal static bool PulledByLocalPlayer(Guid start) { LocalPlayer localPlayer = GameState.LocalPlayer; if (localPlayer == null) { return false; } Guid? guid = start; for (int i = 0; i < 32; i++) { if (!guid.HasValue) { break; } if (!GameState.Entities.TryGetValue(guid.Value, out var value) || value == null) { return false; } AbstractController controller = value.Controller; Cart2Controller val = (Cart2Controller)(object)((controller is Cart2Controller) ? controller : null); if (val != null) { guid = val.FollowingId; continue; } return value.Id == localPlayer.EntityId; } return false; } } internal static class FeatureLoader { private sealed class Feature { internal readonly string Name; internal readonly Func<bool> Wanted; internal readonly Action Fail; internal readonly Type[] Types; internal Feature(string name, ModConfig.LoadSwitch load, params Type[] types) : this(name, () => load.Value, load.Fail, types) { } internal Feature(string name, Func<bool> wanted, Action fail, params Type[] types) { Name = name; Wanted = wanted; Fail = fail; Types = types; } } private static readonly List<string> FailedNames = new List<string>(); internal static IReadOnlyList<string> Failed => FailedNames; internal static void LoadAll() { Feature[] array = new Feature[11] { new Feature("Chain Overflow", ModConfig.LoadChainOverflow, typeof(ChainOverflowPatch), typeof(CartAccess)), new Feature("Grab Priority", () => ModConfig.LoadGrabPriority.Value || ModConfig.LoadCartCapacity.Value, ModConfig.LoadGrabPriority.Fail, typeof(GrabPriorityPatch)), new Feature("Cart Release Fix", ModConfig.LoadCartReleaseFix, typeof(CartReleaseFixPatch)), new Feature("Cart Capacity", ModConfig.LoadCartCapacity, typeof(CartCapacityPatch), typeof(CartCapacityClientPatch)), new Feature("Cart Overlays", ModConfig.LoadCartOverlays, typeof(CartOverlayPatch), typeof(CartDisconnectPatch), typeof(ConstructionSiteMessagePatch)), new Feature("Construction Site Protection", ModConfig.LoadConstructionSiteProtection, typeof(ConstructionSiteProtectionPatch)), new Feature("Collect Range", ModConfig.LoadCollectRange, typeof(CollectRangePatch), typeof(CartAccess)), new Feature("Deposit Range", ModConfig.LoadDepositRange, typeof(DepositRangePatch)), new Feature("Connect Range", ModConfig.LoadConnectRange, typeof(ConnectRangePatch)), new Feature("Stockpile Range", ModConfig.LoadStockpileRange, typeof(StockpileRangePatch)), new Feature("Diagnostic Logs", () => ModConfig.DiagnosticsArmed, delegate { ModConfig.DiagnosticsArmed = false; }, typeof(DiagnosticsPatch), typeof(ModWatchdog)) }; foreach (Feature feature in array) { if (feature.Wanted()) { Load(feature); } } if (FailedNames.Count > 0) { LoadFailureNotice(); } } private static void Load(Feature feature) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown string text = feature.Name.Replace(" ", string.Empty); Harmony val = new Harmony("com.beesq.romestead.bettercarts." + text); try { List<Type> list = new List<Type>(); Type[] types = feature.Types; for (int i = 0; i < types.Length; i++) { AddWithNested(types[i], list); } foreach (Type item in list) { if (!item.ContainsGenericParameters) { RuntimeHelpers.RunClassConstructor(item.TypeHandle); } } foreach (Type item2 in list) { val.CreateClassProcessor(item2).Patch(); } } catch (Exception ex) { try { val.UnpatchSelf(); } catch (Exception ex2) { ModLog.Fault("FeatureLoader." + text + ".Unpatch", ex2); } feature.Fail(); FailedNames.Add(feature.Name); ModLog.Error(feature.Name + " failed to load and is turned off for this session, usually because a game update changed what it patches. The other features are not affected; the cause follows:"); ModLog.Fault("FeatureLoader." + text, ex); } } private static void LoadFailureNotice() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown Harmony val = new Harmony("com.beesq.romestead.bettercarts.FailureNotice"); try { List<Type> list = new List<Type>(); AddWithNested(typeof(FailureNoticePatch), list); foreach (Type item in list) { val.CreateClassProcessor(item).Patch(); } } catch (Exception ex) { ModLog.Fault("FeatureLoader.FailureNotice", ex); } } private static void AddWithNested(Type type, List<Type> types) { types.Add(type); Type[] nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < nestedTypes.Length; i++) { AddWithNested(nestedTypes[i], types); } } } internal static class ModConfig { internal sealed class LoadSwitch { internal bool Value { get; private set; } internal LoadSwitch(ConfigEntry<bool> entry, bool troubleshooting) { Value = !troubleshooting || entry.Value; } internal void Fail() { Value = false; } } internal static ConfigEntry<bool> Enabled; internal static LoadSwitch LoadChainOverflow; internal static LoadSwitch LoadGrabPriority; internal static LoadSwitch LoadCartReleaseFix; internal static LoadSwitch LoadCartCapacity; internal static LoadSwitch LoadCartOverlays; internal static LoadSwitch LoadConstructionSiteProtection; internal static LoadSwitch LoadCollectRange; internal static LoadSwitch LoadDepositRange; internal static LoadSwitch LoadConnectRange; internal static LoadSwitch LoadStockpileRange; internal static ConfigEntry<bool> ChainOverflowEnabled; internal static ConfigEntry<bool> DepositRangeEnabled; internal static ConfigEntry<int> DepositRange; internal static ConfigEntry<bool> CollectRangeEnabled; internal static ConfigEntry<int> CollectRange; internal static ConfigEntry<bool> ConnectRangeEnabled; internal static ConfigEntry<int> ConnectRange; internal static ConfigEntry<bool> GrabPriorityEnabled; internal static ConfigEntry<bool> CartReleaseFixEnabled; internal static ConfigEntry<bool> CartCapacityEnabled; internal static ConfigEntry<int> CartCapacityBlessingBonus; internal static ConfigEntry<bool> CartCapacityEjectOverflow; internal static ConfigEntry<bool> CartOverlaysEnabled; internal static ConfigEntry<bool> CartOverlayShowAboveVanilla; internal static ConfigEntry<bool> CartOverlayShowVanilla; internal static ConfigEntry<bool> CartOverlayShowEmpty; internal static ConfigEntry<bool> CartOverlayDisconnectMessage; internal static ConfigEntry<bool> CartOverlayProtectionMessage; internal static ConfigEntry<bool> ConstructionSiteProtectionEnabled; internal static ConfigEntry<bool> StockpileRangeEnabled; internal static ConfigEntry<int> StockpileRange; internal static ConfigEntry<bool> StockpileWhilePulled; internal static ConfigEntry<bool> StockpileWhileParked; internal static ConfigEntry<bool> TroubleshootingEnabled; internal static ConfigEntry<bool> DiagnosticLogs; internal static bool DiagnosticsArmed; internal static ConfigEntry<bool> DiagSave; internal static ConfigEntry<bool> DiagMemory; internal static ConfigEntry<bool> DiagCensus; internal static ConfigEntry<bool> DiagCapacity; internal static ConfigEntry<bool> DiagPickup; internal static ConfigEntry<bool> DiagChain; internal static ConfigEntry<bool> DiagStateDump; internal static ConfigEntry<bool> DiagLogFile; internal static readonly List<ConfigEntryBase> Bound = new List<ConfigEntryBase>(); internal static void Init(ConfigFile config) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Expected O, but got Unknown //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Expected O, but got Unknown //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Expected O, but got Unknown //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Expected O, but got Unknown //IL_0384: Unknown result type (might be due to invalid IL or missing references) //IL_038e: Expected O, but got Unknown //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Expected O, but got Unknown //IL_040d: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Expected O, but got Unknown //IL_0455: Unknown result type (might be due to invalid IL or missing references) //IL_045f: Expected O, but got Unknown //IL_0496: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Expected O, but got Unknown //IL_04df: Unknown result type (might be due to invalid IL or missing references) //IL_04e9: Expected O, but got Unknown //IL_0520: Unknown result type (might be due to invalid IL or missing references) //IL_052a: Expected O, but got Unknown //IL_0568: Unknown result type (might be due to invalid IL or missing references) //IL_0572: Expected O, but got Unknown //IL_05b0: Unknown result type (might be due to invalid IL or missing references) //IL_05ba: Expected O, but got Unknown //IL_05f8: Unknown result type (might be due to invalid IL or missing references) //IL_0602: Expected O, but got Unknown //IL_063f: Unknown result type (might be due to invalid IL or missing references) //IL_0649: Expected O, but got Unknown //IL_068d: Unknown result type (might be due to invalid IL or missing references) //IL_0697: Expected O, but got Unknown //IL_06db: Unknown result type (might be due to invalid IL or missing references) //IL_06e5: Expected O, but got Unknown //IL_0722: Unknown result type (might be due to invalid IL or missing references) //IL_072c: Expected O, but got Unknown //IL_0769: Unknown result type (might be due to invalid IL or missing references) //IL_0773: Expected O, but got Unknown //IL_07b0: Unknown result type (might be due to invalid IL or missing references) //IL_07ba: Expected O, but got Unknown //IL_07f7: Unknown result type (might be due to invalid IL or missing references) //IL_0801: Expected O, but got Unknown //IL_083e: Unknown result type (might be due to invalid IL or missing references) //IL_0848: Expected O, but got Unknown //IL_0886: Unknown result type (might be due to invalid IL or missing references) //IL_0890: Expected O, but got Unknown //IL_08cf: Unknown result type (might be due to invalid IL or missing references) //IL_08d9: Expected O, but got Unknown //IL_0910: Unknown result type (might be due to invalid IL or missing references) //IL_091a: Expected O, but got Unknown //IL_094a: Unknown result type (might be due to invalid IL or missing references) //IL_0954: Expected O, but got Unknown //IL_0984: Unknown result type (might be due to invalid IL or missing references) //IL_098e: Expected O, but got Unknown //IL_09c2: Unknown result type (might be due to invalid IL or missing references) //IL_09cc: Expected O, but got Unknown //IL_0a15: Unknown result type (might be due to invalid IL or missing references) //IL_0a1f: Expected O, but got Unknown //IL_0a53: Unknown result type (might be due to invalid IL or missing references) //IL_0a5d: Expected O, but got Unknown //IL_0a91: Unknown result type (might be due to invalid IL or missing references) //IL_0a9b: Expected O, but got Unknown //IL_0acf: Unknown result type (might be due to invalid IL or missing references) //IL_0ad9: Expected O, but got Unknown //IL_0b0d: Unknown result type (might be due to invalid IL or missing references) //IL_0b17: Expected O, but got Unknown //IL_0b4b: Unknown result type (might be due to invalid IL or missing references) //IL_0b55: Expected O, but got Unknown //IL_0b89: Unknown result type (might be due to invalid IL or missing references) //IL_0b93: Expected O, but got Unknown //IL_0bc8: Unknown result type (might be due to invalid IL or missing references) //IL_0bd2: Expected O, but got Unknown Bound.Clear(); TroubleshootingEnabled = Track<bool>(config.Bind<bool>("Troubleshooting", "Enabled", false, new ConfigDescription("Shows the diagnostic settings below and a Load switch for each feature in General. Load switches apply only while this is on. Requires a restart.", (AcceptableValueBase)null, new object[2] { SectionTag("Troubleshooting", 11), EntryTag("Troubleshooting Mode (needs restart)", 0) }))); bool value = TroubleshootingEnabled.Value; Enabled = Track<bool>(config.Bind<bool>("General", "Enabled", true, new ConfigDescription("Enables all mod features.", (AcceptableValueBase)null, new object[2] { SectionTag("General", 0), EntryTag("All features", 0) }))); LoadChainOverflow = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Chain Overflow", true, new ConfigDescription("Loads Chain Overflow when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Chain Overflow (needs restart)", 1, !value) }))), value); LoadGrabPriority = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Grab Priority", true, new ConfigDescription("Loads Grab Priority when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Grab Priority (needs restart)", 2, !value) }))), value); LoadCartReleaseFix = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Cart Release Fix", true, new ConfigDescription("Loads Cart Release Fix when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Cart Release Fix (needs restart)", 3, !value) }))), value); LoadCartCapacity = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Cart Capacity", true, new ConfigDescription("Loads Cart Capacity when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Cart Capacity (needs restart)", 4, !value) }))), value); LoadCartOverlays = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Cart Overlays", true, new ConfigDescription("Loads Cart Overlays when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Cart Overlays (needs restart)", 5, !value) }))), value); LoadConstructionSiteProtection = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Construction Site Protection", true, new ConfigDescription("Loads Construction Site Protection when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Construction Site Protection (needs restart)", 6, !value) }))), value); LoadCollectRange = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Collect Range", true, new ConfigDescription("Loads Collect Range when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Collect Range (needs restart)", 7, !value) }))), value); LoadDepositRange = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Deposit Range", true, new ConfigDescription("Loads Deposit Range when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Deposit Range (needs restart)", 8, !value) }))), value); LoadConnectRange = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Connect Range", true, new ConfigDescription("Loads Connect Range when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Connect Range (needs restart)", 9, !value) }))), value); LoadStockpileRange = new LoadSwitch(Track<bool>(config.Bind<bool>("General", "Load Stockpile Range", true, new ConfigDescription("Loads Stockpile Range when the game starts. When disabled, the feature is not loaded at all. Applies only while Troubleshooting Mode is on. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Load Stockpile Range (needs restart)", 10, !value) }))), value); ChainOverflowEnabled = Track<bool>(config.Bind<bool>("Chain Overflow", "Enabled", true, new ConfigDescription("Passes items picked up by a full Cart to the next Cart in the chain with a free slot.", (AcceptableValueBase)null, new object[2] { SectionTag("Chain Overflow", 1), EntryTag("Pass overflow along the chain", 0) }))); CollectRangeEnabled = Track<bool>(config.Bind<bool>("Collect Range", "Enabled", true, new ConfigDescription("Carts automatically pick up loose items within range.", (AcceptableValueBase)null, new object[2] { SectionTag("Collect Range", 7), EntryTag("Automatic pickup", 0) }))); CollectRange = Track<int>(config.Bind<int>("Collect Range", "Range", 2, new ConfigDescription("Pickup range in tiles per side. 0 = vanilla (touch only).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), new object[1] { EntryTag("Range", 1) }))); DepositRangeEnabled = Track<bool>(config.Bind<bool>("Deposit Range", "Enabled", true, new ConfigDescription("Carts deposit matching cargo into Material Storages within range.", (AcceptableValueBase)null, new object[2] { SectionTag("Deposit Range", 8), EntryTag("Automatic deposit", 0) }))); DepositRange = Track<int>(config.Bind<int>("Deposit Range", "Range", 2, new ConfigDescription("Deposit range in tiles per side. 0 = vanilla (park on the storage).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), new object[1] { EntryTag("Range", 1) }))); ConnectRangeEnabled = Track<bool>(config.Bind<bool>("Connect Range", "Enabled", true, new ConfigDescription("Pulls nearby free Carts toward the Cart you are pulling to connect them without touching.", (AcceptableValueBase)null, new object[2] { SectionTag("Connect Range", 9), EntryTag("Automatic connect", 0) }))); ConnectRange = Track<int>(config.Bind<int>("Connect Range", "Range", 2, new ConfigDescription("Connection range in tiles per side. 0 = vanilla (touch only).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), new object[1] { EntryTag("Range", 1) }))); GrabPriorityEnabled = Track<bool>(config.Bind<bool>("Grab Priority", "Enabled", true, new ConfigDescription("Prioritizes Massive Pots, then empty Buckets, when taking items from a Cart.", (AcceptableValueBase)null, new object[2] { SectionTag("Grab Priority", 2), EntryTag("Take Massive Pots and empty Buckets first", 0) }))); CartReleaseFixEnabled = Track<bool>(config.Bind<bool>("Cart Release Fix", "Enabled", true, new ConfigDescription("Prevents grabbing another Cart with the same interact press used to release a Cart.", (AcceptableValueBase)null, new object[2] { SectionTag("Cart Release Fix", 3), EntryTag("Release without re-grabbing", 0) }))); CartCapacityEnabled = Track<bool>(config.Bind<bool>("Cart Capacity", "Enabled", true, new ConfigDescription("Sets base capacity for vanilla Carts only. The Mercury blessing adds the configured bonus. High capacities may cause stutter and tall cargo stacks. Lowering capacity immediately blocks further pickup while full; excess cargo is ejected on world load if Eject Overflow is enabled. In multiplayer, host settings apply; all players need the mod to see cargo beyond the normal 4 items. Extra capacity affects saved Cart data. Before uninstalling, restore vanilla capacities and blessing bonus, keep Eject Overflow on, visit every Cart carrying extra cargo so it drops the excess, then save.", (AcceptableValueBase)null, new object[2] { SectionTag("Cart Capacity", 4), EntryTag("Set capacity per Cart type", 0) }))); CartCapacityEjectOverflow = Track<bool>(config.Bind<bool>("Cart Capacity", "Eject Overflow", true, new ConfigDescription("Drops a Cart's excess cargo beside it the first time the Cart is near a player after a world loads. If disabled, excess cargo remains until manually unloaded.", (AcceptableValueBase)null, new object[1] { EntryTag("Eject Overflow", 1, !CartCapacityEnabled.Value) }))); CartCapacityBlessingBonus = Track<int>(config.Bind<int>("Cart Capacity", "Blessing Bonus", 1, new ConfigDescription("Extra capacity granted by the Mercury blessing. The default is 1.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 64), new object[1] { EntryTag("Blessing Bonus", 2, !CartCapacityEnabled.Value) }))); CartCapacity.BindTypeEntries(config); CartOverlaysEnabled = Track<bool>(config.Bind<bool>("Cart Overlays", "Enabled", true, new ConfigDescription("Shows information above Carts. Visible only to players with the mod installed.", (AcceptableValueBase)null, new object[2] { SectionTag("Cart Overlays", 5), EntryTag("Show info above Carts", 0) }))); CartOverlayShowAboveVanilla = Track<bool>(config.Bind<bool>("Cart Overlays", "Show Above Vanilla Capacity", true, new ConfigDescription("Shows the cargo count on Carts carrying more than 5 items.", (AcceptableValueBase)null, new object[1] { EntryTag("Show above vanilla capacity", 1, !CartOverlaysEnabled.Value) }))); CartOverlayShowVanilla = Track<bool>(config.Bind<bool>("Cart Overlays", "Show For Vanilla Capacity", false, new ConfigDescription("Shows the cargo count on Carts carrying 1-5 items.", (AcceptableValueBase)null, new object[1] { EntryTag("Show for vanilla capacity", 2, !CartOverlaysEnabled.Value) }))); CartOverlayShowEmpty = Track<bool>(config.Bind<bool>("Cart Overlays", "Show For Empty Carts", false, new ConfigDescription("Shows the cargo count on empty Carts.", (AcceptableValueBase)null, new object[1] { EntryTag("Show for empty Carts", 3, !CartOverlaysEnabled.Value) }))); CartOverlayDisconnectMessage = Track<bool>(config.Bind<bool>("Cart Overlays", "Disconnect Message", true, new ConfigDescription("Notifies the pulling player when a Cart disconnects from the chain by itself.", (AcceptableValueBase)null, new object[1] { EntryTag("Show a message when a Cart disconnects", 4, !CartOverlaysEnabled.Value) }))); CartOverlayProtectionMessage = Track<bool>(config.Bind<bool>("Cart Overlays", "Protection Message", true, new ConfigDescription("Shows a message the first time a Cart you pull can't pick up an item because of Construction Site Protection. Appears once per player per world load.", (AcceptableValueBase)null, new object[1] { EntryTag("Show a message when Construction Site Protection keeps an item", 5, !CartOverlaysEnabled.Value) }))); ConstructionSiteProtectionEnabled = Track<bool>(config.Bind<bool>("Construction Site Protection", "Enabled", true, new ConfigDescription("Carts leave loose materials that a Construction Site still needs. Spare materials and items the site does not need can still be picked up.", (AcceptableValueBase)null, new object[2] { SectionTag("Construction Site Protection", 6), EntryTag("Leave needed materials on Construction Sites", 0) }))); StockpileRangeEnabled = Track<bool>(config.Bind<bool>("Stockpile Range", "Enabled", true, new ConfigDescription("Carts collect resources from building output stockpiles within range. Solid resources use free slots; bucket resources fill empty Buckets.", (AcceptableValueBase)null, new object[2] { SectionTag("Stockpile Range", 10), EntryTag("Take from stockpiles", 0) }))); StockpileRange = Track<int>(config.Bind<int>("Stockpile Range", "Range", 2, new ConfigDescription("Stockpile collection range in tiles per side. 0 = disabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), new object[1] { EntryTag("Range", 1) }))); StockpileWhilePulled = Track<bool>(config.Bind<bool>("Stockpile Range", "While Pulled", true, new ConfigDescription("Collects from stockpiles while a player pulls the Cart or its chain.", (AcceptableValueBase)null, new object[1] { EntryTag("While pulled", 2) }))); StockpileWhileParked = Track<bool>(config.Bind<bool>("Stockpile Range", "While Parked", false, new ConfigDescription("Collects from stockpiles while the Cart is not being pulled.", (AcceptableValueBase)null, new object[1] { EntryTag("While parked", 3) }))); DiagnosticLogs = Track<bool>(config.Bind<bool>("Troubleshooting", "Diagnostic Logs", true, new ConfigDescription("Enables diagnostic logging to BepInEx/LogOutput.log. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Write diagnostic logs (needs restart)", 1, !value) }))); DiagnosticsArmed = value && DiagnosticLogs.Value; DiagSave = Track<bool>(config.Bind<bool>("Troubleshooting", "Save Watch", true, new ConfigDescription("Logs save duration, changes during saving and save failures. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Watch saving (needs restart)", 2, !value) }))); DiagMemory = Track<bool>(config.Bind<bool>("Troubleshooting", "Memory Watch", false, new ConfigDescription("Logs memory usage during saves, game pauses and stalls using a background thread. Requires a restart.", (AcceptableValueBase)null, new object[1] { EntryTag("Watch memory and stalls (needs restart)", 3, !value) }))); DiagCensus = Track<bool>(config.Bind<bool>("Troubleshooting", "World Census", true, new ConfigDescription("Logs entity, Cart and extra cargo counts in each world every 30 seconds.", (AcceptableValueBase)null, new object[1] { EntryTag("Count the world periodically", 4, !value) }))); DiagCapacity = Track<bool>(config.Bind<bool>("Troubleshooting", "Cart Capacity", true, new ConfigDescription("Logs Cart adoption, extra cargo pinning and release, and updates sent to other players.", (AcceptableValueBase)null, new object[1] { EntryTag("Trace Cart Capacity", 5, !value) }))); DiagPickup = Track<bool>(config.Bind<bool>("Troubleshooting", "Cart Pickup", true, new ConfigDescription("Logs pickups and deposits through Collect Range, Deposit Range and Stockpile Range.", (AcceptableValueBase)null, new object[1] { EntryTag("Trace pickup and deposit", 6, !value) }))); DiagChain = Track<bool>(config.Bind<bool>("Troubleshooting", "Cart Chain", true, new ConfigDescription("Logs Cart connections, disconnections and overflow transfers along the chain.", (AcceptableValueBase)null, new object[1] { EntryTag("Trace the Cart chain", 7, !value) }))); DiagStateDump = Track<bool>(config.Bind<bool>("Troubleshooting", "Cart State Dump", false, new ConfigDescription("Logs every Cart's complete contents whenever they change. Can produce thousands of lines per minute; enable only for targeted troubleshooting.", (AcceptableValueBase)null, new object[1] { EntryTag("Dump full Cart state when it changes", 8, !value) }))); DiagLogFile = Track<bool>(config.Bind<bool>("Troubleshooting", "Log File", false, new ConfigDescription("Also writes diagnostics to BetterCarts.log next to the mod. Writes continuously to disk.", (AcceptableValueBase)null, new object[1] { EntryTag("Also write a BetterCarts.log file", 9, !value) }))); } private static ConfigEntry<T> Track<T>(ConfigEntry<T> entry) { Bound.Add((ConfigEntryBase)(object)entry); return entry; } private static object SectionTag(string section, int order) { return new { Section = section, DisplayName = section, Order = order }; } internal static object EntryTag(string displayName, int order) { return new { DisplayName = displayName, Order = order }; } internal static object EntryTag(string displayName, int order, bool hidden) { return new { DisplayName = displayName, Order = order, Hidden = hidden }; } } internal static class ModDiagnostics { private const long CensusIntervalMs = 30000L; private const long HeartbeatIntervalMs = 1000L; private const int MaxDistinctKeysPerSave = 32; private const int MaxSerializerErrors = 20; private static readonly string[] SlotKeys = new string[5] { "c1", "c2", "c3", "c4", "c5" }; private static bool _environmentLogged; private static long _nextCensusTick; private static long _nextHeartbeatTick; private static bool _serializerErrorsUnavailable; private static volatile bool _serializing; private static int _serializeThread; private static long _serializeStartTimestamp; private static int _writesDuringSave; private static int _insertsDuringSave; private static int _entitiesCreatedDuringSave; private static int _entitiesRemovedDuringSave; private static readonly HashSet<string> KeysSeenThisSave = new HashSet<string>(); internal static void Tick() { if (ModLog.Enabled) { if (!_environmentLogged) { _environmentLogged = true; LogEnvironment(); } long tickCount = Environment.TickCount64; if (_serializing && ModLog.SaveEnabled && tickCount >= _nextHeartbeatTick) { _nextHeartbeatTick = tickCount + 1000; ModLog.SaveWarn("SAVE alive ms=" + ElapsedMs() + " writes=" + Volatile.Read(in _writesDuringSave) + " inserts=" + Volatile.Read(in _insertsDuringSave) + " created=" + Volatile.Read(in _entitiesCreatedDuringSave) + " removed=" + Volatile.Read(in _entitiesRemovedDuringSave) + " serializerThread=" + _serializeThread); } if (ModLog.CensusEnabled && tickCount >= _nextCensusTick) { _nextCensusTick = tickCount + 30000; LogCensus(); } } } private static void LogEnvironment() { ModLog.Error("> BetterCarts Diagnostics are ON <"); ModLog.Error("=== ENVIRONMENT ==="); AssemblyName name = typeof(ModDiagnostics).Assembly.GetName(); ModLog.Error("mod=" + name.Name + " " + name.Version?.ToString() + " runtime=" + Environment.Version?.ToString() + " os=" + Environment.OSVersion.VersionString + " cores=" + Environment.ProcessorCount); LogNeighbourAssemblies(); ModLog.Error("--- settings ---"); foreach (ConfigEntryBase item in ModConfig.Bound) { if (item != null) { ModLog.Error(" [" + item.Definition.Section + "] " + item.Definition.Key + " = " + item.BoxedValue); } } ModLog.Error("=== END ENVIRONMENT ==="); } private static void LogNeighbourAssemblies() { StringBuilder stringBuilder = new StringBuilder(); int num = 0; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly == null || assembly.IsDynamic) { continue; } num++; string name = assembly.GetName().Name; if (name != null && (name.IndexOf("cart", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("ModSettings", StringComparison.OrdinalIgnoreCase) >= 0)) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(name).Append(' ').Append(assembly.GetName().Version); } } ModLog.Error("assemblies=" + num + " related=[" + ((stringBuilder.Length == 0) ? "none" : stringBuilder.ToString()) + "]"); } private static void LogCensus() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("CENSUS"); int num = 0; foreach (KeyValuePair<Guid, WorldEntitySystemPair> item in ServerEntitySystemManager.WorldIdToSystemMap) { EntitySystem val = ((item.Value == null) ? null : item.Value.System); if (val == null) { continue; } int num2 = 0; int num3 = 0; int num4 = 0; EntityEnumerator entityWrappers = val.GetEntityWrappers(); EntityEnumerator enumerator2 = ((EntityEnumerator)(ref entityWrappers)).GetEnumerator(); while (((EntityEnumerator)(ref enumerator2)).MoveNext()) { EntityWrapper current2 = ((EntityEnumerator)(ref enumerator2)).Current; if (current2 == null || current2.Removed) { continue; } AbstractController controller = current2.Controller; ServerCart2Controller val2 = (ServerCart2Controller)(object)((controller is ServerCart2Controller) ? controller : null); if (val2 == null) { continue; } num2++; Parameters parameters = ((AbstractController)val2).Parameters; if (parameters == null) { continue; } string text = parameters.GetString("bc_cargo", string.Empty); if (!string.IsNullOrEmpty(text)) { num3 += CountEntries(text); if (text.Length > num4) { num4 = text.Length; } } } if (num2 != 0) { num++; stringBuilder.Append(" | world entities=").Append(val.EntityIdMap.Count).Append('/') .Append(val.MaxEntities) .Append(" highWater=") .Append(val.HighestActive) .Append(" carts=") .Append(num2) .Append(" extras=") .Append(num3) .Append(" longestCargo=") .Append(num4); } } if (num != 0) { ModLog.Census(stringBuilder.ToString()); } } private static int CountEntries(string packed) { int num = 1; for (int i = 0; i < packed.Length; i++) { if (packed[i] == ',') { num++; } } return num; } private static long ElapsedMs() { long num = Interlocked.Read(in _serializeStartTimestamp); if (num == 0L) { return 0L; } return (Stopwatch.GetTimestamp() - num) * 1000 / Stopwatch.Frequency; } internal static void NoteSnapshot(bool offThread, bool starting) { ModLog.Save("SAVE snapshot " + (starting ? "start" : "end") + " offThread=" + offThread); } internal static void NoteSnapshotFailed(Exception ex) { ModLog.Error("SAVE snapshot FAILED - the exception below escaped a vanilla save path"); ModLog.Error(ex.ToString()); } internal static void NoteSerializeStart() { _serializeThread = Environment.CurrentManagedThreadId; Interlocked.Exchange(ref _serializeStartTimestamp, Stopwatch.GetTimestamp()); Interlocked.Exchange(ref _writesDuringSave, 0); Interlocked.Exchange(ref _insertsDuringSave, 0); Interlocked.Exchange(ref _entitiesCreatedDuringSave, 0); Interlocked.Exchange(ref _entitiesRemovedDuringSave, 0); lock (KeysSeenThisSave) { KeysSeenThisSave.Clear(); } _nextHeartbeatTick = Environment.TickCount64 + 1000; _serializing = true; ModWatchdog.NoteSaveStart(); ModLog.Save("SAVE serialize start"); } internal static void NoteSerializeEnd() { long num = ElapsedMs(); _serializing = false; ModLog.Save("SAVE serialize end ms=" + num + " paramWrites=" + Volatile.Read(in _writesDuringSave) + " ofWhichInserts=" + Volatile.Read(in _insertsDuringSave) + " entitiesCreated=" + Volatile.Read(in _entitiesCreatedDuringSave) + " entitiesRemoved=" + Volatile.Read(in _entitiesRemovedDuringSave)); ModWatchdog.NoteSaveEnd(); LogSerializerErrors(); } internal static void NoteSerializeFailed(Exception ex) { long num = ElapsedMs(); _serializing = false; ModWatchdog.NoteSaveEnd(); ModLog.Error("SAVE serialize FAILED after ms=" + num + " paramWrites=" + Volatile.Read(in _writesDuringSave) + " inserts=" + Volatile.Read(in _insertsDuringSave) + " created=" + Volatile.Read(in _entitiesCreatedDuringSave) + " removed=" + Volatile.Read(in _entitiesRemovedDuringSave) + " serializerThread=" + _serializeThread); ModLog.Error(ex.ToString()); for (Exception innerException = ex.InnerException; innerException != null; innerException = innerException.InnerException) { ModLog.Error("SAVE inner exception: " + innerException); } } private static void LogSerializerErrors() { if (_serializerErrorsUnavailable) { return; } try { object obj = ReadMember(typeof(GameSaveManager), null, "GameStateSaveSerializer"); if (obj == null) { _serializerErrorsUnavailable = true; ModLog.Save("SAVE serializer errors unavailable - GameStateSaveSerializer was not readable"); return; } object obj2 = ReadMember(obj.GetType(), obj, "Errored"); if (!(obj2 is bool)) { _serializerErrorsUnavailable = true; ModLog.Save("SAVE serializer errors unavailable - Errored was not readable"); } else { if (!(bool)obj2) { return; } ModLog.Error("SAVE serializer REPORTED ERRORS - the save did not throw, so this block is the only record"); if (!(ReadMember(obj.GetType(), obj, "Errors") is IEnumerable enumerable)) { ModLog.Error(" the Errors list was not readable"); return; } int num = 0; foreach (object item in enumerable) { num++; if (num > 20) { ModLog.Error(" further errors suppressed"); return; } ModLog.Error(" error " + num + ": " + item); } if (num == 0) { ModLog.Error(" Errored was true but the Errors list was empty"); } } } catch (Exception ex) { _serializerErrorsUnavailable = true; ModLog.Fault("ModDiagnostics.LogSerializerErrors", ex); } } private static object ReadMember(Type owner, object instance, string name) { BindingFlags bindingAttr = (BindingFlags)(0x30 | ((instance == null) ? 8 : 4)); PropertyInfo property = owner.GetProperty(name, bindingAttr); if (property != null && property.CanRead) { return property.GetValue(instance); } FieldInfo field = owner.GetField(name, bindingAttr); if (!(field == null)) { return field.GetValue(instance); } return null; } internal static void NoteEntityCreated() { if (_serializing) { Interlocked.Increment(ref _entitiesCreatedDuringSave); } } internal static void NoteEntityRemoved() { if (_serializing) { Interlocked.Increment(ref _entitiesRemovedDuringSave); } } internal static void NoteParameterWrite(EntityWrapper entity, string key) { if (key == null) { return; } bool flag = false; Parameters val = ((entity == null || entity.Controller == null) ? null : entity.Controller.Parameter