diff --git a/1.4/Assemblies/CombatAI.dll b/1.4/Assemblies/CombatAI.dll index a9731f6..b954a82 100644 Binary files a/1.4/Assemblies/CombatAI.dll and b/1.4/Assemblies/CombatAI.dll differ diff --git a/1.4/Languages/English/Keyed/Translations.xml b/1.4/Languages/English/Keyed/Translations.xml index 08504df..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 diff --git a/Source/Rule56/AvoidanceTracker.cs b/Source/Rule56/AvoidanceTracker.cs index b32aac0..3d0e89f 100644 --- a/Source/Rule56/AvoidanceTracker.cs +++ b/Source/Rule56/AvoidanceTracker.cs @@ -9,474 +9,474 @@ 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 path_squads; - public ITByteGrid proximity; - public ITByteGrid proximity_squads; - 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); - 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 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 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 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 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 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 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 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 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 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 override void MapRemoved() - { - asyncActions.Kill(); - buckets.Release(); - base.MapRemoved(); - } + public override void MapRemoved() + { + asyncActions.Kill(); + buckets.Release(); + base.MapRemoved(); + } - public void DeRegister(Pawn pawn) - { - buckets.RemoveId(pawn.thingIDNumber); - } + public void DeRegister(Pawn pawn) + { + buckets.RemoveId(pawn.thingIDNumber); + } - 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_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_Death(Pawn pawn, IntVec3 cell) - { - } + public void Notify_Death(Pawn pawn, IntVec3 cell) + { + } - public void Notify_PathFound(Pawn pawn, PawnPath path) - { - } + public void Notify_PathFound(Pawn pawn, PawnPath path) + { + } - public void Notify_CoverPositionSelected(Pawn pawn, IntVec3 cell) - { - } + public void Notify_CoverPositionSelected(Pawn pawn, IntVec3 cell) + { + } - 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 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 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 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 void TryCastShootLine(Pawn pawn) + { - } + } - private bool Valid(Pawn pawn) - { - return !pawn.Destroyed && pawn.Spawned && !pawn.Dead && (pawn.RaceProps.Humanlike || pawn.RaceProps.IsMechanoid); - } + 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; - public readonly List tempPathSquad; + private struct IBucketablePawn : IBucketable + { + public readonly Pawn pawn; + public readonly int bucketIndex; + public readonly List tempPath; + public readonly List tempPathSquad; - public int BucketIndex - { - get => bucketIndex; - } - public int UniqueIdNumber - { - get => pawn.thingIDNumber; - } + public int BucketIndex + { + get => bucketIndex; + } + public int UniqueIdNumber + { + get => pawn.thingIDNumber; + } - public IBucketablePawn(Pawn pawn, int bucketIndex) - { - this.pawn = pawn; - this.bucketIndex = bucketIndex; - tempPath = new List(64); - tempPathSquad = new List(); - } - } + public IBucketablePawn(Pawn pawn, int bucketIndex) + { + this.pawn = pawn; + this.bucketIndex = bucketIndex; + tempPath = new List(64); + tempPathSquad = new List(); + } + } - public class AvoidanceReader - { - private readonly ulong iflags; - private readonly ulong sflags; - private readonly CellIndices indices; - public IHeatGrid affliction_dmg; - public IHeatGrid affliction_pen; + public class AvoidanceReader + { + private readonly ulong iflags; + private readonly ulong sflags; + private readonly CellIndices indices; + public IHeatGrid affliction_dmg; + public IHeatGrid affliction_pen; - public ITByteGrid path; - public ITByteGrid path_squads; - public ITByteGrid proximity; - public ITByteGrid proximity_squads; + public ITByteGrid path; + public ITByteGrid path_squads; + public ITByteGrid proximity; + public ITByteGrid proximity_squads; - public AvoidanceReader(AvoidanceTracker tracker, ulong iflags, ulong sflags) - { - ; - indices = tracker.map.cellIndices; - this.iflags = iflags; - this.sflags = sflags; - } + public AvoidanceReader(AvoidanceTracker tracker, ulong iflags, ulong sflags) + { + ; + indices = tracker.map.cellIndices; + this.iflags = iflags; + this.sflags = sflags; + } - [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); - } - } - } + [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/CombatAI.csproj b/Source/Rule56/CombatAI.csproj index ff6983e..3ce4258 100644 --- a/Source/Rule56/CombatAI.csproj +++ b/Source/Rule56/CombatAI.csproj @@ -35,8 +35,6 @@ - - @@ -63,6 +61,14 @@ + + TextTemplatingFileGenerator + Keyed.cs + + + TextTemplatingFileGenerator + Tex.cs + @@ -111,6 +117,16 @@ + + True + True + Keyed.tt + + + True + True + Tex.tt + diff --git a/Source/Rule56/CombatAIMod.cs b/Source/Rule56/CombatAIMod.cs index 4724434..adf5294 100644 --- a/Source/Rule56/CombatAIMod.cs +++ b/Source/Rule56/CombatAIMod.cs @@ -77,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 => { @@ -130,15 +141,10 @@ 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_RandomizedPersonality, ref Finder.Settings.Personalities_Enabled); - collapsible.CheckboxLabeled(Keyed.CombatAI_Settings_Basic_Pather, ref Finder.Settings.Pather_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); } diff --git a/Source/Rule56/CombatAI_Utility.cs b/Source/Rule56/CombatAI_Utility.cs index 9990939..4f169aa 100644 --- a/Source/Rule56/CombatAI_Utility.cs +++ b/Source/Rule56/CombatAI_Utility.cs @@ -215,6 +215,18 @@ 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) diff --git a/Source/Rule56/Comps/ThingComp_CombatAI.cs b/Source/Rule56/Comps/ThingComp_CombatAI.cs index c6ebae6..74d7a36 100644 --- a/Source/Rule56/Comps/ThingComp_CombatAI.cs +++ b/Source/Rule56/Comps/ThingComp_CombatAI.cs @@ -354,6 +354,8 @@ public void OnScanFinished(ref int progress) 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. @@ -371,53 +373,7 @@ public void OnScanFinished(ref int progress) { return; } - // For debugging and logging. - progress = 2; -#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); - } - } - // 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 + ReactDebug_Internel(out progress); // For debugging and logging. progress = 3; List targetedBy = data.BeingTargetedBy; @@ -474,189 +430,31 @@ public void OnScanFinished(ref int progress) data.LastInterrupted = GenTicks.TicksGame + Rand.Int % 240; return; } - PersonalityTacker.PersonalityResult personality = parent.GetCombatPersonality(); - IntVec3 selPos = selPawn.Position; - Pawn nearestMeleeEnemy = null; - float nearestMeleeEnemyDist = 1e5f; - Thing nearestEnemy = null; - float nearestEnemyDist = 1e5f; - + PersonalityTacker.PersonalityResult personality = parent.GetCombatPersonality(); + IntVec3 selPos = selPawn.Position; // used to update nearest enemy THing - void UpdateNearestEnemy(Thing enemy) - { - float dist = selPawn.DistanceTo_Fast(enemy); - if (dist < nearestEnemyDist) - { - nearestEnemyDist = dist; - nearestEnemy = enemy; - } - } - // 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; - } - } - } // For debugging and logging. progress = 7; // 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) + if (verb is { WarmupStance.ticksLeft: < 40 }) { return; } - float possibleDmgDistance = 0f; - float possibleDmgWarmup = 0f; - float possibleDmg = 0f; - AIEnvThings enemies = data.AllEnemies; // For debugging and logging. progress = 8; rangedEnemiesTargetingSelf.Clear(); - if (Finder.Settings.Retreat_Enabled && (bodySize < 2 || selPawn.RaceProps.Humanlike)) + // 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 (int i = 0; i < targetedBy.Count; i++) - { - Thing enemy = targetedBy[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); - if (damageReport.IsValid && (!(enemy is Pawn enemyPawn) || enemyPawn.mindState?.MeleeThreatStillThreat == false)) - { - UpdateNearestEnemy(enemy); - if (!damageReport.primaryIsRanged) - { - UpdateNearestEnemyMelee(enemy); - } - progress = 81; - 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); - } - progress = 82; - } - } - } // For debugging and logging. progress = 9; - if (rangedEnemiesTargetingSelf.Count > 0 && !selPawn.mindState.MeleeThreatStillThreat && !selPawn.IsApproachingMeleeTarget(8, false)) + if (TryStartRetreat(possibleDmg, possibleDmgWarmup, possibleDmgDistance, personality, nearestMeleeEnemy, nearestMeleeEnemyDist, ref progress)) { - 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; - } - 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; - } - } + return; } } // For debugging and logging. @@ -673,409 +471,611 @@ void UpdateNearestEnemyMelee(Thing enemy) // if the pawn is retreating and the pawn is still in danger or recently took damage, skip any offensive reaction. if (verb.IsMeleeAttack) { - // 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)) + 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 (info.thing == null) + { + Log.Error("Found null thing (3)"); + continue; + } +#endif + // 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)) { - _last = 30; - selPawn.jobs.StopAll(); + // 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; + } } - IntVec3 shiftedPos = PawnPathUtility.GetMovingShiftedPosition(selPawn, 240); - 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()) + else if (enemyPawn != null && !bestEnemyVisibleNow) { - AIEnvAgentInfo info = enumeratorEnemies.Current; -#if DEBUG_REACTION - if (info.thing == null) - { - Log.Error("Found null thing (2)"); - continue; - } -#endif // For debugging and logging. - progress = 203; - if (info.thing.Spawned && selPawn.CanReach(info.thing, PathEndMode.Touch, Danger.Deadly)) + progress = 312; + IntVec3 temp = PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120); + if ((sightReader.GetDynamicFriendlyFlags(temp) & selFlags) != 0 && verb.CanHitTarget(temp)) { - 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 (!bestEnemyVisibleSoon) { - if (!bestEnemyIsMeleeAttackingAlly) - { - bestEnemyIsMeleeAttackingAlly = true; - nearestEnemyDist = 1e5f; - nearestEnemy = null; - } - UpdateNearestEnemy(info.thing); + nearestEnemy = null; + nearestEnemyDist = 1e4f; + bestEnemyVisibleSoon = true; } - else if (!bestEnemyIsMeleeAttackingAlly) + float dist = selPawn.DistanceTo_Fast(info.thing); + if (dist < nearestEnemyDist) { - if (enemyVerb?.IsMeleeAttack == false) - { - if (!bestEnemyIsRanged) - { - bestEnemyIsRanged = true; - nearestEnemyDist = 1e5f; - nearestEnemy = null; - } - UpdateNearestEnemy(info.thing); - } - else if (!bestEnemyIsRanged) - { - UpdateNearestEnemy(info.thing); - } + 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 = 204; } // For debugging and logging. - progress = 205; - if (nearestEnemy == null) + progress = 303; + if (enemyPawn != null && (enemyPawn.CurrentEffectiveVerb?.IsMeleeAttack ?? false)) { - nearestEnemy = selPawn.mindState.enemyTarget; + float dist = selPos.DistanceTo_Fast(PawnPathUtility.GetMovingShiftedPosition(enemyPawn, 120f)); + if (dist < nearestMeleeEnemyDist) + { + nearestMeleeEnemyDist = dist; + nearestMeleeEnemy = enemyPawn; + } } - if (nearestEnemy == null || selPawn.CurJob.Is(JobDefOf.AttackMelee) && selPawn.CurJob.targetA.Thing == nearestEnemy) + } + } + 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)) { - return; + 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); } - // For debugging and logging. - progress = 206; - _bestEnemy = nearestEnemy; - if (!selPawn.mindState.MeleeThreatStillThreat || selPawn.stances?.stagger?.Staggered == false) + } + else if (cell == selPawn.Position) + { + if (!selPawn.CurJob.Is(JobDefOf.Wait_Combat)) { - _last = 31; - Job job_melee = JobMaker.MakeJob(JobDefOf.AttackMelee, nearestEnemy); - job_melee.playerForced = forcedTarget.IsValid; - job_melee.locomotionUrgency = 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.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_melee, JobCondition.InterruptForced); - data.LastInterrupted = GenTicks.TicksGame; - // no enemy cannot be approached solo - // TODO - // no enemy can be approached solo - // TODO + 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; } - // For debugging and logging. - progress = 207; } - // ranged else { - // For debugging and logging. - progress = 208; - if (selPawn.CurJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) && GenTicks.TicksGame - selPawn.CurJob.startTick < 60) + progress = 521; + int shootingNum = 0; + int rangedNum = 0; + IEnumerator enumeratorAllies = data.Allies(); + while (enumeratorAllies.MoveNext()) { - return; + AIEnvAgentInfo info = enumeratorAllies.Current; + if (info.thing is Pawn ally && DamageUtility.GetDamageReport(ally).primaryIsRanged) + { + rangedNum++; + if (ally.stances?.curStance is Stance_Warmup) + { + shootingNum++; + } + } } - // 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))) + 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) { - return; + selPawn.Map.debugDrawer.FlashCell(selPos, distOffset / 20f, $"{distOffset}"); } - // For debugging and logging. - progress = 209; - // check if the verb is available. - if (!verb.Available() || Mod_CE.active && Mod_CE.IsAimingCE(verb)) + if (moveBias <= 0.5f) + { + moveBias = 0f; + } + if (duty.Is(CombatAI_DutyDefOf.CombatAI_AssaultPoint) && Rand.Chance(1 - moveBias)) { 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()) + if (bestEnemyVisibleNow) { - progress = 301; - AIEnvAgentInfo info = enumerator.Current; -#if DEBUG_REACTION - if (info.thing == null) - { - Log.Error("Found null thing (3)"); - continue; - } -#endif - // For debugging and logging. - progress = 302; - if (info.thing.Spawned) + progress = 523; + if (nearestEnemyDist > 6 * personality.cover) { - Pawn enemyPawn = info.thing as Pawn; - if ((sightReader.GetDynamicFriendlyFlags(info.thing.Position) & selFlags) != 0 && verb.CanHitTarget(info.thing)) + 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))) { - // For debugging and logging. - progress = 311; - if (!bestEnemyVisibleNow) - { - nearestEnemy = null; - nearestEnemyDist = 1e4f; - bestEnemyVisibleNow = true; - } - UpdateNearestEnemy(info.thing); + StartOrQueueCoverJob(cell, 0); } - else if (enemyPawn != null && !bestEnemyVisibleNow) + else if (ShouldShootNow()) { - // 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; - } - UpdateNearestEnemy(info.thing); - } - else if (!bestEnemyVisibleSoon) - { - UpdateNearestEnemy(info.thing); - } - } - // For debugging and logging. - progress = 303; - if (enemyPawn != null && (enemyPawn.CurrentEffectiveVerb?.IsMeleeAttack ?? false)) - { - UpdateNearestEnemyMelee(enemyPawn); + _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; + } } - progress = 400; - void StartOrQueueCoverJob(IntVec3 cell, int codeOffset) + // best enemy is approaching but not yet in view + else if (bestEnemyVisibleSoon) { - Job curJob = selPawn.CurJob; - if (curJob != null && (curJob.Is(CombatAI_JobDefOf.CombatAI_Goto_Cover) || curJob.Is(JobDefOf.Goto)) && cell == curJob.targetA.Cell) + progress = 524; + _last = 60; + CoverPositionRequest request = new CoverPositionRequest(); + request.caster = selPawn; + request.verb = verb; + request.target = nearestEnemy; + if (!Finder.Performance.TpsCriticallyLow) { - if (selPawn.jobs.jobQueue.Count == 0 || !selPawn.jobs.jobQueue[0].job.Is(JobDefOf.Wait_Combat)) + request.majorThreats = new List(); + for (int i = 0; i < rangedEnemiesTargetingSelf.Count; i++) { - 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); + request.majorThreats.Add(rangedEnemiesTargetingSelf[Rand.Int % rangedEnemiesTargetingSelf.Count]); } + request.maxRangeFromCaster = Maths.Min(verb.EffectiveRange, 10f) + distOffset; } - else if (cell == selPawn.Position) + else { - if (!selPawn.CurJob.Is(JobDefOf.Wait_Combat)) + 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)) { - 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); + 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); + } } } - else if (selPawn.CurJob.Is(JobDefOf.Wait_Combat)) + } + } + } + } + + 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; + } +#endif + // 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) { - _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); + bestEnemyIsMeleeAttackingAlly = true; + nearestEnemyDist = 1e5f; + nearestEnemy = null; } - else + float dist = selPawn.DistanceTo_Fast(info.thing); + if (dist < nearestEnemyDist) { - _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); + 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; + } } - data.LastInterrupted = GenTicks.TicksGame; } + } + // 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; + } - if (nearestEnemy != null && rangedEnemiesTargetingSelf.Contains(nearestEnemy)) + private void ReactDebug_Internel(out int progress) + { + // For debugging and logging. + progress = 2; +#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) { - rangedEnemiesTargetingSelf.Remove(nearestEnemy); + Log.Warning("Found null thing (1)"); + continue; } - 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)) + if (info.thing.Spawned && info.thing is Pawn pawn) { - _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)) + _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 + } + + 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 = 41; + _last = 11; 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.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); } } - // best enemy is insight - else if (nearestEnemy != null) + } + // 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)) { - _bestEnemy = nearestEnemy; - - if (!selPawn.RaceProps.Humanlike || bodySize > 2.0f) + bool diff = cell != selPos; + // run to cover + if (diff) { - if (bestEnemyVisibleNow && selPawn.mindState.enemyTarget == null) - { - selPawn.mindState.enemyTarget = nearestEnemy; - } + _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; } - else + if (data.TookDamageRecently(45) || !diff) { - 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++; - } - } - } - 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) - { - 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) - { - _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) + distOffset; - } - else - { - request.maxRangeFromCaster = Maths.Max(verb.EffectiveRange / 2f, 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); - } - } - } - } + _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; } /// @@ -1682,54 +1682,15 @@ private void TryStartSapperJob() }; Verse.GenClosest.RegionwiseBFSWorker(selPawn.Position, map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.InteractionCell, TraverseParms.For(selPawn), validator, null, 1, 10, 40, out int _); } - if (!Mod_CE.active && (escorts.Count >= 2 || miningSkill == 0)) + float visibility = sightReader.GetAbsVisibilityToEnemies(cellBefore); + if (!Mod_CE.active && (visibility > 0 || miningSkill < 9) && (escorts.Count >= 2 || miningSkill == 0)) { - Verb verb = selPawn.TryGetAttackVerb(); - - if ((sightReader.GetAbsVisibilityToEnemies(cellBefore) > 0 || miningSkill < 9) && (verb.verbProps.burstShotCount > 1 || !selPawn.RaceProps.IsMechanoid) && verb != null && !verb.IsMeleeAttack && !(verb is Verb_SpewFire || verb is Verb_ShootBeam) && !verb.IsEMP() && !verb.verbProps.CausesExplosion) - { - CastPositionRequest request = new CastPositionRequest(); - request.verb = verb; - request.caster = selPawn; - request.target = blocker; - request.maxRangeFromTarget = 10; - Vector3 dir = (cellBefore - sapperNodes[0]).ToVector3(); - request.validator = cell => - { - return Mathf.Abs(Vector3.Angle((cell - sapperNodes[0]).ToVector3(), dir)) <= 45f && cellBefore.DistanceToSquared(cell) >= 9; - }; - try - { - if (CastPositionFinder.TryFindCastPosition(request, out IntVec3 loc)) - { - job = JobMaker.MakeJob(JobDefOf.UseVerbOnThing); - job.targetA = blocker; - job.targetB = loc; - job.verbToUse = verb; - job.preventFriendlyFire = true; - job.expiryInterval = JobGiver_AIFightEnemy.ExpiryInterval_ShooterSucceeded.RandomInRange; - selPawn.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; - selPawn.jobs.jobQueue.EnqueueFirst(job); - } - } - } - catch (Exception er) - { - Log.Error($"1. {er}"); - } - } + job = TryStartRemoteSapper(selPawn, blocker, sapperNodes[0], cellBefore); } if (job == null) { - if (sightReader.GetAbsVisibilityToEnemies(cellBefore) == 0 && miningSkill > 0) + // 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; @@ -1743,86 +1704,80 @@ private void TryStartSapperJob() cellBefore = IntVec3.Invalid; sapperStartTick = -1; sapperNodes.Clear(); - return; } } - if (!Mod_CE.active && job.def == JobDefOf.UseVerbOnThing) + if (!Mod_CE.active && job != null && job.def == JobDefOf.UseVerbOnThing) { - foreach (Pawn ally in escorts) - { - if (!ally.Destroyed && ally.Spawned && !ally.Downed && !ally.Dead && sightReader.GetAbsVisibilityToEnemies(ally.Position) == 0 ) - { - Verb verb = ally.TryGetAttackVerb(); - if (!ally.CurJobDef.Is(JobDefOf.UseVerbOnThing) && verb != null && !(verb is Verb_SpewFire || verb is Verb_ShootBeam) && !verb.IsEMP() && !verb.verbProps.CausesExplosion) - { - CastPositionRequest request = new CastPositionRequest(); - request.verb = verb; - request.caster = ally; - request.target = blocker; - request.maxRangeFromTarget = 10; - Vector3 dir = (cellBefore - sapperNodes[0]).ToVector3(); - request.validator = cell => - { - return Mathf.Abs(Vector3.Angle((cell - sapperNodes[0]).ToVector3(), dir)) <= 45f && cellBefore.DistanceToSquared(cell) >= 9; - }; - try - { - if (CastPositionFinder.TryFindCastPosition(request, out IntVec3 loc)) - { - Job attack_job = JobMaker.MakeJob(JobDefOf.UseVerbOnThing); - attack_job.targetA = blocker; - attack_job.targetB = loc; - attack_job.verbToUse = verb; - attack_job.preventFriendlyFire = true; - attack_job.expiryInterval = JobGiver_AIFightEnemy.ExpiryInterval_ShooterSucceeded.RandomInRange; - ally.jobs.StartJob(attack_job, JobCondition.InterruptForced); - for (int i = 0; i < 4; i++) - { - attack_job = JobMaker.MakeJob(JobDefOf.UseVerbOnThing); - attack_job.targetA = blocker; - attack_job.targetB = loc; - attack_job.verbToUse = verb; - attack_job.preventFriendlyFire = true; - attack_job.expiryInterval = JobGiver_AIFightEnemy.ExpiryInterval_ShooterSucceeded.RandomInRange; - ally.jobs.jobQueue.EnqueueFirst(attack_job); - } - } - } - catch (Exception er) - { - Log.Error($"2. {er}"); - } - } - } - } + TryStartSapperJobForEscort(blocker); } } } - private static int GetEnemyAttackTargetId(Thing enemy) + /// + /// Tries to start ranged sapping jobs for escorting pawns + /// + /// The path blocker + private void TryStartSapperJobForEscort(Thing blocker) { - if (!TKVCache.TryGet(enemy, out int attackTarget, 15) || attackTarget == -1) + foreach (Pawn ally in escorts) { - Verb enemyVerb = enemy.TryGetAttackVerb(); - if (enemyVerb == null || enemyVerb is Verb_CastPsycast || enemyVerb is Verb_CastAbility) + if (!ally.Destroyed && ally.Spawned && !ally.Downed && !ally.Dead && !ally.IsUsingVerb() && sightReader.GetAbsVisibilityToEnemies(ally.Position) == 0) { - attackTarget = -1; + TryStartRemoteSapper(ally, blocker, sapperNodes[0], cellBefore); } - else if (!enemyVerb.IsMeleeAttack && enemyVerb.currentTarget is { IsValid: true, HasThing: true } && (enemyVerb.WarmingUp && enemyVerb.WarmupTicksLeft < 60 || enemyVerb.Bursting)) + } + } + + /// + /// 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 => { - attackTarget = enemyVerb.currentTarget.Thing.thingIDNumber; - } - else if (enemyVerb.IsMeleeAttack && enemy is Pawn enemyPawn && enemyPawn.CurJobDef.Is(JobDefOf.AttackMelee) && enemyPawn.CurJob.targetA.IsValid) + return Mathf.Abs(Vector3.Angle((cell - cellBlocker).ToVector3(), dir)) <= 45f && cellBefore.DistanceToSquared(cell) >= 9; + }; + try { - attackTarget = enemyPawn.CurJob.targetA.Thing.thingIDNumber; + 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; + } } - else + catch (Exception er) { - attackTarget = -1; + Log.Error($"1. {er}"); } - TKVCache.Put(enemy, attackTarget); } - return attackTarget; + return null; } #region TimeStamps @@ -1963,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); diff --git a/Source/Rule56/DamageReport.cs b/Source/Rule56/DamageReport.cs index 93e9249..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 < 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) - { - 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/DifficultyUtility.cs b/Source/Rule56/DifficultyUtility.cs index 14d9fff..45fda37 100644 --- a/Source/Rule56/DifficultyUtility.cs +++ b/Source/Rule56/DifficultyUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using RimWorld; using Verse; namespace CombatAI @@ -14,15 +15,27 @@ public static void SetDifficulty(Difficulty 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; @@ -47,13 +60,26 @@ 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; @@ -78,12 +104,25 @@ 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; @@ -112,12 +151,25 @@ public static void SetDifficulty(Difficulty difficulty) sappingTech = 0.7f; Finder.Settings.Pathfinding_DestWeight = 0.45f; Finder.Settings.Caster_Enabled = true; - Finder.Settings.Temperature_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; 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 098f089..b018bed 100644 --- a/Source/Rule56/Gui/Window_QuickSetup.cs +++ b/Source/Rule56/Gui/Window_QuickSetup.cs @@ -59,7 +59,6 @@ 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); 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/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/PathFinder_Patch.cs b/Source/Rule56/Patches/PathFinder_Patch.cs index 42f2021..8aeb30f 100644 --- a/Source/Rule56/Patches/PathFinder_Patch.cs +++ b/Source/Rule56/Patches/PathFinder_Patch.cs @@ -45,6 +45,7 @@ private static class PathFinder_FindPath_Patch 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; @@ -60,8 +61,11 @@ 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; original_traverseParms = traverseParms; @@ -96,12 +100,15 @@ internal static void Prefix(PathFinder __instance, ref PawnPath __result, IntVec visibilityAtDest = sightReader.GetVisibilityToEnemies(dest.Cell) * Finder.Settings.Pathfinding_DestWeight; comp = pawn.AI(); Verb verb = pawn.TryGetAttackVerb(); - if (dig = Finder.Settings.Pather_KillboxKiller - && (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)) + 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; @@ -241,6 +248,7 @@ public static void Postfix(PathFinder __instance, ref PawnPath __result, bool __ public static void Reset() { + avoidTempEnabled = false; avoidanceReader = null; isRaider = false; isPlayer = false; @@ -301,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)) 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/Settings.cs b/Source/Rule56/Settings.cs index b445f63..979ef17 100644 --- a/Source/Rule56/Settings.cs +++ b/Source/Rule56/Settings.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; using RimWorld; using Verse; namespace CombatAI @@ -70,17 +72,15 @@ 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 int Pathfinding_SquadPathWidth = 4; - public bool Temperature_Enabled = true; public bool PerformanceOpt_Enabled = true; - public bool React_Enabled = true; - public bool Retreat_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(); @@ -90,6 +90,37 @@ public class Settings : ModSettings 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(); + } + /* * -- * -- * -- * -- * -- * -- * -- * -- * -- */ @@ -140,16 +171,20 @@ 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 Personalities_Enabled, $"Personalities_Enabled.{version}", true); Scribe_Values.Look(ref FogOfWar_OldShader, $"FogOfWar_OldShader.{version}", true); - Scribe_Values.Look(ref Pather_Enabled, $"Pather_Enabled.{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); @@ -157,7 +192,6 @@ public override void ExposeData() 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.8f); - Scribe_Values.Look(ref Pather_KillboxKiller, $"Pather_KillboxKiller.{version}", true); 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}"); @@ -181,6 +215,7 @@ public override void ExposeData() { if (!FactionSettings.Any(t => t.tech == tech)) { + Log.Error($"Tech level {tech} doesn't have tech settings. Resetting tech settings"); FactionSettings.Clear(); break; } @@ -213,6 +248,28 @@ public void ResetTechSettings() * */ + 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; @@ -265,13 +322,13 @@ public FactionTechSettings(TechLevel tech, float retreat, float duck, float cove public void ExposeData() { - Scribe_Values.Look(ref tech, $"tech.{version}", tech); - Scribe_Values.Look(ref retreat, $"retreat.{version}", retreat); - Scribe_Values.Look(ref duck, $"duck.{version}", duck); - Scribe_Values.Look(ref cover, $"cover.{version}", cover); - Scribe_Values.Look(ref sapping, $"sapping.{version}", sapping); - Scribe_Values.Look(ref pathing, $"pathing.{version}", pathing); - Scribe_Values.Look(ref group, $"group.{version}", group); + 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 536979e..57ff031 100644 --- a/Source/Rule56/SightGrid.cs +++ b/Source/Rule56/SightGrid.cs @@ -251,7 +251,7 @@ public void GetThings(ulong flags, IntVec3 cell, List buffer, bool valida Verb verb = thing.TryGetAttackVerb(); if (verb != null) { - if ((verb.IsMeleeAttack && !GenSight.LineOfSight(cell, thing.Position, map, true)) || !verb.CanHitCellFromCellIgnoringRange(thing.Position, cell)) + if (verb.IsMeleeAttack && !GenSight.LineOfSight(cell, thing.Position, map, true) || !verb.CanHitCellFromCellIgnoringRange(thing.Position, cell)) { continue; } @@ -358,7 +358,7 @@ private bool Valid(Thing thing) { 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)); + 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 Valid(IBucketableThing item) @@ -429,7 +429,7 @@ private bool TryCastSight(IBucketableThing item) } 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 (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)) { @@ -532,7 +532,7 @@ private bool TryCastSight(IBucketableThing item) grid_regions.Set(cell); } } - else if(d2 < 360) + else if (d2 < 360) { grid.Set(cell, 0, new Vector2(cell.x - pos.x, cell.z - pos.z)); grid_regions.Set(cell); diff --git a/Source/Rule56/T4/Outputs/Keyed.generated.cs b/Source/Rule56/T4/Sources/Keyed.cs similarity index 96% rename from Source/Rule56/T4/Outputs/Keyed.generated.cs rename to Source/Rule56/T4/Sources/Keyed.cs index 265e61b..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: /// 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/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; + } + } +}