diff --git a/Assemblies/CombatExtended.dll b/Assemblies/CombatExtended.dll index 19317a8aa7..662c0fd261 100644 Binary files a/Assemblies/CombatExtended.dll and b/Assemblies/CombatExtended.dll differ diff --git a/Languages/English/Keyed/FloatMenu.xml b/Languages/English/Keyed/FloatMenu.xml new file mode 100644 index 0000000000..8060d161a6 --- /dev/null +++ b/Languages/English/Keyed/FloatMenu.xml @@ -0,0 +1,8 @@ + + + no viable ammo found nearby + turret magazine is fully loaded + turret belongs to a hostile faction + already reloading turret + turret is on fire + \ No newline at end of file diff --git a/Patches/Core/ThinkTreeDefs/Humanlike.xml b/Patches/Core/ThinkTreeDefs/Humanlike.xml index 7ea9cff210..eadd047edd 100644 --- a/Patches/Core/ThinkTreeDefs/Humanlike.xml +++ b/Patches/Core/ThinkTreeDefs/Humanlike.xml @@ -42,6 +42,15 @@ + + + Defs/DutyDef[defName="Defend"]/thinkNode/subNodes + +
  • + + Prepend + + + Things/Building/Mech/MechDropBeacon Graphic_Single (1,1) @@ -36,9 +36,9 @@ DormantCompInactive
  • -
  • +
  • True -
  • + diff --git a/Source/CombatExtended/CombatExtended.csproj b/Source/CombatExtended/CombatExtended.csproj index 9d7f258f53..773c37c7e1 100644 --- a/Source/CombatExtended/CombatExtended.csproj +++ b/Source/CombatExtended/CombatExtended.csproj @@ -409,7 +409,6 @@ - @@ -419,16 +418,19 @@ + + + diff --git a/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs b/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs index bb7e855134..2a5d34c230 100644 --- a/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs +++ b/Source/CombatExtended/CombatExtended/Comps/CompAmmoUser.cs @@ -68,6 +68,7 @@ public Pawn Wielder return CompEquippable.PrimaryVerb.CasterPawn; } } + public bool IsEquippedGun => Wielder != null; public Pawn Holder { get @@ -110,7 +111,7 @@ public bool HasAmmo return CompInventory != null && CompInventory.ammoList.Any(x => Props.ammoSet.ammoTypes.Any(a => a.ammo == x.def)); } } - public bool HasMagazine { get { return Props.magazineSize > 0; } } + public bool HasMagazine => Props.magazineSize > 0; public AmmoDef CurrentAmmo { get @@ -118,6 +119,20 @@ public AmmoDef CurrentAmmo return UseAmmo ? currentAmmoInt : null; } } + + public bool EmptyMagazine => HasMagazine && CurMagCount == 0; + public int MissingToFullMagazine + { + get + { + if (!HasMagazine) { return 0; } + if (SelectedAmmo == CurrentAmmo) { return Props.magazineSize - CurMagCount; } + return Props.magazineSize; + } + } + + public bool FullMagazine => HasMagazine && SelectedAmmo == CurrentAmmo && CurMagCount >= Props.magazineSize; + public ThingDef CurAmmoProjectile => Props.ammoSet?.ammoTypes?.FirstOrDefault(x => x.ammo == CurrentAmmo)?.projectile ?? parent.def.Verbs.FirstOrDefault().defaultProjectile; public CompInventory CompInventory { @@ -130,7 +145,7 @@ private IntVec3 Position { get { - if (Wielder != null) return Wielder.Position; + if (IsEquippedGun) return Wielder.Position; else if (turret != null) return turret.Position; else if (Holder != null) return Holder.Position; else return parent.Position; @@ -171,7 +186,7 @@ public override void Initialize(CompProperties vprops) { base.Initialize(vprops); - //spawnUnloaded checks have all been moved to methods calling ResetAmmoCount. + //spawnUnloaded checks have all been moved to methods calling ResetAmmoCount. //curMagCountInt = Props.spawnUnloaded && UseAmmo ? 0 : Props.magazineSize; // Initialize ammo with default if none is set @@ -244,7 +259,7 @@ public bool TryReduceAmmoCount(int ammoConsumedPerShot = 1) { ammoConsumedPerShot = (ammoConsumedPerShot > 0) ? ammoConsumedPerShot : 1; - if (Wielder == null && turret == null) + if (!IsEquippedGun && turret == null) { Log.Error(parent.ToString() + " tried reducing its ammo count without a wielder"); } @@ -307,7 +322,7 @@ public void TryStartReload() } return; } - if (Wielder == null && turret == null) + if (!IsEquippedGun && turret == null) return; // secondary branch for if we ended up being called up by a turret somehow... @@ -326,7 +341,7 @@ public void TryStartReload() TryUnload(); // Check for ammo - if (Wielder != null && !HasAmmo) + if (IsEquippedGun && !HasAmmo) { DoOutOfAmmoAction(); return; @@ -340,7 +355,7 @@ public void TryStartReload() } // Issue reload job - if (Wielder != null) + if (IsEquippedGun) { Job reloadJob = TryMakeReloadJob(); if (reloadJob == null) @@ -427,7 +442,7 @@ private void DoOutOfAmmoAction() { MoteMaker.ThrowText(Position.ToVector3Shifted(), Find.CurrentMap, "CE_OutOfAmmo".Translate() + "!"); } - if (Wielder != null && CompInventory != null && (Wielder.CurJob == null || Wielder.CurJob.def != JobDefOf.Hunt)) CompInventory.SwitchToNextViableWeapon(); + if (IsEquippedGun && CompInventory != null && (Wielder.CurJob == null || Wielder.CurJob.def != JobDefOf.Hunt)) CompInventory.SwitchToNextViableWeapon(); } public void LoadAmmo(Thing ammo = null) @@ -552,10 +567,10 @@ public override IEnumerable CompGetGizmosExtra() GizmoAmmoStatus ammoStatusGizmo = new GizmoAmmoStatus { compAmmo = this }; yield return ammoStatusGizmo; - if ((Wielder != null && Wielder.Faction == Faction.OfPlayer) || (turret != null && turret.Faction == Faction.OfPlayer && (turret.MannableComp != null || UseAmmo))) + if ((IsEquippedGun && Wielder.Faction == Faction.OfPlayer) || (turret != null && turret.Faction == Faction.OfPlayer && (turret.MannableComp != null || UseAmmo))) { Action action = null; - if (Wielder != null) action = TryStartReload; + if (IsEquippedGun) action = TryStartReload; else if (turret?.MannableComp != null) action = turret.TryForceReload; // Check for teaching opportunities diff --git a/Source/CombatExtended/CombatExtended/GenClosestAmmo.cs b/Source/CombatExtended/CombatExtended/GenClosestAmmo.cs deleted file mode 100644 index a2e5e440bd..0000000000 --- a/Source/CombatExtended/CombatExtended/GenClosestAmmo.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using RimWorld; -using Verse; -using Verse.AI; -using UnityEngine; - -namespace CombatExtended -{ - public static class GenClosestAmmo - { - public const int pawnsPerTurret = 10; - public const float ammoSearchRadius = 40f; - public static Dictionary latestAmmoUpdate = new Dictionary(); - - //Check for any AmmoThing having SPAWNED (not for AmmoThing having been destroyed, as this cannot resolve issues) - public static Dictionary> listeners = new Dictionary>(); - - public static Thing ClosestAmmoReachable(IntVec3 root, Map map, CompAmmoUser user, TraverseParms traverseParams, PathEndMode peMode = PathEndMode.ClosestTouch, float maxDistance = 9999f, Predicate validator = null, IEnumerable customGlobalSearchSet = null, int searchRegionsMin = 0, int searchRegionsMax = -1, bool forceAllowGlobalSearch = false, RegionType traversableRegionTypes = RegionType.Set_Passable, bool ignoreEntirelyForbiddenRegions = false) - { - //selectedAmmo - //currentAmmo - //others - - if (user == null) - { - Log.ErrorOnce("ClosestAmmoReachable with null CompAmmoUser", 724492); - return null; - } - - if (user.Props.ammoSet?.ammoTypes.NullOrEmpty() ?? true) - return null; - - bool flag = searchRegionsMax < 0 | forceAllowGlobalSearch; - if (!flag && customGlobalSearchSet != null) - { - Log.ErrorOnce("searchRegionsMax >= 0 && customGlobalSearchSet != null && !forceAllowGlobalSearch. customGlobalSearchSet will never be used.", 6568764, false); - } - - #region EarlyOutSearch-like - var findAny = user.Props.ammoSet?.ammoTypes?.Any(x => map.listerThings.ThingsOfDef(x.ammo).Any()) ?? false; - - if (!findAny) - return null; - - var mDsq = maxDistance * maxDistance; - if (maxDistance != 9999f) - { - var mDsqMax = Math.Max( - Math.Max(root.DistanceToSquared(IntVec3.Zero), root.DistanceToSquared(map.Size)), - Math.Max(root.DistanceToSquared(new IntVec3(0, 0, map.Size.z)), root.DistanceToSquared(new IntVec3(map.Size.x, 0, 0)))); - - //maxDistance smaller than the maximum distance possible within the map -- already exclude some of the ammo - if (mDsq < mDsqMax) - { - if (!user.Props.ammoSet.ammoTypes.Any(x => map.listerThings.ThingsOfDef(x.ammo).Any(y => y.Position.DistanceToSquared(root) <= mDsq))) - return null; - } - else - if (!user.Props.ammoSet.ammoTypes.Any(x => map.listerThings.ThingsOfDef(x.ammo).Any())) - return null; - - } - #endregion - - #region Preparing list to search through - List thingList = new List(); - - foreach (var ammo in user.Props.ammoSet.ammoTypes.Select(x => x.ammo)) - { - thingList.AddRange(map.listerThings.ThingsOfDef(ammo).Where(y => y.Position.DistanceToSquared(root) <= mDsq - && (!(y as AmmoThing)?.IsCookingOff ?? true) - && !y.IsBurning())); - } - #endregion - - Func priorityGetter = (x => { if (x.def == user.SelectedAmmo) return 3f; else if (x.def == user.CurrentAmmo) return 2f; else return 1f; }); - - int num = (searchRegionsMax > 0) ? searchRegionsMax : 30; - var thing = RegionwiseBFSWorker(root, map, thingList, peMode, traverseParams, validator, priorityGetter, searchRegionsMin, num, mDsq, out int num2, traversableRegionTypes, ignoreEntirelyForbiddenRegions); - var flag2 = (thing == null && num2 < num); - - if ((thing == null & flag) && !flag2) - { - if (traversableRegionTypes != RegionType.Set_Passable) - { - Log.ErrorOnce("ClosestAmmoReachable had to do a global search, but traversableRegionTypes is not set to passable only. It's not supported, because Reachability is based on passable regions only.", 14345767, false); - } - - Predicate validator2 = (Thing t) => map.reachability.CanReach(root, t, peMode, traverseParams) && (validator == null || validator(t)); - thing = GenClosest.ClosestThing_Global(root, thingList, maxDistance, validator2, null); - } - return thing; - } - - public static Thing RegionwiseBFSWorker(IntVec3 root, Map map, List thingList, PathEndMode peMode, TraverseParms traverseParams, Predicate validator, Func priorityGetter, int minRegions, int maxRegions, float mDsq, out int regionsSeen, RegionType traversableRegionTypes = RegionType.Set_Passable, bool ignoreEntirelyForbiddenRegions = false) - { - regionsSeen = 0; - if (traverseParams.mode == TraverseMode.PassAllDestroyableThings) - { - Log.Error("CombatExtended :: RegionwiseBFSWorker with traverseParams.mode PassAllDestroyableThings. Use ClosestThingGlobal.", false); - return null; - } - if (traverseParams.mode == TraverseMode.PassAllDestroyableThingsNotWater) - { - Log.Error("CombatExtended :: RegionwiseBFSWorker with traverseParams.mode PassAllDestroyableThingsNotWater. Use ClosestThingGlobal.", false); - return null; - } - Region region = root.GetRegion(map, traversableRegionTypes); - if (region == null) - { - return null; - } - - RegionEntryPredicate entryCondition = (Region from, Region to) => to.Allows(traverseParams, false) && (mDsq > 25000000f || to.extentsClose.ClosestDistSquaredTo(root) < mDsq); - Thing closestThing = null; - int regionsSeenScan2 = 0; - float closestDistSquared = 9999999f; - float bestPrio = -1; - RegionProcessor regionProcessor = delegate (Region r) - { - int regionsSeenScan; - if (RegionTraverser.ShouldCountRegion(r)) - { - regionsSeenScan = regionsSeenScan2; - regionsSeenScan2++; - } - if (!r.IsDoorway && !r.Allows(traverseParams, true)) - { - return false; - } - if (!ignoreEntirelyForbiddenRegions || !r.IsForbiddenEntirely(traverseParams.pawn)) - { - foreach (var item in thingList) - { - if (ReachabilityWithinRegion.ThingFromRegionListerReachable(item, r, peMode, traverseParams.pawn)) - { - float num = (priorityGetter != null) ? priorityGetter(item) : 0f; - if (num >= bestPrio) - { - float num2 = (float)(item.Position - root).LengthHorizontalSquared; - if ((num > bestPrio || num2 < closestDistSquared) && num2 < mDsq && (validator == null || validator(item))) - { - closestThing = item; - closestDistSquared = num2; - bestPrio = num; - } - } - } - } - } - return regionsSeenScan2 >= minRegions && closestThing != null; - }; - - RegionTraverser.BreadthFirstTraverse(region, entryCondition, regionProcessor, maxRegions, traversableRegionTypes); - regionsSeen = regionsSeenScan2; - return closestThing; - } - } -} diff --git a/Source/CombatExtended/CombatExtended/Jobs/JobDriver_ReloadTurret.cs b/Source/CombatExtended/CombatExtended/Jobs/JobDriver_ReloadTurret.cs index e237ca9d85..e8c87aa431 100644 --- a/Source/CombatExtended/CombatExtended/Jobs/JobDriver_ReloadTurret.cs +++ b/Source/CombatExtended/CombatExtended/Jobs/JobDriver_ReloadTurret.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using CombatExtended.CombatExtended.LoggerUtils; using RimWorld; using UnityEngine; using Verse; @@ -33,7 +34,22 @@ private CompAmmoUser compReloader public override bool TryMakePreToilReservations(bool errorOnFailed) { - return pawn.Reserve(TargetA, job) && (ammo == null || pawn.Reserve(TargetB, job, Mathf.Max(1, TargetThingB.stackCount - job.count), job.count)); + if (!pawn.Reserve(TargetA, job)) + { + CELogger.Message("Combat Extended: Could not reserve turret for reloading job."); + return false; + } + if (ammo == null) + { + CELogger.Message("Combat Extended: Ammo is null"); + return false; + } + if (!pawn.Reserve(TargetB, job, Mathf.Max(1, TargetThingB.stackCount - job.count), job.count)) { + CELogger.Message("Combat Extended: Could not reserve " + Mathf.Max(1, TargetThingB.stackCount - job.count) + " of ammo."); + return false; + } + CELogger.Message("Combat Extended: Managed to reserve everything successfully."); + return true; } public override string GetReport() diff --git a/Source/CombatExtended/CombatExtended/Jobs/JobGiver_DefenderReloadTurret.cs b/Source/CombatExtended/CombatExtended/Jobs/JobGiver_DefenderReloadTurret.cs new file mode 100644 index 0000000000..8e5309d34b --- /dev/null +++ b/Source/CombatExtended/CombatExtended/Jobs/JobGiver_DefenderReloadTurret.cs @@ -0,0 +1,56 @@ +using CombatExtended.CombatExtended.Jobs.Utils; +using CombatExtended.CombatExtended.LoggerUtils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse; +using Verse.AI; + +namespace CombatExtended +{ + public class JobGiver_DefenderReloadTurret : ThinkNode_JobGiver + { + /// + /// How low can ammo get before we want to reload the turret? + /// Set arbitrarily, balance if needed. + /// + private const float ammoReloadThreshold = .5f; + protected override Job TryGiveJob(Pawn pawn) + { + var turret = TryFindTurretWhichNeedsReloading(pawn); + if (turret == null) + { + return null; // signals ThinkResult.NoJob. + } + return JobGiverUtils_Reload.MakeReloadJob(pawn, turret); + } + + private Building_TurretGunCE TryFindTurretWhichNeedsReloading(Pawn pawn) + { + bool _isTurretThatNeedsReloadingNow(Thing t) + { + var turret = t as Building_TurretGunCE; + if (turret == null) { return false; } + if (!JobGiverUtils_Reload.CanReload(pawn, turret, forced: false, emergency: true)) { return false; } + + return turret.CompAmmo.CurMagCount <= turret.CompAmmo.Props.magazineSize / ammoReloadThreshold; + }; + Thing hopefullyTurret = GenClosest.ClosestThingReachable(pawn.Position, + pawn.Map, + ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial), + PathEndMode.Touch, + TraverseParms.For(pawn), + 100f, + _isTurretThatNeedsReloadingNow); + + var actuallyTurret = hopefullyTurret as Building_TurretGunCE; + if (actuallyTurret == null) { return null; } + return actuallyTurret; + + } + + + } +} diff --git a/Source/CombatExtended/CombatExtended/Jobs/Utils/JobGiverUtils_Reload.cs b/Source/CombatExtended/CombatExtended/Jobs/Utils/JobGiverUtils_Reload.cs new file mode 100644 index 0000000000..34d796e88c --- /dev/null +++ b/Source/CombatExtended/CombatExtended/Jobs/Utils/JobGiverUtils_Reload.cs @@ -0,0 +1,200 @@ +using CombatExtended.CombatExtended.LoggerUtils; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Noise; + +namespace CombatExtended.CombatExtended.Jobs.Utils +{ + class JobGiverUtils_Reload + { + /// + /// The maximum allowed pathing cost to reach potential ammo. 2 ingame hours. + /// This is arbitrarily set. If you think this is too high or too low, feel free to change. + /// + private const float MaxPathCost = 2f * 60f * GenDate.TicksPerHour; + /// + /// Magic number. I took it from the now deprecated GenClosestAmmo class. Not sure why we would want 10 reservations, but there it is. + /// + private const int MagicMaxPawns = 10; + + public static Job MakeReloadJob(Pawn pawn, Building_TurretGunCE turret) + { + var compAmmo = turret.CompAmmo; + var amountNeeded = turret.CompAmmo.MissingToFullMagazine; + if (compAmmo == null) + { + CELogger.Error("Tried to create a reload job on a thing that's not reloadable."); + return null; + } + + var ammo = FindBestAmmo(pawn, turret); + if (ammo == null) + { + CELogger.Error($"{pawn} tried to create a reload job without ammo. This should have been checked earlier."); + return null; + } + CELogger.Message($"Making a reload job for {pawn}, {turret} and {ammo}"); + + Job job = JobMaker.MakeJob(CE_JobDefOf.ReloadTurret, turret, ammo); + job.count = Mathf.Min(ammo.stackCount, turret.CompAmmo.MissingToFullMagazine); + return job; + } + + public static Job MakeReloadJobNoAmmo(Building_TurretGunCE turret) + { + var compAmmo = turret.TryGetComp(); + if (compAmmo == null) + { + CELogger.Error("Tried to create a reload job on a thing that's not reloadable."); + return null; + } + + return JobMaker.MakeJob(CE_JobDefOf.ReloadTurret, turret, null); + } + + public static bool CanReload(Pawn pawn, Thing hopefullyTurret, bool forced = false, bool emergency = false) + { + if (pawn == null || hopefullyTurret == null) + { + CELogger.Error($"{pawn?.ToString() ?? "null pawn"} could not reload {hopefullyTurret?.ToString() ?? "null thing"} one of the two was null."); + return false; + } + if (!(hopefullyTurret is Building_TurretGunCE)) + { + CELogger.Error($"{pawn} could not reload {hopefullyTurret} because {hopefullyTurret} is not a Combat Extended Turret. If you are a modder, make sure to use {nameof(CombatExtended)}.{nameof(Building_TurretGunCE)} for your turret's compClass."); + return false; + } + var turret = hopefullyTurret as Building_TurretGunCE; + var compAmmo = turret.CompAmmo; + + if (compAmmo == null) + { + CELogger.Error($"{pawn} could not reload {turret} because turret has no {nameof(CompAmmoUser)}."); + return false; + } + if (turret.isReloading) + { + CELogger.Message($"{pawn} could not reload {turret} because turret is already reloading."); + JobFailReason.Is("CE_TurretAlreadyReloading".Translate()); + return false; + } + if (turret.IsBurning() && !emergency) + { + CELogger.Message($"{pawn} could not reload {turret} because turret is on fire."); + JobFailReason.Is("CE_TurretIsBurning".Translate()); + } + if (compAmmo.FullMagazine) + { + CELogger.Message($"{pawn} could not reload {turret} because it is full of ammo."); + JobFailReason.Is("CE_TurretFull".Translate()); + return false; + } + if (turret.IsForbidden(pawn) || !pawn.CanReserve(turret, 1, -1, null, forced)) + { + CELogger.Message($"{pawn} could not reload {turret} because it is forbidden or otherwise busy."); + return false; + } + if (turret.Faction != pawn.Faction && (turret.Faction != null && pawn.Faction != null && turret.Faction.RelationKindWith(pawn.Faction) != FactionRelationKind.Ally)) + { + CELogger.Message($"{pawn} could not reload {turret} because the turret is hostile to them."); + JobFailReason.Is("CE_TurretNonAllied".Translate()); + return false; + } + if ((turret.MannableComp?.ManningPawn != pawn) && !pawn.CanReserveAndReach(turret, PathEndMode.ClosestTouch, forced ? Danger.Deadly : pawn.NormalMaxDanger(), MagicMaxPawns)) + { + CELogger.Message($"{pawn} could not reload {turret} because turret is manned (or was recently manned) by someone else."); + return false; + } + if (FindBestAmmo(pawn, turret) == null) + { + JobFailReason.Is("CE_NoAmmoAvailable".Translate()); + return false; + } + return true; + } + + private static Thing FindBestAmmo(Pawn pawn, Building_TurretGunCE reloadable) + { + //ThingFilter filter = refuelable.TryGetComp().Props.fuelFilter; + var requestedAmmo = reloadable.CompAmmo.SelectedAmmo; + + bool validator(Thing potentialAmmo) + { + if (potentialAmmo.IsForbidden(pawn) || !pawn.CanReserve(potentialAmmo)) + { + return false; + } + return GetPathCost(pawn, potentialAmmo) <= MaxPathCost; + } + + return GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, ThingRequest.ForDef(requestedAmmo), PathEndMode.ClosestTouch, TraverseParms.For(pawn), 9999f, validator); + } + + /// + /// This method is a direct copy/paste of the private FindAllFuel method. + /// + /// Finds all relevant ammo in order of distance. + /// + /// + /// + /// + private static List FindAllAmmo(Pawn pawn, Building_TurretGunCE reloadable) + { + int quantity = reloadable.CompAmmo.MissingToFullMagazine; + var ammoKind = reloadable.CompAmmo.SelectedAmmo; + bool validator(Thing potentialAmmo) + { + if (potentialAmmo.IsForbidden(pawn) || !pawn.CanReserve(potentialAmmo)) + { + return false; + } + return GetPathCost(pawn, potentialAmmo) <= MaxPathCost; + } + Region region = reloadable.Position.GetRegion(pawn.Map); + TraverseParms traverseParams = TraverseParms.For(pawn); + bool entryCondition(Region from, Region r) => r.Allows(traverseParams, isDestination: false); + var chosenThings = new List(); + int accumulatedQuantity = 0; + bool regionProcessor(Region r) + { + List list = r.ListerThings.ThingsMatching(ThingRequest.ForDef(ammoKind)); + foreach (var thing in list) + { + if (validator(thing) && !chosenThings.Contains(thing) && ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch, pawn)) + { + chosenThings.Add(thing); + accumulatedQuantity += thing.stackCount; + if (accumulatedQuantity >= quantity) + { + return true; + } + } + } + return false; + } + RegionTraverser.BreadthFirstTraverse(region, entryCondition, regionProcessor, 99999); + if (accumulatedQuantity >= quantity) + { + return chosenThings; + } + return null; + } + + private static float GetPathCost(Pawn pawn, Thing thing) + { + var cell = thing.Position; + var pos = pawn.Position; + var traverseParams = TraverseParms.For(pawn, Danger.Deadly, TraverseMode.PassDoors, false); + + using (PawnPath path = pawn.Map.pathFinder.FindPath(pos, cell, traverseParams, PathEndMode.Touch)) { + return path.TotalCost; + } + } + } + +} diff --git a/Source/CombatExtended/CombatExtended/Jobs/WorkGiver_ReloadTurret.cs b/Source/CombatExtended/CombatExtended/Jobs/WorkGiver_ReloadTurret.cs index 481f742423..803a162d0f 100644 --- a/Source/CombatExtended/CombatExtended/Jobs/WorkGiver_ReloadTurret.cs +++ b/Source/CombatExtended/CombatExtended/Jobs/WorkGiver_ReloadTurret.cs @@ -7,67 +7,21 @@ using Verse; using Verse.AI; using UnityEngine; +using CombatExtended.CombatExtended.LoggerUtils; +using CombatExtended.CombatExtended.Jobs.Utils; namespace CombatExtended { public class WorkGiver_ReloadTurret : WorkGiver_Scanner { - public override ThingRequest PotentialWorkThingRequest - { - get - { - return ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial); - } - } - - private bool Checks(Pawn pawn, Building_TurretGunCE turret, bool forced) - { - //WorkTagIsDisabled check inherent to WorkGiver_Scanner - //MissingRequiredCapacity check inherent to WorkGiver_Scanner - if (turret.isReloading) - { - //Log.Message(pawn.ThingID + " failed " + turret.ThingID + ": turret is already reloading"); - return false; //Turret is already reloading - } - if (turret.FullMagazine) - { - // Log.Message(pawn.ThingID + " failed " + turret.ThingID + ": turret doesn't need reload"); - return false; //Turret doesn't need reload - } - //if (!forced && !turret.AllowAutomaticReload) return false; //Turret doesn't need auto-reload and isn't forced to reload - if (turret.IsBurning()) - { - // Log.Message(pawn.ThingID + " failed " + turret.ThingID + ": turret on fire"); - return false; //Turret on fire - } - if (turret.Faction != pawn.Faction && (turret.Faction != null && pawn.Faction != null && turret.Faction.RelationKindWith(pawn.Faction) != FactionRelationKind.Ally)) - { - // Log.Message(pawn.ThingID + " failed " + turret.ThingID + ": non-allied turret"); - return false; //Allies reload turrets - } - if ((turret.MannableComp?.ManningPawn != pawn) && !pawn.CanReserveAndReach(turret, PathEndMode.ClosestTouch, forced ? Danger.Deadly : pawn.NormalMaxDanger(), GenClosestAmmo.pawnsPerTurret)) - { - //Log.Message(pawn.ThingID + " failed " + turret.ThingID + ": turret unreachable"); - return false; //Pawn cannot reach turret - } - - return true; - } + public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial); - public override float GetPriority(Pawn pawn, TargetInfo t) - { - return GetThingPriority(pawn, t.Thing); - } + public override float GetPriority(Pawn pawn, TargetInfo t) => GetThingPriority(pawn, t.Thing); private float GetThingPriority(Pawn pawn, Thing t, bool forced = false) { + CELogger.Message($"pawn: {pawn}. t: {t}. forced: {forced}"); Building_TurretGunCE turret = t as Building_TurretGunCE; - if (pawn == null) - { - //Log.Message("Checking " + turret.ThingID + ", found pawn == null"); - return 0f; - } - if (turret == null || turret.IsForbidden(pawn) || !Checks(pawn, turret, forced)) return 0f; if (!turret.Active) return 1f; if (turret.EmptyMagazine) return 9f; @@ -77,7 +31,28 @@ private float GetThingPriority(Pawn pawn, Thing t, bool forced = false) public override bool ShouldSkip(Pawn pawn, bool forced = false) { - return !forced && pawn.CurJob != null && pawn.CurJobDef != JobDefOf.ManTurret && (pawn.CurJob.playerForced || pawn.CurJobDef == CE_JobDefOf.ReloadTurret || pawn.CurJobDef == CE_JobDefOf.ReloadWeapon); + if (forced) + { + CELogger.Message("Job is forced. Not skipping."); + return false; + } + if (pawn.CurJob == null) + { + CELogger.Message($"Pawn {pawn.ThingID} has no job. Not skipping."); + return false; + } + if (pawn.CurJobDef == JobDefOf.ManTurret) + { + CELogger.Message($"Pawn {pawn.ThingID}'s current job is {nameof(JobDefOf.ManTurret)}. Not skipping."); + return false; + } + if (pawn.CurJob.playerForced) + { + CELogger.Message($"Pawn {pawn.ThingID}'s current job is forced by the player. Skipping."); + return true; + } + CELogger.Message($"Pawn {pawn.ThingID}'s current job is {pawn.CurJobDef.reportString}. Skipping"); + return (pawn.CurJobDef == CE_JobDefOf.ReloadTurret || pawn.CurJobDef == CE_JobDefOf.ReloadWeapon); } /// @@ -87,53 +62,23 @@ public override bool ShouldSkip(Pawn pawn, bool forced = false) /// Can be non-turret /// Generally not true /// - /*public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false) - { - Building_TurretGunCE turret = t as Building_TurretGunCE; - if (turret == null || !Checks(pawn, turret, forced)) return false; - - if (!turret.CompAmmo.UseAmmo) - return true; - - turret.UpdateNearbyAmmo(); - - Thing ammo = turret.nearestViableAmmo; - - int amountNeeded = turret.CompAmmo.Props.magazineSize; - if (turret.CompAmmo.CurrentAmmo == turret.CompAmmo.SelectedAmmo) amountNeeded -= turret.CompAmmo.CurMagCount; - - if (ammo == null || ammo.IsForbidden(pawn) || !pawn.CanReserveAndReach(new LocalTargetInfo(ammo), PathEndMode.ClosestTouch, pawn.NormalMaxDanger(), Mathf.Max(1, ammo.stackCount - amountNeeded), amountNeeded, null, forced)) - { - ammo = turret.InventoryAmmo(pawn.TryGetComp()); - } - - return ammo != null; - }*/ - + public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false) { + if (!(t is Building_TurretGunCE)) { + return false; + } var priority = GetThingPriority(pawn, t, forced); - if (priority == 0f) return false; + CELogger.Message($"Priority check completed. Got {priority}"); Building_TurretGunCE turret = t as Building_TurretGunCE; + CELogger.Message($"Turret uses ammo? {turret.CompAmmo.UseAmmo}"); if (!turret.CompAmmo.UseAmmo) return true; - int amountNeeded = turret.CompAmmo.Props.magazineSize; - if (turret.CompAmmo.CurrentAmmo == turret.CompAmmo.SelectedAmmo) amountNeeded -= turret.CompAmmo.CurMagCount; - - turret.UpdateNearbyAmmo(); - Thing ammo = PawnClosestAmmo(pawn, turret, out bool _, forced); - - if (ammo == null) - return false; - - // Update selected ammo if necessary - if (ammo.def != turret.CompAmmo.SelectedAmmo - && priority < 9f && ammo.stackCount < amountNeeded) //New option can fill magazine completely (and is otherwise closer) - return false; + CELogger.Message($"Total magazine size: {turret.CompAmmo.Props.magazineSize}. Needed: {turret.CompAmmo.MissingToFullMagazine}"); - return true; + return JobGiverUtils_Reload.CanReload(pawn, turret, forced); } //Checks before called (ONLY when in SCANNER): @@ -153,123 +98,16 @@ public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false) /// public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false) { - var priority = GetThingPriority(pawn, t, forced); - if (priority == 0f) return null; - //Do not check for NeedsReload etc. -- if forced, treat as if NeedsReload && AllowAutomaticReload Building_TurretGunCE turret = t as Building_TurretGunCE; if (!turret.CompAmmo.UseAmmo) - return new Job(CE_JobDefOf.ReloadTurret, t, null); - - int amountNeeded = turret.CompAmmo.Props.magazineSize; - if (turret.CompAmmo.CurrentAmmo == turret.CompAmmo.SelectedAmmo) amountNeeded -= turret.CompAmmo.CurMagCount; - - turret.UpdateNearbyAmmo(); - - var ammo = PawnClosestAmmo(pawn, turret, out bool fromInventory, forced); - - if (ammo == null) - return null; - - // Update selected ammo if necessary - if (ammo.def != turret.CompAmmo.SelectedAmmo) //New option can fill magazine completely (and is otherwise closer) - { - if ((priority >= 9f || ammo.stackCount >= amountNeeded)) - turret.CompAmmo.SelectedAmmo = ammo.def as AmmoDef; - else - return null; - } - - if (fromInventory && !pawn.TryGetComp().container.TryDrop(ammo, pawn.Position, pawn.Map, ThingPlaceMode.Direct, Mathf.Min(ammo.stackCount, amountNeeded), out ammo)) - { - Log.ErrorOnce("Found optimal ammo (" + ammo.LabelCap + "), but could not drop from " + pawn.LabelCap, 8164528); - return null; - } - - return new Job(CE_JobDefOf.ReloadTurret, t, ammo) { count = Mathf.Min(amountNeeded, ammo.stackCount) }; - } - - private Thing PawnClosestAmmo(Pawn pawn, Building_TurretGunCE turret, out bool fromInventory, bool forced = false) - { - var testAmmo = turret.nearestViableAmmo; - - int amount = turret.CompAmmo.Props.magazineSize; - if (turret.CompAmmo.CurrentAmmo == turret.CompAmmo.SelectedAmmo) amount -= turret.CompAmmo.CurMagCount; - - if (testAmmo == null - || testAmmo.IsForbidden(pawn) - || !pawn.CanReserveAndReach(new LocalTargetInfo(testAmmo), PathEndMode.ClosestTouch, forced ? Danger.Deadly : pawn.NormalMaxDanger(), - Mathf.Max(1, testAmmo.stackCount - amount), Mathf.Min(testAmmo.stackCount, amount), null, forced)) - { - testAmmo = turret.InventoryAmmo(pawn?.TryGetComp()); - fromInventory = true; - } - else - fromInventory = false; + return JobGiverUtils_Reload.MakeReloadJobNoAmmo(turret); - return testAmmo; - } - - public Pawn BestNonJobUser(IEnumerable pawns, Building_TurretGunCE turret, out bool fromInventory, bool forced = false) - { - fromInventory = false; - - if (!pawns.Any()) return null; - - //Cut a significant portion of pawns - pawns = pawns.Where(p => !ShouldSkip(p) && !p.Downed && !p.Dead && p.Spawned && (p.mindState?.Active ?? true) - && (!p.mindState?.mentalStateHandler?.InMentalState ?? true) && !turret.IsForbidden(p) - && (turret.Faction == p.Faction || turret.Faction?.RelationKindWith(p.Faction) == FactionRelationKind.Ally) - && p.CanReserveAndReach(new LocalTargetInfo(turret), PathEndMode.InteractionCell, forced ? Danger.Deadly : p.NormalMaxDanger(), GenClosestAmmo.pawnsPerTurret, -1, null, forced)); - - //No pawns remaining => quit - if (!pawns.Any()) - return null; - - //If no ammo is used, minimize distance - if (!turret.CompAmmo?.UseAmmo ?? true) - return pawns.MinBy(x => x.Position.DistanceTo(turret.Position)); - - //ELSE: ammo is used, pawns remain - turret.UpdateNearbyAmmo(forced); - var hasNearbyAmmo = turret.nearestViableAmmo != null; - - int amount = turret.CompAmmo.Props.magazineSize; - if (turret.CompAmmo.CurrentAmmo == turret.CompAmmo.SelectedAmmo) amount -= turret.CompAmmo.CurMagCount; - - if (hasNearbyAmmo) - pawns = pawns.OrderBy(x => x.Position.DistanceTo(turret.Position)); - - var bestNonInventoryDistance = 9999f; - Pawn bestPawn = null; - var minimumDistance = hasNearbyAmmo ? turret.Position.DistanceTo(turret.nearestViableAmmo.Position) : 0f; - - //Sort through ASCENDING DISTANCE TO TURRET -- e.g, inventory ammo is preferred - foreach (var p in pawns) - { - if (PawnClosestAmmo(p, turret, out fromInventory, forced) == null) - continue; - - if (fromInventory) - { - if (p.Position.DistanceTo(turret.Position) < bestNonInventoryDistance + minimumDistance) - return p; - } - else if (hasNearbyAmmo) - { - var dist = p.Position.DistanceTo(turret.nearestViableAmmo.Position); - - if (dist < bestNonInventoryDistance) - { - bestNonInventoryDistance = dist; - bestPawn = p; - } - } - } + // NOTE: The removal of the code that used to be here disables reloading turrets directly from one's inventory. + // The player would need to drop the ammo the pawn is holding first. - fromInventory = false; - return bestPawn; + return JobGiverUtils_Reload.MakeReloadJob(pawn, turret); } } } \ No newline at end of file diff --git a/Source/CombatExtended/CombatExtended/LoggerUtils/CELogger.cs b/Source/CombatExtended/CombatExtended/LoggerUtils/CELogger.cs new file mode 100644 index 0000000000..2990ea58ed --- /dev/null +++ b/Source/CombatExtended/CombatExtended/LoggerUtils/CELogger.cs @@ -0,0 +1,35 @@ +using Verse; + +namespace CombatExtended.CombatExtended.LoggerUtils +{ + class CELogger + { + /// + /// Am I in debug mode? Set this to true and recompile for more log output. + /// TODO: Maybe change this to a toggleable setting if I ever have the time to figure out how <3. + /// + private static readonly bool isInDebugMode = false; + + public static void Message(string message, bool showOutOfDebugMode = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "") + { + if (!isInDebugMode && !showOutOfDebugMode) { return; } + Log.Message($"CE - {memberName}() ({Find.TickManager.TicksGame}) - {message}"); + } + public static void Warn(string message, bool showOutOfDebugMode = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "") + { + if (!isInDebugMode && !showOutOfDebugMode) { return; } + Log.Warning($"CE - {memberName}() ({Find.TickManager.TicksGame}) - {message}"); + } + public static void Error(string message, bool showOutOfDebugMode = true, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "") + { + if (!isInDebugMode && !showOutOfDebugMode) { return; } + Log.Error($"CE - {memberName}() ({Find.TickManager.TicksGame}) - {message}"); + } + + public static void ErrorOnce(string message, bool showOutOfDebugMode = true, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) + { + if (!isInDebugMode && !showOutOfDebugMode) { return; } + Log.ErrorOnce($"CE - {memberName}() ({Find.TickManager.TicksGame}) - {message}", sourceLineNumber); + } + } +} \ No newline at end of file diff --git a/Source/CombatExtended/CombatExtended/Things/AmmoThing.cs b/Source/CombatExtended/CombatExtended/Things/AmmoThing.cs index be86884485..ed6517bebd 100644 --- a/Source/CombatExtended/CombatExtended/Things/AmmoThing.cs +++ b/Source/CombatExtended/CombatExtended/Things/AmmoThing.cs @@ -108,10 +108,6 @@ public override void Tick() Destroy(DestroyMode.KillFinalize); } } - - //Resubscribe ammo - if (numToCookOff <= 0) - RegisterAmmo(); } } @@ -197,14 +193,6 @@ private bool TryLaunchCookOffProjectile() return true; } - public override void SpawnSetup(Map map, bool respawningAfterLoad) - { - base.SpawnSetup(map, respawningAfterLoad); - - //Keep track of newly spawned, non-cookoff AmmoThing - RegisterAmmo(); - } - public override void ExposeData() { base.ExposeData(); @@ -212,39 +200,6 @@ public override void ExposeData() //Such that save-reloading doesn't stop ammo cookoff Scribe_Values.Look(ref numToCookOff, "numToCookOff", 0); } - - public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) - { - base.DeSpawn(mode); - - if (GenClosestAmmo.listeners.ContainsKey(def)) - { - foreach (var listener in GenClosestAmmo.listeners[def]) - { - if (listener.nearestViableAmmo == this) - { - listener.nearestViableAmmo = null; - listener.isSlow = false; - } - } - - if (GenClosestAmmo.latestAmmoUpdate.ContainsKey(def)) - GenClosestAmmo.latestAmmoUpdate[def] = Find.TickManager.TicksGame; - } - else if (GenClosestAmmo.latestAmmoUpdate.ContainsKey(def)) - GenClosestAmmo.latestAmmoUpdate.Clear(); - } - - private void RegisterAmmo() - { - if (numToCookOff <= 0 && !this.IsBurning() && GenClosestAmmo.listeners.ContainsKey(def)) - { - GenClosestAmmo.listeners[def].ForEach(x => x.isSlow = false); - - if (GenClosestAmmo.latestAmmoUpdate.ContainsKey(def)) - GenClosestAmmo.latestAmmoUpdate[def] = Find.TickManager.TicksGame; - } - } #endregion } } diff --git a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs index 112213464f..41aad73f5e 100644 --- a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs +++ b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs @@ -8,6 +8,8 @@ using Verse.AI.Group; using Verse.Sound; using UnityEngine; +using CombatExtended.CombatExtended.LoggerUtils; +using CombatExtended.CombatExtended.Jobs.Utils; namespace CombatExtended { @@ -46,7 +48,7 @@ public class Building_TurretGunCE : Building_Turret private CompChangeableProjectile compChangeable = null; public bool isReloading = false; private int ticksUntilAutoReload = 0; - private int lastSurroundingAmmoCheck = 0; + private int lastSurroundingAmmoCheck = int.MinValue; #endregion @@ -78,9 +80,9 @@ public Thing Gun { this.gunInt = ThingMaker.MakeThing(this.def.building.turretGunDef, null); this.compAmmo = gunInt.TryGetComp(); - + InitGun(); - + // FIXME: Hack to make player-crafted turrets spawn unloaded if (//Map != null && !Map.IsPlayerHome //!Faction.IsPlayer @@ -98,22 +100,22 @@ public ThingDef Projectile { if (CompAmmo != null && CompAmmo.CurrentAmmo != null) { - return CompAmmo.CurAmmoProjectile; + return CompAmmo.CurAmmoProjectile; } if (CompChangeable != null && CompChangeable.Loaded) { - return CompChangeable.Projectile; + return CompChangeable.Projectile; } return this.GunCompEq.PrimaryVerb.verbProps.defaultProjectile; } } public CompChangeableProjectile CompChangeable { - get - { - if (compChangeable == null && Gun != null) compChangeable = Gun.TryGetComp(); - return compChangeable; - } + get + { + if (compChangeable == null && Gun != null) compChangeable = Gun.TryGetComp(); + return compChangeable; + } } public CompAmmoUser CompAmmo { @@ -132,25 +134,21 @@ public CompFireModes CompFireModes } } - public bool EmptyMagazine => Reloadable && CompAmmo.CurMagCount == 0; - public bool FullMagazine => !Reloadable || (CompAmmo.SelectedAmmo == CompAmmo.CurrentAmmo && CompAmmo.CurMagCount == CompAmmo.Props.magazineSize); + public bool EmptyMagazine => CompAmmo?.EmptyMagazine ?? false; + public bool FullMagazine => CompAmmo?.FullMagazine ?? false; public bool AutoReloadableMagazine => AutoReloadableNow && CompAmmo.CurMagCount <= Mathf.CeilToInt(CompAmmo.Props.magazineSize / 6); public bool AutoReloadableNow => (mannableComp == null || (!mannableComp.MannedNow && ticksUntilAutoReload == 0)) && Reloadable; //suppress manned turret auto-reload for a short time after spawning - public bool Reloadable => CompAmmo != null && CompAmmo.HasMagazine; - + public bool Reloadable => CompAmmo?.HasMagazine ?? false; public CompMannable MannableComp => mannableComp; #endregion - #region Constructors - // Core constructor public Building_TurretGunCE() { top = new TurretTop(this); } - #endregion #region Methods - + public override void SpawnSetup(Map map, bool respawningAfterLoad) //Add mannableComp, ticksUntilAutoReload, register to GenClosestAmmo { base.SpawnSetup(map, respawningAfterLoad); @@ -159,6 +157,7 @@ public override void SpawnSetup(Map map, bool respawningAfterLoad) //Add ma mannableComp = GetComp(); if (!respawningAfterLoad) { + CELogger.Message($"top is {top?.ToString() ?? "null"}"); top.SetRotationFromOrientation(); burstCooldownTicksLeft = def.building.turretInitialCooldownTime.SecondsToTicks(); @@ -167,20 +166,20 @@ public override void SpawnSetup(Map map, bool respawningAfterLoad) //Add ma ticksUntilAutoReload = minTicksBeforeAutoReload; } - if (CompAmmo == null || CompAmmo.Props == null || CompAmmo.Props.ammoSet == null || CompAmmo.Props.ammoSet.ammoTypes.NullOrEmpty()) - return; + // if (CompAmmo == null || CompAmmo.Props == null || CompAmmo.Props.ammoSet == null || CompAmmo.Props.ammoSet.ammoTypes.NullOrEmpty()) + // return; - //"Subscribe" turret to GenClosestAmmo - foreach (var ammo in CompAmmo.Props.ammoSet.ammoTypes.Select(x => x.ammo)) - { - if (!GenClosestAmmo.listeners.ContainsKey(ammo)) - GenClosestAmmo.listeners.Add(ammo, new List() { this }); - else - GenClosestAmmo.listeners[ammo].Add(this); + // //"Subscribe" turret to GenClosestAmmo + // foreach (var ammo in CompAmmo.Props.ammoSet.ammoTypes.Select(x => x.ammo)) + // { + // if (!GenClosestAmmo.listeners.ContainsKey(ammo)) + // GenClosestAmmo.listeners.Add(ammo, new List() { this }); + // else + // GenClosestAmmo.listeners[ammo].Add(this); - if (!GenClosestAmmo.latestAmmoUpdate.ContainsKey(ammo)) - GenClosestAmmo.latestAmmoUpdate.Add(ammo, 0); - } + // if (!GenClosestAmmo.latestAmmoUpdate.ContainsKey(ammo)) + // GenClosestAmmo.latestAmmoUpdate.Add(ammo, 0); + // } } //PostMake not added -- MakeGun-like code is run whenever Gun is called @@ -189,15 +188,9 @@ public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) // Added { base.DeSpawn(mode); ResetCurrentTarget(); - - foreach (var ammo in CompAmmo.Props.ammoSet.ammoTypes.Select(x => x.ammo)) - { - if (GenClosestAmmo.listeners.ContainsKey(ammo) && GenClosestAmmo.listeners[ammo].Contains(this)) - GenClosestAmmo.listeners[ammo].Remove(this); - } } - + public override void ExposeData() // Added new variables, removed bool loaded (not used in CE) { base.ExposeData(); @@ -260,14 +253,14 @@ public override void Tick() //Autoreload code and IsReloading check base.Tick(); if (ticksUntilAutoReload > 0) ticksUntilAutoReload--; // Reduce time until we can auto-reload - if (!isReloading && this.IsHashIntervalTick(TicksBetweenAmmoChecks)) + if (!isReloading && this.IsHashIntervalTick(TicksBetweenAmmoChecks) && (MannableComp?.MannedNow ?? false)) { TryOrderReload(); } //This code runs TryOrderReload for manning pawns or for non-humanlike intelligence such as mechs - /*if (this.IsHashIntervalTick(TicksBetweenAmmoChecks) && !isReloading && (MannableComp?.MannedNow ?? false)) - TryOrderReload(CompAmmo?.CurMagCount == 0);*/ + /*if (this.IsHashIntervalTick(TicksBetweenAmmoChecks) && !isReloading && (MannableComp?.MannedNow ?? false)) + TryOrderReload(CompAmmo?.CurMagCount == 0);*/ if (!CanSetForcedTarget && !isReloading && forcedTarget.IsValid && burstCooldownTicksLeft <= 0) { ResetForcedTarget(); @@ -313,14 +306,14 @@ public override void Tick() //Autoreload code and IsReloading check this.ResetCurrentTarget(); } } - + protected void TryStartShootSomething(bool canBeginBurstImmediately) // Added ammo check and use verb warmup time instead of turret's { // Check for ammo first if (!Spawned || (holdFire && CanToggleHoldFire) || (Projectile.projectile.flyOverhead && Map.roofGrid.Roofed(Position)) - //|| !AttackVerb.Available() -- Check replaced by the following: + //|| !AttackVerb.Available() -- Check replaced by the following: || (CompAmmo != null && (isReloading || (mannableComp == null && CompAmmo.CurMagCount <= 0)))) { ResetCurrentTarget(); @@ -357,7 +350,7 @@ protected void TryStartShootSomething(bool canBeginBurstImmediately) // Added } burstWarmupTicksLeft = 1; } - + protected LocalTargetInfo TryFindNewTarget() // Core method { IAttackTargetSearcher attackTargetSearcher = this.TargSearcher(); @@ -420,14 +413,14 @@ private bool IsValidTarget(Thing t) // Projectile flyoverhead check } return true; } - + protected void BeginBurst() // Added handling for ticksUntilAutoReload { ticksUntilAutoReload = minTicksBeforeAutoReload; AttackVerb.TryStartCastOn(CurrentTarget, false, true); OnAttackedTarget(CurrentTarget); } - + protected void BurstComplete() // Added CompAmmo reload check { burstCooldownTicksLeft = BurstCooldownTime().SecondsToTicks(); @@ -445,7 +438,7 @@ protected float BurstCooldownTime() // Core method } return AttackVerb.verbProps.defaultCooldownTime; } - + public override string GetInspectString() // Replaced vanilla loaded text with CE reloading { StringBuilder stringBuilder = new StringBuilder(); @@ -454,7 +447,7 @@ public override string GetInspectString() // Replaced vanilla loaded text { stringBuilder.AppendLine(inspectString); } - + stringBuilder.AppendLine("GunInstalled".Translate() + ": " + this.Gun.LabelCap); // New code if (this.GunCompEq.PrimaryVerb.verbProps.minRange > 0f) @@ -658,37 +651,6 @@ private void InitGun() verb.castCompleteCallback = new Action(this.BurstComplete); } } - - public Thing nearestViableAmmo; - public void UpdateNearbyAmmo(bool forceCheck = false) - { - if (lastSurroundingAmmoCheck + TicksBetweenAmmoChecks >= Find.TickManager.TicksGame) - return; - - if (!CompAmmo.Props.ammoSet.ammoTypes.Any(x => GenClosestAmmo.latestAmmoUpdate[x.ammo] > lastSurroundingAmmoCheck)) - return; - - //If ammo remained viable, don't bother unless forced - if (nearestViableAmmo != null - && !forceCheck - && nearestViableAmmo.Spawned - && !nearestViableAmmo.IsForbidden(Faction) - && nearestViableAmmo.ParentHolder == null - && !Map.physicalInteractionReservationManager.IsReserved(nearestViableAmmo)) - return; - - //var prevIsSlow = isSlow; - var prevViableAmmo = nearestViableAmmo; - nearestViableAmmo = GenClosestAmmo.ClosestAmmoReachable(Position, Map, CompAmmo, - TraverseParms.For(TraverseMode.NoPassClosedDoors, Danger.Deadly), PathEndMode.ClosestTouch, GenClosestAmmo.ammoSearchRadius, - x => !x.IsForbidden(Faction) && !Map.physicalInteractionReservationManager.IsReserved(x)); - - isSlow = (nearestViableAmmo == null || nearestViableAmmo == prevViableAmmo); - - //Log.Message("Updated " + ThingID + " (slow = "+prevIsSlow+" -> "+isSlow+") to " + nearestViableAmmo?.ThingID); - - lastSurroundingAmmoCheck = Find.TickManager.TicksGame; - } public void TryForceReload() { @@ -720,84 +682,29 @@ public void TryOrderReload(bool forced = false) //Non-mannableComp interaction if (!mannableComp?.MannedNow ?? true) { - //If auto-turret test for "auto-reload" - if (!forced && ticksUntilAutoReload > 0) - return; - - //Unmanned or autoturret -- cancel job on reserved turret - if (Map.physicalInteractionReservationManager.IsReserved(new LocalTargetInfo(this))) - return; - - //If manningPawn is null, WorkGiver_ReloadTurret might find a Humanlike intelligence pawn to reload the turret -- however, this method won't pick one at random to do so - //If manningPawn is null, try to assign jobs to a pawn of faction Mechanoids - if (Faction.def != FactionDefOf.Mechanoid) - return; - - List pawns; - if (this.GetLord() != null && this.GetLord().AnyActivePawn) //Prevents mechs in ancient danger from joining in - { - pawns = this.GetLord().ownedPawns; - } - //else if (Map.mapPawns?.PawnsInFaction(Faction).Any() ?? false) - //{ - // pawns = Map.mapPawns.PawnsInFaction(Faction); - //} - else return; //Nothing could reload this - - var giver = new WorkGiver_ReloadTurret(); - var pawn = giver.BestNonJobUser(pawns.Where(x => x.RaceProps.intelligence == Intelligence.ToolUser - //|| (x.training?.HasLearned(CE_TrainableDefOf.Haul) ?? false) //would allow trained pawns to reload as well - ), this, out bool fromInventory, forced); - if (pawn == null) return; - - Job reloadJob = null; - if (!CompAmmo.UseAmmo) reloadJob = new Job(CE_JobDefOf.ReloadTurret, this, null); - else - { - Thing ammo = fromInventory ? InventoryAmmo(pawn.TryGetComp()) : nearestViableAmmo; - if (ammo != null) - { - if (ammo.def != CompAmmo.SelectedAmmo) - CompAmmo.SelectedAmmo = ammo.def as AmmoDef; - - int amount = CompAmmo.Props.magazineSize; - if (CompAmmo.CurrentAmmo == CompAmmo.SelectedAmmo) amount -= CompAmmo.CurMagCount; - - if (fromInventory) - { - if (!pawn.TryGetComp().container.TryDrop(ammo, Position, Map, ThingPlaceMode.Direct, Mathf.Min(ammo.stackCount, amount), out ammo)) - { - Log.ErrorOnce("Found optimal ammo (" + ammo.LabelCap + "), but could not drop from " + pawn.LabelCap, 81274728); - return; - } - } + return; + } - reloadJob = new Job(CE_JobDefOf.ReloadTurret, this, ammo) { count = Mathf.Min(ammo.stackCount, amount) }; - } - } + //Only have manningPawn reload after a long time of no firing + if (!forced && Reloadable && (compAmmo.CurMagCount != 0 || ticksUntilAutoReload > 0)) + return; - if (reloadJob != null) - pawn.jobs.StartJob(reloadJob, JobCondition.Ongoing, null, pawn.CurJob?.def != reloadJob.def && (!pawn.CurJob?.def.isIdle ?? true)); - } - else //MannableComp interaction + //Already reserved for manning + Pawn manningPawn = mannableComp.ManningPawn; + if (manningPawn != null) { - //Only have manningPawn reload after a long time of no firing - if (!forced && Reloadable && (compAmmo.CurMagCount != 0 || ticksUntilAutoReload > 0)) + if (!JobGiverUtils_Reload.CanReload(manningPawn, this)) + { return; + } + var jobOnThing = JobGiverUtils_Reload.MakeReloadJob(manningPawn, this); - //Already reserved for manning - Pawn manningPawn = mannableComp.ManningPawn; - if (manningPawn != null) + if (jobOnThing != null) { - UpdateNearbyAmmo(forced); - var jobOnThing = new WorkGiver_ReloadTurret().JobOnThing(manningPawn, this, forced); - - if (jobOnThing != null) - manningPawn.jobs.StartJob(jobOnThing, JobCondition.Ongoing, null, manningPawn.CurJob?.def != CE_JobDefOf.ReloadTurret); - - return; + manningPawn.jobs.StartJob(jobOnThing, JobCondition.Ongoing, null, manningPawn.CurJob?.def != CE_JobDefOf.ReloadTurret); } } + } #endregion }