Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DvergrWorkers v0.5.0
DvergrWorkers.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("DvergrWorkers")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Dvergr workers help around your base and defend it when it is under attack.")] [assembly: AssemblyFileVersion("0.5.0.0")] [assembly: AssemblyInformationalVersion("0.5.0+4234aec8c31961fa23bb5cb260d90d719f41e04b")] [assembly: AssemblyProduct("DvergrWorkers")] [assembly: AssemblyTitle("DvergrWorkers")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.5.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DvergrWorkers { public class FarmerWorker : WorkerBrain { private enum Phase { Plan, Fetch, Walk, Plant, Harvest, Collect, Deposit } private const float PlantTime = 0.6f; private const float HarvestWait = 0.9f; private const float CollectInterval = 0.3f; private const int MaxCollectTries = 12; private const float DropRadius = 3f; private const int FullSlots = 2; private const float FineStep = 0.5f; private const float CoarseStep = 1f; private Phase _phase; private PlantType _batchType; private readonly List<Vector3> _batchSpots = new List<Vector3>(); private Pickable _harvest; private Vector3 _collectPos; private float _timer; private int _collectTries; private int _planted; private int _harvested; private int _handledLastRound; private bool _noSpots; private bool _noSupply; private readonly HashSet<PlantType> _loggedBiome = new HashSet<PlantType>(); private readonly HashSet<PlantType> _failedTypes = new HashSet<PlantType>(); private readonly HashSet<Pickable> _failedPickables = new HashSet<Pickable>(); private readonly HashSet<string> _ours = new HashSet<string>(); private readonly Dictionary<string, int> _plantedByFamily = new Dictionary<string, int>(); private readonly List<Occupied> _plantedSpots = new List<Occupied>(); private Phase _nextAfterWalk = Phase.Plant; internal override WorkerRole Role => WorkerRoles.Farmer; protected override string IdleStatus() { if (_handledLastRound <= 0) { return "Idle"; } return "Idle (planted/harvested " + _handledLastRound + " last round)"; } protected override void OnRoundEnded(bool finished) { _harvest = null; _batchSpots.Clear(); _handledLastRound = _planted + _harvested; } internal override bool WantsItem(string sharedName) { return PlantCatalogue.Crops.Any((PlantType t) => t.RequiredShared == sharedName); } private int Supply(string sharedName) { return base.Logistics.Carried.CountItems(sharedName, -1, true) + base.Logistics.AvailableTo(sharedName); } private bool IsOurs(ItemData item) { if (item != null) { return _ours.Contains(item.m_shared.m_name); } return false; } private bool CarryingSeeds() { foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (IsOurs(allItem) && PlantCatalogue.IsSeedName(allItem.m_shared.m_name)) { return true; } } return false; } private bool CarryingOther() { foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (IsOurs(allItem) && !PlantCatalogue.IsSeedName(allItem.m_shared.m_name)) { return true; } } return false; } private static bool IsCultivatedSpot(Vector3 p, out Heightmap heightmap) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) heightmap = Heightmap.FindHeightmap(p); if ((Object)(object)heightmap != (Object)null) { return heightmap.IsCultivated(p); } return false; } private List<Pickable> RipeCrops() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) List<Pickable> list = new List<Pickable>(); Pickable[] array = Object.FindObjectsByType<Pickable>((FindObjectsSortMode)0); foreach (Pickable val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.m_nview == (Object)null) && val.m_nview.IsValid() && !val.m_picked && !_failedPickables.Contains(val) && InJobArea(((Component)val).transform.position) && IsCultivatedSpot(((Component)val).transform.position, out var _)) { list.Add(val); } } return list; } private Dictionary<string, int> CountFamilies() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (PlantType crop in PlantCatalogue.Crops) { dictionary[crop.Family] = 0; } Plant[] array = Object.FindObjectsByType<Plant>((FindObjectsSortMode)0); foreach (Plant val in array) { if ((Object)(object)val != (Object)null && InJobArea(((Component)val).transform.position)) { PlantType plantType = PlantCatalogue.Of(((Component)val).gameObject); if (plantType != null && plantType.IsTree) { plantType = null; } if (plantType != null) { dictionary[plantType.Family]++; } } } Pickable[] array2 = Object.FindObjectsByType<Pickable>((FindObjectsSortMode)0); foreach (Pickable val2 in array2) { if ((Object)(object)val2 == (Object)null || (Object)(object)val2.m_itemPrefab == (Object)null || !InJobArea(((Component)val2).transform.position) || !IsCultivatedSpot(((Component)val2).transform.position, out var _)) { continue; } string name = val2.m_itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name; foreach (PlantType crop2 in PlantCatalogue.Crops) { if (crop2.YieldShared == name) { dictionary[crop2.Family]++; break; } } } foreach (KeyValuePair<string, int> item in _plantedByFamily) { if (dictionary.ContainsKey(item.Key)) { dictionary[item.Key] += item.Value; } } return dictionary; } private List<Vector3> FindSpots(PlantType type, int max) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0126: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_040e: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022f: 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_0245: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) //IL_04cf: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_04ea: Unknown result type (might be due to invalid IL or missing references) //IL_04ec: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_0528: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) int mask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid" }); int mask2 = LayerMask.GetMask(new string[3] { "Default", "static_solid", "piece" }); float value = WorkerConfig.JobRadius.Value; List<Vector3> list = new List<Vector3>(); int num = 0; List<Occupied> list2 = new List<Occupied>(_plantedSpots); Plant[] array = Object.FindObjectsByType<Plant>((FindObjectsSortMode)0); foreach (Plant val in array) { if ((Object)(object)val != (Object)null && Utils.DistanceXZ(((Component)val).transform.position, base.Home) <= value + 5f) { list2.Add(new Occupied { Position = ((Component)val).transform.position, Radius = val.m_growRadius }); } } Pickable[] array2 = Object.FindObjectsByType<Pickable>((FindObjectsSortMode)0); Heightmap heightmap; foreach (Pickable val2 in array2) { if ((Object)(object)val2 != (Object)null && Utils.DistanceXZ(((Component)val2).transform.position, base.Home) <= value + 5f && IsCultivatedSpot(((Component)val2).transform.position, out heightmap)) { list2.Add(new Occupied { Position = ((Component)val2).transform.position, Radius = type.Plant.m_growRadius }); } } Vector3 val3 = default(Vector3); Vector3 val5 = default(Vector3); for (float num2 = 0f - value; num2 <= value; num2 += 1f) { for (float num3 = 0f - value; num3 <= value; num3 += 1f) { ((Vector3)(ref val3))..ctor(base.Home.x + num2, base.Home.y, base.Home.z + num3); if (!InJobArea(val3) || !IsCultivatedSpot(val3, out heightmap)) { continue; } Vector2[] array3 = (Vector2[])(object)new Vector2[4] { Vector2.zero, new Vector2(0.5f, 0f), new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f) }; foreach (Vector2 val4 in array3) { ((Vector3)(ref val5))..ctor(val3.x + val4.x, 0f, val3.z + val4.y); val5.y = ZoneSystem.instance.GetGroundHeight(val5); if (!IsCultivatedSpot(val5, out heightmap)) { continue; } if (!PlantCatalogue.BiomeAllows(type, val5)) { num++; } else { if (Physics.CheckSphere(val5 + Vector3.up * 0.15f, type.Plant.m_growRadius + 0.2f, mask) || Physics.Raycast(val5 + Vector3.up * 0.2f, Vector3.up, 100f, mask2)) { continue; } bool flag = false; foreach (Occupied item in list2) { flag |= Utils.DistanceXZ(item.Position, val5) < Mathf.Max(type.Plant.m_growRadius, item.Radius) + 0.4f; } if (!flag) { list.Add(val5); } } } } } if (list.Count == 0 && num > 0 && _loggedBiome.Add(type)) { DvergrWorkersPlugin.Log.LogInfo((object)("Farmer: " + ((Object)type.Prefab).name + " cannot be planted here, wrong biome (" + ((object)Heightmap.FindBiome(base.Home)/*cast due to .constrained prefix*/).ToString() + " is not in plant biomes " + ((object)Unsafe.As<Biome, Biome>(ref type.Plant.m_biome)/*cast due to .constrained prefix*/).ToString() + (((int)type.Piece.m_onlyInBiome != 0) ? (", piece biomes " + ((object)Unsafe.As<Biome, Biome>(ref type.Piece.m_onlyInBiome)/*cast due to .constrained prefix*/).ToString()) : "") + ")")); } Vector3 here = ((Component)this).transform.position; list.Sort((Vector3 a, Vector3 b) => Vector3.Distance(a, here).CompareTo(Vector3.Distance(b, here))); List<Vector3> list3 = new List<Vector3>(); foreach (Vector3 item2 in list) { bool flag2 = false; foreach (Vector3 item3 in list3) { flag2 |= Utils.DistanceXZ(item3, item2) < type.Plant.m_growRadius + 0.4f; } if (!flag2) { list3.Add(item2); if (list3.Count >= max) { break; } } } return list3; } private PlantType ChooseType(string family) { PlantType plantType = null; PlantType plantType2 = null; foreach (PlantType crop in PlantCatalogue.Crops) { if (!(crop.Family != family) && !_failedTypes.Contains(crop)) { if (crop.Raw) { plantType2 = crop; } else { plantType = crop; } } } int num = ((plantType != null) ? Supply(plantType.RequiredShared) : 0); int num2 = ((plantType2 != null) ? Supply(plantType2.RequiredShared) : 0); if (plantType != null && num > WorkerConfig.SeedReserve.Value) { return plantType; } if (plantType2 != null && num2 >= plantType2.Amount) { return plantType2; } if (plantType != null && num >= plantType.Amount) { return plantType; } return null; } protected override bool BeginRound() { _planted = 0; _harvested = 0; _noSpots = false; _noSupply = false; _failedTypes.Clear(); _loggedBiome.Clear(); _failedPickables.Clear(); _plantedByFamily.Clear(); _plantedSpots.Clear(); _batchSpots.Clear(); _batchType = null; _harvest = null; if (!PlantCatalogue.Crops.Any()) { base.IdleNote = "No crops found in the cultivator"; return false; } bool flag = false; bool flag2 = RipeCrops().Count > 0 || CarryingOther() || CarryingSeeds(); if (!flag2) { foreach (string item in OrderedFamilies(CountFamilies())) { PlantType plantType = ChooseType(item); if (plantType == null) { _noSupply = true; continue; } List<Vector3> list = FindSpots(plantType, 1); flag |= list.Count > 0; if (list.Count > 0) { flag2 = true; break; } _noSpots = true; } } if (!flag2) { _handledLastRound = 0; base.IdleNote = NothingToDoNote(); return false; } base.Equipment.EquipTool(); _phase = Phase.Plan; DvergrWorkersPlugin.Log.LogInfo((object)"Farmer round started"); return true; } private string NothingToDoNote() { if (_noSupply && !_noSpots) { return "Waiting for seeds in a chest"; } if (_noSpots && !_noSupply) { return "No free tilled soil to plant"; } return null; } private static List<string> OrderedFamilies(Dictionary<string, int> counts) { List<string> list = new List<string>(counts.Keys); list.Sort((string a, string b) => counts[a].CompareTo(counts[b])); return list; } protected override bool RunTask(float dt) { return _phase switch { Phase.Plan => Plan(), Phase.Fetch => Fetch(dt), Phase.Walk => Walk(dt), Phase.Plant => PlantNow(dt), Phase.Harvest => HarvestNow(dt), Phase.Collect => Collect(dt), Phase.Deposit => Deposit(dt), _ => false, }; } private bool Abort(string reason) { DvergrWorkersPlugin.Log.LogInfo((object)("Farmer stopping: " + reason)); base.IdleNote = reason; EndRound(finished: false); return false; } private bool Plan() { //IL_0072: Unknown result type (might be due to invalid IL or missing references) if ((CarryingOther() || CarryingSeeds()) && base.Logistics.Carried.GetEmptySlots() <= 2) { return StartDeposit(); } if (_batchSpots.Count > 0 && _batchType != null && base.Logistics.Carried.CountItems(_batchType.RequiredShared, -1, true) >= _batchType.Amount) { return BeginWalk(_batchSpots[0], "Walking to plant " + ((Object)_batchType.Prefab).name.Replace("sapling_", ""), Phase.Plant); } _batchSpots.Clear(); Pickable val = NearestRipe(); if ((Object)(object)val != (Object)null) { _harvest = val; Pickable target = val; if (base.Navigator.Begin(((Object)val).name, (Vector3 from) => WorkerNavigator.ClosestPointOnColliders(((Component)target).gameObject, from))) { _phase = Phase.Walk; _nextAfterWalk = Phase.Harvest; SetStatus("Walking to ripe " + ((Object)val).name.Replace("Pickable_", "").Replace("(Clone)", "")); } else { _failedPickables.Add(val); _harvest = null; } return true; } if (StartBatch()) { return true; } if (CarryingOther() || CarryingSeeds()) { return StartDeposit(); } if (_planted + _harvested == 0) { base.IdleNote = NothingToDoNote(); } EndRound(finished: true); return false; } private Pickable NearestRipe() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Pickable result = null; float num = float.MaxValue; foreach (Pickable item in RipeCrops()) { float num2 = Vector3.Distance(((Component)item).transform.position, ((Component)this).transform.position); if (num2 < num) { num = num2; result = item; } } return result; } private bool StartBatch() { foreach (string item in OrderedFamilies(CountFamilies())) { PlantType plantType = ChooseType(item); if (plantType == null) { _noSupply = true; continue; } List<Vector3> list = FindSpots(plantType, WorkerConfig.PlantsPerTrip.Value); if (list.Count == 0) { _noSpots = true; continue; } int num = Supply(plantType.RequiredShared); int num2 = Math.Min(list.Count, num / plantType.Amount); if (num2 > 0) { _batchType = plantType; _batchSpots.Clear(); _batchSpots.AddRange(list.GetRange(0, num2)); _ours.Add(plantType.RequiredShared); int num3 = num2 * plantType.Amount - base.Logistics.Carried.CountItems(plantType.RequiredShared, -1, true); if (num3 <= 0) { _phase = Phase.Plan; return true; } string shared = plantType.RequiredShared; if (base.Logistics.BeginFetchAny((ItemData i) => i.m_shared.m_name == shared, num3)) { _phase = Phase.Fetch; SetStatus("Fetching " + shared.TrimStart(new char[1] { '$' }).Replace("item_", "") + " to plant"); return true; } DvergrWorkersPlugin.Log.LogInfo((object)("Farmer fetch failed: " + base.Logistics.Reason)); _failedTypes.Add(plantType); _batchSpots.Clear(); } } return false; } private bool Fetch(float dt) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: DvergrWorkersPlugin.Log.LogInfo((object)("Farmer fetch failed: " + base.Logistics.Reason)); if (_batchType != null) { _failedTypes.Add(_batchType); } break; } _phase = Phase.Plan; return true; } private bool BeginWalk(Vector3 spot, string status, Phase next) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (!base.Navigator.Begin("spot", (Vector3 from) => spot)) { DvergrWorkersPlugin.Log.LogInfo((object)"No reachable spot next to a planting spot, skipping"); if (_batchSpots.Count > 0) { _batchSpots.RemoveAt(0); } _phase = Phase.Plan; return true; } _nextAfterWalk = next; _phase = Phase.Walk; SetStatus(status); return true; } private bool Walk(float dt) { if (_nextAfterWalk == Phase.Harvest && ((Object)(object)_harvest == (Object)null || _harvest.m_picked)) { _harvest = null; _phase = Phase.Plan; return true; } switch (base.Navigator.Step(dt)) { case NavResult.InReach: case NavResult.CloseEnough: _timer = ((_nextAfterWalk == Phase.Harvest) ? 0.2f : 0.6f); _phase = _nextAfterWalk; break; case NavResult.Failed: if (_nextAfterWalk == Phase.Harvest && (Object)(object)_harvest != (Object)null) { _failedPickables.Add(_harvest); _harvest = null; } else if (_batchSpots.Count > 0) { _batchSpots.RemoveAt(0); } _phase = Phase.Plan; break; } return true; } private bool PlantNow(float dt) { //IL_004d: 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_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0118: 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_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0204: 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_0287: Unknown result type (might be due to invalid IL or missing references) ((BaseAI)base.Ai).StopMoving(); _timer -= dt; if (_timer > 0f) { return true; } if (_batchSpots.Count == 0 || _batchType == null) { _phase = Phase.Plan; return true; } Vector3 val = _batchSpots[0]; _batchSpots.RemoveAt(0); PlantType batchType = _batchType; ItemData val2 = null; foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (allItem.m_shared.m_name == batchType.RequiredShared && allItem.m_stack >= batchType.Amount) { val2 = allItem; break; } } int mask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid" }); if (val2 != null && IsCultivatedSpot(val, out var _) && PlantCatalogue.BiomeAllows(batchType, val) && !Physics.CheckSphere(val + Vector3.up * 0.15f, batchType.Plant.m_growRadius + 0.2f, mask)) { base.Logistics.Carried.RemoveItem(val2, batchType.Amount); Quaternion val3 = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f); GameObject val4 = Object.Instantiate<GameObject>(batchType.Prefab, val, val3); batchType.Piece.m_placeEffect.Create(val, val3, val4.transform, 1f, -1, default(ZDOID)); base.Animation.PlaySwing(); DvergrWorkersPlugin.Log.LogInfo((object)("Farmer planted " + ((Object)batchType.Prefab).name + " in " + ((object)Heightmap.FindBiome(val)/*cast due to .constrained prefix*/).ToString() + " (plant biomes " + ((object)Unsafe.As<Biome, Biome>(ref batchType.Plant.m_biome)/*cast due to .constrained prefix*/).ToString() + ", piece biomes " + ((object)Unsafe.As<Biome, Biome>(ref batchType.Piece.m_onlyInBiome)/*cast due to .constrained prefix*/).ToString() + ")")); _planted++; _plantedSpots.Add(new Occupied { Position = val, Radius = batchType.Plant.m_growRadius }); _plantedByFamily[batchType.Family] = (_plantedByFamily.TryGetValue(batchType.Family, out var value) ? value : 0) + 1; SetStatus("Planting " + ((Object)batchType.Prefab).name.Replace("sapling_", "")); } _phase = Phase.Plan; return true; } private bool HarvestNow(float dt) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) ((BaseAI)base.Ai).StopMoving(); _timer -= dt; if (_timer > 0f) { return true; } if ((Object)(object)_harvest == (Object)null || _harvest.m_picked) { _harvest = null; _phase = Phase.Plan; return true; } _collectPos = ((Component)_harvest).transform.position; _harvest.m_nview.ClaimOwnership(); _harvest.Interact(base.Humanoid, false, false); _harvested++; base.Animation.PlaySwing(); _harvest = null; _collectTries = 0; _timer = 0.9f; _phase = Phase.Collect; SetStatus("Harvesting"); return true; } private bool Collect(float dt) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) _timer -= dt; if (_timer > 0f) { return true; } _timer = 0.3f; ItemDrop val = null; foreach (ItemDrop s_instance in ItemDrop.s_instances) { if ((Object)(object)s_instance != (Object)null && s_instance.m_itemData != null && Vector3.Distance(((Component)s_instance).transform.position, _collectPos) < 3f) { val = s_instance; break; } } if ((Object)(object)val == (Object)null || ++_collectTries > 12) { _phase = Phase.Plan; return true; } val.RequestOwn(); string name = val.m_itemData.m_shared.m_name; if (base.Humanoid.Pickup(((Component)val).gameObject, false, false)) { _ours.Add(name); _collectTries = 0; } return true; } private bool StartDeposit() { if (CarryingOther()) { if (!base.Logistics.BeginDeposit((ItemData i) => IsOurs(i) && !PlantCatalogue.IsSeedName(i.m_shared.m_name))) { return Abort(base.Logistics.Reason); } _phase = Phase.Deposit; SetStatus("Delivering crops to a chest"); return true; } if (CarryingSeeds()) { if (!base.Logistics.BeginDeposit((ItemData i) => IsOurs(i) && PlantCatalogue.IsSeedName(i.m_shared.m_name), ChestKind.Input)) { EndRound(finished: true); return false; } _phase = Phase.Deposit; SetStatus("Returning seeds to a chest"); return true; } _phase = Phase.Plan; return true; } private bool Deposit(float dt) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: return Abort(base.Logistics.Reason); default: if (base.Logistics.Transferred == 0) { return Abort("Output chests are full"); } return StartDeposit(); } } } public class BlacksmithWorker : MachineTenderWorker { internal override WorkerRole Role => WorkerRoles.Blacksmith; protected override string AllowedMachines => WorkerConfig.BlacksmithMachines.Value; protected override string NoMachinesNote => "No kiln, smelter or furnace nearby"; } public class BarleyWorker : MachineTenderWorker { internal override WorkerRole Role => WorkerRoles.Barley; protected override string AllowedMachines => WorkerConfig.BarleyMachines.Value; protected override string NoMachinesNote => "No windmill nearby"; } public class FlaxWorker : MachineTenderWorker { internal override WorkerRole Role => WorkerRoles.Flax; protected override string AllowedMachines => WorkerConfig.FlaxMachines.Value; protected override string NoMachinesNote => "No spinning wheel nearby"; } public class EitrWorker : MachineTenderWorker { internal override WorkerRole Role => WorkerRoles.Eitr; protected override string AllowedMachines => WorkerConfig.EitrMachines.Value; protected override string NoMachinesNote => "No eitr refinery nearby"; } public abstract class MachineTenderWorker : WorkerBrain { private enum Phase { Plan, Fetch, Walk, Work, Deposit } private enum Stage { Empty, Collect, Feed } private sealed class Need { public string SharedName; public string PrefabName; public bool Fuel; public int Count; } private const float FeedInterval = 0.35f; private const float EmptyWait = 0.8f; private const float CollectInterval = 0.3f; private const int MaxCollectTries = 12; private const float OutputRadius = 3.5f; private const int FullSlots = 2; private static readonly HashSet<string> LoggedUnknown = new HashSet<string>(); private Phase _phase; private Stage _stage; private Smelter _machine; private List<Need> _needs = new List<Need>(); private int _needIndex; private bool _fetchActive; private bool _pendingOutput; private float _timer; private int _collectTries; private bool _starved; private string _missingFuel = ""; private readonly HashSet<string> _missingOre = new HashSet<string>(); private float _lastDiagnostic = -1000f; private bool _deliverFirst; private int _handled; private int _handledLastRound; private readonly HashSet<Smelter> _served = new HashSet<Smelter>(); private readonly HashSet<Smelter> _unreachable = new HashSet<Smelter>(); private readonly HashSet<string> _productNames = new HashSet<string>(); private HashSet<string> _wanted = new HashSet<string>(); private float _wantedAt = -1000f; protected abstract string AllowedMachines { get; } protected abstract string NoMachinesNote { get; } internal override bool WantsItem(string sharedName) { if (Time.time - _wantedAt > 10f) { _wantedAt = Time.time; HashSet<string> hashSet = new HashSet<string>(); foreach (Smelter item in FindMachines()) { foreach (ItemConversion item2 in item.m_conversion) { if ((Object)(object)item2.m_from != (Object)null) { hashSet.Add(item2.m_from.m_itemData.m_shared.m_name); } } if ((Object)(object)item.m_fuelItem != (Object)null && item.m_maxFuel > 0) { hashSet.Add(item.m_fuelItem.m_itemData.m_shared.m_name); } } _wanted = hashSet; } return _wanted.Contains(sharedName); } protected override string IdleStatus() { if (_handledLastRound <= 0) { return "Idle"; } return "Idle (handled " + _handledLastRound + " items last round)"; } protected override void OnRoundEnded(bool finished) { _machine = null; _handledLastRound = _handled; } private bool IsProduct(ItemData item) { if (item != null) { return _productNames.Contains(item.m_shared.m_name); } return false; } private bool CarryingProduct() { foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (IsProduct(allItem)) { return true; } } return false; } private static string PrefabOf(Smelter machine) { ZNetView nview = machine.m_nview; if (!((Object)(object)nview != (Object)null)) { return Utils.GetPrefabName(((Component)machine).gameObject); } return Utils.GetPrefabName(((Component)nview).gameObject); } private static bool IsValid(Smelter machine) { if ((Object)(object)machine != (Object)null && (Object)(object)machine.m_nview != (Object)null) { return machine.m_nview.IsValid(); } return false; } private static string DisplayName(Smelter machine) { return PrefabOf(machine).Replace('_', ' '); } private static IEnumerable<string> OutputNames(Smelter machine) { foreach (ItemConversion item in machine.m_conversion) { if ((Object)(object)item.m_to != (Object)null) { yield return item.m_to.m_itemData.m_shared.m_name; } } } private List<Smelter> FindMachines() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(AllowedMachines.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)); List<Smelter> list = new List<Smelter>(); _productNames.Clear(); Smelter[] array = Object.FindObjectsByType<Smelter>((FindObjectsSortMode)0); foreach (Smelter val in array) { if (!IsValid(val) || !InJobArea(((Component)val).transform.position)) { continue; } string text = PrefabOf(val); if (!hashSet.Contains(text.Trim())) { if (LoggedUnknown.Add(text)) { DvergrWorkersPlugin.Log.LogInfo((object)(Role.Name + " ignoring '" + text + "' (add it to " + Role.Name + ".Machines to have this worker tend it)")); } continue; } list.Add(val); foreach (string item in OutputNames(val)) { _productNames.Add(item); } } return list; } private IEnumerable<ItemDrop> ProductDropsNear(Smelter machine) { if ((Object)(object)machine.m_outputPoint == (Object)null) { yield break; } HashSet<string> outputs = new HashSet<string>(OutputNames(machine)); foreach (ItemDrop s_instance in ItemDrop.s_instances) { if ((Object)(object)s_instance != (Object)null && s_instance.m_itemData != null && outputs.Contains(s_instance.m_itemData.m_shared.m_name) && Vector3.Distance(((Component)s_instance).transform.position, machine.m_outputPoint.position) < 3.5f) { yield return s_instance; } } } private bool HasPendingOutput(Smelter machine) { if (machine.GetProcessedQueueSize() > 0) { return true; } using (IEnumerator<ItemDrop> enumerator = ProductDropsNear(machine).GetEnumerator()) { if (enumerator.MoveNext()) { _ = enumerator.Current; return true; } } return false; } private int Supply(string sharedName) { return base.Logistics.Carried.CountItems(sharedName, -1, true) + base.Logistics.AvailableTo(sharedName); } private List<Need> BuildNeeds(Smelter machine, Dictionary<string, int> fuelUsers = null) { List<Need> list = new List<Need>(); if ((Object)(object)machine.m_fuelItem != (Object)null && machine.m_maxFuel > 0) { int num = Mathf.FloorToInt((float)machine.m_maxFuel - machine.GetFuel()); string name = machine.m_fuelItem.m_itemData.m_shared.m_name; if (num > 0) { int num2 = Supply(name); int num3 = 1; if (fuelUsers != null && fuelUsers.TryGetValue(name, out var value)) { num3 = Math.Max(1, value); } if (num2 > 0) { num2 = Math.Min(num2, (num2 + num3 - 1) / num3); list.Add(new Need { SharedName = name, PrefabName = ((Object)((Component)machine.m_fuelItem).gameObject).name, Fuel = true, Count = Math.Min(num, num2) }); } else { _starved = true; _missingFuel = name.Replace("$item_", ""); } } } if (machine.m_maxOre > 0) { int num4 = machine.m_maxOre - machine.GetQueueSize(); bool flag = false; foreach (ItemConversion item in machine.m_conversion) { if (num4 <= 0) { break; } if (!((Object)(object)item.m_from == (Object)null)) { string name2 = item.m_from.m_itemData.m_shared.m_name; int num5 = Math.Min(num4, Supply(name2)); if (num5 > 0) { flag = true; num4 -= num5; list.Add(new Need { SharedName = name2, PrefabName = ((Object)((Component)item.m_from).gameObject).name, Count = num5 }); } } } if (!flag && num4 > 0) { _starved = true; foreach (ItemConversion item2 in machine.m_conversion) { if ((Object)(object)item2.m_from != (Object)null) { _missingOre.Add(item2.m_from.m_itemData.m_shared.m_name.Replace("$item_", "")); } } } } return list; } private static bool IsFuelAnywhere(List<Smelter> machines, string sharedName) { foreach (Smelter machine in machines) { if ((Object)(object)machine.m_fuelItem != (Object)null && machine.m_maxFuel > 0 && machine.m_fuelItem.m_itemData.m_shared.m_name == sharedName) { return true; } } return false; } private static string JoinNames(IEnumerable<string> names) { List<string> list = new List<string>(); foreach (string name in names) { list.Add((name.EndsWith("seeds") && name.Length > 5) ? (name.Substring(0, name.Length - 5) + " seeds") : name); } if (list.Count <= 1) { return string.Join("", list); } return string.Join(", ", list.GetRange(0, list.Count - 1)) + " or " + list[list.Count - 1]; } private string WaitingNote() { List<string> list = new List<string>(); if (_missingFuel.Length > 0) { list.Add(_missingFuel); } if (_missingOre.Count > 0) { list.Add((_missingOre.Count <= 3) ? JoinNames(_missingOre) : "items"); } return "Waiting for " + string.Join(" and ", list) + " in a chest"; } private void LogSupply(List<Smelter> machines) { if (Time.time - _lastDiagnostic < 120f) { return; } _lastDiagnostic = Time.time; foreach (Smelter machine in machines) { List<string> list = new List<string>(); foreach (ItemConversion item in machine.m_conversion) { if (!((Object)(object)item.m_from == (Object)null)) { string shared = item.m_from.m_itemData.m_shared.m_name; Func<ItemData, bool> func = (ItemData i) => i.m_shared.m_name == shared; list.Add(shared + " carried " + base.Logistics.Carried.CountItems(shared, -1, true) + ", input " + base.Logistics.CountInChests(ChestKind.Input, func) + ", output " + base.Logistics.CountInChests(ChestKind.Output, func) + ", usable " + base.Logistics.CountAvailable(func)); } } DvergrWorkersPlugin.Log.LogInfo((object)(Role.Name + " has nothing to do at " + PrefabOf(machine) + " (queue " + machine.GetQueueSize() + "/" + machine.m_maxOre + "): " + string.Join("; ", list))); } } protected override bool BeginRound() { _served.Clear(); _unreachable.Clear(); _starved = false; _missingFuel = ""; _missingOre.Clear(); _handled = 0; _machine = null; _fetchActive = false; List<Smelter> list = FindMachines(); if (list.Count == 0) { _handledLastRound = 0; base.IdleNote = NoMachinesNote; return false; } _deliverFirst = CarryingProduct(); bool flag = _deliverFirst; foreach (Smelter item in list) { if (HasPendingOutput(item) || BuildNeeds(item).Count > 0) { flag = true; break; } } if (!flag) { _handledLastRound = 0; base.IdleNote = (_starved ? WaitingNote() : null); LogSupply(list); return false; } _phase = Phase.Plan; DvergrWorkersPlugin.Log.LogInfo((object)(Role.Name + " round started with " + list.Count + " machine(s)")); return true; } protected override bool RunTask(float dt) { return _phase switch { Phase.Plan => Plan(), Phase.Fetch => Fetch(dt), Phase.Walk => Walk(dt), Phase.Work => Work(dt), Phase.Deposit => Deposit(dt), _ => false, }; } private bool Abort(string reason) { DvergrWorkersPlugin.Log.LogInfo((object)(Role.Name + " stopping: " + reason)); base.IdleNote = reason; EndRound(finished: false); return false; } private bool Plan() { //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) List<Smelter> list = FindMachines(); if (CarryingProduct() && (_deliverFirst || base.Logistics.Carried.GetEmptySlots() <= 2)) { _deliverFirst = false; return StartDeposit(); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); HashSet<string> hashSet = new HashSet<string>(); foreach (Smelter item in list) { if (!((Object)(object)item.m_fuelItem == (Object)null) && item.m_maxFuel > 0) { string name = item.m_fuelItem.m_itemData.m_shared.m_name; if (!_served.Contains(item) && !_unreachable.Contains(item) && Mathf.FloorToInt((float)item.m_maxFuel - item.GetFuel()) > 0) { dictionary[name] = ((!dictionary.TryGetValue(name, out var value)) ? 1 : (value + 1)); } } } foreach (Smelter item2 in list) { if (!((Object)(object)item2.m_fuelItem == (Object)null) && item2.m_maxFuel > 0) { continue; } foreach (string item3 in OutputNames(item2)) { hashSet.Add(item3); } } Smelter val = null; List<Need> needs = null; bool pendingOutput = false; int num = int.MaxValue; float num2 = float.MaxValue; foreach (Smelter item4 in list) { if (_served.Contains(item4) || _unreachable.Contains(item4)) { continue; } bool flag = HasPendingOutput(item4); List<Need> list2 = BuildNeeds(item4, dictionary); if (!flag && list2.Count == 0) { continue; } bool flag2 = false; foreach (string item5 in OutputNames(item4)) { flag2 |= hashSet.Contains(item5) && IsFuelAnywhere(list, item5); } int num3 = ((!flag2) ? 1 : 0); float num4 = Vector3.Distance(((Component)item4).transform.position, ((Component)this).transform.position); if (num3 < num || (num3 == num && num4 < num2)) { num = num3; num2 = num4; val = item4; needs = list2; pendingOutput = flag; } } if ((Object)(object)val != (Object)null) { _machine = val; _needs = needs; _pendingOutput = pendingOutput; _needIndex = 0; _fetchActive = false; _phase = Phase.Fetch; return true; } if (CarryingProduct()) { return StartDeposit(); } if (_handled == 0 && _starved) { base.IdleNote = WaitingNote(); } EndRound(finished: true); return false; } private bool Fetch(float dt) { if (!IsValid(_machine)) { _phase = Phase.Plan; return true; } if (_fetchActive) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: DvergrWorkersPlugin.Log.LogInfo((object)(Role.Name + " fetch failed: " + base.Logistics.Reason)); break; } _fetchActive = false; _needIndex++; } while (_needIndex < _needs.Count) { Need need = _needs[_needIndex]; Func<ItemData, bool> match = (ItemData i) => i.m_shared.m_name == need.SharedName; int num = need.Count - base.Logistics.Carried.CountItems(need.SharedName, -1, true); if (num > 0 && base.Logistics.BeginFetchAny(match, num)) { _fetchActive = true; SetStatus("Fetching " + need.SharedName.TrimStart(new char[1] { '$' }).Replace("item_", "") + " from a chest"); return true; } _needIndex++; } return BeginWalk(); } private bool BeginWalk() { _needs.RemoveAll((Need n) => base.Logistics.Carried.CountItems(n.SharedName, -1, true) == 0); foreach (Need need in _needs) { need.Count = Math.Min(need.Count, base.Logistics.Carried.CountItems(need.SharedName, -1, true)); } if (_needs.Count == 0 && !_pendingOutput) { _served.Add(_machine); _phase = Phase.Plan; return true; } Smelter machine = _machine; if (!base.Navigator.Begin(PrefabOf(machine), (Vector3 from) => WorkerNavigator.ClosestPointOnColliders(((Component)machine.m_nview).gameObject, from))) { DvergrWorkersPlugin.Log.LogInfo((object)("No reachable spot next to " + PrefabOf(machine) + ", skipping")); _unreachable.Add(machine); _phase = Phase.Plan; return true; } _phase = Phase.Walk; SetStatus("Walking to the " + DisplayName(machine)); return true; } private bool Walk(float dt) { if (!IsValid(_machine)) { _phase = Phase.Plan; return true; } switch (base.Navigator.Step(dt)) { case NavResult.InReach: case NavResult.CloseEnough: _machine.m_nview.ClaimOwnership(); _phase = Phase.Work; _stage = Stage.Empty; _timer = 0.2f; SetStatus("Tending the " + DisplayName(_machine)); break; case NavResult.Failed: _unreachable.Add(_machine); _phase = Phase.Plan; break; } return true; } private bool Work(float dt) { if (!IsValid(_machine)) { _phase = Phase.Plan; return true; } ((BaseAI)base.Ai).StopMoving(); FaceMachine(); _timer -= dt; if (_timer > 0f) { return true; } switch (_stage) { case Stage.Empty: if (_machine.GetProcessedQueueSize() > 0) { _machine.m_nview.InvokeRPC("RPC_EmptyProcessed", Array.Empty<object>()); _timer = 0.8f; } _stage = Stage.Collect; _collectTries = 0; break; case Stage.Collect: CollectOne(); break; case Stage.Feed: FeedOne(); break; } return true; } private void CollectOne() { _timer = 0.3f; ItemDrop val = null; using (IEnumerator<ItemDrop> enumerator = ProductDropsNear(_machine).GetEnumerator()) { if (enumerator.MoveNext()) { val = enumerator.Current; } } if ((Object)(object)val == (Object)null || ++_collectTries > 12) { _stage = Stage.Feed; _timer = 0.1f; return; } val.RequestOwn(); int stack = val.m_itemData.m_stack; if (base.Humanoid.Pickup(((Component)val).gameObject, false, false)) { _handled += stack; _collectTries = 0; } } private void FeedOne() { _timer = 0.35f; Need need = _needs.Find((Need n) => n.Count > 0); if (need == null) { _served.Add(_machine); _phase = Phase.Plan; return; } ItemData val = null; foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (allItem.m_shared.m_name == need.SharedName) { val = allItem; break; } } if (val != null) { bool num; if (!need.Fuel) { if (_machine.GetQueueSize() >= _machine.m_maxOre) { goto IL_010b; } num = !_machine.IsItemAllowed(((Object)val.m_dropPrefab).name); } else { num = _machine.GetFuel() > (float)(_machine.m_maxFuel - 1); } if (!num) { base.Logistics.Carried.RemoveItem(val, 1); if (need.Fuel) { _machine.m_nview.InvokeRPC("RPC_AddFuel", Array.Empty<object>()); } else { _machine.m_nview.InvokeRPC("RPC_AddOre", new object[2] { ((Object)val.m_dropPrefab).name, val.m_cheated }); } need.Count--; _handled++; base.Animation.PlaySwing(); return; } } goto IL_010b; IL_010b: need.Count = 0; } private void FaceMachine() { //IL_000b: 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_0020: 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) //IL_0058: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)_machine).transform.position - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { base.Character.SetLookDir(((Vector3)(ref val)).normalized, 0f); ((BaseAI)base.Ai).LookTowards(val); } } private bool StartDeposit() { if (!CarryingProduct()) { _phase = Phase.Plan; return true; } if (!base.Logistics.BeginDeposit(IsProduct)) { return Abort(base.Logistics.Reason); } _phase = Phase.Deposit; SetStatus("Delivering product to a chest"); return true; } private bool Deposit(float dt) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: return Abort(base.Logistics.Reason); default: if (base.Logistics.Transferred == 0) { return Abort("Output chests are full"); } if (CarryingProduct()) { return StartDeposit(); } _phase = Phase.Plan; return true; } } } internal sealed class PlantType { public GameObject Prefab; public Piece Piece; public Plant Plant; public string RequiredShared; public int Amount; public bool Raw; public bool IsTree; public string Family; public string YieldShared; public HashSet<string> GrownNames = new HashSet<string>(); } internal struct Occupied { public Vector3 Position; public float Radius; } internal static class PlantCatalogue { private const string CultivatorPrefab = "Cultivator"; private static List<PlantType> _all; public const float SpacingMargin = 0.4f; public static List<PlantType> All { get { if (_all == null) { Load(); } return _all; } } public static IEnumerable<PlantType> Crops => All.Where((PlantType t) => !t.IsTree); public static IEnumerable<PlantType> Trees => All.Where((PlantType t) => t.IsTree); public static HashSet<string> TreeSeedNames => new HashSet<string>(Trees.Select((PlantType t) => t.RequiredShared)); public static bool IsSeedName(string sharedName) { return sharedName.ToLowerInvariant().Contains("seed"); } public static PlantType Of(GameObject go) { string name = Utils.GetPrefabName(go); return All.FirstOrDefault((PlantType t) => ((Object)t.Prefab).name == name || (t.IsTree && t.GrownNames.Contains(name))); } public static bool BiomeAllows(PlantType type, Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0033: Invalid comparison between Unknown and I4 //IL_0014: 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) Biome val = Heightmap.FindBiome(p); if ((int)type.Piece.m_onlyInBiome != 0 && (val & type.Piece.m_onlyInBiome) == 0) { return false; } return (val & type.Plant.m_biome) > 0; } private static void Load() { _all = new List<PlantType>(); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Cultivator"); PieceTable val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_buildPieces : null); if ((Object)(object)val == (Object)null) { DvergrWorkersPlugin.Log.LogError((object)"Cultivator piece table not found"); return; } foreach (GameObject piece in val.m_pieces) { Piece component = piece.GetComponent<Piece>(); Plant component2 = piece.GetComponent<Plant>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || component2.m_grownPrefabs.Length == 0 || component2.m_attachDistance > 0f || component.m_resources.Length == 0 || (Object)(object)component.m_resources[0].m_resItem == (Object)null) { continue; } GameObject val2 = component2.m_grownPrefabs[0]; string name = component.m_resources[0].m_resItem.m_itemData.m_shared.m_name; int amount = Math.Max(1, component.m_resources[0].m_amount); if ((Object)(object)val2.GetComponent<TreeBase>() != (Object)null) { _all.Add(new PlantType { Prefab = piece, Piece = component, Plant = component2, RequiredShared = name, Amount = amount, IsTree = true, Family = name, GrownNames = new HashSet<string>(component2.m_grownPrefabs.Select((GameObject g) => ((Object)g).name)) }); DvergrWorkersPlugin.Log.LogInfo((object)("Tree: " + ((Object)piece).name + " (needs " + name + ", room " + component2.m_growRadius + "m, grows " + string.Join("/", component2.m_grownPrefabs.Select((GameObject g) => ((Object)g).name)) + ")")); continue; } Pickable component3 = val2.GetComponent<Pickable>(); if (!((Object)(object)component3 == (Object)null) && !((Object)(object)component3.m_itemPrefab == (Object)null)) { string name2 = component3.m_itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name; PlantType plantType = new PlantType { Prefab = piece, Piece = component, Plant = component2, RequiredShared = name, Amount = amount, Raw = (IsSeedName(name2) && !IsSeedName(name)), YieldShared = name2, Family = (IsSeedName(name) ? name2 : name) }; _all.Add(plantType); DvergrWorkersPlugin.Log.LogInfo((object)("Farmer crop: " + ((Object)piece).name + " (needs " + name + ", room " + component2.m_growRadius + "m, yields " + name2 + (plantType.Raw ? ", raw" : "") + ", family " + plantType.Family + ")")); } } } } [BepInPlugin("javidahmed64592.dvergrworkers", "Dvergr Workers", "0.5.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class DvergrWorkersPlugin : BaseUnityPlugin { public const string PluginGUID = "javidahmed64592.dvergrworkers"; public const string PluginName = "Dvergr Workers"; public const string PluginVersion = "0.5.0"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; WorkerConfig.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("javidahmed64592.dvergrworkers"); _harmony.PatchAll(); PrefabManager.OnVanillaPrefabsAvailable += RegisterPrefabs; } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void RegisterPrefabs() { PrefabManager.OnVanillaPrefabsAvailable -= RegisterPrefabs; try { WorkerPrefabs.RegisterAll(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to register Dvergr worker prefabs: " + ex)); } } } internal static class WorkerConfig { public static ConfigEntry<float> JobRadius; public static ConfigEntry<float> RespawnDelay; public static ConfigEntry<float> ThreatRadius; public static ConfigEntry<int> TreesPerRound; public static ConfigEntry<int> StumpsPerRound; public static ConfigEntry<string> BlacksmithMachines; public static ConfigEntry<string> BarleyMachines; public static ConfigEntry<string> FlaxMachines; public static ConfigEntry<string> EitrMachines; public static ConfigEntry<bool> TakeFromOutputChests; public static ConfigEntry<string> RefuelerFuels; public static ConfigEntry<float> RefuelBelow; public static ConfigEntry<int> SeedReserve; public static ConfigEntry<int> PlantsPerTrip; public static ConfigEntry<int> MaxTrees; public static ConfigEntry<int> TreesPerTrip; public static ConfigEntry<float> TreeBuildingClearance; public static ConfigEntry<bool> WorkAtNight; public static ConfigEntry<int> InventoryWidth; public static ConfigEntry<int> InventoryHeight; public static void Bind(ConfigFile config) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Expected O, but got Unknown //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Expected O, but got Unknown //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Expected O, but got Unknown //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Expected O, but got Unknown //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Expected O, but got Unknown //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Expected O, but got Unknown //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Expected O, but got Unknown //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Expected O, but got Unknown JobRadius = config.Bind<float>("Workers", "JobRadius", 30f, new ConfigDescription("Workers wander and perform tasks within this distance (metres) of their bed. A small tolerance is added for tasks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), Array.Empty<object>())); RespawnDelay = config.Bind<float>("Workers", "RespawnDelay", 30f, new ConfigDescription("Seconds after a worker is lost before its bed spawns a replacement.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 600f), Array.Empty<object>())); ThreatRadius = config.Bind<float>("Workers", "ThreatRadius", 25f, new ConfigDescription("Workers drop their task and defend themselves if an enemy comes within this distance (metres).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 80f), Array.Empty<object>())); WorkAtNight = config.Bind<bool>("Workers", "WorkAtNight", false, "If false, workers rest at night and only work in daylight."); TakeFromOutputChests = config.Bind<bool>("Workers", "TakeFromOutputChests", true, "Let workers take items from output chests when another worker nearby produces them, e.g. the blacksmith taking the woodcutter's wood. Other items in output chests are never touched."); InventoryWidth = config.Bind<int>("Workers", "InventoryWidth", 8, new ConfigDescription("Columns in a worker's own inventory.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 12), Array.Empty<object>())); InventoryHeight = config.Bind<int>("Workers", "InventoryHeight", 4, new ConfigDescription("Rows in a worker's own inventory.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); foreach (WorkerRole item in WorkerRoles.All) { item.BindConfig(config); } BlacksmithMachines = config.Bind<string>(WorkerRoles.Blacksmith.Name, "Machines", "charcoal_kiln,smelter,blastfurnace", "Comma-separated prefab names of the kilns, smelters and furnaces the blacksmith tends."); BarleyMachines = config.Bind<string>(WorkerRoles.Barley.Name, "Machines", "windmill", "Comma-separated prefab names of the windmills the barley worker tends."); FlaxMachines = config.Bind<string>(WorkerRoles.Flax.Name, "Machines", "piece_spinningwheel", "Comma-separated prefab names of the spinning wheels the flax worker tends."); EitrMachines = config.Bind<string>(WorkerRoles.Eitr.Name, "Machines", "eitrrefinery", "Comma-separated prefab names of the eitr refineries the eitr worker tends."); RefuelerFuels = config.Bind<string>(WorkerRoles.Refueler.Name, "Fuels", "Resin,Wood,Coal", "Comma-separated item prefab names the refueler may use. Fires that burn anything else are left alone."); RefuelBelow = config.Bind<float>(WorkerRoles.Refueler.Name, "RefuelBelow", 0.6f, new ConfigDescription("A fire is topped up once its fuel falls to this fraction of its maximum.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>())); SeedReserve = config.Bind<int>(WorkerRoles.Farmer.Name, "SeedReserve", 4, new ConfigDescription("The farmer plants raw ingredients (a carrot, a turnip...) to make more seeds once a crop's seeds fall to this many or fewer.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>())); PlantsPerTrip = config.Bind<int>(WorkerRoles.Farmer.Name, "PlantsPerTrip", 8, new ConfigDescription("How many of one crop the farmer plants before moving on to the next, which keeps the crops evenly mixed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>())); MaxTrees = config.Bind<int>(WorkerRoles.TreeFarmer.Name, "MaxTrees", 40, new ConfigDescription("The tree farmer stops planting once the job area holds this many trees and saplings, wild ones included.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 500), Array.Empty<object>())); TreesPerTrip = config.Bind<int>(WorkerRoles.TreeFarmer.Name, "TreesPerTrip", 5, new ConfigDescription("How many of one species the tree farmer plants before moving on to the next.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>())); TreeBuildingClearance = config.Bind<float>(WorkerRoles.TreeFarmer.Name, "BuildingClearance", 8f, new ConfigDescription("Trees are never planted closer than this (metres) to any building piece.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 40f), Array.Empty<object>())); TreesPerRound = config.Bind<int>(WorkerRoles.Woodcutter.Name, "TreesPerRound", 3, new ConfigDescription("How many standing trees a woodcutter fells per round.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>())); StumpsPerRound = config.Bind<int>(WorkerRoles.Woodcutter.Name, "StumpsPerRound", 10, new ConfigDescription("How many leftover tree stumps a woodcutter clears per round. 0 leaves stumps alone.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>())); } } public class RefuelerWorker : WorkerBrain { private enum Phase { Plan, Fetch, Walk, Work, Return } private const float AddInterval = 0.35f; private Phase _phase; private Fireplace _fire; private string _fuelShared; private int _toAdd; private float _timer; private bool _starved; private string _missingFuel = ""; private int _refueledThisRound; private int _refueledLastRound; private readonly HashSet<Fireplace> _served = new HashSet<Fireplace>(); private readonly HashSet<Fireplace> _unreachable = new HashSet<Fireplace>(); private readonly HashSet<string> _fuelNames = new HashSet<string>(); internal override WorkerRole Role => WorkerRoles.Refueler; protected override string IdleStatus() { if (_refueledLastRound <= 0) { return "Idle"; } return "Idle (refuelled " + _refueledLastRound + " fires last round)"; } protected override void OnRoundEnded(bool finished) { _fire = null; _refueledLastRound = _refueledThisRound; } internal override bool WantsItem(string sharedName) { string[] array = WorkerConfig.RefuelerFuels.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text.Trim()); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val != (Object)null && val.m_itemData.m_shared.m_name == sharedName) { return true; } } return false; } private static bool IsValid(Fireplace fire) { if ((Object)(object)fire != (Object)null && (Object)(object)fire.m_nview != (Object)null) { return fire.m_nview.IsValid(); } return false; } private static float Fuel(Fireplace fire) { return fire.m_nview.GetZDO().GetFloat(ZDOVars.s_fuel, 0f); } private static string PrefabOf(Fireplace fire) { return Utils.GetPrefabName(((Component)fire.m_nview).gameObject); } private static string FuelShared(Fireplace fire) { return fire.m_fuelItem.m_itemData.m_shared.m_name; } private bool IsFuel(ItemData item) { if (item != null) { return _fuelNames.Contains(item.m_shared.m_name); } return false; } private static int Room(Fireplace fire) { float num = Fuel(fire); float num2 = fire.m_maxFuel * WorkerConfig.RefuelBelow.Value; if (!(num <= num2)) { return 0; } return Mathf.FloorToInt(fire.m_maxFuel - num); } private List<Fireplace> FindFires() { //IL_0094: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(WorkerConfig.RefuelerFuels.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)); List<Fireplace> list = new List<Fireplace>(); _fuelNames.Clear(); Fireplace[] array = Object.FindObjectsByType<Fireplace>((FindObjectsSortMode)0); foreach (Fireplace val in array) { if (IsValid(val) && !val.m_infiniteFuel && val.m_canRefill && !((Object)(object)val.m_fuelItem == (Object)null) && !(val.m_secPerFuel <= 0f) && InJobArea(((Component)val).transform.position) && hashSet.Contains(((Object)((Component)val.m_fuelItem).gameObject).name.Trim())) { Piece componentInParent = ((Component)val).GetComponentInParent<Piece>(); if (!((Object)(object)componentInParent == (Object)null) && componentInParent.IsPlacedByPlayer() && val.m_nview.GetZDO().GetInt(ZDOVars.s_state, 1) == 1) { list.Add(val); _fuelNames.Add(FuelShared(val)); } } } return list; } private int Supply(string sharedName) { return base.Logistics.Carried.CountItems(sharedName, -1, true) + base.Logistics.AvailableTo(sharedName); } private bool CarryingFuel() { foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (IsFuel(allItem)) { return true; } } return false; } protected override bool BeginRound() { _served.Clear(); _unreachable.Clear(); _starved = false; _missingFuel = ""; _refueledThisRound = 0; _fire = null; List<Fireplace> list = FindFires(); bool flag = false; foreach (Fireplace item in list) { if (Room(item) > 0) { if (Supply(FuelShared(item)) > 0) { flag = true; break; } _starved = true; _missingFuel = FuelName(item); } } if (!flag) { _refueledLastRound = 0; base.IdleNote = (_starved ? ("Waiting for " + _missingFuel + " in a chest") : null); return false; } base.Equipment.EquipTool(); _phase = Phase.Plan; DvergrWorkersPlugin.Log.LogInfo((object)"Refueler round started"); return true; } private static string FuelName(Fireplace fire) { return ((Object)((Component)fire.m_fuelItem).gameObject).name.ToLowerInvariant(); } protected override bool RunTask(float dt) { return _phase switch { Phase.Plan => Plan(), Phase.Fetch => Fetch(dt), Phase.Walk => Walk(dt), Phase.Work => Work(dt), Phase.Return => Return(dt), _ => false, }; } private bool Plan() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) List<Fireplace> list = FindFires(); Fireplace val = null; float num = float.MaxValue; foreach (Fireplace item in list) { if (_served.Contains(item) || _unreachable.Contains(item) || Room(item) <= 0) { continue; } if (Supply(FuelShared(item)) <= 0) { _starved = true; _missingFuel = FuelName(item); continue; } float num2 = Vector3.Distance(((Component)item).transform.position, ((Component)this).transform.position); if (num2 < num) { num = num2; val = item; } } if ((Object)(object)val == (Object)null) { if (CarryingFuel()) { return StartReturn(); } if (_refueledThisRound == 0 && _starved) { base.IdleNote = "Waiting for " + _missingFuel + " in a chest"; } EndRound(finished: true); return false; } _fire = val; _fuelShared = FuelShared(val); int num3 = 0; foreach (Fireplace item2 in list) { if (!_served.Contains(item2) && !_unreachable.Contains(item2) && FuelShared(item2) == _fuelShared) { num3 += Math.Max(0, Room(item2)); } } int num4 = Math.Min(num3, Supply(_fuelShared)) - base.Logistics.Carried.CountItems(_fuelShared, -1, true); _toAdd = Math.Min(Room(val), base.Logistics.Carried.CountItems(_fuelShared, -1, true)); if (num4 > 0) { string shared = _fuelShared; if (base.Logistics.BeginFetchAny((ItemData i) => i.m_shared.m_name == shared, num4)) { _phase = Phase.Fetch; SetStatus("Fetching fuel from a chest"); return true; } DvergrWorkersPlugin.Log.LogInfo((object)("Refueler fetch failed: " + base.Logistics.Reason)); } return BeginWalk(); } private bool Fetch(float dt) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: DvergrWorkersPlugin.Log.LogInfo((object)("Refueler fetch failed: " + base.Logistics.Reason)); break; } _toAdd = (IsValid(_fire) ? Math.Min(Room(_fire), base.Logistics.Carried.CountItems(_fuelShared, -1, true)) : 0); return BeginWalk(); } private bool BeginWalk() { if (!IsValid(_fire) || _toAdd <= 0) { if ((Object)(object)_fire != (Object)null) { _unreachable.Add(_fire); } _phase = Phase.Plan; return true; } Fireplace fire = _fire; if (!base.Navigator.Begin(PrefabOf(fire), (Vector3 from) => WorkerNavigator.ClosestPointOnColliders(((Component)fire.m_nview).gameObject, from))) { DvergrWorkersPlugin.Log.LogInfo((object)("No reachable spot next to " + PrefabOf(fire) + ", skipping")); _unreachable.Add(fire); _phase = Phase.Plan; return true; } _phase = Phase.Walk; SetStatus("Walking to a " + PrefabOf(fire).Replace('_', ' ')); return true; } private bool Walk(float dt) { if (!IsValid(_fire)) { _phase = Phase.Plan; return true; } switch (base.Navigator.Step(dt)) { case NavResult.InReach: case NavResult.CloseEnough: _fire.m_nview.ClaimOwnership(); _phase = Phase.Work; _timer = 0.2f; SetStatus("Refuelling a " + PrefabOf(_fire).Replace('_', ' ')); break; case NavResult.Failed: _unreachable.Add(_fire); _phase = Phase.Plan; break; } return true; } private bool Work(float dt) { //IL_002c: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (!IsValid(_fire)) { _phase = Phase.Plan; return true; } ((BaseAI)base.Ai).StopMoving(); Vector3 val = ((Component)_fire).transform.position - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { base.Character.SetLookDir(((Vector3)(ref val)).normalized, 0f); ((BaseAI)base.Ai).LookTowards(val); } _timer -= dt; if (_timer > 0f) { return true; } _timer = 0.35f; ItemData val2 = null; foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (allItem.m_shared.m_name == _fuelShared) { val2 = allItem; break; } } if (val2 == null || _toAdd <= 0 || (float)Mathf.CeilToInt(Fuel(_fire)) >= _fire.m_maxFuel) { _served.Add(_fire); _phase = Phase.Plan; return true; } base.Logistics.Carried.RemoveItem(val2, 1); _fire.m_nview.InvokeRPC("RPC_AddFuel", Array.Empty<object>()); _toAdd--; _refueledThisRound++; return true; } private bool StartReturn() { if (!base.Logistics.BeginDeposit(IsFuel, ChestKind.Input)) { EndRound(finished: true); return false; } _phase = Phase.Return; SetStatus("Returning spare fuel to a chest"); return true; } private bool Return(float dt) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: EndRound(finished: true); return false; default: if (base.Logistics.Transferred > 0 && CarryingFuel()) { return StartReturn(); } EndRound(finished: true); return false; } } } public class RepairWorker : WorkerBrain { private enum Phase { Walking, Windup, Recover } private const float WindupTime = 0.5f; private const float RecoverTime = 1.1f; private Phase _phase; private float _phaseTimer; private readonly List<WearNTear> _queue = new List<WearNTear>(); private readonly HashSet<WearNTear> _failed = new HashSet<WearNTear>(); private WearNTear _current; private int _repairedThisRound; private int _repairedLastRound; internal override WorkerRole Role => WorkerRoles.Repair; protected override string IdleStatus() { if (_repairedLastRound <= 0) { return "Idle"; } return "Idle (repaired " + _repairedLastRound + " pieces last round)"; } protected override void OnRoundEnded(bool finished) { _queue.Clear(); _current = null; _repairedLastRound = _repairedThisRound; } protected override bool BeginRound() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) _queue.Clear(); _failed.Clear(); _repairedThisRound = 0; foreach (WearNTear allInstance in WearNTear.GetAllInstances()) { if (NeedsRepair(allInstance) && InJobArea(((Component)allInstance).transform.position)) { _queue.Add(allInstance); } } if (_queue.Count == 0) { _repairedLastRound = 0; return false; } base.Equipment.EquipTool(); _current = null; _phase = Phase.Walking; DvergrWorkersPlugin.Log.LogInfo((object)("Repair round started with " + _queue.Count + " damaged pieces")); return true; } private static bool NeedsRepair(WearNTear wnt) { if ((Object)(object)wnt == (Object)null || (Object)(object)wnt.m_nview == (Object)null || !wnt.m_nview.IsValid()) { return false; } Piece component = ((Component)wnt).GetComponent<Piece>(); if ((Object)(object)component == (Object)null || !component.IsPlacedByPlayer()) { return false; } return wnt.GetHealthPercentage() < 0.999f; } protected override bool RunTask(float dt) { switch (_phase) { case Phase.Walking: return Walk(dt); case Phase.Windup: ((BaseAI)base.Ai).StopMoving(); FacePiece(); _phaseTimer -= dt; if (_phaseTimer <= 0f) { Hammer(); _phase = Phase.Recover; _phaseTimer = 1.1f; } return true; case Phase.Recover: ((BaseAI)base.Ai).StopMoving(); _phaseTimer -= dt; if (_phaseTimer <= 0f) { _current = null; _phase = Phase.Walking; } return true; default: return false; } } private bool Walk(float dt) { if ((Object)(object)_current == (Object)null && !PickNext()) { EndRound(finished: true); return false; } if (!NeedsRepair(_current)) { _current = null; return true; } switch (base.Navigator.Step(dt)) { case NavResult.InReach: case NavResult.CloseEnough: _phase = Phase.Windup; _phaseTimer = 0.5f; break; case NavResult.Failed: _failed.Add(_current); _current = null; break; } return true; } private bool PickNext() { //IL_003c: 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) while (_queue.Count > 0) { int num = -1; float num2 = float.MaxValue; for (int i = 0; i < _queue.Count; i++) { if (!((Object)(object)_queue[i] == (Object)null)) { float num3 = Vector3.Distance(((Component)_queue[i]).transform.position, ((Component)this).transform.position); if (num3 < num2) { num2 = num3; num = i; } } } if (num < 0) { _queue.Clear(); return false; } WearNTear candidate = _queue[num]; _queue.RemoveAt(num); if (NeedsRepair(candidate) && !_failed.Contains(candidate)) { if (base.Navigator.Begin(((Object)candidate).name, (Vector3 from) => WorkerNavigator.ClosestPointOnColliders(((Component)candidate).gameObject, from))) { _current = candidate; SetStatus("Walking to a damaged piece (" + (_queue.Count + 1) + " left)"); return true; } DvergrWorkersPlugin.Log.LogInfo((object)("No reachable spot next to " + ((Object)candidate).name + ", skipping")); } } return false; } private void FacePiece() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_002f: 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_0067: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_current == (Object)null)) { Vector3 val = ((Component)_current).transform.position - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { base.Character.SetLookDir(((Vector3)(ref val)).normalized, 0f); ((BaseAI)base.Ai).LookTowards(val); } } } private void Hammer() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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) if (!NeedsRepair(_current)) { return; } if (_current.Repair()) { _repairedThisRound++; SetStatus("Repairing (" + _queue.Count + " more queued)"); Piece component = ((Component)_current).GetComponent<Piece>(); if ((Object)(object)component != (Object)null && component.m_placeEffect != null) { component.m_placeEffect.Create(((Component)component).transform.position, ((Component)component).transform.rotation, (Transform)null, 1f, -1, base.Character.GetZDOID()); } } base.Animation.PlaySwing(); } } public class TreeFarmerWorker : WorkerBrain { private enum Phase { Plan, Fetch, Walk, Plant, Return } private const float PlantTime = 0.6f; private const float GridStep = 2f; private const float TreeSpacingMargin = 1f; private const float DefaultTreeRoom = 2f; private const float MinWaterClearance = 0.5f; private const float FarmlandClearance = 2.5f; private Phase _phase; private PlantType _batchType; private readonly List<Vector3> _batchSpots = new List<Vector3>(); private float _timer; private int _planted; private int _plantedLastRound; private bool _noSupply; private bool _noSpots; private bool _full; private readonly HashSet<PlantType> _failedTypes = new HashSet<PlantType>(); private readonly HashSet<PlantType> _loggedBiome = new HashSet<PlantType>(); private readonly Dictionary<string, int> _plantedByFamily = new Dictionary<string, int>(); private readonly List<Occupied> _plantedSpots = new List<Occupied>(); internal override WorkerRole Role => WorkerRoles.TreeFarmer; protected override string IdleStatus() { if (_plantedLastRound <= 0) { return "Idle"; } return "Idle (planted " + _plantedLastRound + " trees last round)"; } protected override void OnRoundEnded(bool finished) { _batchSpots.Clear(); _plantedLastRound = _planted; } internal override bool WantsItem(string sharedName) { return PlantCatalogue.TreeSeedNames.Contains(sharedName); } private int Supply(string sharedName) { return base.Logistics.Carried.CountItems(sharedName, -1, true) + base.Logistics.AvailableTo(sharedName); } private bool CarryingSeeds() { HashSet<string> treeSeedNames = PlantCatalogue.TreeSeedNames; foreach (ItemData allItem in base.Logistics.Carried.GetAllItems()) { if (treeSeedNames.Contains(allItem.m_shared.m_name)) { return true; } } return false; } private Dictionary<string, int> CountFamilies(out int total) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (PlantType tree in PlantCatalogue.Trees) { dictionary[tree.Family] = 0; } total = 0; TreeBase[] array = Object.FindObjectsByType<TreeBase>((FindObjectsSortMode)0); foreach (TreeBase val in array) { if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && InJobArea(((Component)val).transform.position)) { total++; PlantType plantType = PlantCatalogue.Of(((Component)val).gameObject); if (plantType != null && plantType.IsTree) { dictionary[plantType.Family]++; } } } Plant[] array2 = Object.FindObjectsByType<Plant>((FindObjectsSortMode)0); foreach (Plant val2 in array2) { if ((Object)(object)val2 != (Object)null && InJobArea(((Component)val2).transform.position)) { PlantType plantType2 = PlantCatalogue.Of(((Component)val2).gameObject); if (plantType2 != null && plantType2.IsTree) { total++; dictionary[plantType2.Family]++; } } } foreach (KeyValuePair<string, int> item in _plantedByFamily) { if (dictionary.ContainsKey(item.Key)) { dictionary[item.Key] += item.Value; } } return dictionary; } private static List<string> OrderedFamilies(Dictionary<string, int> counts) { List<string> list = new List<string>(counts.Keys); list.Sort((string a, string b) => counts[a].CompareTo(counts[b])); return list; } private PlantType ChooseType(string family) { foreach (PlantType tree in PlantCatalogue.Trees) { if (tree.Family == family && !_failedTypes.Contains(tree) && Supply(tree.RequiredShared) >= tree.Amount) { return tree; } } return null; } private List<Vector3> FindSpots(PlantType type, int max) { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_049b: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_044c: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_04e3: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Unknown result type (might be due to invalid IL or missing references) //IL_04ec: Unknown result type (might be due to invalid IL or missing references) //IL_04ee: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0527: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) int mask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid" }); int mask2 = LayerMask.GetMask(new string[3] { "Default", "static_solid", "piece" }); int mask3 = LayerMask.GetMask(new string[2] { "piece", "piece_nonsolid" }); int mask4 = LayerMask.GetMask(new string[1] { "terrain" }); float value = WorkerConfig.JobRadius.Value; float num = Mathf.Max(type.Plant.m_growRadius, 0.5f); float value2 = WorkerConfig.TreeBuildingClearance.Value; int num2 = 0; List<Occupied> list = new List<Occupied>(_plantedSpots); Plant[] array = Object.FindObjectsByType<Plant>((FindObjectsSortMode)0); foreach (Plant val in array) { if ((Object)(object)val != (Object)null && Utils.DistanceXZ(((Component)val).transform.position, base.Home) <= value + 10f) { list.Add(new Occupied { Position = ((Component)val).transform.position, Radius = val.m_growRadius }); } } TreeBase[] array2 = Object.FindObjectsByType<TreeBase>((FindObjectsSortMode)0); foreach (TreeBase val2 in array2) { if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy && Utils.DistanceXZ(((Component)val2).transform.position, base.Home) <= value + 10f) { PlantType plantType = PlantCatalogue.Of(((Component)val2).gameObject); float radius = ((plantType != null && plantType.IsTree) ? plantType.Plant.m_growRadius : 2f); list.Add(new Occupied { Position = ((Component)val2).transform.position, Radius = radius }); } } List<Vector3> list2 = new List<Vector3>(); Vector3 val3 = default(Vector3); for (float num3 = 0f - value; num3 <= value; num3 += 2f) { for (float num4 = 0f - value; num4 <= value; num4 += 2f) { ((Vector3)(ref val3))..ctor(base.Home.x + num3 + Random.Range(-0.6f, 0.6f), 0f, base.Home.z + num4 + Random.Range(-0.6f, 0.6f)); if (!InJobArea(val3)) { continue; } val3.y = ZoneSystem.instance.GetGroundHeight(val3); Heightmap val4 = Heightmap.FindHeightmap(val3); if ((Object)(object)val4 == (Object)null || val3.y <= ZoneSystem.instance.m_waterLevel + 0.5f) { continue; } if (!PlantCatalogue.BiomeAllows(type, val3)) { num2++; } else { if ((type.Piece.m_vegetationGroundOnly && !HasDirt(val4, val3)) || NearFarmland(val3) || Physics.CheckSphere(val3, value2, mask3) || !FlatEnough(val3, mask4) || Physics.CheckSphere(val3 + Vector3.up * 0.2f, num + 0.3f, mask) || Physics.Raycast(val3 + Vector3.up * 0.2f, Vector3.up, 100f, mask2)) { continue; } bool flag = false; foreach (Occupied item in list) { flag |= Utils.DistanceXZ(item.Position, val3) < Mathf.Max(num, item.Radius) + 1f; } if (!flag) { list2.Add(val3); } } } } if (list2.Count == 0 && num2 > 0 && _loggedBiome.Add(type)) { DvergrWorkersPlugin.Log.LogInfo((object)("Tree farmer: " + ((Object)type.Prefab).name + " cannot be planted here, wrong biome (" + ((object)Heightmap.FindBiome(base.Home)/*cast due to .constrained prefix*/).ToString() + " is not in plant biomes " + ((object)Unsafe.As<Biome, Biome>(ref type.Plant.m_biome)/*cast due to .constrained prefix*/).ToString() + ")")); } Vector3 here = ((Component)this).transform.position; list2.Sort((Vector3 a, Vector3 b) => Vector3.Distance(a, here).CompareTo(Vector3.Distance(b, here))); List<Vector3> list3 = new List<Vector3>(); foreach (Vector3 item2 in list2) { bool flag2 = false; foreach (Vector3 item3 in list3) { flag2 |= Utils.DistanceXZ(item3, item2) < num * 2f + 1f; } if (!flag2) { list3.Add(item2); if (list3.Count >= max) { break; } } } return list3; } private static bool HasDirt(Heightmap heightmap, Vector3 p) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 float vegetationMask = heightmap.GetVegetationMask(p); if ((int)heightmap.GetBiome(p, 0.02f, false) != 32) { return vegetationMask >= 0.25f; } return vegetationMask <= 0.1f; } private static bool FlatEnough(Vector3 p, int terrainMask) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_002b: Unknown result type (might be due to invalid IL or missing references) RaycastHit val = default(RaycastHit); if (Physics.Raycast(p + Vector3.up * 2f, Vector3.down, ref val, 6f, terrainMask)) { return ((RaycastHit)(ref val)).normal.y >= 0.8f; } return false; } private static bool NearFarmland(Vector3 p) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_002a: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[5] { Vector3.zero, Vector3.right * 2.5f, Vector3.left * 2.5f, Vector3.forward * 2.5f, Vector3.back * 2.5f }; foreach (Vector3 val in array) { Heightmap val2 = Heightmap.FindHeightmap(p + val); if ((Object)(object)val2 != (Object)null && val2.IsCultivated(p + val)) { return true; } } return false; } protected override bool BeginRound() { _planted = 0; _noSupply = false; _noSpots = false; _full = false; _failedTypes.Clear(); _loggedBiome.Clear(); _plantedByFamily.Clear(); _plantedSpots.Clear(); _batchSpots.Clear(); _batchType = null; if (!PlantCatalogue.Trees.Any()) { base.IdleNote = "No tree saplings found in the cultivator"; return false; } if (!AnythingToPlant()) { _plantedLastRound = 0; base.IdleNote = NothingToDoNote(); return false; } base.Equipment.EquipTool(); _phase = Phase.Plan; DvergrWorkersPlugin.Log.LogInfo((object)"Tree farmer round started"); return true; } private bool AnythingToPlant() { int total; Dictionary<string, int> counts = CountFamilies(out total); if (total >= WorkerConfig.MaxTrees.Value) { _full = true; return CarryingSeeds(); } foreach (string item in OrderedFamilies(counts)) { PlantType plantType = ChooseType(item); if (plantType == null) { _noSupply = true; continue; } if (FindSpots(plantType, 1).Count > 0) { return true; } _noSpots = true; } return CarryingSeeds(); } private string NothingToDoNote() { if (_full) { return "The forest is full"; } if (_noSupply && !_noSpots) { return "Waiting for tree seeds in a chest"; } if (_noSpots && !_noSupply) { return "No suitable ground to plant trees"; } return null; } protected override bool RunTask(float dt) { return _phase switch { Phase.Plan => Plan(), Phase.Fetch => Fetch(dt), Phase.Walk => Walk(dt), Phase.Plant => PlantNow(dt), Phase.Return => Return(dt), _ => false, }; } private bool Plan() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (_batchSpots.Count > 0 && _batchType != null && base.Logistics.Carried.CountItems(_batchType.RequiredShared, -1, true) >= _batchType.Amount) { return BeginWalk(_batchSpots[0]); } _batchSpots.Clear(); if (StartBatch()) { return true; } if (CarryingSeeds()) { return StartReturn(); } if (_planted == 0) { base.IdleNote = NothingToDoNote(); } EndRound(finished: true); return false; } private bool StartBatch() { int total; Dictionary<string, int> counts = CountFamilies(out total); int num = WorkerConfig.MaxTrees.Value - total; if (num <= 0) { _full = true; return false; } foreach (string item in OrderedFamilies(counts)) { PlantType plantType = ChooseType(item); if (plantType == null) { _noSupply = true; continue; } List<Vector3> list = FindSpots(plantType, Math.Min(WorkerConfig.TreesPerTrip.Value, num)); if (list.Count == 0) { _noSpots = true; continue; } int num2 = Math.Min(list.Count, Supply(plantType.RequiredShared) / plantType.Amount); if (num2 > 0) { _batchType = plantType; _batchSpots.Clear(); _batchSpots.AddRange(list.GetRange(0, num2)); int num3 = num2 * plantType.Amount - base.Logistics.Carried.CountItems(plantType.RequiredShared, -1, true); if (num3 <= 0) { _phase = Phase.Plan; return true; } string shared = plantType.RequiredShared; if (base.Logistics.BeginFetchAny((ItemData i) => i.m_shared.m_name == shared, num3)) { _phase = Phase.Fetch; SetStatus("Fetching seeds to plant"); return true; } DvergrWorkersPlugin.Log.LogInfo((object)("Tree farmer fetch failed: " + base.Logistics.Reason)); _failedTypes.Add(plantType); _batchSpots.Clear(); } } return false; } private bool Fetch(float dt) { switch (base.Logistics.Step(dt)) { case LogisticsResult.Moving: return true; case LogisticsResult.Failed: DvergrWorkersPlugin.Log.LogInfo((object)("Tree farmer fetch failed: " + base.Logistics.Reason)); if (_batchType != null) { _failedTypes