diff --git a/1.4/Assemblies/CombatAI.dll b/1.4/Assemblies/CombatAI.dll
index 7966a70..b954a82 100644
Binary files a/1.4/Assemblies/CombatAI.dll and b/1.4/Assemblies/CombatAI.dll differ
diff --git a/1.4/Defs/DutyDefs/Duties_Misc.xml b/1.4/Defs/DutyDefs/Duties_Misc.xml
index dea5110..cf65c45 100755
--- a/1.4/Defs/DutyDefs/Duties_Misc.xml
+++ b/1.4/Defs/DutyDefs/Duties_Misc.xml
@@ -10,10 +10,19 @@
true
+
+ 900
+
+
+ 35
+ 42
+
+
+
65
72
-
+
16
@@ -23,7 +32,7 @@
- 32
+ 12
Sprint
@@ -45,9 +54,21 @@
true
+
+ Abilities_Aggressive
+
+
+ 900
+
+
+ 35
+ 42
+
+
+
- 65
- 72
+ 32
+ 28
16
@@ -59,6 +80,7 @@
8
+ 30
Sprint
@@ -78,17 +100,28 @@
true
+
+ 900
+
+
+ 35
+ 42
+
+
+
- 65
+ 35
72
-
-
+
+
SatisfyVeryUrgentNeeds
8
-
+ 60
+ Jog
+
true
diff --git a/1.4/Defs/Jobs/Jobs_Misc.xml b/1.4/Defs/Jobs/Jobs_Misc.xml
index 8011779..e50d555 100644
--- a/1.4/Defs/Jobs/Jobs_Misc.xml
+++ b/1.4/Defs/Jobs/Jobs_Misc.xml
@@ -27,7 +27,7 @@
CombatAI_Goto_Cover
JobDriver_Goto
moving.
- true
+ false
true
false
Never
diff --git a/1.4/Languages/English/Keyed/Translations.xml b/1.4/Languages/English/Keyed/Translations.xml
index 5a8546f..dbd9320 100644
--- a/1.4/Languages/English/Keyed/Translations.xml
+++ b/1.4/Languages/English/Keyed/Translations.xml
@@ -8,6 +8,9 @@
Hide
Hide
Welcome to CAI-5000
+ AI Race Settings
+ Here you can configure different AI settings for each race.
+ Selected
Move-Attack
Warning: Melee pawns cannot use move-attack. Melee pawns skipped.
Cancel Move-Attack
@@ -21,6 +24,7 @@
Hold
Hold at the current angle.
Basic Settings
+ Enable variation in faction tactics depending on the faction leader and the current time of year
Performance/Difficulty presets
Easy/Perf
Normal
@@ -37,6 +41,8 @@
Cost multiplier for pathing through walls. Higher values means raiders are less likely to path through walls.
[BETA] Fog of war
Enable fog of war
+ Use the old legacy shader
+ The new shader settings will be applied after restart!
Allies reveal fog of war
Colony animals reveal fog of war
Only smart animals reveal fog of war
@@ -64,6 +70,8 @@
Debugging
Enable Debugging
Advance Settings
+ Avoidance path width. Avoidance path width determine flanking aggressiveness.
+ Path width {0} cells (default: 1)
WARNING: This is only for advanced users! Don't enable this if you don't know what you're doing!
I'm an advanced user!
Performance
@@ -77,4 +85,13 @@
Ticks between bucket updates {0} Ticks
{0} Things maximum can obstuct line of sight
The maximum number of things along line of sight. This includes trees, buildings, etc. Higher values means more accurate sight model but higher performance impact.
+ Faction Tech Level Settings
+ Here you can configure factions with different tech levels.
+ Tech {0}
+ Duck {0}x. Higher values means more likely to duck for cover
+ Retreat {0}x. Higher values means more likely to retreat
+ Cover {0}x. Higher values means more aggressive cover
+ Pathing {0}x. Higher values means more aggressive pathing
+ Sapping {0}x. Higher values means less aggressive sapping
+ Charge {0}x. Higher values means groups are more likely to charge
\ No newline at end of file
diff --git a/About/About.xml b/About/About.xml
index 8d2ccbd..4c48448 100755
--- a/About/About.xml
+++ b/About/About.xml
@@ -28,6 +28,7 @@
Ludeon.RimWorld.Royalty
brrainz.harmony
zetrith.prepatcher
+ owlchemist.simplefx.vapor
CETeam.CombatExtended
diff --git a/Source/Rule56/ArmorUtility.cs b/Source/Rule56/ArmorUtility.cs
index 4055bac..158b8fb 100644
--- a/Source/Rule56/ArmorUtility.cs
+++ b/Source/Rule56/ArmorUtility.cs
@@ -21,7 +21,7 @@ public static ArmorReport GetArmorReport(this Pawn pawn, Listing_Collapsible col
{
return default(ArmorReport);
}
- if (collapsible != null || !reports.TryGetValue(pawn.thingIDNumber, out ArmorReport report) || GenTicks.TicksGame - report.createdAt > 900)
+ if (collapsible != null || !reports.TryGetValue(pawn.thingIDNumber, out ArmorReport report) || GenTicks.TicksGame - report.createdAt > 7200)
{
reports[pawn.thingIDNumber] = report = CreateReport(pawn, collapsible);
}
@@ -179,5 +179,13 @@ public float Coverage(ApparelProperties apparel)
return coverage;
}
}
+
+ public static void Invalidate(Thing thing)
+ {
+ if (reports.ContainsKey(thing.thingIDNumber))
+ {
+ reports.Remove(thing.thingIDNumber);
+ }
+ }
}
}
diff --git a/Source/Rule56/AsyncActions.cs b/Source/Rule56/AsyncActions.cs
index caaa43e..c079912 100644
--- a/Source/Rule56/AsyncActions.cs
+++ b/Source/Rule56/AsyncActions.cs
@@ -1,22 +1,25 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using Verse;
namespace CombatAI
{
public class AsyncActions
{
-
+ private static readonly List running = new List();
+
private readonly int hashOffset;
public readonly object locker_Main = new object();
public readonly object locker_offMain = new object();
private readonly int mainLoopTickInterval;
- private readonly List queuedMainThreadActions = new List();
- private readonly List queuedOffThreadActions = new List();
- private readonly Thread thread;
- private bool mainThreadActionQueueEmpty;
+ private readonly List queuedMainThreadActions = new List();
+ private readonly List queuedOffThreadActions = new List();
+ private readonly AutoResetEvent waitHandle = new AutoResetEvent(false);
+ private readonly Thread thread;
+ private bool mainThreadActionQueueEmpty;
public AsyncActions(int mainLoopTickInterval = 5)
{
@@ -34,6 +37,7 @@ public bool Alive
public void Start()
{
thread.Start();
+ running.Add(this);
}
public void ExecuteMainThreadActions()
@@ -44,6 +48,7 @@ public void ExecuteMainThreadActions()
public void Kill()
{
Alive = false;
+ running.Remove(this);
try
{
lock (locker_Main)
@@ -55,12 +60,22 @@ public void Kill()
queuedOffThreadActions.Clear();
}
thread.Abort();
+ thread.Join(100);
+ waitHandle.Close();
}
catch (Exception)
{
}
}
+ public static void KillAll()
+ {
+ foreach (AsyncActions asyncActions in running.ToList())
+ {
+ asyncActions.Kill();
+ }
+ }
+
public void EnqueueOffThreadAction(Action action)
{
lock (locker_offMain)
@@ -72,6 +87,7 @@ public void EnqueueOffThreadAction(Action action)
else
{
queuedOffThreadActions.Add(action);
+ waitHandle.Set();
}
}
}
@@ -151,6 +167,10 @@ private void OffMainThreadActionLoop()
{
action();
}
+ catch (ThreadAbortException)
+ {
+ throw;
+ }
catch (Exception er)
{
Log.Error(er.ToString());
@@ -158,7 +178,7 @@ private void OffMainThreadActionLoop()
}
else
{
- Thread.Sleep(1);
+ waitHandle.WaitOne();
}
}
}
diff --git a/Source/Rule56/AvoidanceTracker.cs b/Source/Rule56/AvoidanceTracker.cs
index 7ec75f9..3d0e89f 100644
--- a/Source/Rule56/AvoidanceTracker.cs
+++ b/Source/Rule56/AvoidanceTracker.cs
@@ -1,411 +1,482 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using CombatAI.Comps;
+using CombatAI.Squads;
+using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
namespace CombatAI
{
- public class AvoidanceTracker : MapComponent
- {
- private readonly HashSet _drawnCells = new HashSet();
- private readonly List _removalList = new List();
- private readonly AsyncActions asyncActions;
- private readonly IBuckets buckets;
- private readonly CellFlooder flooder;
+ public class AvoidanceTracker : MapComponent
+ {
+ private readonly HashSet _drawnCells = new HashSet();
+ private readonly List _removalList = new List();
+ private readonly AsyncActions asyncActions;
+ private readonly IBuckets buckets;
+ private readonly CellFlooder flooder;
- public IHeatGrid affliction_dmg;
- public IHeatGrid affliction_pen;
- public ITByteGrid path;
- public ITByteGrid proximity;
- public ITByteGrid shootLine;
+ public IHeatGrid affliction_dmg;
+ public IHeatGrid affliction_pen;
+ public ITByteGrid path;
+ public ITByteGrid path_squads;
+ public ITByteGrid proximity;
+ public ITByteGrid proximity_squads;
+ public ITByteGrid shootLine;
- private bool wait;
+ private bool wait;
- public AvoidanceTracker(Map map) : base(map)
- {
- path = new ITByteGrid(map);
- proximity = new ITByteGrid(map);
- shootLine = new ITByteGrid(map);
- buckets = new IBuckets(30);
- flooder = new CellFlooder(map);
- affliction_dmg = new IHeatGrid(map, 60, 64, 6);
- affliction_pen = new IHeatGrid(map, 60, 64, 6);
- asyncActions = new AsyncActions();
- }
+ public AvoidanceTracker(Map map) : base(map)
+ {
+ path = new ITByteGrid(map);
+ path_squads = new ITByteGrid(map);
+ proximity = new ITByteGrid(map);
+ proximity_squads = new ITByteGrid(map);
+ shootLine = new ITByteGrid(map);
+ buckets = new IBuckets(30);
+ flooder = new CellFlooder(map);
+ affliction_dmg = new IHeatGrid(map, 60, 64, 6);
+ affliction_pen = new IHeatGrid(map, 60, 64, 6);
+ asyncActions = new AsyncActions();
+ }
- public override void FinalizeInit()
- {
- base.FinalizeInit();
- asyncActions.Start();
- }
+ public override void FinalizeInit()
+ {
+ base.FinalizeInit();
+ asyncActions.Start();
+ }
- public override void MapComponentTick()
- {
- base.MapComponentTick();
- asyncActions.ExecuteMainThreadActions();
- if (wait)
- {
- return;
- }
- _removalList.Clear();
- List items = buckets.Next();
- for (int i = 0; i < items.Count; i++)
- {
- IBucketablePawn item = items[i];
- if (!Valid(item.pawn))
- {
- _removalList.Add(item.pawn);
- continue;
- }
- if (item.pawn.Downed || GenTicks.TicksGame - (item.pawn.needs?.rest?.lastRestTick ?? 0) < 30)
- {
- continue;
- }
- TryCastProximity(item.pawn, IntVec3.Invalid);
- if (item.pawn.pather?.MovingNow ?? false)
- {
- TryCastPath(item);
- }
- }
- for (int i = 0; i < _removalList.Count; i++)
- {
- DeRegister(_removalList[i]);
- }
- if (buckets.Index == 0)
- {
- wait = true;
- asyncActions.EnqueueOffThreadAction(() =>
- {
- proximity.NextCycle();
- path.NextCycle();
- wait = false;
- });
- }
- if (Finder.Settings.Debug_DrawAvoidanceGrid_Proximity)
- {
- if (Find.Selector.SelectedPawns.NullOrEmpty())
- {
- IntVec3 center = UI.MouseMapPosition().ToIntVec3();
- if (center.InBounds(map))
- {
- for (int i = center.x - 64; i < center.x + 64; i++)
- {
- for (int j = center.z - 64; j < center.z + 64; j++)
- {
- IntVec3 cell = new IntVec3(i, 0, j);
- if (cell.InBounds(map))
- {
- int value = path.Get(cell) + proximity.Get(cell);
- if (value > 0)
- {
- map.debugDrawer.FlashCell(cell, Mathf.Clamp(value / 10f, 0f, 0.99f), $"{path.Get(cell)} {proximity.Get(cell)}", 15);
- }
- }
- }
- }
- }
- }
- else
- {
- _drawnCells.Clear();
- foreach (Pawn pawn in Find.Selector.SelectedPawns)
- {
- TryGetReader(pawn, out AvoidanceReader reader);
- if (reader != null)
- {
- IntVec3 center = pawn.Position;
- if (center.InBounds(map))
- {
- for (int i = center.x - 64; i < center.x + 64; i++)
- {
- for (int j = center.z - 64; j < center.z + 64; j++)
- {
- IntVec3 cell = new IntVec3(i, 0, j);
- if (cell.InBounds(map) && !_drawnCells.Contains(cell))
- {
- _drawnCells.Add(cell);
- int value = reader.GetPath(cell) + reader.GetProximity(cell);
- if (value > 0)
- {
- map.debugDrawer.FlashCell(cell, Mathf.Clamp(value / 10f, 0f, 0.99f), $"{reader.GetPath(cell)} {reader.GetProximity(cell)}", 15);
- }
- }
- }
- }
- }
- }
- }
- }
- }
- if (Finder.Settings.Debug_DrawAvoidanceGrid_Danger)
- {
- IntVec3 center = UI.MouseMapPosition().ToIntVec3();
- if (center.InBounds(map))
- {
- for (int i = center.x - 64; i < center.x + 64; i++)
- {
- for (int j = center.z - 64; j < center.z + 64; j++)
- {
- IntVec3 cell = new IntVec3(i, 0, j);
- if (cell.InBounds(map))
- {
- float value = affliction_pen.Get(cell) + affliction_dmg.Get(cell);
- if (value > 0)
- {
- map.debugDrawer.FlashCell(cell, Mathf.Clamp(value / 32f, 0f, 0.99f), $"{Math.Round(affliction_pen.Get(cell), 1)} {Math.Round(affliction_dmg.Get(cell), 1)}", 15);
- }
- }
- }
- }
- }
- }
- }
+ public override void MapComponentUpdate()
+ {
+ base.MapComponentUpdate();
+ asyncActions.ExecuteMainThreadActions();
+ if (wait)
+ {
+ return;
+ }
+ _removalList.Clear();
+ List items = buckets.Next();
+ for (int i = 0; i < items.Count; i++)
+ {
+ IBucketablePawn item = items[i];
+ if (!Valid(item.pawn))
+ {
+ _removalList.Add(item.pawn);
+ continue;
+ }
+ if (item.pawn.Downed || GenTicks.TicksGame - (item.pawn.needs?.rest?.lastRestTick ?? 0) < 30)
+ {
+ continue;
+ }
+ TryCastProximity(item.pawn, IntVec3.Invalid);
+ if (item.pawn.pather?.MovingNow ?? false)
+ {
+ TryCastPath(item);
+ }
+ }
+ for (int i = 0; i < _removalList.Count; i++)
+ {
+ DeRegister(_removalList[i]);
+ }
+ if (buckets.Index == 0)
+ {
+ wait = true;
+ asyncActions.EnqueueOffThreadAction(() =>
+ {
+ proximity.NextCycle();
+ proximity_squads.NextCycle();
+ path.NextCycle();
+ path_squads.NextCycle();
+ wait = false;
+ });
+ }
+ }
- public bool TryGetReader(Pawn pawn, out AvoidanceReader reader)
- {
- reader = null;
- if (buckets.ContainsId(pawn.thingIDNumber))
- {
- reader = new AvoidanceReader(this, pawn.GetThingFlags());
- reader.proximity = proximity;
- reader.path = path;
- reader.affliction_dmg = affliction_dmg;
- reader.affliction_pen = affliction_pen;
- return true;
- }
- return false;
- }
+ public override void MapComponentTick()
+ {
+ base.MapComponentTick();
+ if (Finder.Settings.Debug_DrawAvoidanceGrid_Proximity)
+ {
+ if (Find.Selector.SelectedPawns.NullOrEmpty())
+ {
+ IntVec3 center = UI.MouseMapPosition().ToIntVec3();
+ if (center.InBounds(map))
+ {
+ for (int i = center.x - 64; i < center.x + 64; i++)
+ {
+ for (int j = center.z - 64; j < center.z + 64; j++)
+ {
+ IntVec3 cell = new IntVec3(i, 0, j);
+ if (cell.InBounds(map))
+ {
+ int value = Maths.Max(path_squads.Get(cell), path.Get(cell)) + Maths.Max(proximity_squads.Get(cell), proximity.Get(cell));
+ if (value > 0)
+ {
+ map.debugDrawer.FlashCell(cell, Mathf.Clamp(value / 10f, 0f, 0.99f), $"{Maths.Max(path_squads.Get(cell), path.Get(cell))} {Maths.Max(proximity_squads.Get(cell), proximity.Get(cell))}", 15);
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ _drawnCells.Clear();
+ foreach (Pawn pawn in Find.Selector.SelectedPawns)
+ {
+ TryGetReader(pawn, out AvoidanceReader reader);
+ if (reader != null)
+ {
+ IntVec3 center = pawn.Position;
+ if (center.InBounds(map))
+ {
+ for (int i = center.x - 64; i < center.x + 64; i++)
+ {
+ for (int j = center.z - 64; j < center.z + 64; j++)
+ {
+ IntVec3 cell = new IntVec3(i, 0, j);
+ if (cell.InBounds(map) && !_drawnCells.Contains(cell))
+ {
+ _drawnCells.Add(cell);
+ int value = reader.GetPath(cell) + reader.GetProximity(cell);
+ if (value > 0)
+ {
+ map.debugDrawer.FlashCell(cell, Mathf.Clamp(value / 10f, 0f, 0.99f), $"{reader.GetPath(cell)} {reader.GetProximity(cell)}", 15);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (Finder.Settings.Debug_DrawAvoidanceGrid_Danger)
+ {
+ IntVec3 center = UI.MouseMapPosition().ToIntVec3();
+ if (center.InBounds(map))
+ {
+ for (int i = center.x - 64; i < center.x + 64; i++)
+ {
+ for (int j = center.z - 64; j < center.z + 64; j++)
+ {
+ IntVec3 cell = new IntVec3(i, 0, j);
+ if (cell.InBounds(map))
+ {
+ float value = affliction_pen.Get(cell) + affliction_dmg.Get(cell);
+ if (value > 0)
+ {
+ map.debugDrawer.FlashCell(cell, Mathf.Clamp(value / 32f, 0f, 0.99f), $"{Math.Round(affliction_pen.Get(cell), 1)} {Math.Round(affliction_dmg.Get(cell), 1)}", 15);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
- public void Register(Pawn pawn)
- {
- if (buckets.ContainsId(pawn.thingIDNumber))
- {
- buckets.RemoveId(pawn.thingIDNumber);
- }
- if (Valid(pawn))
- {
- buckets.Add(new IBucketablePawn(pawn, pawn.thingIDNumber % buckets.numBuckets));
- }
- }
+ public bool TryGetReader(Pawn pawn, out AvoidanceReader reader)
+ {
+ reader = null;
+ if (buckets.ContainsId(pawn.thingIDNumber))
+ {
+ reader = new AvoidanceReader(this, pawn.GetThingFlags(), pawn.GetThingFlags());
+ reader.proximity = proximity;
+ reader.path = path;
+ if (!pawn.Faction.IsPlayerSafe())
+ {
+ reader.path_squads = path_squads;
+ reader.proximity_squads = proximity_squads;
+ }
+ reader.affliction_dmg = affliction_dmg;
+ reader.affliction_pen = affliction_pen;
+ return true;
+ }
+ return false;
+ }
- public override void MapRemoved()
- {
- asyncActions.Kill();
- buckets.Release();
- base.MapRemoved();
- }
+ public void Register(Pawn pawn)
+ {
+ if (buckets.ContainsId(pawn.thingIDNumber))
+ {
+ buckets.RemoveId(pawn.thingIDNumber);
+ }
+ if (Valid(pawn))
+ {
+ buckets.Add(new IBucketablePawn(pawn, pawn.thingIDNumber % buckets.numBuckets));
+ }
+ }
- public void DeRegister(Pawn pawn)
- {
- buckets.RemoveId(pawn.thingIDNumber);
- }
+ public override void MapRemoved()
+ {
+ asyncActions.Kill();
+ buckets.Release();
+ base.MapRemoved();
+ }
- public void Notify_Injury(Pawn pawn, DamageInfo dinfo)
- {
- IntVec3 loc = pawn.Position;
- asyncActions.EnqueueOffThreadAction(() =>
- {
- flooder.Flood(loc, node =>
- {
- float f = Maths.Max(1 - node.dist / 5.65685424949f, 0.25f);
- affliction_dmg.Push(node.cell, dinfo.Amount * f);
- affliction_pen.Push(node.cell, dinfo.ArmorPenetrationInt * f);
- }, maxDist: 30, maxCellNum: 25, passThroughDoors: true);
- });
- }
+ public void DeRegister(Pawn pawn)
+ {
+ buckets.RemoveId(pawn.thingIDNumber);
+ }
- public void Notify_Death(Pawn pawn, IntVec3 cell)
- {
- }
+ public void Notify_Injury(Pawn pawn, DamageInfo dinfo)
+ {
+ IntVec3 loc = pawn.Position;
+ asyncActions.EnqueueOffThreadAction(() =>
+ {
+ flooder.Flood(loc, node =>
+ {
+ float f = Maths.Max(1 - node.dist / 5.65685424949f, 0.25f);
+ affliction_dmg.Push(node.cell, dinfo.Amount * f);
+ affliction_pen.Push(node.cell, dinfo.ArmorPenetrationInt * f);
+ }, maxDist: 30, maxCellNum: 25, passThroughDoors: true);
+ });
+ }
- public void Notify_PathFound(Pawn pawn, PawnPath path)
- {
- }
+ public void Notify_Death(Pawn pawn, IntVec3 cell)
+ {
+ }
- public void Notify_CoverPositionSelected(Pawn pawn, IntVec3 cell)
- {
- }
+ public void Notify_PathFound(Pawn pawn, PawnPath path)
+ {
+ }
- private void TryCastProximity(Pawn pawn, IntVec3 dest)
- {
- IntVec3 orig;
- orig = pawn.Position;
- ulong flags = pawn.GetThingFlags();
- if (pawn.pather?.MovingNow == true && (dest.IsValid || (dest = pawn.pather.Destination.Cell).IsValid))
- {
- asyncActions.EnqueueOffThreadAction(() =>
- {
- proximity.Next();
- flooder.Flood(orig, node =>
- {
- proximity.Set(node.cell, 1, flags);
- }, maxDist: 4, maxCellNum: 9, passThroughDoors: true);
- flooder.Flood(dest, node =>
- {
- proximity.Set(node.cell, 1, flags);
- }, maxDist: 4, maxCellNum: 9, passThroughDoors: true);
- });
- }
- else
- {
- asyncActions.EnqueueOffThreadAction(() =>
- {
- proximity.Next();
- flooder.Flood(orig, node =>
- {
- proximity.Set(node.cell, 1, flags);
- }, maxDist: 4, maxCellNum: 9, passThroughDoors: true);
- });
- }
- }
+ public void Notify_CoverPositionSelected(Pawn pawn, IntVec3 cell)
+ {
+ }
- private void TryCastPath(IBucketablePawn item, PawnPath pawnPath = null)
- {
- Pawn pawn = item.pawn;
- pawnPath ??= pawn.pather?.curPath;
- if (pawnPath?.nodes == null || pawnPath.curNodeIndex <= 5)
- {
- return;
- }
- ulong flags = pawn.GetThingFlags();
- List cells = item.tempPath;
- cells.Clear();
- int index = Maths.Max(pawnPath.curNodeIndex - 90, 0);
- int limit = Maths.Min(index + 80, pawnPath.curNodeIndex + 1);
- for (int i = index; i < limit; i++)
- {
- cells.Add(pawnPath.nodes[i]);
- }
-// cells.AddRange(pawnPath.nodes.GetRange(Maths.Max(pawnPath.curNodeIndex - 80, 0), Maths.Min(pawnPath.curNodeIndex + 1, 80)));
- if (cells.Count == 0)
- {
- return;
- }
- WallGrid walls = map.GetComp_Fast();
- asyncActions.EnqueueOffThreadAction(() =>
- {
- path.Next();
- IntVec3 prev = cells[0];
- for (int i = 1; i < cells.Count; i++)
- {
- IntVec3 cur = cells[i];
- int dx = Math.Sign(prev.x - cur.x);
- int dz = Math.Sign(prev.z - cur.z);
- int val = 1;
- IntVec3 left;
- IntVec3 right;
- if (dx == 0)
- {
- left = cur + new IntVec3(-1, 0, 0);
- right = cur + new IntVec3(1, 0, 0);
- }
- else if (dz == 0)
- {
- left = cur + new IntVec3(0, 0, -1);
- right = cur + new IntVec3(0, 0, 1);
- }
- else
- {
- left = cur + new IntVec3(dx, 0, 0);
- right = cur + new IntVec3(0, 0, dz);
- }
- if (!left.InBounds(map) || walls.GetFillCategory(left) == FillCategory.Full)
- {
- val++;
- }
- if (!right.InBounds(map) || walls.GetFillCategory(right) == FillCategory.Full)
- {
- val++;
- }
- path.Set(left, (byte)val, flags);
- path.Set(right, (byte)val, flags);
- path.Set(cur, (byte)val, flags);
- prev = cur;
- }
- cells.Clear();
- });
- }
+ private void TryCastProximity(Pawn pawn, IntVec3 dest)
+ {
+ IntVec3 orig;
+ orig = pawn.Position;
+ ulong flags = pawn.GetThingFlags();
+ if (pawn.pather?.MovingNow == true && (dest.IsValid || (dest = pawn.pather.Destination.Cell).IsValid))
+ {
+ asyncActions.EnqueueOffThreadAction(() =>
+ {
+ proximity.Next();
+ proximity_squads.Next();
+ flooder.Flood(orig, node =>
+ {
+ proximity.Set(node.cell, 1, flags);
+ }, maxDist: 4, maxCellNum: 9, passThroughDoors: true);
+ flooder.Flood(dest, node =>
+ {
+ proximity.Set(node.cell, 1, flags);
+ }, maxDist: 4, maxCellNum: 9, passThroughDoors: true);
+ });
+ }
+ else
+ {
+ asyncActions.EnqueueOffThreadAction(() =>
+ {
+ proximity.Next();
+ flooder.Flood(orig, node =>
+ {
+ proximity.Set(node.cell, 1, flags);
+ }, maxDist: 4, maxCellNum: 9, passThroughDoors: true);
+ });
+ }
+ }
- private void TryCastShootLine(Pawn pawn)
- {
+ private void TryCastPath(IBucketablePawn item, PawnPath pawnPath = null)
+ {
+ Pawn pawn = item.pawn;
+ pawnPath ??= pawn.pather?.curPath;
+ if (pawnPath?.nodes == null || pawnPath.curNodeIndex <= 5)
+ {
+ return;
+ }
+ ulong flags = pawn.GetThingFlags();
+ List cells = item.tempPath;
+ List cells2 = item.tempPathSquad;
+ cells.Clear();
+ cells2.Clear();
+ int index = Maths.Max(pawnPath.curNodeIndex - 90, 0);
+ int limit = Maths.Min(index + 80, pawnPath.curNodeIndex + 1);
+ for (int i = index; i < limit; i++)
+ {
+ cells.Add(pawnPath.nodes[i]);
+ }
+ if (cells.Count == 0)
+ {
+ return;
+ }
+ int width = Finder.Settings.Pathfinding_SquadPathWidth;
+ ulong sflags = flags;
+ ThingComp_CombatAI comp = pawn.AI();
+ Squad squad = null;
+ if (comp != null)
+ {
+ squad = comp.squad;
+ if (squad != null)
+ {
+ width = Maths.Max(width, squad.members.Count);
+ sflags = squad.GetSquadFlags();
+ }
+ }
+ WallGrid walls = map.GetComp_Fast();
+ asyncActions.EnqueueOffThreadAction(() =>
+ {
+ path.Next();
+ path_squads.Next();
+ IntVec3 prev = cells[0];
+ for (int i = 1; i < cells.Count; i++)
+ {
+ IntVec3 cur = cells[i];
+ int dx = Math.Sign(prev.x - cur.x);
+ int dz = Math.Sign(prev.z - cur.z);
+ int val = 1;
+ IntVec3 leftOffset;
+ IntVec3 rightOffset;
+ if (dx == 0)
+ {
+ leftOffset = new IntVec3(-1, 0, 0);
+ rightOffset = new IntVec3(1, 0, 0);
+ }
+ else if (dz == 0)
+ {
+ leftOffset = new IntVec3(0, 0, -1);
+ rightOffset = new IntVec3(0, 0, 1);
+ }
+ else
+ {
+ leftOffset = new IntVec3(dx, 0, 0);
+ rightOffset = new IntVec3(0, 0, dz);
+ }
+ IntVec3 left = cur + leftOffset;
+ IntVec3 right = cur + rightOffset;
+ if (!left.InBounds(map) || walls.GetFillCategory(left) == FillCategory.Full)
+ {
+ val++;
+ }
+ if (!right.InBounds(map) || walls.GetFillCategory(right) == FillCategory.Full)
+ {
+ val++;
+ }
+ path.Set(left, (byte)val, flags);
+ path.Set(right, (byte)val, flags);
+ path.Set(cur, (byte)val, flags);
+ prev = cur;
+ if (sflags != 0)
+ {
+ cells2.Clear();
+ for (int j = 2; j <= width; j++)
+ {
+ IntVec3 l = cur + leftOffset * j;
+ if (!l.InBounds(map) || walls.GetFillCategory(l) == FillCategory.Full)
+ {
+ val++;
+ }
+ IntVec3 r = cur + rightOffset * j;
+ if (!r.InBounds(map) || walls.GetFillCategory(r) == FillCategory.Full)
+ {
+ val++;
+ }
+ cells2.Add(r);
+ cells2.Add(l);
+ }
+ for (int j = 0; j < cells2.Count; j++)
+ {
+ path_squads.Set(cells2[j], (byte)val, sflags);
+ }
+ path_squads.Set(left, (byte)val, flags);
+ path_squads.Set(right, (byte)val, flags);
+ path_squads.Set(cur, (byte)val, flags);
+ }
+ }
+ cells.Clear();
+ cells2.Clear();
+ });
+ }
- }
+ private void TryCastShootLine(Pawn pawn)
+ {
- private bool Valid(Pawn pawn)
- {
- return !pawn.Destroyed && pawn.Spawned && !pawn.Dead && (pawn.RaceProps.Humanlike || pawn.RaceProps.IsMechanoid);
- }
+ }
- private struct IBucketablePawn : IBucketable
- {
- public readonly Pawn pawn;
- public readonly int bucketIndex;
- public readonly List tempPath;
+ private bool Valid(Pawn pawn)
+ {
+ return !pawn.Destroyed && pawn.Spawned && !pawn.Dead && (pawn.RaceProps.Humanlike || pawn.RaceProps.IsMechanoid);
+ }
- public int BucketIndex
- {
- get => bucketIndex;
- }
- public int UniqueIdNumber
- {
- get => pawn.thingIDNumber;
- }
+ private struct IBucketablePawn : IBucketable
+ {
+ public readonly Pawn pawn;
+ public readonly int bucketIndex;
+ public readonly List tempPath;
+ public readonly List tempPathSquad;
- public IBucketablePawn(Pawn pawn, int bucketIndex)
- {
- this.pawn = pawn;
- this.bucketIndex = bucketIndex;
- tempPath = new List(64);
- }
- }
+ public int BucketIndex
+ {
+ get => bucketIndex;
+ }
+ public int UniqueIdNumber
+ {
+ get => pawn.thingIDNumber;
+ }
- public class AvoidanceReader
- {
- private readonly ulong iflags;
- private readonly CellIndices indices;
- public IHeatGrid affliction_dmg;
- public IHeatGrid affliction_pen;
+ public IBucketablePawn(Pawn pawn, int bucketIndex)
+ {
+ this.pawn = pawn;
+ this.bucketIndex = bucketIndex;
+ tempPath = new List(64);
+ tempPathSquad = new List();
+ }
+ }
- public ITByteGrid path;
- public ITByteGrid proximity;
+ public class AvoidanceReader
+ {
+ private readonly ulong iflags;
+ private readonly ulong sflags;
+ private readonly CellIndices indices;
+ public IHeatGrid affliction_dmg;
+ public IHeatGrid affliction_pen;
- public AvoidanceReader(AvoidanceTracker tracker, ulong iflags)
- {
- ;
- indices = tracker.map.cellIndices;
- this.iflags = iflags;
- }
+ public ITByteGrid path;
+ public ITByteGrid path_squads;
+ public ITByteGrid proximity;
+ public ITByteGrid proximity_squads;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public float GetDanger(IntVec3 cell)
- {
- return GetDanger(indices.CellToIndex(cell));
- }
- public float GetDanger(int index)
- {
- return affliction_dmg.Get(index) + affliction_pen.Get(index);
- }
+ public AvoidanceReader(AvoidanceTracker tracker, ulong iflags, ulong sflags)
+ {
+ ;
+ indices = tracker.map.cellIndices;
+ this.iflags = iflags;
+ this.sflags = sflags;
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int GetProximity(IntVec3 cell)
- {
- return GetProximity(indices.CellToIndex(cell));
- }
- public int GetProximity(int index)
- {
- return Maths.Max(proximity.Get(index) - ((proximity.GetFlags(index) & iflags) != 0 ? 1 : 0), 0);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int GetPath(IntVec3 cell)
- {
- return GetPath(indices.CellToIndex(cell));
- }
- public int GetPath(int index)
- {
- return Maths.Max(path.Get(index) - ((path.GetFlags(index) & iflags) != 0 ? 1 : 0), 0);
- }
- }
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float GetDanger(IntVec3 cell)
+ {
+ return GetDanger(indices.CellToIndex(cell));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float GetDanger(int index)
+ {
+ return affliction_dmg.Get(index) + affliction_pen.Get(index);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetProximity(IntVec3 cell)
+ {
+ return GetProximity(indices.CellToIndex(cell));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetProximity(int index)
+ {
+ return Maths.Max(proximity.Get(index) - ((proximity.GetFlags(index) & iflags) != 0 ? 1 : 0), proximity_squads != null ? proximity_squads.Get(index) - ((proximity_squads.GetFlags(index) & sflags) != 0 ? 1 : 0) : 0, 0);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetPath(IntVec3 cell)
+ {
+ return GetPath(indices.CellToIndex(cell));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetPath(int index)
+ {
+ return Maths.Max(path.Get(index) - ((path.GetFlags(index) & iflags) != 0 ? 1 : 0), path_squads != null ? path_squads.Get(index) - ((path_squads.GetFlags(index) & sflags) != 0 ? 1 : 0) : 0, 0);
+ }
+ }
+ }
}
diff --git a/Source/Rule56/Cache/TCacheHelper.cs b/Source/Rule56/Cache/TCacheHelper.cs
index 3d3012e..5138333 100644
--- a/Source/Rule56/Cache/TCacheHelper.cs
+++ b/Source/Rule56/Cache/TCacheHelper.cs
@@ -1,10 +1,16 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using HarmonyLib;
+using RimWorld;
+using Verse;
namespace CombatAI
{
public static class TCacheHelper
{
- public static readonly List clearFuncs = new List();
+ public static readonly List clearFuncs = new List();
public static void ClearCache()
{
@@ -14,5 +20,41 @@ public static void ClearCache()
action();
}
}
+
+ internal static class IndexGetter where T : notnull
+ {
+ internal static readonly bool indexable;
+ internal static readonly unsafe delegate* Default;
+
+ static unsafe IndexGetter()
+ {
+ foreach (MethodInfo method in typeof(Methods).GetMethods(AccessTools.all))
+ {
+ if (method.ReturnType == typeof(int))
+ {
+ ParameterInfo[] parameterInfos = method.GetParameters();
+ if (parameterInfos.Length == 1 && parameterInfos[0].ParameterType.IsAssignableFrom(typeof(T)))
+ {
+ Default = (delegate*) method.MethodHandle.GetFunctionPointer();
+ indexable = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private static class Methods
+ {
+ public static int Def(Def def) => def.index;
+ public static int Thing(Thing thing) => thing.thingIDNumber;
+ public static int ThingComp(ThingComp comp) => comp.parent.thingIDNumber;
+ public static int Map(Map map) => map.uniqueID;
+ public static int DesignationManager(DesignationManager manager) => manager.map.uniqueID;
+ public static int Room(Room room) => room.ID;
+ public static int Ideo(Ideo ideo) => ideo.id;
+ public static int HediffSet(HediffSet set) => set.pawn.thingIDNumber;
+ public static int Bill(Bill bill) => bill.loadID;
+ }
}
}
diff --git a/Source/Rule56/Cache/TKCache.cs b/Source/Rule56/Cache/TKCache.cs
index 41fa9d1..04313a5 100644
--- a/Source/Rule56/Cache/TKCache.cs
+++ b/Source/Rule56/Cache/TKCache.cs
@@ -1,25 +1,77 @@
using System.Runtime.CompilerServices;
+using HarmonyLib;
+using Verse;
namespace CombatAI
{
public static class TKCache
{
- public static readonly CachedDict cache = new CachedDict(512);
+ private static readonly CachedDict dict_indexed;
+ private static readonly CachedDict dict;
+ private static readonly unsafe delegate* getter;
+ private static readonly unsafe delegate* setter;
- static TKCache()
+ static unsafe TKCache()
{
- TCacheHelper.clearFuncs.Add(() => cache.Clear());
+ if (TCacheHelper.IndexGetter.indexable)
+ {
+ if (Finder.Settings.Debug)
+ {
+ Log.Message($"ISMA: {typeof(T)} using indexed cached!");
+ }
+ dict_indexed = new CachedDict(128);
+ TCacheHelper.clearFuncs.Add(dict_indexed.Clear);
+ getter = (delegate*) typeof(TKCache).GetMethod("TryGet_Indexed", AccessTools.all).MethodHandle.GetFunctionPointer();
+ setter = (delegate*)typeof(TKCache).GetMethod("Put_Indexed", AccessTools.all).MethodHandle.GetFunctionPointer();
+ }
+ else
+ {
+ dict = new CachedDict(128);
+ TCacheHelper.clearFuncs.Add(dict.Clear);
+ getter = (delegate*) typeof(TKCache).GetMethod("TryGet_Default", AccessTools.all).MethodHandle.GetFunctionPointer();
+ setter = (delegate*)typeof(TKCache).GetMethod("Put_Default", AccessTools.all).MethodHandle.GetFunctionPointer();
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool TryGet(T key, out K value, int expiry = -1)
+ public static bool TryGet(T key, out K val, int expiry = -1)
{
- return cache.TryGetValue(key, out value, expiry);
+ unsafe
+ {
+ return getter(key, out val, expiry);
+ }
}
-
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Put(T key, K value)
{
- cache[key] = value;
+ unsafe
+ {
+ setter(key, value);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe bool TryGet_Indexed(T key, out K val, int expiry)
+ {
+ return dict_indexed.TryGetValue(TCacheHelper.IndexGetter.Default(key), out val, expiry);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe bool TryGet_Default(T key, out K val, int expiry)
+ {
+ return dict.TryGetValue(key, out val, expiry);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe void Put_Indexed(T key, K val)
+ {
+ dict_indexed[TCacheHelper.IndexGetter.Default(key)] = val;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe void Put_Default(T key, K val)
+ {
+ dict[key] = val;
}
}
}
diff --git a/Source/Rule56/Cache/TKVCache.cs b/Source/Rule56/Cache/TKVCache.cs
index ccfe066..6e63a24 100644
--- a/Source/Rule56/Cache/TKVCache.cs
+++ b/Source/Rule56/Cache/TKVCache.cs
@@ -1,25 +1,78 @@
using System.Runtime.CompilerServices;
+using HarmonyLib;
+using UnityEngine;
+using Verse;
namespace CombatAI
{
public static class TKVCache
{
- public static readonly CachedDict cache = new CachedDict(512);
+ private static readonly CachedDict dict_indexed;
+ private static readonly CachedDict dict;
+ private static readonly unsafe delegate* getter;
+ private static readonly unsafe delegate* setter;
- static TKVCache()
+ static unsafe TKVCache()
{
- TCacheHelper.clearFuncs.Add(() => cache.Clear());
+ if (TCacheHelper.IndexGetter.indexable)
+ {
+ if (Finder.Settings.Debug)
+ {
+ Log.Message($"ISMA: {typeof(T)} using indexed cached!");
+ }
+ dict_indexed = new CachedDict(128);
+ TCacheHelper.clearFuncs.Add(dict_indexed.Clear);
+ getter = (delegate*) typeof(TKVCache).GetMethod("TryGet_Indexed", AccessTools.all).MethodHandle.GetFunctionPointer();
+ setter = (delegate*)typeof(TKVCache).GetMethod("Put_Indexed", AccessTools.all).MethodHandle.GetFunctionPointer();
+ }
+ else
+ {
+ dict = new CachedDict(128);
+ TCacheHelper.clearFuncs.Add(dict.Clear);
+ getter = (delegate*) typeof(TKVCache).GetMethod("TryGet_Default", AccessTools.all).MethodHandle.GetFunctionPointer();
+ setter = (delegate*)typeof(TKVCache).GetMethod("Put_Default", AccessTools.all).MethodHandle.GetFunctionPointer();
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool TryGet(T key, out V value, int expiry = -1)
+ public static bool TryGet(T key, out V val, int expiry = -1)
{
- return cache.TryGetValue(key, out value, expiry);
+ unsafe
+ {
+ return getter(key, out val, expiry);
+ }
}
-
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Put(T key, V value)
{
- cache[key] = value;
+ unsafe
+ {
+ setter(key, value);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe bool TryGet_Indexed(T key, out V val, int expiry)
+ {
+ return dict_indexed.TryGetValue(TCacheHelper.IndexGetter.Default(key), out val, expiry);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe bool TryGet_Default(T key, out V val, int expiry)
+ {
+ return dict.TryGetValue(key, out val, expiry);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe void Put_Indexed(T key, V val)
+ {
+ dict_indexed[TCacheHelper.IndexGetter.Default(key)] = val;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe void Put_Default(T key, V val)
+ {
+ dict[key] = val;
}
}
}
diff --git a/Source/Rule56/CacheUtility.cs b/Source/Rule56/CacheUtility.cs
new file mode 100644
index 0000000..95fb0d2
--- /dev/null
+++ b/Source/Rule56/CacheUtility.cs
@@ -0,0 +1,46 @@
+using CombatAI.Patches;
+using Verse;
+namespace CombatAI
+{
+ public class CacheUtility
+ {
+ public static void ClearAllCache(bool mapRemoved = false)
+ {
+ TCacheHelper.ClearCache();
+ StatCache.ClearCache();
+ CompCache.ClearCaches();
+ SightUtility.ClearCache();
+ JobGiver_AITrashBuildingsDistant_Patch.ClearCache();
+ GenSight_Patch.ClearCache();
+ MetaCombatAttributeUtility.ClearCache();
+ LordToil_AssaultColony_Patch.ClearCache();
+ AttackTargetFinder_Patch.ClearCache();
+ TrashUtility_Patch.ClearCache();
+ if (mapRemoved)
+ {
+ DamageUtility.ClearCache();
+ ArmorUtility.ClearCache();
+ }
+ }
+
+ public static void ClearShortCache()
+ {
+ TrashUtility_Patch.ClearCache();
+ TCacheHelper.ClearCache();
+ StatCache.ClearCache();
+ CompCache.ClearCaches();
+ SightUtility.ClearCache();
+ JobGiver_AITrashBuildingsDistant_Patch.ClearCache();
+ GenSight_Patch.ClearCache();
+ MetaCombatAttributeUtility.ClearCache();
+ LordToil_AssaultColony_Patch.ClearCache();
+ AttackTargetFinder_Patch.ClearCache();
+ }
+
+ public static void ClearThingCache(Thing thing)
+ {
+ DamageUtility.Invalidate(thing);
+ ArmorUtility.Invalidate(thing);
+ }
+ }
+}
diff --git a/Source/Rule56/Collections/CachedDict.cs b/Source/Rule56/Collections/CachedDict.cs
index 70ac2e4..381d056 100644
--- a/Source/Rule56/Collections/CachedDict.cs
+++ b/Source/Rule56/Collections/CachedDict.cs
@@ -30,7 +30,7 @@ public bool IsValid(int expiry = 0)
public class CachedDict
{
- private const int MAX_CACHE_SIZE = 10000;
+ private const int MAX_CACHE_SIZE = 2000;
private readonly bool autoCleanUp;
private readonly Dictionary> cache;
diff --git a/Source/Rule56/Collections/Controlled/ControlledCollectionTracker.cs b/Source/Rule56/Collections/Controlled/ControlledCollectionTracker.cs
new file mode 100644
index 0000000..1c966f8
--- /dev/null
+++ b/Source/Rule56/Collections/Controlled/ControlledCollectionTracker.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+namespace CombatAI
+{
+ public class ControlledCollectionTracker
+ {
+ public static void Register(ICollection collection)
+ {
+ }
+
+ public abstract class ITCollection
+ {
+ public abstract bool IsValid { get; }
+ public abstract bool TryGetCount(out int count);
+ }
+
+ public class ITList
+ {
+ }
+ }
+}
diff --git a/Source/Rule56/Collections/Controlled/TDictionary.cs b/Source/Rule56/Collections/Controlled/TDictionary.cs
new file mode 100644
index 0000000..9b2c41b
--- /dev/null
+++ b/Source/Rule56/Collections/Controlled/TDictionary.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+namespace CombatAI
+{
+ public sealed class TDictionary : Dictionary
+ {
+ public TDictionary() : base()
+ {
+ Register();
+ }
+
+ public TDictionary(int size) : base(size)
+ {
+ Register();
+ }
+
+ private void Register()
+ {
+ ControlledCollectionTracker.Register(this);
+ }
+ }
+}
diff --git a/Source/Rule56/Collections/Controlled/THashSet.cs b/Source/Rule56/Collections/Controlled/THashSet.cs
new file mode 100644
index 0000000..98078a2
--- /dev/null
+++ b/Source/Rule56/Collections/Controlled/THashSet.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+namespace CombatAI
+{
+ public sealed class THashSet : HashSet
+ {
+ public THashSet() : base()
+ {
+ Register();
+ }
+
+ public THashSet(int size) : base(size)
+ {
+ Register();
+ }
+
+ private void Register()
+ {
+ ControlledCollectionTracker.Register(this);
+ }
+ }
+}
diff --git a/Source/Rule56/Collections/Controlled/TList.cs b/Source/Rule56/Collections/Controlled/TList.cs
new file mode 100644
index 0000000..82dbb42
--- /dev/null
+++ b/Source/Rule56/Collections/Controlled/TList.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+namespace CombatAI
+{
+ public sealed class TList : List
+ {
+ public TList() : base()
+ {
+ Register();
+ }
+
+ public TList(int size) : base(size)
+ {
+ Register();
+ }
+
+ private void Register()
+ {
+ ControlledCollectionTracker.Register(this);
+ }
+ }
+}
diff --git a/Source/Rule56/CombatAI.csproj b/Source/Rule56/CombatAI.csproj
index 88f0389..3ce4258 100644
--- a/Source/Rule56/CombatAI.csproj
+++ b/Source/Rule56/CombatAI.csproj
@@ -4,7 +4,7 @@
CombatAI
CombatAI
net472
- 8.0
+ latest
x64
..\..\1.4\Assemblies
true
@@ -35,8 +35,6 @@
-
-
@@ -63,6 +61,14 @@
+
+ TextTemplatingFileGenerator
+ Keyed.cs
+
+
+ TextTemplatingFileGenerator
+ Tex.cs
+
@@ -101,7 +107,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -111,6 +117,16 @@
+
+ True
+ True
+ Keyed.tt
+
+
+ True
+ True
+ Tex.tt
+
@@ -120,7 +136,7 @@
$(PkgMono_TextTransform)\tools\TextTransform.exe
-
+
@@ -163,7 +179,7 @@
- false
+ true
diff --git a/Source/Rule56/CombatAIMod.cs b/Source/Rule56/CombatAIMod.cs
index 4b4c63b..adf5294 100644
--- a/Source/Rule56/CombatAIMod.cs
+++ b/Source/Rule56/CombatAIMod.cs
@@ -16,6 +16,7 @@ public class CombatAIMod : Mod
private readonly Listing_Collapsible collapsible_basic = new Listing_Collapsible(true);
private readonly Listing_Collapsible collapsible_debug = new Listing_Collapsible(true);
private readonly Listing_Collapsible collapsible_fog = new Listing_Collapsible();
+ private readonly Listing_Collapsible collapsible_tech = new Listing_Collapsible();
private readonly Listing_Collapsible.Group_Collapsible collapsible_groupLeft = new Listing_Collapsible.Group_Collapsible();
private readonly Listing_Collapsible.Group_Collapsible collapsible_groupRight = new Listing_Collapsible.Group_Collapsible();
private readonly Listing_Collapsible collapsible_performance = new Listing_Collapsible(true);
@@ -76,11 +77,22 @@ private void FillCollapsible_Basic(Listing_Collapsible collapsible)
}, useMargins: false);
collapsible.Line(1);
}
+
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_CELean, ref Finder.Settings.LeanCE_Enabled);
collapsible.Line(1);
collapsible.Label(Keyed.CombatAI_Settings_Basic_Presets);
collapsible.Gap(1);
+ collapsible.Lambda(22, rect =>
+ {
+ if (Widgets.ButtonText(rect, Keyed.CombatAI_DefKindSettings_Title))
+ {
+ if (!Find.WindowStack.windows.Any(w => w is Window_DefKindSettings))
+ {
+ Find.WindowStack.Add(new Window_DefKindSettings());
+ }
+ }
+ });
collapsible.Label(Keyed.CombatAI_Settings_Basic_Presets_Description);
collapsible.Lambda(25, inRect =>
{
@@ -128,15 +140,11 @@ private void FillCollapsible_Basic(Listing_Collapsible collapsible)
{
Messages.Message(Keyed.CombatAI_Settings_Basic_PerformanceOpt_Warning, MessageTypeDefOf.CautionInput);
}
- collapsible.Line(1);
- collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_KillBoxKiller, ref Finder.Settings.Pather_KillboxKiller);
- collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Pather, ref Finder.Settings.Pather_Enabled);
+ collapsible.Line(1);
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_RandomizedPersonality, ref Finder.Settings.Personalities_Enabled);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Caster, ref Finder.Settings.Caster_Enabled);
- collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Temperature, ref Finder.Settings.Temperature_Enabled);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Targeter, ref Finder.Settings.Targeter_Enabled);
- collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Reaction, ref Finder.Settings.React_Enabled);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Flanking, ref Finder.Settings.Flank_Enabled);
- collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Retreat, ref Finder.Settings.Retreat_Enabled);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Groups, ref Finder.Settings.Enable_Groups, Keyed.CombatAI_Settings_Basic_Groups_Description);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Sprinting, ref Finder.Settings.Enable_Sprinting, Keyed.CombatAI_Settings_Basic_Sprinting_Description);
}
@@ -148,7 +156,11 @@ private void FillCollapsible_FogOfWar(Listing_Collapsible collapsible)
if (Finder.Settings.FogOfWar_Enabled)
{
collapsible.Line(1);
-
+ if (collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_FogOfWar_OldShader, ref Finder.Settings.FogOfWar_OldShader))
+ {
+ Messages.Message(R.Keyed.CombatAI_Settings_Basic_FogOfWar_OldShader_Restart, MessageTypeDefOf.CautionInput);
+ }
+ collapsible.Line(1);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_FogOfWar_Animals, ref Finder.Settings.FogOfWar_Animals);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_FogOfWar_Animals_SmartOnly, ref Finder.Settings.FogOfWar_AnimalsSmartOnly, disabled: !Finder.Settings.FogOfWar_Animals);
collapsible.Line(1);
@@ -168,7 +180,7 @@ private void FillCollapsible_FogOfWar(Listing_Collapsible collapsible)
collapsible.Label(Keyed.CombatAI_Settings_Basic_FogOfWar_RangeMul);
collapsible.Lambda(25, rect =>
{
- Finder.Settings.FogOfWar_RangeMultiplier = HorizontalSlider_NewTemp(rect, Finder.Settings.FogOfWar_RangeMultiplier, 0.75f, 2.0f, false, Keyed.CombatAI_Settings_Basic_FogOfWar_RangeMul_Readouts.Formatted(Finder.Settings.FogOfWar_RangeMultiplier.ToString()), 0.05f);
+ Finder.Settings.FogOfWar_RangeMultiplier = HorizontalSlider_NewTemp(rect, Finder.Settings.FogOfWar_RangeMultiplier, 0.75f, 8.0f, false, Keyed.CombatAI_Settings_Basic_FogOfWar_RangeMul_Readouts.Formatted(Finder.Settings.FogOfWar_RangeMultiplier.ToString()), 0.05f);
}, useMargins: true);
// collapsible.Line(1);
@@ -318,9 +330,58 @@ private void FillCollapsible_Advance(Listing_Collapsible collapsible)
{
Widgets.HorizontalSlider(rect, ref Finder.Settings.Pathfinding_SappingMul, new FloatRange(0.5f, 1.5f), Keyed.CombatAI_Settings_Basic_SappingMul);
}, useMargins: true);
+ collapsible.Line(1);
+ collapsible.Lambda(25, rect =>
+ {
+ float val = Finder.Settings.Pathfinding_SquadPathWidth;
+ Widgets.HorizontalSlider(rect, ref val, new FloatRange(1, 10), Keyed.CombatAI_Settings_Advance_SquadPathWidth_Description.Formatted(Finder.Settings.Pathfinding_SquadPathWidth));
+ Finder.Settings.Pathfinding_SquadPathWidth = Mathf.RoundToInt(Mathf.Clamp(val, 1, 10));
+ }, useMargins: true);
}
}
+ public void FillCollapsible_FactionTechSettings(Listing_Collapsible collapsible)
+ {
+ collapsible.Label(R.Keyed.CombatAI_Settings_FactionTech_Desciption);
+ collapsible.Line(1);
+ foreach (TechLevel tech in Enum.GetValues(typeof(TechLevel)))
+ {
+ Settings.FactionTechSettings techSettings = Finder.Settings.GetTechSettings(tech);
+ collapsible.Label(R.Keyed.CombatAI_Settings_FactionTech_Tech.Formatted(tech.ToStringHuman()));
+ collapsible.Gap(1);
+ collapsible.Lambda(25, rect =>
+ {
+ Widgets.HorizontalSlider(rect, ref techSettings.cover, new FloatRange(0.0f, 3.0f), Keyed.CombatAI_Settings_FactionTech_Cover.Formatted(Math.Round(techSettings.cover, 2)));
+ }, useMargins: true);
+ collapsible.Gap(1);
+ collapsible.Lambda(25, rect =>
+ {
+ Widgets.HorizontalSlider(rect, ref techSettings.retreat, new FloatRange(0.0f, 3.0f), Keyed.CombatAI_Settings_FactionTech_Retreat.Formatted(Math.Round(techSettings.retreat, 2)));
+ }, useMargins: true);
+ collapsible.Gap(1);
+ collapsible.Lambda(25, rect =>
+ {
+ Widgets.HorizontalSlider(rect, ref techSettings.pathing, new FloatRange(0.0f, 1.25f), Keyed.CombatAI_Settings_FactionTech_Pathing.Formatted(Math.Round(techSettings.pathing, 2)));
+ }, useMargins: true);
+ collapsible.Gap(1);
+ collapsible.Lambda(25, rect =>
+ {
+ Widgets.HorizontalSlider(rect, ref techSettings.duck, new FloatRange(0.0f, 3.0f), Keyed.CombatAI_Settings_FactionTech_Duck.Formatted(Math.Round(techSettings.duck, 2)));
+ }, useMargins: true);
+ collapsible.Gap(1);
+ collapsible.Lambda(25, rect =>
+ {
+ Widgets.HorizontalSlider(rect, ref techSettings.group, new FloatRange(0.0f, 3.0f), Keyed.CombatAI_Settings_FactionTech_Group.Formatted(Math.Round(techSettings.group, 2)));
+ }, useMargins: true);
+ collapsible.Gap(1);
+ collapsible.Lambda(25, rect =>
+ {
+ Widgets.HorizontalSlider(rect, ref techSettings.sapping, new FloatRange(0.0f, 3.0f), Keyed.CombatAI_Settings_FactionTech_Sapping.Formatted(Math.Round(techSettings.sapping, 2)));
+ }, useMargins: true);
+ collapsible.Line(1);
+ }
+ }
+
private void FillCollapsible_Debugging(Listing_Collapsible collapsible)
{
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Debugging_Enable, ref Finder.Settings.Debug);
@@ -359,6 +420,9 @@ public override void DoSettingsWindowContents(Rect inRect)
collapsible_fog.Group = collapsible_groupLeft;
collapsible_groupLeft.Register(collapsible_fog);
+ collapsible_tech.Group = collapsible_groupLeft;
+ collapsible_groupLeft.Register(collapsible_tech);
+
collapsible_basic.Group = collapsible_groupLeft;
collapsible_groupLeft.Register(collapsible_basic);
collapsible_basic.Expanded = true;
@@ -377,7 +441,12 @@ public override void DoSettingsWindowContents(Rect inRect)
FillCollapsible_Basic(collapsible_basic);
collapsible_basic.End(ref rectLeft);
rectLeft.yMin += 5;
-
+
+ collapsible_tech.Begin(rectLeft, R.Keyed.CombatAI_Settings_FactionTech);
+ FillCollapsible_FactionTechSettings(collapsible_tech);
+ collapsible_tech.End(ref rectLeft);
+ rectLeft.yMin += 5;
+
collapsible_fog.Begin(rectLeft, Keyed.CombatAI_Settings_Basic_FogOfWar);
FillCollapsible_FogOfWar(collapsible_fog);
collapsible_fog.End(ref rectLeft);
diff --git a/Source/Rule56/CombatAI_Utility.cs b/Source/Rule56/CombatAI_Utility.cs
index bd939e4..4f169aa 100644
--- a/Source/Rule56/CombatAI_Utility.cs
+++ b/Source/Rule56/CombatAI_Utility.cs
@@ -1,4 +1,7 @@
using System.Runtime.CompilerServices;
+using CombatAI.Comps;
+using CombatAI.Patches;
+using CombatAI.Squads;
using RimWorld;
using UnityEngine;
using Verse;
@@ -10,31 +13,44 @@ namespace CombatAI
{
public static class CombatAI_Utility
{
- public static bool Is(this T def, T other) where T : Def
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsBurning_Fast(this Pawn pawn)
+ {
+ CompAttachBase comp = pawn.CompAttachBase();
+ if (comp != null)
+ {
+ return comp.HasAttachment(ThingDefOf.Fire);
+ }
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool Is(this T def, T other) where T : Def
{
return def != null && other != null && def == other;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Is(this Job job, T other) where T : Def
{
return job != null && other != null && job.def == other;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Is(this PawnDuty duty, T other) where T : Def
{
return duty != null && other != null && duty.def == other;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Is(this Thing thing, T other) where T : Def
{
return thing != null && other != null && thing.def == other;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Is(this Thing thing, Thing other) where T : Def
{
return thing != null && other != null && thing == other;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDormant(this Thing thing)
{
if (!TKVCache.TryGet(thing, out bool value, 240))
@@ -84,12 +100,12 @@ public static IntVec3 TryGetNextDutyDest(this Pawn pawn, float maxDistFromPawn =
public static bool IsApproachingMeleeTarget(this Pawn pawn, float distLimit = 5, bool allowCached = true)
{
- if (!allowCached || !TKVCache.TryGet(pawn.thingIDNumber, out bool result, 5))
+ if (!allowCached || !TKVCache.TryGet(pawn, out bool result, 5))
{
result = IsApproachingMeleeTarget(pawn, out _, distLimit);
if (allowCached)
{
- TKVCache.Put(pawn.thingIDNumber, result);
+ TKVCache.Put(pawn, result);
}
}
return result;
@@ -120,9 +136,9 @@ public static Verb TryGetAttackVerb(this Thing thing)
{
return meleeVerbs.curMeleeVerb;
}
- if (!TKVCache.TryGet(thing.thingIDNumber, out Verb verb, 480) || verb == null || verb.DirectOwner != thing)
+ if (!TKVCache.TryGet(thing, out Verb verb, 600) || verb == null || verb.DirectOwner != thing)
{
- TKVCache.Put(thing.thingIDNumber, verb = meleeVerbs.TryGetMeleeVerb(null));
+ TKVCache.Put(thing, verb = meleeVerbs.TryGetMeleeVerb(null));
return verb;
}
}
@@ -134,18 +150,24 @@ public static Verb TryGetAttackVerb(this Thing thing)
}
return null;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasWeaponVisible(this Pawn pawn)
{
return (pawn.CurJob?.def.alwaysShowWeapon ?? false) || (pawn.mindState?.duty?.def.alwaysShowWeapon ?? false);
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetAvoidanceReader(this Pawn pawn, out AvoidanceReader reader)
{
return pawn.Map.GetComp_Fast().TryGetReader(pawn, out reader);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetSkillLevelSafe(this Pawn pawn, SkillDef def, int fallback)
+ {
+ return pawn?.skills?.GetSkill(def).Level ?? fallback;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetSightReader(this Pawn pawn, out SightReader reader)
{
if (pawn.Map.GetComp_Fast().TryGetReader(pawn, out reader) && reader != null)
@@ -155,29 +177,66 @@ public static bool TryGetSightReader(this Pawn pawn, out SightReader reader)
}
return false;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ISGrid GetFloatGrid(this Map map)
{
ISGrid grid = map.GetComp_Fast().f_grid;
grid.Reset();
return grid;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CellFlooder GetCellFlooder(this Map map)
{
return map.GetComp_Fast().flooder;
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong GetThingFlags(this Thing thing)
{
return (ulong)1 << GetThingFlagsIndex(thing);
}
-
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetThingFlagsIndex(this Thing thing)
{
return thing.thingIDNumber % 64;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong GetSquadFlags(this Squad squad)
+ {
+ return squad != null ? ((ulong)1 << (squad.squadIDNumber % 64)) : 0;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong GetSquadFlags(this Pawn pawn)
+ {
+ return (ulong)1 << GetSquadFlagsIndex(pawn);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetSquadFlagsIndex(this Pawn pawn)
+ {
+ return pawn.AI()?.squad?.squadIDNumber ?? 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsRangedSappingCompatible(this Verb verb)
+ {
+ return verb != null && !verb.IsMeleeAttack && verb != null && !(verb is Verb_SpewFire || verb is Verb_ShootBeam) && !verb.IsEMP() && !verb.verbProps.CausesExplosion;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsUsingVerb(this Pawn pawn)
+ {
+ return pawn.CurJobDef?.Is(JobDefOf.UseVerbOnThing) ?? false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PersonalityTacker.PersonalityResult GetCombatPersonality(this Thing thing, int expiry = 240)
+ {
+ if (!TKCache.TryGet(thing, out PersonalityTacker.PersonalityResult result, expiry))
+ {
+ TKCache.Put(thing, result = Current.Game.GetComponent().GetPersonality(thing));
+ }
+ return result;
+ }
private class IsApproachingMeleeTargetCache
{
diff --git a/Source/Rule56/Comps/ThingComp_CombatAI.cs b/Source/Rule56/Comps/ThingComp_CombatAI.cs
index 37e2160..74d7a36 100644
--- a/Source/Rule56/Comps/ThingComp_CombatAI.cs
+++ b/Source/Rule56/Comps/ThingComp_CombatAI.cs
@@ -1,1495 +1,1817 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using CombatAI.Abilities;
using CombatAI.R;
+using CombatAI.Squads;
+using CombatAI.Utilities;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
namespace CombatAI.Comps
{
- public class ThingComp_CombatAI : ThingComp
- {
- private readonly Dictionary allAllies;
- private readonly Dictionary allEnemies;
- ///
- /// Escorting pawns.
- ///
- private readonly List escorts = new List();
- ///
- /// Set of visible enemies. A queue for visible enemies during scans.
- ///
- private readonly List rangedEnemiesTargetingSelf = new List(4);
- ///
- /// Sapper path nodes.
- ///
- private readonly List sapperNodes = new List();
- ///
- /// Aggro countdown ticks.
- ///
- private int aggroTicks;
- ///
- /// Aggro target.
- ///
- private LocalTargetInfo aggroTarget;
+ public class ThingComp_CombatAI : ThingComp
+ {
+ private readonly Dictionary allAllies;
+ private readonly Dictionary allEnemies;
+ ///
+ /// Escorting pawns.
+ ///
+ private readonly List escorts = new List();
+ ///
+ /// Set of visible enemies. A queue for visible enemies during scans.
+ ///
+ private readonly List rangedEnemiesTargetingSelf = new List(4);
+ ///
+ /// Sapper path nodes.
+ ///
+ private readonly List sapperNodes = new List();
+ ///
+ /// Aggro countdown ticks.
+ ///
+ private int aggroTicks;
+ ///
+ /// Aggro target.
+ ///
+ private LocalTargetInfo aggroTarget;
- private Thing _bestEnemy;
- private int _last;
+ private Thing _bestEnemy;
+ private int _last;
- private int _sap;
- ///
- /// Pawn ability caster.
- ///
- public Pawn_AbilityCaster abilities;
- ///
- /// Saves job logs. for debugging only.
- ///
- public List jobLogs;
- ///
- /// Parent armor report.
- ///
- private ArmorReport armor;
- ///
- /// Cell to stand on while sapping
- ///
- private IntVec3 cellBefore = IntVec3.Invalid;
- public AIAgentData data;
- ///
- /// Custom pawn duty tracker. Allows the execution of new duties then going back to the old one once the new one is
- /// finished.
- ///
- public Pawn_CustomDutyTracker duties;
- ///
- /// Number of enemies in range.
- /// Updated by the sightgrid.
- ///
- public int enemiesInRangeNum;
- ///
- /// Whether to find escorts.
- ///
- private bool findEscorts;
- ///
- /// Target forced by the player.
- ///
- public LocalTargetInfo forcedTarget = LocalTargetInfo.Invalid;
- ///
- /// Sapper timestamp
- ///
- private int sapperStartTick;
- //Whether a scan is occuring.
- private bool scanning;
- ///
- /// Parent pawn.
- ///
- public Pawn selPawn;
- ///
- /// Parent sight reader.
- ///
- public SightTracker.SightReader sightReader;
+ private int _sap;
+ ///
+ /// Pawn ability caster.
+ ///
+ public Pawn_AbilityCaster abilities;
+ ///
+ /// Saves job logs. for debugging only.
+ ///
+ public List jobLogs;
+ ///
+ /// Pawn squad
+ ///
+ public Squad squad;
+ ///
+ /// Parent armor report.
+ ///
+ private ArmorReport armor;
+ ///
+ /// Cell to stand on while sapping
+ ///
+ private IntVec3 cellBefore = IntVec3.Invalid;
+ private IntVec3 cellAhead = IntVec3.Invalid;
+ public AIAgentData data;
+ ///
+ /// Custom pawn duty tracker. Allows the execution of new duties then going back to the old one once the new one is
+ /// finished.
+ ///
+ public Pawn_CustomDutyTracker duties;
+ ///
+ /// Number of enemies in range.
+ /// Updated by the sightgrid.
+ ///
+ public int enemiesInRangeNum;
+ ///
+ /// Whether to find escorts.
+ ///
+ private bool findEscorts;
+ ///
+ /// Target forced by the player.
+ ///
+ public LocalTargetInfo forcedTarget = LocalTargetInfo.Invalid;
+ ///
+ /// Sapper timestamp
+ ///
+ private int sapperStartTick;
+ //Whether a scan is occuring.
+ private bool scanning;
+ ///
+ /// Parent pawn.
+ ///
+ public Pawn selPawn;
+ ///
+ /// Parent sight reader.
+ ///
+ public SightTracker.SightReader sightReader;
- public ThingComp_CombatAI()
- {
- allEnemies = new Dictionary(32);
- allAllies = new Dictionary(32);
- data = new AIAgentData();
- }
-
- ///
- /// 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;
- }
- ///
- /// Whether the pawn is available to escort other pawns or available for sapping.
- ///
- public bool CanSappOrEscort
- {
- get => !IsSapping && GenTicks.TicksGame - releasedTick > 900;
- }
-
- public override void Initialize(CompProperties props)
- {
- base.Initialize(props);
- selPawn = parent as Pawn;
- if (selPawn == null)
- {
- throw new Exception($"ThingComp_CombatAI initialized for a non pawn {parent}/def:{parent.def}");
- }
- }
+ public ThingComp_CombatAI()
+ {
+ allEnemies = new Dictionary(32);
+ allAllies = new Dictionary(32);
+ data = new AIAgentData();
+ }
- public override void PostSpawnSetup(bool respawningAfterLoad)
- {
- base.PostSpawnSetup(respawningAfterLoad);
- armor = selPawn.GetArmorReport();
- duties ??= new Pawn_CustomDutyTracker(selPawn);
- duties.pawn = selPawn;
- abilities ??= new Pawn_AbilityCaster(selPawn);
- abilities.pawn = selPawn;
- }
-
- public override void CompTickRare()
- {
- base.CompTickRare();
- if (!selPawn.Spawned)
- {
- return;
- }
- if (IsDeadOrDowned)
- {
- if (escorts.Count > 0)
- {
- ReleaseEscorts(false);
- sapperNodes.Clear();
- cellBefore = IntVec3.Invalid;
- sapperStartTick = -1;
- }
- return;
- }
- if (aggroTicks > 0)
- {
- aggroTicks -= GenTicks.TickRareInterval;
- if (aggroTicks <= 0)
- {
- if (aggroTarget.IsValid)
- {
- TryAggro(aggroTarget, 0.8f, Rand.Int);
- }
- aggroTarget = LocalTargetInfo.Invalid;
- }
+ ///
+ /// Whether the pawn is downed or dead.
+ ///
+ public bool IsDeadOrDowned
+ {
+ get => selPawn.Dead || selPawn.Downed;
+ }
- }
- if (duties != null)
- {
- duties.TickRare();
- }
- if (abilities != null)
- {
- // abilities.TickRare(visibleEnemies);
- }
- if (selPawn.IsApproachingMeleeTarget(out Thing target))
- {
- ThingComp_CombatAI comp = target.GetComp_Fast();;
- if (comp != null)
- {
- comp.Notify_BeingTargeted(selPawn, selPawn.CurrentEffectiveVerb);
- }
- }
- if (IsSapping && !IsDeadOrDowned)
- {
- // end if this pawn is in
- if (sapperNodes[0].GetEdifice(parent.Map) == null)
- {
- cellBefore = sapperNodes[0];
- sapperNodes.RemoveAt(0);
- if (sapperNodes.Count > 0)
- {
- _sap++;
- TryStartSapperJob();
- }
- else
- {
- ReleaseEscorts(success: true);
- sapperNodes.Clear();
- cellBefore = IntVec3.Invalid;
- sapperStartTick = -1;
- }
- }
- else
- {
- TryStartSapperJob();
- }
- }
- else if (forcedTarget.IsValid && !IsDeadOrDowned)
- {
- if (Mod_CE.active && (selPawn.CurJobDef.Is(Mod_CE.ReloadWeapon) || selPawn.CurJobDef.Is(Mod_CE.HunkerDown)))
- {
- return;
- }
- // remove the forced target on when not drafted and near the target
- if (!selPawn.Drafted || selPawn.Position.DistanceToSquared(forcedTarget.Cell) < 25)
- {
- forcedTarget = LocalTargetInfo.Invalid;
- ;
- }
- else if (enemiesInRangeNum == 0 && (selPawn.jobs.curJob?.def.Is(JobDefOf.Goto) == false || selPawn.pather?.Destination != forcedTarget.Cell))
- {
- Job gotoJob = JobMaker.MakeJob(JobDefOf.Goto, forcedTarget);
- gotoJob.canUseRangedWeapon = true;
- gotoJob.locomotionUrgency = LocomotionUrgency.Jog;
- gotoJob.playerForced = true;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(gotoJob);
- }
- }
- }
+ ///
+ /// Whether the pawning is sapping.
+ ///
+ public bool IsSapping
+ {
+ get => cellBefore.IsValid && sapperNodes.Count > 0;
+ }
+ ///
+ /// Whether the pawn is available to escort other pawns or available for sapping.
+ ///
+ public bool CanSappOrEscort
+ {
+ get => !IsSapping && GenTicks.TicksGame - releasedTick > 900;
+ }
- public override void CompTickLong()
- {
- base.CompTickLong();
- // update the current armor report.
- armor = selPawn.GetArmorReport();
- }
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ selPawn = parent as Pawn;
+ if (selPawn == null)
+ {
+ throw new Exception($"ThingComp_CombatAI initialized for a non pawn {parent}/def:{parent.def}");
+ }
+ }
- ///
- /// Returns whether the parent has took damage in the last number of ticks.
- ///
- /// The number of ticks
- /// Whether the pawn took damage in the last number of ticks
- public bool TookDamageRecently(int ticks)
- {
- return data.TookDamageRecently(ticks);
- }
- ///
- /// Returns whether the parent has reacted in the last number of ticks.
- ///
- /// The number of ticks
- /// Whether the reacted in the last number of ticks
- public bool ReactedRecently(int ticks)
- {
- return data.InterruptedRecently(ticks);
- }
+ public override void PostDeSpawn(Map map)
+ {
+ base.PostDeSpawn(map);
+ allAllies.Clear();
+ allEnemies.Clear();
+ escorts.Clear();
+ rangedEnemiesTargetingSelf.Clear();
+ sapperNodes.Clear();
+ aggroTarget = LocalTargetInfo.Invalid;
+ data?.PostDeSpawn();
+ }
- ///
- /// Called when a scan for enemies starts. Will clear the visible enemy queue. If not called, calling OnScanFinished or
- /// Notify_VisibleEnemy(s) will result in an error.
- /// Should only be called from the main thread.
- ///
- public void OnScanStarted()
- {
- if (allEnemies.Count != 0)
- {
- if (scanning)
- {
- Log.Warning($"ISMA: OnScanStarted called while scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
- return;
- }
- allEnemies.Clear();
- }
- if (allAllies.Count != 0)
- {
- allAllies.Clear();
- }
- scanning = true;
- data.LastScanned = lastScanned = GenTicks.TicksGame;
- }
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+ armor = selPawn.GetArmorReport();
+ duties ??= new Pawn_CustomDutyTracker(selPawn);
+ duties.pawn = selPawn;
+ abilities ??= new Pawn_AbilityCaster(selPawn);
+ abilities.pawn = selPawn;
+ }
- ///
- /// Called a scan is finished. This will process enemies queued in visibleEnemies. Responsible for parent reacting.
- /// If OnScanStarted is not called before then this will result in an error.
- /// Should only be called from the main thread.
- ///
- public void OnScanFinished()
- {
- if (scanning == false)
- {
- Log.Warning($"ISMA: OnScanFinished called while not scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
- return;
- }
- scanning = false;
- // set enemies.
- data.ReSetEnemies(allEnemies);
- // set allies.
- data.ReSetAllies(allAllies);
- // update when this pawn last saw enemies
- data.LastSawEnemies = data.NumEnemies > 0 ? GenTicks.TicksGame : -1;
- // skip for animals.
- if (selPawn.mindState == null || selPawn.RaceProps.Animal || IsDeadOrDowned)
- {
- return;
- }
- // skip for player pawns with no forced target.
- if (selPawn.Faction.IsPlayerSafe() && !forcedTarget.IsValid)
- {
- return;
- }
#if DEBUG_REACTION
- if (Finder.Settings.Debug && Finder.Settings.Debug_ValidateSight)
- {
- _visibleEnemies.Clear();
- IEnumerator enumerator = data.Enemies();
- while (enumerator.MoveNext())
- {
- AIEnvAgentInfo info = enumerator.Current;
- if (info.thing == null)
- {
- Log.Warning("Found null thing (1)");
- continue;
- }
- if (info.thing.Spawned && info.thing is Pawn pawn)
- {
- _visibleEnemies.Add(pawn);
- }
- }
- if (_path.Count == 0 || _path.Last() != parent.Position)
- {
- _path.Add(parent.Position);
- if (GenTicks.TicksGame - lastInterupted < 150)
- {
- _colors.Add(Color.red);
- }
- else if (GenTicks.TicksGame - lastInterupted < 240)
- {
- _colors.Add(Color.yellow);
- }
- else
- {
- _colors.Add(Color.black);
- }
- if (_path.Count >= 30)
- {
- _path.RemoveAt(0);
- _colors.RemoveAt(0);
- }
- }
- }
+ private static List _buffer = new List(16);
+ public override void CompTick()
+ {
+ base.CompTick();
+ if (selPawn.IsHashIntervalTick(15) && Find.Selector.SelectedPawns.Contains(selPawn))
+ {
+ List buffer = _buffer;
+ buffer.Clear();
+ sightReader.GetEnemies(selPawn.Position, buffer);
+ foreach (var thing in buffer)
+ {
+ selPawn.Map.debugDrawer.FlashCell(thing.Position, 0.01f, "H", 15);
+ }
+ buffer.Clear();
+ sightReader.GetFriendlies(selPawn.Position, buffer);
+ foreach (var thing in buffer)
+ {
+ selPawn.Map.debugDrawer.FlashCell(thing.Position, 0.99f, "F", 15);
+ }
+ buffer.Clear();
+ }
+ }
#endif
- List targetedBy = data.BeingTargetedBy;
- // update when last saw enemies
- data.LastSawEnemies = data.NumEnemies > 0 ? GenTicks.TicksGame : data.LastSawEnemies;
- // if no enemies are visible nor anyone targeting self skip.
- if (data.NumEnemies == 0 && targetedBy.Count == 0)
- {
- return;
- }
- // check if the TPS is good enough.
- // reduce cooldown if the pawn hasn't seen enemies for a few ticks
- if (!Finder.Performance.TpsCriticallyLow)
- {
- // if the pawn haven't seen enemies in a while and recently reacted then reset lastInterupted.
- // This is done to ensure fast reaction times when exiting then entering combat.
- if (GenTicks.TicksGame - lastSawEnemies > 90)
- {
- lastInterupted = -1;
- if (Finder.Settings.Debug && Finder.Settings.Debug_ValidateSight)
- {
- parent.Map.debugDrawer.FlashCell(parent.Position, 1.0f, "X", 60);
- }
- }
- lastSawEnemies = GenTicks.TicksGame;
- }
- // get body size and use it in cooldown math.
- float bodySize = selPawn.RaceProps.baseBodySize;
- // pawn reaction cooldown changes with their body size.
- if (data.InterruptedRecently((int)(30 * bodySize)) || data.RetreatedRecently((int)(60 * bodySize)))
- {
- return;
- }
- // if the pawn is kidnapping a pawn skip.
- if (selPawn.CurJobDef.Is(JobDefOf.Kidnap) || selPawn.CurJobDef.Is(JobDefOf.Flee))
- {
- return;
- }
- // if the pawn is sapping, stop sapping.
- if (selPawn.CurJobDef.Is(JobDefOf.Mine) && sightReader.GetVisibilityToEnemies(selPawn.Position) > 0)
- {
- selPawn.jobs.StopAll();
- }
- // Skip if some vanilla duties are active.
- PawnDuty duty = selPawn.mindState.duty;
- if (duty.Is(DutyDefOf.Build) || duty.Is(DutyDefOf.SleepForever) || duty.Is(DutyDefOf.TravelOrLeave))
- {
- data.LastInterrupted = GenTicks.TicksGame + Rand.Int % 240;
- return;
- }
- IntVec3 selPos = selPawn.Position;
- Pawn nearestMeleeEnemy = null;
- float nearestMeleeEnemyDist = 1e5f;
- Thing nearestEnemy = null;
- float nearestEnemyDist = 1e5f;
- // used to update nearest enemy THing
- void UpdateNearestEnemy(Thing enemy)
- {
- float dist = selPawn.DistanceTo_Fast(enemy);
- if (dist < nearestEnemyDist)
- {
- nearestEnemyDist = dist;
- nearestEnemy = enemy;
- }
- }
+ public override void CompTickRare()
+ {
+ base.CompTickRare();
+ if (!selPawn.Spawned)
+ {
+ return;
+ }
+ if (IsDeadOrDowned)
+ {
+ if (IsSapping || escorts.Count > 0)
+ {
+ ReleaseEscorts(false);
+ sapperNodes.Clear();
+ cellBefore = IntVec3.Invalid;
+ sapperStartTick = -1;
+ }
+ return;
+ }
+ if (selPawn.IsBurning_Fast())
+ {
+ return;
+ }
+ if (aggroTicks > 0)
+ {
+ aggroTicks -= GenTicks.TickRareInterval;
+ if (aggroTicks <= 0)
+ {
+ if (aggroTarget.IsValid)
+ {
+ TryAggro(aggroTarget, 0.8f, Rand.Int);
+ }
+ aggroTarget = LocalTargetInfo.Invalid;
+ }
- // used to update nearest melee pawn
- void UpdateNearestEnemyMelee(Thing enemy)
- {
- if (enemy is Pawn enemyPawn)
- {
- float dist = selPos.DistanceTo_Fast(PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120f));
- if (dist < nearestMeleeEnemyDist)
- {
- nearestMeleeEnemyDist = dist;
- nearestMeleeEnemy = enemyPawn;
- }
- }
- }
+ }
+ if (duties != null)
+ {
+ duties.TickRare();
+ }
+ if (abilities != null)
+ {
+ // abilities.TickRare(visibleEnemies);
+ }
+ if (selPawn.IsApproachingMeleeTarget(out Thing target))
+ {
+ ThingComp_CombatAI comp = target.GetComp_Fast();
+ ;
+ if (comp != null)
+ {
+ comp.Notify_BeingTargeted(selPawn, selPawn.CurrentEffectiveVerb);
+ }
+ }
+ if (IsSapping)
+ {
+ // end if this pawn is in
+ if (sapperNodes[0].GetEdifice(parent.Map) == null)
+ {
+ cellBefore = sapperNodes[0];
+ sapperNodes.RemoveAt(0);
+ if (sapperNodes.Count > 0)
+ {
+ _sap++;
+ TryStartSapperJob();
+ }
+ else
+ {
+ ReleaseEscorts(success: true);
+ sapperNodes.Clear();
+ cellBefore = IntVec3.Invalid;
+ sapperStartTick = -1;
+ }
+ }
+ else
+ {
+ TryStartSapperJob();
+ }
+ }
+ if (forcedTarget.IsValid)
+ {
+ if (Mod_CE.active && (selPawn.CurJobDef.Is(Mod_CE.ReloadWeapon) || selPawn.CurJobDef.Is(Mod_CE.HunkerDown)))
+ {
+ return;
+ }
+ // remove the forced target on when not drafted and near the target
+ if (!selPawn.Drafted || selPawn.Position.DistanceToSquared(forcedTarget.Cell) < 25)
+ {
+ forcedTarget = LocalTargetInfo.Invalid;
+ }
+ else if (enemiesInRangeNum == 0 && (selPawn.jobs.curJob?.def.Is(JobDefOf.Goto) == false || selPawn.pather?.Destination != forcedTarget.Cell))
+ {
+ Job gotoJob = JobMaker.MakeJob(JobDefOf.Goto, forcedTarget);
+ gotoJob.canUseRangedWeapon = true;
+ gotoJob.checkOverrideOnExpire = false;
+ gotoJob.locomotionUrgency = LocomotionUrgency.Jog;
+ gotoJob.playerForced = true;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(gotoJob);
+ }
+ }
+ }
- // check if the chance of survivability is high enough
- // defensive actions
- Verb verb = selPawn.CurrentEffectiveVerb;
- if (verb != null && verb.WarmupStance != null && verb.WarmupStance.ticksLeft < 40)
- {
- return;
- }
- float possibleDmgDistance = 0f;
- float possibleDmgWarmup = 0f;
- float possibleDmg = 0f;
- AIEnvThings enemies = data.AllEnemies;
+ public override void CompTickLong()
+ {
+ base.CompTickLong();
+ // update the current armor report.
+ armor = selPawn.GetArmorReport();
+ }
- rangedEnemiesTargetingSelf.Clear();
- if (bodySize < 2 || selPawn.RaceProps.Humanlike)
- {
- for (int i = 0; i < targetedBy.Count; i++)
- {
- Thing enemy = targetedBy[i];
+ ///
+ /// Returns whether the parent has took damage in the last number of ticks.
+ ///
+ /// The number of ticks
+ /// Whether the pawn took damage in the last number of ticks
+ public bool TookDamageRecently(int ticks)
+ {
+ return data.TookDamageRecently(ticks);
+ }
+ ///
+ /// Returns whether the parent has reacted in the last number of ticks.
+ ///
+ /// The number of ticks
+ /// Whether the reacted in the last number of ticks
+ public bool ReactedRecently(int ticks)
+ {
+ return data.InterruptedRecently(ticks);
+ }
+
+ ///
+ /// Called when a scan for enemies starts. Will clear the visible enemy queue. If not called, calling OnScanFinished or
+ /// Notify_VisibleEnemy(s) will result in an error.
+ /// Should only be called from the main thread.
+ ///
+ public void OnScanStarted()
+ {
+ if (allEnemies.Count != 0)
+ {
+ if (scanning)
+ {
+ Log.Warning($"ISMA: OnScanStarted called while scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
+ return;
+ }
+ allEnemies.Clear();
+ }
+ if (allAllies.Count != 0)
+ {
+ allAllies.Clear();
+ }
+ scanning = true;
+ data.LastScanned = lastScanned = GenTicks.TicksGame;
+ }
+
+ ///
+ /// Called a scan is finished. This will process enemies queued in visibleEnemies. Responsible for parent reacting.
+ /// If OnScanStarted is not called before then this will result in an error.
+ /// Should only be called from the main thread.
+ ///
+ /// Used to debug where an exception was thrown
+ public void OnScanFinished(ref int progress)
+ {
+ if (scanning == false)
+ {
+ Log.Warning($"ISMA: OnScanFinished called while not scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
+ return;
+ }
+ scanning = false;
+ // set enemies.
+ data.ReSetEnemies(allEnemies);
+ // set allies.
+ data.ReSetAllies(allAllies);
+ // update when this pawn last saw enemies
+ data.LastSawEnemies = data.NumEnemies > 0 ? GenTicks.TicksGame : -1;
+ //
+ var settings = parent is Pawn ? Finder.Settings.GetDefKindSettings(parent as Pawn) : Finder.Settings.GetDefKindSettings(parent.def, null);
+ // For debugging and logging.
+ progress = 1;
+ // skip for animals.
+ if (selPawn.mindState == null || selPawn.RaceProps.Animal || IsDeadOrDowned)
+ {
+ return;
+ }
+ // skip for player pawns with no forced target.
+ if (selPawn.Faction.IsPlayerSafe() && !forcedTarget.IsValid)
+ {
+ return;
+ }
+ // if the pawn is burning don't react.
+ if (selPawn.IsBurning_Fast())
+ {
+ return;
+ }
+ ReactDebug_Internel(out progress);
+ // For debugging and logging.
+ progress = 3;
+ List targetedBy = data.BeingTargetedBy;
+ // update when last saw enemies
+ data.LastSawEnemies = data.NumEnemies > 0 ? GenTicks.TicksGame : data.LastSawEnemies;
+ // if no enemies are visible nor anyone targeting self skip.
+ if (data.NumEnemies == 0 && targetedBy.Count == 0)
+ {
+ return;
+ }
+ // For debugging and logging.
+ progress = 4;
+ // check if the TPS is good enough.
+ // reduce cooldown if the pawn hasn't seen enemies for a few ticks
+ if (!Finder.Performance.TpsCriticallyLow)
+ {
+ // if the pawn haven't seen enemies in a while and recently reacted then reset lastInterupted.
+ // This is done to ensure fast reaction times when exiting then entering combat.
+ if (GenTicks.TicksGame - lastSawEnemies > 90)
+ {
+ lastInterupted = -1;
+ if (Finder.Settings.Debug && Finder.Settings.Debug_ValidateSight)
+ {
+ parent.Map.debugDrawer.FlashCell(parent.Position, 1.0f, "X", 60);
+ }
+ }
+ lastSawEnemies = GenTicks.TicksGame;
+ }
+ // For debugging and logging.
+ progress = 5;
+ // get body size and use it in cooldown math.
+ float bodySize = selPawn.RaceProps.baseBodySize;
+ // pawn reaction cooldown changes with their body size.
+ if (data.InterruptedRecently((int)(45 * bodySize)) || data.RetreatedRecently((int)(120 * bodySize)))
+ {
+ return;
+ }
+ // if the pawn is kidnapping a pawn skip.
+ if (selPawn.CurJobDef.Is(JobDefOf.Kidnap) || selPawn.CurJobDef.Is(JobDefOf.Flee))
+ {
+ return;
+ }
+ // if the pawn is sapping, stop sapping.
+ if (selPawn.CurJobDef.Is(JobDefOf.Mine) && sightReader.GetVisibilityToEnemies(selPawn.Position) > 0)
+ {
+ selPawn.jobs.StopAll();
+ }
+ // For debugging and logging.
+ progress = 6;
+ // Skip if some vanilla duties are active.
+ PawnDuty duty = selPawn.mindState.duty;
+ if (duty.Is(DutyDefOf.Build) || duty.Is(DutyDefOf.SleepForever) || duty.Is(DutyDefOf.TravelOrLeave))
+ {
+ data.LastInterrupted = GenTicks.TicksGame + Rand.Int % 240;
+ return;
+ }
+ PersonalityTacker.PersonalityResult personality = parent.GetCombatPersonality();
+ IntVec3 selPos = selPawn.Position;
+ // used to update nearest enemy THing
+ // For debugging and logging.
+ progress = 7;
+ // check if the chance of survivability is high enough
+ // defensive actions
+ Verb verb = selPawn.CurrentEffectiveVerb;
+ if (verb is { WarmupStance.ticksLeft: < 40 })
+ {
+ return;
+ }
+ // For debugging and logging.
+ progress = 8;
+ rangedEnemiesTargetingSelf.Clear();
+ // Calc the current threat
+ ThreatUtility.CalculateThreat(selPawn, targetedBy, armor, rangedEnemiesTargetingSelf, null, out float possibleDmg, out float possibleDmgDistance, out float possibleDmgWarmup, out Thing nearestEnemy, out float nearestEnemyDist, out Pawn nearestMeleeEnemy, out float nearestMeleeEnemyDist, ref progress);
+ // Try retreat
+ if ((settings?.Retreat_Enabled ?? true) && (bodySize < 2 || selPawn.RaceProps.Humanlike))
+ {
+ // For debugging and logging.
+ progress = 9;
+ if (TryStartRetreat(possibleDmg, possibleDmgWarmup, possibleDmgDistance, personality, nearestMeleeEnemy, nearestMeleeEnemyDist, ref progress))
+ {
+ return;
+ }
+ }
+ // For debugging and logging.
+ progress = 100;
+ if (duty.Is(DutyDefOf.ExitMapRandom))
+ {
+ return;
+ }
+ // For debugging and logging.
+ progress = 200;
+ // offensive actions
+ if (verb != null)
+ {
+ // if the pawn is retreating and the pawn is still in danger or recently took damage, skip any offensive reaction.
+ if (verb.IsMeleeAttack)
+ {
+ TryMeleeReaction(possibleDmg, ref progress);
+ }
+ // ranged
+ else
+ {
+ TryRangedReaction(verb, selPos, personality, bodySize, duty, ref progress);
+ }
+ }
+ }
+ private void TryRangedReaction(Verb verb, IntVec3 selPos, PersonalityTacker.PersonalityResult personality, float bodySize, PawnDuty duty, ref int progress)
+ {
+ Thing nearestEnemy = null;
+ float nearestEnemyDist = 1e6f;
+ Pawn nearestMeleeEnemy = null;
+ float nearestMeleeEnemyDist = 1e6f;
+ // For debugging and logging.
+ progress = 208;
+ if (selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) && GenTicks.TicksGame - selPawn.CurJob.startTick < 60)
+ {
+ return;
+ }
+ // if CE is active skip reaction if the pawn is reloading or hunkering down.
+ if (Mod_CE.active && (selPawn.CurJobDef.Is(Mod_CE.ReloadWeapon) || selPawn.CurJobDef.Is(Mod_CE.HunkerDown)))
+ {
+ return;
+ }
+ // For debugging and logging.
+ progress = 209;
+ // check if the verb is available.
+ if (!verb.Available() || Mod_CE.active && Mod_CE.IsAimingCE(verb))
+ {
+ return;
+ }
+ bool bestEnemyVisibleNow = false;
+ bool bestEnemyVisibleSoon = false;
+ ulong selFlags = selPawn.GetThingFlags();
+ // For debugging and logging.
+ progress = 210;
+ // A not fast check will check for retreat and for reactions to enemies that are visible or soon to be visible.
+ // A fast check will check only for retreat.
+ IEnumerator enumerator = data.Enemies();
+ while (enumerator.MoveNext())
+ {
+ progress = 301;
+ AIEnvAgentInfo info = enumerator.Current;
#if DEBUG_REACTION
- if (enemy == null)
- {
- Log.Error("Found null thing (2)");
- continue;
- }
+ if (info.thing == null)
+ {
+ Log.Error("Found null thing (3)");
+ continue;
+ }
#endif
- if (GetEnemyAttackTargetId(enemy) == selPawn.thingIDNumber)
- {
- DamageReport damageReport = DamageUtility.GetDamageReport(enemy);
- if (damageReport.IsValid && (!(enemy is Pawn enemyPawn) || enemyPawn.mindState?.MeleeThreatStillThreat == false))
- {
- UpdateNearestEnemy(enemy);
- if (!damageReport.primaryIsRanged)
- {
- UpdateNearestEnemyMelee(enemy);
- }
- float damage = damageReport.SimulatedDamage(armor);
- if (!damageReport.primaryIsRanged)
- {
- // reduce the possible damage for far away melee pawns.
- damage *= (5f - Mathf.Clamp(Maths.Sqrt_Fast(selPos.DistanceToSquared(enemy.Position), 4), 0f, 5f)) / 5f;
- }
- possibleDmg += damage;
- possibleDmgDistance += enemy.DistanceTo_Fast(selPawn);
- if (damageReport.primaryIsRanged)
- {
- possibleDmgWarmup += damageReport.primaryVerbProps.warmupTime;
- rangedEnemiesTargetingSelf.Add(enemy);
- }
- }
- }
- }
- if (rangedEnemiesTargetingSelf.Count > 0 && !selPawn.mindState.MeleeThreatStillThreat && !selPawn.IsApproachingMeleeTarget(8, false))
- {
- int retreatRoll = Rand.Range(0, 50);
- // major retreat attempt if the pawn is doomed
- if (possibleDmg - retreatRoll > 0.001f && possibleDmg >= 50)
- {
- _last = 10;
- _bestEnemy = nearestMeleeEnemy;
- CoverPositionRequest request = new CoverPositionRequest();
- request.caster = selPawn;
- request.target = nearestMeleeEnemy;
- request.majorThreats = rangedEnemiesTargetingSelf;
- request.maxRangeFromCaster = 12;
- request.checkBlockChance = true;
- if (CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell) && ShouldMoveTo(cell))
- {
- if (cell != selPos)
- {
- _last = 11;
- Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Retreat, cell);
- job_goto.playerForced = forcedTarget.IsValid;
- job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
- data.LastRetreated = GenTicks.TicksGame;
- }
- return;
- }
- }
- // try minor retreat (duck for cover fast)
- if (possibleDmg - retreatRoll * 0.5f > 0.001f && possibleDmg >= 20)
- {
- // selPawn.Map.debugDrawer.FlashCell(selPos, 1.0f, $"{possibleDmg}, {targetedBy.Count}, {rangedEnemiesTargetingSelf.Count}");
- CoverPositionRequest request = new CoverPositionRequest();
- request.caster = selPawn;
- request.majorThreats = rangedEnemiesTargetingSelf;
- request.checkBlockChance = true;
- request.maxRangeFromCaster = Mathf.Clamp(possibleDmgWarmup * 5f - rangedEnemiesTargetingSelf.Count, 6f, 10f);
- if (CoverPositionFinder.TryFindDuckPosition(request, out IntVec3 cell))
- {
- bool diff = cell != selPos;
- // run to cover
- if (diff)
- {
- _last = 12;
- Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Duck, cell);
- job_goto.playerForced = forcedTarget.IsValid;
- job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
- data.LastRetreated = lastRetreated = GenTicks.TicksGame;
- }
- if (data.TookDamageRecently(45) || !diff)
- {
- _last = 13;
- Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 50 + 50);
- job_waitCombat.playerForced = forcedTarget.IsValid;
- job_waitCombat.checkOverrideOnExpire = true;
- selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
- data.LastRetreated = lastRetreated = GenTicks.TicksGame;
- }
- return;
- }
- }
- }
- }
- if (duty.Is(DutyDefOf.ExitMapRandom))
- {
- return;
- }
- // offensive actions
- if (verb != null)
- {
- // if the pawn is retreating and the pawn is still in danger or recently took damage, skip any offensive reaction.
- if (verb.IsMeleeAttack)
- {
- if ((selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Retreat) || selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover)) && (rangedEnemiesTargetingSelf.Count == 0 || possibleDmg < 2.5f))
- {
- _last = 30;
- selPawn.jobs.StopAll();
- }
- bool bestEnemyIsRanged = false;
- bool bestEnemyIsMeleeAttackingAlly = false;
- // TODO create melee reactions.
- IEnumerator enumeratorEnemies = data.EnemiesWhere(AIEnvAgentState.nearby);
- while (enumeratorEnemies.MoveNext())
- {
- AIEnvAgentInfo info = enumeratorEnemies.Current;
+ // For debugging and logging.
+ progress = 302;
+ if (info.thing.Spawned)
+ {
+ Pawn enemyPawn = info.thing as Pawn;
+ if ((sightReader.GetDynamicFriendlyFlags(info.thing.Position) & selFlags) != 0 && verb.CanHitTarget(info.thing))
+ {
+ // For debugging and logging.
+ progress = 311;
+ if (!bestEnemyVisibleNow)
+ {
+ nearestEnemy = null;
+ nearestEnemyDist = 1e4f;
+ bestEnemyVisibleNow = true;
+ }
+ float dist = selPawn.DistanceTo_Fast(info.thing);
+ if (dist < nearestEnemyDist)
+ {
+ nearestEnemyDist = dist;
+ nearestEnemy = info.thing;
+ }
+ }
+ else if (enemyPawn != null && !bestEnemyVisibleNow)
+ {
+ // For debugging and logging.
+ progress = 312;
+ IntVec3 temp = PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120);
+ if ((sightReader.GetDynamicFriendlyFlags(temp) & selFlags) != 0 && verb.CanHitTarget(temp))
+ {
+ if (!bestEnemyVisibleSoon)
+ {
+ nearestEnemy = null;
+ nearestEnemyDist = 1e4f;
+ bestEnemyVisibleSoon = true;
+ }
+ float dist = selPawn.DistanceTo_Fast(info.thing);
+ if (dist < nearestEnemyDist)
+ {
+ nearestEnemyDist = dist;
+ nearestEnemy = info.thing;
+ }
+ }
+ else if (!bestEnemyVisibleSoon)
+ {
+ float dist = selPawn.DistanceTo_Fast(info.thing);
+ if (dist < nearestEnemyDist)
+ {
+ nearestEnemyDist = dist;
+ nearestEnemy = info.thing;
+ }
+ }
+ }
+ // For debugging and logging.
+ progress = 303;
+ if (enemyPawn != null && (enemyPawn.CurrentEffectiveVerb?.IsMeleeAttack ?? false))
+ {
+ float dist = selPos.DistanceTo_Fast(PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120f));
+ if (dist < nearestMeleeEnemyDist)
+ {
+ nearestMeleeEnemyDist = dist;
+ nearestMeleeEnemy = enemyPawn;
+ }
+ }
+ }
+ }
+ progress = 400;
+ void StartOrQueueCoverJob(IntVec3 cell, int codeOffset)
+ {
+ Job curJob = selPawn.CurJob;
+ if (curJob != null && (curJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) || curJob.Is(JobDefOf.Goto)) && cell == curJob.targetA.Cell)
+ {
+ if (selPawn.jobs.jobQueue.Count == 0 || !selPawn.jobs.jobQueue[0].job.Is(JobDefOf.Wait_Combat))
+ {
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 150 + 200);
+ job_waitCombat.targetA = nearestEnemy;
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.endIfCantShootTargetFromCurPos = true;
+ job_waitCombat.checkOverrideOnExpire = true;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
+ }
+ }
+ else if (cell == selPawn.Position)
+ {
+ if (!selPawn.CurJob.Is(JobDefOf.Wait_Combat))
+ {
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 150 + 200);
+ job_waitCombat.targetA = nearestEnemy;
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.endIfCantShootTargetFromCurPos = true;
+ job_waitCombat.checkOverrideOnExpire = true;
+ selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced);
+ }
+ }
+ else if (selPawn.CurJob.Is(JobDefOf.Wait_Combat))
+ {
+ _last = 50 + codeOffset;
+ Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Cover, cell);
+ job_goto.playerForced = forcedTarget.IsValid;
+ job_goto.expiryInterval = -1;
+ job_goto.checkOverrideOnExpire = false;
+ job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 150 + 200);
+ job_waitCombat.targetA = nearestEnemy;
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.endIfCantShootTargetFromCurPos = true;
+ job_waitCombat.checkOverrideOnExpire = true;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
+ selPawn.jobs.jobQueue.EnqueueFirst(job_goto);
+ }
+ else
+ {
+ _last = 51 + codeOffset;
+ selPawn.mindState.enemyTarget = nearestEnemy;
+ Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Cover, cell);
+ job_goto.expiryInterval = -1;
+ job_goto.checkOverrideOnExpire = false;
+ job_goto.playerForced = forcedTarget.IsValid;
+ job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 150 + 200);
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.endIfCantShootTargetFromCurPos = true;
+ job_waitCombat.checkOverrideOnExpire = true;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
+ selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
+ }
+ data.LastInterrupted = GenTicks.TicksGame;
+ }
+
+ if (nearestEnemy != null && rangedEnemiesTargetingSelf.Contains(nearestEnemy))
+ {
+ rangedEnemiesTargetingSelf.Remove(nearestEnemy);
+ }
+ progress = 500;
+ bool retreatMeleeThreat = nearestMeleeEnemy != null && verb.EffectiveRange * personality.retreat > 16 && nearestMeleeEnemyDist < Maths.Max(verb.EffectiveRange * personality.retreat / 3f, 9) && 0.25f * data.NumAllies < data.NumEnemies;
+ bool retreatThreat = !retreatMeleeThreat && nearestEnemy != null && nearestEnemyDist < Maths.Max(verb.EffectiveRange * personality.retreat / 4f, 5);
+ _bestEnemy = retreatMeleeThreat ? nearestMeleeEnemy : nearestEnemy;
+ // retreat because of a close melee threat
+ if (bodySize < 2.0f && (retreatThreat || retreatMeleeThreat))
+ {
+ progress = 501;
+ _bestEnemy = retreatThreat ? nearestEnemy : nearestMeleeEnemy;
+ _last = 40;
+ CoverPositionRequest request = new CoverPositionRequest();
+ request.caster = selPawn;
+ request.target = nearestMeleeEnemy;
+ request.verb = verb;
+ request.majorThreats = rangedEnemiesTargetingSelf;
+ request.checkBlockChance = true;
+ request.maxRangeFromCaster = verb.EffectiveRange / 2 + 8;
+ if (CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell))
+ {
+ if (cell != selPos)
+ {
+ _last = 41;
+ Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Retreat, cell);
+ job_goto.expiryInterval = -1;
+ job_goto.checkOverrideOnExpire = false;
+ job_goto.playerForced = forcedTarget.IsValid;
+ job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
+ data.LastRetreated = GenTicks.TicksGame;
+ }
+ }
+ }
+ // best enemy is insight
+ else if (nearestEnemy != null)
+ {
+ progress = 502;
+ _bestEnemy = nearestEnemy;
+ if (!selPawn.RaceProps.Humanlike || bodySize > 2.0f)
+ {
+ progress = 511;
+ if (bestEnemyVisibleNow && selPawn.mindState.enemyTarget == null)
+ {
+ selPawn.mindState.enemyTarget = nearestEnemy;
+ }
+ }
+ else
+ {
+ progress = 521;
+ int shootingNum = 0;
+ int rangedNum = 0;
+ IEnumerator enumeratorAllies = data.Allies();
+ while (enumeratorAllies.MoveNext())
+ {
+ AIEnvAgentInfo info = enumeratorAllies.Current;
+ if (info.thing is Pawn ally && DamageUtility.GetDamageReport(ally).primaryIsRanged)
+ {
+ rangedNum++;
+ if (ally.stances?.curStance is Stance_Warmup)
+ {
+ shootingNum++;
+ }
+ }
+ }
+ progress = 522;
+ float distOffset = Mathf.Clamp(2.0f * shootingNum - rangedEnemiesTargetingSelf.Count, 0, 25);
+ float moveBias = Mathf.Clamp01(2f * shootingNum / (rangedNum + 1f) * personality.group);
+ if (Finder.Settings.Debug_LogJobs && distOffset > 0)
+ {
+ selPawn.Map.debugDrawer.FlashCell(selPos, distOffset / 20f, $"{distOffset}");
+ }
+ if (moveBias <= 0.5f)
+ {
+ moveBias = 0f;
+ }
+ if (duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint) && Rand.Chance(1 - moveBias))
+ {
+ return;
+ }
+ if (bestEnemyVisibleNow)
+ {
+ progress = 523;
+ if (nearestEnemyDist > 6 * personality.cover)
+ {
+ CastPositionRequest request = new CastPositionRequest();
+ request.caster = selPawn;
+ request.target = nearestEnemy;
+ request.maxRangeFromTarget = 9999;
+ request.verb = verb;
+ request.maxRangeFromCaster = (Maths.Max(Maths.Min(verb.EffectiveRange, nearestEnemyDist) / 2f, 10f) * personality.cover + distOffset) * Finder.P50;
+ request.wantCoverFromTarget = true;
+ if (CastPositionFinder.TryFindCastPosition(request, out IntVec3 cell) && cell != selPos && (ShouldMoveTo(cell) || Rand.Chance(moveBias)))
+ {
+ StartOrQueueCoverJob(cell, 0);
+ }
+ else if (ShouldShootNow())
+ {
+ _last = 52;
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100);
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.endIfCantShootTargetFromCurPos = true;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced);
+ data.LastInterrupted = GenTicks.TicksGame;
+ }
+ }
+ else if (ShouldShootNow())
+ {
+ _last = 53;
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100);
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.endIfCantShootTargetFromCurPos = true;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced);
+ data.LastInterrupted = GenTicks.TicksGame;
+ }
+ }
+ // best enemy is approaching but not yet in view
+ else if (bestEnemyVisibleSoon)
+ {
+ progress = 524;
+ _last = 60;
+ CoverPositionRequest request = new CoverPositionRequest();
+ request.caster = selPawn;
+ request.verb = verb;
+ request.target = nearestEnemy;
+ if (!Finder.Performance.TpsCriticallyLow)
+ {
+ request.majorThreats = new List();
+ for (int i = 0; i < rangedEnemiesTargetingSelf.Count; i++)
+ {
+ request.majorThreats.Add(rangedEnemiesTargetingSelf[Rand.Int % rangedEnemiesTargetingSelf.Count]);
+ }
+ request.maxRangeFromCaster = Maths.Min(verb.EffectiveRange, 10f) + distOffset;
+ }
+ else
+ {
+ request.maxRangeFromCaster = Maths.Max(verb.EffectiveRange, 10f);
+ }
+ request.maxRangeFromCaster *= personality.cover;
+ request.checkBlockChance = true;
+ if (CoverPositionFinder.TryFindCoverPosition(request, out IntVec3 cell))
+ {
+ if (ShouldMoveTo(cell) || Rand.Chance(moveBias))
+ {
+ StartOrQueueCoverJob(cell, 10);
+ }
+ else if (nearestEnemy is Pawn enemyPawn)
+ {
+ _last = 71;
+ // fallback
+ request.target = PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 90);
+ request.maxRangeFromCaster = Mathf.Min(request.maxRangeFromCaster, 5) + distOffset;
+ if (verb.CanHitFromCellIgnoringRange(selPos, request.target, out _) && CoverPositionFinder.TryFindCoverPosition(request, out cell) && (ShouldMoveTo(cell) || Rand.Chance(moveBias)))
+ {
+ StartOrQueueCoverJob(cell, 20);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void TryMeleeReaction(float possibleDmg, ref int progress)
+ {
+ float nearestEnemyDist = 1e6f;
+ Thing nearestEnemy = null;
+ // For debugging and logging.
+ progress = 201;
+ if ((selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Retreat) || selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover)) && (rangedEnemiesTargetingSelf.Count == 0 || possibleDmg < 2.5f))
+ {
+ _last = 30;
+ selPawn.jobs.StopAll();
+ }
+ bool bestEnemyIsRanged = false;
+ bool bestEnemyIsMeleeAttackingAlly = false;
+ // TODO create melee reactions.
+ IEnumerator enumeratorEnemies = data.EnemiesWhere(AIEnvAgentState.nearby);
+ // For debugging and logging.
+ progress = 202;
+ while (enumeratorEnemies.MoveNext())
+ {
+ AIEnvAgentInfo info = enumeratorEnemies.Current;
#if DEBUG_REACTION
- if (info.thing == null)
- {
- Log.Error("Found null thing (2)");
- continue;
- }
+ if (info.thing == null)
+ {
+ Log.Error("Found null thing (2)");
+ continue;
+ }
#endif
- if (info.thing.Spawned && selPawn.CanReach(info.thing, PathEndMode.Touch, Danger.Deadly))
- {
- Verb enemyVerb = info.thing.TryGetAttackVerb();
- if (enemyVerb?.IsMeleeAttack == true && info.thing is Pawn enemyPawn && enemyPawn.CurJob.Is(JobDefOf.AttackMelee) && enemyPawn.CurJob.targetA.Thing?.TryGetAttackVerb()?.IsMeleeAttack == false)
- {
- if (!bestEnemyIsMeleeAttackingAlly)
- {
- bestEnemyIsMeleeAttackingAlly = true;
- nearestEnemyDist = 1e5f;
- nearestEnemy = null;
- }
- UpdateNearestEnemy(info.thing);
- }
- else if (!bestEnemyIsMeleeAttackingAlly)
- {
- if (enemyVerb?.IsMeleeAttack == false)
- {
- if (!bestEnemyIsRanged)
- {
- bestEnemyIsRanged = true;
- nearestEnemyDist = 1e5f;
- nearestEnemy = null;
- }
- UpdateNearestEnemy(info.thing);
- }
- else if (!bestEnemyIsRanged)
- {
- UpdateNearestEnemy(info.thing);
- }
- }
- }
- }
- if (nearestEnemy == null)
- {
- nearestEnemy = selPawn.mindState.enemyTarget;
- }
- if (nearestEnemy == null || selPawn.CurJob.Is(JobDefOf.AttackMelee) && selPawn.CurJob.targetA.Thing == nearestEnemy)
- {
- return;
- }
- _bestEnemy = nearestEnemy;
- if (!selPawn.mindState.MeleeThreatStillThreat || selPawn.stances?.stagger?.Staggered == false)
- {
- _last = 31;
- Job job_melee = JobMaker.MakeJob(JobDefOf.AttackMelee, nearestEnemy);
- job_melee.playerForced = forcedTarget.IsValid;
- job_melee.locomotionUrgency = LocomotionUrgency.Jog;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_melee, JobCondition.InterruptForced);
- data.LastInterrupted = GenTicks.TicksGame;
- // no enemy cannot be approached solo
- // TODO
- // no enemy can be approached solo
- // TODO
- }
- }
- // ranged
- else
- {
- // if CE is active skip reaction if the pawn is reloading or hunkering down.
- if (Mod_CE.active && (selPawn.CurJobDef.Is(Mod_CE.ReloadWeapon) || selPawn.CurJobDef.Is(Mod_CE.HunkerDown)))
- {
- return;
- }
- // check if the verb is available.
- if (!verb.Available() || Mod_CE.active && Mod_CE.IsAimingCE(verb))
- {
- return;
- }
- bool bestEnemyVisibleNow = false;
- bool bestEnemyVisibleSoon = false;
- // A not fast check will check for retreat and for reactions to enemies that are visible or soon to be visible.
- // A fast check will check only for retreat.
- IEnumerator enumerator = data.Enemies();
- while (enumerator.MoveNext())
- {
- AIEnvAgentInfo info = enumerator.Current;
+ // For debugging and logging.
+ progress = 203;
+ if (info.thing.Spawned && selPawn.CanReach(info.thing, PathEndMode.Touch, Danger.Deadly))
+ {
+ Verb enemyVerb = info.thing.TryGetAttackVerb();
+ if (enemyVerb?.IsMeleeAttack == true && info.thing is Pawn enemyPawn && enemyPawn.CurJob.Is(JobDefOf.AttackMelee) && enemyPawn.CurJob.targetA.Thing?.TryGetAttackVerb()?.IsMeleeAttack == false)
+ {
+ if (!bestEnemyIsMeleeAttackingAlly)
+ {
+ bestEnemyIsMeleeAttackingAlly = true;
+ nearestEnemyDist = 1e5f;
+ nearestEnemy = null;
+ }
+ float dist = selPawn.DistanceTo_Fast(info.thing);
+ if (dist < nearestEnemyDist)
+ {
+ nearestEnemyDist = dist;
+ nearestEnemy = info.thing;
+ }
+ }
+ else if (!bestEnemyIsMeleeAttackingAlly)
+ {
+ if (enemyVerb?.IsMeleeAttack == false)
+ {
+ if (!bestEnemyIsRanged)
+ {
+ bestEnemyIsRanged = true;
+ nearestEnemyDist = 1e5f;
+ nearestEnemy = null;
+ }
+ float dist = selPawn.DistanceTo_Fast(info.thing);
+ if (dist < nearestEnemyDist)
+ {
+ nearestEnemyDist = dist;
+ nearestEnemy = info.thing;
+ }
+ }
+ else if (!bestEnemyIsRanged)
+ {
+ float dist = selPawn.DistanceTo_Fast(info.thing);
+ if (dist < nearestEnemyDist)
+ {
+ nearestEnemyDist = dist;
+ nearestEnemy = info.thing;
+ }
+ }
+ }
+ }
+ // For debugging and logging.
+ progress = 204;
+ }
+ // For debugging and logging.
+ progress = 205;
+ if (nearestEnemy == null)
+ {
+ nearestEnemy = selPawn.mindState.enemyTarget;
+ }
+ if (nearestEnemy == null || selPawn.CurJob.Is(JobDefOf.AttackMelee) && selPawn.CurJob.targetA.Thing == nearestEnemy)
+ {
+ return;
+ }
+ // For debugging and logging.
+ progress = 206;
+ _bestEnemy = nearestEnemy;
+ if (!selPawn.mindState.MeleeThreatStillThreat || selPawn.stances?.stagger?.Staggered == false)
+ {
+ _last = 31;
+ Job job_melee = JobMaker.MakeJob(JobDefOf.AttackMelee, nearestEnemy);
+ job_melee.playerForced = forcedTarget.IsValid;
+ job_melee.locomotionUrgency = LocomotionUrgency.Jog;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_melee, JobCondition.InterruptForced);
+ data.LastInterrupted = GenTicks.TicksGame;
+ }
+ // For debugging and logging.
+ progress = 207;
+ }
+
+ private void ReactDebug_Internel(out int progress)
+ {
+ // For debugging and logging.
+ progress = 2;
#if DEBUG_REACTION
- if (info.thing == null)
- {
- Log.Error("Found null thing (3)");
- continue;
- }
+ if (Finder.Settings.Debug && Finder.Settings.Debug_ValidateSight)
+ {
+ _visibleEnemies.Clear();
+ IEnumerator enumerator = data.Enemies();
+ while (enumerator.MoveNext())
+ {
+ AIEnvAgentInfo info = enumerator.Current;
+ if (info.thing == null)
+ {
+ Log.Warning("Found null thing (1)");
+ continue;
+ }
+ if (info.thing.Spawned && info.thing is Pawn pawn)
+ {
+ _visibleEnemies.Add(pawn);
+ }
+ }
+ // For debugging and logging.
+ progress = 21;
+ if (_path.Count == 0 || _path.Last() != parent.Position)
+ {
+ _path.Add(parent.Position);
+ if (GenTicks.TicksGame - lastInterupted < 150)
+ {
+ _colors.Add(Color.red);
+ }
+ else if (GenTicks.TicksGame - lastInterupted < 240)
+ {
+ _colors.Add(Color.yellow);
+ }
+ else
+ {
+ _colors.Add(Color.black);
+ }
+ if (_path.Count >= 30)
+ {
+ _path.RemoveAt(0);
+ _colors.RemoveAt(0);
+ }
+ }
+ // For debugging and logging.
+ progress = 22;
+ }
#endif
- if (info.thing.Spawned)
- {
- Pawn enemyPawn = info.thing as Pawn;
- if (verb.CanHitTarget(info.thing))
- {
- if (!bestEnemyVisibleNow)
- {
- nearestEnemy = null;
- nearestEnemyDist = 1e4f;
- bestEnemyVisibleNow = true;
- }
- UpdateNearestEnemy(info.thing);
- }
- else if (enemyPawn != null && !bestEnemyVisibleNow)
- {
- if (verb.CanHitTarget(PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120)))
- {
- if (!bestEnemyVisibleSoon)
- {
- nearestEnemy = null;
- nearestEnemyDist = 1e4f;
- bestEnemyVisibleSoon = true;
- }
- UpdateNearestEnemy(info.thing);
- }
- else if (!bestEnemyVisibleSoon)
- {
- UpdateNearestEnemy(info.thing);
- }
- }
- if (enemyPawn != null && enemyPawn.CurrentEffectiveVerb.IsMeleeAttack)
- {
- UpdateNearestEnemyMelee(enemyPawn);
- }
- }
- }
-
- void StartOrQueueCoverJob(IntVec3 cell, int codeOffset)
- {
- if (selPawn.CurJob.Is(JobDefOf.Wait_Combat))
- {
- _last = 50 + codeOffset;
- Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Cover, cell);
- job_goto.playerForced = forcedTarget.IsValid;
- job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
- Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 150 + 200);
- job_waitCombat.targetA = nearestEnemy;
- job_waitCombat.playerForced = forcedTarget.IsValid;
- job_waitCombat.endIfCantShootTargetFromCurPos = true;
- job_waitCombat.checkOverrideOnExpire = true;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
- selPawn.jobs.jobQueue.EnqueueFirst(job_goto);
- data.LastInterrupted = GenTicks.TicksGame;
- }
- else
- {
- _last = 51 + codeOffset;
- selPawn.mindState.enemyTarget = nearestEnemy;
- Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Cover, cell);
- job_goto.playerForced = forcedTarget.IsValid;
- job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
- Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 150 + 200);
- job_waitCombat.playerForced = forcedTarget.IsValid;
- job_waitCombat.endIfCantShootTargetFromCurPos = true;
- job_waitCombat.checkOverrideOnExpire = true;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
- selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
- data.LastInterrupted = GenTicks.TicksGame;
- }
- }
-
- if (nearestEnemy != null && rangedEnemiesTargetingSelf.Contains(nearestEnemy))
- {
- rangedEnemiesTargetingSelf.Remove(nearestEnemy);
- }
- bool retreatMeleeThreat = nearestMeleeEnemy != null && nearestMeleeEnemyDist < Maths.Max(verb.EffectiveRange / 3f, 9);
- bool retreatThreat = !retreatMeleeThreat && nearestEnemy != null && nearestEnemyDist < Maths.Max(verb.EffectiveRange / 4f, 5);
- _bestEnemy = retreatMeleeThreat ? nearestMeleeEnemy : nearestEnemy;
- // retreat because of a close melee threat
- if (bodySize < 2.0f && (retreatThreat || retreatMeleeThreat))
- {
- _bestEnemy = retreatThreat ? nearestEnemy : nearestMeleeEnemy;
- _last = 40;
- CoverPositionRequest request = new CoverPositionRequest();
- request.caster = selPawn;
- request.target = nearestMeleeEnemy;
- request.verb = verb;
- request.majorThreats = rangedEnemiesTargetingSelf;
- request.checkBlockChance = true;
- request.maxRangeFromCaster = verb.EffectiveRange / 2 + 8;
- if (CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell))
- {
- if (cell != selPos)
- {
- _last = 41;
- Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Retreat, cell);
- job_goto.playerForced = forcedTarget.IsValid;
- job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
- data.LastRetreated = GenTicks.TicksGame;
- }
- }
- }
- // best enemy is insight
- else if (nearestEnemy != null)
- {
- _bestEnemy = nearestEnemy;
+ }
+
+ private bool TryStartRetreat(float possibleDmg, float possibleDmgWarmup, float possibleDmgDistance, PersonalityTacker.PersonalityResult personality, Pawn nearestMeleeEnemy, float nearestMeleeEnemyDist, ref int progress)
+ {
+ IntVec3 selPos = selPawn.Position;
+ if (rangedEnemiesTargetingSelf.Count > 0 && nearestMeleeEnemyDist > 2)
+ {
+ float retreatRoll = 15 + Rand.Range(0, 15 * rangedEnemiesTargetingSelf.Count) + data.NumAllies * 15;
+ if (Finder.Settings.Debug_LogJobs)
+ {
+ MoteMaker.ThrowText(selPawn.DrawPos, selPawn.Map, $"r:{Math.Round(retreatRoll)},d:{possibleDmg}", Color.white);
+ }
+ // For debugging and logging.
+ progress = 91;
+ // major retreat attempt if the pawn is doomed
+ if (possibleDmg * personality.retreat - retreatRoll > 0.001f && possibleDmg * personality.retreat >= 50)
+ {
+ _last = 10;
+ _bestEnemy = nearestMeleeEnemy;
+ CoverPositionRequest request = new CoverPositionRequest();
+ request.caster = selPawn;
+ request.target = nearestMeleeEnemy;
+ request.majorThreats = rangedEnemiesTargetingSelf;
+ request.maxRangeFromCaster = 12;
+ request.checkBlockChance = true;
+ if (CoverPositionFinder.TryFindRetreatPosition(request, out IntVec3 cell))
+ {
+ // For debugging and logging.
+ progress = 911;
+ if (ShouldMoveTo(cell))
+ {
+ if (cell != selPos)
+ {
+ _last = 11;
+ Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Retreat, cell);
+ job_goto.playerForced = forcedTarget.IsValid;
+ job_goto.checkOverrideOnExpire = false;
+ job_goto.expiryInterval = -1;
+ job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
+ data.LastRetreated = GenTicks.TicksGame;
+ if (Rand.Chance(0.5f) && !Finder.Settings.Debug_LogJobs)
+ {
+ MoteMaker.ThrowText(selPawn.DrawPos, selPawn.Map, "Cover me!", Color.white);
+ }
+ }
+ return true;
+ }
+ if (Finder.Settings.Debug_LogJobs)
+ {
+ MoteMaker.ThrowText(selPawn.DrawPos, selPawn.Map, "retreat skipped", Color.white);
+ }
+ }
+ }
+ // For debugging and logging.
+ progress = 92;
+ // try minor retreat (duck for cover fast)
+ if (possibleDmg * personality.duck - retreatRoll * 0.5f > 0.001f && possibleDmg * personality.duck >= 30)
+ {
+ // selPawn.Map.debugDrawer.FlashCell(selPos, 1.0f, $"{possibleDmg}, {targetedBy.Count}, {rangedEnemiesTargetingSelf.Count}");
+ CoverPositionRequest request = new CoverPositionRequest();
+ request.caster = selPawn;
+ request.majorThreats = rangedEnemiesTargetingSelf;
+ request.checkBlockChance = true;
+ request.maxRangeFromCaster = Mathf.Clamp(possibleDmgWarmup * 5f - rangedEnemiesTargetingSelf.Count, 4f, 8f);
+ if (CoverPositionFinder.TryFindDuckPosition(request, out IntVec3 cell))
+ {
+ bool diff = cell != selPos;
+ // run to cover
+ if (diff)
+ {
+ _last = 12;
+ Job job_goto = JobMaker.MakeJob(CombatAI_JobDefOf.CombatAI_Goto_Duck, cell);
+ job_goto.playerForced = forcedTarget.IsValid;
+ job_goto.checkOverrideOnExpire = false;
+ job_goto.expiryInterval = -1;
+ job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog;
+ selPawn.jobs.ClearQueuedJobs();
+ selPawn.jobs.StartJob(job_goto, JobCondition.InterruptForced);
+ data.LastRetreated = lastRetreated = GenTicks.TicksGame;
+ }
+ if (data.TookDamageRecently(45) || !diff)
+ {
+ _last = 13;
+ Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 50 + 50);
+ job_waitCombat.playerForced = forcedTarget.IsValid;
+ job_waitCombat.checkOverrideOnExpire = true;
+ selPawn.jobs.jobQueue.EnqueueFirst(job_waitCombat);
+ data.LastRetreated = lastRetreated = GenTicks.TicksGame;
+ }
+ if (Rand.Chance(0.5f) && !Finder.Settings.Debug_LogJobs)
+ {
+ MoteMaker.ThrowText(selPawn.DrawPos, selPawn.Map, "Finding cover!", Color.white);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
- if (!selPawn.RaceProps.Humanlike || bodySize > 2.0f)
- {
- if (bestEnemyVisibleNow && selPawn.mindState.enemyTarget == null)
- {
- selPawn.mindState.enemyTarget = nearestEnemy;
- }
- }
- else if (bestEnemyVisibleNow)
- {
- if (nearestEnemyDist > 8)
- {
- CastPositionRequest request = new CastPositionRequest();
- request.caster = selPawn;
- request.target = nearestEnemy;
- request.maxRangeFromTarget = 9999;
- request.verb = verb;
- request.maxRangeFromCaster = Maths.Max(Maths.Min(verb.EffectiveRange, nearestEnemyDist) / 2f, 10f);
- request.wantCoverFromTarget = true;
- if (CastPositionFinder.TryFindCastPosition(request, out IntVec3 cell) && ShouldMoveTo(cell))
- {
- StartOrQueueCoverJob(cell, 0);
- }
- else if (!selPawn.CurJob.Is(JobDefOf.Wait_Combat))
- {
- _last = 52;
- Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100);
- job_waitCombat.playerForced = forcedTarget.IsValid;
- job_waitCombat.endIfCantShootTargetFromCurPos = true;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced);
- data.LastInterrupted = GenTicks.TicksGame;
- }
- }
- else
- {
- _last = 53;
- Job job_waitCombat = JobMaker.MakeJob(JobDefOf.Wait_Combat, Rand.Int % 100 + 100);
- job_waitCombat.playerForced = forcedTarget.IsValid;
- job_waitCombat.endIfCantShootTargetFromCurPos = true;
- selPawn.jobs.ClearQueuedJobs();
- selPawn.jobs.StartJob(job_waitCombat, JobCondition.InterruptForced);
- data.LastInterrupted = GenTicks.TicksGame;
- }
- }
- // best enemy is approaching but not yet in view
- else if (bestEnemyVisibleSoon || duty.Is(DutyDefOf.Escort) || duty.Is(CombatAI_DutyDefOf.CombatAI_Escort) || duty.Is(DutyDefOf.Defend) || duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint) || duty.Is(DutyDefOf.HuntEnemiesIndividual))
- {
- _last = 60;
- CoverPositionRequest request = new CoverPositionRequest();
- request.caster = selPawn;
- request.verb = verb;
- request.target = nearestEnemy;
- if (!bestEnemyVisibleSoon && !Finder.Performance.TpsCriticallyLow)
- {
- while (rangedEnemiesTargetingSelf.Count > 3)
- {
- rangedEnemiesTargetingSelf.RemoveAt(Rand.Int % rangedEnemiesTargetingSelf.Count);
- }
- request.majorThreats = rangedEnemiesTargetingSelf;
- request.maxRangeFromCaster = Maths.Min(verb.EffectiveRange, 10f);
- }
- else
- {
- request.maxRangeFromCaster = Maths.Max(verb.EffectiveRange / 2f, 10f);
- }
- request.checkBlockChance = true;
- if (CoverPositionFinder.TryFindCoverPosition(request, out IntVec3 cell))
- {
- if (ShouldMoveTo(cell))
- {
- StartOrQueueCoverJob(cell, 10);
- }
- else if (nearestEnemy is Pawn enemyPawn)
- {
- _last = 71;
- // fallback
- request.target = PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 90);
- request.maxRangeFromCaster = Mathf.Min(request.maxRangeFromCaster, 5);
- if (verb.CanHitFromCellIgnoringRange(selPos, request.target, out _) && CoverPositionFinder.TryFindCoverPosition(request, out cell) && ShouldMoveTo(cell))
- {
- StartOrQueueCoverJob(cell, 20);
- }
- }
- }
- }
- }
- }
- }
- }
+ ///
+ /// Returns whether parent pawn should move to a new position.
+ ///
+ /// New position
+ /// Whether to move or not
+ private bool ShouldMoveTo(IntVec3 newPos)
+ {
+ IntVec3 pos = selPawn.Position;
+ if (pos == newPos)
+ {
+ return true;
+ }
+ float curVisibility = sightReader.GetVisibilityToEnemies(pos);
+ float curThreat = sightReader.GetVisibilityToEnemies(pos);
+ Job job = selPawn.CurJob;
+ if (curThreat == 0 && curVisibility == 0 && !(job.Is(JobDefOf.Wait_Combat) || job.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) || job.Is(CombatAI_JobDefOf.CombatAI_Goto_Duck) || job.Is(CombatAI_JobDefOf.CombatAI_Goto_Retreat)))
+ {
+ return sightReader.GetVisibilityToEnemies(newPos) <= 2f && sightReader.GetThreat(newPos) < 1f;
+ }
+ float visDiff = curVisibility - sightReader.GetVisibilityToEnemies(newPos);
+ float magDiff = Maths.Sqrt_Fast(sightReader.GetEnemyDirection(pos).sqrMagnitude, 4) - Maths.Sqrt_Fast(sightReader.GetEnemyDirection(newPos).sqrMagnitude, 4);
+ float threatDiff = curThreat - sightReader.GetThreat(newPos);
+ return Rand.Chance(visDiff) && Rand.Chance(threatDiff) && Rand.Chance(magDiff);
+ }
- ///
- /// Returns whether parent pawn should move to a new position.
- ///
- /// New position
- /// Whether to move or not
- private bool ShouldMoveTo(IntVec3 newPos)
- {
- IntVec3 pos = selPawn.Position;
- float curVisibility = sightReader.GetVisibilityToEnemies(pos);
- float curThreat = sightReader.GetVisibilityToEnemies(pos);
- Job job = selPawn.CurJob;
- if (curThreat == 0 && curVisibility == 0 && !(job.Is(JobDefOf.Wait_Combat) || job.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) || job.Is(CombatAI_JobDefOf.CombatAI_Goto_Duck) || job.Is(CombatAI_JobDefOf.CombatAI_Goto_Retreat)))
- {
- return sightReader.GetVisibilityToEnemies(newPos) <= 2f && sightReader.GetThreat(newPos) < 1f;
- }
- float visDiff = curVisibility - sightReader.GetVisibilityToEnemies(newPos);
- float magDiff = Maths.Sqrt_Fast(sightReader.GetEnemyDirection(pos).sqrMagnitude, 4) - Maths.Sqrt_Fast(sightReader.GetEnemyDirection(newPos).sqrMagnitude, 4);
- float threatDiff = curThreat - sightReader.GetThreat(newPos);
- return Rand.Chance(visDiff) && Rand.Chance(threatDiff) && Rand.Chance(magDiff);
- }
+ ///
+ /// Whether the pawn should start shooting now.
+ ///
+ ///
+ private bool ShouldShootNow()
+ {
+ return !selPawn.CurJob.Is(JobDefOf.Wait_Combat) && (!selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) && !selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Duck) || !ShouldMoveTo(selPawn.CurJob.targetA.Cell));
+ }
- ///
- /// Called When the parent takes damage.
- ///
- /// Damage info
- public void Notify_TookDamage(DamageInfo dInfo)
- {
- // notify the custom duty manager that this pawn took damage.
- if (duties != null)
- {
- duties.Notify_TookDamage();
- }
- data.LastTookDamage = lastTookDamage = GenTicks.TicksGame;
- if (dInfo.Instigator != null && data.NumAllies != 0 && dInfo.Instigator.HostileTo(selPawn))
- {
- StartAggroCountdown(dInfo.Instigator);
- }
- }
+ ///
+ /// Called When the parent takes damage.
+ ///
+ /// Damage info
+ public void Notify_TookDamage(DamageInfo dInfo)
+ {
+ // notify the custom duty manager that this pawn took damage.
+ if (duties != null)
+ {
+ duties.Notify_TookDamage();
+ }
+ data.LastTookDamage = lastTookDamage = GenTicks.TicksGame;
+ if (dInfo.Instigator != null && data.NumAllies != 0 && dInfo.Instigator.HostileTo(selPawn))
+ {
+ StartAggroCountdown(dInfo.Instigator);
+ }
+ }
- ///
- /// Called when a bullet impacts nearby.
- ///
- /// Attacker
- /// Impact position
- public void Notify_BulletImpact(Thing instigator, IntVec3 cell)
- {
- if (instigator == null)
- {
- StartAggroCountdown(new LocalTargetInfo(cell));
- }
- else
- {
- StartAggroCountdown(new LocalTargetInfo(instigator));
- }
- }
- ///
- /// Start aggro countdown.
- ///
- /// Enemy.
- public void StartAggroCountdown(LocalTargetInfo enemy)
- {
- aggroTarget = enemy;
- aggroTicks = Rand.Range(30, 90);
- }
+ ///
+ /// Called when a bullet impacts nearby.
+ ///
+ /// Attacker
+ /// Impact position
+ public void Notify_BulletImpact(Thing instigator, IntVec3 cell)
+ {
+ if (instigator == null)
+ {
+ StartAggroCountdown(new LocalTargetInfo(cell));
+ }
+ else
+ {
+ StartAggroCountdown(new LocalTargetInfo(instigator));
+ }
+ }
+ ///
+ /// Start aggro countdown.
+ ///
+ /// Enemy.
+ public void StartAggroCountdown(LocalTargetInfo enemy)
+ {
+ aggroTarget = enemy;
+ aggroTicks = Rand.Range(30, 90);
+ }
- ///
- /// Switch the pawn to an aggro mode and their allies around them.
- ///
- /// Attacker
- /// Chance to aggro nearbyAllies
- /// Aggro sig
- private void TryAggro(LocalTargetInfo enemy, float aggroAllyChance, int sig)
- {
- if (selPawn.mindState.duty.Is(DutyDefOf.Defend) && data.AgroSig != sig)
- {
- Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.HuntDownEnemies(enemy.Cell, Rand.Int % 1200 + 2400);
- if (selPawn.TryStartCustomDuty(custom))
- {
- data.AgroSig = sig;
- // aggro nearby Allies
- IEnumerator allies = data.AlliesNearBy();
- while (allies.MoveNext())
- {
- AIEnvAgentInfo ally = allies.Current;
- // make allies not targeting anyone target the attacking enemy
- if (Rand.Chance(aggroAllyChance) && ally.thing is Pawn { Destroyed: false, Spawned: true, Downed: false } other && other.mindState.duty.Is(DutyDefOf.Defend))
- {
- ThingComp_CombatAI comp = other.AI();
- if (comp != null && comp.data.AgroSig != sig)
- {
- if (enemy.HasThing)
- {
- other.mindState.enemyTarget ??= enemy.Thing;
- }
- comp.TryAggro(enemy, aggroAllyChance / 2f, sig);
- }
- }
- }
- }
- }
- }
+ ///
+ /// Switch the pawn to an aggro mode and their allies around them.
+ ///
+ /// Attacker
+ /// Chance to aggro nearbyAllies
+ /// Aggro sig
+ private void TryAggro(LocalTargetInfo enemy, float aggroAllyChance, int sig)
+ {
+ if (selPawn.mindState.duty.Is(DutyDefOf.Defend) && data.AgroSig != sig)
+ {
+ Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.HuntDownEnemies(enemy.Cell, Rand.Int % 1200 + 2400);
+ if (selPawn.TryStartCustomDuty(custom))
+ {
+ data.AgroSig = sig;
+ // aggro nearby Allies
+ IEnumerator allies = data.AlliesNearBy();
+ while (allies.MoveNext())
+ {
+ AIEnvAgentInfo ally = allies.Current;
+ // make allies not targeting anyone target the attacking enemy
+ if (Rand.Chance(aggroAllyChance) && ally.thing is Pawn { Destroyed: false, Spawned: true, Downed: false } other && other.mindState.duty.Is(DutyDefOf.Defend))
+ {
+ ThingComp_CombatAI comp = other.AI();
+ if (comp != null && comp.data.AgroSig != sig)
+ {
+ if (enemy.HasThing)
+ {
+ other.mindState.enemyTarget ??= enemy.Thing;
+ }
+ comp.TryAggro(enemy, aggroAllyChance / 2f, sig);
+ }
+ }
+ }
+ }
+ }
+ }
- ///
- /// Start a sapping task.
- ///
- /// Blocked cells
- /// Cell before blocked cells
- /// Whether to look for escorts
- public void StartSapper(List blocked, IntVec3 cellBefore, bool findEscorts)
- {
- if (cellBefore.IsValid && sapperNodes.Count > 0 && GenTicks.TicksGame - sapperStartTick < 4800)
- {
- ReleaseEscorts(false);
- }
- this.cellBefore = cellBefore;
- this.findEscorts = findEscorts;
- sapperStartTick = GenTicks.TicksGame;
- sapperNodes.Clear();
- sapperNodes.AddRange(blocked);
- _sap = 0;
- TryStartSapperJob();
- }
+ ///
+ /// Start a sapping task.
+ ///
+ /// Blocked cells
+ /// Cell before blocked cells
+ /// Whether to look for escorts
+ public void StartSapper(List blocked, IntVec3 cellBefore, IntVec3 cellAhead, bool findEscorts)
+ {
+ if (cellBefore.IsValid && sapperNodes.Count > 0 && GenTicks.TicksGame - sapperStartTick < 4800)
+ {
+ ReleaseEscorts(false);
+ }
+ this.cellBefore = cellBefore;
+ this.cellAhead = cellAhead;
+ this.findEscorts = findEscorts;
+ sapperStartTick = GenTicks.TicksGame;
+ sapperNodes.Clear();
+ sapperNodes.AddRange(blocked);
+ _sap = 0;
+// TryStartSapperJob();
+ }
- ///
- /// Returns debug gizmos.
- ///
- ///
- public override IEnumerable CompGetGizmosExtra()
- {
- if (Finder.Settings.Debug && Finder.Settings.Debug_LogJobs)
- {
- Command_Action jobs = new Command_Action();
- jobs.defaultLabel = "DEV: View job logs";
- jobs.action = delegate
- {
- if (Find.WindowStack.windows.Any(w => w is Window_JobLogs logs && logs.comp == this))
- {
- return;
- }
- jobLogs ??= new List();
- Window_JobLogs window = new Window_JobLogs(this);
- Find.WindowStack.Add(window);
- };
- yield return jobs;
- }
- 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", 150);
- }
- };
- Command_Action duck = new Command_Action();
- duck.defaultLabel = "DEV: Duck position search";
- duck.action = delegate
- {
- CoverPositionRequest request = new CoverPositionRequest();
- request.majorThreats = data.BeingTargetedBy;
- request.caster = selPawn;
- request.verb = verb;
- request.maxRangeFromCaster = 5;
- request.checkBlockChance = true;
- CoverPositionFinder.TryFindDuckPosition(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", 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", 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", 150);
- }
- }
- catch (Exception er)
- {
- Log.Error(er.ToString());
- }
- finally
- {
- DebugViewSettings.drawCastPositionSearch = false;
- }
- };
- yield return retreat;
- yield return duck;
- yield return cover;
- yield return cast;
- }
- if (selPawn.IsColonist)
- {
- Command_Target attackMove = new Command_Target();
- attackMove.defaultLabel = Keyed.CombatAI_Gizmos_AttackMove;
- attackMove.targetingParams = new TargetingParameters();
- attackMove.targetingParams.canTargetPawns = true;
- attackMove.targetingParams.canTargetLocations = true;
- attackMove.targetingParams.canTargetSelf = false;
- attackMove.targetingParams.validator = target =>
- {
- if (!target.IsValid || !target.Cell.InBounds(selPawn.Map))
- {
- return false;
- }
- foreach (Pawn pawn in Find.Selector.SelectedPawns)
- {
- if (pawn == null)
- {
- continue;
- }
- if (pawn.CanReach(target.Cell, PathEndMode.OnCell, Danger.Deadly))
- {
- return true;
- }
- }
- return false;
- };
- attackMove.icon = Tex.Isma_Gizmos_move_attack;
- attackMove.groupable = true;
- attackMove.shrinkable = false;
- attackMove.action = target =>
- {
- foreach (Pawn pawn in Find.Selector.SelectedPawns)
- {
- if (pawn.IsColonist && pawn.drafter != null)
- {
- if (!pawn.CanReach(target.Cell, PathEndMode.OnCell, Danger.Deadly))
- {
- continue;
- }
- if (!pawn.Drafted)
- {
- if (!pawn.drafter.ShowDraftGizmo)
- {
- continue;
- }
- DevelopmentalStage stage = pawn.DevelopmentalStage;
- if (stage <= DevelopmentalStage.Child && stage != DevelopmentalStage.None)
- {
- continue;
- }
- pawn.drafter.Drafted = true;
- }
- if (pawn.CurrentEffectiveVerb?.IsMeleeAttack ?? true)
- {
- Messages.Message(Keyed.CombatAI_Gizmos_AttackMove_Warning, MessageTypeDefOf.RejectInput, false);
- continue;
- }
- pawn.AI().forcedTarget = target;
- Job gotoJob = JobMaker.MakeJob(JobDefOf.Goto, target);
- gotoJob.canUseRangedWeapon = true;
- gotoJob.locomotionUrgency = LocomotionUrgency.Jog;
- gotoJob.playerForced = true;
- pawn.jobs.ClearQueuedJobs();
- pawn.jobs.StartJob(gotoJob);
- }
- }
- };
- yield return attackMove;
- if (forcedTarget.IsValid)
- {
- Command_Action cancelAttackMove = new Command_Action();
- cancelAttackMove.defaultLabel = Keyed.CombatAI_Gizmos_AttackMove_Cancel;
- cancelAttackMove.groupable = true;
- //
- // cancelAttackMove.disabled = forcedTarget.IsValid;
- cancelAttackMove.action = () =>
- {
- foreach (Pawn pawn in Find.Selector.SelectedPawns)
- {
- if (pawn.IsColonist)
- {
- pawn.AI().forcedTarget = LocalTargetInfo.Invalid;
- pawn.jobs.ClearQueuedJobs();
- pawn.jobs.StopAll();
- }
- }
- };
- }
- }
- }
+ ///
+ /// Returns debug gizmos.
+ ///
+ ///
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ if (Finder.Settings.Debug && Finder.Settings.Debug_LogJobs)
+ {
+ Command_Action jobs = new Command_Action();
+ jobs.defaultLabel = "DEV: View job logs";
+ jobs.action = delegate
+ {
+ if (Find.WindowStack.windows.Any(w => w is Window_JobLogs logs && logs.comp == this))
+ {
+ return;
+ }
+ jobLogs ??= new List();
+ Window_JobLogs window = new Window_JobLogs(this);
+ Find.WindowStack.Add(window);
+ };
+ yield return jobs;
+ }
+ if (Prefs.DevMode && DebugSettings.godMode)
+ {
+ if ((selPawn.mindState.duty.Is(DutyDefOf.Escort) || selPawn.mindState.duty.Is(CombatAI_DutyDefOf.CombatAI_Escort)) && selPawn.mindState.duty.focus.IsValid)
+ {
+ Command_Action escort = new Command_Action();
+ escort.defaultLabel = "DEV: Flash escort area";
+ escort.action = delegate
+ {
+ Pawn focus = selPawn.mindState.duty.focus.Thing as Pawn;
+ Map map = focus.Map;
+ float radius = selPawn.mindState.duty.radius;
+ map.debugDrawer.FlashCell(focus.Position, 1, "XXXXXXX");
+ foreach (IntVec3 cell in GenRadial.RadialCellsAround(focus.Position, 0, 20))
+ {
+ if (JobGiver_CAIFollowEscortee.NearFollowee(selPawn, focus, cell, radius, out _))
+ {
+ map.debugDrawer.FlashCell(cell, 0.9f, $"{cell.HeuristicDistanceTo(focus.Position, map)}");
+ }
+ else
+ {
+ map.debugDrawer.FlashCell(cell, 0.01f, $"{cell.HeuristicDistanceTo(focus.Position, map)}");
+ }
+ }
+ };
+ yield return escort;
+ }
+ 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", 150);
+ }
+ };
+ Command_Action duck = new Command_Action();
+ duck.defaultLabel = "DEV: Duck position search";
+ duck.action = delegate
+ {
+ CoverPositionRequest request = new CoverPositionRequest();
+ request.majorThreats = data.BeingTargetedBy;
+ request.caster = selPawn;
+ request.verb = verb;
+ request.maxRangeFromCaster = 5;
+ request.checkBlockChance = true;
+ CoverPositionFinder.TryFindDuckPosition(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", 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", 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", 150);
+ }
+ }
+ catch (Exception er)
+ {
+ Log.Error(er.ToString());
+ }
+ finally
+ {
+ DebugViewSettings.drawCastPositionSearch = false;
+ }
+ };
+ yield return retreat;
+ yield return duck;
+ yield return cover;
+ yield return cast;
+ }
+ if (selPawn.IsColonist)
+ {
+ Command_Target attackMove = new Command_Target();
+ attackMove.defaultLabel = Keyed.CombatAI_Gizmos_AttackMove;
+ attackMove.targetingParams = new TargetingParameters();
+ attackMove.targetingParams.canTargetPawns = true;
+ attackMove.targetingParams.canTargetLocations = true;
+ attackMove.targetingParams.canTargetSelf = false;
+ attackMove.targetingParams.validator = target =>
+ {
+ if (!target.IsValid || !target.Cell.InBounds(selPawn.Map))
+ {
+ return false;
+ }
+ foreach (Pawn pawn in Find.Selector.SelectedPawns)
+ {
+ if (pawn == null)
+ {
+ continue;
+ }
+ if (pawn.CanReach(target.Cell, PathEndMode.OnCell, Danger.Deadly))
+ {
+ return true;
+ }
+ }
+ return false;
+ };
+ attackMove.icon = Tex.Isma_Gizmos_move_attack;
+ attackMove.groupable = true;
+ attackMove.shrinkable = false;
+ attackMove.action = target =>
+ {
+ foreach (Pawn pawn in Find.Selector.SelectedPawns)
+ {
+ if (pawn.IsColonist && pawn.drafter != null)
+ {
+ if (!pawn.CanReach(target.Cell, PathEndMode.OnCell, Danger.Deadly))
+ {
+ continue;
+ }
+ if (!pawn.Drafted)
+ {
+ if (!pawn.drafter.ShowDraftGizmo)
+ {
+ continue;
+ }
+ DevelopmentalStage stage = pawn.DevelopmentalStage;
+ if (stage <= DevelopmentalStage.Child && stage != DevelopmentalStage.None)
+ {
+ continue;
+ }
+ pawn.drafter.Drafted = true;
+ }
+ if (pawn.CurrentEffectiveVerb?.IsMeleeAttack ?? true)
+ {
+ Messages.Message(Keyed.CombatAI_Gizmos_AttackMove_Warning, MessageTypeDefOf.RejectInput, false);
+ continue;
+ }
+ pawn.AI().forcedTarget = target;
+ Job gotoJob = JobMaker.MakeJob(JobDefOf.Goto, target);
+ gotoJob.canUseRangedWeapon = true;
+ gotoJob.locomotionUrgency = LocomotionUrgency.Jog;
+ gotoJob.playerForced = true;
+ pawn.jobs.ClearQueuedJobs();
+ pawn.jobs.StartJob(gotoJob);
+ }
+ }
+ };
+ yield return attackMove;
+ if (forcedTarget.IsValid)
+ {
+ Command_Action cancelAttackMove = new Command_Action();
+ cancelAttackMove.defaultLabel = Keyed.CombatAI_Gizmos_AttackMove_Cancel;
+ cancelAttackMove.groupable = true;
+ //
+ // cancelAttackMove.disabled = forcedTarget.IsValid;
+ cancelAttackMove.action = () =>
+ {
+ foreach (Pawn pawn in Find.Selector.SelectedPawns)
+ {
+ if (pawn.IsColonist)
+ {
+ pawn.AI().forcedTarget = LocalTargetInfo.Invalid;
+ pawn.jobs.ClearQueuedJobs();
+ pawn.jobs.StopAll();
+ }
+ }
+ };
+ }
+ }
+ }
- ///
- /// Release escorts pawns.
- ///
- public void ReleaseEscorts(bool success)
- {
- for (int i = 0; i < escorts.Count; i++)
- {
- Pawn escort = escorts[i];
- if (escort == null || escort.Destroyed || escort.Dead || escort.Downed || escort.mindState.duty == null)
- {
- continue;
- }
- if (escort.mindState.duty.focus == parent)
- {
- if (success)
- {
- escort.AI().releasedTick = GenTicks.TicksGame;
- }
- escort.AI().duties.FinishAllDuties(CombatAI_DutyDefOf.CombatAI_Escort, parent);
- }
- }
- if (success)
- {
- Predicate validator = t =>
- {
- if (!t.HostileTo(selPawn))
- {
- ThingComp_CombatAI comp = t.GetComp_Fast();
- if (comp != null && comp.IsSapping && comp.sapperNodes.Count > 3)
- {
- ReleaseEscorts(false);
- comp.cellBefore = IntVec3.Invalid;
- comp.sapperStartTick = GenTicks.TicksGame + 800;
- comp.sapperNodes.Clear();
- }
- }
- return false;
- };
- GenClosest.RegionwiseBFSWorker(selPawn.Position, selPawn.Map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(selPawn), validator, null, 1, 4, 15, out int _);
- }
- escorts.Clear();
- }
+ ///
+ /// Release escorts pawns.
+ ///
+ public void ReleaseEscorts(bool success)
+ {
+ for (int i = 0; i < escorts.Count; i++)
+ {
+ Pawn escort = escorts[i];
+ if (escort == null || escort.Destroyed || escort.Dead || escort.Downed || escort.mindState.duty == null)
+ {
+ continue;
+ }
+ if (escort.mindState.duty.focus == parent)
+ {
+ if (success)
+ {
+ escort.AI().releasedTick = GenTicks.TicksGame;
+ }
+ escort.AI().duties.FinishAllDuties(CombatAI_DutyDefOf.CombatAI_Escort, parent);
+ }
+ }
+ if (success)
+ {
+ Predicate validator = t =>
+ {
+ if (!t.HostileTo(selPawn))
+ {
+ ThingComp_CombatAI comp = t.GetComp_Fast();
+ if (comp != null && comp.IsSapping && comp.sapperNodes.Count > 3)
+ {
+ ReleaseEscorts(false);
+ comp.cellBefore = IntVec3.Invalid;
+ comp.sapperStartTick = GenTicks.TicksGame + 800;
+ comp.sapperNodes.Clear();
+ }
+ }
+ return false;
+ };
+ Verse.GenClosest.RegionwiseBFSWorker(selPawn.Position, selPawn.Map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(selPawn), validator, null, 1, 4, 15, out int _);
+ }
+ escorts.Clear();
+ }
- ///
- /// Add enemy targeting self to Env data.
- ///
- ///
- ///
- public void Notify_BeingTargeted(Thing enemy, Verb verb)
- {
- if (enemy != null)
- {
- data.BeingTargeted(enemy);
- if (Rand.Chance(0.15f) && (selPawn.mindState.duty.Is(DutyDefOf.Defend) || selPawn.mindState.duty.Is(DutyDefOf.Escort)))
- {
- StartAggroCountdown(enemy);
- }
- }
- else
- {
- Log.Error($"{selPawn} received a null thing in Notify_BeingTargeted");
- }
- }
+ ///
+ /// Add enemy targeting self to Env data.
+ ///
+ ///
+ ///
+ public void Notify_BeingTargeted(Thing enemy, Verb verb)
+ {
+ if (enemy != null && !enemy.Destroyed)
+ {
+ data.BeingTargeted(enemy);
+ if (Rand.Chance(0.15f) && (selPawn.mindState.duty.Is(DutyDefOf.Defend) || selPawn.mindState.duty.Is(DutyDefOf.Escort)))
+ {
+ StartAggroCountdown(enemy);
+ }
+ }
+ else
+ {
+ Log.Error($"{selPawn} received a null thing in Notify_BeingTargeted");
+ }
+ }
- ///
- /// Enqueue enemy for reaction processing.
- ///
- /// Spotted enemy
- public void Notify_Enemy(AIEnvAgentInfo info)
- {
- if (!scanning)
- {
- Log.Warning($"ISMA: Notify_EnemiesVisible called while not scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
- return;
- }
- if (info.thing is Pawn enemy)
- {
- // skip if the enemy is downed
- if (enemy.Downed)
- {
- return;
- }
- // skip for children
- DevelopmentalStage stage = enemy.DevelopmentalStage;
- if (stage <= DevelopmentalStage.Child && stage != DevelopmentalStage.None)
- {
- return;
- }
- }
- if (allEnemies.TryGetValue(info.thing, out AIEnvAgentInfo store))
- {
- info = store.Combine(info);
- }
- allEnemies[info.thing] = info;
- }
+ ///
+ /// Enqueue enemy for reaction processing.
+ ///
+ /// Spotted enemy
+ public void Notify_Enemy(AIEnvAgentInfo info)
+ {
+ if (!scanning)
+ {
+ Log.Warning($"ISMA: Notify_EnemiesVisible called while not scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
+ return;
+ }
+ if (info.thing is Pawn enemy)
+ {
+ // skip if the enemy is downed
+ if (enemy.Downed)
+ {
+ return;
+ }
+ // skip for children
+ DevelopmentalStage stage = enemy.DevelopmentalStage;
+ if (stage <= DevelopmentalStage.Child && stage != DevelopmentalStage.None)
+ {
+ return;
+ }
+ }
+ if (allEnemies.TryGetValue(info.thing, out AIEnvAgentInfo store))
+ {
+ info = store.Combine(info);
+ }
+ allEnemies[info.thing] = info;
+ }
- ///
- /// Enqueue ally for reaction processing.
- ///
- /// Spotted enemy
- public void Notify_Ally(AIEnvAgentInfo info)
- {
- if (!scanning)
- {
- Log.Warning($"ISMA: Notify_EnemiesVisible called while not scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
- return;
- }
- if (info.thing is Pawn ally)
- {
- // skip if the ally is downed
- if (ally.Downed)
- {
- return;
- }
- // skip for children
- DevelopmentalStage stage = ally.DevelopmentalStage;
- if (stage <= DevelopmentalStage.Child && stage != DevelopmentalStage.None)
- {
- return;
- }
- }
- if (allAllies.TryGetValue(info.thing, out AIEnvAgentInfo store))
- {
- info = store.Combine(info);
- }
- allAllies[info.thing] = info;
- }
+ ///
+ /// Enqueue ally for reaction processing.
+ ///
+ /// Spotted enemy
+ public void Notify_Ally(AIEnvAgentInfo info)
+ {
+ if (!scanning)
+ {
+ Log.Warning($"ISMA: Notify_EnemiesVisible called while not scanning. ({allEnemies.Count}, {Thread.CurrentThread.ManagedThreadId})");
+ return;
+ }
+ if (info.thing is Pawn ally)
+ {
+ // skip if the ally is downed
+ if (ally.Downed)
+ {
+ return;
+ }
+ // skip for children
+ DevelopmentalStage stage = ally.DevelopmentalStage;
+ if (stage <= DevelopmentalStage.Child && stage != DevelopmentalStage.None)
+ {
+ return;
+ }
+ }
+ if (allAllies.TryGetValue(info.thing, out AIEnvAgentInfo store))
+ {
+ info = store.Combine(info);
+ }
+ allAllies[info.thing] = info;
+ }
- ///
- /// Called to notify a wait job started by reaction has ended. Will reduce the reaction cooldown.
- ///
- public void Notify_WaitJobEnded()
- {
- lastInterupted -= 30;
- }
+ ///
+ /// Called to notify a wait job started by reaction has ended. Will reduce the reaction cooldown.
+ ///
+ public void Notify_WaitJobEnded()
+ {
+ lastInterupted -= 30;
+ }
- ///
- /// Called when the parent sightreader group has changed.
- /// Should only be called from SighTracker/SightGrid.
- ///
- /// The new sightReader
- public void Notify_SightReaderChanged(SightTracker.SightReader reader)
- {
- sightReader = reader;
- }
+ ///
+ /// Called when the parent sightreader group has changed.
+ /// Should only be called from SighTracker/SightGrid.
+ ///
+ /// The new sightReader
+ public void Notify_SightReaderChanged(SightTracker.SightReader reader)
+ {
+ sightReader = reader;
+ }
- public override void PostExposeData()
- {
- base.PostExposeData();
- Scribe_Deep.Look(ref data, "AIAgentData.0");
- data ??= new AIAgentData();
- Scribe_Deep.Look(ref duties, "duties2");
- Scribe_Deep.Look(ref abilities, "abilities2");
- Scribe_TargetInfo.Look(ref forcedTarget, "forcedTarget");
- if (duties == null)
- {
- duties = new Pawn_CustomDutyTracker(selPawn);
- }
- if (abilities == null)
- {
- abilities = new Pawn_AbilityCaster(selPawn);
- }
- duties.pawn = selPawn;
- abilities.pawn = selPawn;
- }
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ if (Finder.Settings.Debug)
+ {
+ PersonalityTacker.PersonalityResult personality = parent.GetCombatPersonality();
+ Scribe_Deep.Look(ref personality, "personality");
+ }
+ Scribe_Deep.Look(ref data, "AIAgentData.0");
+ data ??= new AIAgentData();
+ Scribe_Deep.Look(ref duties, "duties2");
+ Scribe_Deep.Look(ref abilities, "abilities2");
+ Scribe_TargetInfo.Look(ref forcedTarget, "forcedTarget");
+ if (duties == null)
+ {
+ duties = new Pawn_CustomDutyTracker(selPawn);
+ }
+ if (abilities == null)
+ {
+ abilities = new Pawn_AbilityCaster(selPawn);
+ }
+ duties.pawn = selPawn;
+ abilities.pawn = selPawn;
+ }
- private void TryStartSapperJob()
- {
- if (sightReader.GetVisibilityToEnemies(cellBefore) > 0 || sapperNodes.Count == 0)
- {
- ReleaseEscorts(false);
- cellBefore = IntVec3.Invalid;
- sapperStartTick = -1;
- sapperNodes.Clear();
- return;
- }
- if (selPawn.Destroyed || IsDeadOrDowned || selPawn.mindState.duty == null || !(selPawn.mindState.duty.Is(DutyDefOf.AssaultColony) || selPawn.mindState.duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint) || selPawn.mindState.duty.Is(DutyDefOf.AssaultThing)))
- {
- ReleaseEscorts(false);
- return;
- }
- Map map = selPawn.Map;
- Thing blocker = sapperNodes[0].GetEdifice(map);
- if (blocker != null)
- {
- Job job = DigUtility.PassBlockerJob(selPawn, blocker, cellBefore, true, true);
- if (job != null)
- {
- job.playerForced = true;
- job.expiryInterval = 3600;
- job.maxNumMeleeAttacks = 300;
- selPawn.jobs.StopAll();
- selPawn.jobs.StartJob(job, JobCondition.InterruptForced);
- if (findEscorts && Rand.Chance(1 - Maths.Min(escorts.Count / 12, 0.85f)))
- {
- int count = escorts.Count;
- int countTarget = Rand.Int % 4 + 12 + Maths.Min(sapperNodes.Count, 10);
- Faction faction = selPawn.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 != CombatAI_DutyDefOf.CombatAI_Escort
- && (sightReader == null || sightReader.GetAbsVisibilityToEnemies(ally.Position) == 0)
- && ally.skills?.GetSkill(SkillDefOf.Mining).Level < 10)
- {
- ThingComp_CombatAI comp = ally.AI();
- if (comp?.duties != null && comp.duties?.Any(CombatAI_DutyDefOf.CombatAI_Escort) == false && !comp.IsSapping && GenTicks.TicksGame - comp.releasedTick > 600)
- {
- Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.Escort(selPawn, 20, 100, 600 + Mathf.CeilToInt(12 * selPawn.Position.DistanceTo(cellBefore)) + 540 * sapperNodes.Count + Rand.Int % 600);
- if (ally.TryStartCustomDuty(custom))
- {
- escorts.Add(ally);
- }
- if (comp.duties.curCustomDuty?.duty != duties.curCustomDuty?.duty)
- {
- count += 3;
- }
- else
- {
- count++;
- }
- }
- return count == countTarget;
- }
- return false;
- };
- GenClosest.RegionwiseBFSWorker(selPawn.Position, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(selPawn), validator, null, 1, 10, 40, out int _);
- }
- }
- }
- }
+ private void TryStartSapperJob()
+ {
+ bool failed = sapperNodes.Count == 0 || (sightReader.GetVisibilityToFriendlies(cellAhead) > 0 && GenTicks.TicksGame - sapperStartTick > 1000);
+ if (failed)
+ {
+ ReleaseEscorts(false);
+ cellBefore = IntVec3.Invalid;
+ sapperStartTick = -1;
+ sapperNodes.Clear();
+ return;
+ }
+ if (IsDeadOrDowned || !(selPawn.mindState.duty.Is(DutyDefOf.AssaultColony) || selPawn.mindState.duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint) || selPawn.mindState.duty.Is(DutyDefOf.AssaultThing)) || selPawn.CurJob.Is(JobDefOf.Wait_Combat) || selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) || selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Retreat) || selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Duck))
+ {
+ ReleaseEscorts(false);
+ cellBefore = IntVec3.Invalid;
+ sapperStartTick = -1;
+ sapperNodes.Clear();
+ return;
+ }
+ Map map = selPawn.Map;
+ Thing blocker = sapperNodes[0].GetEdifice(map);
+ if (blocker != null)
+ {
+ Job job = null;
+ float miningSkill = selPawn.GetSkillLevelSafe(SkillDefOf.Mining, 0);
+ PersonalityTacker.PersonalityResult personality = parent.GetCombatPersonality();
+ if (findEscorts && Rand.Chance(1 - Maths.Min(escorts.Count / (Maths.Max(miningSkill, 7) * personality.sapping), 0.85f)))
+ {
+ int count = escorts.Count;
+ int countTarget = 7 + Mathf.FloorToInt(Maths.Max(miningSkill, 7) * personality.sapping) + Maths.Min(sapperNodes.Count, 10);
+ Faction faction = selPawn.Faction;
+ Predicate validator = t =>
+ {
+ if (count < countTarget && t.Faction == faction && t is Pawn ally && !ally.Destroyed && !ally.CurJobDef.Is(JobDefOf.Mine) && !ally.IsColonist && ally.def != CombatAI_ThingDefOf.Mech_Tunneler && ally.mindState?.duty?.def != CombatAI_DutyDefOf.CombatAI_Escort
+ && (sightReader == null || sightReader.GetAbsVisibilityToEnemies(ally.Position) == 0)
+ && ally.GetSkillLevelSafe(SkillDefOf.Mining, 0) < miningSkill)
+ {
+ ThingComp_CombatAI comp = ally.AI();
+ if (comp?.duties != null && comp.duties?.Any(CombatAI_DutyDefOf.CombatAI_Escort) == false && !comp.IsSapping && GenTicks.TicksGame - comp.releasedTick > 600)
+ {
+ Pawn_CustomDutyTracker.CustomPawnDuty custom = CustomDutyUtility.Escort(selPawn, 20, 100, 600 + Mathf.CeilToInt(12 * selPawn.Position.DistanceTo(cellBefore)) + 540 * sapperNodes.Count + Rand.Int % 600);
+ if (ally.TryStartCustomDuty(custom))
+ {
+ escorts.Add(ally);
+ }
+ if (comp.duties.curCustomDuty?.duty != duties.curCustomDuty?.duty)
+ {
+ count += 4;
+ }
+ else
+ {
+ count++;
+ }
+ }
+ return count == countTarget;
+ }
+ return false;
+ };
+ Verse.GenClosest.RegionwiseBFSWorker(selPawn.Position, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(selPawn), validator, null, 1, 10, 40, out int _);
+ }
+ float visibility = sightReader.GetAbsVisibilityToEnemies(cellBefore);
+ if (!Mod_CE.active && (visibility > 0 || miningSkill < 9) && (escorts.Count >= 2 || miningSkill == 0))
+ {
+ job = TryStartRemoteSapper(selPawn, blocker, sapperNodes[0], cellBefore);
+ }
+ if (job == null)
+ {
+ // if no remote sapping job has been started, start a melee sapping job. If that's not possible release held escorts
+ if (visibility <= 1e-2f && miningSkill > 0)
+ {
+ job = DigUtility.PassBlockerJob(selPawn, blocker, cellBefore, true, true);
+ job.playerForced = true;
+ job.expiryInterval = 3600;
+ job.maxNumMeleeAttacks = 300;
+ selPawn.jobs.StartJob(job, JobCondition.InterruptForced);
+ }
+ else
+ {
+ ReleaseEscorts(false);
+ cellBefore = IntVec3.Invalid;
+ sapperStartTick = -1;
+ sapperNodes.Clear();
+ }
+ }
+ if (!Mod_CE.active && job != null && job.def == JobDefOf.UseVerbOnThing)
+ {
+ TryStartSapperJobForEscort(blocker);
+ }
+ }
+ }
- private static int GetEnemyAttackTargetId(Thing enemy)
- {
- if (!TKVCache.TryGet(enemy.thingIDNumber, out int attackTarget, 15) || attackTarget == -1)
- {
- Verb enemyVerb = enemy.TryGetAttackVerb();
- if (enemyVerb == null || enemyVerb is Verb_CastPsycast || enemyVerb is Verb_CastAbility)
- {
- attackTarget = -1;
- }
- else if (!enemyVerb.IsMeleeAttack && enemyVerb.currentTarget is { IsValid: true, HasThing: true } && (enemyVerb.WarmingUp && enemyVerb.WarmupTicksLeft < 45 || enemyVerb.Bursting))
- {
- attackTarget = enemyVerb.currentTarget.Thing.thingIDNumber;
- }
- else if (enemyVerb.IsMeleeAttack && enemy is Pawn enemyPawn && enemyPawn.CurJobDef.Is(JobDefOf.AttackMelee) && enemyPawn.CurJob.targetA.IsValid)
- {
- attackTarget = enemyPawn.CurJob.targetA.Thing.thingIDNumber;
- }
- else
- {
- attackTarget = -1;
- }
- TKVCache.Put(enemy.thingIDNumber, attackTarget);
- }
- return attackTarget;
- }
+ ///
+ /// Tries to start ranged sapping jobs for escorting pawns
+ ///
+ /// The path blocker
+ private void TryStartSapperJobForEscort(Thing blocker)
+ {
+ foreach (Pawn ally in escorts)
+ {
+ if (!ally.Destroyed && ally.Spawned && !ally.Downed && !ally.Dead && !ally.IsUsingVerb() && sightReader.GetAbsVisibilityToEnemies(ally.Position) == 0)
+ {
+ TryStartRemoteSapper(ally, blocker, sapperNodes[0], cellBefore);
+ }
+ }
+ }
+
+ ///
+ /// Starts remote sapping for a pawn given a blocker
+ ///
+ ///
+ ///
+ private static Job TryStartRemoteSapper(Pawn pawn, Thing blocker, IntVec3 cellBlocker, IntVec3 cellBefore)
+ {
+ Verb verb = pawn.TryGetAttackVerb();
+ if (verb.IsRangedSappingCompatible() && (verb.verbProps.burstShotCount > 1 || !pawn.RaceProps.IsMechanoid))
+ {
+ CastPositionRequest request = new CastPositionRequest();
+ request.verb = verb;
+ request.caster = pawn;
+ request.target = blocker;
+ request.maxRangeFromTarget = 10;
+ Vector3 dir = (cellBefore - cellBlocker).ToVector3();
+ request.validator = cell =>
+ {
+ return Mathf.Abs(Vector3.Angle((cell - cellBlocker).ToVector3(), dir)) <= 45f && cellBefore.DistanceToSquared(cell) >= 9;
+ };
+ try
+ {
+ if (CastPositionFinder.TryFindCastPosition(request, out IntVec3 loc))
+ {
+ Job job = JobMaker.MakeJob(JobDefOf.UseVerbOnThing);
+ job.targetA = blocker;
+ job.targetB = loc;
+ job.verbToUse = verb;
+ job.preventFriendlyFire = true;
+ job.expiryInterval = JobGiver_AIFightEnemy.ExpiryInterval_ShooterSucceeded.RandomInRange;
+ pawn.jobs.StartJob(job, JobCondition.InterruptForced);
+ for (int i = 0; i < 4; i++)
+ {
+ job = JobMaker.MakeJob(JobDefOf.UseVerbOnThing);
+ job.targetA = blocker;
+ job.targetB = loc;
+ job.verbToUse = verb;
+ job.preventFriendlyFire = true;
+ job.expiryInterval = JobGiver_AIFightEnemy.ExpiryInterval_ShooterSucceeded.RandomInRange;
+ pawn.jobs.jobQueue.EnqueueFirst(job);
+ }
+ return job;
+ }
+ }
+ catch (Exception er)
+ {
+ Log.Error($"1. {er}");
+ }
+ }
+ return null;
+ }
- #region TimeStamps
+ #region TimeStamps
- ///
- /// When the last injury occured/damage.
- ///
- private int lastTookDamage;
- ///
- /// When the last scan occured. SightGrid is responisble for these scan cycles.
- ///
- private int lastScanned;
- ///
- /// When did this comp last interupt the parent pawn. IE: reacted, retreated, etc.
- ///
- private int lastInterupted;
- ///
- /// When the pawn was last order to retreat by CAI.
- ///
- private int lastRetreated;
- ///
- /// Last tick any enemies where reported in a scan.
- ///
- private int lastSawEnemies;
- ///
- /// 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;
+ ///
+ /// When the last injury occured/damage.
+ ///
+ private int lastTookDamage;
+ ///
+ /// When the last scan occured. SightGrid is responisble for these scan cycles.
+ ///
+ private int lastScanned;
+ ///
+ /// When did this comp last interupt the parent pawn. IE: reacted, retreated, etc.
+ ///
+ private int lastInterupted;
+ ///
+ /// When the pawn was last order to retreat by CAI.
+ ///
+ private int lastRetreated;
+ ///
+ /// Last tick any enemies where reported in a scan.
+ ///
+ private int lastSawEnemies;
+ ///
+ /// 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
+ #endregion
#if DEBUG_REACTION
/*
@@ -1512,7 +1834,7 @@ public override void DrawGUIOverlay()
.Select(t => t as Pawn)
.Where(p => !p.Dead && !p.Downed && PawnPathUtility.GetMovingShiftedPosition(p, 60).DistanceToSquared(shiftedPos) < sightRangeSqr && verb.CanHitTargetFrom(shiftedPos, PawnPathUtility.GetMovingShiftedPosition(p, 60)) && p.HostileTo(pawn))
.ToList();
- GUIUtility.ExecuteSafeGUIAction(() =>
+ Gui.GUIUtility.ExecuteSafeGUIAction(() =>
{
Vector2 drawPosUI = drawPos.MapToUIPosition();
Text.Font = GameFont.Tiny;
@@ -1596,7 +1918,7 @@ public override void DrawGUIOverlay()
}
foreach (Thing enemy in data.BeingTargetedBy)
{
- if (enemy != null && enemy.TryGetAttackVerb() is Verb enemyVerb && GetEnemyAttackTargetId(enemy) == selPawn.thingIDNumber)
+ if (enemy != null && enemy.TryGetAttackVerb() is Verb enemyVerb && ThreatUtility.GetEnemyAttackTargetId(enemy) == selPawn.thingIDNumber)
{
Vector2 b = enemy.DrawPos.MapToUIPosition();
Ray2D ray = new Ray2D(a, b - a);
@@ -1628,5 +1950,5 @@ public override void DrawGUIOverlay()
private readonly List _path = new List();
private readonly List _colors = new List();
#endif
- }
+ }
}
diff --git a/Source/Rule56/CoverPositionFinder.cs b/Source/Rule56/CoverPositionFinder.cs
index 4ee31b4..e56796e 100644
--- a/Source/Rule56/CoverPositionFinder.cs
+++ b/Source/Rule56/CoverPositionFinder.cs
@@ -10,6 +10,9 @@ namespace CombatAI
[StaticConstructorOnStartup]
public static class CoverPositionFinder
{
+ private static int checks = 0;
+ private static int checksSkipped = 0;
+// private static int checksFault = 0;
private static readonly CellMetrics metric_cover = new CellMetrics();
private static readonly CellMetrics metric_coverPath = new CellMetrics();
private static readonly CellMetrics metric_retreat = new CellMetrics();
@@ -28,19 +31,17 @@ static CoverPositionFinder()
metric_cover.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell), 0.25f);
metric_cover.Add("threat", (reader, cell) => reader.GetThreat(cell), 0.25f);
- metric_coverPath.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell));
- metric_coverPath.Add("dir", (reader, cell) => Maths.Sqrt_Fast(Mathf.CeilToInt(reader.GetEnemyDirection(cell).SqrMagnitude()), 3), -1);
+ metric_coverPath.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell), 4);
metric_coverPath.Add("traverse", (map, cell) => (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (cell.GetTerrain(map)?.pathCost / 22f ?? 0), 1, false);
metric_coverPath.Add("visibilityFriendlies", (reader, cell) => reader.GetVisibilityToFriendlies(cell), -0.05f);
// retreating
- metric_retreat.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell), 0.25f);
+ metric_retreat.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell));
metric_retreat.Add("threat", (reader, cell) => reader.GetThreat(cell), 0.25f);
metric_retreat.Add("visibilityFriendlies", (reader, cell) => reader.GetVisibilityToFriendlies(cell), -0.10f);
- metric_retreatPath.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell));
- metric_retreatPath.Add("dir", (reader, cell) => Maths.Sqrt_Fast(Mathf.CeilToInt(reader.GetEnemyDirection(cell).SqrMagnitude()), 3), -1);
+ metric_retreatPath.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell), 4);
metric_retreatPath.Add("traverse", (map, cell) => (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (cell.GetTerrain(map)?.pathCost / 22f ?? 0), 1, false);
metric_retreatPath.Add("visibilityFriendlies", (reader, cell) => reader.GetVisibilityToFriendlies(cell), -0.05f);
metric_retreatPath.Add("danger", (reader, cell) => reader.GetDanger(cell), 0.05f);
@@ -50,14 +51,16 @@ static CoverPositionFinder()
metric_duck.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell), 0.25f);
metric_duck.Add("threat", (reader, cell) => reader.GetThreat(cell), 0.25f);
- metric_duckPath.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell));
- metric_duckPath.Add("dir", (reader, cell) => Maths.Sqrt_Fast(Mathf.CeilToInt(reader.GetEnemyDirection(cell).SqrMagnitude()), 3), -1);
+ metric_duckPath.Add("visibilityEnemies", (reader, cell) => reader.GetVisibilityToEnemies(cell), 4);
metric_duckPath.Add("traverse", (map, cell) => (cell.GetEdifice(map)?.def.pathCost / 22f ?? 0) + (cell.GetTerrain(map)?.pathCost / 22f ?? 0), 1, false);
metric_duckPath.Add("visibilityFriendlies", (reader, cell) => reader.GetVisibilityToFriendlies(cell), -0.05f);
}
public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec3 coverCell, Action callback = null)
{
+ checks = 0;
+// checksFault = 0;
+ checksSkipped = 0;
request.caster.TryGetSightReader(out SightReader sightReader);
request.caster.TryGetAvoidanceReader(out AvoidanceReader avoidanceReader);
if (sightReader == null || avoidanceReader == null)
@@ -109,7 +112,8 @@ public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec
flooder.Flood(request.locus,
node =>
{
- if (request.verb != null && !request.verb.CanHitTargetFrom(node.cell, enemyLoc) || maxDistSqr < request.locus.DistanceToSquared(node.cell) || !map.reservationManager.CanReserve(caster, node.cell))
+
+ if (request.verb != null && (sightReader.GetNearestEnemy(node.cell).DistanceToSquared(enemyLoc) > Maths.Sqr(effectiveRange) || !request.verb.CanHitTargetFrom(node.cell, enemyLoc)) || maxDistSqr < request.locus.DistanceToSquared(node.cell) || !map.reservationManager.CanReserve(caster, node.cell))
{
return;
}
@@ -173,11 +177,15 @@ public static bool TryFindCoverPosition(CoverPositionRequest request, out IntVec
(int)Maths.Min(request.maxRangeFromLocus, 30)
);
coverCell = bestCell;
+// Log.Message($"{checksSkipped}/{checks + checksSkipped}:{checksFault}");
return bestCell.IsValid;
}
public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntVec3 coverCell, Action callback = null)
{
+ checks = 0;
+// checksFault = 0;
+ checksSkipped = 0;
request.caster.TryGetSightReader(out SightReader sightReader);
request.caster.TryGetAvoidanceReader(out AvoidanceReader avoidanceReader);
if (sightReader == null || avoidanceReader == null)
@@ -243,10 +251,6 @@ public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntV
{
c += enemiesWarmingUp / 10f;
}
- if (rootDutyDestDist > 0)
- {
- c += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 5) - rootDutyDestDist) * 0.25f, -0.5f, 0.5f);
- }
float d = node.cell.DistanceToSquared(enemyLoc);
if (bestCellScore - c >= 0.05f)
{
@@ -300,11 +304,15 @@ public static bool TryFindRetreatPosition(CoverPositionRequest request, out IntV
bestCell = cell;
}
coverCell = bestCell;
+// Log.Message($"{checksSkipped}/{checks + checksSkipped}:{checksFault}");
return bestCell.IsValid;
}
public static bool TryFindDuckPosition(CoverPositionRequest request, out IntVec3 coverCell, Action callback = null)
{
+ checks = 0;
+// checksFault = 0;
+ checksSkipped = 0;
request.caster.TryGetSightReader(out SightReader sightReader);
request.caster.TryGetAvoidanceReader(out AvoidanceReader avoidanceReader);
if (sightReader == null || avoidanceReader == null)
@@ -381,10 +389,6 @@ public static bool TryFindDuckPosition(CoverPositionRequest request, out IntVec3
{
c += enemiesWarmingUp / 5f;
}
- if (rootDutyDestDist > 0)
- {
- c += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 5) - rootDutyDestDist) * 0.25f, -1f, 1f);
- }
if (bestCellScore - c >= 0.05f)
{
if (visibleTo <= bestVisibleTo)
@@ -410,12 +414,50 @@ public static bool TryFindDuckPosition(CoverPositionRequest request, out IntVec3
(int)Maths.Min(request.maxRangeFromLocus, 30)
);
coverCell = bestCell;
+// Log.Message($"{checksSkipped}/{checks + checksSkipped}:{checksFault}");
return bestCell.IsValid && bestVisibleTo == 0;
}
- private static Func GetCanHitTargetFunc(Thing thing, Verb enemyVerb)
+ private static Func GetCanHitTargetFunc(Thing thing, Verb verb)
{
- return cell => enemyVerb.CanHitTarget(cell);
+ IntVec3 position = thing.Position;
+ if (thing.Map.Sight().TryGetReader(thing, out SightReader reader))
+ {
+ ulong thingFlags = thing.GetThingFlags();
+ float minDistSqr = Maths.Min(reader.GetNearestEnemy(position).DistanceToSquared(position), Maths.Sqr(verb.EffectiveRange + 5));
+ return cell =>
+ {
+ if (cell.DistanceToSquared(position) < minDistSqr && (reader.GetDynamicFriendlyFlags(cell) & thingFlags) != 0)
+ {
+ checks++;
+ return verb.CanHitTarget(cell);
+ }
+ checksSkipped++;
+// if (verb.CanHitTarget(cell))
+// {
+// checksFault++;
+// }
+ return false;
+ };
+ }
+ else
+ {
+ float minDistSqr = Maths.Sqr(verb.EffectiveRange);
+ return cell =>
+ {
+ if (cell.DistanceToSquared(position) < minDistSqr)
+ {
+ checks++;
+ return verb.CanHitTarget(cell);
+ }
+ checksSkipped++;
+// if (verb.CanHitTarget(cell))
+// {
+// checksFault++;
+// }
+ return false;
+ };
+ }
}
}
}
diff --git a/Source/Rule56/DamageReport.cs b/Source/Rule56/DamageReport.cs
index 1c8763b..f21f47b 100644
--- a/Source/Rule56/DamageReport.cs
+++ b/Source/Rule56/DamageReport.cs
@@ -2,350 +2,323 @@
using RimWorld;
using UnityEngine;
using Verse;
-namespace CombatAI
+
+namespace CombatAI;
+
+public struct DamageReport
{
- public struct DamageReport
- {
- private static readonly Dictionary> _maneuvers = new Dictionary>(128);
-
- private int _createdAt;
- private bool _finalized;
-
- ///
- /// Report thing.
- ///
- public Thing thing;
- ///
- /// Ranged blunt damage potential per second.
- ///
- public float rangedBlunt;
- ///
- /// Ranged sharp damage potential per second.
- ///
- public float rangedSharp;
- ///
- /// Ranged blunt armor penetration potential.
- ///
- public float rangedBluntAp;
- ///
- /// Ranged sharp armor penetration potential.
- ///
- public float rangedSharpAp;
- ///
- /// Whether report thing can melee.
- ///
- public bool canMelee;
- ///
- /// Melee blunt damage potential per second.
- ///
- public float meleeBlunt;
- ///
- /// Melee sharp damage potential per second.
- ///
- public float meleeSharp;
- ///
- /// Melee blunt armor penetration potential.
- ///
- public float meleeBluntAp;
- ///
- /// Melee sharp armor penetration potential.
- ///
- public float meleeSharpAp;
- ///
- /// Report meta flags.
- ///
- public MetaCombatAttribute attributes;
- ///
- /// Whether the primary damage model is ranged.
- ///
- public bool primaryIsRanged;
- ///
- /// The combined and the adjusted sharp value.
- ///
- public float adjustedSharp;
- ///
- /// The combined and the adjusted blunr value.
- ///
- public float adjustedBlunt;
- ///
- /// Primary verb properties.
- ///
- public VerbProperties primaryVerbProps;
- ///
- /// Damage def for the primary attack verb.
- ///
- public DamageDef primaryVerbDamageDef;
-
- public bool IsValid
- {
- get => _finalized && GenTicks.TicksGame - _createdAt < 1800;
- }
-
- public float GetAdjustedDamage(DamageArmorCategoryDef def)
- {
- if (def == null)
- {
- return adjustedSharp * 0.6f + adjustedBlunt * 0.4f;
- }
- if (def == DamageArmorCategoryDefOf.Sharp)
- {
- return adjustedSharp;
- }
- return adjustedBlunt;
- }
-
- public void Finalize(float rangedMul, float meleeMul)
- {
- float mainSharp;
- float mainBlunt;
- float weakSharp;
- float weakBlunt;
- if (primaryIsRanged)
- {
- mainSharp = Adjust(rangedSharp, rangedSharpAp) * rangedMul;
- mainBlunt = Adjust(rangedBlunt, rangedBluntAp) * rangedMul;
- weakSharp = Adjust(meleeSharp, meleeSharpAp) * meleeMul;
- weakBlunt = Adjust(meleeBlunt, meleeBluntAp) * meleeMul;
- }
- else
- {
- mainSharp = Adjust(meleeSharp, meleeSharpAp) * meleeMul;
- mainBlunt = Adjust(meleeBlunt, meleeBluntAp) * meleeMul;
- weakSharp = Adjust(rangedSharp, rangedSharpAp) * rangedMul;
- weakBlunt = Adjust(rangedBlunt, rangedBluntAp) * rangedMul;
- }
- adjustedSharp = mainSharp * 0.95f + weakSharp * 0.05f;
- adjustedBlunt = mainBlunt * 0.95f + weakBlunt * 0.05f;
- _createdAt = GenTicks.TicksGame;
- _finalized = true;
- }
-
- public void AddVerb(Verb verb)
- {
- if (verb != null && verb.Available())
- {
- if (verb.IsEMP())
- {
- attributes |= MetaCombatAttribute.Emp;
- }
- if (!verb.IsMeleeAttack)
- {
- ProjectileProperties projectile = verb.GetProjectile()?.projectile ?? null;
- if (projectile != null)
- {
- if (projectile.explosionRadius > 0)
- {
- attributes |= MetaCombatAttribute.AOE;
- if (projectile.explosionRadius > 3.5f)
- {
- attributes |= MetaCombatAttribute.AOELarge;
- }
- }
- float burstShotCount = Mathf.Clamp(verb.verbProps.burstShotCount * 0.66f, 0.75f, 3f);
- if (projectile.damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp)
- {
- rangedSharp = Maths.Max(projectile.damageAmountBase * burstShotCount, rangedSharp);
- ;
- rangedSharpAp = Maths.Max(GetArmorPenetration(projectile), rangedSharpAp);
- }
- else
- {
- rangedBlunt = Maths.Max(projectile.damageAmountBase * burstShotCount, rangedBlunt);
- rangedBluntAp = Maths.Max(GetArmorPenetration(projectile), rangedBluntAp);
- }
- }
- }
- else
- {
- AddTool(verb.tool);
- }
- }
- }
-
- public void AddTool(Tool tool)
- {
- if (tool != null && tool.capacities != null)
- {
- for (int i = 0; i < tool.capacities.Count; i++)
- {
- ToolCapacityDef def = tool.capacities[i];
- if (!_maneuvers.TryGetValue(def, out List maneuvers))
- {
- _maneuvers[def] = maneuvers = new List(def.Maneuvers);
- }
- for (int j = 0; j < maneuvers.Count; j++)
- {
- ManeuverDef maneuver = maneuvers[j];
- if (maneuver.verb != null)
- {
- if (maneuver.verb.meleeDamageDef == DamageDefOf.Blunt)
- {
- meleeBlunt = (meleeBlunt + tool.power) / 2f;
- meleeBluntAp = (meleeBluntAp + Maths.Max(maneuver.verb.meleeArmorPenetrationBase, tool.armorPenetration, 0)) / 2f;
- }
- else
- {
- meleeSharp = (meleeSharp + tool.power) / 2f;
- meleeSharpAp = (meleeSharpAp + Maths.Max(maneuver.verb.meleeArmorPenetrationBase, tool.armorPenetration, 0)) / 2f;
- }
- }
- }
- }
- }
- }
-
- public float SimulatedDamage(ArmorReport armorReport, int iterations = 5)
- {
- float damage = 0f;
+ private static readonly Dictionary> _maneuvers = new(128);
+
+ private int _createdAt;
+ private bool _finalized;
+
+ ///
+ /// Report thing.
+ ///
+ public Thing thing;
+
+ ///
+ /// Ranged blunt damage potential per second.
+ ///
+ public float rangedBlunt;
+
+ ///
+ /// Ranged sharp damage potential per second.
+ ///
+ public float rangedSharp;
+
+ ///
+ /// Ranged blunt armor penetration potential.
+ ///
+ public float rangedBluntAp;
+
+ ///
+ /// Ranged sharp armor penetration potential.
+ ///
+ public float rangedSharpAp;
+
+ ///
+ /// Whether report thing can melee.
+ ///
+ public bool canMelee;
+
+ ///
+ /// Melee blunt damage potential per second.
+ ///
+ public float meleeBlunt;
+
+ ///
+ /// Melee sharp damage potential per second.
+ ///
+ public float meleeSharp;
+
+ ///
+ /// Melee blunt armor penetration potential.
+ ///
+ public float meleeBluntAp;
+
+ ///
+ /// Melee sharp armor penetration potential.
+ ///
+ public float meleeSharpAp;
+
+ ///
+ /// Report meta flags.
+ ///
+ public MetaCombatAttribute attributes;
+
+ ///
+ /// Whether the primary damage model is ranged.
+ ///
+ public bool primaryIsRanged;
+
+ ///
+ /// The combined and the adjusted sharp value.
+ ///
+ public float adjustedSharp;
+
+ ///
+ /// The combined and the adjusted blunr value.
+ ///
+ public float adjustedBlunt;
+
+ ///
+ /// Primary verb properties.
+ ///
+ public VerbProperties primaryVerbProps;
+
+ ///
+ /// Damage def for the primary attack verb.
+ ///
+ public DamageDef primaryVerbDamageDef;
+
+ public bool IsValid => _finalized && GenTicks.TicksGame - _createdAt < 60000;
+
+ public float GetAdjustedDamage(DamageArmorCategoryDef def)
+ {
+ if (def == null) return adjustedSharp * 0.6f + adjustedBlunt * 0.4f;
+ if (def == DamageArmorCategoryDefOf.Sharp) return adjustedSharp;
+ return adjustedBlunt;
+ }
+
+ public void Finalize(float rangedMul, float meleeMul)
+ {
+ float mainSharp;
+ float mainBlunt;
+ float weakSharp;
+ float weakBlunt;
+ if (primaryIsRanged)
+ {
+ mainSharp = Adjust(rangedSharp, rangedSharpAp) * rangedMul;
+ mainBlunt = Adjust(rangedBlunt, rangedBluntAp) * rangedMul;
+ weakSharp = Adjust(meleeSharp, meleeSharpAp) * meleeMul;
+ weakBlunt = Adjust(meleeBlunt, meleeBluntAp) * meleeMul;
+ }
+ else
+ {
+ mainSharp = Adjust(meleeSharp, meleeSharpAp) * meleeMul;
+ mainBlunt = Adjust(meleeBlunt, meleeBluntAp) * meleeMul;
+ weakSharp = Adjust(rangedSharp, rangedSharpAp) * rangedMul;
+ weakBlunt = Adjust(rangedBlunt, rangedBluntAp) * rangedMul;
+ }
+
+ adjustedSharp = mainSharp * 0.95f + weakSharp * 0.05f;
+ adjustedBlunt = mainBlunt * 0.95f + weakBlunt * 0.05f;
+ _createdAt = GenTicks.TicksGame;
+ _finalized = true;
+ }
+
+ public void AddVerb(Verb verb)
+ {
+ if (verb != null && verb.Available())
+ {
+ if (verb.IsEMP()) attributes |= MetaCombatAttribute.Emp;
+ if (!verb.IsMeleeAttack)
+ {
+ var projectile = verb.GetProjectile()?.projectile ?? null;
+ if (projectile != null)
+ {
+ if (projectile.explosionRadius > 0)
+ {
+ attributes |= MetaCombatAttribute.AOE;
+ if (projectile.explosionRadius > 3.5f) attributes |= MetaCombatAttribute.AOELarge;
+ }
+
+ var burstShotCount = Mathf.Clamp(verb.verbProps.burstShotCount * 0.66f, 0.75f, 3f);
+ if (projectile.damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp)
+ {
+ rangedSharp = Maths.Max(projectile.damageAmountBase * burstShotCount, rangedSharp);
+ ;
+ rangedSharpAp = Maths.Max(GetArmorPenetration(projectile), rangedSharpAp);
+ }
+ else
+ {
+ rangedBlunt = Maths.Max(projectile.damageAmountBase * burstShotCount, rangedBlunt);
+ rangedBluntAp = Maths.Max(GetArmorPenetration(projectile), rangedBluntAp);
+ }
+ }
+ }
+ else
+ {
+ AddTool(verb.tool);
+ }
+ }
+ }
+
+ public void AddTool(Tool tool)
+ {
+ if (tool != null && tool.capacities != null)
+ for (var i = 0; i < tool.capacities.Count; i++)
+ {
+ var def = tool.capacities[i];
+ if (!_maneuvers.TryGetValue(def, out var maneuvers))
+ _maneuvers[def] = maneuvers = new List(def.Maneuvers);
+ for (var j = 0; j < maneuvers.Count; j++)
+ {
+ var maneuver = maneuvers[j];
+ if (maneuver.verb != null)
+ {
+ if (maneuver.verb.meleeDamageDef == DamageDefOf.Blunt)
+ {
+ meleeBlunt = (meleeBlunt + tool.power) / 2f;
+ meleeBluntAp = (meleeBluntAp + Maths.Max(maneuver.verb.meleeArmorPenetrationBase,
+ tool.armorPenetration, 0)) / 2f;
+ }
+ else
+ {
+ meleeSharp = (meleeSharp + tool.power) / 2f;
+ meleeSharpAp = (meleeSharpAp + Maths.Max(maneuver.verb.meleeArmorPenetrationBase,
+ tool.armorPenetration, 0)) / 2f;
+ }
+ }
+ }
+ }
+ }
+
+ public float SimulatedDamage(ArmorReport armorReport, int iterations = 5)
+ {
+ var damage = 0f;
// bool hasWorkingShield = includeShields && armorReport.shield?.PawnOwner != null;
- for (int i = 0; i < iterations; i++)
- {
- damage += SimulatedDamage_Internal(armorReport, (i + 1f) / (iterations + 2f));
-// if (!hasWorkingShield || armorReport.shield.Energy - damage * armorReport.shield.Props.energyLossPerDamage <= 0)
-// {
-// damage += temp;
-// }
- }
- return damage / iterations;
- }
-
- private float SimulatedDamage_Internal(ArmorReport armorReport, float roll)
- {
- DamageDef damageDef = primaryVerbDamageDef ?? (primaryIsRanged ? DamageDefOf.Bullet : DamageDefOf.Bullet);
- DamageArmorCategoryDef category = damageDef?.armorCategory ?? DamageArmorCategoryDefOf.Sharp;
- float damage = 0f;
- float armorPen = 0f;
- if (category == DamageArmorCategoryDefOf.Sharp)
- {
- Sharp(out damage, out armorPen);
- }
- else
- {
- Blunt(out damage, out armorPen);
- }
- ApplyDamage(ref damage, armorPen, armorReport.GetArmor(damageDef), ref damageDef, roll);
- if (damage > 0.01f)
- {
- ApplyDamage(ref damage, armorPen, armorReport.GetBodyArmor(damageDef), ref damageDef, roll);
- }
- return damage;
- }
-
- private void ApplyDamage(ref float damageAmount, float armorPenetration, float armorRating, ref DamageDef damageDef, float roll)
- {
- float pen = Mathf.Max(armorRating - armorPenetration, 0f);
- float blocked = pen * 0.5f;
- float reduced = pen;
- if (roll < blocked)
- {
- // stopped.
- damageAmount = 0f;
- }
- else if (roll < reduced)
- {
- // reduced enough to become blunt damage.
- damageAmount = damageAmount / 2f;
- if (damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp)
- {
- damageDef = DamageDefOf.Blunt;
- }
- }
- }
-
- private static float Adjust(float dmg, float ap)
- {
- if (Mod_CE.active)
- {
- return dmg / 18f + ap;
- }
- if (ap != 0)
- {
- return dmg / 12f * ap;
- }
- return dmg / 18f;
- }
-
- private void Sharp(out float damage, out float ap)
- {
- if (primaryIsRanged)
- {
- damage = rangedSharp;
- ap = rangedSharpAp;
- }
- else
- {
- damage = meleeSharp;
- ap = meleeSharpAp;
- }
- }
-
- private void Blunt(out float damage, out float ap)
- {
- if (primaryIsRanged)
- {
- damage = rangedBlunt;
- ap = rangedBluntAp;
- }
- else
- {
- damage = meleeBlunt;
- ap = meleeBluntAp;
- }
- }
-
- private float AdjustedSharp()
- {
- float damage;
- float ap;
- if (primaryIsRanged)
- {
- damage = rangedSharp;
- ap = rangedSharpAp;
- }
- else
- {
- damage = meleeSharp;
- ap = meleeSharpAp;
- }
- if (Mod_CE.active)
- {
- return damage / 12f + ap;
- }
- if (ap != 0)
- {
- return damage / 12f * ap;
- }
- return damage / 18f;
- }
-
- private float AdjustedBlunt()
- {
- float damage;
- float ap;
- if (primaryIsRanged)
- {
- damage = rangedBlunt;
- ap = rangedBluntAp;
- }
- else
- {
- damage = meleeBlunt;
- ap = meleeBluntAp;
- }
- if (Mod_CE.active)
- {
- return damage / 12f + ap;
- }
- if (ap != 0)
- {
- return damage / 12f * ap;
- }
- return damage / 18f;
- }
-
- private float GetArmorPenetration(ProjectileProperties projectile)
- {
- return Mod_CE.GetProjectileArmorPenetration(projectile);
- }
- }
-}
+ for (var i = 0; i < iterations; i++)
+ damage += SimulatedDamage_Internal(armorReport, (i + 1f) / (iterations + 2f));
+ // if (!hasWorkingShield || armorReport.shield.Energy - damage * armorReport.shield.Props.energyLossPerDamage <= 0)
+ // {
+ // damage += temp;
+ // }
+ return damage / iterations;
+ }
+
+ private float SimulatedDamage_Internal(ArmorReport armorReport, float roll)
+ {
+ var damageDef = primaryVerbDamageDef ?? (primaryIsRanged ? DamageDefOf.Bullet : DamageDefOf.Bullet);
+ var category = damageDef?.armorCategory ?? DamageArmorCategoryDefOf.Sharp;
+ var damage = 0f;
+ var armorPen = 0f;
+ if (category == DamageArmorCategoryDefOf.Sharp)
+ Sharp(out damage, out armorPen);
+ else
+ Blunt(out damage, out armorPen);
+ ApplyDamage(ref damage, armorPen, armorReport.GetArmor(damageDef), ref damageDef, roll);
+ if (damage > 0.01f) ApplyDamage(ref damage, armorPen, armorReport.GetBodyArmor(damageDef), ref damageDef, roll);
+ return damage;
+ }
+
+ private void ApplyDamage(ref float damageAmount, float armorPenetration, float armorRating, ref DamageDef damageDef,
+ float roll)
+ {
+ var pen = Mathf.Max(armorRating - armorPenetration, 0f);
+ var blocked = pen * 0.5f;
+ var reduced = pen;
+ if (roll < blocked)
+ {
+ // stopped.
+ damageAmount = 0f;
+ }
+ else if (roll < reduced)
+ {
+ // reduced enough to become blunt damage.
+ damageAmount = damageAmount / 2f;
+ if (damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp) damageDef = DamageDefOf.Blunt;
+ }
+ }
+
+ private static float Adjust(float dmg, float ap)
+ {
+ if (Mod_CE.active) return dmg / 18f + ap;
+ if (ap != 0) return dmg / 12f * ap;
+ return dmg / 18f;
+ }
+
+ private void Sharp(out float damage, out float ap)
+ {
+ if (primaryIsRanged)
+ {
+ damage = rangedSharp;
+ ap = rangedSharpAp;
+ }
+ else
+ {
+ damage = meleeSharp;
+ ap = meleeSharpAp;
+ }
+ }
+
+ private void Blunt(out float damage, out float ap)
+ {
+ if (primaryIsRanged)
+ {
+ damage = rangedBlunt;
+ ap = rangedBluntAp;
+ }
+ else
+ {
+ damage = meleeBlunt;
+ ap = meleeBluntAp;
+ }
+ }
+
+ private float AdjustedSharp()
+ {
+ float damage;
+ float ap;
+ if (primaryIsRanged)
+ {
+ damage = rangedSharp;
+ ap = rangedSharpAp;
+ }
+ else
+ {
+ damage = meleeSharp;
+ ap = meleeSharpAp;
+ }
+
+ if (Mod_CE.active) return damage / 12f + ap;
+ if (ap != 0) return damage / 12f * ap;
+ return damage / 18f;
+ }
+
+ private float AdjustedBlunt()
+ {
+ float damage;
+ float ap;
+ if (primaryIsRanged)
+ {
+ damage = rangedBlunt;
+ ap = rangedBluntAp;
+ }
+ else
+ {
+ damage = meleeBlunt;
+ ap = meleeBluntAp;
+ }
+
+ if (Mod_CE.active) return damage / 12f + ap;
+ if (ap != 0) return damage / 12f * ap;
+ return damage / 18f;
+ }
+
+ private float GetArmorPenetration(ProjectileProperties projectile)
+ {
+ return Mod_CE.GetProjectileArmorPenetration(projectile);
+ }
+}
\ No newline at end of file
diff --git a/Source/Rule56/DamageUtility.cs b/Source/Rule56/DamageUtility.cs
index d7f4871..2a58745 100644
--- a/Source/Rule56/DamageUtility.cs
+++ b/Source/Rule56/DamageUtility.cs
@@ -68,7 +68,7 @@ public static DamageReport GetDamageReport(Thing thing, Listing_Collapsible coll
}
}
}
- Verb effectiveVerb = pawn.CurrentEffectiveVerb;
+ Verb effectiveVerb = pawn.TryGetAttackVerb();
if (effectiveVerb != null)
{
if (!effectiveVerb.IsMeleeAttack && !pawn.Downed)
@@ -164,6 +164,14 @@ public static DamageDef GetDamageDef(this VerbProperties props)
return props.meleeDamageDef;
}
+ public static void Invalidate(Thing thing)
+ {
+ if (reports.ContainsKey(thing.thingIDNumber))
+ {
+ reports.Remove(thing.thingIDNumber);
+ }
+ }
+
public static void ClearCache()
{
reports.Clear();
diff --git a/Source/Rule56/Data/AIAgentData.cs b/Source/Rule56/Data/AIAgentData.cs
index bf85876..93e004a 100644
--- a/Source/Rule56/Data/AIAgentData.cs
+++ b/Source/Rule56/Data/AIAgentData.cs
@@ -155,7 +155,8 @@ public List BeingTargetedBy
}
public AIEnvThings AllEnemies
{
- get => enemies.AsReadonly;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => enemies;
}
public int NumEnemies
{
@@ -200,7 +201,8 @@ public void ReSetEnemies()
public AIEnvThings AllAllies
{
- get => allies.AsReadonly;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => allies;
}
public int NumAllies
{
@@ -243,5 +245,12 @@ public void BeingTargeted(Thing targeter)
#endregion
+
+ public void PostDeSpawn()
+ {
+ enemies?.Clear();
+ allies?.Clear();
+ targetedBy?.Clear();
+ }
}
}
diff --git a/Source/Rule56/Data/AIEnvThings.cs b/Source/Rule56/Data/AIEnvThings.cs
index 44b8a2e..81d1d61 100644
--- a/Source/Rule56/Data/AIEnvThings.cs
+++ b/Source/Rule56/Data/AIEnvThings.cs
@@ -120,10 +120,6 @@ public void CopyTo(Array array, int index)
public void ExposeData()
{
-// if (Scribe.mode != LoadSaveMode.Saving)
-// {
-// Scribe_Collections.Look(ref elements, $"collectionThings", LookMode.Deep);
-// }
}
public void Clear()
diff --git a/Source/Rule56/Debugging/ExceptionUtility.cs b/Source/Rule56/Debugging/ExceptionUtility.cs
index 39d9e20..01a9836 100644
--- a/Source/Rule56/Debugging/ExceptionUtility.cs
+++ b/Source/Rule56/Debugging/ExceptionUtility.cs
@@ -9,14 +9,14 @@ public static class ExceptionUtility
public static void ShowExceptionGui(this Exception er, bool rethrow = true)
{
Log.Error($"ISMA: base error {er}");
-#if DEBUG
- if (enabled && Find.WindowStack.windows.Count(w => w is Window_Exception) <= 3)
- {
- StackTrace trace = new StackTrace();
- Window_Exception window = new Window_Exception(er, trace, string.Empty);
- Find.WindowStack.Add(window);
- }
-#endif
+//#if DEBUG
+// if (enabled && Find.WindowStack.windows.Count(w => w is Window_Exception) <= 3)
+// {
+// StackTrace trace = new StackTrace();
+// Window_Exception window = new Window_Exception(er, trace, string.Empty);
+// Find.WindowStack.Add(window);
+// }
+//#endif
if (rethrow)
{
throw er;
diff --git a/Source/Rule56/Debugging/JobLog.cs b/Source/Rule56/Debugging/JobLog.cs
index 593d2a5..18c010d 100644
--- a/Source/Rule56/Debugging/JobLog.cs
+++ b/Source/Rule56/Debugging/JobLog.cs
@@ -14,9 +14,12 @@ public class JobLog
public string duty;
public int id;
public string job;
+ public string note = string.Empty;
public IntVec3 origin;
public List stacktrace;
public List thinknode;
+ public List path;
+ public List pathSapper;
public int timestamp;
@@ -104,6 +107,7 @@ public override string ToString()
builder.AppendFormat("duty:\t{0}\n", duty);
builder.AppendFormat("timestamp:\t{0}\n", timestamp);
builder.AppendFormat("(origin:{0}, dest:{1})\n", origin, destination);
+ builder.AppendFormat($"note: {note}\n");
if (destination.IsValid)
{
builder.AppendFormat("distanceToDest:\n{0}",destination.DistanceTo(origin));
diff --git a/Source/Rule56/Debugging/JobLogUtility.cs b/Source/Rule56/Debugging/JobLogUtility.cs
new file mode 100644
index 0000000..f5be199
--- /dev/null
+++ b/Source/Rule56/Debugging/JobLogUtility.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Linq;
+using CombatAI.Comps;
+using Verse;
+using Verse.AI;
+namespace CombatAI
+{
+ public static class JobLogUtility
+ {
+ public static JobLog LogFor(this Pawn pawn, Job job, string tag = null)
+ {
+ ThingComp_CombatAI comp = pawn.AI();
+ JobLog log = comp.jobLogs?.FirstOrDefault(j => j.id == job.loadID) ?? null;
+ if (log == null)
+ {
+ log = JobLog.For(pawn, job, tag);
+ comp.jobLogs ??= new List();
+ comp.jobLogs.Add(log);
+ }
+ return log;
+ }
+ }
+}
diff --git a/Source/Rule56/Debugging/Window_JobLogs.cs b/Source/Rule56/Debugging/Window_JobLogs.cs
index c69f875..c91615d 100644
--- a/Source/Rule56/Debugging/Window_JobLogs.cs
+++ b/Source/Rule56/Debugging/Window_JobLogs.cs
@@ -170,7 +170,8 @@ private void DoTestContents(Rect inRect)
{
foreach (Pawn other in Find.Selector.SelectedPawns)
{
- other.mindState.duty = new PawnDuty(DutyDefOf.Defend, escortee);
+ other.mindState.duty = new PawnDuty(CombatAI_DutyDefOf.CombatAI_Escort, escortee);
+ other.mindState.duty.radius = 15;
}
Messages.Message($"Success: Escorting {escortee}", MessageTypeDefOf.CautionInput);
}
@@ -212,6 +213,41 @@ private void DoTestContents(Rect inRect)
}
});
}
+ if (ButtonText(collapsible_dutyTest, "Flash sapper path to"))
+ {
+ Find.Targeter.BeginTargeting(new TargetingParameters
+ {
+ canTargetAnimals = false,
+ canTargetBuildings = false,
+ canTargetCorpses = false,
+ canTargetHumans = false,
+ canTargetSelf = false,
+ canTargetMechs = false,
+ canTargetLocations = true
+ }, info =>
+ {
+ if (info.Cell.IsValid)
+ {
+ PathFinder_Patch.FlashSapperPath = true;
+ try
+ {
+ PawnPath path = pawn.Map.pathFinder.FindPath(pawn.Position, info.Cell, TraverseParms.For(pawn, Danger.Deadly, TraverseMode.PassAllDestroyableThings));
+ if (path is { Found: true })
+ {
+ path.ReleaseToPool();
+ }
+ }
+ catch (Exception er)
+ {
+ Log.Error(er.ToString());
+ }
+ finally
+ {
+ PathFinder_Patch.FlashSapperPath = false;
+ }
+ }
+ });
+ }
if (ButtonText(collapsible_dutyTest, "Region-wise distance"))
{
Find.Targeter.BeginTargeting(new TargetingParameters
@@ -270,6 +306,7 @@ private void DoTestContents(Rect inRect)
}
if (ButtonText(collapsible_dutyTest, "Reachability check"))
{
+ Pawn parentPawn = comp.selPawn;
Find.Targeter.BeginTargeting(new TargetingParameters
{
canTargetAnimals = false,
@@ -281,14 +318,15 @@ private void DoTestContents(Rect inRect)
canTargetLocations = true,
validator = info =>
{
- if (info.Cell.IsValid)
+ if (info.Cell.IsValid && info.Cell.InBounds(map))
{
- string result = $"ByPawn={pawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.ByPawn)}\n"
- + $"NoPassClosedDoors={pawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.NoPassClosedDoors)}\n"
- + $"NoPassClosedDoorsOrWater={pawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.NoPassClosedDoorsOrWater)}\n"
- + $"PassDoors={pawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.PassDoors)}\n"
- + $"PassAllDestroyableThings={pawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.PassAllDestroyableThings)}\n"
- + $"PassAllDestroyableThingsNotWater={pawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.PassAllDestroyableThingsNotWater)}\n";
+ string result = $"ByPawn={parentPawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.ByPawn)}\n"
+ + $"NoPassClosedDoors={parentPawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.NoPassClosedDoors)}\n"
+ + $"NoPassClosedDoorsOrWater={parentPawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.NoPassClosedDoorsOrWater)}\n"
+ + $"PassDoors={parentPawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.PassDoors)}\n"
+ + $"PassAllDestroyableThings={parentPawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.PassAllDestroyableThings)}\n"
+ + $"PassAllDestroyableThingsNotWater={parentPawn.CanReach(info.Cell, PathEndMode.InteractionCell, Danger.Deadly, true, true, TraverseMode.PassAllDestroyableThingsNotWater)}\n";
+ Log.Message(result);
}
return true;
},
@@ -444,10 +482,30 @@ private void DrawSelection(Rect inRect)
UnityEngine.GUIUtility.systemCopyBuffer = selectedLog.ToString();
Messages.Message("Job info copied to clipboard", MessageTypeDefOf.CautionInput);
}
+ if (selectedLog.path != null && Widgets.ButtonText(rect.LeftPartPixels(300).RightPartPixels(150), "Flash path"))
+ {
+ Messages.Message("Flashed path", MessageTypeDefOf.CautionInput);
+ map.debugDrawer.debugCells.Clear();
+ for (int i = 0; i < selectedLog.path.Count; i++)
+ {
+ map.debugDrawer.FlashCell(selectedLog.path[i],(float)i / (selectedLog.path.Count), $"{selectedLog.path.Count - i}");
+ }
+
+ }
+ if (selectedLog.pathSapper != null && Widgets.ButtonText(rect.LeftPartPixels(450).RightPartPixels(150), "Flash sapper path"))
+ {
+ Messages.Message("Flashed sapper path", MessageTypeDefOf.CautionInput);
+ map.debugDrawer.debugCells.Clear();
+ for (int i = 0; i < selectedLog.pathSapper.Count; i++)
+ {
+ map.debugDrawer.FlashCell(selectedLog.pathSapper[i],(float)i / (selectedLog.pathSapper.Count), $"{selectedLog.pathSapper.Count - i}");
+ }
+ }
});
collapsible.Label($"JobDef.defName:\t{selectedLog.job}");
collapsible.Line(1);
collapsible.Label($"DutyDef.defName:\t{selectedLog.duty}");
+ collapsible.Label($"Notes:\t{selectedLog.note}");
collapsible.Line(1);
collapsible.Lambda(40, rect =>
{
diff --git a/Source/Rule56/DefOfs/CombatAI_HediffDefOf.cs b/Source/Rule56/DefOfs/CombatAI_HediffDefOf.cs
index 81cdd81..5872d59 100644
--- a/Source/Rule56/DefOfs/CombatAI_HediffDefOf.cs
+++ b/Source/Rule56/DefOfs/CombatAI_HediffDefOf.cs
@@ -5,6 +5,7 @@ namespace CombatAI
[DefOf]
public static class CombatAI_HediffDefOf
{
+ [MayRequireBiotech]
public static HediffDef MechlinkImplant;
}
}
diff --git a/Source/Rule56/DefOfs/CombatAI_ThingDefOf.cs b/Source/Rule56/DefOfs/CombatAI_ThingDefOf.cs
new file mode 100644
index 0000000..83912e6
--- /dev/null
+++ b/Source/Rule56/DefOfs/CombatAI_ThingDefOf.cs
@@ -0,0 +1,11 @@
+using RimWorld;
+using Verse;
+namespace CombatAI
+{
+ [DefOf]
+ public static class CombatAI_ThingDefOf
+ {
+ [MayRequireBiotech()]
+ public static ThingDef Mech_Tunneler;
+ }
+}
diff --git a/Source/Rule56/Deprecated/Utilities/GenClosest.cs b/Source/Rule56/Deprecated/Utilities/GenClosest.cs
index c82ed83..34d36b7 100644
--- a/Source/Rule56/Deprecated/Utilities/GenClosest.cs
+++ b/Source/Rule56/Deprecated/Utilities/GenClosest.cs
@@ -1,4 +1,10 @@
-namespace CombatAI
+#if DEBUG_REACTION
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using CombatAI.Utilities;
+using Verse;
+#endif
+namespace CombatAI
{
#if DEBUG_REACTION
public static class GenClosest
diff --git a/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTracker.cs b/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTracker.cs
index cd2d107..79097ab 100644
--- a/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTracker.cs
+++ b/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTracker.cs
@@ -1,6 +1,13 @@
namespace CombatAI.Utilities
{
#if DEBUG_REACTION
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Runtime.CompilerServices;
+ using RimWorld;
+ using UnityEngine;
+ using Verse;
public class ThingsTracker : MapComponent
{
diff --git a/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTrackingModel.cs b/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTrackingModel.cs
index 02c4ed6..c6981c0 100644
--- a/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTrackingModel.cs
+++ b/Source/Rule56/Deprecated/Utilities/Tracking/ThingsTrackingModel.cs
@@ -1,6 +1,12 @@
namespace CombatAI.Utilities
{
#if DEBUG_REACTION
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+ using UnityEngine;
+ using Verse;
public class ThingsTrackingModel
{
diff --git a/Source/Rule56/DifficultyUtility.cs b/Source/Rule56/DifficultyUtility.cs
index 8840e85..45fda37 100644
--- a/Source/Rule56/DifficultyUtility.cs
+++ b/Source/Rule56/DifficultyUtility.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Linq;
+using RimWorld;
using Verse;
namespace CombatAI
{
@@ -5,28 +8,43 @@ public static class DifficultyUtility
{
public static void SetDifficulty(Difficulty difficulty)
{
+ float sappingTech = 1;
+ Finder.Settings.ResetTechSettings();
switch (difficulty)
{
case Difficulty.Easy:
Finder.Settings.Pathfinding_DestWeight = 0.875f;
Finder.Settings.Caster_Enabled = false;
- Finder.Settings.Temperature_Enabled = true;
+ foreach (var def in DefDatabase.AllDefs.Where(d => d.race != null))
+ {
+ var settings = Finder.Settings.GetDefKindSettings(def, null);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = false;
+ settings.React_Enabled = false;
+ settings.Retreat_Enabled = false;
+ foreach (var kind in DefDatabase.AllDefs.Where(d => d.race == def))
+ {
+ settings = Finder.Settings.GetDefKindSettings(def, kind);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = false;
+ settings.React_Enabled = false;
+ settings.Retreat_Enabled = false;
+ }
+ }
Finder.Settings.Targeter_Enabled = false;
- Finder.Settings.Pather_Enabled = true;
- Finder.Settings.Pather_KillboxKiller = false;
Finder.Settings.PerformanceOpt_Enabled = true;
- Finder.Settings.React_Enabled = false;
- Finder.Settings.Retreat_Enabled = false;
Finder.Settings.Flank_Enabled = false;
+ Finder.Settings.Enable_Sprinting = false;
+ Finder.Settings.Enable_Groups = false;
+ Finder.Settings.Pathfinding_SappingMul = 1.5f;
+ Finder.Settings.Pathfinding_SquadPathWidth = 1;
- Finder.Settings.Enable_Sprinting = false;
- Finder.Settings.Enable_Groups = false;
- Finder.Settings.Pathfinding_SappingMul = 1.5f;
-
- Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 3;
+ Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 1;
if (Current.ProgramState != ProgramState.Playing)
{
- Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 10;
+ Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 4;
}
Finder.Settings.SightSettings_Wildlife.interval = 6;
if (Current.ProgramState != ProgramState.Playing)
@@ -42,21 +60,35 @@ public static void SetDifficulty(Difficulty difficulty)
case Difficulty.Normal:
Finder.Settings.Pathfinding_DestWeight = 0.725f;
Finder.Settings.Caster_Enabled = true;
- Finder.Settings.Temperature_Enabled = true;
+ foreach (var def in DefDatabase.AllDefs.Where(d => d.race != null))
+ {
+ var settings = Finder.Settings.GetDefKindSettings(def, null);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = true;
+ settings.React_Enabled = true;
+ settings.Retreat_Enabled = false;
+ foreach (var kind in DefDatabase.AllDefs.Where(d => d.race == def))
+ {
+ settings = Finder.Settings.GetDefKindSettings(def, kind);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = true;
+ settings.React_Enabled = true;
+ settings.Retreat_Enabled = false;
+ }
+ }
Finder.Settings.Targeter_Enabled = true;
- Finder.Settings.Pather_Enabled = true;
- Finder.Settings.Pather_KillboxKiller = true;
Finder.Settings.PerformanceOpt_Enabled = true;
- Finder.Settings.React_Enabled = true;
- Finder.Settings.Retreat_Enabled = false;
Finder.Settings.Flank_Enabled = true;
Finder.Settings.Enable_Sprinting = false;
Finder.Settings.Enable_Groups = true;
Finder.Settings.Pathfinding_SappingMul = 1.3f;
- Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 3;
+ Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 1;
+ Finder.Settings.Pathfinding_SquadPathWidth = 2;
if (Current.ProgramState != ProgramState.Playing)
{
- Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 5;
+ Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 4;
}
Finder.Settings.SightSettings_Wildlife.interval = 3;
if (Current.ProgramState != ProgramState.Playing)
@@ -72,23 +104,37 @@ public static void SetDifficulty(Difficulty difficulty)
case Difficulty.Hard:
Finder.Settings.Pathfinding_DestWeight = 0.625f;
Finder.Settings.Caster_Enabled = true;
- Finder.Settings.Temperature_Enabled = true;
+ foreach (var def in DefDatabase.AllDefs.Where(d => d.race != null))
+ {
+ var settings = Finder.Settings.GetDefKindSettings(def, null);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = true;
+ settings.React_Enabled = true;
+ settings.Retreat_Enabled = true;
+ foreach (var kind in DefDatabase.AllDefs.Where(d => d.race == def))
+ {
+ settings = Finder.Settings.GetDefKindSettings(def, kind);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = true;
+ settings.React_Enabled = true;
+ settings.Retreat_Enabled = true;
+ }
+ }
Finder.Settings.Targeter_Enabled = true;
- Finder.Settings.Pather_Enabled = true;
- Finder.Settings.Pather_KillboxKiller = true;
- Finder.Settings.React_Enabled = true;
- Finder.Settings.Retreat_Enabled = true;
Finder.Settings.Flank_Enabled = true;
Finder.Settings.PerformanceOpt_Enabled = true;
- Finder.Settings.Enable_Sprinting = false;
- Finder.Settings.Enable_Groups = true;
- Finder.Settings.Pathfinding_SappingMul = 1.0f;
-
- Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 2;
+ Finder.Settings.Enable_Sprinting = false;
+ Finder.Settings.Enable_Groups = true;
+ Finder.Settings.Pathfinding_SappingMul = 1.0f;
+ Finder.Settings.Pathfinding_SquadPathWidth = 4;
+
+ Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 1;
if (Current.ProgramState != ProgramState.Playing)
{
- Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 5;
+ Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 4;
}
Finder.Settings.SightSettings_Wildlife.interval = 2;
if (Current.ProgramState != ProgramState.Playing)
@@ -102,25 +148,39 @@ public static void SetDifficulty(Difficulty difficulty)
}
break;
case Difficulty.DeathWish:
+ sappingTech = 0.7f;
Finder.Settings.Pathfinding_DestWeight = 0.45f;
- Finder.Settings.Caster_Enabled = true;
- Finder.Settings.Temperature_Enabled = true;
+ Finder.Settings.Caster_Enabled = true;
Finder.Settings.Targeter_Enabled = true;
- Finder.Settings.Pather_Enabled = true;
- Finder.Settings.Pather_KillboxKiller = true;
- Finder.Settings.React_Enabled = true;
- Finder.Settings.Retreat_Enabled = true;
+ foreach (var def in DefDatabase.AllDefs.Where(d => d.race != null))
+ {
+ var settings = Finder.Settings.GetDefKindSettings(def, null);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = true;
+ settings.React_Enabled = true;
+ settings.Retreat_Enabled = true;
+ foreach (var kind in DefDatabase.AllDefs.Where(d => d.race == def))
+ {
+ settings = Finder.Settings.GetDefKindSettings(def, kind);
+ settings.Temperature_Enabled = true;
+ settings.Pather_Enabled = true;
+ settings.Pather_KillboxKiller = true;
+ settings.React_Enabled = true;
+ settings.Retreat_Enabled = true;
+ }
+ }
Finder.Settings.Flank_Enabled = true;
Finder.Settings.PerformanceOpt_Enabled = false;
- Finder.Settings.Enable_Sprinting = true;
- Finder.Settings.Enable_Groups = true;
- Finder.Settings.Pathfinding_SappingMul = 1.0f;
-
+ Finder.Settings.Enable_Sprinting = true;
+ Finder.Settings.Enable_Groups = true;
+ Finder.Settings.Pathfinding_SappingMul = 0.8f;
+ Finder.Settings.Pathfinding_SquadPathWidth = 6;
Finder.Settings.SightSettings_FriendliesAndRaiders.interval = 1;
if (Current.ProgramState != ProgramState.Playing)
{
- Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 5;
+ Finder.Settings.SightSettings_FriendliesAndRaiders.buckets = 4;
}
Finder.Settings.SightSettings_Wildlife.interval = 2;
if (Current.ProgramState != ProgramState.Playing)
@@ -134,6 +194,10 @@ public static void SetDifficulty(Difficulty difficulty)
}
break;
}
+ foreach (TechLevel tech in Enum.GetValues(typeof(TechLevel)))
+ {
+ Finder.Settings.GetTechSettings(tech).sapping *= sappingTech;
+ }
}
}
}
diff --git a/Source/Rule56/Extern.cs b/Source/Rule56/Extern.cs
index 3f8a0bd..6a93fd5 100644
--- a/Source/Rule56/Extern.cs
+++ b/Source/Rule56/Extern.cs
@@ -14,40 +14,50 @@ public static class Extern
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ThingComp_CombatAI AI(this Pawn pawn)
{
- if (active)
- {
- return CombatAI_ThingComp(pawn);
- }
+// if (active)
+// {
+// return CombatAI_ThingComp(pawn);
+// }
return pawn.GetComp_Fast();
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static CompAttachBase CompAttachBase(this Pawn pawn)
+ {
+ if (active)
+ {
+ return CombatAI_CompAttachBase(pawn);
+ }
+ return pawn.GetComp_Fast();
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MapComponent_CombatAI AI(this Map map)
{
- if (active)
- {
- return CombatAI_MapComp(map);
- }
+// if (active)
+// {
+// return CombatAI_MapComp(map);
+// }
return map.GetComp_Fast();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SightTracker Sight(this Map map)
{
- if (active)
- {
- return CombatAI_Sight(map);
- }
+// if (active)
+// {
+// return CombatAI_Sight(map);
+// }
return map.GetComp_Fast();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AvoidanceTracker Avoidance(this Map map)
{
- if (active)
- {
- return CombatAI_Avoidance(map);
- }
+// if (active)
+// {
+// return CombatAI_Avoidance(map);
+// }
return map.GetComp_Fast();
}
@@ -56,6 +66,9 @@ public static AvoidanceTracker Avoidance(this Map map)
private static extern ThingComp_CombatAI CombatAI_ThingComp(Pawn pawn);
[PrepatcherField]
[InjectComponent]
+ private static extern CompAttachBase CombatAI_CompAttachBase(Pawn pawn);
+ [PrepatcherField]
+ [InjectComponent]
private static extern MapComponent_CombatAI CombatAI_MapComp(Map map);
[PrepatcherField]
[InjectComponent]
diff --git a/Source/Rule56/GenNearby.cs b/Source/Rule56/GenNearby.cs
index de25a5c..9ef141e 100644
--- a/Source/Rule56/GenNearby.cs
+++ b/Source/Rule56/GenNearby.cs
@@ -16,7 +16,7 @@ public static void NearbyPawns(this IntVec3 root, Map map, List pawns, Tra
}
return pawns.Count >= maxNum;
};
- GenClosest.RegionwiseBFSWorker(root, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, parms, func, null, 1, maxReigons, maxDist, out int _);
+ Verse.GenClosest.RegionwiseBFSWorker(root, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, parms, func, null, 1, maxReigons, maxDist, out int _);
}
}
}
diff --git a/Source/Rule56/Gui/Window_DefKindSettings.cs b/Source/Rule56/Gui/Window_DefKindSettings.cs
new file mode 100644
index 0000000..cc4877a
--- /dev/null
+++ b/Source/Rule56/Gui/Window_DefKindSettings.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Linq;
+using CombatAI.R;
+using UnityEngine;
+using Verse;
+
+namespace CombatAI.Gui
+{
+ public class Window_DefKindSettings : Window
+ {
+ private string searchTerm;
+ private Vector2 pos;
+ private (ThingDef, PawnKindDef, Settings.DefKindAISettings)? cur;
+ private readonly Listing_Collapsible collapsible;
+ private readonly List<(ThingDef, PawnKindDef, Settings.DefKindAISettings)> defs;
+
+ public Window_DefKindSettings()
+ {
+ drawShadow = true;
+ forcePause = true;
+ layer = WindowLayer.Super;
+ draggable = false;
+ collapsible = new Listing_Collapsible();
+ defs = new List<(ThingDef, PawnKindDef, Settings.DefKindAISettings)>();
+ searchTerm = string.Empty;
+ foreach (ThingDef def in DefDatabase.AllDefs.Where(d => d.race != null))
+ {
+ foreach (PawnKindDef kind in DefDatabase.AllDefs.Where(d => d.race == def))
+ {
+ defs.Add((def, kind, Finder.Settings.GetDefKindSettings(def, kind)));
+ }
+ defs.Add((def, null, Finder.Settings.GetDefKindSettings(def, null)));
+ }
+ }
+
+ public override Vector2 InitialSize
+ {
+ get
+ {
+ Vector2 vec = new Vector2();
+ vec.x = Mathf.RoundToInt(Maths.Max(UI.screenWidth * 0.6f, 450));
+ vec.y = Mathf.RoundToInt(Maths.Max(UI.screenHeight * 0.9f, 400));
+ return vec;
+ }
+ }
+
+ public override void DoWindowContents(Rect inRect)
+ {
+ collapsible.Expanded = true;
+ collapsible.Begin(inRect, Keyed.CombatAI_DefKindSettings_Title, true, false);
+ collapsible.Label(Keyed.CombatAI_DefKindSettings_Description);
+ collapsible.Line(1);
+ if (cur != null)
+ {
+ ThingDef def = cur?.Item1;
+ PawnKindDef kind = cur?.Item2;
+ collapsible.Label(Keyed.CombatAI_DefKindSettings_Selected + ":" + def.label + " " + (kind?.label ?? string.Empty), null, false, true, GUIFontSize.Smaller);
+ collapsible.Line(1);
+ Settings.DefKindAISettings settings = cur?.Item3;
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Pather, ref settings.Pather_Enabled);
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_KillBoxKiller, ref settings.Pather_KillboxKiller, disabled: !settings.Pather_Enabled);
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Temperature, ref settings.Temperature_Enabled, disabled: !settings.Pather_Enabled);
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Reaction, ref settings.React_Enabled);
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Retreat, ref settings.Retreat_Enabled);
+ }
+ collapsible.Line(1);
+ collapsible.Lambda(18, (rect) =>
+ {
+ Widgets.Label(rect.LeftPart(0.5f), "Def");
+ Widgets.Label(rect.RightPart(0.5f), "Kind");
+ }, false, true);
+ collapsible.End(ref inRect);
+ searchTerm = Widgets.TextField(inRect.TopPartPixels(30).BottomPartPixels(25), searchTerm).ToLower().Trim();
+ inRect.yMin += 35;
+ GUIUtility.ScrollView(inRect.TopPartPixels(inRect.height - 45), ref pos, defs.Where(d => searchTerm.NullOrEmpty() || (d.Item1?.label?.StartsWith(searchTerm) ?? false) || (d.Item2?.label?.ToLower().StartsWith(searchTerm) ?? false)), (item) => 20, (rect, tuple) =>
+ {
+ ThingDef def = tuple.Item1;
+ PawnKindDef kind = tuple.Item2;
+ if (tuple == cur)
+ {
+ Widgets.DrawHighlight(rect);
+ }
+ Widgets.DefLabelWithIcon(rect.LeftPart(0.5f), def);
+ if (kind != null)
+ {
+ Widgets.DefLabelWithIcon(rect.RightPart(0.5f), kind);
+ }
+ if (Widgets.ButtonInvisible(rect, false))
+ {
+ cur = tuple;
+ }
+ });
+ inRect.yMin += inRect.height - 40;
+ var center = inRect.center;
+ inRect.width = 200;
+ inRect.center = center;
+ if (Widgets.ButtonText(inRect, R.Keyed.CombatAI_Close))
+ {
+ this.Close();
+ }
+ }
+ }
+}
diff --git a/Source/Rule56/Gui/Window_QuickSetup.cs b/Source/Rule56/Gui/Window_QuickSetup.cs
index fdd757e..b018bed 100644
--- a/Source/Rule56/Gui/Window_QuickSetup.cs
+++ b/Source/Rule56/Gui/Window_QuickSetup.cs
@@ -59,7 +59,7 @@ public override void DoWindowContents(Rect inRect)
collapsible.Line(1);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_PerformanceOpt, ref Finder.Settings.PerformanceOpt_Enabled);
collapsible.Line(1);
- collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_KillBoxKiller, ref Finder.Settings.Pather_KillboxKiller);
+ collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_RandomizedPersonality, ref Finder.Settings.Personalities_Enabled);
collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Sprinting, ref Finder.Settings.Enable_Sprinting, Keyed.CombatAI_Settings_Basic_Sprinting_Description);
collapsible.End(ref optionsRect);
optionsRect.yMin += 5;
diff --git a/Source/Rule56/HeuristicDistanceUtility.cs b/Source/Rule56/HeuristicDistanceUtility.cs
index 8fd9e8c..8e1b6f2 100644
--- a/Source/Rule56/HeuristicDistanceUtility.cs
+++ b/Source/Rule56/HeuristicDistanceUtility.cs
@@ -64,12 +64,12 @@ public static int HeuristicDistanceTo_RegionWise(this IntVec3 first, IntVec3 sec
Region origin = first.GetRegion(map);
if (origin == null)
{
- return int.MaxValue;
+ return short.MaxValue;
}
Region target = second.GetRegion(map);
if (target == null)
{
- return int.MaxValue;
+ return short.MaxValue;
}
// always place the smaller id first.
int a = origin.id;
@@ -107,11 +107,11 @@ public static float HeuristicDistanceTo(this IntVec3 first, IntVec3 second, Map
MapComponent_CombatAI comp = map.AI();
if (!first.IsValid)
{
- return float.MaxValue;
+ return short.MaxValue;
}
if (!second.IsValid)
{
- return float.MaxValue;
+ return short.MaxValue;
}
_regions.Clear();
saveRegions = true;
@@ -124,7 +124,7 @@ public static float HeuristicDistanceTo(this IntVec3 first, IntVec3 second, Map
{
_distCell = int.MaxValue;
_targetCell = second;
- comp.flooder.Flood(first, second, DistanceTo_CellWise_Delegate, null, Validator_CellWise_Delegate, 9999, 9999);
+ comp.flooder_heursitic.Flood(first, second, DistanceTo_CellWise_Delegate, null, Validator_CellWise_Delegate, 9999, 9999);
}
}
catch (Exception exp)
diff --git a/Source/Rule56/ITSignalGrid.cs b/Source/Rule56/ITSignalGrid.cs
index cd23e9a..8106ed0 100644
--- a/Source/Rule56/ITSignalGrid.cs
+++ b/Source/Rule56/ITSignalGrid.cs
@@ -18,20 +18,24 @@ public class ITSignalGrid
{
private readonly IFieldInfo[] cells;
private readonly IField[] cells_aiming;
- private readonly IField[] cells_blunt;
+ private readonly IField[] cells_dist;
private readonly IField[] cells_dir;
- private readonly IField[] cells_flags;
+ private readonly IField[] cells_staticFlags;
+ private readonly IField[] cells_dynamicFlags;
private readonly IField[] cells_meta;
private readonly IField[] cells_sharp;
+ private readonly IField[] cells_blunt;
private readonly IField[] cells_strength;
private readonly CellIndices indices;
public readonly int NumGridCells;
- private byte curAimAvailablity;
- private float curBlunt;
- private MetaCombatAttribute curMeta;
- private float curSharp;
+ public IntVec3 curRoot;
+ public byte curAimAvailablity;
+ public float curBlunt;
+ public MetaCombatAttribute curMeta;
+ public ulong curFlags;
+ public float curSharp;
private short r_sig = 19;
@@ -41,14 +45,16 @@ public ITSignalGrid(Map map)
indices = map.cellIndices;
NumGridCells = indices.NumGridCells;
- cells = new IFieldInfo[NumGridCells];
- cells_strength = new IField[NumGridCells];
- cells_dir = new IField[NumGridCells];
- cells_flags = new IField[NumGridCells];
- cells_blunt = new IField[NumGridCells];
- cells_sharp = new IField[NumGridCells];
- cells_meta = new IField[NumGridCells];
- cells_aiming = new IField[NumGridCells];
+ cells = new IFieldInfo[NumGridCells];
+ cells_strength = new IField[NumGridCells];
+ cells_dir = new IField[NumGridCells];
+ cells_dist = new IField[NumGridCells];
+ cells_staticFlags = new IField[NumGridCells];
+ cells_dynamicFlags = new IField[NumGridCells];
+ cells_blunt = new IField[NumGridCells];
+ cells_sharp = new IField[NumGridCells];
+ cells_meta = new IField[NumGridCells];
+ cells_aiming = new IField[NumGridCells];
}
public short CycleNum
@@ -57,6 +63,16 @@ public short CycleNum
private set;
} = 19;
+ private Vector2 ToV2(IntVec3 vec)
+ {
+ return new Vector2(vec.x, vec.z);
+ }
+
+ private IntVec3 ToIntV3(Vector2 vec)
+ {
+ return new IntVec3((int)vec.x, 0, (int)vec.y);
+ }
+
public void Set(IntVec3 cell, float signalStrength, Vector2 dir)
{
Set(indices.CellToIndex(cell), signalStrength, dir);
@@ -71,14 +87,22 @@ public void Set(int index, float signalStrength, Vector2 dir)
int dc = CycleNum - info.cycle;
if (dc == 0)
{
- info.num += 1;
- cells_strength[index].value += signalStrength;
- cells_dir[index].value += dir;
- cells_meta[index].value |= curMeta;
- cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
- cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
- cells_aiming[index].value += curAimAvailablity;
-// cells_aiming[index].value
+ info.num += 1;
+ cells_strength[index].value += signalStrength;
+ cells_dir[index].value += dir;
+ cells_meta[index].value |= curMeta;
+ if (curRoot.IsValid)
+ {
+ Vector2 distVec = ToV2( curRoot - indices.IndexToCell(index));
+ if (distVec.sqrMagnitude < cells_dist[index].value.sqrMagnitude)
+ {
+ cells_dist[index].value = distVec;
+ }
+ }
+ cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
+ cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
+ cells_aiming[index].value += curAimAvailablity;
+ cells_dynamicFlags[index].value |= curFlags;
}
else
{
@@ -94,7 +118,16 @@ public void Set(int index, float signalStrength, Vector2 dir)
info.num = 1;
cells_strength[index].ReSet(signalStrength, expired);
cells_dir[index].ReSet(dir, expired);
- cells_flags[index].ReSet(0, expired);
+ if (curRoot.IsValid)
+ {
+ cells_dist[index].ReSet(ToV2(curRoot - indices.IndexToCell(index) ), expired);
+ }
+ else
+ {
+ cells_dist[index].ReSet(new Vector2(1000, 1000), expired);
+ }
+ cells_staticFlags[index].ReSet(0, expired);
+ cells_dynamicFlags[index].ReSet(curFlags, expired);
cells_meta[index].ReSet(curMeta, expired);
cells_sharp[index].ReSet(curSharp, expired);
cells_blunt[index].ReSet(curBlunt, expired);
@@ -121,13 +154,22 @@ public void Set(int index, float signalStrength, Vector2 dir, MetaCombatAttribut
int dc = CycleNum - info.cycle;
if (dc == 0)
{
- info.num += 1;
- cells_strength[index].value += signalStrength;
- cells_dir[index].value += dir;
- cells_meta[index].value |= metaAttributes;
- cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
- cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
- cells_aiming[index].value += curAimAvailablity;
+ info.num += 1;
+ cells_strength[index].value += signalStrength;
+ cells_dir[index].value += dir;
+ cells_meta[index].value |= metaAttributes;
+ if (curRoot.IsValid)
+ {
+ Vector2 distVec = ToV2( curRoot - indices.IndexToCell(index) );
+ if (distVec.sqrMagnitude < cells_dist[index].value.sqrMagnitude)
+ {
+ cells_dist[index].value = distVec;
+ }
+ }
+ cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
+ cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
+ cells_aiming[index].value += curAimAvailablity;
+ cells_dynamicFlags[index].value |= curFlags;
}
else
{
@@ -143,7 +185,16 @@ public void Set(int index, float signalStrength, Vector2 dir, MetaCombatAttribut
info.num = 1;
cells_strength[index].ReSet(signalStrength, expired);
cells_dir[index].ReSet(dir, expired);
- cells_flags[index].ReSet(0, expired);
+ cells_staticFlags[index].ReSet(0, expired);
+ cells_dynamicFlags[index].ReSet(curFlags, expired);
+ if (curRoot.IsValid)
+ {
+ cells_dist[index].ReSet(ToV2( curRoot - indices.IndexToCell(index)), expired);
+ }
+ else
+ {
+ cells_dist[index].ReSet(new Vector2(1000, 1000), expired);
+ }
cells_meta[index].ReSet(metaAttributes, expired);
cells_sharp[index].ReSet(curSharp, expired);
cells_blunt[index].ReSet(curBlunt, expired);
@@ -170,10 +221,11 @@ public void Set(int index, MetaCombatAttribute metaAttributes)
int dc = CycleNum - info.cycle;
if (dc == 0)
{
- cells_meta[index].value |= metaAttributes;
- cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
- cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
- cells_aiming[index].value += curAimAvailablity;
+ cells_meta[index].value |= metaAttributes;
+ cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
+ cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
+ cells_aiming[index].value += curAimAvailablity;
+ cells_dynamicFlags[index].value |= curFlags;
}
else
{
@@ -189,8 +241,10 @@ public void Set(int index, MetaCombatAttribute metaAttributes)
info.num = 0;
cells_strength[index].ReSet(0, expired);
cells_dir[index].ReSet(Vector2.zero, expired);
- cells_flags[index].ReSet(0, expired);
+ cells_staticFlags[index].ReSet(0, expired);
+ cells_dynamicFlags[index].ReSet(curFlags, expired);
cells_meta[index].ReSet(metaAttributes, expired);
+ cells_dist[index].ReSet(new Vector2(1000, 1000), expired);
cells_sharp[index].ReSet(curSharp, expired);
cells_blunt[index].ReSet(curBlunt, expired);
cells_aiming[index].ReSet(curAimAvailablity, expired);
@@ -216,7 +270,7 @@ public void Set(int index, ulong flags)
int dc = CycleNum - info.cycle;
if (dc == 0)
{
- cells_flags[index].value |= flags;
+ cells_staticFlags[index].value |= flags;
cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
cells_aiming[index].value += curAimAvailablity;
@@ -235,8 +289,10 @@ public void Set(int index, ulong flags)
info.num = 0;
cells_strength[index].ReSet(0, expired);
cells_dir[index].ReSet(Vector2.zero, expired);
- cells_flags[index].ReSet(flags, expired);
+ cells_staticFlags[index].ReSet(flags, expired);
+ cells_dynamicFlags[index].ReSet(curFlags, expired);
cells_meta[index].ReSet(curMeta, expired);
+ cells_dist[index].ReSet(new Vector2(1000, 1000), expired);
cells_sharp[index].ReSet(curSharp, expired);
cells_blunt[index].ReSet(curBlunt, expired);
cells_aiming[index].ReSet(curAimAvailablity, expired);
@@ -265,8 +321,16 @@ public void Set(int index, float signalStrength, Vector2 dir, ulong flags)
info.num += 1;
cells_strength[index].value += signalStrength;
cells_dir[index].value += dir;
- cells_flags[index].value |= flags;
+ cells_staticFlags[index].value |= flags;
cells_meta[index].value |= curMeta;
+ if (curRoot.IsValid)
+ {
+ Vector2 distVec = ToV2( curRoot - indices.IndexToCell(index));
+ if (distVec.sqrMagnitude < cells_dist[index].value.sqrMagnitude)
+ {
+ cells_dist[index].value = distVec;
+ }
+ }
cells_sharp[index].value = Maths.Max(curSharp, cells_sharp[index].value);
cells_blunt[index].value = Maths.Max(curBlunt, cells_blunt[index].value);
cells_aiming[index].value += curAimAvailablity;
@@ -285,7 +349,16 @@ public void Set(int index, float signalStrength, Vector2 dir, ulong flags)
info.num = 1;
cells_strength[index].ReSet(signalStrength, expired);
cells_dir[index].ReSet(dir, expired);
- cells_flags[index].ReSet(flags, expired);
+ cells_staticFlags[index].ReSet(flags, expired);
+ cells_dynamicFlags[index].ReSet(curFlags, expired);
+ if (curRoot.IsValid)
+ {
+ cells_dist[index].ReSet(ToV2(curRoot - indices.IndexToCell(index) ), expired);
+ }
+ else
+ {
+ cells_dist[index].ReSet(new Vector2(1000, 1000), expired);
+ }
cells_meta[index].ReSet(curMeta, expired);
cells_sharp[index].ReSet(curSharp, expired);
cells_blunt[index].ReSet(curBlunt, expired);
@@ -396,11 +469,11 @@ public float GetSignalStrengthAt(int index, out int signalNum)
return signalNum = 0;
}
- public ulong GetFlagsAt(IntVec3 cell)
+ public ulong GetStaticFlagsAt(IntVec3 cell)
{
- return GetFlagsAt(indices.CellToIndex(cell));
+ return GetStaticFlagsAt(indices.CellToIndex(cell));
}
- public ulong GetFlagsAt(int index)
+ public ulong GetStaticFlagsAt(int index)
{
if (index >= 0 && index < NumGridCells)
{
@@ -408,16 +481,76 @@ public ulong GetFlagsAt(int index)
switch (CycleNum - cell.cycle)
{
case 0:
- IField flags = cells_flags[index];
+ IField flags = cells_staticFlags[index];
return flags.value | flags.valuePrev;
case 1:
- return cells_flags[index].value;
+ return cells_staticFlags[index].value;
}
}
return 0;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IntVec3 GetNearestSourceAt(int index)
+ {
+ return GetNearestSourceAtInner(index, indices.IndexToCell(index));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IntVec3 GetNearestSourceAt(IntVec3 pos)
+ {
+ return GetNearestSourceAtInner(indices.CellToIndex(pos), pos);
+ }
+ private IntVec3 GetNearestSourceAtInner(int index, IntVec3 pos)
+ {
+ if (index >= 0 && index < NumGridCells)
+ {
+ IFieldInfo cell = cells[index];
+ Vector2 offset;
+ switch (CycleNum - cell.cycle)
+ {
+ case 0:
+ IField dist = cells_dist[index];
+ offset = (dist.value.sqrMagnitude < dist.valuePrev.sqrMagnitude ? dist.value : dist.valuePrev);
+ if (offset.x > 300 || offset.y > 300)
+ {
+ return IntVec3.Invalid;
+ }
+ return pos + ToIntV3(offset);
+ case 1:
+ offset = cells_dist[index].value;
+ if (offset.x > 300 || offset.y > 300)
+ {
+ return IntVec3.Invalid;
+ }
+ return pos + ToIntV3(offset);
+ }
+ }
+ return IntVec3.Invalid;
+ }
+
+ public ulong GetDynamicFlagsAt(IntVec3 cell)
+ {
+ return GetDynamicFlagsAt(indices.CellToIndex(cell));
+ }
+ public ulong GetDynamicFlagsAt(int index)
+ {
+ if (index >= 0 && index < NumGridCells)
+ {
+ IFieldInfo cell = cells[index];
+ switch (CycleNum - cell.cycle)
+ {
+ case 0:
+ IField flags = cells_dynamicFlags[index];
+
+ return flags.value | flags.valuePrev;
+ case 1:
+ return cells_dynamicFlags[index].value;
+ }
+ }
+ return 0;
+ }
+
public MetaCombatAttribute GetCombatAttributesAt(IntVec3 cell)
{
return GetCombatAttributesAt(indices.CellToIndex(cell));
@@ -538,12 +671,14 @@ public Vector2 GetSignalDirectionAt(int index)
/// Sharp damage output/s
/// Blunt damage output/s
/// Meta flags.
- public void Next(float sharp, float blunt, MetaCombatAttribute meta)
+ public void Next(IntVec3 root, ulong flags, float sharp, float blunt, MetaCombatAttribute meta)
{
if (r_sig++ == short.MaxValue)
{
r_sig = 19;
}
+ curRoot = root;
+ curFlags = flags;
curSharp = sharp;
curBlunt = blunt;
curMeta = meta;
diff --git a/Source/Rule56/MapComponent_CombatAI.cs b/Source/Rule56/MapComponent_CombatAI.cs
index 5013937..d73c25d 100644
--- a/Source/Rule56/MapComponent_CombatAI.cs
+++ b/Source/Rule56/MapComponent_CombatAI.cs
@@ -10,7 +10,7 @@ namespace CombatAI
{
public class MapComponent_CombatAI : MapComponent
{
-
+ private int clearCacheCountDown = 14400;
/* Threading
* ----- ----- ----- -----
*/
@@ -24,6 +24,7 @@ public class MapComponent_CombatAI : MapComponent
*/
public CellFlooder flooder;
+ public CellFlooder flooder_heursitic;
public InterceptorTracker interceptors;
/* Cache
@@ -39,10 +40,11 @@ public class MapComponent_CombatAI : MapComponent
public MapComponent_CombatAI(Map map) : base(map)
{
- flooder = new CellFlooder(map);
- f_grid = new ISGrid(map);
- asyncActions = new AsyncActions();
- interceptors = new InterceptorTracker(this);
+ flooder = new CellFlooder(map);
+ flooder_heursitic = new CellFlooder(map);
+ f_grid = new ISGrid(map);
+ asyncActions = new AsyncActions();
+ interceptors = new InterceptorTracker(this);
}
public override void FinalizeInit()
@@ -60,6 +62,22 @@ public override void MapComponentTick()
interceptors.Tick();
}
+ public override void MapComponentUpdate()
+ {
+ base.MapComponentUpdate();
+ if (clearCacheCountDown-- <= 0)
+ {
+ CacheUtility.ClearAllCache();
+ regionWiseDist.Clear();
+ clearCacheCountDown = 14400;
+ }
+ else if (clearCacheCountDown % 7200 == 0)
+ {
+ CacheUtility.ClearShortCache();
+ regionWiseDist.Clear();
+ }
+ }
+
public override void MapComponentOnGUI()
{
base.MapComponentOnGUI();
diff --git a/Source/Rule56/MapComponent_FogGrid.cs b/Source/Rule56/MapComponent_FogGrid.cs
index 63d6d51..a7155ef 100644
--- a/Source/Rule56/MapComponent_FogGrid.cs
+++ b/Source/Rule56/MapComponent_FogGrid.cs
@@ -1,311 +1,441 @@
-using System.Diagnostics;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.Threading;
using UnityEngine;
using UnityEngine.Assertions;
using Verse;
namespace CombatAI
{
- [StaticConstructorOnStartup]
- public class MapComponent_FogGrid : MapComponent
- {
- private const int SECTION_SIZE = 16;
+ [StaticConstructorOnStartup]
+ public class MapComponent_FogGrid : MapComponent
+ {
+ private const int SECTION_SIZE = 16;
- private static float zoom;
- private static readonly Texture2D fogTex;
- private static readonly Mesh mesh;
- private static readonly Shader fogShader;
+ private static float zoom;
+ private static readonly Texture2D fogTex;
+ private static readonly Mesh mesh;
+ private static readonly Shader fogShader;
- private readonly AsyncActions asyncActions;
- private readonly ISection[][] grid2d;
- private readonly Rect mapRect;
- private bool alive;
- public CellIndices cellIndices;
- public GlowGrid glow;
+ private readonly object _lockerSpots = new object();
+ private readonly AsyncActions asyncActions;
+ private readonly ISection[][] grid2d;
+ private readonly Rect mapRect;
+ private readonly List spotBuffer;
- public bool[] grid;
- private bool initialized;
+ private readonly HashSet spotsQueued;
+ private bool alive;
+ public CellIndices cellIndices;
+ public GlowGrid glow;
- private Rect mapScreenRect;
- private bool ready;
- public SightGrid sight;
- private int updateNum;
- public WallGrid walls;
+ public bool[] grid;
+ private bool initialized;
+ private Rect mapScreenRect;
+ private bool ready;
+ public SightGrid sight;
+ private volatile int ticksGame;
+ private int updateNum;
+ public WallGrid walls;
- static MapComponent_FogGrid()
- {
- fogTex = new Texture2D(SECTION_SIZE, SECTION_SIZE, TextureFormat.RGBAFloat, true);
- fogTex.Apply();
- mesh = CombatAI_MeshMaker.NewPlaneMesh(Vector2.zero, Vector2.one * SECTION_SIZE);
- fogShader = AssetBundleDatabase.Get("assets/fogshader.shader");
- Assert.IsNotNull(fogShader);
- }
- public MapComponent_FogGrid(Map map) : base(map)
- {
- alive = true;
- asyncActions = new AsyncActions();
- cellIndices = map.cellIndices;
- mapRect = new Rect(0, 0, cellIndices.mapSizeX, cellIndices.mapSizeZ);
- grid = new bool[map.cellIndices.NumGridCells];
- grid2d = new ISection[Mathf.CeilToInt(cellIndices.mapSizeX / (float)SECTION_SIZE)][];
- }
+ static MapComponent_FogGrid()
+ {
+ fogTex = new Texture2D(SECTION_SIZE, SECTION_SIZE, TextureFormat.RGBAFloat, true);
+ fogTex.Apply();
+ mesh = CombatAI_MeshMaker.NewPlaneMesh(Vector2.zero, Vector2.one * SECTION_SIZE);
+ fogShader = AssetBundleDatabase.Get("assets/fogshader.shader");
+ Assert.IsNotNull(fogShader);
+ }
- public float SkyGlow
- {
- get;
- private set;
- }
+ public MapComponent_FogGrid(Map map) : base(map)
+ {
+ alive = true;
+ spotsQueued = new HashSet(16);
+ spotBuffer = new List(256);
+ asyncActions = new AsyncActions();
+ cellIndices = map.cellIndices;
+ mapRect = new Rect(0, 0, cellIndices.mapSizeX, cellIndices.mapSizeZ);
+ grid = new bool[map.cellIndices.NumGridCells];
+ grid2d = new ISection[Mathf.CeilToInt(cellIndices.mapSizeX / (float)SECTION_SIZE)][];
+ }
- public override void FinalizeInit()
- {
- base.FinalizeInit();
- asyncActions.Start();
- }
+ public float SkyGlow
+ {
+ get;
+ private set;
+ }
- public bool IsFogged(IntVec3 cell)
- {
- return IsFogged(cellIndices.CellToIndex(cell));
- }
- public bool IsFogged(int index)
- {
- if (!Finder.Settings.FogOfWar_Enabled)
- {
- return false;
- }
- if (index >= 0 && index < cellIndices.NumGridCells)
- {
- return grid[index];
- }
- return false;
- }
+ public override void FinalizeInit()
+ {
+ base.FinalizeInit();
+ asyncActions.Start();
+ }
- public override void MapComponentUpdate()
- {
- if (!initialized)
- {
- initialized = true;
- for (int i = 0; i < grid2d.Length; i++)
- {
- grid2d[i] = new ISection[Mathf.CeilToInt(cellIndices.mapSizeZ / (float)SECTION_SIZE)];
- for (int j = 0; j < grid2d[i].Length; j++)
- {
- grid2d[i][j] = new ISection(this, new Rect(new Vector2(i * SECTION_SIZE, j * SECTION_SIZE), Vector2.one * SECTION_SIZE), mapRect, mesh, fogTex, fogShader);
- }
- }
- asyncActions.EnqueueOffThreadAction(() =>
- {
- OffThreadLoop(0, 0, grid2d.Length, grid2d[0].Length);
- });
- }
- if (!alive || !Finder.Settings.FogOfWar_Enabled)
- {
- return;
- }
- if (Find.CurrentMap != map)
- {
- ready = false;
- return;
- }
- if (!ready)
- {
- sight = map.GetComp_Fast().colonistsAndFriendlies;
- walls = map.GetComp_Fast();
- glow = map.glowGrid;
- ready = sight != null;
- }
- base.MapComponentUpdate();
- if (ready)
- {
- SkyGlow = map.skyManager.CurSkyGlow;
- Rect rect = new Rect();
- CellRect cellRect = Find.CameraDriver.CurrentViewRect;
- rect.xMin = Mathf.Clamp(cellRect.minX - SECTION_SIZE, 0, cellIndices.mapSizeX);
- rect.xMax = Mathf.Clamp(cellRect.maxX + SECTION_SIZE, 0, cellIndices.mapSizeX);
- rect.yMin = Mathf.Clamp(cellRect.minZ - SECTION_SIZE, 0, cellIndices.mapSizeZ);
- rect.yMax = Mathf.Clamp(cellRect.maxZ + SECTION_SIZE, 0, cellIndices.mapSizeZ);
- mapScreenRect = rect;
- //mapScreenRect.ExpandedBy(32, 32);
- asyncActions.ExecuteMainThreadActions();
- zoom = Mathf.CeilToInt(Mathf.Clamp(Find.CameraDriver?.rootPos.y ?? 30, 15, 30f));
- DrawFog(Mathf.FloorToInt(mapScreenRect.xMin / SECTION_SIZE), Mathf.FloorToInt(mapScreenRect.yMin / SECTION_SIZE), Mathf.FloorToInt(mapScreenRect.xMax / SECTION_SIZE), Mathf.FloorToInt(mapScreenRect.yMax / SECTION_SIZE));
- }
- }
+ public bool IsFogged(IntVec3 cell)
+ {
+ return IsFogged(cellIndices.CellToIndex(cell));
+ }
+ public bool IsFogged(int index)
+ {
+ if (!Finder.Settings.FogOfWar_Enabled)
+ {
+ return false;
+ }
+ if (index >= 0 && index < cellIndices.NumGridCells)
+ {
+ return grid[index];
+ }
+ return false;
+ }
- public override void MapRemoved()
- {
- alive = false;
- asyncActions.Kill();
- base.MapRemoved();
- }
+ public override void MapComponentUpdate()
+ {
+ if (!initialized && Finder.Settings.FogOfWar_Enabled)
+ {
+ initialized = true;
+ for (int i = 0; i < grid2d.Length; i++)
+ {
+ grid2d[i] = new ISection[Mathf.CeilToInt(cellIndices.mapSizeZ / (float)SECTION_SIZE)];
+ for (int j = 0; j < grid2d[i].Length; j++)
+ {
+ grid2d[i][j] = new ISection(this, new Rect(new Vector2(i * SECTION_SIZE, j * SECTION_SIZE), Vector2.one * SECTION_SIZE), mapRect, mesh, fogTex, fogShader);
+ }
+ }
+ asyncActions.EnqueueOffThreadAction(() =>
+ {
+ OffThreadLoop(0, 0, grid2d.Length, grid2d[0].Length);
+ });
+ }
+ if (!alive || !Finder.Settings.FogOfWar_Enabled)
+ {
+ return;
+ }
+ if (Find.CurrentMap != map)
+ {
+ ready = false;
+ return;
+ }
+ ticksGame = GenTicks.TicksGame;
+ if (!ready)
+ {
+ sight = map.GetComp_Fast().colonistsAndFriendlies;
+ walls = map.GetComp_Fast();
+ glow = map.glowGrid;
+ ready = sight != null;
+ }
+ base.MapComponentUpdate();
+ if (ready)
+ {
+ SkyGlow = map.skyManager.CurSkyGlow;
+ Rect rect = new Rect();
+ CellRect cellRect = Find.CameraDriver.CurrentViewRect;
+ rect.xMin = Mathf.Clamp(cellRect.minX - SECTION_SIZE, 0, cellIndices.mapSizeX);
+ rect.xMax = Mathf.Clamp(cellRect.maxX + SECTION_SIZE, 0, cellIndices.mapSizeX);
+ rect.yMin = Mathf.Clamp(cellRect.minZ - SECTION_SIZE, 0, cellIndices.mapSizeZ);
+ rect.yMax = Mathf.Clamp(cellRect.maxZ + SECTION_SIZE, 0, cellIndices.mapSizeZ);
+ mapScreenRect = rect;
+ //mapScreenRect.ExpandedBy(32, 32);
+ asyncActions.ExecuteMainThreadActions();
+ zoom = Mathf.CeilToInt(Mathf.Clamp(Find.CameraDriver?.rootPos.y ?? 30, 15, 30f));
+ DrawFog(Mathf.FloorToInt(mapScreenRect.xMin / SECTION_SIZE), Mathf.FloorToInt(mapScreenRect.yMin / SECTION_SIZE), Mathf.FloorToInt(mapScreenRect.xMax / SECTION_SIZE), Mathf.FloorToInt(mapScreenRect.yMax / SECTION_SIZE));
+ }
+ }
- private void DrawFog(int minU, int minV, int maxU, int maxV)
- {
- maxU = Mathf.Clamp(Maths.Max(maxU, minU + 1), 0, grid2d.Length - 1);
- minU = Mathf.Clamp(minU - 1, 0, grid2d.Length - 1);
- maxV = Mathf.Clamp(Maths.Max(maxV, minV + 1), 0, grid2d[0].Length - 1);
- minV = Mathf.Clamp(minV - 1, 0, grid2d[0].Length - 1);
- bool update = updateNum % 2 == 0;
- bool updateForced = updateNum % 4 == 0;
- float color = Finder.Settings.FogOfWar_FogColor;
- for (int u = minU; u <= maxU; u++)
- {
- for (int v = minV; v <= maxV; v++)
- {
- ISection section = grid2d[u][v];
- if (section.s_color != color)
- {
- section.ApplyColor();
- }
- if (updateForced || update && section.dirty)
- {
- section.ApplyFogged();
- }
- section.Draw(mapScreenRect);
- }
- }
- updateNum++;
- }
+ public void RevealSpot(IntVec3 cell, float radius, int duration)
+ {
+ ITempSpot spot = new ITempSpot();
+ spot.center = cell;
+ spot.radius = Maths.Max(radius, 1f) * Finder.Settings.FogOfWar_RangeMultiplier;
+ spot.duration = duration;
+ lock (_lockerSpots)
+ {
+ spotsQueued.Add(spot);
+ }
+ }
- private void OffThreadLoop(int minU, int minV, int maxU, int maxV)
- {
- Stopwatch stopwatch = new Stopwatch();
- while (alive)
- {
- stopwatch.Restart();
- if (ready && Finder.Settings.FogOfWar_Enabled)
- {
- for (int u = minU; u < maxU; u++)
- {
- for (int v = minV; v < maxV; v++)
- {
- grid2d[u][v].Update();
- }
- }
- }
- stopwatch.Stop();
- float t = 0.064f - (float)stopwatch.ElapsedTicks / Stopwatch.Frequency;
- if (t > 0f)
- {
- Thread.Sleep(Mathf.CeilToInt(t * 1000));
- }
- }
- }
+ public override void MapRemoved()
+ {
+ alive = false;
+ asyncActions.Kill();
+ base.MapRemoved();
+ }
- private class ISection
- {
- public readonly float[] cells;
- private readonly MapComponent_FogGrid comp;
- private readonly Material mat;
- private readonly Mesh mesh;
- private readonly Vector3 pos;
- public bool dirty = true;
- public Rect rect;
- public float s_color;
+ private void DrawFog(int minU, int minV, int maxU, int maxV)
+ {
+ maxU = Mathf.Clamp(Maths.Max(maxU, minU + 1), 0, grid2d.Length - 1);
+ minU = Mathf.Clamp(minU - 1, 0, grid2d.Length - 1);
+ maxV = Mathf.Clamp(Maths.Max(maxV, minV + 1), 0, grid2d[0].Length - 1);
+ minV = Mathf.Clamp(minV - 1, 0, grid2d[0].Length - 1);
+ bool update = updateNum % 2 == 0;
+ bool updateForced = updateNum % 4 == 0;
+ float color = Finder.Settings.FogOfWar_FogColor;
+ for (int u = minU; u <= maxU; u++)
+ {
+ for (int v = minV; v <= maxV; v++)
+ {
+ ISection section = grid2d[u][v];
+ if (section.s_color != color)
+ {
+ section.ApplyColor();
+ }
+ if (updateForced || update && section.dirty)
+ {
+ section.ApplyFogged();
+ }
+ section.Draw(mapScreenRect);
+ }
+ }
+ updateNum++;
+ }
- public ISection(MapComponent_FogGrid comp, Rect rect, Rect mapRect, Mesh mesh, Texture2D tex, Shader shader)
- {
- this.comp = comp;
- this.rect = rect;
- this.mesh = mesh;
- pos = new Vector3(rect.position.x, 8, rect.position.y);
- mat = new Material(shader);
- mat.SetVector("_Color", new Vector4(0.1f, 0.1f, 0.1f, 0.8f));
- mat.SetTexture("_Tex", tex);
- cells = new float[256];
- cells.Initialize();
- mat.SetFloatArray("_Fog", cells);
- }
+ private void OffThreadLoop(int minU, int minV, int maxU, int maxV)
+ {
+ Stopwatch stopwatch = new Stopwatch();
+ List spots = new List