using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.NET.Common; using Candide.Entities.Controllers.Legacy; using Candide.Entities.Controllers.Other; using Candide.Entities.PlayerState; using Candide.GameModels; using Candide.World; 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.Models; using CandideServer.ServerManagers; using CandideServer.SimulationModels; using CandideServer.World; using HarmonyLib; using Microsoft.Xna.Framework; using Shared.Collision; using Shared.Entity; using Shared.Entity.Base; using Shared.Entity.Components; [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.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+2228eeed8e6ba663b19a895538c79697055c5fa1")] [assembly: AssemblyProduct("BetterCarts")] [assembly: AssemblyTitle("BetterCarts")] [assembly: TargetPlatform("Windows7.0")] [assembly: SupportedOSPlatform("Windows7.0")] [assembly: AssemblyVersion("1.1.0.0")] [module: RefSafetyRules(11)] namespace BetterCarts { internal static class CartAccess { internal static readonly Func PickupEntity = AccessTools.MethodDelegate>(AccessTools.Method(typeof(ServerCart2Controller), "PickupEntity", (Type[])null, (Type[])null), (object)null, true, (Type[])null); } internal static class ModConfig { internal static ConfigEntry Enabled; internal static ConfigEntry ChainOverflowEnabled; internal static ConfigEntry DepositRangeEnabled; internal static ConfigEntry DepositRange; internal static ConfigEntry CollectRangeEnabled; internal static ConfigEntry CollectRange; internal static ConfigEntry ConnectRangeEnabled; internal static ConfigEntry ConnectRange; internal static ConfigEntry BucketPriorityEnabled; internal static void Init(ConfigFile config) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Master on/off for the whole mod."); ChainOverflowEnabled = config.Bind("Chain Overflow", "Enabled", true, "When a full cart picks up an item, the item is passed to the next cart in the chain with a free slot."); CollectRangeEnabled = config.Bind("Collect Range", "Enabled", true, "Carts automatically pick up loose items within range."); CollectRange = config.Bind("Collect Range", "Range", 2, new ConfigDescription("Collect reach in tiles per side. 0 = vanilla (touch only).", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); DepositRangeEnabled = config.Bind("Deposit Range", "Enabled", true, "Carts deposit matching cargo into Material Storages within range."); DepositRange = config.Bind("Deposit Range", "Range", 2, new ConfigDescription("Deposit reach in tiles per side, 0 = vanilla (park on the storage).", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); ConnectRangeEnabled = config.Bind("Connect Range", "Enabled", true, "A free cart is pulled toward a cart the player is pulling once it is within range, so they connect without touching."); ConnectRange = config.Bind("Connect Range", "Range", 2, new ConfigDescription("Connect reach in tiles per side. 0 = vanilla (touch only).", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); BucketPriorityEnabled = config.Bind("Bucket Priority", "Enabled", true, "When taking an item off a cart, prefer grabbing an empty bucket over other cargo."); } } [BepInPlugin("com.beesq.romestead.bettercarts", "Better Carts", "1.1.0")] public class BetterCartsPlugin : BasePlugin { public const string PluginGuid = "com.beesq.romestead.bettercarts"; public const string PluginName = "Better Carts"; public const string PluginVersion = "1.1.0"; public override void Load() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) ModConfig.Init(((BasePlugin)this).Config); new Harmony("com.beesq.romestead.bettercarts").PatchAll(); ((BasePlugin)this).Log.LogInfo((object)"Better Carts 1.1.0 loaded."); } } internal static class WorldInfo { private static readonly FieldInfo WorldField = AccessTools.Field(typeof(ExteriorWorldHandler), "World"); private static readonly PropertyInfo WorldProperty = AccessTools.Property(typeof(ExteriorWorldHandler), "World"); internal static float TileSize { get { object? obj = ((WorldField != null) ? WorldField.GetValue(null) : WorldProperty?.GetValue(null)); ChunkedWorld val = (ChunkedWorld)((obj is ChunkedWorld) ? obj : null); if (val != null && val.TileSize.X > 0) { return val.TileSize.X; } return ChunkedWorld.DefaultWorld.TileSize.X; } } } } namespace BetterCarts.Patches { [HarmonyPatch(typeof(GrabActionHelper), "TryPlayerGrabActionProximity")] internal static class BucketPriorityPatch { private const float HeightTolerance = 16f; private static bool Prefix(EntityWrapper grabbingEntity, float radius, ref EntityWrapper __result) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.Enabled.Value || !ModConfig.BucketPriorityEnabled.Value) { return true; } if (grabbingEntity == null || grabbingEntity.Removed) { return true; } float z = grabbingEntity.Position.Z; float num = grabbingEntity.Position.Z + 16f; EntityWrapper val = null; float num2 = float.MaxValue; EntityWrapper val2 = null; float num3 = float.MaxValue; foreach (EntityWrapper item in grabbingEntity.System.GetEntitiesTouchingCircleArea(grabbingEntity.Position2, radius, z, num)) { if (item != null && !((object)grabbingEntity).Equals((object?)item) && GameState.Entities.ContainsKey(item.Id) && ((Enum)item.MaskRef).HasFlag((Enum)(object)(Component)1024) && item.Carriable && IntervalHelper.IntervalOverlap(z, num, item.PositionZ, item.PositionZ + item.Shape.Height3D - 0.5f)) { float num4 = Vector2.Distance(item.Position2, grabbingEntity.Position2); if (num4 < num2) { num2 = num4; val = item; } if (num4 < num3 && IsEmptyBucketOnCart(item) && grabbingEntity.CanAttach(item, false, true)) { num3 = num4; val2 = item; } } } if (val == null) { return true; } if (!grabbingEntity.CanAttach(val, false, true)) { return true; } if (!IsCarriedByCart(val)) { return true; } if (val2 == null) { return true; } __result = val2; return false; } private static bool IsCarriedByCart(EntityWrapper item) { EntityWrapper carryingEntity = item.CarryingEntity; if (carryingEntity != null) { return carryingEntity.Controller is Cart2Controller; } return false; } private static bool IsEmptyBucketOnCart(EntityWrapper item) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) AbstractController controller = item.Controller; BucketController val = (BucketController)(object)((controller is BucketController) ? controller : null); if (val == null) { return false; } if ((int)val.Content != 0) { return false; } return IsCarriedByCart(item); } } [HarmonyPatch(typeof(ServerCart2Controller), "PickupEntity")] internal static class ChainOverflowPatch { private const int MaxChainWalkFallback = 256; [ThreadStatic] private static bool _walkingChain; private static void Postfix(ServerCart2Controller __instance, EntityWrapper entity, ref bool __result) { if (__result || _walkingChain || !ModConfig.Enabled.Value || !ModConfig.ChainOverflowEnabled.Value) { return; } _walkingChain = true; try { __result = TryPickupIntoChain(__instance, entity); } finally { _walkingChain = false; } } private static bool TryPickupIntoChain(ServerCart2Controller source, EntityWrapper entity) { HashSet visited = new HashSet { ((AbstractController)source).Entity.Id }; if (WalkChain(source, entity, visited, followers: true)) { return true; } return WalkChain(source, entity, visited, followers: false); } private static bool WalkChain(ServerCart2Controller source, EntityWrapper entity, HashSet visited, bool followers) { ServerCart2Controller val = source; for (int i = 0; i < 256; i++) { Guid? guid = (followers ? val.FollowerCartId : val.FollowingId); if (!guid.HasValue || !visited.Add(guid.Value)) { return false; } EntityWrapper entityById = ((AbstractController)source).Entity.System.GetEntityById(guid); if (entityById == null || entityById.Removed) { return false; } AbstractController controller = entityById.Controller; ServerCart2Controller val2 = (ServerCart2Controller)(object)((controller is ServerCart2Controller) ? controller : null); if (val2 == null) { return false; } if (CartAccess.PickupEntity(val2, entity)) { return true; } val = val2; } return false; } } [HarmonyPatch(typeof(ServerCart2Controller), "Update", new Type[] { typeof(GameTime) })] internal static class CollectRangePatch { private static void Postfix(ServerCart2Controller __instance) { //IL_004c: 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) if (!ModConfig.Enabled.Value || !ModConfig.CollectRangeEnabled.Value) { return; } int value = ModConfig.CollectRange.Value; if (value <= 0) { return; } EntityWrapper entity = ((AbstractController)__instance).Entity; if (entity == null || entity.Removed) { return; } float num = (float)value * WorldInfo.TileSize; foreach (EntityWrapper item in entity.System.GetEntitiesTouchingCircleArea(entity.Position2, num, entity.Position.Z, entity.Position.Z + 8f)) { if (ServerCart2Controller.CanBeAutoPicked(item) && !CartAccess.PickupEntity(__instance, item)) { break; } } } } [HarmonyPatch(typeof(ServerCart2Controller), "Update", new Type[] { typeof(GameTime) })] internal static class ConnectRangePatch { private sealed class SpeedHolder { public float MoveSpeed; public float TurnSpeed; } private const int MaxChainWalk = 256; private const float ConnectAccel = 260f; private const float MaxConnectSpeed = 130f; private const float TurnAccel = 2600f; private const float MaxTurnSpeed = 1300f; private static readonly float TurnAccelRad = MathHelper.ToRadians(2600f); private static readonly float MaxTurnSpeedRad = MathHelper.ToRadians(1300f); private static readonly ConditionalWeakTable Speeds = new ConditionalWeakTable(); private static void Postfix(ServerCart2Controller __instance) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_017f: 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_0198: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if (__instance == null) { return; } SpeedHolder orCreateValue = Speeds.GetOrCreateValue(__instance); EntityWrapper entity = ((AbstractController)__instance).Entity; if (!ModConfig.Enabled.Value || !ModConfig.ConnectRangeEnabled.Value || ModConfig.ConnectRange.Value <= 0 || entity == null || entity.Removed || __instance.FollowingId.HasValue) { if (orCreateValue.MoveSpeed > 0f || orCreateValue.TurnSpeed > 0f) { orCreateValue.MoveSpeed = 0f; orCreateValue.TurnSpeed = 0f; if (entity != null && !entity.Removed && !__instance.FollowingId.HasValue) { entity.Velocity = new Vector3(0f, 0f, entity.Velocity.Z); } } return; } EntityWrapper val = FindTarget(entity); if (val == null) { if (orCreateValue.MoveSpeed > 0f || orCreateValue.TurnSpeed > 0f) { orCreateValue.MoveSpeed = 0f; orCreateValue.TurnSpeed = 0f; entity.Velocity = new Vector3(0f, 0f, entity.Velocity.Z); } return; } Vector2 val2 = val.Position2 - entity.Position2; float num = ((Vector2)(ref val2)).Length(); if (!(num <= 0.01f)) { Vector2 val3 = val2 / num; orCreateValue.MoveSpeed = Math.Min(orCreateValue.MoveSpeed + 260f * entity.Fdt, 130f); entity.Velocity = new Vector3(val3.X * orCreateValue.MoveSpeed, val3.Y * orCreateValue.MoveSpeed, entity.Velocity.Z); float target = VectorExtension.ToFloatDirection(val3); orCreateValue.TurnSpeed = Math.Min(orCreateValue.TurnSpeed + TurnAccelRad * entity.Fdt, MaxTurnSpeedRad); entity.Direction = RotateTowards(entity.Direction, target, orCreateValue.TurnSpeed * entity.Fdt, out var reached); if (reached) { orCreateValue.TurnSpeed = 0f; } } } private static float RotateTowards(float current, float target, float maxDelta, out bool reached) { if (maxDelta <= 0f) { reached = false; return current; } float value = MathHelper.WrapAngle(target - current); if (Math.Abs(value) <= maxDelta) { reached = true; return MathHelper.WrapAngle(target); } reached = false; return MathHelper.WrapAngle(current + (float)Math.Sign(value) * maxDelta); } private static EntityWrapper FindTarget(EntityWrapper cart) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) int value = ModConfig.ConnectRange.Value; float num = (float)value * WorldInfo.TileSize; EntityWrapper result = null; float num2 = float.MaxValue; foreach (EntityWrapper item in cart.System.GetEntitiesTouchingCircleArea(cart.Position2, num, cart.Position.Z, cart.Position.Z + 8f)) { if (item == null || item.Removed || item.Id == cart.Id) { continue; } AbstractController controller = item.Controller; ServerCart2Controller val = (ServerCart2Controller)(object)((controller is ServerCart2Controller) ? controller : null); if (val == null || val.FollowerCartId.HasValue) { continue; } int pulledCartChainLength = GetPulledCartChainLength(val, cart.System); int effectiveConnectRange = GetEffectiveConnectRange(value, pulledCartChainLength); if (effectiveConnectRange > 0) { float num3 = (float)effectiveConnectRange * WorldInfo.TileSize; float num4 = num3 * num3; float num5 = Vector2.DistanceSquared(item.Position2, cart.Position2); if (!(num5 > num4) && num5 < num2) { num2 = num5; result = item; } } } return result; } private static int GetEffectiveConnectRange(int maxRangeTiles, int chainLength) { if (maxRangeTiles <= 0 || chainLength <= 0) { return 0; } return Math.Min(maxRangeTiles, chainLength); } private static int GetPulledCartChainLength(ServerCart2Controller cart, EntitySystem system) { HashSet hashSet = new HashSet(); ServerCart2Controller val = cart; int num = 0; for (int i = 0; i < 256; i++) { num++; Guid? followingId = val.FollowingId; if (!followingId.HasValue) { return 0; } if (!hashSet.Add(followingId.Value)) { return 0; } EntityWrapper entityById = system.GetEntityById(followingId); if (entityById == null || entityById.Removed) { return 0; } AbstractController controller = entityById.Controller; ServerCart2Controller val2 = (ServerCart2Controller)(object)((controller is ServerCart2Controller) ? controller : null); if (val2 == null) { return num; } val = val2; } return 0; } } [HarmonyPatch(typeof(ServerMaterialStoragePitController), "Update", new Type[] { typeof(GameTime) })] internal static class DepositRangePatch { private sealed class TimerHolder { public float Value; } private const float CheckTime = 0.1f; private const float MaxDepositZ = 24f; private static readonly ConditionalWeakTable Timers = new ConditionalWeakTable(); private static readonly List ReuseList = new List(); private static readonly FieldRef WorldBoundsRef = AccessTools.FieldRefAccess("_worldBounds"); private static readonly FieldRef StorageTypeRef = AccessTools.FieldRefAccess("_storageType"); private static void Postfix(ServerMaterialStoragePitController __instance) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0121: 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_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.Enabled.Value || !ModConfig.DepositRangeEnabled.Value) { return; } int value = ModConfig.DepositRange.Value; if (value <= 0 || StorageTypeRef.Invoke(__instance)) { return; } BuildingSimulationModel currentlyUpdatingBuilding = ServerTempState.CurrentlyUpdatingBuilding; if (currentlyUpdatingBuilding == null || !currentlyUpdatingBuilding.InstanceModel.ResourceStorageId.HasValue) { return; } EntityWrapper entity = ((AbstractController)__instance).Entity; if (entity == null || entity.Removed) { return; } TimerHolder orCreateValue = Timers.GetOrCreateValue(__instance); orCreateValue.Value -= entity.Fdt; if (orCreateValue.Value > 0f) { return; } orCreateValue.Value += 0.1f; Guid? parentWorldId = ServerTempState.CurrentlyUpdatingWorldModel.ParentWorldId; if (!parentWorldId.HasValue) { return; } CollisionSpecialGroup entityCollisionsOrNull = ServerWorldHandler.GetEntityCollisionsOrNull(parentWorldId.Value); InternalResourceStorageModel val = default(InternalResourceStorageModel); if (entityCollisionsOrNull == null || !ServerGameState.TryGetResourceStorage(currentlyUpdatingBuilding.InstanceModel.ResourceStorageId.Value, ref val)) { return; } int num = (int)((float)value * WorldInfo.TileSize); Rectangle val2 = WorldBoundsRef.Invoke(__instance); Rectangle val3 = val2; ((Rectangle)(ref val3)).Inflate(num, num); ReuseList.Clear(); entityCollisionsOrNull.GetEntitiesInRectangleArea(RectangleF.op_Implicit(val3), ReuseList); foreach (EntityWrapper reuse in ReuseList) { if (reuse.PositionZ > 24f || reuse.Id == entity.Id || !((Enum)reuse.MaskRef).HasFlag((Enum)(object)(Component)1024) || !reuse.Carriable || ((Rectangle)(ref val2)).Contains((int)reuse.Position2.X, (int)reuse.Position2.Y)) { continue; } EntityWrapper carrierEntity = reuse.CarrierEntity; if (carrierEntity != null && carrierEntity.Controller is ServerCart2Controller && reuse.ConstructionMaterials != null && !reuse.ConstructionMaterials.IsEmpty && val.CanAdjustByConstructionMaterialsAggregate(reuse.ConstructionMaterials)) { ServerEntityModel serverModelFromWrapperOrNull = ServerEntityHelper.GetServerModelFromWrapperOrNull(reuse); if (serverModelFromWrapperOrNull != null && BuildingsServerManager.TryDepositResourceEntityIntoStorage(serverModelFromWrapperOrNull, currentlyUpdatingBuilding.InstanceModel.Id)) { ServerSendMessageHelper.PlaySoundOnPosition(serverModelFromWrapperOrNull.Position, serverModelFromWrapperOrNull.WorldId, "event:/hits/impact/impact_storage", 1f, 1f); break; } } } } } }