diff --git a/1.4/Assemblies/CombatAI.dll b/1.4/Assemblies/CombatAI.dll index 6fb6804..8a578a9 100644 Binary files a/1.4/Assemblies/CombatAI.dll and b/1.4/Assemblies/CombatAI.dll differ diff --git a/Source/Rule56/Comps/ThingComp_CombatAI.cs b/Source/Rule56/Comps/ThingComp_CombatAI.cs index 84cee73..077f785 100644 --- a/Source/Rule56/Comps/ThingComp_CombatAI.cs +++ b/Source/Rule56/Comps/ThingComp_CombatAI.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using CombatAI.Utilities; +using HarmonyLib; using RimWorld; using UnityEngine; using Verse; @@ -39,15 +40,6 @@ public class ThingComp_CombatAI : ThingComp /// Whether to find escorts. /// private bool findEscorts; - - /// - /// Move job started by this comp. - /// - public Job moveJob; - /// - /// Tick when this pawn was released as an escort. - /// - private int releasedTick; /// /// Sapper path nodes. /// @@ -66,36 +58,60 @@ public class ThingComp_CombatAI : ThingComp /// Wait job started/queued by this comp. /// public Job waitJob; + /// + /// Parent pawn. + /// + public Pawn selPawn; public ThingComp_CombatAI() { visibleEnemies = new HashSet(32); } + /// + /// Whether the pawn is downed or dead. + /// + public bool IsDeadOrDowned + { + get => selPawn.Dead || selPawn.Downed; + } + + /// + /// Whether the pawning is sapping. + /// public bool IsSapping { get => cellBefore.IsValid && sapperNodes.Count > 0 && GenTicks.TicksGame - sapperStartTick < 4800 && parent.Position.DistanceToSquared(cellBefore) < 1600; } - + /// + /// Whether the pawn is available to escort other pawns or available for sapping. + /// public bool CanSappOrEscort { get => GenTicks.TicksGame - releasedTick > 1200 && !IsSapping; } - public override void PostSpawnSetup(bool respawningAfterLoad) + public override void Initialize(CompProperties props) { - base.PostSpawnSetup(respawningAfterLoad); - if (parent is Pawn pawn) + base.Initialize(props); + selPawn = parent as Pawn; + if (selPawn == null) { - armor = pawn.GetArmorReport(); - duties = new Pawn_CustomDutyTracker(pawn); + throw new Exception($"ThingComp_CombatAI initialized for a non pawn {parent}/def:{parent.def}"); } } + public override void PostSpawnSetup(bool respawningAfterLoad) + { + base.PostSpawnSetup(respawningAfterLoad); + armor = selPawn.GetArmorReport(); + duties = new Pawn_CustomDutyTracker(selPawn); + } + public override void CompTickRare() { base.CompTickRare(); - if (!parent.Spawned) + if (!selPawn.Spawned) { return; } @@ -103,7 +119,7 @@ public override void CompTickRare() { duties.TickRare(); } - if (IsSapping && parent is Pawn pawn && !pawn.Downed && !pawn.Dead) + if (IsSapping && !IsDeadOrDowned) { if (sapperNodes[0].GetEdifice(parent.Map) == null) { @@ -133,10 +149,7 @@ public override void CompTickRare() public override void CompTickLong() { base.CompTickLong(); - if (parent is Pawn pawn) - { - armor = pawn.GetArmorReport(); - } + this.armor = this.selPawn.GetArmorReport(); } /// @@ -249,26 +262,26 @@ public void OnScanFinished() } lastSawEnemies = GenTicks.TicksGame; } - if (parent is Pawn pawn && !(pawn.RaceProps?.Animal ?? true)) + if (!(selPawn.RaceProps?.Animal ?? true)) { - float bodySize = pawn.RaceProps.baseBodySize; + float bodySize = selPawn.RaceProps.baseBodySize; // pawn reaction cooldown changes with their bodysize. if (GenTicks.TicksGame - lastInterupted < 60 * bodySize || GenTicks.TicksGame - lastRetreated < 65 * bodySize) { return; } // if CE is acrive skip reaction if the pawn is reloading or hunkering down. - if (Mod_CE.active && (pawn.CurJobDef.Is(Mod_CE.ReloadWeapon) || pawn.CurJobDef.Is(Mod_CE.HunkerDown))) + if (Mod_CE.active && (selPawn.CurJobDef.Is(Mod_CE.ReloadWeapon) || selPawn.CurJobDef.Is(Mod_CE.HunkerDown))) { return; } // if the pawn is kidnaping a pawn skip. - if (pawn.CurJobDef.Is(JobDefOf.Kidnap)) + if (selPawn.CurJobDef.Is(JobDefOf.Kidnap)) { return; } // Skip if some vanilla duties are active. - PawnDuty duty = pawn.mindState.duty; + PawnDuty duty = selPawn.mindState.duty; if (duty != null && (duty.def.Is(DutyDefOf.Build) || duty.def.Is(DutyDefOf.SleepForever) || duty.def.Is(DutyDefOf.TravelOrLeave))) { lastInterupted = GenTicks.TicksGame + Rand.Int % 240; @@ -276,7 +289,7 @@ public void OnScanFinished() } // pawns above a certain bodysize who are worming up should be skiped. // This is mainly for large mech pawns. - Stance_Warmup warmup = (pawn.stances?.curStance ?? null) as Stance_Warmup; + Stance_Warmup warmup = (selPawn.stances?.curStance ?? null) as Stance_Warmup; if (warmup != null && bodySize > 2.5f) { return; @@ -295,9 +308,9 @@ public void OnScanFinished() } if (verb.IsMeleeAttack) { - if (pawn.CurJobDef == JobDefOf.Mine) + if (selPawn.CurJobDef == JobDefOf.Mine) { - pawn.jobs.StopAll(); + selPawn.jobs.StopAll(); } return; } @@ -305,13 +318,13 @@ public void OnScanFinished() { return; } - Thing bestEnemy = pawn.mindState.enemyTarget; + Thing bestEnemy = selPawn.mindState.enemyTarget; IntVec3 bestEnemyPositon = IntVec3.Invalid; - IntVec3 pawnPosition = pawn.Position; + IntVec3 pawnPosition = selPawn.Position; float bestEnemyScore = verb.currentTarget.IsValid && verb.currentTarget.Cell.IsValid ? verb.currentTarget.Cell.DistanceToSquared(pawnPosition) : 1e6f; bool bestEnemyVisibleNow = warmup != null; bool retreat = false; - bool canRetreat = Finder.Settings.Retreat_Enabled && pawn.RaceProps.baseHealthScale <= 2.0f && pawn.RaceProps.baseBodySize <= 2.2f; + bool canRetreat = Finder.Settings.Retreat_Enabled && selPawn.RaceProps.baseHealthScale <= 2.0f && selPawn.RaceProps.baseBodySize <= 2.2f; float retreatDistSqr = Maths.Max(verb.EffectiveRange * verb.EffectiveRange / 9, 36); foreach (Thing enemy in visibleEnemies) { @@ -334,8 +347,9 @@ public void OnScanFinished() { if (enemyPawn != null && distSqr < 81) { - bestEnemy = enemy; - retreat = true; + bestEnemy = enemy; + bestEnemyScore = distSqr; + retreat = true; break; } } @@ -380,33 +394,37 @@ public void OnScanFinished() { return; } + if (Prefs.DevMode && DebugSettings.godMode) + { + _bestEnemy = bestEnemy; + } if (retreat) { + _last = 1; waitJob = null; //pawn.Map.debugDrawer.FlashCell(pawn.Position, 1f, "FLEE", 200); - pawn.mindState.enemyTarget = bestEnemy; + selPawn.mindState.enemyTarget = bestEnemy; CoverPositionRequest request = new CoverPositionRequest(); - request.caster = pawn; + request.caster = selPawn; request.target = new LocalTargetInfo(bestEnemyPositon); request.verb = verb; - request.maxRangeFromCaster = Maths.Min(Mathf.Max(retreatDistSqr * 2 / (pawn.BodySize + 0.01f), 5), 15); + request.maxRangeFromCaster = Maths.Min(Mathf.Max(retreatDistSqr * 2 / (selPawn.BodySize + 0.01f), 5), 15); request.checkBlockChance = true; if (CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell) && cell != pawnPosition) { -// if (Rand.Chance((sightReader.GetThreat(pawn.Position) - sightReader.GetThreat(cell)) + 0.1f)) -// { + _last = 11; Job job_goto = JobMaker.MakeJob(JobDefOf.Goto, cell); job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog; - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.StopAll(); - pawn.jobs.StartJob(moveJob = job_goto, JobCondition.InterruptForced); -// } + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.StopAll(); + selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced); } else if (warmup == null) { + _last = 12; Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100); - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced); + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced); } lastRetreated = GenTicks.TicksGame - Rand.Int % 50; } @@ -415,69 +433,74 @@ public void OnScanFinished() bool changedPos = false; // // ------------------------------------------------------------ - float moveSpeed = pawn.GetStatValue_Fast(StatDefOf.MoveSpeed, 450); - if (pawn.stances?.stagger?.Staggered ?? false) + float moveSpeed = selPawn.GetStatValue_Fast(StatDefOf.MoveSpeed, 450); + if (selPawn.stances?.stagger?.Staggered ?? false) { - moveSpeed = pawn.stances.stagger.StaggerMoveSpeedFactor; + moveSpeed = selPawn.stances.stagger.StaggerMoveSpeedFactor; } float dist = bestEnemyScore; if (dist > 25) { if (bestEnemyVisibleNow) { - waitJob = null; - pawn.mindState.enemyTarget = bestEnemy; + _last = 2; + waitJob = null; + selPawn.mindState.enemyTarget = bestEnemy; CastPositionRequest request = new CastPositionRequest(); - request.caster = pawn; + request.caster = selPawn; request.target = bestEnemy; request.verb = verb; request.maxRangeFromTarget = 9999; - request.maxRangeFromCaster = Rand.Chance(Finder.P50 - 0.1f) ? Mathf.Clamp(moveSpeed * 2 / (pawn.BodySize + 0.01f), 4, 10) : 4; + request.maxRangeFromCaster = Mathf.Clamp(moveSpeed * 3 / (selPawn.BodySize + 0.01f), 4, 15); request.wantCoverFromTarget = true; if (CastPositionFinder.TryFindCastPosition(request, out IntVec3 cell)) { - if (cell != pawnPosition && (prevEnemyDir == Vector2.zero || Rand.Chance(Mathf.Abs(1 - Vector2.Dot(prevEnemyDir, sightReader.GetEnemyDirection(cell).normalized))) || Rand.Chance(sightReader.GetVisibilityToEnemies(pawn.Position) - sightReader.GetVisibilityToEnemies(cell)))) + if (cell != pawnPosition && (prevEnemyDir == Vector2.zero || Rand.Chance(Mathf.Abs(1 - Vector2.Dot(prevEnemyDir, sightReader.GetEnemyDirection(cell).normalized))) || Rand.Chance(sightReader.GetVisibilityToEnemies(selPawn.Position) - sightReader.GetVisibilityToEnemies(cell)))) { + _last = 21; Job job_goto = JobMaker.MakeJob(JobDefOf.Goto, cell); job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog; Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100); job_waitCombat.checkOverrideOnExpire = true; - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.StartJob(moveJob = job_goto, JobCondition.InterruptForced); - pawn.jobs.jobQueue.EnqueueFirst(waitJob = job_waitCombat); + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.StartJob( job_goto, JobCondition.InterruptForced); + selPawn.jobs.jobQueue.EnqueueFirst(waitJob = job_waitCombat); changedPos = true; prevEnemyDir = sightReader.GetEnemyDirection(cell).normalized; } else if (warmup == null) { - pawn.mindState.enemyTarget = bestEnemy; + _last = 22; + selPawn.mindState.enemyTarget = bestEnemy; Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100); job_waitCombat.checkOverrideOnExpire = true; - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.StartJob(waitJob = job_waitCombat, JobCondition.InterruptForced); + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.StartJob(waitJob = job_waitCombat, JobCondition.InterruptForced); } } } else { - pawn.mindState.enemyTarget = bestEnemy; + _last = 3; + selPawn.mindState.enemyTarget = bestEnemy; CoverPositionRequest request = new CoverPositionRequest(); - request.caster = pawn; + request.caster = selPawn; request.target = new LocalTargetInfo(bestEnemy.Position); request.verb = verb; - request.maxRangeFromCaster = Rand.Chance(Finder.P50 - 0.1f) ? Mathf.Clamp(moveSpeed * 2 / (pawn.BodySize + 0.01f), 4, 10) : 4; + request.maxRangeFromCaster = Mathf.Clamp(moveSpeed * 3 / (selPawn.BodySize + 0.01f), 4, 15); request.checkBlockChance = true; if (CoverPositionFinder.TryFindCoverPosition(request, out IntVec3 cell) && cell != pawnPosition) { - if (prevEnemyDir == Vector2.zero || Rand.Chance(Mathf.Abs(1 - Vector2.Dot(prevEnemyDir, sightReader.GetEnemyDirection(cell).normalized))) || Rand.Chance(sightReader.GetVisibilityToEnemies(pawn.Position) - sightReader.GetVisibilityToEnemies(cell))) + if (prevEnemyDir == Vector2.zero || Rand.Chance(Mathf.Abs(1 - Vector2.Dot(prevEnemyDir, sightReader.GetEnemyDirection(cell).normalized))) || Rand.Chance(sightReader.GetVisibilityToEnemies(selPawn.Position) - sightReader.GetVisibilityToEnemies(cell))) { + _last = 31; Job job_goto = JobMaker.MakeJob(JobDefOf.Goto, cell); job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog; Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100); job_waitCombat.checkOverrideOnExpire = true; - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.StartJob(moveJob = job_goto, JobCondition.InterruptForced); - pawn.jobs.jobQueue.EnqueueFirst(waitJob = job_waitCombat); + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced); + selPawn.jobs.jobQueue.EnqueueFirst(waitJob = job_waitCombat); changedPos = true; prevEnemyDir = sightReader.GetEnemyDirection(cell).normalized; } @@ -486,11 +509,12 @@ public void OnScanFinished() } else if (warmup == null) { - waitJob = null; - pawn.mindState.enemyTarget = bestEnemy; + _last = 4; + waitJob = null; + selPawn.mindState.enemyTarget = bestEnemy; Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100); - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced); + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced); } if (changedPos) { @@ -516,17 +540,17 @@ public void Notify_TookDamage(DamageInfo dInfo) duties.Notify_TookDamage(); } // if the pawn is tanky enough skip. - if (Finder.Settings.Retreat_Enabled && parent.Spawned && GenTicks.TicksGame - lastScanned < 90 && parent is Pawn pawn && !pawn.Dead && !pawn.Downed && armor.TankInt < 0.4f) + if (Finder.Settings.Retreat_Enabled && parent.Spawned && GenTicks.TicksGame - lastScanned < 90 && !IsDeadOrDowned && armor.TankInt < 0.4f) { - if (!pawn.RaceProps.IsMechanoid && dInfo.Def != null && dInfo.Instigator != null) + if (!selPawn.RaceProps.IsMechanoid && dInfo.Def != null && dInfo.Instigator != null) { - if (pawn.CurJobDef.Is(JobDefOf.Mine)) + if (selPawn.CurJobDef.Is(JobDefOf.Mine)) { - pawn.jobs.StopAll(); + selPawn.jobs.StopAll(); } else { - Verb effectiveVerb = pawn.CurrentEffectiveVerb; + Verb effectiveVerb = selPawn.CurrentEffectiveVerb; if (effectiveVerb != null && effectiveVerb.Available() && effectiveVerb.EffectiveRange > 5) { float enemyRange = dInfo.Instigator.TryGetAttackVerb()?.EffectiveRange ?? 5f; @@ -534,24 +558,24 @@ public void Notify_TookDamage(DamageInfo dInfo) if (armorVal == 0 || Rand.Chance(dInfo.ArmorPenetrationInt / armorVal) || GenTicks.TicksGame - lastTookDamage < 30 && Rand.Chance(0.50f)) { IntVec3 pawnPosition = parent.Position; - waitJob = null; - pawn.mindState.enemyTarget = dInfo.Instigator; + waitJob = null; + selPawn.mindState.enemyTarget = dInfo.Instigator; CoverPositionRequest request = new CoverPositionRequest(); - request.caster = pawn; + request.caster = selPawn; request.target = new LocalTargetInfo(dInfo.Instigator); request.verb = effectiveVerb; - request.maxRangeFromCaster = Maths.Min(enemyRange * 2 / (pawn.BodySize + 0.01f), 15); + request.maxRangeFromCaster = Maths.Min(enemyRange * 2 / (selPawn.BodySize + 0.01f), 15); request.checkBlockChance = true; if (CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell) && cell != pawnPosition) { - if (Rand.Chance(sightReader.GetVisibilityToEnemies(pawn.Position) - sightReader.GetVisibilityToEnemies(cell)) || Rand.Chance(sightReader.GetThreat(pawn.Position) - sightReader.GetThreat(cell))) + if (Rand.Chance(sightReader.GetVisibilityToEnemies(selPawn.Position) - sightReader.GetVisibilityToEnemies(cell)) || Rand.Chance(sightReader.GetThreat(selPawn.Position) - sightReader.GetThreat(cell))) { Job job_goto = JobMaker.MakeJob(JobDefOf.Goto, cell); job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog; Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100); - pawn.jobs.StartJob(moveJob = job_goto, JobCondition.InterruptForced); - pawn.jobs.ClearQueuedJobs(); - pawn.jobs.jobQueue.EnqueueFirst(job_waitCombat); + selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced); + selPawn.jobs.ClearQueuedJobs(); + selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat); } } lastRetreated = GenTicks.TicksGame - Rand.Int % 50; @@ -571,11 +595,6 @@ public void Notify_TookDamage(DamageInfo dInfo) /// Whether to look for escorts public void StartSapper(List blocked, IntVec3 cellBefore, bool findEscorts) { - Pawn pawn = parent as Pawn; - if (pawn == null) - { - return; - } if (cellBefore.IsValid && sapperNodes.Count > 0 && GenTicks.TicksGame - sapperStartTick < 4800) { ReleaseEscorts(); @@ -587,12 +606,95 @@ public void StartSapper(List blocked, IntVec3 cellBefore, bool findEsco sapperNodes.AddRange(blocked); _sap = 0; TryStartSapperJob(); -// if ((pawn.needs?.food?.CurCategory != HungerCategory.Fed) && pawn.Position.DistanceToSquared(cellBefore) < 13f) -// { -// List pawns = escorts.ToList(); -// pawns.Add(pawn); -// SuppliesUtility.FulfillFoodSupplies(pawns, pawn.Map); -// } + } + + /// + /// Returns debug gizmos. + /// + /// + public override IEnumerable CompGetGizmosExtra() + { + if (Prefs.DevMode && DebugSettings.godMode) + { + Verb verb = selPawn.TryGetAttackVerb(); + float retreatDistSqr = Maths.Max(verb.EffectiveRange * verb.EffectiveRange / 9, 36); + Map map = selPawn.Map; + Command_Action retreat = new Command_Action(); + retreat.defaultLabel = "DEV: Retreat position search"; + retreat.action = delegate + { + CoverPositionRequest request = new CoverPositionRequest(); + if (_bestEnemy != null) + { + request.target = new LocalTargetInfo(_bestEnemy.Position); + } + request.caster = selPawn; + request.verb = verb; + request.maxRangeFromCaster = Maths.Min(Mathf.Max(retreatDistSqr * 2 / (selPawn.BodySize + 0.01f), 5), 15); + request.checkBlockChance = true; + CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell, (cell, val) => map.debugDrawer.FlashCell(cell, Mathf.Clamp((val + 15f) / 30f, 0.01f, 0.99f), $"{Math.Round(val, 3)}")); + if (cell.IsValid) + { + map.debugDrawer.FlashCell(cell, 1, "XXXXXXX", duration: 150); + } + }; + Command_Action cover = new Command_Action(); + cover.defaultLabel = "DEV: Cover position search"; + cover.action = delegate + { + CoverPositionRequest request = new CoverPositionRequest(); + if (_bestEnemy != null) + { + request.target = new LocalTargetInfo(_bestEnemy.Position); + } + request.caster = selPawn; + request.verb = verb; + request.maxRangeFromCaster = Mathf.Clamp(selPawn.GetStatValue_Fast(StatDefOf.MoveSpeed, 60) * 3 / (selPawn.BodySize + 0.01f), 4, 15); + request.checkBlockChance = true; + CoverPositionFinder.TryFindCoverPosition(request, out IntVec3 cell, (cell, val) => map.debugDrawer.FlashCell(cell, Mathf.Clamp((val + 15f) / 30f, 0.01f, 0.99f), $"{Math.Round(val, 3)}")); + if (cell.IsValid) + { + map.debugDrawer.FlashCell(cell, 1, "XXXXXXX", duration: 150); + } + }; + Command_Action cast = new Command_Action(); + cast.defaultLabel = "DEV: Cast position search"; + cast.action = delegate + { + if (_bestEnemy == null) + { + return; + } + CastPositionRequest request = new CastPositionRequest(); + request.caster = selPawn; + request.target = _bestEnemy; + request.verb = verb; + request.maxRangeFromTarget = 9999; + request.maxRangeFromCaster = Mathf.Clamp(selPawn.GetStatValue_Fast(StatDefOf.MoveSpeed, 60) * 3 / (selPawn.BodySize + 0.01f), 4, 15); + request.wantCoverFromTarget = true; + try + { + DebugViewSettings.drawCastPositionSearch = true; + CastPositionFinder.TryFindCastPosition(request, out IntVec3 cell); + if (cell.IsValid) + { + map.debugDrawer.FlashCell(cell, 1, "XXXXXXX", duration: 150); + } + } + catch (Exception er) + { + Log.Error(er.ToString()); + } + finally + { + DebugViewSettings.drawCastPositionSearch = false; + } + }; + yield return retreat; + yield return cover; + yield return cast; + } + yield break; } /// @@ -666,14 +768,11 @@ public override void PostExposeData() { base.PostExposeData(); Scribe_Deep.Look(ref duties, "duties"); - if (parent is Pawn pawn) - { - if (duties == null) + if (duties == null) { - duties = new Pawn_CustomDutyTracker(pawn); + duties = new Pawn_CustomDutyTracker(selPawn); } - duties.pawn = pawn; - } + duties.pawn = selPawn; } private void TryStartSapperJob() @@ -687,29 +786,28 @@ private void TryStartSapperJob() sapperNodes.Clear(); return; } - Pawn pawn = parent as Pawn; - if (pawn.Destroyed || pawn.Downed || pawn.Dead || pawn.mindState?.duty == null || !(pawn.mindState.duty.def.Is(DutyDefOf.AssaultColony) || pawn.mindState.duty.def.Is(DutyDefOf.Defend) || pawn.mindState.duty.def.Is(DutyDefOf.AssaultThing) || pawn.mindState.duty.def.Is(DutyDefOf.Breaching))) + if (selPawn.Destroyed || IsDeadOrDowned || selPawn.mindState?.duty == null || !(selPawn.mindState.duty.def.Is(DutyDefOf.AssaultColony) || selPawn.mindState.duty.def.Is(DutyDefOf.Defend) || selPawn.mindState.duty.def.Is(DutyDefOf.AssaultThing) || selPawn.mindState.duty.def.Is(DutyDefOf.Breaching))) { ReleaseEscorts(); return; } - Map map = pawn.Map; + Map map = selPawn.Map; Thing blocker = sapperNodes[0].GetEdifice(map); if (blocker != null) { - Job job = DigUtility.PassBlockerJob(pawn, blocker, cellBefore, true, true); + Job job = DigUtility.PassBlockerJob(selPawn, blocker, cellBefore, true, true); if (job != null) { job.playerForced = true; job.expiryInterval = 3600; job.maxNumMeleeAttacks = 300; - pawn.jobs.StopAll(); - pawn.jobs.StartJob(job, JobCondition.InterruptForced); + selPawn.jobs.StopAll(); + selPawn.jobs.StartJob(job, JobCondition.InterruptForced); if (findEscorts && Rand.Chance(1 - Maths.Max(1f / (escorts.Count + 1f), 0.85f))) { int count = escorts.Count; - int countTarget = Rand.Int % 4 + 3 + Maths.Min(sapperNodes.Count, 10) - Maths.Min(Mathf.CeilToInt(pawn.Position.DistanceTo(cellBefore) / 10f), 5); - Faction faction = pawn.Faction; + int countTarget = Rand.Int % 4 + 3 + Maths.Min(sapperNodes.Count, 10) - Maths.Min(Mathf.CeilToInt(selPawn.Position.DistanceTo(cellBefore) / 10f), 5); + Faction faction = selPawn.Faction; Predicate validator = t => { if (count < countTarget && t.Faction == faction && t is Pawn ally && !ally.Destroyed @@ -721,7 +819,7 @@ private void TryStartSapperJob() ThingComp_CombatAI comp = ally.GetComp_Fast(); if (comp?.duties != null && comp.duties?.Any(DutyDefOf.Escort) == false && !comp.IsSapping && GenTicks.TicksGame - comp.releasedTick > 600) { - Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.Escort(pawn, 20, 100, (500 * sapperNodes.Count) / (escorts.Count + 1) + Rand.Int % 500); + Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.Escort(selPawn, 20, 100, (500 * sapperNodes.Count) / (escorts.Count + 1) + Rand.Int % 500); if (ally.TryStartCustomDuty(custom)) { escorts.Add(ally); @@ -739,7 +837,7 @@ private void TryStartSapperJob() } return false; }; - Verse.GenClosest.RegionwiseBFSWorker(pawn.Position, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(pawn), validator, null, 1, 10, 40, out int _); + Verse.GenClosest.RegionwiseBFSWorker(selPawn.Position, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(selPawn), validator, null, 1, 10, 40, out int _); } } } @@ -775,6 +873,10 @@ private void TryStartSapperJob() /// The general direction of enemies last time the pawn reacted. /// private Vector2 prevEnemyDir = Vector2.zero; + /// + /// Tick when this pawn was released as an escort. + /// + private int releasedTick; #endregion @@ -805,7 +907,7 @@ public override void DrawGUIOverlay() Vector2 drawPosUI = drawPos.MapToUIPosition(); Text.Font = GameFont.Tiny; string state = GenTicks.TicksGame - lastInterupted > 120 ? "O" : "X"; - Widgets.Label(new Rect(drawPosUI.x - 25, drawPosUI.y - 15, 50, 30), $"{state}/{_visibleEnemies.Count}"); + Widgets.Label(new Rect(drawPosUI.x - 25, drawPosUI.y - 15, 50, 30), $"{state}/{_visibleEnemies.Count}:{_last}"); }); bool bugged = nearbyVisiblePawns.Count != _visibleEnemies.Count; if (bugged) @@ -868,6 +970,8 @@ public override void DrawGUIOverlay() } } + private int _last; + private Thing _bestEnemy; private readonly HashSet _visibleEnemies = new HashSet(); private readonly List _path = new List(); private readonly List _colors = new List(); diff --git a/Source/Rule56/CoverPositionFinder.cs b/Source/Rule56/CoverPositionFinder.cs index 852d338..059ff4e 100644 --- a/Source/Rule56/CoverPositionFinder.cs +++ b/Source/Rule56/CoverPositionFinder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using UnityEngine; using Verse; using static CombatAI.AvoidanceTracker; @@ -11,7 +12,7 @@ public static class CoverPositionFinder private static readonly Dictionary parentTree = new Dictionary(512); private static readonly Dictionary scores = new Dictionary(512); - public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec3 coverCell) + public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec3 coverCell, Action callback = null) { request.caster.TryGetSightReader(out SightReader sightReader); request.caster.TryGetAvoidanceReader(out AvoidanceReader avoidanceReader); @@ -54,9 +55,9 @@ public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec float c = (node.dist - node.distAbs) / (node.distAbs + 1f) - interceptors.grid.Get(node.cell) * 2 + (sightReader.GetThreat(node.cell) - rootThreat) * 0.25f; if (rootDutyDestDist > 0) { - c += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 3) - rootDutyDestDist) * 0.25f, -2f, 2f); + c += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 3) - rootDutyDestDist) * 0.25f, -1f, 1f); } - if (c < bestCellScore) + if (bestCellScore - c >= 0.05f) { float v = sightReader.GetVisibilityToEnemies(node.cell); if (v < bestCellVisibility) @@ -66,14 +67,14 @@ public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec bestCell = node.cell; } } - //if (Find.Selector.SelectedPawns.Contains(request.caster)) - //{ - // map.debugDrawer.FlashCell(node.cell, c / 5f, text: $"{Math.Round(c, 2)}"); - //} + if (callback != null) + { + callback(node.cell, c); + } }, cell => { - return (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (sightReader.GetVisibilityToEnemies(cell) - rootVis) * 2 - interceptors.grid.Get(cell); + return (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (cell.GetTerrain(map)?.pathCost / 22f ?? 0) + (sightReader.GetVisibilityToEnemies(cell) - rootVis) * 2 - interceptors.grid.Get(cell); }, cell => { @@ -85,7 +86,7 @@ public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec return bestCell.IsValid; } - public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntVec3 coverCell) + public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntVec3 coverCell, Action callback = null) { request.caster.TryGetSightReader(out SightReader sightReader); request.caster.TryGetAvoidanceReader(out AvoidanceReader avoidanceReader); @@ -136,9 +137,9 @@ public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntV float c = (node.dist - node.distAbs) / (node.distAbs + 1f) + avoidanceReader.GetProximity(node.cell) * 0.5f - interceptors.grid.Get(node.cell) + (sightReader.GetThreat(node.cell) - rootThreat) * 0.75f; if (rootDutyDestDist > 0) { - c += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 3) - rootDutyDestDist) * 0.25f, -2f, 2f); + c += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 5) - rootDutyDestDist) * 0.25f, -1f, 1f); } - if (c < bestCellScore) + if (bestCellScore - c >= 0.05f) { float d = node.cell.DistanceToSquared(enemyLoc); if (d > bestCellDist) @@ -148,11 +149,15 @@ public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntV bestCell = node.cell; } } + if (callback != null) + { + callback(node.cell, c); + } scores[node.cell] = c; }, cell => { - return (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (sightReader.GetVisibilityToEnemies(cell) - rootVis) * 2 - (rootVisFriendlies - sightReader.GetVisibilityToFriendlies(cell)) - interceptors.grid.Get(cell) + (sightReader.GetThreat(cell) - rootThreat) * 0.25f; + return (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (cell.GetTerrain(map)?.pathCost / 22f ?? 0) + (sightReader.GetVisibilityToEnemies(cell) - rootVis) * 2 - (rootVisFriendlies - sightReader.GetVisibilityToFriendlies(cell)) - interceptors.grid.Get(cell) + (sightReader.GetThreat(cell) - rootThreat) * 0.25f; }, cell => { diff --git a/Source/Rule56/Patches/CastPositionFinder_Patch.cs b/Source/Rule56/Patches/CastPositionFinder_Patch.cs index 3c6e5ee..9ccadfa 100644 --- a/Source/Rule56/Patches/CastPositionFinder_Patch.cs +++ b/Source/Rule56/Patches/CastPositionFinder_Patch.cs @@ -193,18 +193,13 @@ public static void Postfix(IntVec3 c, ref float __result) } if (sightReader != null) { - float before = __result; if (!grid.IsSet(c)) { __result = -1; } else { - __result -= grid[c] * Finder.P50; - //if (Find.Selector.SelectedPawns.Contains(pawn)) - //{ - // map.debugDrawer.FlashCell(c, (grid[c] + 5f) / 10f, $"{Math.Round(grid[c], 2)}"); - //} + __result = __result - grid[c] * Finder.P50 + 1; } } } diff --git a/Source/Rule56/Patches/PathFinder_Patch.cs b/Source/Rule56/Patches/PathFinder_Patch.cs index 71dd3ab..aac4a0d 100644 --- a/Source/Rule56/Patches/PathFinder_Patch.cs +++ b/Source/Rule56/Patches/PathFinder_Patch.cs @@ -197,45 +197,6 @@ public static void Postfix(PathFinder __instance, ref PawnPath __result, bool __ else { comp.StartSapper(blocked, cellBefore, enemiesAhead); - //Job job = DigUtility.PassBlockerJob(pawn, blocker, cellBefore, true, true); - //if (job != null) - //{ - // job.playerForced = true; - // job.expiryInterval = 3600; - // job.maxNumMeleeAttacks = 300; - // pawn.jobs.StopAll(); - // pawn.jobs.StartJob(job, JobCondition.InterruptForced); - // if (enemiesAhead) - // { - // int count = 0; - // int countTarget = Rand.Int % 6 + 4 + Maths.Min(blocked.Count, 10); - // Faction faction = pawn.Faction; - // Predicate validator = t => - // { - // if (count < countTarget && t.Faction == faction && t is Pawn ally && !ally.Destroyed - // && !ally.CurJobDef.Is(JobDefOf.Mine) - // && ally.mindState?.duty?.def != DutyDefOf.Escort - // && (sightReader == null || sightReader.GetAbsVisibilityToEnemies(ally.Position) == 0) - // && ally.skills?.GetSkill(SkillDefOf.Mining).Level < 10) - // { - // ThingComp_CombatAI comp = ally.GetComp_Fast(); - // if (comp?.duties != null && comp.duties?.Any(DutyDefOf.Escort) == false) - // { - // Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.Escort(ally, pawn, 20, 100, 300 * blocked.Count + Rand.Int % 1000); - // if (custom != null) - // { - // custom.duty.locomotion = LocomotionUrgency.Sprint; - // comp.duties.StartDuty(custom); - // } - // } - // count++; - // return count == countTarget; - // } - // return false; - // }; - // Verse.GenClosest.RegionwiseBFSWorker(pawn.Position, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(pawn), validator, null, 1, 10, 40, out int _); - // } - //} } } }