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(); + while (alive) + { + stopwatch.Restart(); + if (ready && Finder.Settings.FogOfWar_Enabled) + { + lock (_lockerSpots) + { + if (spotsQueued.Count > 0) + { + spots.AddRange(spotsQueued); + spotsQueued.Clear(); + } + } + if (spots.Count > 0) + { + int ticks = ticksGame; + while (spots.Count > 0) + { + ITempSpot spot = spots.Pop(); + Action setAction = (cell, carry, dist, coverRating) => + { + if (cell.InBounds(map)) + { + int u = cell.x / SECTION_SIZE; + int v = cell.z / SECTION_SIZE; + ISection section = grid2d[u][v]; + if (section != null) + { + ITempCell tCell = new ITempCell(); + tCell.u = (byte)(cell.x % SECTION_SIZE); + tCell.v = (byte)(cell.z % SECTION_SIZE); + tCell.val = Mathf.Clamp01(1f - cell.DistanceTo_Fast(spot.center) / spot.radius); + tCell.timestamp = GenTicks.TicksGame; + tCell.duration = (short)spot.duration; + section.extraCells.Add(tCell); + } + } + }; + setAction(spot.center, 0, 0, 0); + ShadowCastingUtility.CastWeighted(map, spot.center, setAction, Mathf.CeilToInt(spot.radius), 16, spotBuffer); + } + } + for (int u = minU; u < maxU; u++) + { + for (int v = minV; v < maxV; v++) + { + grid2d[u][v].Update(); + } + } + stopwatch.Stop(); + float t = 0.016f - (float)stopwatch.ElapsedTicks / Stopwatch.Frequency; + if (t > 0f) + { + Thread.Sleep(Mathf.CeilToInt(t * 1000)); + } + } + else + { + Thread.Sleep(100); + } + } + } - public void ApplyColor() - { - mat.SetVector("_Color", new Vector4(0.1f, 0.1f, 0.1f, s_color = Finder.Settings.FogOfWar_FogColor)); - } + private class ISection + { + public readonly float[] cells; + private readonly MapComponent_FogGrid comp; + public readonly List extraCells; + private readonly Material mat; + private readonly Mesh mesh; + private readonly Vector3 pos; + public bool dirty = true; + public Rect rect; + public float s_color; - public void ApplyFogged() - { - mat.SetFloatArray("_Fog", cells); - dirty = false; - } + public ISection(MapComponent_FogGrid comp, Rect rect, Rect mapRect, Mesh mesh, Texture2D tex, Shader shader) + { + extraCells = new List(); + this.comp = comp; + this.rect = rect; + this.mesh = mesh; + pos = new Vector3(rect.position.x, AltitudeLayer.MapDataOverlay.AltitudeFor() , 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); + } - public void Update() - { - CellIndices indices = comp.map?.cellIndices; - if (indices == null) - { - return; - } - int numGridCells = indices.NumGridCells; - WallGrid walls = comp.walls; - ITFloatGrid fogGrid = comp.sight.gridFog; - IntVec3 pos = this.pos.ToIntVec3(); - IntVec3 loc; + public void ApplyColor() + { + mat.SetVector("_Color", new Vector4(0.1f, 0.1f, 0.1f, s_color = Finder.Settings.FogOfWar_FogColor)); + } - ColorInt[] glowGrid = comp.glow.glowGrid; - float glowSky = comp.SkyGlow; - bool changed = false; - if (fogGrid != null) - { - for (int x = 0; x < SECTION_SIZE; x++) - { - for (int z = 0; z < SECTION_SIZE; z++) - { - int index = indices.CellToIndex(loc = pos + new IntVec3(x, 0, z)); - if (index >= 0 && index < numGridCells) - { - float val; - float old = cells[x * SECTION_SIZE + z]; - if (!walls.CanBeSeenOver(index)) - { - val = 0.5f; - comp.grid[index] = false; - } - else - { - float visRLimit = 0; - float visibility = fogGrid.Get(index); - if (glowSky < 1) - { - ColorInt glow = glowGrid[index]; - visRLimit = Mathf.Lerp(0, 0.5f, 1 - Maths.Max(Mathf.Clamp01(Maths.Max(glow.r, glow.g, glow.b) / 255f * 3.6f), glowSky)); - } - if (visibility <= visRLimit + 1e-3f) - { - comp.grid[index] = true; - } - else - { - comp.grid[index] = false; - } - val = Maths.Max(1 - visibility, 0); - } - if (old != val) - { - changed = true; - if (val > old) - { - cells[x * SECTION_SIZE + z] = Maths.Min(old + 0.1f, val); - } - else - { - cells[x * SECTION_SIZE + z] = Maths.Max(old - 0.4f, val); - } - } - } - else - { - cells[x * SECTION_SIZE + z] = 0f; - } - } - } - } - dirty = changed; - } + public void ApplyFogged() + { + mat.SetFloatArray("_Fog", cells); + dirty = false; + } - public void Draw(Rect screenRect) - { - GenDraw.DrawMeshNowOrLater(mesh, pos, Quaternion.identity, mat, false); - } - } - } + public void Update() + { + CellIndices indices = comp.map?.cellIndices; + if (indices == null) + { + return; + } + int numGridCells = indices.NumGridCells; + WallGrid walls = comp.walls; + ITFloatGrid fogGrid = comp.sight.gridFog; + IntVec3 pos = this.pos.ToIntVec3(); + IntVec3 loc; + + ColorInt[] glowGrid = comp.glow.glowGrid; + float glowSky = comp.SkyGlow; + bool changed = false; + + void SetCell(int x, int z, float glowOffset, float visibilityOffset, bool allowLowerValues) + { + int index = indices.CellToIndex(loc = pos + new IntVec3(x, 0, z)); + if (index >= 0 && index < numGridCells) + { + float old = cells[x * SECTION_SIZE + z]; + bool isWall = !walls.CanBeSeenOver(index); + float visRLimit = 0; + float visibility = fogGrid.Get(index); + float visibilityAdj = 0; + for (int i = 0; i < 9; i++) + { + int adjIndex = index + indices.mapSizeX * (i / 3 - 1) + i % 3 - 1; + if (adjIndex >= 0 && adjIndex < numGridCells && (isWall || walls.CanBeSeenOver(adjIndex))) + { + visibilityAdj += fogGrid.Get(adjIndex); + } + } + visibility = Maths.Max(visibilityAdj / 9, visibility) + visibilityOffset; + if (glowSky < 1) + { + ColorInt glow = glowGrid[index]; + visRLimit = Mathf.Lerp(0, 0.5f, 1 - (Maths.Max(!isWall ? 1f : Mathf.Clamp01(Maths.Max(glow.r, glow.g, glow.b) / 255f * 3.6f), glowSky) + glowOffset)); + } + float val = Maths.Max(1 - visibility, 0); + if (allowLowerValues || old >= val) + { + if (visibility <= visRLimit + 1e-3f) + { + comp.grid[index] = true; + } + else + { + comp.grid[index] = false; + } + } + if (old != val) + { + changed = true; + if (allowLowerValues) + { + if (val > old) + { + cells[x * SECTION_SIZE + z] = Maths.Min(old + 0.5f, val); + } + else + { + cells[x * SECTION_SIZE + z] = Maths.Max(old - 0.5f, val); + } + } + else + { + cells[x * SECTION_SIZE + z] = Maths.Min(old, val); + } + } + } + else + { + cells[x * SECTION_SIZE + z] = 0f; + } + } + + if (fogGrid != null) + { + for (int x = 0; x < SECTION_SIZE; x++) + { + for (int z = 0; z < SECTION_SIZE; z++) + { + SetCell(x, z, 0, 0, true); + } + } + int ticks = comp.ticksGame; + int i = 0; + while (i < extraCells.Count) + { + ITempCell tCell = extraCells[i]; + if (tCell.timestamp + tCell.duration < ticks) + { + extraCells.RemoveAt(i); + changed = true; + continue; + } + i++; + float fade = Mathf.Lerp(0.7f, 1.0f, 1f - (float)(GenTicks.TicksGame - tCell.timestamp) / tCell.duration); + SetCell(tCell.u, tCell.v, 0.5f * fade * tCell.val, fade * tCell.val, false); + } + } + dirty = changed; + } + + public void Draw(Rect screenRect) + { + GenDraw.DrawMeshNowOrLater(mesh, pos, Quaternion.identity, mat, false); + } + } + + private struct ITempSpot : IEquatable + { + public IntVec3 center; + public float radius; + public int duration; + + public override bool Equals(object obj) + { + return obj is ITempSpot other && other.Equals(this); + } + + public bool Equals(ITempSpot other) + { + return center == other.center; + } + + public override int GetHashCode() + { + return center.GetHashCode(); + } + } + + private struct ITempCell + { + public byte u; + public byte v; + public float val; + public int timestamp; + public short duration; + } + } } diff --git a/Source/Rule56/MetaCombatAttributeUtility.cs b/Source/Rule56/MetaCombatAttributeUtility.cs index 2c18aac..192d91a 100644 --- a/Source/Rule56/MetaCombatAttributeUtility.cs +++ b/Source/Rule56/MetaCombatAttributeUtility.cs @@ -102,12 +102,8 @@ public static MetaCombatAttribute Sum(this IEnumerable attr public static void ClearCache() { - w_race.Clear(); - w_kind.Clear(); - w_pawn.Clear(); - s_race.Clear(); - s_kind.Clear(); - s_pawn.Clear(); + w_pawn.Clear(); + s_pawn.Clear(); } } } diff --git a/Source/Rule56/Mods/Mod_Vehicles.cs b/Source/Rule56/Mods/Mod_Vehicles.cs new file mode 100644 index 0000000..46e4514 --- /dev/null +++ b/Source/Rule56/Mods/Mod_Vehicles.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.CompilerServices; +using Verse; +namespace CombatAI +{ + [LoadIf("SmashPhil.VehicleFramework")] + public class Mod_Vehicles + { + public static bool active; + + [LoadNamed("Vehicles.VehiclePawn")] + public static Type Vehicle; + + [RunIf(loaded: true)] + private static void OnActive() + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsVehicle(Pawn pawn) + { + return active && (Vehicle?.IsInstanceOfType(pawn) ?? false); + } + } +} diff --git a/Source/Rule56/Mods/Mod_ZombieLand.cs b/Source/Rule56/Mods/Mod_ZombieLand.cs new file mode 100644 index 0000000..4876cd9 --- /dev/null +++ b/Source/Rule56/Mods/Mod_ZombieLand.cs @@ -0,0 +1,48 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using HarmonyLib; +using RimWorld; +using UnityEngine; +using Verse; +namespace CombatAI +{ + [LoadIf("brrainz.zombieland")] + public class Mod_ZombieLand + { + public static bool active; + + [LoadNamed("ZombieLand.Zombie")] + public static Type Zombie; + [LoadNamed("ZombieLand.Zombie:Render", LoadableType.Method)] + public static MethodInfo Zombie_Render; + + [RunIf(loaded: true)] + private static void OnActive() + { + Finder.Harmony.Patch(Zombie_Render, prefix: new HarmonyMethod(AccessTools.Method(typeof(Zombie_Patch), nameof(Zombie_Patch.Render)))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsZombie(Pawn pawn) + { + return active && (Zombie?.IsInstanceOfType(pawn) ?? false); + } + + private class Zombie_Patch + { + public static bool Render(Pawn __instance) + { + if (Finder.Settings.FogOfWar_Enabled && __instance.Spawned) + { + MapComponent_FogGrid grid = __instance.Map.GetComp_Fast(); + if (grid != null) + { + return !grid.IsFogged(__instance.Position); + } + } + return true; + } + } + } +} diff --git a/Source/Rule56/Patches/AttackTargetFinder_Patch.cs b/Source/Rule56/Patches/AttackTargetFinder_Patch.cs index 6e7178e..109ce91 100644 --- a/Source/Rule56/Patches/AttackTargetFinder_Patch.cs +++ b/Source/Rule56/Patches/AttackTargetFinder_Patch.cs @@ -1,183 +1,137 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using HarmonyLib; +using JetBrains.Annotations; using RimWorld; using UnityEngine; using Verse; using Verse.AI; namespace CombatAI.Patches { - internal static class AttackTargetFinder_Patch - { - private static Map map; - private static Pawn searcherPawn; - private static Faction searcherFaction; - private static Verb searcherVerb; - private static float dmg05; - private static ProjectileProperties projectile; - private static SightTracker.SightReader sightReader; - private static DamageReport damageReport; - private static readonly Dictionary distDict = new Dictionary(256); + internal static class AttackTargetFinder_Patch + { + private static Map map; + private static Pawn searcherPawn; + private static Faction searcherFaction; + private static SightTracker.SightReader sightReader; + private static DamageReport damageReport; + private static readonly Dictionary distDict = new Dictionary(256); - [HarmonyPatch(typeof(AttackTargetFinder), nameof(AttackTargetFinder.BestAttackTarget))] - internal static class AttackTargetFinder_BestAttackTarget_Patch - { - internal static void Prefix(IAttackTargetSearcher searcher) - { - map = searcher.Thing?.Map; + public static void ClearCache() + { + distDict.Clear(); + } - if (searcher.Thing is Pawn pawn && !pawn.RaceProps.Animal) - { - distDict.Clear(); - damageReport = DamageUtility.GetDamageReport(searcher.Thing); - searcherPawn = pawn; - searcherVerb = pawn.CurrentEffectiveVerb; - pawn.TryGetSightReader(out sightReader); - if (sightReader != null) - { - int num = 0; - TraverseParms traverse = new TraverseParms(); - traverse.pawn = pawn; - traverse.canBashDoors = pawn.HostileTo(pawn.Map.ParentFaction) && pawn.RaceProps.Humanlike; - traverse.canBashFences = traverse.canBashDoors; - traverse.mode = traverse.canBashDoors ? TraverseMode.PassDoors : TraverseMode.ByPawn; - traverse.maxDanger = Danger.Deadly; - Func action = (region, score, depth) => - { - if (sightReader.GetRegionAbsVisibilityToEnemies(region) > 0) - { - List things = region.ListerThings.ThingsInGroup(ThingRequestGroup.Pawn); - score = Maths.Min(score, 45); - for (int i = 0; i < things.Count; i++) - { - if (things[i] != null) - { - num++; - distDict[things[i].thingIDNumber] = score; - } - } - } - return num >= 32; - }; - Func cost = region => - { - return Maths.Min(sightReader.GetRegionAbsVisibilityToEnemies(region), 8) * 10; - }; - RegionFlooder.Flood(pawn.Position, IntVec3.Invalid, pawn.Map, action, null, cost, maxRegions: 150, traverseParms:traverse); - } - if (searcherVerb != null && !searcherVerb.IsMeleeAttack && (projectile = searcherVerb.GetProjectile()?.projectile ?? null) != null) - { - dmg05 = projectile.damageAmountBase / 2f; - } - } - searcherFaction = searcher.Thing?.Faction ?? null; - } + [HarmonyPatch(typeof(AttackTargetFinder), nameof(AttackTargetFinder.BestAttackTarget))] + internal static class AttackTargetFinder_BestAttackTarget_Patch + { + internal static void Prefix(IAttackTargetSearcher searcher) + { + map = searcher.Thing?.Map; - internal static void Postfix() - { - map = null; - damageReport = default(DamageReport); - searcherPawn = null; - sightReader = null; - distDict.Clear(); - } - } + if (searcher.Thing is Pawn pawn && !pawn.RaceProps.Animal) + { + distDict.Clear(); + damageReport = DamageUtility.GetDamageReport(searcher.Thing); + searcherPawn = pawn; + pawn.TryGetSightReader(out sightReader); + } + searcherFaction = searcher.Thing?.Faction ?? null; + } - [HarmonyPatch(typeof(AttackTargetFinder), nameof(AttackTargetFinder.GetShootingTargetScore))] - internal static class Harmony_AttackTargetFinder_GetShootingTargetScore - { - internal static IEnumerable Transpiler(IEnumerable instructions) - { - List codes = instructions.ToList(); - bool finished = false; - for (int i = 0; i < instructions.Count(); i++) - { - if (!finished) - { - if (codes[i].opcode == OpCodes.Ldc_R4) - { - finished = true; - yield return new CodeInstruction(OpCodes.Ldarg_0); - yield return new CodeInstruction(OpCodes.Ldarg_1); - yield return new CodeInstruction(OpCodes.Ldarg_2); - yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Harmony_AttackTargetFinder_GetShootingTargetScore), nameof(GetTargetBaseScore))); - continue; - } - } - yield return codes[i]; - } - } + internal static void Postfix() + { + map = null; + damageReport = default(DamageReport); + searcherPawn = null; + sightReader = null; + distDict.Clear(); + } + } - public static float GetTargetBaseScore(IAttackTarget target, IAttackTargetSearcher searcher, Verb verb) - { - float result = 60f; - if (Finder.Settings.Targeter_Enabled && sightReader != null && searcherPawn != null) - { - if (distDict.TryGetValue(target.Thing.thingIDNumber, out float intCost)) - { - result += 45 - intCost; - } - else if (target.Thing is Building_Turret) - { - result += 22; - } - if (verb.IsMeleeAttack || verb.EffectiveRange <= 15) - { - if (sightReader.GetAbsVisibilityToEnemies(target.Thing.Position) > sightReader.GetAbsVisibilityToFriendlies(target.Thing.Position) + 1) - { - result -= 30f * Finder.P50; - } - if (sightReader.GetVisibilityToEnemies(target.Thing.Position) > 3) - { - result -= 15f * Finder.P50; - } - result += Maths.Sqrt_Fast(sightReader.GetEnemyDirection(target.Thing.Position).sqrMagnitude, 4); - } - if (target.Thing is Pawn enemy) - { - if (damageReport.IsValid) - { - ArmorReport armorReport = enemy.GetArmorReport(); - float diff = 0f; - if (Mod_CE.active) - { - diff = Mathf.Clamp01(Maths.Max(damageReport.adjustedBlunt / (armorReport.Blunt + 1f), damageReport.adjustedSharp / (armorReport.Sharp + 1f), 0f)); - } - else - { - diff = Mathf.Clamp01(1 - Maths.Max(armorReport.Blunt - damageReport.adjustedBlunt, armorReport.Sharp - damageReport.adjustedSharp, 0f)); - } - result += 8f * diff; - } - Thing targeted; - if (enemy.stances?.stagger != null && enemy.stances.stagger.Staggered) - { - result += 12; - } - if (!verb.IsMeleeAttack) - { - if (enemy.CurrentEffectiveVerb?.IsMeleeAttack ?? false) - { - if (searcherFaction != null && (targeted = enemy.jobs?.curJob?.targetA.Thing)?.Faction == searcherFaction) - { - result += 8 + enemy.Position.DistanceToSquared(targeted.Position) < 150 ? 16 : 0; - } - } - if (enemy.pather?.MovingNow ?? false) - { - result += 16; - } - } - if (!enemy.HasWeaponVisible()) - { - result -= 8; - } - } - } - return result; - } - } - } + [HarmonyPatch(typeof(AttackTargetFinder), nameof(AttackTargetFinder.GetShootingTargetScore))] + internal static class Harmony_AttackTargetFinder_GetShootingTargetScore + { + internal static IEnumerable Transpiler(IEnumerable instructions) + { + List codes = instructions.ToList(); + bool finished = false; + for (int i = 0; i < instructions.Count(); i++) + { + if (!finished) + { + if (codes[i].opcode == OpCodes.Ldc_R4) + { + finished = true; + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Ldarg_2); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Harmony_AttackTargetFinder_GetShootingTargetScore), nameof(GetTargetBaseScore))); + continue; + } + } + yield return codes[i]; + } + } + + public static float GetTargetBaseScore(IAttackTarget target, IAttackTargetSearcher searcher, Verb verb) + { + float result = 60f; + if (Finder.Settings.Targeter_Enabled && sightReader != null && searcherPawn != null) + { + if (target.Thing is Building_Turret) + { + result += 22; + } + if (target.Thing is Pawn enemy) + { + if (damageReport.IsValid) + { + ArmorReport armorReport = enemy.GetArmorReport(); + float diff; + if (Mod_CE.active) + { + diff = Mathf.Clamp01(Maths.Max(damageReport.adjustedBlunt / (armorReport.Blunt + 1f), damageReport.adjustedSharp / (armorReport.Sharp + 1f), 0f)); + } + else + { + diff = Mathf.Clamp01(1 - Maths.Max(armorReport.Blunt - damageReport.adjustedBlunt, armorReport.Sharp - damageReport.adjustedSharp, 0f)); + } + result += 8f * diff; + } + if (!TKVCache.TryGet(enemy.thingIDNumber, out int offset, 45)) + { + offset = 0; + if (enemy.stances?.stagger != null && enemy.stances.stagger.Staggered) + { + offset += 12; + } + DamageReport enemyReport = DamageUtility.GetDamageReport(enemy); + if (enemyReport is { IsValid: true, primaryIsRanged: false }) + { + Thing targeted; + if (searcherFaction != null && (targeted = enemy.jobs?.curJob?.targetA.Thing)?.Faction == searcherFaction) + { + offset += 8 + enemy.Position.DistanceToSquared(targeted.Position) < 150 ? 16 : 0; + } + } + if (enemy.pather?.MovingNow ?? false) + { + offset += 16; + } + TKVCache.Put(enemy.thingIDNumber, offset); + } + result += offset; + } + } + return result; + } + } + + [UsedImplicitly] + private sealed class AttackTargetFinderCache + { + } + } } diff --git a/Source/Rule56/Patches/CastPositionFinder_Patch.cs b/Source/Rule56/Patches/CastPositionFinder_Patch.cs index f51629b..3c6affe 100644 --- a/Source/Rule56/Patches/CastPositionFinder_Patch.cs +++ b/Source/Rule56/Patches/CastPositionFinder_Patch.cs @@ -156,10 +156,10 @@ private static void FloodCellRect(CellRect rect) map.GetCellFlooder().Flood(root, node => { - float val = (node.dist - node.distAbs) / (node.distAbs + 1f) * 2 + (sightReader.GetVisibilityToEnemies(node.cell) - rootVis) * 0.25f + Maths.Min(avoidanceReader.GetProximity(node.cell), 4f) - Maths.Min(avoidanceReader.GetDanger(node.cell), 1f) - interceptors.grid.Get(node.cell) * 4 + (sightReader.GetThreat(node.cell) - rootThreat) * 0.5f; + float val = (node.dist - node.distAbs) / (node.distAbs + 1f) * 2 + (sightReader.GetVisibilityToEnemies(node.cell) - rootVis) * 0.5f + Maths.Min(avoidanceReader.GetProximity(node.cell), 2f) + Maths.Min(avoidanceReader.GetDanger(node.cell), 1f) - interceptors.grid.Get(node.cell) * 4 + (sightReader.GetThreat(node.cell) - rootThreat) * 0.5f; if (rootDutyDestDist > 0) { - val += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 3) - rootDutyDestDist) * 0.25f, -2f, 2f); + val += Mathf.Clamp((Maths.Sqrt_Fast(dutyDest.DistanceToSquared(node.cell), 3) - rootDutyDestDist) * 0.25f, -0.5f, 0.5f); } if (effectiveRange > 0) { @@ -169,17 +169,7 @@ private static void FloodCellRect(CellRect rect) }, cell => { - //Vector2 dir = sightReader.GetEnemyDirection(cell); - //IntVec3 adjustedLoc; - //if (dir.sqrMagnitude < 4) - //{ - // adjustedLoc = targetPosition; - //} - //else - //{ - // adjustedLoc = cell + new IntVec3((int)dir.x, 0, (int)dir.y); - //} - return (sightReader.GetVisibilityToEnemies(cell) - rootVis) * 2 - interceptors.grid.Get(cell) + (sightReader.GetThreat(cell) - rootThreat) * 0.25f; + return (sightReader.GetVisibilityToEnemies(cell) - rootVis) * 2.5f - interceptors.grid.Get(cell) + (sightReader.GetThreat(cell) - rootThreat) * 0.25f; }, cell => { diff --git a/Source/Rule56/Patches/Deprecated/Thing_Patch.cs b/Source/Rule56/Patches/Deprecated/Thing_Patch.cs index 89f49c6..3980666 100644 --- a/Source/Rule56/Patches/Deprecated/Thing_Patch.cs +++ b/Source/Rule56/Patches/Deprecated/Thing_Patch.cs @@ -1,10 +1,15 @@ -#if DEBUG_REACTION -using CombatAI.Utilities; -#endif - -namespace CombatAI.Patches +namespace CombatAI.Patches.Debug { #if DEBUG_REACTION + using HarmonyLib; + using RimWorld; + using System; + using Verse; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Reflection.Emit; + using CombatAI.Utilities; public static class Thing_Patch { [HarmonyPatch(typeof(Thing), nameof(Thing.Position), MethodType.Setter)] diff --git a/Source/Rule56/Patches/FleckMakerCE_Patch.cs b/Source/Rule56/Patches/FleckMakerCE_Patch.cs new file mode 100644 index 0000000..61dd0ec --- /dev/null +++ b/Source/Rule56/Patches/FleckMakerCE_Patch.cs @@ -0,0 +1,44 @@ +using System.Reflection; +using HarmonyLib; +using RimWorld; +using Verse; +namespace CombatAI.Patches +{ + public static class FleckMakerCE_Patch + { + public static IntVec3 Current = IntVec3.Invalid; + [HarmonyPatch] + private static class FleckMakerCE_Static_Patch + { + private static MethodBase mStatic = AccessTools.Method("FleckMakerCE:Static", new []{typeof(IntVec3), typeof(Map), typeof(FleckDef), typeof(float)}); + + public static bool Prepare() + { + return mStatic != null; + } + + public static MethodBase TargetMethod() + { + return mStatic; + } + + public static void Prefix(IntVec3 cell, Map map, FleckDef fleckDef, float scale) + { + if (Finder.Settings.FogOfWar_Enabled && fleckDef == FleckDefOf.ShotFlash) + { + MapComponent_FogGrid grid = map.GetComp_Fast(); + if (grid != null) + { + Current = cell; + grid.RevealSpot(cell, Maths.Max(scale, 3f), Rand.Range(120, 240)); + } + } + } + + public static void Postfix() + { + Current = IntVec3.Invalid; + } + } + } +} diff --git a/Source/Rule56/Patches/FleckMaker_Patch.cs b/Source/Rule56/Patches/FleckMaker_Patch.cs index f1c9a5e..c8a0716 100644 --- a/Source/Rule56/Patches/FleckMaker_Patch.cs +++ b/Source/Rule56/Patches/FleckMaker_Patch.cs @@ -5,6 +5,22 @@ namespace CombatAI.Patches { public static class FleckMaker_Patch { + [HarmonyPatch(typeof(FleckMaker), nameof(FleckMaker.Static), new []{typeof(IntVec3), typeof(Map), typeof(FleckDef), typeof(float)})] + private static class FleckMaker_Static_Patch + { + public static void Prefix(IntVec3 cell, Map map, FleckDef fleckDef, float scale) + { + if (Finder.Settings.FogOfWar_Enabled && fleckDef == FleckDefOf.ShotFlash && cell != FleckMakerCE_Patch.Current) + { + MapComponent_FogGrid grid = map.GetComp_Fast(); + if (grid != null) + { + grid.RevealSpot(cell, Maths.Max(scale, 3f), Rand.Range(120, 240)); + } + } + } + } + [HarmonyPatch(typeof(FleckMaker), nameof(FleckMaker.ThrowMetaIcon))] private static class FleckMaker_ThrowMetaIcon_Patch { diff --git a/Source/Rule56/Patches/Game_Patch.cs b/Source/Rule56/Patches/Game_Patch.cs index 768cc84..efc073c 100644 --- a/Source/Rule56/Patches/Game_Patch.cs +++ b/Source/Rule56/Patches/Game_Patch.cs @@ -12,6 +12,7 @@ private static class Game_DeinitAndRemoveMap_Patch public static void Prefix(Map map) { CompCache.Notify_MapRemoved(map); + CacheUtility.ClearAllCache(mapRemoved: true); } } @@ -20,16 +21,7 @@ private static class Game_ClearCaches_Patch { public static void Prefix() { - TCacheHelper.ClearCache(); - StatCache.ClearCache(); - CompCache.ClearCaches(); - SightUtility.ClearCache(); - JobGiver_AITrashBuildingsDistant_Patch.ClearCache(); - GenSight_Patch.ClearCache(); - ArmorUtility.ClearCache(); - DamageUtility.ClearCache(); - MetaCombatAttributeUtility.ClearCache(); - LordToil_AssaultColony_Patch.ClearCache(); + CacheUtility.ClearAllCache(); } } diff --git a/Source/Rule56/Patches/GridsUtility_Patch.cs b/Source/Rule56/Patches/GridsUtility_Patch.cs new file mode 100644 index 0000000..304042c --- /dev/null +++ b/Source/Rule56/Patches/GridsUtility_Patch.cs @@ -0,0 +1,17 @@ +using HarmonyLib; +using Verse; +namespace CombatAI.Patches +{ + [HarmonyPatch(typeof(GridsUtility), nameof(GridsUtility.Fogged), new []{typeof(IntVec3), typeof(Map)})] + public class GridsUtility_Patch + { + private static bool Prefix(Map map, ref bool __result) + { + if (map == null) + { + return __result = false; + } + return true; + } + } +} diff --git a/Source/Rule56/Patches/JobDriver_FollowClose_Patch.cs b/Source/Rule56/Patches/JobDriver_FollowClose_Patch.cs new file mode 100644 index 0000000..150877a --- /dev/null +++ b/Source/Rule56/Patches/JobDriver_FollowClose_Patch.cs @@ -0,0 +1,48 @@ +using CombatAI.Comps; +using HarmonyLib; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.AI; +namespace CombatAI.Patches +{ + public static class JobDriver_FollowClose_Patch + { +// [HarmonyPatch(typeof(JobDriver_FollowClose), nameof(JobDriver_FollowClose.NearFollowee))] +// private static class JobDriver_FollowClose_NearFollowee_Patch +// { +// public static bool Prefix(Pawn follower, Pawn followee, float radius, ref bool __result) +// { +// if (follower.mindState != null && (follower.mindState.duty.Is(CombatAI_DutyDefOf.CombatAI_Escort) || follower.mindState.duty.Is(DutyDefOf.Escort))) +// { +// __result = NearFollowee(follower, followee, follower.Position, radius); +// return false; +// } +// return true; +// } +// } +// +// public static bool NearFollowee(Pawn follower, Pawn followee, IntVec3 locus, float radius) +// { +//// if (radius <= 15) +//// { +//// ThingComp_CombatAI comp = follower.AI(); +//// if (comp?.data != null) +//// { +//// if (!comp.data.AllAllies.Contains(followee)) +//// { +//// return false; +//// } +//// } +//// } +// float speedMul = Mathf.Clamp(followee.GetStatValue_Fast(StatDefOf.MoveSpeed, 3600) / (follower.GetStatValue_Fast(StatDefOf.MoveSpeed, 3600) + 0.1f), 0.5f, 2); +// float adjustedRadius = radius * 1.2f; +// IntVec3 shiftedPos = PawnPathUtility.GetMovingShiftedPosition(followee, 60 * speedMul * 3); +// if (shiftedPos.HeuristicDistanceTo(locus, followee.Map, Mathf.CeilToInt(radius / 12f + 2)) >= adjustedRadius) +// { +// return false; +// } +// return true; +// } + } +} diff --git a/Source/Rule56/Patches/JobGiver_AITrashBuildingsDistant_Patch.cs b/Source/Rule56/Patches/JobGiver_AITrashBuildingsDistant_Patch.cs index ed3fe47..0e83792 100644 --- a/Source/Rule56/Patches/JobGiver_AITrashBuildingsDistant_Patch.cs +++ b/Source/Rule56/Patches/JobGiver_AITrashBuildingsDistant_Patch.cs @@ -23,6 +23,10 @@ public static bool Prefix(Pawn pawn) { return false; } + if (pawn.TryGetSightReader(out SightTracker.SightReader reader) && reader.GetVisibilityToEnemies(pawn.Position) > 0) + { + return false; + } return true; } diff --git a/Source/Rule56/Patches/LordToil_AssaultColony_Patch.cs b/Source/Rule56/Patches/LordToil_AssaultColony_Patch.cs index befc7f1..2ac0780 100644 --- a/Source/Rule56/Patches/LordToil_AssaultColony_Patch.cs +++ b/Source/Rule56/Patches/LordToil_AssaultColony_Patch.cs @@ -8,6 +8,11 @@ namespace CombatAI.Patches { public static class LordToil_AssaultColony_Patch { + private static readonly List rangedLong = new List(); + private static readonly List rangedShort = new List(); + private static readonly List> ranged = new List>(); + private static readonly List melee = new List(); + private static readonly List[] forces = new List[10]; private static readonly List things = new List(); private static readonly List thingsImportant = new List(); @@ -31,6 +36,10 @@ public static void ClearCache() { zones.Clear(); things.Clear(); + rangedLong.Clear(); + rangedShort.Clear(); + ranged.Clear(); + melee.Clear(); thingsImportant.Clear(); forces[0].Clear(); forces[1].Clear(); @@ -135,7 +144,7 @@ public static void Postfix(LordToil_AssaultColony __instance) if (Rand.Chance(0.33f)) { Pawn_CustomDutyTracker.CustomPawnDuty customDuty2 = CustomDutyUtility.DefendPoint(zone.Position, Rand.Range(30, 60), true, 3600 + Rand.Range(0, 60000)); - force[j].EnqueueFirstCustomDuty(customDuty); + force[j].EnqueueFirstCustomDuty(customDuty2); if (Finder.Settings.Debug) { Log.Message($"{comp.parent} task force {i} occupying area around {zone}"); @@ -174,7 +183,7 @@ public static void Postfix(LordToil_AssaultColony __instance) if (Rand.Chance(0.33f)) { Pawn_CustomDutyTracker.CustomPawnDuty customDuty2 = CustomDutyUtility.DefendPoint(thing.Position, Rand.Range(30, 60), true, 3600 + Rand.Range(0, 60000)); - force[j].EnqueueFirstCustomDuty(customDuty); + force[j].EnqueueFirstCustomDuty(customDuty2); if (Finder.Settings.Debug) { Log.Message($"{comp.parent} task force {i} occupying area around {thing}"); @@ -186,6 +195,118 @@ public static void Postfix(LordToil_AssaultColony __instance) } } } +// foreach (Pawn pawn in __instance.lord.ownedPawns) +// { +// DamageReport report = DamageUtility.GetDamageReport(pawn); +// if (report.IsValid) +// { +// if (!report.primaryIsRanged) +// { +// melee.Add(pawn); +// ranged.Add(new Pair(pawn, 0)); +// } +// else +// { +// float range = report.primaryVerbProps?.range ?? 10; +// ranged.Add(new Pair(pawn, range)); +// if (range > 16) +// { +// rangedLong.Add(pawn); +// } +// else +// { +// rangedShort.Add(pawn); +// } +// } +// } +// } +// ranged.SortBy(p => p.second); +// int maxRange = Rand.Range(15, 30); +// int num = 0; +// int limit = (int) (ranged.Count * Rand.Range(0.25f, 0.4f)); +// for (int i = 0; i < ranged.Count && num < limit; i += (Rand.Int % 2 + 1)) +// { +// Pair pair = ranged[i]; +// if (pair.second > maxRange) +// { +// break; +// } +// SkillRecord record = pair.First.skills?.GetSkill(SkillDefOf.Mining) ?? null; +// if (record != null && Rand.Chance(record.Level / 15f)) +// { +// continue; +// } +// if (!Rand.Chance(i / (ranged.Count * 2f) + 0.01f)) +// { +// for (int j = i + 1; j < ranged.Count; j++) +// { +// Pair other = ranged[j]; +// if (other.second > pair.second) +// { +// int index = Rand.Range(j, ranged.Count - 1); +// if (index >= 0) +// { +// Pair escortee = ranged[index]; +// Pawn_CustomDutyTracker.CustomPawnDuty customDuty = CustomDutyUtility.Escort(escortee.First, 15, 64, 2400 + Rand.Range(0, 12000)); +// customDuty.endOnTookDamage = true; +// pair.First.TryStartCustomDuty(customDuty); +// num++; +// if (Finder.Settings.Debug) +// { +// Log.Message($"{num}. {pair.first}({pair.second}) escorting {escortee.first}({escortee.second})"); +// } +// } +// break; +// } +// } +// } +// } +// foreach (Pawn pawn in melee) +// { +// ThingComp_CombatAI comp = pawn.AI(); +// if (comp != null) +// { +// Pawn ally = null; +// if (rangedLong.Count > 0 && Rand.Chance(Maths.Min(0.5f, rangedLong.Count / 5f))) +// { +// ally = rangedLong.RandomElement(); +// } +// else if (rangedShort.Count > 0 && Rand.Chance(Maths.Min(0.5f, rangedShort.Count / 5f))) +// { +// ally = rangedShort.RandomElement(); +// } +// if (ally != null) +// { +// Pawn_CustomDutyTracker.CustomPawnDuty customDuty = CustomDutyUtility.Escort(ally, 15, 64, 3600 + Rand.Range(0, 9600)); +// customDuty.endOnTookDamage = true; +// if (comp.duties != null) +// { +// pawn.TryStartCustomDuty(customDuty); +// } +// } +// } +// } +// foreach (Pawn pawn in rangedShort) +// { +// ThingComp_CombatAI comp = pawn.AI(); +// if (comp != null) +// { +// Pawn ally = null; +// if (rangedShort.Count > 0 && Rand.Chance(Maths.Min(0.5f, rangedLong.Count / 5f))) +// { +// ally = rangedLong.RandomElement(); +// } +// if (ally != null) +// { +// Pawn_CustomDutyTracker.CustomPawnDuty customDuty = CustomDutyUtility.Escort(ally, 15, 64, 3600 + Rand.Range(0, 9600)); +// customDuty.endOnTookDamage = true; +// if (comp.duties != null) +// { +// pawn.TryStartCustomDuty(customDuty); +// } +// } +// } +// } ClearCache(); } } diff --git a/Source/Rule56/Patches/MapPawns_Patch.cs b/Source/Rule56/Patches/MapPawns_Patch.cs index 28cf52f..d080f49 100644 --- a/Source/Rule56/Patches/MapPawns_Patch.cs +++ b/Source/Rule56/Patches/MapPawns_Patch.cs @@ -21,6 +21,7 @@ public static void Prefix(MapPawns __instance, Pawn p) { __instance.map.GetComp_Fast().DeRegister(p); __instance.map.GetComp_Fast().DeRegister(p); + CacheUtility.ClearThingCache(p); } } } diff --git a/Source/Rule56/Patches/MemoryUtility_Patch.cs b/Source/Rule56/Patches/MemoryUtility_Patch.cs new file mode 100644 index 0000000..99f9e72 --- /dev/null +++ b/Source/Rule56/Patches/MemoryUtility_Patch.cs @@ -0,0 +1,17 @@ +using HarmonyLib; +using Verse.Profile; +namespace CombatAI.Patches +{ + public static class MemoryUtility_Patch + { + [HarmonyPatch(typeof(MemoryUtility), nameof(MemoryUtility.ClearAllMapsAndWorld))] + private static class MemoryUtility_ClearAllMapsAndWorld_Patch + { + public static void Postfix() + { + AsyncActions.KillAll(); + CacheUtility.ClearAllCache(true); + } + } + } +} diff --git a/Source/Rule56/Patches/PathFinder_Patch.cs b/Source/Rule56/Patches/PathFinder_Patch.cs index cbfa219..8aeb30f 100644 --- a/Source/Rule56/Patches/PathFinder_Patch.cs +++ b/Source/Rule56/Patches/PathFinder_Patch.cs @@ -13,6 +13,7 @@ namespace CombatAI.Patches public static class PathFinder_Patch { public static bool FlashSearch; + public static bool FlashSapperPath; [HarmonyPatch(typeof(PathFinder), nameof(PathFinder.FindPath), typeof(IntVec3), typeof(LocalTargetInfo), typeof(TraverseParms), typeof(PathEndMode), typeof(PathFinderCostTuning))] private static class PathFinder_FindPath_Patch @@ -21,29 +22,30 @@ private static class PathFinder_FindPath_Patch #if DEBUG private static PathFinder flashInstance; #endif - private static ThingComp_CombatAI comp; - private static FloatRange temperatureRange; - private static bool checkAvoidance; - private static bool checkVisibility; - private static bool dig; - private static Pawn pawn; - private static Map map; - private static IntVec3 destPos; - private static PathFinder instance; - private static SightTracker.SightReader sightReader; - private static WallGrid walls; - private static AvoidanceTracker.AvoidanceReader avoidanceReader; + private static ThingComp_CombatAI comp; + private static FloatRange temperatureRange; + private static bool checkAvoidance; + private static bool checkVisibility; + private static bool dig; + private static Pawn pawn; + private static PersonalityTacker.PersonalityResult personality = PersonalityTacker.PersonalityResult.Default; + private static Map map; + private static IntVec3 destPos; + private static PathFinder instance; + private static SightTracker.SightReader sightReader; + private static WallGrid walls; + private static AvoidanceTracker.AvoidanceReader avoidanceReader; // private static int counter; private static float threatAtDest; private static float availabilityAtDest; private static float visibilityAtDest; - private static float multiplier = 1.0f; private static bool flashCost; private static bool isRaider; private static bool isPlayer; private static readonly List blocked = new List(128); private static bool fallbackCall; private static ISGrid f_grid; + private static bool avoidTempEnabled; private static TraverseParms original_traverseParms; private static PathEndMode origina_peMode; @@ -51,7 +53,7 @@ private static class PathFinder_FindPath_Patch [HarmonyPriority(int.MaxValue)] internal static void Prefix(PathFinder __instance, ref PawnPath __result, IntVec3 start, LocalTargetInfo dest, ref TraverseParms traverseParms, PathEndMode peMode, ref PathFinderCostTuning tuning, out bool __state) { - if (fallbackCall) + if (fallbackCall) { __state = true; return; @@ -59,10 +61,13 @@ internal static void Prefix(PathFinder __instance, ref PawnPath __result, IntVec #if DEBUG flashInstance = flashCost ? __instance : null; #endif + var settings = Finder.Settings.GetDefKindSettings(traverseParms.pawn); + // + avoidTempEnabled = settings?.Temperature_Enabled ?? true; // only allow factioned pawns. - if (Finder.Settings.Pather_Enabled && (pawn = traverseParms.pawn) != null && pawn.Faction != null && (pawn.RaceProps.Humanlike || pawn.RaceProps.IsMechanoid || pawn.RaceProps.Insect)) + if ((settings?.Pather_Enabled ?? true) && (pawn = traverseParms.pawn) != null && !Mod_ZombieLand.IsZombie(pawn) && pawn.Faction != null && (pawn.RaceProps.Humanlike || pawn.RaceProps.IsMechanoid || pawn.RaceProps.Insect)) { - destPos = dest.Cell; + destPos = dest.Cell; original_traverseParms = traverseParms; origina_peMode = peMode; // prepare the modifications @@ -71,6 +76,8 @@ internal static void Prefix(PathFinder __instance, ref PawnPath __result, IntVec walls = __instance.map.GetComp_Fast(); f_grid = __instance.map.GetComp_Fast().f_grid; f_grid.Reset(); + // get faction data. + personality = pawn.GetCombatPersonality(); // get temperature data. temperatureRange = new FloatRange(pawn.GetStatValue_Fast(StatDefOf.ComfyTemperatureMin, 1600), pawn.GetStatValue_Fast(StatDefOf.ComfyTemperatureMax, 1600)); temperatureRange = temperatureRange.ExpandedBy(12); @@ -81,32 +88,43 @@ internal static void Prefix(PathFinder __instance, ref PawnPath __result, IntVec } // set faction params. isPlayer = pawn.Faction.IsPlayerSafe(); - isRaider = !isPlayer && (pawn.HostileTo(Faction.OfPlayerSilentFail) || map.ParentFaction != null && pawn.Faction.HostileTo(map.ParentFaction)); + isRaider = !isPlayer && ((map.ParentFaction != null && pawn.HostileTo(map.ParentFaction)) || pawn.Faction == Faction.OfMechanoids); // grab different readers. pawn.TryGetAvoidanceReader(out avoidanceReader); pawn.TryGetSightReader(out sightReader); // prepare sapping data if (sightReader != null) { - threatAtDest = sightReader.GetThreat(dest.Cell) * Finder.Settings.Pathfinding_DestWeight; + threatAtDest = sightReader.GetThreat(dest.Cell) * Finder.Settings.Pathfinding_DestWeight; availabilityAtDest = sightReader.GetEnemyAvailability(dest.Cell) * Finder.Settings.Pathfinding_DestWeight; visibilityAtDest = sightReader.GetVisibilityToEnemies(dest.Cell) * Finder.Settings.Pathfinding_DestWeight; comp = pawn.AI(); - if (dig = Finder.Settings.Pather_KillboxKiller - && isRaider - && comp != null && comp.CanSappOrEscort && !comp.IsSapping - && !pawn.mindState.duty.Is(DutyDefOf.Sapper) && !pawn.CurJob.Is(JobDefOf.Mine) && !pawn.mindState.duty.Is(DutyDefOf.ExitMapRandom) && !pawn.mindState.duty.Is(DutyDefOf.Escort)) - { - float costMultiplier = 1; - costMultiplier *= comp.TookDamageRecently(360) ? 4 : 1; - float miningSkill = pawn.skills?.GetSkill(SkillDefOf.Mining)?.Level ?? 0f; - isRaider = true; + Verb verb = pawn.TryGetAttackVerb(); + if (dig = ((settings?.Pather_KillboxKiller ?? true) + && (verb == null || (!verb.IsIncendiary_Ranged() && + !(verb is Verb_ShootBeam || verb is Verb_SpewFire))) + && isRaider + && (pawn.guest == null || !pawn.guest.IsPrisoner) + && comp != null && comp.CanSappOrEscort && !comp.IsSapping + && !pawn.mindState.duty.Is(DutyDefOf.Sapper) && !pawn.CurJob.Is(JobDefOf.Mine) && + !pawn.mindState.duty.Is(DutyDefOf.ExitMapRandom) && + !pawn.mindState.duty.Is(DutyDefOf.Escort))) + { + isRaider = true; + float costMultiplier = personality.sapping; + bool tunneler = pawn.def == CombatAI_ThingDefOf.Mech_Tunneler; + float miningSkill = pawn.skills?.GetSkill(SkillDefOf.Mining)?.Level ?? (tunneler ? 15f : 0f); + if (!Mod_CE.active && miningSkill < 9 && verb != null && !verb.IsMeleeAttack && !(verb.IsEMP() || (verb.verbProps.CausesExplosion && verb.verbProps.defaultProjectile?.projectile?.explosionRadius > 2))) + { + tunneler = true; + miningSkill = Maths.Max(verb.verbProps.defaultProjectile.projectile.damageAmountBase * verb.verbProps.burstShotCount / 4f, miningSkill); + } TraverseParms parms = traverseParms; parms.maxDanger = Danger.Deadly; parms.canBashDoors = true; parms.canBashFences = true; bool humanlike = pawn.RaceProps.Humanlike; - if (humanlike) + if (humanlike || tunneler) { parms.mode = TraverseMode.PassAllDestroyableThings; } @@ -117,24 +135,23 @@ internal static void Prefix(PathFinder __instance, ref PawnPath __result, IntVec traverseParms = parms; if (tuning == null) { - tuning = new PathFinderCostTuning(); - tuning.costBlockedDoor = (int)(15f * costMultiplier); - tuning.costBlockedDoorPerHitPoint = costMultiplier - 1; - if (humanlike) - { - tuning.costBlockedWallBase = (int)(32f * costMultiplier); - tuning.costBlockedWallExtraForNaturalWalls = (int)(32f * costMultiplier); - tuning.costBlockedWallExtraPerHitPoint = (20f - Mathf.Clamp(miningSkill, 5, 15)) / 100 * Finder.Settings.Pathfinding_SappingMul * costMultiplier; - tuning.costOffLordWalkGrid = 0; - } + tuning = new PathFinderCostTuning(); + tuning.costBlockedDoor = (int)(15f * costMultiplier); + tuning.costBlockedDoorPerHitPoint = costMultiplier - 1; + if (humanlike || tunneler) + { + tuning.costBlockedWallBase = (int)(32f * costMultiplier); + tuning.costBlockedWallExtraForNaturalWalls = (int)(32f * costMultiplier); + tuning.costBlockedWallExtraPerHitPoint = (20f - Mathf.Clamp(miningSkill, 5, 15)) / 100 * Finder.Settings.Pathfinding_SappingMul * costMultiplier; + tuning.costOffLordWalkGrid = 0; + } } } } checkAvoidance = Finder.Settings.Flank_Enabled && avoidanceReader != null && !isPlayer; checkVisibility = sightReader != null; -// counter = 0; - flashCost = Finder.Settings.Debug_DebugPathfinding && Find.Selector.SelectedPawns.Contains(pawn); - __state = true; + flashCost = Finder.Settings.Debug_DebugPathfinding && Find.Selector.SelectedPawns.Contains(pawn); + __state = true; return; } __state = false; @@ -151,40 +168,58 @@ public static void Postfix(PathFinder __instance, ref PawnPath __result, bool __ } if (__state) { -// if (__result == null || __result == PawnPath.NotFound || !__result.Found) -// { -// try -// { -// __result?.Dispose(); -// fallbackCall = true; -// dig = false; -// __result = __instance.FindPath(start, dest, original_traverseParms, origina_peMode, tuning); -// } -// catch (Exception er) -// { -// Log.Error($"ISMA: Error occured in FindPath fallback call {er}"); -// } -// finally -// { -// fallbackCall = false; -// } -// } - if (dig && !(__result?.nodes.NullOrEmpty() ?? true)) + if (Finder.Settings.Debug_LogJobs && Finder.Settings.Debug && __result.Found) + { + Job job = pawn.CurJob; + if (job != null) + { + comp.jobLogs ??= new List(); + JobLog log = comp.jobLogs.FirstOrDefault(j => j.id == job.loadID); + if (log != null) + { + log.path ??= new List(); + log.pathSapper ??= new List(); + log.path.Clear(); + log.pathSapper.Clear(); + log.path.AddRange(__result.nodes); + } + } + } + if (dig && !(__result?.nodes.NullOrEmpty() ?? true)) { blocked.Clear(); Thing blocker; - if (__result.TryGetSapperSubPath(pawn, blocked, 15, 3, out IntVec3 cellBefore, out IntVec3 cellAhead, out bool enemiesAhead, out bool enemiesBefore) && blocked.Count > 0 && (blocker = blocked[0].GetEdifice(map)) != null) + if (__result.TryGetSapperSubPath(pawn, blocked, 15, 3, out IntVec3 cellBefore, out IntVec3 cellAhead, out bool enemiesAhead, out bool enemiesBefore, FlashSapperPath) && blocked.Count > 0 && (blocker = blocked[0].GetEdifice(map)) != null && !FlashSapperPath) { - if (tuning != null && (!enemiesAhead || enemiesBefore || map.fogGrid.IsFogged(cellAhead) || cellAhead.HeuristicDistanceTo(cellBefore, map, 2) <= 8)) + FlashSapperPath = false; + if (Finder.Settings.Debug_LogJobs && Finder.Settings.Debug && __result.Found) + { + Job job = pawn.CurJob; + if (job != null) + { + comp.jobLogs ??= new List(); + JobLog log = comp.jobLogs.FirstOrDefault(j => j.id == job.loadID); + if (log != null) + { + log.pathSapper ??= new List(); + log.pathSapper.Clear(); + log.pathSapper.AddRange(__result.nodes); + } + } + } + if (!enemiesAhead || map.fogGrid.IsFogged(cellAhead) || cellAhead.HeuristicDistanceTo(cellBefore, map, 2) <= 8) { try { __result.Dispose(); fallbackCall = true; dig = false; - tuning.costBlockedWallBase = Maths.Max(tuning.costBlockedWallBase * 3, 128); - tuning.costBlockedWallExtraForNaturalWalls = Maths.Max(tuning.costBlockedWallExtraForNaturalWalls * 3, 128); - tuning.costBlockedWallExtraPerHitPoint = Maths.Max(tuning.costBlockedWallExtraPerHitPoint * 4, 4); + if (tuning != null) + { + tuning.costBlockedWallBase = Maths.Max(tuning.costBlockedWallBase * 3, 128); + tuning.costBlockedWallExtraForNaturalWalls = Maths.Max(tuning.costBlockedWallExtraForNaturalWalls * 3, 128); + tuning.costBlockedWallExtraPerHitPoint = Maths.Max(tuning.costBlockedWallExtraPerHitPoint * 4, 4); + } __result = __instance.FindPath(start, dest, original_traverseParms, origina_peMode, tuning); } catch (Exception er) @@ -196,9 +231,9 @@ public static void Postfix(PathFinder __instance, ref PawnPath __result, bool __ fallbackCall = false; } } - else + else if(!FlashSapperPath) { - comp.StartSapper(blocked, cellBefore, enemiesAhead); + comp.StartSapper(blocked, cellBefore, cellAhead, enemiesAhead); } } } @@ -213,10 +248,11 @@ public static void Postfix(PathFinder __instance, ref PawnPath __result, bool __ public static void Reset() { + avoidTempEnabled = false; avoidanceReader = null; isRaider = false; isPlayer = false; - multiplier = 1f; + personality = PersonalityTacker.PersonalityResult.Default; sightReader = null; dig = false; threatAtDest = 0; @@ -273,7 +309,7 @@ private static int GetCostOffsetAt(int index, int parentIndex, int openNum) else { // find the cell cost offset - if (Finder.Settings.Temperature_Enabled) + if (avoidTempEnabled) { float temperature = GenTemperature.TryGetTemperature(index, map); if (!temperatureRange.Includes(temperature)) @@ -311,7 +347,7 @@ private static int GetCostOffsetAt(int index, int parentIndex, int openNum) if (visibilityParent > 0) { // we do this to prevent sapping where there is enemies. - value = 1000 * visibilityParent; + value = 400 * visibilityParent; } } } @@ -331,7 +367,7 @@ private static int GetCostOffsetAt(int index, int parentIndex, int openNum) { // // TODO make this into a maxcost -= something system - value = value * multiplier * Finder.P75 * Mathf.Clamp(1 - PathFinder.calcGrid[parentIndex].knownCost / 2500, 0.1f, 1); + value = value * personality.pathing * Finder.P75 * Mathf.Clamp(1 - PathFinder.calcGrid[parentIndex].knownCost / 2500, 0.1f, 1); } return (int)value; } diff --git a/Source/Rule56/Patches/PawnRenderer_Patch.cs b/Source/Rule56/Patches/PawnRenderer_Patch.cs new file mode 100644 index 0000000..4c3169d --- /dev/null +++ b/Source/Rule56/Patches/PawnRenderer_Patch.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using UnityEngine; +using Verse; +namespace CombatAI.Patches +{ + [HarmonyPatch(typeof(PawnRenderer), nameof(PawnRenderer.RenderPawnAt))] + public static class PawnRenderer_Patch + { + private static bool Prefix(PawnRenderer __instance, Vector3 drawLoc) + { + if (Mod_ZombieLand.active && Finder.Settings.FogOfWar_Enabled && __instance.pawn != null && __instance.pawn.Spawned) + { + var grid = __instance.pawn.Map.GetComp_Fast(); + if (grid != null) + { + return !grid.IsFogged(drawLoc.ToIntVec3()); + } + } + return true; + } + } +} diff --git a/Source/Rule56/Patches/Pawn_ApparelTracker_Patch.cs b/Source/Rule56/Patches/Pawn_ApparelTracker_Patch.cs new file mode 100644 index 0000000..def2b80 --- /dev/null +++ b/Source/Rule56/Patches/Pawn_ApparelTracker_Patch.cs @@ -0,0 +1,44 @@ +using HarmonyLib; +using RimWorld; +using Verse; +namespace CombatAI.Patches +{ + public static class Pawn_ApparelTracker_Patch + { + [HarmonyPatch(typeof(Pawn_ApparelTracker), nameof(Pawn_ApparelTracker.Notify_ApparelAdded))] + private static class Pawn_ApparelTracker_Notify_ApparelAdded_Patch + { + public static void Postfix(Pawn_ApparelTracker __instance) + { + if (__instance.pawn != null) + { + CacheUtility.ClearThingCache(__instance.pawn); + } + } + } + + [HarmonyPatch(typeof(Pawn_ApparelTracker), nameof(Pawn_ApparelTracker.Notify_ApparelRemoved))] + private static class Pawn_ApparelTracker_Notify_ApparelRemoved_Patch + { + public static void Postfix(Pawn_ApparelTracker __instance) + { + if (__instance.pawn != null) + { + CacheUtility.ClearThingCache(__instance.pawn); + } + } + } + + [HarmonyPatch(typeof(Pawn_ApparelTracker), nameof(Pawn_ApparelTracker.Notify_ApparelChanged))] + private static class Pawn_ApparelTracker_Notify_ApparelChanged_Patch + { + public static void Postfix(Pawn_ApparelTracker __instance) + { + if (__instance.pawn != null) + { + CacheUtility.ClearThingCache(__instance.pawn); + } + } + } + } +} diff --git a/Source/Rule56/Patches/Pawn_EquipmentTracker_Patch.cs b/Source/Rule56/Patches/Pawn_EquipmentTracker_Patch.cs new file mode 100644 index 0000000..0b05542 --- /dev/null +++ b/Source/Rule56/Patches/Pawn_EquipmentTracker_Patch.cs @@ -0,0 +1,32 @@ +using HarmonyLib; +using RimWorld; +using Verse; +namespace CombatAI.Patches +{ + public class Pawn_EquipmentTracker_Patch + { + [HarmonyPatch(typeof(Pawn_EquipmentTracker), nameof(Pawn_EquipmentTracker.Notify_EquipmentAdded))] + private static class Pawn_EquipmentTracker_Notify_EquipmentAdded_Patch + { + public static void Postfix(Pawn_EquipmentTracker __instance) + { + if (__instance.pawn != null) + { + CacheUtility.ClearThingCache(__instance.pawn); + } + } + } + + [HarmonyPatch(typeof(Pawn_EquipmentTracker), nameof(Pawn_EquipmentTracker.Notify_EquipmentRemoved))] + private static class Pawn_EquipmentTracker_Notify_EquipmentRemoved_Patch + { + public static void Postfix(Pawn_EquipmentTracker __instance) + { + if (__instance.pawn != null) + { + CacheUtility.ClearThingCache(__instance.pawn); + } + } + } + } +} diff --git a/Source/Rule56/Patches/ThingComp_Patch.cs b/Source/Rule56/Patches/ThingComp_Patch.cs new file mode 100644 index 0000000..d44a2d6 --- /dev/null +++ b/Source/Rule56/Patches/ThingComp_Patch.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Verse; +namespace CombatAI.Patches +{ + public static class ThingComp_Patch + { + [HarmonyPatch] + private static class ThingComp_PostDraw_Patch + { + public static IEnumerable TargetMethods() + { + HashSet methods = new HashSet(); + foreach (Type t in typeof(ThingComp).AllSubclasses()) + { + MethodInfo method = t.GetMethod(nameof(ThingComp.PostDraw)); + if(method != null) + { + if (method != null && !methods.Contains(method) && !method.IsStatic && !method.IsAbstract && method.HasMethodBody() && method.Name == "PostDraw" && method.DeclaringType == t) + { + Log.Message($"ISMA: Patched ThingComp type {t.FullName}:{method.Name}"); // + methods.Add(method); + } + } + } + return methods; + } + + public static bool Prefix(ThingComp __instance) + { + if (Finder.Settings.FogOfWar_Enabled && __instance.parent is Pawn { Spawned: true, Dead: false } pawn) + { + return !(pawn.Map.GetComp_Fast()?.IsFogged(__instance.parent.Position) ?? false); + } + return true; + } + } + } +} diff --git a/Source/Rule56/Patches/Thing_Patch.cs b/Source/Rule56/Patches/Thing_Patch.cs new file mode 100644 index 0000000..8e62a37 --- /dev/null +++ b/Source/Rule56/Patches/Thing_Patch.cs @@ -0,0 +1,16 @@ +using HarmonyLib; +using Verse; +namespace CombatAI.Patches +{ + public static class Thing_Patch + { + [HarmonyPatch(typeof(Thing), nameof(Thing.DeSpawn))] + public static class Thing_DeSpawn_Patch + { + public static void Prefix(Thing __instance) + { + CacheUtility.ClearThingCache(__instance); + } + } + } +} diff --git a/Source/Rule56/Patches/TrashUtility_Patch.cs b/Source/Rule56/Patches/TrashUtility_Patch.cs new file mode 100644 index 0000000..161135f --- /dev/null +++ b/Source/Rule56/Patches/TrashUtility_Patch.cs @@ -0,0 +1,61 @@ +using HarmonyLib; +using RimWorld; +using Verse; +namespace CombatAI.Patches +{ + public static class TrashUtility_Patch + { + private static SightTracker.SightReader sightReader; + private static Pawn sightPawn; + + [HarmonyPatch(typeof(TrashUtility), nameof(TrashUtility.ShouldTrashBuilding), new []{typeof(Pawn), typeof(Building), typeof(bool)})] + private static class TrashUtility_ShouldTrashBuilding_Patch + { + public static void Postfix(Pawn pawn, Building b, bool attackAllInert, ref bool __result) + { + if (__result) + { + if ((sightPawn == pawn && sightReader != null) || (sightPawn = pawn).TryGetSightReader(out sightReader)) + { + foreach (IntVec3 cell in b.OccupiedRect()) + { + if (sightReader.GetVisibilityToEnemies(cell) > 0) + { + __result = false; + break; + } + } + } + } + } + } + + [HarmonyPatch(typeof(TrashUtility), nameof(TrashUtility.CanTrash))] + private static class TrashUtility_CanTrash_Patch + { + public static void Postfix(Pawn pawn, Thing t, ref bool __result) + { + if (__result) + { + if ((sightPawn == pawn && sightReader != null) || (sightPawn = pawn).TryGetSightReader(out sightReader)) + { + foreach (IntVec3 cell in t.OccupiedRect()) + { + if (sightReader.GetVisibilityToEnemies(cell) > 0) + { + __result = false; + break; + } + } + } + } + } + } + + public static void ClearCache() + { + sightPawn = null; + sightReader = null; + } + } +} diff --git a/Source/Rule56/Patches/Trigger_FractionColonyDamageTaken_Patch.cs b/Source/Rule56/Patches/Trigger_FractionColonyDamageTaken_Patch.cs index c352c01..7ae263f 100644 --- a/Source/Rule56/Patches/Trigger_FractionColonyDamageTaken_Patch.cs +++ b/Source/Rule56/Patches/Trigger_FractionColonyDamageTaken_Patch.cs @@ -11,7 +11,7 @@ private static class Trigger_FractionColonyDamageTaken_Constructor public static void Prefix(ref float desiredColonyDamageFraction, ref float minDamage) { minDamage *= 15f; - desiredColonyDamageFraction = Maths.Max(Rand.Range(0.25f, 5.0f), desiredColonyDamageFraction); + desiredColonyDamageFraction = Maths.Max(Rand.Range(0.75f, 5.0f), desiredColonyDamageFraction); } } diff --git a/Source/Rule56/Patches/Verb_Patch.cs b/Source/Rule56/Patches/Verb_Patch.cs index 864654f..f0690c4 100644 --- a/Source/Rule56/Patches/Verb_Patch.cs +++ b/Source/Rule56/Patches/Verb_Patch.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Reflection.Emit; using CombatAI.Comps; using HarmonyLib; using RimWorld; @@ -9,12 +11,12 @@ namespace CombatAI.Patches { public class Verb_Patch { - private static Verb callerVerb; + private static Verb callerVerb; - [HarmonyPatch] + [HarmonyPatch] private static class Verb_TryStartCast_Patch { - public static IEnumerable TargetMethods() + public static IEnumerable TargetMethods() { HashSet methods = new HashSet(); foreach (Type t in typeof(Verb).AllSubclasses()) @@ -64,5 +66,72 @@ public static void Postfix(Verb __instance, bool __result, bool __state) } } } + + [HarmonyPatch] + private static class Verb_TryCastNextBurstShot_Patch + { + private static MethodBase mFleckMakerStatic = AccessTools.Method(typeof(FleckMaker), nameof(FleckMaker.Static), new []{typeof(IntVec3), typeof(Map), typeof(FleckDef), typeof(float)}); + + public static IEnumerable TargetMethods() + { + HashSet methods = new HashSet(); + foreach (Type t in typeof(Verb).AllSubclasses()) + { + foreach (MethodInfo method in t.GetMethods(AccessTools.all)) + { + if (method != null && !methods.Contains(method) && !method.IsStatic && method.Name.Contains("TryCastNextBurstShot") && !method.IsAbstract && method.HasMethodBody() && method.DeclaringType == t) + { + Log.Message($"ISMA: TryCastNextBurstShot Patched verb type {t.FullName}:{method.Name}"); + methods.Add(method); + } + } + } + foreach (MethodInfo method in typeof(Verb).GetMethods(AccessTools.all)) + { + if (method != null && !methods.Contains(method) && !method.IsStatic && method.Name.Contains("TryCastNextBurstShot") && !method.IsAbstract && method.HasMethodBody() && method.DeclaringType == typeof(Verb)) + { + Log.Message($"ISMA: TryCastNextBurstShot Patched verb type {typeof(Verb).FullName}:{method.Name}"); + methods.Add(method); + } + } + return methods; + } + + public static IEnumerable Transpiler(IEnumerable instructions) + { + List codes = instructions.ToList(); + bool finished = false; + for (int i = 0; i < codes.Count; i++) + { + yield return codes[i]; + if (!finished) + { + if (codes[i].opcode == OpCodes.Call && codes[i].OperandIs(mFleckMakerStatic)) + { + finished = true; + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Verb_TryCastNextBurstShot_Patch), nameof(Verb_TryCastNextBurstShot_Patch.OnShot))); + } + } + } + } + + + public static void OnShot(Verb verb) + { + if (verb.verbProps.muzzleFlashScale > 0) + { + Map map = verb.caster.Map; + if (map != null) + { + MapComponent_FogGrid grid = map.GetComp_Fast(); + if (grid != null) + { + grid.RevealSpot(verb.caster.Position, Maths.Min(verb.verbProps.muzzleFlashScale, 8f), Rand.Range(120, 360)); + } + } + } + } + } } } diff --git a/Source/Rule56/PawnPathUtility.cs b/Source/Rule56/PawnPathUtility.cs index da49f74..b6ebbc4 100644 --- a/Source/Rule56/PawnPathUtility.cs +++ b/Source/Rule56/PawnPathUtility.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Runtime.CompilerServices; using RimWorld; using UnityEngine; using Verse; @@ -28,7 +29,7 @@ public static bool TryGetCellIndexAhead(Pawn pawn, float ticksAhead, out int ind return true; } - public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List store, int sightAhead, int sightStep, out IntVec3 cellBefore, out IntVec3 cellAhead, out bool enemiesAhead, out bool enemiesBefore) + public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List store, int sightAhead, int sightStep, out IntVec3 cellBefore, out IntVec3 cellAhead, out bool enemiesAhead, out bool enemiesBefore, bool debugFlash = false) { cellBefore = IntVec3.Invalid; cellAhead = IntVec3.Invalid; @@ -43,10 +44,14 @@ public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List= 0) { IntVec3 next = path.nodes[i]; - if (store.Count == 0 && reader.GetAbsVisibilityToEnemies(next) > 0) + if (store.Count == 0 && reader.GetVisibilityToEnemies(next) > 0) { enemiesBefore = true; } @@ -106,6 +111,10 @@ public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List 0) { cellAhead = next; @@ -114,6 +123,20 @@ public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List 0 && i < 0) + { + return false; + } + if (debugFlash) + { + int k = i; + while (k >= 0) + { + IntVec3 next = path.nodes[k]; + map.debugDrawer.FlashCell(next, 0.5f, "+"); + k--; + } + } if (store.Count > 0) { int i0 = i; @@ -121,7 +144,7 @@ public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List= limit) { IntVec3 next = path.nodes[i]; - if (reader.GetAbsVisibilityToEnemies(next) > 0 || home != null && home.innerGrid[next]) + if (reader.GetVisibilityToEnemies(next) > 0 || home != null && home.innerGrid[next]) { enemiesAhead = true; break; @@ -148,11 +171,22 @@ public static bool TryGetSapperSubPath(this PawnPath path, Pawn pawn, List 0 && cellAhead.IsValid; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool WalkableBy(this IntVec3 cell, Pawn pawn) { - if (!cell.WalkableBy(pawn.Map, pawn)) +// if (!cell.WalkableBy(pawn.Map, pawn)) +// { +// return false; +// } + Building building = cell.GetEdifice(pawn.Map); + if (building != null && (building.def.Fillage == FillCategory.Full || building.def.passability == Traversability.Impassable)) { - return false; + return false; + } + TerrainDef terrainDef = cell.GetTerrain(pawn.Map); + if (terrainDef != null && terrainDef.passability == Traversability.Impassable) + { + return false; } return true; } diff --git a/Source/Rule56/PersonalityTacker.cs b/Source/Rule56/PersonalityTacker.cs new file mode 100644 index 0000000..b77e1ae --- /dev/null +++ b/Source/Rule56/PersonalityTacker.cs @@ -0,0 +1,195 @@ +using System.Runtime.CompilerServices; +using RimWorld; +using UnityEngine; +using Verse; +namespace CombatAI +{ + public class PersonalityTacker : GameComponent + { + public PersonalityTacker(Game game) + { + } + + public PersonalityResult GetPersonality(Thing thing) + { + if (thing == null) + { + return PersonalityResult.From(Finder.Settings.GetTechSettings(TechLevel.Undefined)); + } + if (thing is Pawn pawn) + { + if (pawn.RaceProps.Animal) + { + return PersonalityResult.From(Finder.Settings.GetTechSettings(TechLevel.Animal)); + } + } + return GetPersonality(thing.Faction); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PersonalityResult GetPersonality(Faction faction) + { + if (faction == null) + { + PersonalityResult.From(Finder.Settings.GetTechSettings(TechLevel.Undefined)); + } + if (faction.leader == null || !Finder.Settings.Personalities_Enabled) + { + return PersonalityResult.From(Finder.Settings.GetTechSettings(faction.def.techLevel)); + } + return PersonalityResult.From(Finder.Settings.GetTechSettings(faction.def.techLevel)) * 0.60f + GetPawnPersonality(faction.leader) * 0.20f + GetFactionPersonality(faction) * 0.20f; + } + + private PersonalityResult GetPawnPersonality(Pawn pawn) + { + if (!TKVCache.TryGet(pawn.thingIDNumber, out PersonalityResult result) || !result.IsValid) + { + int seed = pawn.thingIDNumber; + Rand.PushState(seed + (GenTicks.TicksGame % GenDate.TicksPerYear) / (GenDate.TicksPerDay * 7)); + result = PersonalityResult.Default; + result.retreat = Rand.Range(0.0f, 2.0f); + result.duck = Rand.Range(0.0f, 2.0f); + result.sapping = Rand.Range(0.0f, 2.0f); + result.pathing = Rand.Range(0.5f, 2.0f); + result.cover = Rand.Range(0.5f, 2.0f); + result.group = Rand.Range(0.5f, 2.0f); + Rand.PopState(); + TKVCache.Put(pawn.thingIDNumber, result); + } + return result; + } + + private PersonalityResult GetFactionPersonality(Faction faction) + { + if (!TKVCache.TryGet(faction.loadID, out PersonalityResult result) || !result.IsValid) + { + int seed = faction.loadID; + Rand.PushState(seed + (GenTicks.TicksGame % GenDate.TicksPerYear) / (GenDate.TicksPerDay * 2)); + result = PersonalityResult.Default; + result.retreat = Rand.Range(0.0f, 2.0f); + result.duck = Rand.Range(0.0f, 2.0f); + result.sapping = Rand.Range(0.0f, 2.0f); + result.pathing = Rand.Range(0.5f, 2.0f); + result.cover = Rand.Range(0.5f, 2.0f); + result.group = Rand.Range(0.5f, 2.0f); + Rand.PopState(); + TKVCache.Put(faction.loadID, result); + } + return result; + } + + public struct PersonalityResult : IExposable + { + private const int version = 1; + private static readonly PersonalityResult _default; + static PersonalityResult() + { + PersonalityResult defaultPersonality = new PersonalityResult(); + defaultPersonality.retreat = 1; + defaultPersonality.duck = 1; + defaultPersonality.cover = 1; + defaultPersonality.sapping = 1; + defaultPersonality.pathing = 1; + defaultPersonality.group = 1; + defaultPersonality._valid = true; + _default = defaultPersonality; + } + + private bool _valid; + public float retreat; + public float duck; + public float cover; + public float sapping; + public float pathing; + public float group; + + public bool IsValid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _valid; + } + + public static PersonalityResult Default + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _default; + } + + public static PersonalityResult operator +(PersonalityResult first, PersonalityResult second) + { + PersonalityResult result = new PersonalityResult(); + result.retreat = first.retreat + second.retreat; + result.duck = first.duck + second.duck; + result.cover = first.cover + second.cover; + result.sapping = first.sapping + second.sapping; + result.pathing = first.pathing + second.pathing; + result.group = first.group + second.group; + result._valid = true; + return result; + } + + public static PersonalityResult operator *(PersonalityResult first, PersonalityResult second) + { + PersonalityResult result = new PersonalityResult(); + result.retreat = first.retreat * second.retreat; + result.duck = first.duck * second.duck; + result.cover = first.cover * second.cover; + result.sapping = first.sapping * second.sapping; + result.pathing = first.pathing * second.pathing; + result.group = first.group * second.group; + result._valid = true; + return result; + } + + public static PersonalityResult operator +(PersonalityResult first, float val) + { + PersonalityResult result = new PersonalityResult(); + result.retreat = first.retreat + val; + result.duck = first.duck + val; + result.cover = first.cover + val; + result.sapping = first.sapping + val; + result.pathing = first.pathing + val; + result.group = first.group + val; + result._valid = true; + return result; + } + + public static PersonalityResult operator *(PersonalityResult first, float val) + { + PersonalityResult result = new PersonalityResult(); + result.retreat = first.retreat * val; + result.duck = first.duck * val; + result.cover = first.cover * val; + result.sapping = first.sapping * val; + result.pathing = first.pathing * val; + result.group = first.group * val; + result._valid = true; + return result; + } + + public static PersonalityResult From(Settings.FactionTechSettings settings) + { + PersonalityResult result = new PersonalityResult(); + result.retreat = settings.retreat; + result.duck = settings.duck; + result.cover = settings.cover; + result.sapping = settings.sapping; + result.pathing = settings.pathing; + result.group = settings.group; + result._valid = true; + return result; + } + + public void ExposeData() + { + Scribe_Values.Look(ref retreat, $"retreat.{version}", 1); + Scribe_Values.Look(ref duck, $"duck.{version}", 1); + Scribe_Values.Look(ref cover, $"cover.{version}", 1); + Scribe_Values.Look(ref sapping, $"sapping.{version}", 1); + Scribe_Values.Look(ref pathing, $"pathing.{version}", 1); + Scribe_Values.Look(ref group, $"group.{version}", 1); + Scribe_Values.Look(ref _valid, $"valid.{version}"); + } + } + } +} diff --git a/Source/Rule56/Settings.cs b/Source/Rule56/Settings.cs index b829369..979ef17 100644 --- a/Source/Rule56/Settings.cs +++ b/Source/Rule56/Settings.cs @@ -1,10 +1,15 @@ -using Verse; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using RimWorld; +using Verse; namespace CombatAI { public class Settings : ModSettings { - private const int version = 4; + private const int version = 15; public int Advanced_SightThreadIdleSleepTimeMS = 1; /* @@ -14,14 +19,14 @@ public class Settings : ModSettings */ public bool AdvancedUser; - public bool Caster_Enabled = true; + public bool Caster_Enabled = true; + public bool Personalities_Enabled = true; /* * * -- Debug -- * */ - #if DEBUG public bool Debug = true; public bool Debug_LogJobs = true; @@ -49,9 +54,10 @@ public class Settings : ModSettings public bool FogOfWar_AnimalsSmartOnly = true; public bool FogOfWar_Enabled; - public float FogOfWar_FogColor = 0.35f; + public bool FogOfWar_OldShader = true; + public float FogOfWar_FogColor = 0.5f; public float FogOfWar_RangeFadeMultiplier = 0.5f; - public float FogOfWar_RangeMultiplier = 1.0f; + public float FogOfWar_RangeMultiplier = 1.8f; public bool FogOfWar_Turrets = true; /* @@ -65,50 +71,96 @@ public class Settings : ModSettings */ public bool LeanCE_Enabled; - public bool Pather_DisableL1L2 = false; - public bool Pather_Enabled = true; - public bool Pather_KillboxKiller = true; - public float Pathfinding_DestWeight = 0.85f; - public float Pathfinding_SappingMul = 1.0f; - public bool Temperature_Enabled = true; - public bool PerformanceOpt_Enabled = true; - public bool React_Enabled = true; - public bool Retreat_Enabled = true; + public bool Pather_DisableL1L2 = false; + public float Pathfinding_DestWeight = 0.85f; + public float Pathfinding_SappingMul = 1.0f; + public int Pathfinding_SquadPathWidth = 4; + public bool PerformanceOpt_Enabled = true; public bool FinishedQuickSetup; + private Dictionary _raceSettings = new (); + private Dictionary<(ThingDef, PawnKindDef), DefKindAISettings> _raceSettingsTemp = new (); + + private FactionTechSettings FactionSettings_Undefined = new FactionTechSettings(TechLevel.Undefined, 1, 1, 1, 1, 1,1); + private List FactionSettings = new List(); - public SightPerformanceSettings SightSettings_FriendliesAndRaiders = new SightPerformanceSettings(3, 5, 16); + public SightPerformanceSettings SightSettings_FriendliesAndRaiders = new SightPerformanceSettings(1, 3, 16); public SightPerformanceSettings SightSettings_MechsAndInsects = new SightPerformanceSettings(3, 10, 6); - public SightPerformanceSettings SightSettings_SettlementTurrets = new SightPerformanceSettings(8, 15, 12); - public SightPerformanceSettings SightSettings_Wildlife = new SightPerformanceSettings(6, 5, 4); + public SightPerformanceSettings SightSettings_Wildlife = new SightPerformanceSettings(3, 10, 4); + public SightPerformanceSettings SightSettings_SettlementTurrets = new SightPerformanceSettings(3, 15, 12); public bool Targeter_Enabled = true; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public DefKindAISettings GetDefKindSettings(Pawn pawn) + { + if (pawn == null) + { + return null; + } + return GetDefKindSettings(pawn.def, pawn.kindDef); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public DefKindAISettings GetDefKindSettings(ThingDef def, PawnKindDef kind) + { + if (def == null) + { + return null; + } + if (_raceSettingsTemp.TryGetValue((def, kind), out var settings)) + { + return settings; + } + var key = def.defName + "_" + kind?.defName ?? string.Empty; + if (_raceSettings.TryGetValue(key, out settings)) + { + _raceSettingsTemp[(def, kind)] = settings; + return settings; + } + return _raceSettings[key] = _raceSettingsTemp[(def, kind)] = new DefKindAISettings(); + } + /* * -- * -- * -- * -- * -- * -- * -- * -- * -- */ + public FactionTechSettings GetTechSettings(TechLevel level) + { + for (int i = 0; i < FactionSettings.Count; i++) + { + FactionTechSettings settings = FactionSettings[i]; + if (settings.tech == level) + { + return settings; + } + } + Log.Warning($"ISMA: Tech settings for {level} not found. returning default value."); + return FactionSettings_Undefined; + } + public override void ExposeData() { - base.ExposeData(); - Scribe_Deep.Look(ref SightSettings_FriendliesAndRaiders, $"CombatAI.SightSettings_FriendliesAndRaiders.{version}"); + base.ExposeData(); + Scribe_Deep.Look(ref SightSettings_FriendliesAndRaiders, $"CombatAI.SightSettings_FriendliesAndRaiders2.{version}"); if (SightSettings_FriendliesAndRaiders == null) { - SightSettings_FriendliesAndRaiders = new SightPerformanceSettings(3, 5, 16); + SightSettings_FriendliesAndRaiders = new SightPerformanceSettings(1, 3, 16); } - Scribe_Deep.Look(ref SightSettings_MechsAndInsects, $"CombatAI.SightSettings_MechsAndInsects.{version}"); + Scribe_Deep.Look(ref SightSettings_MechsAndInsects, $"CombatAI.SightSettings_MechsAndInsects2.{version}"); if (SightSettings_MechsAndInsects == null) { SightSettings_MechsAndInsects = new SightPerformanceSettings(3, 10, 6); } - Scribe_Deep.Look(ref SightSettings_Wildlife, $"CombatAI.SightSettings_Wildlife.{version}"); + Scribe_Deep.Look(ref SightSettings_Wildlife, $"CombatAI.SightSettings_Wildlife2.{version}"); if (SightSettings_Wildlife == null) { - SightSettings_Wildlife = new SightPerformanceSettings(6, 10, 4); + SightSettings_Wildlife = new SightPerformanceSettings(3, 10, 4); } - Scribe_Deep.Look(ref SightSettings_SettlementTurrets, $"CombatAI.SightSettings_SettlementTurrets.{version}"); + Scribe_Deep.Look(ref SightSettings_SettlementTurrets, $"CombatAI.SightSettings_SettlementTurrets2.{version}"); if (SightSettings_SettlementTurrets == null) { - SightSettings_SettlementTurrets = new SightPerformanceSettings(8, 15, 12); + SightSettings_SettlementTurrets = new SightPerformanceSettings(3, 15, 12); } Scribe_Values.Look(ref LeanCE_Enabled, $"LeanCE_Enabled.{version}"); @@ -119,21 +171,27 @@ public override void ExposeData() Scribe_Values.Look(ref Debug, $"Release.Debug.{version}"); Scribe_Values.Look(ref Debug_LogJobs, $"Release.Debug_LogJobs.{version}"); #endif - + if (Scribe.mode == LoadSaveMode.Saving) + { + foreach (var pair in _raceSettingsTemp) + { + _raceSettings.TryAdd(pair.Key.Item1.defName + "_" + (pair.Key.Item2?.defName ?? string.Empty), pair.Value); + } + } + Scribe_Collections.Look(ref _raceSettings, "raceSettings2"); + _raceSettings ??= new Dictionary(); Scribe_Values.Look(ref FinishedQuickSetup, $"FinishedQuickSetup2.{version}"); - Scribe_Values.Look(ref Pather_Enabled, $"Pather_Enabled.{version}", true); + Scribe_Values.Look(ref Personalities_Enabled, $"Personalities_Enabled.{version}", true); + Scribe_Values.Look(ref FogOfWar_OldShader, $"FogOfWar_OldShader.{version}", true); Scribe_Values.Look(ref Caster_Enabled, $"Caster_Enabled.{version}", true); Scribe_Values.Look(ref Targeter_Enabled, $"Targeter_Enabled.{version}", true); - Scribe_Values.Look(ref Temperature_Enabled, $"Temperature_Enabled.{version}", true); - Scribe_Values.Look(ref React_Enabled, $"React_Enabled.{version}", true); - Scribe_Values.Look(ref Retreat_Enabled, $"Retreat_Enabled.{version}", true); Scribe_Values.Look(ref Flank_Enabled, $"Flank_Enabled.{version}", true); Scribe_Values.Look(ref Pathfinding_DestWeight, $"Pathfinding_DestWeight.{version}", 0.85f); + Scribe_Values.Look(ref Pathfinding_SquadPathWidth, $"Pathfinding_SquadPathWidth.{version}", 4); Scribe_Values.Look(ref AdvancedUser, $"AdvancedUser.{version}"); - Scribe_Values.Look(ref FogOfWar_FogColor, $"FogOfWar_FogColor.{version}", 0.35f); + Scribe_Values.Look(ref FogOfWar_FogColor, $"FogOfWar_FogColor.{version}", 0.65f); Scribe_Values.Look(ref FogOfWar_RangeFadeMultiplier, $"FogOfWar_RangeFadeMultiplier.{version}", 0.5f); - Scribe_Values.Look(ref FogOfWar_RangeMultiplier, $"FogOfWar_RangeMultiplier.{version}", 1.0f); - Scribe_Values.Look(ref Pather_KillboxKiller, $"Pather_KillboxKiller.{version}", true); + Scribe_Values.Look(ref FogOfWar_RangeMultiplier, $"FogOfWar_RangeMultiplier.{version}", 1.8f); Scribe_Values.Look(ref PerformanceOpt_Enabled, $"PerformanceOpt_Enabled.{version}", true); Scribe_Values.Look(ref FogOfWar_Enabled, $"FogOfWar_Enabled.{version}"); Scribe_Values.Look(ref Debug_ValidateSight, $"Debug_ValidateSight.{version}"); @@ -147,6 +205,38 @@ public override void ExposeData() Scribe_Values.Look(ref Pathfinding_SappingMul, $"Pathfinding_SappingMul2.{version}", 1.0f); //ScribeValues(); // Scribe values. (Will not scribe IExposables nor enums) + + Scribe_Collections.Look(ref FactionSettings, $"FactionSettings.{version}", LookMode.Deep); + if (FactionSettings.NullOrEmpty()) + { + FactionSettings = new List(); + } + foreach (TechLevel tech in Enum.GetValues(typeof(TechLevel))) + { + if (!FactionSettings.Any(t => t.tech == tech)) + { + Log.Error($"Tech level {tech} doesn't have tech settings. Resetting tech settings"); + FactionSettings.Clear(); + break; + } + } + if (FactionSettings.NullOrEmpty()) + { + ResetTechSettings(); + } + } + + public void ResetTechSettings() + { + FactionSettings.Clear(); + FactionSettings.Add(new FactionTechSettings(TechLevel.Undefined, retreat: 1, duck: 1, cover: 1, sapping: 1, pathing: 1, group: 1)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Animal, retreat: 0.0f, duck: 0.0f, cover: 0.25f, sapping: 2.0f, pathing: 1.15f, group: 2.0f)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Neolithic, retreat: 0.0f, duck: 0.0f, cover: 0.25f, sapping: 0.75f, pathing: 1.1f, group: 2.0f)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Medieval, retreat: 0.25f, duck: 0.25f, cover: 0.5f, sapping: 0.50f, pathing: 1f, group: 2.0f)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Industrial, retreat: 0.75f, duck: 0.75f, cover: 1, sapping: 1, pathing: 1, group: 1.25f)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Spacer, retreat: 0.95f, duck: 0.95f, cover: 1, sapping: 1, pathing: 1, group: 1.0f)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Archotech, retreat: 1, duck: 1, cover: 1, sapping: 1, pathing: 0.9f, group: 1.0f)); + FactionSettings.Add(new FactionTechSettings(TechLevel.Ultra, retreat: 1.0f, duck: 1.0f, cover: 1.0f, sapping: 0.9f, pathing: 1, group: 1.0f)); } /* * -- * -- * -- * -- * -- * -- * -- * -- * -- @@ -158,6 +248,28 @@ public override void ExposeData() * */ + public class DefKindAISettings : IExposable + { + public bool Pather_Enabled = true; + public bool Pather_KillboxKiller = true; + public bool Temperature_Enabled = true; + public bool React_Enabled = true; + public bool Retreat_Enabled = true; + + public DefKindAISettings() + { + } + + public void ExposeData() + { + Scribe_Values.Look(ref Pather_Enabled, "Pather_Enabled", true); + Scribe_Values.Look(ref Pather_KillboxKiller, "Pather_KillboxKiller", true); + Scribe_Values.Look(ref Temperature_Enabled, "Temperature_Enabled", true); + Scribe_Values.Look(ref React_Enabled, "React_Enabled", true); + Scribe_Values.Look(ref Retreat_Enabled, "Retreat_Enabled", true); + } + } + public class SightPerformanceSettings : IExposable { public int buckets; @@ -182,5 +294,42 @@ public void ExposeData() Scribe_Values.Look(ref carryLimit, $"carryLimit.{version}"); } } + + public class FactionTechSettings : IExposable + { + public float retreat; + public float duck; + public float cover; + public float sapping; + public float pathing; + public float group; + public TechLevel tech; + + public FactionTechSettings() + { + } + + public FactionTechSettings(TechLevel tech, float retreat, float duck, float cover, float sapping, float pathing, float group) + { + this.tech = tech; + this.retreat = retreat; + this.duck = duck; + this.cover = cover; + this.sapping = sapping; + this.pathing = pathing; + this.group = group; + } + + public void ExposeData() + { + Scribe_Values.Look(ref tech, $"tech.{version}"); + Scribe_Values.Look(ref retreat, $"retreat.{version}"); + Scribe_Values.Look(ref duck, $"duck.{version}"); + Scribe_Values.Look(ref cover, $"cover.{version}"); + Scribe_Values.Look(ref sapping, $"sapping.{version}"); + Scribe_Values.Look(ref pathing, $"pathing.{version}"); + Scribe_Values.Look(ref group, $"group.{version}"); + } + } } } diff --git a/Source/Rule56/SightGrid.cs b/Source/Rule56/SightGrid.cs index 02ddf47..57ff031 100644 --- a/Source/Rule56/SightGrid.cs +++ b/Source/Rule56/SightGrid.cs @@ -7,782 +7,831 @@ using Verse.AI; namespace CombatAI { - public class SightGrid - { + public class SightGrid + { + private const int COVERCARRYLIMIT = 6; + private readonly AsyncActions asyncActions; + private readonly IBuckets buckets; + private readonly List buffer = new List(1024); + private readonly CellFlooder flooder; + /// + /// Sight grid contains all sight data. + /// + public readonly ITSignalGrid grid; + /// + /// Region grid contains sight data for regions. + /// + public readonly ITRegionGrid grid_regions; + /// + /// SightGrid Id. + /// + public readonly int gridId; + /// + /// Parent map. + /// + public readonly Map map; + private readonly Dictionary numsByFaction = new Dictionary(); + /// + /// Performance settings. + /// + public readonly Settings.SightPerformanceSettings settings; + /// + /// Parent map sight tracker. + /// + public readonly SightTracker sightTracker; + private readonly List thingBuffer1 = new List(256); + private readonly List thingBuffer2 = new List(256); + /// + /// Things UInt64 map. + /// + public readonly IThingsUInt64Map thingsUInt64Map; + private readonly List tmpDeRegisterList = new List(64); + private readonly List tmpInconsistentRecords = new List(64); + private readonly List tmpInvalidRecords = new List(64); - private const int COVERCARRYLIMIT = 6; - private readonly AsyncActions asyncActions; - private readonly IBuckets buckets; - private readonly List buffer = new List(1024); - private readonly CellFlooder flooder; - /// - /// Sight grid contains all sight data. - /// - public readonly ITSignalGrid grid; - /// - /// Region grid contains sight data for regions. - /// - public readonly ITRegionGrid grid_regions; + private readonly List _getThings = new List(); + private WallGrid _walls; + /// + /// Fog of war grid. Can be null. + /// + public ITFloatGrid gridFog; + private int ops; + /// + /// Whether this is the player grid + /// + public bool playerAlliance = false; - /// - /// Parent map. - /// - public readonly Map map; - private readonly Dictionary numsByFaction = new Dictionary(); - /// - /// Performance settings. - /// - public readonly Settings.SightPerformanceSettings settings; - /// - /// Parent map sight tracker. - /// - public readonly SightTracker sightTracker; - private readonly List thingBuffer1 = new List(256); - private readonly List thingBuffer2 = new List(256); - private readonly List tmpDeRegisterList = new List(64); - private readonly List tmpInconsistentRecords = new List(64); - private readonly List tmpInvalidRecords = new List(64); - private WallGrid _walls; - /// - /// Fog of war grid. Can be null. - /// - public ITFloatGrid gridFog; + private int regionUpdateIndex; -// private int regionUpdateIndex; - private int ops; - /// - /// Whether this is the player grid - /// - public bool playerAlliance = false; + private IntVec3 suCentroid; + private IntVec3 suCentroidPrev; + /// + /// Super rect containing all none sighter things casting. + /// + private CellRect suRect_Combatant = CellRect.Empty; + /// + /// Super rect containing all none sighter things casting from the prev cycle. + /// + private CellRect suRectPrev_Combatant = CellRect.Empty; + /// + /// Ticks until update. + /// + private int ticksUntilUpdate; + /// + /// Whether this is the player grid + /// + public bool trackFactions = false; + private bool wait; - private IntVec3 suCentroid; - private IntVec3 suCentroidPrev; - /// - /// Super rect containing all none sighter things casting. - /// - private CellRect suRect_Combatant = CellRect.Empty; - /// - /// Super rect containing all none sighter things casting from the prev cycle. - /// - private CellRect suRectPrev_Combatant = CellRect.Empty; - /// - /// Ticks until update. - /// - private int ticksUntilUpdate; - /// - /// Whether this is the player grid - /// - public bool trackFactions = false; - /// - /// SightGrid Id. - /// - public readonly int gridId; - private bool wait; + public SightGrid(SightTracker sightTracker, Settings.SightPerformanceSettings settings, int gridId) + { + this.gridId = gridId; + this.sightTracker = sightTracker; + thingsUInt64Map = new IThingsUInt64Map(); + map = sightTracker.map; + this.settings = settings; + grid = new ITSignalGrid(map); + if (!Extern.active) + { + grid_regions = new ITRegionGridLegacy(map); + } + else + { + grid_regions = new ITRegionGridPrepatched(map, gridId); + } + asyncActions = new AsyncActions(1); + ticksUntilUpdate = Rand.Int % this.settings.interval; + buckets = new IBuckets(settings.buckets); + suRect_Combatant = new CellRect(); + suRect_Combatant.minX = map.cellIndices.mapSizeX; + suRect_Combatant.maxX = 0; + suRect_Combatant.minZ = map.cellIndices.mapSizeZ; + suRect_Combatant.maxZ = 0; + suRectPrev_Combatant = CellRect.Empty; + flooder = new CellFlooder(map); + } + /// + /// Tracks the number of factions tracked. + /// + public int FactionNum + { + get => numsByFaction.Count; + } + /// + /// CellRect containing all combatant pawns. + /// + public CellRect SuRect_Combatant + { + get => suRectPrev_Combatant; + } + /// + /// Avg position of combatant pawns. + /// + public IntVec3 SuCentroid + { + get => suCentroidPrev; + } + /// + /// The map's wallgrid. + /// + public WallGrid Walls + { + get => _walls != null ? _walls : _walls = sightTracker.map.GetComp_Fast(); + } - public SightGrid(SightTracker sightTracker, Settings.SightPerformanceSettings settings, int gridId) - { - this.gridId = gridId; - this.sightTracker = sightTracker; - map = sightTracker.map; - this.settings = settings; - grid = new ITSignalGrid(map); - if (!Extern.active) - grid_regions = new ITRegionGridLegacy(map); - else - grid_regions = new ITRegionGridPrepatched(map, gridId); - asyncActions = new AsyncActions(1); - ticksUntilUpdate = Rand.Int % this.settings.interval; - buckets = new IBuckets(settings.buckets); - suRect_Combatant = new CellRect(); - suRect_Combatant.minX = map.cellIndices.mapSizeX; - suRect_Combatant.maxX = 0; - suRect_Combatant.minZ = map.cellIndices.mapSizeZ; - suRect_Combatant.maxZ = 0; - suRectPrev_Combatant = CellRect.Empty; - flooder = new CellFlooder(map); - } - /// - /// Tracks the number of factions tracked. - /// - public int FactionNum - { - get => numsByFaction.Count; - } - /// - /// CellRect containing all combatant pawns. - /// - public CellRect SuRect_Combatant - { - get => suRectPrev_Combatant; - } - /// - /// Avg position of combatant pawns. - /// - public IntVec3 SuCentroid - { - get => suCentroidPrev; - } - /// - /// The map's wallgrid. - /// - public WallGrid Walls - { - get => _walls != null ? _walls : _walls = sightTracker.map.GetComp_Fast(); - } + public void FinalizeInit() + { + // initialize the region grid. + for (int i = 0; i < map.cellIndices.NumGridCells; i++) + { + grid_regions.SetRegionAt(i, map.regionGrid.regionGrid[i]); + } + // start async actions. + asyncActions.Start(); + } - public void FinalizeInit() - { - // initialize the region grid. - for (int i = 0; i < map.cellIndices.NumGridCells; i++) - { - grid_regions.SetRegionAt(i, map.regionGrid.regionGrid[i]); - } - // start async actions. - asyncActions.Start(); - } + public virtual void SightGridOptionalUpdate(bool gamePaused, bool performanceOkay) + { + if (gamePaused || performanceOkay) + { + int limit = gamePaused ? 32 : 8; + int numGridCells = map.cellIndices.NumGridCells; + for (int i = 0; i < limit; i++) + { + Region region = map.regionGrid.regionGrid[regionUpdateIndex]; + if (region?.valid ?? false) + { + grid_regions.SetRegionAt(regionUpdateIndex, region); + regionUpdateIndex++; + if (regionUpdateIndex >= numGridCells) + { + regionUpdateIndex = 0; + } + } + } + } + } - public virtual void SightGridUpdate(bool gamePaused, bool performanceOkay) - { - if (gamePaused || performanceOkay) - { -// int limit = gamePaused ? 32 : 8; -// int numGridCells = map.cellIndices.NumGridCells; -// for (int i = 0; i < limit; i++) -// { -// Region region = map.regionGrid.regionGrid[regionUpdateIndex]; -// if (region?.valid ?? false) -// { -// grid_regions.SetRegionAt(regionUpdateIndex, region); -// regionUpdateIndex++; -// if (regionUpdateIndex >= numGridCells) -// { -// regionUpdateIndex = 0; -// } -// } -// } - } - } - - public virtual void SightGridTick() - { - asyncActions.ExecuteMainThreadActions(); - if (ticksUntilUpdate-- > 0 || wait) - { - return; - } - tmpInvalidRecords.Clear(); - tmpInconsistentRecords.Clear(); - List bucket = buckets.Current; - for (int i = 0; i < bucket.Count; i++) - { - try - { - IBucketableThing item = bucket[i]; - if (!Valid(item)) - { - tmpInvalidRecords.Add(item); - continue; - } - if (!Consistent(item)) - { - tmpInconsistentRecords.Add(item); - continue; - } - TryCastSight(item); - } - catch (Exception er) - { - er.ShowExceptionGui(); - } - } - if (tmpInvalidRecords.Count != 0) - { - for (int i = 0; i < tmpInvalidRecords.Count; i++) - { - TryDeRegister(tmpInvalidRecords[i].thing); - } - tmpInvalidRecords.Clear(); - } - if (tmpInconsistentRecords.Count != 0) - { - for (int i = 0; i < tmpInconsistentRecords.Count; i++) - { - TryDeRegister(tmpInconsistentRecords[i].thing); - sightTracker.Register(tmpInconsistentRecords[i].thing); - } - tmpInconsistentRecords.Clear(); - } - ticksUntilUpdate = settings.interval + Mathf.CeilToInt(settings.interval * (1.0f - Finder.P50)); - buckets.Next(); - if (buckets.Index == 0) - { - wait = true; - asyncActions.EnqueueOffThreadAction(delegate - { - asyncActions.EnqueueMainThreadAction(Continue); - }); - } - } + public void SightGridUpdate() + { + asyncActions.ExecuteMainThreadActions(); + if (ticksUntilUpdate-- > 0 || wait) + { + return; + } + tmpInvalidRecords.Clear(); + tmpInconsistentRecords.Clear(); + List bucket = buckets.Current; + for (int i = 0; i < bucket.Count; i++) + { + try + { + IBucketableThing item = bucket[i]; + if (!Valid(item)) + { + tmpInvalidRecords.Add(item); + continue; + } + if (!Consistent(item)) + { + tmpInconsistentRecords.Add(item); + continue; + } + TryCastSight(item); + } + catch (Exception er) + { + er.ShowExceptionGui(); + } + } + if (tmpInvalidRecords.Count != 0) + { + for (int i = 0; i < tmpInvalidRecords.Count; i++) + { + TryDeRegister(tmpInvalidRecords[i].thing); + } + tmpInvalidRecords.Clear(); + } + if (tmpInconsistentRecords.Count != 0) + { + for (int i = 0; i < tmpInconsistentRecords.Count; i++) + { + TryDeRegister(tmpInconsistentRecords[i].thing); + sightTracker.Register(tmpInconsistentRecords[i].thing); + } + tmpInconsistentRecords.Clear(); + } + ticksUntilUpdate = settings.interval + Mathf.CeilToInt(settings.interval * (1.0f - Finder.P50)); + buckets.Next(); + if (buckets.Index == 0) + { + wait = true; + asyncActions.EnqueueOffThreadAction(delegate + { + asyncActions.EnqueueMainThreadAction(Continue); + }); + } + } - public virtual void Register(Thing thing) - { - buckets.RemoveId(thing.thingIDNumber); - if (Valid(thing)) - { - IBucketableThing item; - buckets.Add(item = new IBucketableThing(this, thing, (thing.thingIDNumber + 19) % settings.buckets)); - if (trackFactions) - { - numsByFaction.TryGetValue(thing.Faction, out int num); - numsByFaction[thing.Faction] = num + 1; - } - } - } + public void GetThings(ulong flags, IntVec3 cell, List buffer, bool validateLoS = false) + { + _getThings.Clear(); + thingsUInt64Map.GetThings(flags, _getThings); + if (_getThings.Count > 0) + { + for (int i = 0; i < _getThings.Count; i++) + { + Thing thing = _getThings[i]; + if (thing != null && thing.Spawned) + { + if (thing.Position.DistanceToSquared(cell) < Maths.Sqr(SightUtility.GetSightRadius_Fast(thing))) + { + if (validateLoS) + { + Verb verb = thing.TryGetAttackVerb(); + if (verb != null) + { + if (verb.IsMeleeAttack && !GenSight.LineOfSight(cell, thing.Position, map, true) || !verb.CanHitCellFromCellIgnoringRange(thing.Position, cell)) + { + continue; + } + } + } + buffer.Add(thing); + } + } + } + // cleanup. + _getThings.Clear(); + } + } - public virtual void TryDeRegister(Thing thing) - { - if (trackFactions) - { - IBucketableThing bucketable = buckets.GetById(thing.thingIDNumber); - if (bucketable != null && numsByFaction.TryGetValue(bucketable.registeredFaction, out int num)) - { - if (num > 1) - { - numsByFaction[bucketable.registeredFaction] = num - 1; - } - else - { - numsByFaction.Remove(bucketable.registeredFaction); - } - } - } - buckets.RemoveId(thing.thingIDNumber); - } + public virtual void Register(Thing thing) + { + buckets.RemoveId(thing.thingIDNumber); + if (Valid(thing)) + { + IBucketableThing item; + thingsUInt64Map.Add(thing); + buckets.Add(item = new IBucketableThing(this, thing, (thing.thingIDNumber + 19) % settings.buckets)); + if (trackFactions) + { + numsByFaction.TryGetValue(thing.Faction, out int num); + numsByFaction[thing.Faction] = num + 1; + } + } + } - public virtual void Destroy() - { - try - { - buckets.Release(); - asyncActions.Kill(); - } - catch (Exception er) - { - Log.Error($"CAI: SightGridManager Notify_MapRemoved failed to stop thread with {er}"); - } - } + public virtual void TryDeRegister(Thing thing) + { + if (trackFactions) + { + IBucketableThing bucketable = buckets.GetById(thing.thingIDNumber); + thingsUInt64Map.Remove(thing); + if (bucketable != null && numsByFaction.TryGetValue(bucketable.registeredFaction, out int num)) + { + if (num > 1) + { + numsByFaction[bucketable.registeredFaction] = num - 1; + } + else + { + numsByFaction.Remove(bucketable.registeredFaction); + } + } + } + buckets.RemoveId(thing.thingIDNumber); + } - private void Continue() - { - suRectPrev_Combatant = suRect_Combatant; - suRect_Combatant.minX = map.cellIndices.mapSizeX; - suRect_Combatant.maxX = 0; - suRect_Combatant.minZ = map.cellIndices.mapSizeZ; - suRect_Combatant.maxZ = 0; - suRectPrev_Combatant.minX -= 5; - suRectPrev_Combatant.minZ -= 5; - suRectPrev_Combatant.maxX += 5; - suRectPrev_Combatant.maxZ += 5; - suCentroidPrev = suCentroid; - gridFog?.NextCycle(); - grid.NextCycle(); - grid_regions.NextCycle(); - wait = false; - suCentroidPrev = suCentroid; - suCentroidPrev.x = Mathf.CeilToInt(suCentroidPrev.x / (ops + 1e-3f)); - suCentroidPrev.z = Mathf.CeilToInt(suCentroidPrev.z / (ops + 1e-3f)); - suCentroid = IntVec3.Zero; - ops = 0; - } + public virtual void Destroy() + { + try + { + buckets.Release(); + asyncActions.Kill(); + } + catch (Exception er) + { + Log.Error($"CAI: SightGridManager Notify_MapRemoved failed to stop thread with {er}"); + } + } - private bool Consistent(IBucketableThing item) - { - if (item.registeredFaction != item.thing.Faction) - { - return false; - } - return true; - } + private void Continue() + { + suRectPrev_Combatant = suRect_Combatant; + suRect_Combatant.minX = map.cellIndices.mapSizeX; + suRect_Combatant.maxX = 0; + suRect_Combatant.minZ = map.cellIndices.mapSizeZ; + suRect_Combatant.maxZ = 0; + suRectPrev_Combatant.minX -= 5; + suRectPrev_Combatant.minZ -= 5; + suRectPrev_Combatant.maxX += 5; + suRectPrev_Combatant.maxZ += 5; + suCentroidPrev = suCentroid; + gridFog?.NextCycle(); + grid.NextCycle(); + grid_regions.NextCycle(); + wait = false; + suCentroidPrev = suCentroid; + suCentroidPrev.x = Mathf.CeilToInt(suCentroidPrev.x / (ops + 1e-3f)); + suCentroidPrev.z = Mathf.CeilToInt(suCentroidPrev.z / (ops + 1e-3f)); + suCentroid = IntVec3.Zero; + ops = 0; + } - private bool Valid(Thing thing) - { - if (thing == null) - { - return false; - } - if (thing.Destroyed || !thing.Spawned) - { - return false; - } - return thing is Pawn pawn && !pawn.Dead || thing is Building_Turret || thing.def.HasComp(typeof(ThingComp_Sighter)) || thing.def.HasComp(typeof(ThingComp_CCTVTop)); - } + private bool Consistent(IBucketableThing item) + { + if (item.registeredFaction != item.thing.Faction) + { + return false; + } + return true; + } - private bool Valid(IBucketableThing item) - { - return !item.thing.Destroyed && item.thing.Spawned && (item.Pawn == null || !item.Pawn.Dead); - } + private bool Valid(Thing thing) + { + if (thing == null) + { + return false; + } + if (thing.Destroyed || !thing.Spawned) + { + return false; + } + return thing is Pawn pawn && !Mod_ZombieLand.IsZombie(pawn) && !pawn.Dead || thing is Building_Turret || thing.def.HasComp(typeof(ThingComp_Sighter)) || thing.def.HasComp(typeof(ThingComp_CCTVTop)); + } - private bool Skip(IBucketableThing item) - { - if (!playerAlliance && item.dormant != null) - { - return !item.dormant.Awake || item.dormant.WaitingToWakeUp; - } - if (item.Pawn != null) - { - return !playerAlliance && (GenTicks.TicksGame - item.Pawn.needs?.rest?.lastRestTick <= 30 || item.Pawn.Downed); - } - if (item.sighter != null) - { - return playerAlliance && !item.sighter.Active; - } - if (item.TurretGun != null) - { - return playerAlliance && (!item.TurretGun.Active || item.TurretGun.IsMannable && !(item.TurretGun.mannableComp?.MannedNow ?? false)); - } - if (Mod_CE.active && item.thing is Building_Turret turret) - { - return !Mod_CE.IsTurretActiveCE(turret); - } - return false; - } + private bool Valid(IBucketableThing item) + { + return !item.thing.Destroyed && item.thing.Spawned && (item.Pawn == null || !item.Pawn.Dead); + } - private ulong GetFlags(IBucketableThing item) - { - return item.thing.GetThingFlags(); - } + private bool Skip(IBucketableThing item) + { + if (!playerAlliance && item.dormant != null) + { + return !item.dormant.Awake || item.dormant.WaitingToWakeUp; + } + if (item.Pawn != null) + { + return !playerAlliance && (GenTicks.TicksGame - item.Pawn.needs?.rest?.lastRestTick <= 30 || item.Pawn.Downed); + } + if (item.sighter != null) + { + return playerAlliance && !item.sighter.Active; + } + if (item.TurretGun != null) + { + return playerAlliance && (!item.TurretGun.Active || item.TurretGun.IsMannable && !(item.TurretGun.mannableComp?.MannedNow ?? false)); + } + if (Mod_CE.active && item.thing is Building_Turret turret) + { + return !Mod_CE.IsTurretActiveCE(turret); + } + return false; + } - private bool TryCastSight(IBucketableThing item) - { - if (grid.CycleNum == item.lastCycle || Skip(item)) - { - return false; - } - if (!item.cachedSightRadius.IsValid) - { - item.cachedSightRadius = SightUtility.GetSightRadius(item.thing); - } - if (item.cachedSightRadius.sight == 0) - { - return false; - } - if (!!item.cachedDamage.IsValid) - { - item.cachedDamage = DamageUtility.GetDamageReport(item.thing); - } - int ticks = GenTicks.TicksGame; - IntVec3 origin = item.thing.Position; - IntVec3 pos = GetShiftedPosition(item.thing, 30, item.path); - if (!pos.InBounds(map)) - { - Log.Error($"ISMA: SighGridUpdater {item.thing} position is outside the map's bounds!"); - return false; - } - IntVec3 flagPos = pos; - if (item.Pawn != null) - { - flagPos = GetShiftedPosition(item.Pawn, 60, null); - } - SightTracker.SightReader reader = item.ai?.sightReader ?? null; - bool scanForEnemies; - if (scanForEnemies = Finder.Settings.React_Enabled && item.sighter == null && reader != null && item.ai != null && !item.ai.ReactedRecently(45) && ticks - item.lastScannedForEnemies >= (!Finder.Performance.TpsCriticallyLow ? 10 : 15)) - { - if (!item.registeredFaction.IsPlayerSafe() || (item.ai?.forcedTarget.IsValid ?? false)) - { - if (item.dormant != null && !item.dormant.Awake) - { - scanForEnemies = false; - } - else if (item.Pawn != null && item.Pawn.mindState?.duty?.def == DutyDefOf.SleepForever) - { - scanForEnemies = false; - } - } - } - bool defenseMode = false; - if (scanForEnemies) - { - if (item.Pawn != null && (item.Pawn.mindState.duty.Is(DutyDefOf.Defend) || item.Pawn.mindState.duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint)) && item.Pawn.CurJob.Is(JobDefOf.Wait_Wander)) - { - defenseMode = true; - } - item.lastScannedForEnemies = ticks; - try - { - item.ai.OnScanStarted(); - } - catch (Exception er) - { - er.ShowExceptionGui(); - } - item.spottings.Clear(); - } - if (scanForEnemies || item.sighter == null && item.CctvTop == null) - { - suRect_Combatant.minX = Maths.Min(suRect_Combatant.minX, pos.x); - suRect_Combatant.maxX = Maths.Max(suRect_Combatant.maxX, pos.x); - suRect_Combatant.minZ = Maths.Min(suRect_Combatant.minZ, pos.z); - suRect_Combatant.maxZ = Maths.Max(suRect_Combatant.maxZ, pos.z); - ops += 1; - suCentroid += pos; - } - MetaCombatAttribute availability = 0; - if (item.thing != null) - { - Verb verb = item.thing.TryGetAttackVerb(); - if (verb != null) - { - if (!verb.IsMeleeAttack) - { - if (verb.WarmingUp || verb.Bursting) - { - availability = MetaCombatAttribute.Occupied; - } - else - { - availability = MetaCombatAttribute.Free; - } - } - else if (item.Pawn != null) - { - if (item.Pawn.mindState.MeleeThreatStillThreat) - { - availability = MetaCombatAttribute.Occupied; - } - else - { - availability = MetaCombatAttribute.Free; - } - } - } - } - bool engagedInMelee = item.Pawn?.mindState.MeleeThreatStillThreat == true; - scanForEnemies &= !engagedInMelee; - ISightRadius sightRadius = item.cachedSightRadius; - Action action = () => - { - if (playerAlliance && sightRadius.fog > 0) - { - gridFog.Next(); - gridFog.Set(origin, 1.0f); - for (int i = 0; i < item.path.Count; i++) - { - gridFog.Set(item.path[i], 1.0f); - } - } - MetaCombatAttribute attr = item.cachedDamage.attributes | availability; - grid.Next(item.cachedDamage.adjustedSharp, item.cachedDamage.adjustedBlunt, attr); - grid_regions.Next(); - float r_fade = sightRadius.fog * Finder.Settings.FogOfWar_RangeFadeMultiplier; - float d_fade = sightRadius.fog - r_fade; - float rSqr_sight = Maths.Sqr(sightRadius.sight); - float rSqr_scan = Maths.Sqr(sightRadius.scan); - float rSqr_fog = Maths.Sqr(sightRadius.fog); - float rSqr_fade = Maths.Sqr(r_fade); - Action setAction = (cell, carry, dist, coverRating) => - { - float d2 = pos.DistanceToSquared(cell); - float visibility = 0f; - if (!engagedInMelee && d2 < rSqr_sight) - { - visibility = Maths.Max(1f - coverRating, 0.20f); - if (visibility > 0f) - { - grid.Set(cell, visibility, new Vector2(cell.x - pos.x, cell.z - pos.z)); - grid_regions.Set(cell); - } - } - if (playerAlliance && d2 < rSqr_fog) - { - float val; - if (d2 < rSqr_fade) - { - val = 1f; - } - else - { - val = 1f - Mathf.Clamp01((Maths.Sqrt_Fast(d2, 5) - r_fade) / d_fade); - } - gridFog?.Set(cell, val); - } - if (scanForEnemies && d2 < rSqr_scan) - { - ulong flag = reader.GetEnemyFlags(cell); - if (flag != 0) - { - ISpotRecord spotting = new ISpotRecord(); - spotting.cell = cell; - spotting.flag = flag; - spotting.state = (int)AIEnvAgentState.visible; - spotting.score = visibility; - item.spottings.Add(spotting); - } - } - }; - if (item.CctvTop == null) - { - ShadowCastingUtility.CastWeighted(map, pos, setAction, Maths.Max(sightRadius.scan, sightRadius.fog, sightRadius.sight), settings.carryLimit, buffer); - } - else - { - ShadowCastingUtility.CastWeighted(map, pos, item.CctvTop.LookDirection, setAction, Maths.Max(sightRadius.scan, sightRadius.fog, sightRadius.sight), item.CctvTop.BaseWidth, settings.carryLimit, buffer); - } - flooder.Flood(origin, node => - { - if (!grid.IsSet(node.cell)) - { - grid.Set(node.cell, 0.198f, new Vector2(node.cell.x - node.parent.x, node.cell.z - node.parent.z)); - grid_regions.Set(node.cell); - } - if (scanForEnemies) - { - ulong flag = reader.GetEnemyFlags(node.cell) | reader.GetFriendlyFlags(node.cell); - if (flag != 0) - { - ISpotRecord spotting = new ISpotRecord(); - spotting.cell = node.cell; - spotting.flag = flag; - spotting.state = (int)AIEnvAgentState.nearby; - spotting.score = node.distAbs; - item.spottings.Add(spotting); - } - } - }, maxDist: defenseMode ? 32 : 12, maxCellNum: defenseMode ? 325 : 225, passThroughDoors: true); + private ulong GetFlags(IBucketableThing item) + { + return item.thing.GetThingFlags(); + } - grid.Set(origin, 1.0f, new Vector2(origin.x - pos.x, origin.z - pos.z)); - grid.Set(pos, 1.0f, new Vector2(origin.x - pos.x, origin.z - pos.z)); - grid.Next(0, 0, item.cachedDamage.attributes); - grid.Set(flagPos, item.Pawn == null || !item.Pawn.Downed ? GetFlags(item) : 0); - if (scanForEnemies) - { - if (item.ai.data.NumEnemies > 0 || item.ai.data.NumAllies > 0 || item.spottings.Count > 0 || Finder.Settings.Debug && Finder.Settings.Debug_ValidateSight) - { - // on the main thread check for enemies on or near this cell. - asyncActions.EnqueueMainThreadAction(delegate - { - if (!item.thing.Destroyed && item.thing.Spawned) - { - for (int i = 0; i < item.spottings.Count; i++) - { - ISpotRecord record = item.spottings[i]; - thingBuffer1.Clear(); - sightTracker.factionedUInt64Map.GetThings(record.flag, thingBuffer1); - for (int j = 0; j < thingBuffer1.Count; j++) - { - Thing agent = thingBuffer1[j]; - if (agent != item.thing - && agent.Spawned - && (agent.Position.DistanceToSquared(record.cell) < 225 || agent is Pawn enemyPawn && PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 70).DistanceToSquared(record.cell) < 255) - && !agent.IsDormant()) - { - if (agent.HostileTo(item.thing)) - { - item.ai.Notify_Enemy(new AIEnvAgentInfo(agent, (AIEnvAgentState)record.state)); - } - else - { - item.ai.Notify_Ally(new AIEnvAgentInfo(agent, (AIEnvAgentState)record.state)); - } - } - } - } - // - // notify the pawn so they can start processing targets. - try - { - item.ai.OnScanFinished(); - } - catch (Exception er) - { - er.ShowExceptionGui(); - } - item.spottings.Clear(); - } - }); - } - } - }; - asyncActions.EnqueueOffThreadAction(action); - item.lastCycle = grid.CycleNum; - return true; - } + private bool TryCastSight(IBucketableThing item) + { + if (grid.CycleNum == item.lastCycle || Skip(item)) + { + return false; + } + if (!item.cachedSightRadius.IsValid) + { + item.cachedSightRadius = SightUtility.GetSightRadius(item.thing); + } + if (item.cachedSightRadius.sight == 0) + { + return false; + } + if (!!item.cachedDamage.IsValid) + { + item.cachedDamage = DamageUtility.GetDamageReport(item.thing); + } + int ticks = GenTicks.TicksGame; + IntVec3 origin = item.thing.Position; + IntVec3 pos = GetShiftedPosition(item.thing, (int)Maths.Min(settings.interval * settings.buckets * Find.TickManager.TickRateMultiplier, 90), item.path); + if (!pos.InBounds(map)) + { + Log.Error($"ISMA: SighGridUpdater {item.thing} position is outside the map's bounds!"); + return false; + } + IntVec3 flagPos = pos; + if (item.Pawn != null) + { + flagPos = GetShiftedPosition(item.Pawn, (int)Maths.Min(60 * Find.TickManager.TickRateMultiplier, 80), null); + } + SightTracker.SightReader reader = item.ai?.sightReader ?? null; + bool scanForEnemies; + if (scanForEnemies = (Finder.Settings.GetDefKindSettings(item.Pawn)?.React_Enabled ?? true) && item.sighter == null && reader != null && item.ai != null && !item.ai.ReactedRecently(45) && ticks - item.lastScannedForEnemies >= (!Finder.Performance.TpsCriticallyLow ? 10 : 15)) + { + if (!item.registeredFaction.IsPlayerSafe() || (item.ai?.forcedTarget.IsValid ?? false)) + { + if (item.dormant != null && !item.dormant.Awake) + { + scanForEnemies = false; + } + else if (item.Pawn != null && item.Pawn.mindState?.duty?.def == DutyDefOf.SleepForever) + { + scanForEnemies = false; + } + } + } + bool defenseMode = false; + if (scanForEnemies) + { + if (item.Pawn != null && (item.Pawn.mindState.duty.Is(DutyDefOf.Defend) || item.Pawn.mindState.duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint)) && item.Pawn.CurJob.Is(JobDefOf.Wait_Wander)) + { + defenseMode = true; + } + item.lastScannedForEnemies = ticks; + try + { + item.ai.OnScanStarted(); + } + catch (Exception er) + { + er.ShowExceptionGui(); + } + item.spottings.Clear(); + } + if (scanForEnemies || item.sighter == null && item.CctvTop == null) + { + suRect_Combatant.minX = Maths.Min(suRect_Combatant.minX, pos.x); + suRect_Combatant.maxX = Maths.Max(suRect_Combatant.maxX, pos.x); + suRect_Combatant.minZ = Maths.Min(suRect_Combatant.minZ, pos.z); + suRect_Combatant.maxZ = Maths.Max(suRect_Combatant.maxZ, pos.z); + ops += 1; + suCentroid += pos; + } + MetaCombatAttribute availability = 0; + bool engagedInMelee = false; + if (item.Pawn != null) + { + if ((engagedInMelee = item.Pawn.mindState.MeleeThreatStillThreat) || item.Pawn.stances?.curStance is Stance_Warmup) + { + availability = MetaCombatAttribute.Occupied; + } + else + { + availability = MetaCombatAttribute.Free; + } + } + Vector3 drawPos = item.thing.DrawPos; + scanForEnemies &= !engagedInMelee; + ISightRadius sightRadius = item.cachedSightRadius; + Action action = () => + { + float r_fade = sightRadius.fog * Finder.Settings.FogOfWar_RangeFadeMultiplier; + float d_fade = sightRadius.fog - r_fade; + float rSqr_sight = Maths.Sqr(sightRadius.sight); + float rSqr_scan = Maths.Sqr(sightRadius.scan); + float rSqr_fog = Maths.Sqr(sightRadius.fog); + float rSqr_fade = Maths.Sqr(r_fade); + if (playerAlliance && sightRadius.fog > 0) + { + gridFog.Next(); + gridFog.Set(origin, 1.0f); + for (int i = 0; i < item.path.Count; i++) + { + IntVec3 cell = item.path[i]; + float d3 = Maths.Sqr(drawPos.x - cell.x) + Maths.Sqr(drawPos.z - cell.z); + float val; + if (d3 < rSqr_fade) + { + val = 1f; + } + else + { + val = 1f - Mathf.Clamp01((Maths.Sqrt_Fast(d3, 5) - r_fade) / d_fade); + } + gridFog.Set(cell, val); + } + } + MetaCombatAttribute attr = item.cachedDamage.attributes | availability; + grid.Next(pos, GetFlags(item), item.cachedDamage.adjustedSharp, item.cachedDamage.adjustedBlunt, attr); + grid_regions.Next(); + Action setAction = (cell, carry, dist, coverRating) => + { + float d2 = pos.DistanceToSquared(cell); + float visibility = 0f; + if (!engagedInMelee) + { + if (d2 < rSqr_sight) + { + visibility = Maths.Max(1f - coverRating, 0.20f); + if (visibility > 0f) + { + grid.Set(cell, visibility, new Vector2(cell.x - pos.x, cell.z - pos.z)); + grid_regions.Set(cell); + } + } + else if (d2 < 360) + { + grid.Set(cell, 0, new Vector2(cell.x - pos.x, cell.z - pos.z)); + grid_regions.Set(cell); + } + } + if (playerAlliance && d2 < rSqr_fog) + { + float d3 = Maths.Sqr(drawPos.x - cell.x) + Maths.Sqr(drawPos.z - cell.z); + float val; + if (d3 < rSqr_fade) + { + val = 1f; + } + else + { + val = 1f - Mathf.Clamp01((Maths.Sqrt_Fast(d3, 5) - r_fade) / d_fade); + } + gridFog?.Set(cell, val); + } + if (scanForEnemies && d2 < rSqr_scan) + { + ulong flag = reader.GetStaticEnemyFlags(cell); + if (flag != 0) + { + ISpotRecord spotting = new ISpotRecord(); + spotting.cell = cell; + spotting.flag = flag; + spotting.state = (int)AIEnvAgentState.visible; + spotting.score = visibility; + item.spottings.Add(spotting); + } + } + }; + if (item.CctvTop == null) + { + ShadowCastingUtility.CastWeighted(map, pos, setAction, Maths.Max(sightRadius.scan, sightRadius.fog, sightRadius.sight), settings.carryLimit, buffer); + } + else + { + ShadowCastingUtility.CastWeighted(map, pos, item.CctvTop.LookDirection, setAction, Maths.Max(sightRadius.scan, sightRadius.fog, sightRadius.sight), item.CctvTop.BaseWidth, settings.carryLimit, buffer); + } + grid.curRoot = IntVec3.Invalid; + flooder.Flood(origin, node => + { + if (!grid.IsSet(node.cell)) + { + grid.Set(node.cell, 0.198f, new Vector2(node.cell.x - node.parent.x, node.cell.z - node.parent.z)); + grid_regions.Set(node.cell); + } + if (scanForEnemies) + { + ulong flag = reader.GetStaticEnemyFlags(node.cell) | reader.GetStaticFriendlyFlags(node.cell); + if (flag != 0) + { + ISpotRecord spotting = new ISpotRecord(); + spotting.cell = node.cell; + spotting.flag = flag; + spotting.state = (int)AIEnvAgentState.nearby; + spotting.score = node.distAbs; + item.spottings.Add(spotting); + } + } + }, maxDist: defenseMode ? 32 : 12, maxCellNum: defenseMode ? 325 : 225, passThroughDoors: true); + grid.Set(origin, 1.0f, new Vector2(origin.x - pos.x, origin.z - pos.z)); + grid.Set(pos, 1.0f, new Vector2(origin.x - pos.x, origin.z - pos.z)); + grid.Next(pos, GetFlags(item), 0, 0, item.cachedDamage.attributes); + grid.Set(flagPos, item.Pawn == null || !item.Pawn.Downed ? GetFlags(item) : 0); + if (scanForEnemies) + { + if (item.ai.data.NumEnemies > 0 || item.ai.data.NumAllies > 0 || item.spottings.Count > 0 || Finder.Settings.Debug && Finder.Settings.Debug_ValidateSight) + { + // on the main thread check for enemies on or near this cell. + asyncActions.EnqueueMainThreadAction(delegate + { + if (!item.thing.Destroyed && item.thing.Spawned) + { + for (int i = 0; i < item.spottings.Count; i++) + { + ISpotRecord record = item.spottings[i]; + thingBuffer1.Clear(); + sightTracker.factionedUInt64Map.GetThings(record.flag, thingBuffer1); + for (int j = 0; j < thingBuffer1.Count; j++) + { + Thing agent = thingBuffer1[j]; + if (agent != item.thing + && agent.Spawned + && (agent.Position.DistanceToSquared(record.cell) < 225 || agent is Pawn enemyPawn && PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 70).DistanceToSquared(record.cell) < 255) + && !agent.IsDormant()) + { + if (agent.HostileTo(item.thing)) + { + item.ai.Notify_Enemy(new AIEnvAgentInfo(agent, (AIEnvAgentState)record.state)); + } + else + { + item.ai.Notify_Ally(new AIEnvAgentInfo(agent, (AIEnvAgentState)record.state)); + } + } + } + } + int p = 0; + // + // notify the pawn so they can start processing targets. + try + { + item.ai.OnScanFinished(ref p); + } + catch (Exception er) + { + Log.Error($"progress:{p} {item.thing?.thingIDNumber} {item.thing?.ToString()} encountered an error while doing OnScanFinished"); + er.ShowExceptionGui(); + } + item.spottings.Clear(); + } + }); + } + } + }; + asyncActions.EnqueueOffThreadAction(action); + item.lastCycle = grid.CycleNum; + return true; + } - private IntVec3 GetShiftedPosition(Thing thing, int ticksAhead, List subPath) - { - if (thing is Pawn pawn) - { - WallGrid walls = Walls; - if (subPath != null) - { - subPath.Clear(); - } - if (walls != null && PawnPathUtility.TryGetCellIndexAhead(pawn, ticksAhead, out int index)) - { - PawnPath path = pawn.pather.curPath; - IntVec3 cell = pawn.Position; - IntVec3 temp; - for (int i = 0; i < index; i++) - { - if (!walls.CanBeSeenOver(temp = path.Peek(i))) - { - return cell; - } - cell = temp; - if (subPath != null) - { - subPath.Add(cell); - } - } - return cell; - } - return thing.Position; - } - return thing.Position; - } + private IntVec3 GetShiftedPosition(Thing thing, int ticksAhead, List subPath) + { + if (thing is Pawn pawn) + { + WallGrid walls = Walls; + if (subPath != null) + { + subPath.Clear(); + } + if (walls != null && PawnPathUtility.TryGetCellIndexAhead(pawn, ticksAhead, out int index)) + { + PawnPath path = pawn.pather.curPath; + IntVec3 cell = pawn.Position; + IntVec3 temp; + for (int i = 0; i < index; i++) + { + if (!walls.CanBeSeenOver(temp = path.Peek(i))) + { + return cell; + } + cell = temp; + if (subPath != null) + { + subPath.Add(cell); + } + } + return cell; + } + return thing.Position; + } + return thing.Position; + } - public struct ISightRadius - { - /// - /// Scan radius. Used while scanning for enemies. - /// - public int scan; - /// - /// Sight radius. Determine the radius of the thing's influence map. - /// - public int sight; - /// - /// Fog radius. Determine how far this thing will reveal fog of war. - /// - public int fog; - /// - /// Creation tick timestamp. - /// - public int createdAt; - /// - /// Whether this is a valid report. - /// - public bool IsValid - { - get => createdAt != 0 && GenTicks.TicksGame - createdAt < 600; - } - } + public struct ISightRadius + { + /// + /// Scan radius. Used while scanning for enemies. + /// + public int scan; + /// + /// Sight radius. Determine the radius of the thing's influence map. + /// + public int sight; + /// + /// Fog radius. Determine how far this thing will reveal fog of war. + /// + public int fog; + /// + /// Creation tick timestamp. + /// + public int createdAt; + /// + /// Whether this is a valid report. + /// + public bool IsValid + { + get => createdAt != 0 && GenTicks.TicksGame - createdAt < 600; + } + } - private struct ISpotRecord - { - /// - /// Spotted flag. - /// - public ulong flag; - /// - /// Cell at which the spotting occured. - /// - public IntVec3 cell; - /// - /// state - /// - public int state; - /// - /// Cell visibility. - /// - public float score; - } + private struct ISpotRecord + { + /// + /// Spotted flag. + /// + public ulong flag; + /// + /// Cell at which the spotting occured. + /// + public IntVec3 cell; + /// + /// state + /// + public int state; + /// + /// Cell visibility. + /// + public float score; + } - private class IBucketableThing : IBucketable - { - /// - /// Dormant comp. - /// - public readonly ThingComp_CombatAI ai; - /// - /// Sighting component. - /// - public readonly ThingComp_CCTVTop CctvTop; - /// - /// Dormant comp. - /// - public readonly CompCanBeDormant dormant; - /// - /// Current sight grid. - /// - public readonly SightGrid grid; - /// - /// Pawn pawn - /// - public readonly List path = new List(16); - /// - /// Thing's faction on IBucketableThing instance creation. - /// - public readonly Faction registeredFaction; - /// - /// Sighting component. - /// - public readonly ThingComp_Sighter sighter; - /// - /// Contains spotting records that are to be processed on the main thread once the scan is finished. - /// - public readonly List spottings = new List(16); - /// - /// Thing. - /// - public readonly Thing thing; - /// - /// Thing potential damage report. - /// - public DamageReport cachedDamage; - /// - /// Cached sight radius report. - /// - public ISightRadius cachedSightRadius; - /// - /// Last cycle. - /// - public int lastCycle; - /// - /// Last tick this pawn scanned for enemies - /// - public int lastScannedForEnemies; + private class IBucketableThing : IBucketable + { + /// + /// Dormant comp. + /// + public readonly ThingComp_CombatAI ai; + /// + /// Sighting component. + /// + public readonly ThingComp_CCTVTop CctvTop; + /// + /// Dormant comp. + /// + public readonly CompCanBeDormant dormant; + /// + /// Current sight grid. + /// + public readonly SightGrid grid; + /// + /// Pawn pawn + /// + public readonly List path = new List(16); + /// + /// Thing's faction on IBucketableThing instance creation. + /// + public readonly Faction registeredFaction; + /// + /// Sighting component. + /// + public readonly ThingComp_Sighter sighter; + /// + /// Contains spotting records that are to be processed on the main thread once the scan is finished. + /// + public readonly List spottings = new List(16); + /// + /// Thing. + /// + public readonly Thing thing; + /// + /// Thing potential damage report. + /// + public DamageReport cachedDamage; + /// + /// Cached sight radius report. + /// + public ISightRadius cachedSightRadius; + /// + /// Last cycle. + /// + public int lastCycle; + /// + /// Last tick this pawn scanned for enemies + /// + public int lastScannedForEnemies; - public IBucketableThing(SightGrid grid, Thing thing, int bucketIndex) - { - this.grid = grid; - this.thing = thing; - dormant = thing.GetComp_Fast(); - ai = thing.GetComp_Fast(); - sighter = thing.GetComp_Fast(); - registeredFaction = thing.Faction; - BucketIndex = bucketIndex; - cachedDamage = DamageUtility.GetDamageReport(thing); - cachedSightRadius = SightUtility.GetSightRadius(thing); - CctvTop = thing.GetComp_Fast(); - } - /// - /// Thing. - /// - public Pawn Pawn - { - get => thing as Pawn; - } - /// - /// Thing. - /// - public Building_TurretGun TurretGun - { - get => thing as Building_TurretGun; - } - /// - /// Bucket index. - /// - public int BucketIndex - { - get; - } - /// - /// Thing id number. - /// - public int UniqueIdNumber - { - get => thing.thingIDNumber; - } - } - } + public IBucketableThing(SightGrid grid, Thing thing, int bucketIndex) + { + this.grid = grid; + this.thing = thing; + dormant = thing.GetComp_Fast(); + ai = thing.GetComp_Fast(); + sighter = thing.GetComp_Fast(); + registeredFaction = thing.Faction; + BucketIndex = bucketIndex; + cachedDamage = DamageUtility.GetDamageReport(thing); + cachedSightRadius = SightUtility.GetSightRadius(thing); + CctvTop = thing.GetComp_Fast(); + } + /// + /// Thing. + /// + public Pawn Pawn + { + get => thing as Pawn; + } + /// + /// Thing. + /// + public Building_TurretGun TurretGun + { + get => thing as Building_TurretGun; + } + /// + /// Bucket index. + /// + public int BucketIndex + { + get; + } + /// + /// Thing id number. + /// + public int UniqueIdNumber + { + get => thing.thingIDNumber; + } + } + } } diff --git a/Source/Rule56/SightTracker.cs b/Source/Rule56/SightTracker.cs index c0ce4ca..928d196 100644 --- a/Source/Rule56/SightTracker.cs +++ b/Source/Rule56/SightTracker.cs @@ -10,7 +10,8 @@ namespace CombatAI { public class SightTracker : MapComponent { - private readonly HashSet _drawnCells = new HashSet(); + private readonly HashSet _drawnCells = new HashSet(); + private int updateCounter; public readonly SightGrid colonistsAndFriendlies; public readonly IThingsUInt64Map factionedUInt64Map; @@ -58,7 +59,8 @@ public override void FinalizeInit() public override void MapComponentUpdate() { base.MapComponentUpdate(); - bool gamePaused = false; + updateCounter++; + bool gamePaused = false; bool performanceOkay = false; if (Find.TickManager != null) { @@ -66,37 +68,31 @@ public override void MapComponentUpdate() performanceOkay = Finder.Performance.TpsDeficit <= 5 * Find.TickManager.TickRateMultiplier; } // -------------- - colonistsAndFriendlies.SightGridUpdate(gamePaused, performanceOkay); + colonistsAndFriendlies.SightGridUpdate(); + colonistsAndFriendlies.SightGridOptionalUpdate(gamePaused, performanceOkay); // -------------- - raidersAndHostiles.SightGridUpdate(gamePaused, performanceOkay); - // -------------- - insectsAndMechs.SightGridUpdate(gamePaused, performanceOkay); - // -------------- - wildlife.SightGridUpdate(gamePaused, performanceOkay); - } - - public override void MapComponentTick() - { - base.MapComponentTick(); - int ticks = GenTicks.TicksGame; - // -------------- - colonistsAndFriendlies.SightGridTick(); - // -------------- - if (colonistsAndFriendlies.FactionNum > 1 && !Finder.Performance.TpsCriticallyLow || ticks % 2 == 0) + if (colonistsAndFriendlies.FactionNum > 1 || updateCounter % 3 == 0) { - raidersAndHostiles.SightGridTick(); + raidersAndHostiles.SightGridUpdate(); } + raidersAndHostiles.SightGridOptionalUpdate(gamePaused, performanceOkay); // -------------- - if (!Finder.Performance.TpsCriticallyLow || ticks % 2 == 1) + if ((!Finder.Performance.TpsCriticallyLow && colonistsAndFriendlies.FactionNum > 1) || updateCounter % 3 == 1) { - insectsAndMechs.SightGridTick(); + insectsAndMechs.SightGridUpdate(); } + insectsAndMechs.SightGridOptionalUpdate(gamePaused, performanceOkay); // -------------- - if (!Finder.Performance.TpsCriticallyLow || ticks % 3 == 2) + wildlife.SightGridUpdate(); + if (!Finder.Performance.TpsCriticallyLow || updateCounter % 3 == 2) { - wildlife.SightGridTick(); + wildlife.SightGridOptionalUpdate(gamePaused, performanceOkay); } - // + } + + public override void MapComponentTick() + { + base.MapComponentTick(); // debugging stuff. if ((Finder.Settings.Debug_DrawShadowCasts || Finder.Settings.Debug_DrawThreatCasts || Finder.Settings.Debug_DebugAvailability) && GenTicks.TicksGame % 15 == 0) { @@ -111,7 +107,16 @@ public override void MapComponentTick() reader.armor = pawn.GetArmorReport(); if (reader != null) { - IntVec3 center = pawn.Position; + if (Input.GetKey(KeyCode.LeftShift)) + { + IntVec3 cell = reader.GetNearestEnemy(pawn.Position); + if (cell.IsValid && cell.InBounds(map)) + { + map.debugDrawer.FlashCell(cell, 0.99f, $"XXXX", 15); + map.debugDrawer.FlashCell(cell, 0.99f, $"XXXX", 15); + } + } + IntVec3 center = pawn.Position; if (center.InBounds(map)) { for (int i = center.x - 64; i < center.x + 64; i++) @@ -141,6 +146,26 @@ public override void MapComponentTick() map.debugDrawer.FlashCell(cell, Mathf.Clamp01(value / 20f), $"{Math.Round(value, 2)}", 15); } } + else if (Finder.Settings.Debug_DebugAvailability) + { + if (!Input.GetKey(KeyCode.LeftShift)) + { + float value = reader.GetEnemyAvailability(cell); + if (value > 0) + { + map.debugDrawer.FlashCell(cell, Mathf.Clamp01(value / 20f), $"{Math.Round(value, 2)}", 15); + } + } + else + { +// List store = new List(); + IntVec3 loc = reader.GetNearestEnemy(cell); + if (loc.IsValid) + { + map.debugDrawer.FlashCell(loc, 0.99f, $"{loc}", 15); + } + } + } } } } @@ -191,11 +216,26 @@ public override void MapComponentTick() } else if (Finder.Settings.Debug_DebugAvailability) { - float value = raidersAndHostiles.grid.GetAvailability(cell) + colonistsAndFriendlies.grid.GetAvailability(cell) + insectsAndMechs.grid.GetAvailability(cell); - if (value > 0) - { - map.debugDrawer.FlashCell(cell, Mathf.Clamp01(value / 20f), $"{Math.Round(value, 2)}", 15); - } + if (!Input.GetKey(KeyCode.LeftShift)) + { + float value = raidersAndHostiles.grid.GetAvailability(cell) + colonistsAndFriendlies.grid.GetAvailability(cell) + insectsAndMechs.grid.GetAvailability(cell); + if (value > 0) + { + map.debugDrawer.FlashCell(cell, Mathf.Clamp01(value / 20f), $"{Math.Round(value, 2)}", 15); + } + } + else + { + IntVec3 loc = raidersAndHostiles.grid.GetNearestSourceAt(cell); + if (loc.IsValid) + { + float value = loc.DistanceTo(cell); + if (value > 0) + { + map.debugDrawer.FlashCell(cell, Mathf.Clamp01(value / 80), $"{loc}", 15); + } + } + } } } } @@ -257,23 +297,16 @@ public bool TryGetReader(Thing thing, out SightReader reader) if (faction == null) { reader = new SightReader(this, - new ITSignalGrid[] + new SightGrid[] { }, new[] { - insectsAndMechs.grid + insectsAndMechs }, new[] { - wildlife.grid, colonistsAndFriendlies.grid, raidersAndHostiles.grid - }, - new ITRegionGrid[] - { - }, - new[] - { - insectsAndMechs.grid_regions + wildlife, colonistsAndFriendlies, raidersAndHostiles }); return true; } @@ -282,23 +315,15 @@ public bool TryGetReader(Thing thing, out SightReader reader) reader = new SightReader(this, new[] { - insectsAndMechs.grid - }, - new[] - { - colonistsAndFriendlies.grid, raidersAndHostiles.grid - }, - new[] - { - wildlife.grid + insectsAndMechs }, new[] { - insectsAndMechs.grid_regions + colonistsAndFriendlies, raidersAndHostiles }, new[] { - colonistsAndFriendlies.grid_regions, raidersAndHostiles.grid_regions + wildlife }); return true; } @@ -308,23 +333,15 @@ public bool TryGetReader(Thing thing, out SightReader reader) reader = new SightReader(this, new[] { - colonistsAndFriendlies.grid - }, - new[] - { - raidersAndHostiles.grid, insectsAndMechs.grid - }, - new[] - { - wildlife.grid + colonistsAndFriendlies }, new[] { - colonistsAndFriendlies.grid_regions + raidersAndHostiles, insectsAndMechs }, new[] { - raidersAndHostiles.grid_regions, insectsAndMechs.grid_regions + wildlife }); } else @@ -332,23 +349,15 @@ public bool TryGetReader(Thing thing, out SightReader reader) reader = new SightReader(this, new[] { - raidersAndHostiles.grid + raidersAndHostiles }, new[] { - colonistsAndFriendlies.grid, insectsAndMechs.grid + colonistsAndFriendlies, insectsAndMechs }, new[] { - wildlife.grid - }, - new[] - { - raidersAndHostiles.grid_regions - }, - new[] - { - colonistsAndFriendlies.grid_regions, insectsAndMechs.grid_regions + wildlife }); } return true; @@ -359,23 +368,16 @@ public bool TryGetReader(Faction faction, out SightReader reader) if (faction == null) { reader = new SightReader(this, - new ITSignalGrid[] + new SightGrid[] { }, new[] { - insectsAndMechs.grid + insectsAndMechs }, new[] { - wildlife.grid, colonistsAndFriendlies.grid, raidersAndHostiles.grid - }, - new ITRegionGrid[] - { - }, - new[] - { - insectsAndMechs.grid_regions + wildlife, colonistsAndFriendlies, raidersAndHostiles }); return true; } @@ -384,23 +386,15 @@ public bool TryGetReader(Faction faction, out SightReader reader) reader = new SightReader(this, new[] { - insectsAndMechs.grid - }, - new[] - { - colonistsAndFriendlies.grid, raidersAndHostiles.grid + insectsAndMechs }, new[] { - wildlife.grid + colonistsAndFriendlies, raidersAndHostiles }, new[] { - insectsAndMechs.grid_regions - }, - new[] - { - colonistsAndFriendlies.grid_regions, raidersAndHostiles.grid_regions + wildlife }); return true; } @@ -410,23 +404,15 @@ public bool TryGetReader(Faction faction, out SightReader reader) reader = new SightReader(this, new[] { - colonistsAndFriendlies.grid - }, - new[] - { - raidersAndHostiles.grid, insectsAndMechs.grid + colonistsAndFriendlies }, new[] { - wildlife.grid + raidersAndHostiles, insectsAndMechs }, new[] { - colonistsAndFriendlies.grid_regions - }, - new[] - { - raidersAndHostiles.grid_regions, insectsAndMechs.grid_regions + wildlife }); } else @@ -434,23 +420,15 @@ public bool TryGetReader(Faction faction, out SightReader reader) reader = new SightReader(this, new[] { - raidersAndHostiles.grid - }, - new[] - { - colonistsAndFriendlies.grid, insectsAndMechs.grid - }, - new[] - { - wildlife.grid + raidersAndHostiles }, new[] { - raidersAndHostiles.grid_regions + colonistsAndFriendlies, insectsAndMechs }, new[] { - colonistsAndFriendlies.grid_regions, insectsAndMechs.grid_regions + wildlife }); } return true; @@ -571,29 +549,49 @@ public override void MapRemoved() public class SightReader { - private readonly CellIndices indices; - - public ArmorReport armor; - public ITSignalGrid[] friendlies; - - public ITRegionGrid[] friendlies_regions; - public ITSignalGrid[] hostiles; - public ITRegionGrid[] hostiles_regions; - public ITSignalGrid[] neutrals; - - public SightReader(SightTracker tracker, ITSignalGrid[] friendlies, ITSignalGrid[] hostiles, ITSignalGrid[] neutrals, ITRegionGrid[] friendlies_regions, ITRegionGrid[] hostiles_regions) - { - Tacker = tracker; - Map = tracker.map; - indices = tracker.map.cellIndices; - this.friendlies = friendlies.ToArray(); - this.hostiles = hostiles.ToArray(); - this.neutrals = neutrals.ToArray(); - this.friendlies_regions = friendlies_regions.ToArray(); - this.hostiles_regions = hostiles_regions.ToArray(); + private readonly CellIndices indices; + + public ArmorReport armor; + public readonly ITSignalGrid[] friendlies; + public readonly ITRegionGrid[] friendlies_regions; + public readonly ITSignalGrid[] hostiles; + public readonly ITRegionGrid[] hostiles_regions; + public readonly ITSignalGrid[] neutrals; + + private readonly SightGrid[] hSight; + private readonly SightGrid[] fSight; + private readonly SightGrid[] nSight; + + public SightReader(SightTracker tracker, SightGrid[] friendlies, SightGrid[] hostiles, SightGrid[] neutrals) + { + Tracker = tracker; + Map = tracker.map; + indices = tracker.map.cellIndices; + this.hSight = hostiles; + this.hostiles = new ITSignalGrid[hostiles.Length]; + this.hostiles_regions = new ITRegionGrid[hostiles.Length]; + for (int i = 0; i < hostiles.Length; i++) + { + this.hostiles[i] = hostiles[i].grid; + this.hostiles_regions[i] = hostiles[i].grid_regions; + } + this.fSight = friendlies; + this.friendlies = new ITSignalGrid[friendlies.Length]; + this.friendlies_regions = new ITRegionGrid[friendlies.Length]; + for (int i = 0; i < friendlies.Length; i++) + { + this.friendlies[i] = friendlies[i].grid; + this.friendlies_regions[i] = friendlies[i].grid_regions; + } + this.nSight = neutrals; + this.neutrals = new ITSignalGrid[neutrals.Length]; + for (int i = 0; i < neutrals.Length; i++) + { + this.neutrals[i] = neutrals[i].grid; + } } - - public SightTracker Tacker + + public SightTracker Tracker { get; } @@ -757,6 +755,51 @@ public float GetAbsVisibilityToEnemies(int index) } return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IntVec3 GetNearestEnemy(int index) + { + return GetNearestEnemy(indices.IndexToCell(index)); + } + public IntVec3 GetNearestEnemy(IntVec3 pos) + { + IntVec3 result = IntVec3.Invalid; + int min = 999 * 999; + for (int i = 0; i < hostiles.Length; i++) + { + IntVec3 cell = hostiles[i].GetNearestSourceAt(pos); + if (cell.IsValid) + { + int dist = cell.DistanceToSquared(pos); + if (dist < min) + { + min = dist; + result = cell; + } + } + } + return result; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IntVec3 GetNearestEnemy(int index, List store) + { + return GetNearestEnemy(indices.IndexToCell(index), store); + } + public IntVec3 GetNearestEnemy(IntVec3 pos, List store) + { + IntVec3 result = GetNearestEnemy(pos); + if (result.IsValid) + { + ulong flags = GetStaticEnemyFlags(result); + if (flags != 0) + { + for (int i = 0; i < hSight.Length; i++) + { + hSight[i].GetThings(flags, result, store); + } + } + } + return result; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetAbsVisibilityToFriendlies(IntVec3 cell) @@ -787,7 +830,28 @@ public float GetVisibilityToNeutrals(int index) } return value; } - + public void GetEnemies(IntVec3 cell, List store) + { + ulong flags = GetDynamicEnemyFlags(cell); + if (flags != 0) + { + for (int i = 0; i < hSight.Length; i++) + { + hSight[i].GetThings(flags, cell, store); + } + } + } + public void GetFriendlies(IntVec3 cell, List store) + { + ulong flags = GetDynamicFriendlyFlags(cell); + if (flags != 0) + { + for (int i = 0; i < fSight.Length; i++) + { + fSight[i].GetThings(flags, cell, store); + } + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetVisibilityToEnemies(IntVec3 cell) { @@ -819,45 +883,62 @@ public float GetVisibilityToFriendlies(int index) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CheckFlags(IntVec3 cell, ulong flags) - { - return CheckFlags(indices.CellToIndex(cell), flags); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CheckFlags(int index, ulong flags) - { - return (flags & GetEnemyFlags(index)) == flags; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong GetEnemyFlags(IntVec3 cell) + public ulong GetStaticEnemyFlags(IntVec3 cell) { - return GetEnemyFlags(indices.CellToIndex(cell)); + return GetStaticEnemyFlags(indices.CellToIndex(cell)); } - public ulong GetEnemyFlags(int index) + public ulong GetStaticEnemyFlags(int index) { ulong value = 0; for (int i = 0; i < hostiles.Length; i++) { - value |= hostiles[i].GetFlagsAt(index); + value |= hostiles[i].GetStaticFlagsAt(index); } return value; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong GetFriendlyFlags(IntVec3 cell) + public ulong GetDynamicEnemyFlags(IntVec3 cell) { - return GetFriendlyFlags(indices.CellToIndex(cell)); + return GetDynamicEnemyFlags(indices.CellToIndex(cell)); } - public ulong GetFriendlyFlags(int index) + public ulong GetDynamicEnemyFlags(int index) + { + ulong value = 0; + for (int i = 0; i < hostiles.Length; i++) + { + value |= hostiles[i].GetDynamicFlagsAt(index); + } + return value; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong GetStaticFriendlyFlags(IntVec3 cell) + { + return GetStaticFriendlyFlags(indices.CellToIndex(cell)); + } + public ulong GetStaticFriendlyFlags(int index) { ulong value = 0; for (int i = 0; i < friendlies.Length; i++) { - value |= friendlies[i].GetFlagsAt(index); + value |= friendlies[i].GetStaticFlagsAt(index); } return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong GetDynamicFriendlyFlags(IntVec3 cell) + { + return GetDynamicFriendlyFlags(indices.CellToIndex(cell)); + } + public ulong GetDynamicFriendlyFlags(int index) + { + ulong value = 0; + for (int i = 0; i < friendlies.Length; i++) + { + value |= friendlies[i].GetDynamicFlagsAt(index); + } + return value; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 GetEnemyDirection(IntVec3 cell) diff --git a/Source/Rule56/SightUtility.cs b/Source/Rule56/SightUtility.cs index b533b24..8d2c904 100644 --- a/Source/Rule56/SightUtility.cs +++ b/Source/Rule56/SightUtility.cs @@ -8,9 +8,16 @@ namespace CombatAI { public static class SightUtility { - - private static readonly Dictionary> rangeCache = new Dictionary>(256); - public static SightGrid.ISightRadius GetSightRadius(Thing thing) + public static float GetSightRadius_Fast(Thing thing) + { + if (!TKVCache.TryGet(thing, out float val, 12000)) + { + TKVCache.Put(thing, val = GetSightRadius(thing).sight); + } + return val; + } + + public static SightGrid.ISightRadius GetSightRadius(Thing thing) { bool isSmartPawn = false; SightGrid.ISightRadius result; @@ -20,7 +27,7 @@ public static SightGrid.ISightRadius GetSightRadius(Thing thing) result = GetSightRadius_Sighter(sighter); Faction f = thing.Faction; - if (f != null && (f.IsPlayerSafe() || !f.HostileTo(Faction.OfPlayer) && Finder.Settings.FogOfWar_Allies)) + if (f != null && (f.IsPlayerSafe() || (!f.HostileTo(Faction.OfPlayer) && Finder.Settings.FogOfWar_Allies))) { result.fog = Maths.Max(Mathf.CeilToInt(GetFogRadius(thing, result.sight) * Finder.Settings.FogOfWar_RangeMultiplier), 3); } @@ -30,7 +37,7 @@ public static SightGrid.ISightRadius GetSightRadius(Thing thing) result = GetSightRadius_Pawn(pawn); Faction f = thing.Faction; isSmartPawn = !pawn.RaceProps.Animal && !(pawn.Dead || pawn.Downed); - if (f != null && (f.IsPlayerSafe() || !f.HostileTo(Faction.OfPlayer) && Finder.Settings.FogOfWar_Allies)) + if (f != null && (f.IsPlayerSafe() || (!f.HostileTo(Faction.OfPlayer) && Finder.Settings.FogOfWar_Allies))) { if (pawn.RaceProps.Animal) { @@ -53,7 +60,7 @@ public static SightGrid.ISightRadius GetSightRadius(Thing thing) { Faction f = thing.Faction; - if (f != null && (f.IsPlayerSafe() || !f.HostileTo(Faction.OfPlayer) && Finder.Settings.FogOfWar_Allies)) + if (f != null && (f.IsPlayerSafe() || (!f.HostileTo(Faction.OfPlayer) && Finder.Settings.FogOfWar_Allies))) { result.fog = Maths.Max(Mathf.CeilToInt(GetFogRadius(thing, result.sight) * Finder.Settings.FogOfWar_RangeMultiplier), 3); } @@ -70,6 +77,7 @@ public static SightGrid.ISightRadius GetSightRadius(Thing thing) result.sight = result.sight + 8; } result.createdAt = GenTicks.TicksGame; + TKVCache.Put(thing.thingIDNumber, result.sight); return result; } @@ -165,168 +173,18 @@ private static float GetFogRadius(Thing thing, float sightRadius) { return 3; } - float vision = pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Sight) ?? 1f; - float hearing = pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Hearing) ?? 1f; - float rest = Mathf.Lerp(0.65f, 1f, pawn.needs?.rest?.curLevelInt ?? 1f); - float mul = Mathf.Clamp(Maths.Min(vision, hearing, rest) * 0.6f + Maths.Max(vision, hearing, rest) * 0.4f, 0.5f, 1.5f); - return Maths.Max(Maths.Max(sightRadius, 17) * mul, 10); + float vision = Maths.Sqr(pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Sight) ?? 1f); + float consciousness = Maths.Sqr(pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Consciousness) ?? 1f); + float hearing = Mathf.Lerp(0.80f, 1f, pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Hearing) ?? 1f); + float rest = Mathf.Lerp(0.40f, 1f, pawn.needs?.rest?.curLevelInt ?? 1f); + float mul = Mathf.Clamp(Maths.Min(rest, Maths.Min(hearing, vision, consciousness)) * 0.80f + Maths.Max(rest, Maths.Max(hearing, vision, consciousness)) * 0.20f, 0.15f, 2.5f); + return Maths.Max(Maths.Max(sightRadius, 10) * mul, 3); } return sightRadius; } - //public static int GetSightRange(Thing thing) - //{ - // return GetSightRange(thing, !(Faction.OfPlayerSilentFail?.HostileTo(thing.Faction) ?? true)); - //} - - //public static int GetSightRange(ThingComp_Sighter sighter, bool isPlayer) - //{ - // if (!isPlayer || !Finder.Settings.FogOfWar_Enabled) - // { - // return 0; - // } - // if (rangeCache.TryGetValue(sighter.parent.thingIDNumber, out Pair store) && GenTicks.TicksGame - store.First <= 600) - // { - // return store.Second; - // } - // int range = Mathf.CeilToInt(sighter.SightRadius * Finder.Settings.FogOfWar_RangeMultiplier); - // rangeCache[sighter.parent.thingIDNumber] = new Pair(GenTicks.TicksGame, range); - // return range; - //} - - //public static int GetSightRange(Thing thing, bool isPlayer) - //{ - // if (rangeCache.TryGetValue(thing.thingIDNumber, out Pair store) && GenTicks.TicksGame - store.First <= 600) - // { - // return store.Second; - // } - // int range = 0; - // if (thing is Pawn pawn) - // { - // range = GetSightRange(pawn); - // } - // else if (thing is Building_Turret turret) - // { - // Verb verb = turret.AttackVerb; - // if (verb != null) - // { - // if (verb.verbProps.isMortar) - // { - // range = Mathf.CeilToInt(Maths.Min(48, verb.EffectiveRange)); - // } - // else - // { - // range = Mathf.CeilToInt(turret.AttackVerb?.EffectiveRange ?? 0f); - // } - // } - // else - // { - // range = 0; - // } - // } - // rangeCache[thing.thingIDNumber] = new Pair(GenTicks.TicksGame, range); - // return range; - //} - - private static int GetSightRangePlayer(Pawn pawn, bool checkCapcities) - { - bool downed = pawn.Downed; - float multiplier = 1.0f; - if (checkCapcities) - { - float vision = !downed ? pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Sight) ?? 1f : 0.2f; - float hearing = !downed ? pawn.health.capacities?.GetLevel(PawnCapacityDefOf.Hearing) ?? 1f : 1.0f; - multiplier = Maths.Max(vision * hearing, 0.3f); - } - if (downed) - { - return (int)Maths.Max(5 * multiplier, 3); - } - if (GenTicks.TicksGame - pawn.needs?.rest?.lastRestTick < 30) - { - return (int)Maths.Max(10 * multiplier, 4); - } - if (pawn.RaceProps.Animal || pawn.RaceProps.Insect) - { - return (int)Mathf.Clamp(pawn.BodySize * multiplier * 10f, 10, 30); - } - Verb verb = pawn.CurrentEffectiveVerb; - if (verb == null) - { - return (int)Maths.Max(15 * multiplier, 12); - } - if (verb.IsMeleeAttack) - { - SkillRecord melee = pawn.skills?.GetSkill(SkillDefOf.Melee) ?? null; - if (melee != null && melee.Level > 5) - { - multiplier += melee.Level / 20f; - } - return (int)Maths.Max(20 * multiplier, 12); - } - SkillRecord ranged = pawn.skills?.GetSkill(SkillDefOf.Shooting) ?? null; - if (ranged != null && ranged.Level > 5) - { - multiplier += (ranged.Level - 5f) / 15f; - } - return (int)Maths.Max(verb.EffectiveRange * multiplier, 20f * multiplier, verb.EffectiveRange * 0.8f); - } - - //private static int GetSightRange(Pawn pawn) - //{ - // if (pawn.RaceProps.Animal && pawn.Faction == null) - // { - // return Mathf.FloorToInt(Mathf.Clamp(pawn.BodySize * 3f, 2f, 10f)); - // } - // Verb verb = pawn.equipment?.PrimaryEq?.PrimaryVerb ?? null; - // if (verb == null || !verb.Available()) - // { - // verb = pawn.verbTracker?.AllVerbs.Where(v => v.Available()).MaxBy(v => v.IsMeleeAttack ? 0 : v.EffectiveRange) ?? null; - // } - // if (verb == null) - // { - // return 4; - // } - // float range; - // if (pawn.RaceProps.Insect || pawn.RaceProps.IsMechanoid || pawn.RaceProps.Animal) - // { - // if (verb.IsMeleeAttack) - // { - // return 10; - // } - // if ((range = verb.EffectiveRange) > 2.5f) - // { - // return (int)Maths.Max(range * 0.75f, 5f); - // } - // return 4; - // } - // if (verb.IsMeleeAttack) - // { - // SkillRecord melee = pawn.skills?.GetSkill(SkillDefOf.Melee) ?? null; - // if (melee != null) - // { - // float meleeSkill = melee.Level; - // return (int)(Mathf.Clamp(meleeSkill, 5, 13) * ((pawn.equipment?.Primary?.def.IsMeleeWeapon ?? null) != null ? 1.5f : 0.85f)); - // } - // return 4; - // } - // else - // { - // range = verb.EffectiveRange * 0.75f; - // SkillRecord shooting = pawn.skills?.GetSkill(SkillDefOf.Shooting) ?? null; - // float skill = 5; - // if (shooting != null) - // { - // skill = shooting.Level; - // } - // range = Maths.Max(range * Mathf.Clamp(skill / 7.5f, 0.778f, 1.425f), 4); - // return Mathf.CeilToInt(range); - // } - //} - public static void ClearCache() { - rangeCache.Clear(); } } } diff --git a/Source/Rule56/Squads/Squad.cs b/Source/Rule56/Squads/Squad.cs new file mode 100644 index 0000000..9e44cf5 --- /dev/null +++ b/Source/Rule56/Squads/Squad.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Verse; +namespace CombatAI.Squads +{ + public class Squad : IExposable, ILoadReferenceable + { + public int squadIDNumber; + public List members = new List(); + + private Squad() + { + } + + public virtual void ExposeData() + { + Scribe_Values.Look(ref squadIDNumber, "squadIDNumber"); + Scribe_Collections.Look(ref members, "members", LookMode.Reference); + } + + public string GetUniqueLoadID() + { + return $"squad_{squadIDNumber}"; + } + } +} diff --git a/Source/Rule56/T4/Outputs/Keyed.generated.cs b/Source/Rule56/T4/Sources/Keyed.cs similarity index 81% rename from Source/Rule56/T4/Outputs/Keyed.generated.cs rename to Source/Rule56/T4/Sources/Keyed.cs index 66fd53f..d5ec372 100644 --- a/Source/Rule56/T4/Outputs/Keyed.generated.cs +++ b/Source/Rule56/T4/Sources/Keyed.cs @@ -5,7 +5,7 @@ namespace CombatAI.R // Auto generated file! // ------------------------------------------------- // This file is auto generated! Mod path is: - // $(SolutionDir)/../../../1.4/Languages/English/Keyed/Translations.xml + // $(SolutionDir)/../../../../../1.4/Languages/English/Keyed/Translations.xml // ------------------------------------------------- /// /// Keyed string database. This Class is generated automatically by a T4 template Core/Assets/Keyed.tt @@ -102,6 +102,36 @@ public static TaggedString CombatAI_Quick_Welcome { _CombatAI_Quick_Welcome : _CombatAI_Quick_Welcome = "CombatAI.Quick.Welcome".Translate(); } + private static TaggedString _CombatAI_DefKindSettings_Title = null; + /// Keyed string. key=CombatAI.DefKindSettings.Title. inner text: + /// + /// AI Race Settings + /// + public static TaggedString CombatAI_DefKindSettings_Title { + get => _CombatAI_DefKindSettings_Title != null ? + _CombatAI_DefKindSettings_Title : _CombatAI_DefKindSettings_Title = "CombatAI.DefKindSettings.Title".Translate(); + } + + private static TaggedString _CombatAI_DefKindSettings_Description = null; + /// Keyed string. key=CombatAI.DefKindSettings.Description. inner text: + /// + /// Here you can configure different AI settings for each race. + /// + public static TaggedString CombatAI_DefKindSettings_Description { + get => _CombatAI_DefKindSettings_Description != null ? + _CombatAI_DefKindSettings_Description : _CombatAI_DefKindSettings_Description = "CombatAI.DefKindSettings.Description".Translate(); + } + + private static TaggedString _CombatAI_DefKindSettings_Selected = null; + /// Keyed string. key=CombatAI.DefKindSettings.Selected. inner text: + /// + /// Selected + /// + public static TaggedString CombatAI_DefKindSettings_Selected { + get => _CombatAI_DefKindSettings_Selected != null ? + _CombatAI_DefKindSettings_Selected : _CombatAI_DefKindSettings_Selected = "CombatAI.DefKindSettings.Selected".Translate(); + } + private static TaggedString _CombatAI_Gizmos_AttackMove = null; /// Keyed string. key=CombatAI.Gizmos.AttackMove. inner text: /// @@ -232,6 +262,16 @@ public static TaggedString CombatAI_Settings_Basic { _CombatAI_Settings_Basic : _CombatAI_Settings_Basic = "CombatAI.Settings.Basic".Translate(); } + private static TaggedString _CombatAI_Settings_Basic_RandomizedPersonality = null; + /// Keyed string. key=CombatAI.Settings.Basic.RandomizedPersonality. inner text: + /// + /// Enable variation in faction tactics depending on the faction leader and the current time of year + /// + public static TaggedString CombatAI_Settings_Basic_RandomizedPersonality { + get => _CombatAI_Settings_Basic_RandomizedPersonality != null ? + _CombatAI_Settings_Basic_RandomizedPersonality : _CombatAI_Settings_Basic_RandomizedPersonality = "CombatAI.Settings.Basic.RandomizedPersonality".Translate(); + } + private static TaggedString _CombatAI_Settings_Basic_Presets = null; /// Keyed string. key=CombatAI.Settings.Basic.Presets. inner text: /// @@ -392,6 +432,26 @@ public static TaggedString CombatAI_Settings_Basic_FogOfWar_Enable { _CombatAI_Settings_Basic_FogOfWar_Enable : _CombatAI_Settings_Basic_FogOfWar_Enable = "CombatAI.Settings.Basic.FogOfWar.Enable".Translate(); } + private static TaggedString _CombatAI_Settings_Basic_FogOfWar_OldShader = null; + /// Keyed string. key=CombatAI.Settings.Basic.FogOfWar.OldShader. inner text: + /// + /// Use the old legacy shader + /// + public static TaggedString CombatAI_Settings_Basic_FogOfWar_OldShader { + get => _CombatAI_Settings_Basic_FogOfWar_OldShader != null ? + _CombatAI_Settings_Basic_FogOfWar_OldShader : _CombatAI_Settings_Basic_FogOfWar_OldShader = "CombatAI.Settings.Basic.FogOfWar.OldShader".Translate(); + } + + private static TaggedString _CombatAI_Settings_Basic_FogOfWar_OldShader_Restart = null; + /// Keyed string. key=CombatAI.Settings.Basic.FogOfWar.OldShader.Restart. inner text: + /// + /// The new shader settings will be applied after restart! + /// + public static TaggedString CombatAI_Settings_Basic_FogOfWar_OldShader_Restart { + get => _CombatAI_Settings_Basic_FogOfWar_OldShader_Restart != null ? + _CombatAI_Settings_Basic_FogOfWar_OldShader_Restart : _CombatAI_Settings_Basic_FogOfWar_OldShader_Restart = "CombatAI.Settings.Basic.FogOfWar.OldShader.Restart".Translate(); + } + private static TaggedString _CombatAI_Settings_Basic_FogOfWar_Allies = null; /// Keyed string. key=CombatAI.Settings.Basic.FogOfWar.Allies. inner text: /// @@ -662,6 +722,26 @@ public static TaggedString CombatAI_Settings_Advance { _CombatAI_Settings_Advance : _CombatAI_Settings_Advance = "CombatAI.Settings.Advance".Translate(); } + private static TaggedString _CombatAI_Settings_Advance_SquadPathWidth = null; + /// Keyed string. key=CombatAI.Settings.Advance.SquadPathWidth. inner text: + /// + /// Avoidance path width. Avoidance path width determine flanking aggressiveness. + /// + public static TaggedString CombatAI_Settings_Advance_SquadPathWidth { + get => _CombatAI_Settings_Advance_SquadPathWidth != null ? + _CombatAI_Settings_Advance_SquadPathWidth : _CombatAI_Settings_Advance_SquadPathWidth = "CombatAI.Settings.Advance.SquadPathWidth".Translate(); + } + + private static TaggedString _CombatAI_Settings_Advance_SquadPathWidth_Description = null; + /// Keyed string. key=CombatAI.Settings.Advance.SquadPathWidth.Description. inner text: + /// + /// Path width {0} cells (default: 1) + /// + public static TaggedString CombatAI_Settings_Advance_SquadPathWidth_Description { + get => _CombatAI_Settings_Advance_SquadPathWidth_Description != null ? + _CombatAI_Settings_Advance_SquadPathWidth_Description : _CombatAI_Settings_Advance_SquadPathWidth_Description = "CombatAI.Settings.Advance.SquadPathWidth.Description".Translate(); + } + private static TaggedString _CombatAI_Settings_Advance_Warning = null; /// Keyed string. key=CombatAI.Settings.Advance.Warning. inner text: /// @@ -791,5 +871,95 @@ public static TaggedString CombatAI_Settings_Advance_Sight_Performance_Readouts_ get => _CombatAI_Settings_Advance_Sight_Performance_Readouts_CarryLimit_Description != null ? _CombatAI_Settings_Advance_Sight_Performance_Readouts_CarryLimit_Description : _CombatAI_Settings_Advance_Sight_Performance_Readouts_CarryLimit_Description = "CombatAI.Settings.Advance.Sight.Performance.Readouts.CarryLimit.Description".Translate(); } + + private static TaggedString _CombatAI_Settings_FactionTech = null; + /// Keyed string. key=CombatAI.Settings.FactionTech. inner text: + /// + /// Faction Tech Level Settings + /// + public static TaggedString CombatAI_Settings_FactionTech { + get => _CombatAI_Settings_FactionTech != null ? + _CombatAI_Settings_FactionTech : _CombatAI_Settings_FactionTech = "CombatAI.Settings.FactionTech".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Desciption = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Desciption. inner text: + /// + /// Here you can configure factions with different tech levels. + /// + public static TaggedString CombatAI_Settings_FactionTech_Desciption { + get => _CombatAI_Settings_FactionTech_Desciption != null ? + _CombatAI_Settings_FactionTech_Desciption : _CombatAI_Settings_FactionTech_Desciption = "CombatAI.Settings.FactionTech.Desciption".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Tech = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Tech. inner text: + /// + /// Tech {0} + /// + public static TaggedString CombatAI_Settings_FactionTech_Tech { + get => _CombatAI_Settings_FactionTech_Tech != null ? + _CombatAI_Settings_FactionTech_Tech : _CombatAI_Settings_FactionTech_Tech = "CombatAI.Settings.FactionTech.Tech".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Duck = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Duck. inner text: + /// + /// Duck {0}x. Higher values means more likely to duck for cover + /// + public static TaggedString CombatAI_Settings_FactionTech_Duck { + get => _CombatAI_Settings_FactionTech_Duck != null ? + _CombatAI_Settings_FactionTech_Duck : _CombatAI_Settings_FactionTech_Duck = "CombatAI.Settings.FactionTech.Duck".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Retreat = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Retreat. inner text: + /// + /// Retreat {0}x. Higher values means more likely to retreat + /// + public static TaggedString CombatAI_Settings_FactionTech_Retreat { + get => _CombatAI_Settings_FactionTech_Retreat != null ? + _CombatAI_Settings_FactionTech_Retreat : _CombatAI_Settings_FactionTech_Retreat = "CombatAI.Settings.FactionTech.Retreat".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Cover = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Cover. inner text: + /// + /// Cover {0}x. Higher values means more aggressive cover + /// + public static TaggedString CombatAI_Settings_FactionTech_Cover { + get => _CombatAI_Settings_FactionTech_Cover != null ? + _CombatAI_Settings_FactionTech_Cover : _CombatAI_Settings_FactionTech_Cover = "CombatAI.Settings.FactionTech.Cover".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Pathing = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Pathing. inner text: + /// + /// Pathing {0}x. Higher values means more aggressive pathing + /// + public static TaggedString CombatAI_Settings_FactionTech_Pathing { + get => _CombatAI_Settings_FactionTech_Pathing != null ? + _CombatAI_Settings_FactionTech_Pathing : _CombatAI_Settings_FactionTech_Pathing = "CombatAI.Settings.FactionTech.Pathing".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Sapping = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Sapping. inner text: + /// + /// Sapping {0}x. Higher values means less aggressive sapping + /// + public static TaggedString CombatAI_Settings_FactionTech_Sapping { + get => _CombatAI_Settings_FactionTech_Sapping != null ? + _CombatAI_Settings_FactionTech_Sapping : _CombatAI_Settings_FactionTech_Sapping = "CombatAI.Settings.FactionTech.Sapping".Translate(); + } + + private static TaggedString _CombatAI_Settings_FactionTech_Group = null; + /// Keyed string. key=CombatAI.Settings.FactionTech.Group. inner text: + /// + /// Charge {0}x. Higher values means groups are more likely to charge + /// + public static TaggedString CombatAI_Settings_FactionTech_Group { + get => _CombatAI_Settings_FactionTech_Group != null ? + _CombatAI_Settings_FactionTech_Group : _CombatAI_Settings_FactionTech_Group = "CombatAI.Settings.FactionTech.Group".Translate(); + } } } \ No newline at end of file diff --git a/Source/Rule56/T4/Sources/Keyed.tt b/Source/Rule56/T4/Sources/Keyed.tt index a1ec496..ed6a010 100644 --- a/Source/Rule56/T4/Sources/Keyed.tt +++ b/Source/Rule56/T4/Sources/Keyed.tt @@ -5,7 +5,7 @@ <#@ output extension=".cs" #> <#@ import namespace="System.Xml" #> <# - string docPath = "$(SolutionDir)/../../../1.4/Languages/English/Keyed/Translations.xml"; + string docPath = "$(SolutionDir)/../../../../../1.4/Languages/English/Keyed/Translations.xml"; XmlDocument doc = new XmlDocument(); doc.Load(docPath); #> diff --git a/Source/Rule56/T4/Outputs/Tex.generated.cs b/Source/Rule56/T4/Sources/Tex.cs similarity index 69% rename from Source/Rule56/T4/Outputs/Tex.generated.cs rename to Source/Rule56/T4/Sources/Tex.cs index bf36d89..0751aed 100644 --- a/Source/Rule56/T4/Outputs/Tex.generated.cs +++ b/Source/Rule56/T4/Sources/Tex.cs @@ -10,7 +10,7 @@ namespace CombatAI.R // Auto generated file! // ------------------------------------------------- // This file is auto generated! Mod path is: - // $(SolutionDir)/../../../1.4/Textures + // $(SolutionDir)/../../../../../1.4/Textures // ------------------------------------------------- /// /// Texture database. This Class is generated automatically by a T4 template Core/Assets/Tex.tt @@ -28,87 +28,87 @@ namespace CombatAI.R public static class Tex { - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/logo.png: + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\logo.png: /// Isma/logo.png /// public static readonly Texture2D Isma_logo = ContentFinder.Get( "Isma/logo", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Gizmos/move_attack.png: - /// Isma/Gizmos/move_attack.png - /// - public static readonly Texture2D Isma_Gizmos_move_attack = ContentFinder.Get( "Isma/Gizmos/move_attack", true); - - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_wall_base_east.png: - /// Isma/Buildings/CCTV/cctv_wall_base_east.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctvicon.png: + /// Isma/Buildings/CCTV/cctvicon.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_base_east = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_base_east", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctvicon = ContentFinder.Get( "Isma/Buildings/CCTV/cctvicon", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_poleBird.png: - /// Isma/Buildings/CCTV/cctv_poleBird.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_pole.png: + /// Isma/Buildings/CCTV/cctv_pole.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_poleBird = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_poleBird", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_pole = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_pole", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_wall_turret.png: - /// Isma/Buildings/CCTV/cctv_wall_turret.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_poleADV.png: + /// Isma/Buildings/CCTV/cctv_poleADV.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_turret = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_turret", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_poleADV = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_poleADV", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_wall_body_north.png: - /// Isma/Buildings/CCTV/cctv_wall_body_north.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_poleBird.png: + /// Isma/Buildings/CCTV/cctv_poleBird.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_body_north = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_body_north", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_poleBird = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_poleBird", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_sentinel.png: + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_sentinel.png: /// Isma/Buildings/CCTV/cctv_sentinel.png /// public static readonly Texture2D Isma_Buildings_CCTV_cctv_sentinel = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_sentinel", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_poleADV.png: - /// Isma/Buildings/CCTV/cctv_poleADV.png - /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_poleADV = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_poleADV", true); - - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctvicon.png: - /// Isma/Buildings/CCTV/cctvicon.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_wall_base_east.png: + /// Isma/Buildings/CCTV/cctv_wall_base_east.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctvicon = ContentFinder.Get( "Isma/Buildings/CCTV/cctvicon", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_base_east = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_base_east", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_pole.png: - /// Isma/Buildings/CCTV/cctv_pole.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_wall_base_north.png: + /// Isma/Buildings/CCTV/cctv_wall_base_north.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_pole = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_pole", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_base_north = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_base_north", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_wall_base_south.png: + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_wall_base_south.png: /// Isma/Buildings/CCTV/cctv_wall_base_south.png /// public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_base_south = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_base_south", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Buildings/CCTV/cctv_wall_base_north.png: - /// Isma/Buildings/CCTV/cctv_wall_base_north.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_wall_body_north.png: + /// Isma/Buildings/CCTV/cctv_wall_body_north.png /// - public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_base_north = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_base_north", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_body_north = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_body_north", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Tutorials/JobLog/selection_screenshot.png: - /// Isma/Tutorials/JobLog/selection_screenshot.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Buildings\CCTV\cctv_wall_turret.png: + /// Isma/Buildings/CCTV/cctv_wall_turret.png /// - public static readonly Texture2D Isma_Tutorials_JobLog_selection_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/selection_screenshot", true); + public static readonly Texture2D Isma_Buildings_CCTV_cctv_wall_turret = ContentFinder.Get( "Isma/Buildings/CCTV/cctv_wall_turret", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Tutorials/JobLog/gizmo_screenshot.png: + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Gizmos\move_attack.png: + /// Isma/Gizmos/move_attack.png + /// + public static readonly Texture2D Isma_Gizmos_move_attack = ContentFinder.Get( "Isma/Gizmos/move_attack", true); + + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Tutorials\JobLog\clipboard_screenshot.png: + /// Isma/Tutorials/JobLog/clipboard_screenshot.png + /// + public static readonly Texture2D Isma_Tutorials_JobLog_clipboard_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/clipboard_screenshot", true); + + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Tutorials\JobLog\gizmo_screenshot.png: /// Isma/Tutorials/JobLog/gizmo_screenshot.png /// public static readonly Texture2D Isma_Tutorials_JobLog_gizmo_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/gizmo_screenshot", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Tutorials/JobLog/position_screenshot.png: + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Tutorials\JobLog\position_screenshot.png: /// Isma/Tutorials/JobLog/position_screenshot.png /// public static readonly Texture2D Isma_Tutorials_JobLog_position_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/position_screenshot", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Tutorials/JobLog/clipboard_screenshot.png: - /// Isma/Tutorials/JobLog/clipboard_screenshot.png + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Tutorials\JobLog\selection_screenshot.png: + /// Isma/Tutorials/JobLog/selection_screenshot.png /// - public static readonly Texture2D Isma_Tutorials_JobLog_clipboard_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/clipboard_screenshot", true); + public static readonly Texture2D Isma_Tutorials_JobLog_selection_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/selection_screenshot", true); - /// Texture at $(SolutionDir)/../../../1.4/Textures/Isma/Tutorials/JobLog/window_screenshot.png: + /// Texture at $(SolutionDir)/../../../../../1.4/Textures\Isma\Tutorials\JobLog\window_screenshot.png: /// Isma/Tutorials/JobLog/window_screenshot.png /// public static readonly Texture2D Isma_Tutorials_JobLog_window_screenshot = ContentFinder.Get( "Isma/Tutorials/JobLog/window_screenshot", true); diff --git a/Source/Rule56/T4/Sources/Tex.tt b/Source/Rule56/T4/Sources/Tex.tt index 44097cd..8c479ad 100644 --- a/Source/Rule56/T4/Sources/Tex.tt +++ b/Source/Rule56/T4/Sources/Tex.tt @@ -5,7 +5,7 @@ <#@ output extension=".cs" #> <#@ import namespace="System.IO" #> <# - string texturesPath = "$(SolutionDir)/../../../1.4/Textures"; + string texturesPath = "$(SolutionDir)/../../../../../1.4/Textures"; #> using System; diff --git a/Source/Rule56/ThinkNodes/JobGiver_CAIFollowEscortee.cs b/Source/Rule56/ThinkNodes/JobGiver_CAIFollowEscortee.cs new file mode 100644 index 0000000..95ac9e4 --- /dev/null +++ b/Source/Rule56/ThinkNodes/JobGiver_CAIFollowEscortee.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.CompilerServices; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.AI; +namespace CombatAI +{ + public class JobGiver_CAIFollowEscortee : JobGiver_AIFollowEscortee + { + public override Job TryGiveJob(Pawn pawn) + { + LocalTargetInfo focus = pawn.mindState.duty?.focus ?? LocalTargetInfo.Invalid; + if (focus is { IsValid: true, HasThing: true, Thing: Pawn followee } + && !pawn.CurJobDef.Is(JobDefOf.Wait_Combat) + && !NearFollowee(pawn, followee, pawn.mindState.duty.radius, out IntVec3 root) + && pawn.TryGetSightReader(out SightTracker.SightReader reader)) + { + Map map = pawn.Map; + MapComponent_CombatAI comp = map.AI(); + IntVec3 bestCell = IntVec3.Invalid; + IntVec3 pawnPos = pawn.Position; + float bestCellScore = float.MaxValue; + float radius = pawn.mindState.duty.radius * 0.8f; + Action action = (node) => + { + if (node.distAbs > radius) + { + return; + } + float score = reader.GetEnemyAvailability(node.cell) + reader.GetEnemyAvailability(node.cell); + if (!pawn.CanReserve(node.cell) || node.cell.GetEdifice(map) != null) + { + score += 16f; + } + if (bestCellScore > score) + { + bestCellScore = score; + bestCell = node.cell; + } + }; + comp.flooder.Flood(root, action, maxDist: Mathf.CeilToInt(Maths.Max(radius, Mathf.Abs(pawnPos.x - root.x) + Mathf.Abs(pawnPos.z - root.z)))); + if (bestCell.IsValid && pawn.CanReach(bestCell, PathEndMode.ClosestTouch, Danger.Deadly, true, true, TraverseMode.PassDoors)) + { + Job job_goto = JobMaker.MakeJob(JobDefOf.Goto, bestCell); + job_goto.expiryInterval = 30; + job_goto.locomotionUrgency = Finder.Settings.Enable_Sprinting ? LocomotionUrgency.Sprint : LocomotionUrgency.Jog; + job_goto.checkOverrideOnExpire = true; + return job_goto; + } + return base.TryGiveJob(pawn); + } + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool NearFollowee(Pawn follower, Pawn followee, float radius, out IntVec3 dest) + { + return NearFollowee(follower, followee, follower.Position, radius, out dest); + } + + public static bool NearFollowee(Pawn follower, Pawn followee, IntVec3 locus, float radius, out IntVec3 followeeRoot) + { + float speedMul = Mathf.Clamp(followee.GetStatValue_Fast(StatDefOf.MoveSpeed, 3600) / (follower.GetStatValue_Fast(StatDefOf.MoveSpeed, 3600) + 0.1f), 0.5f, 2); + float adjustedRadius = radius * 1.2f; + IntVec3 shiftedPos = followeeRoot = PawnPathUtility.GetMovingShiftedPosition(followee, 60 * speedMul * 6); + if (shiftedPos.HeuristicDistanceTo(locus, followee.Map, Mathf.CeilToInt(radius / 12f + 2)) >= adjustedRadius) + { + return false; + } + return true; + } + } +} diff --git a/Source/Rule56/ThinkNodes/JobGiver_CAIWanderNearEscortee.cs b/Source/Rule56/ThinkNodes/JobGiver_CAIWanderNearEscortee.cs new file mode 100644 index 0000000..b553f65 --- /dev/null +++ b/Source/Rule56/ThinkNodes/JobGiver_CAIWanderNearEscortee.cs @@ -0,0 +1,12 @@ +using Verse; +using Verse.AI; +namespace CombatAI +{ + public class JobGiver_CAIWanderNearEscortee : JobGiver_WanderNearDutyLocation + { + public override Job TryGiveJob(Pawn pawn) + { + return base.TryGiveJob(pawn); + } + } +} diff --git a/Source/Rule56/ThinkNodes/ThinkNode_ConditionalReacted.cs b/Source/Rule56/ThinkNodes/ThinkNode_ConditionalReacted.cs index 3c2a4f3..be5e01c 100644 --- a/Source/Rule56/ThinkNodes/ThinkNode_ConditionalReacted.cs +++ b/Source/Rule56/ThinkNodes/ThinkNode_ConditionalReacted.cs @@ -12,8 +12,8 @@ public class ThinkNode_ConditionalReacted : ThinkNode_Conditional public override bool Satisfied(Pawn pawn) { - ThingComp_CombatAI comp = pawn.AI(); - if (comp != null) + ThingComp_CombatAI comp = pawn.GetComp_Fast(); + if (comp != null && comp.data != null) { return comp.data.InterruptedRecently(ticks) || comp.data.RetreatedRecently(ticks); } diff --git a/Source/Rule56/ThreatUtility.cs b/Source/Rule56/ThreatUtility.cs new file mode 100644 index 0000000..4e4bafd --- /dev/null +++ b/Source/Rule56/ThreatUtility.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; +namespace CombatAI +{ + public static class ThreatUtility + { + public static void CalculateThreat(Pawn selPawn, List inEnemies, out float possibleDmg, out float possibleDmgDistance, out float possibleDmgWarmup, out Thing nearestEnemy, out float nearestEnemyDist, out Pawn nearestEnemyMelee, out float nearestEnemyMeleeDist, ref int progress) + { + CalculateThreat(selPawn, inEnemies, selPawn.GetArmorReport(), null, null, out possibleDmg, out possibleDmgDistance, out possibleDmgWarmup, out nearestEnemy, out nearestEnemyDist, out nearestEnemyMelee, out nearestEnemyMeleeDist, ref progress); + } + + public static void CalculateThreat(Pawn selPawn, List inEnemies, ArmorReport inArmorReport, List outEnemiesRanged, List outEnemiesMelee, out float possibleDmg, out float possibleDmgDistance, out float possibleDmgWarmup, out Thing nearestEnemy, out float nearestEnemyDist, out Pawn nearestEnemyMelee, out float nearestEnemyMeleeDist, ref int progress) + { + nearestEnemyDist = 1e6f; + nearestEnemy = null; + nearestEnemyMeleeDist = 1e6f; + nearestEnemyMelee = null; + IntVec3 selPos = selPawn.Position; + possibleDmg = 0; + possibleDmgWarmup = 0; + possibleDmgDistance = 0; + for (int i = 0; i < inEnemies.Count; i++) + { + Thing enemy = inEnemies[i]; +#if DEBUG_REACTION + if (enemy == null) + { + Log.Error("Found null thing (2)"); + continue; + } +#endif + // For debugging and logging. + progress = 80; + if (GetEnemyAttackTargetId(enemy) == selPawn.thingIDNumber) + { + DamageReport damageReport = DamageUtility.GetDamageReport(enemy); + Pawn enemyPawn = enemy as Pawn; + if (damageReport.IsValid && (enemyPawn == null || enemyPawn.mindState?.MeleeThreatStillThreat == false)) + { + float dist = enemyPawn == null ? selPawn.DistanceTo_Fast(enemy) : selPos.DistanceTo_Fast(PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120f)); + if (dist < nearestEnemyDist) + { + nearestEnemyDist = dist; + nearestEnemy = enemy; + } + if (!damageReport.primaryIsRanged) + { + if (dist < nearestEnemyMeleeDist) + { + nearestEnemyMeleeDist = dist; + nearestEnemyMelee = enemyPawn; + } + outEnemiesMelee?.Add(enemy); + } + progress = 81; + float damage = damageReport.SimulatedDamage(inArmorReport); + if (!damageReport.primaryIsRanged) + { + damage *= (5f - Mathf.Clamp(Maths.Sqrt_Fast(dist * dist, 4), 0f, 5f)) / 5f; + } + possibleDmg += damage; + possibleDmgDistance += dist; + if (damageReport.primaryIsRanged) + { + possibleDmgWarmup += damageReport.primaryVerbProps.warmupTime; + outEnemiesRanged?.Add(enemy); + } + progress = 82; + } + } + } + } + + internal static int GetEnemyAttackTargetId(Thing enemy) + { + if (!TKVCache.TryGet(enemy, 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 < 60 || 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, attackTarget); + } + return attackTarget; + } + } +}