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<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 static class ModConfig
{
internal static ConfigEntry<bool> Enabled;
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> 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<bool>("General", "Enabled", true, "Master on/off for the whole mod.");
ChainOverflowEnabled = config.Bind<bool>("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<bool>("Collect Range", "Enabled", true, "Carts automatically pick up loose items within range.");
CollectRange = config.Bind<int>("Collect Range", "Range", 2, new ConfigDescription("Collect reach in tiles per side. 0 = vanilla (touch only).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>()));
DepositRangeEnabled = config.Bind<bool>("Deposit Range", "Enabled", true, "Carts deposit matching cargo into Material Storages within range.");
DepositRange = config.Bind<int>("Deposit Range", "Range", 2, new ConfigDescription("Deposit reach in tiles per side, 0 = vanilla (park on the storage).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>()));
ConnectRangeEnabled = config.Bind<bool>("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<int>("Connect Range", "Range", 2, new ConfigDescription("Connect reach in tiles per side. 0 = vanilla (touch only).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>()));
BucketPriorityEnabled = config.Bind<bool>("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<Guid> visited = new HashSet<Guid> { ((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<Guid> 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<ServerCart2Controller, SpeedHolder> Speeds = new ConditionalWeakTable<ServerCart2Controller, SpeedHolder>();
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<Guid> hashSet = new HashSet<Guid>();
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<ServerMaterialStoragePitController, TimerHolder> Timers = new ConditionalWeakTable<ServerMaterialStoragePitController, TimerHolder>();
private static readonly List<EntityWrapper> ReuseList = new List<EntityWrapper>();
private static readonly FieldRef<ServerMaterialStoragePitController, Rectangle> WorldBoundsRef = AccessTools.FieldRefAccess<ServerMaterialStoragePitController, Rectangle>("_worldBounds");
private static readonly FieldRef<ServerMaterialStoragePitController, IOStorageType> StorageTypeRef = AccessTools.FieldRefAccess<ServerMaterialStoragePitController, IOStorageType>("_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;
}
}
}
}
}
}