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 _);
- // }
- //}
}
}
}