diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 4f46de9c68..8bdb719fca 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -801,6 +801,11 @@ protected bool CheckForCollisionBetween() return collided; } + /// + /// Cache field holding things that a projectile might collide with. + /// + private static readonly List potentialCollisionCandidates = new List(); + /// /// Checks whether a collision occurs along flight path within this cell. /// @@ -814,35 +819,49 @@ protected bool CheckCellForCollision(IntVec3 cell) } var roofChecked = false; - var mainThingList = new List(Map.thingGrid.ThingsListAtFast(cell)).Where(t => t is Pawn || t.def.Fillage != FillCategory.None).ToList(); + potentialCollisionCandidates.Clear(); + + foreach (var thing in Map.thingGrid.ThingsListAtFast(cell)) + { + if (thing is Pawn || thing.def.Fillage != FillCategory.None) + { + potentialCollisionCandidates.AddDistinct(thing); + } + } //Find pawns in adjacent cells and append them to main list - var adjList = new List(); var rot4 = Rot4.FromAngleFlat(shotRotation); if (rot4.rotInt > 1) { //For some reason south and west returns incorrect adjacent cells collection rot4 = rot4.Opposite; } - adjList.AddRange(GenAdj.CellsAdjacentCardinal(cell, rot4, new IntVec2(collisionCheckSize, 0)).ToList()); + if (Controller.settings.DebugDrawInterceptChecks) { Map.debugDrawer.debugCells.Clear(); Map.debugDrawer.DebugDrawerUpdate(); } //Iterate through adjacent cells and find all the pawns - foreach (var curCell in adjList) + foreach (var curCell in GenAdj.CellsAdjacentCardinal(cell, rot4, new IntVec2(collisionCheckSize, 0))) { - if (curCell != cell && curCell.InBounds(Map)) + if (curCell == cell || !curCell.InBounds(Map)) { - mainThingList.AddRange(Map.thingGrid.ThingsListAtFast(curCell) - .Where(x => x is Pawn)); + continue; + } - if (Controller.settings.DebugDrawInterceptChecks) + foreach (var thing in Map.thingGrid.ThingsListAtFast(curCell)) + { + if (thing is Pawn) { - Map.debugDrawer.FlashCell(curCell, 0.7f); + potentialCollisionCandidates.AddDistinct(thing); } } + + if (Controller.settings.DebugDrawInterceptChecks) + { + Map.debugDrawer.FlashCell(curCell, 0.7f); + } } //If the last position is above the wallCollisionHeight, we should check for roof intersections first @@ -855,8 +874,15 @@ protected bool CheckCellForCollision(IntVec3 cell) roofChecked = true; } - foreach (var thing in mainThingList.Distinct().Where(x => !(x is ProjectileCE)).OrderBy(x => (x.DrawPos - LastPos).sqrMagnitude)) + potentialCollisionCandidates.SortBy(thing => (thing.DrawPos - LastPos).sqrMagnitude); + + foreach (var thing in potentialCollisionCandidates) { + if (thing is ProjectileCE) + { + continue; + } + if ((thing == launcher || thing == mount) && !canTargetSelf) { continue; diff --git a/Source/CombatExtended/CombatExtended/SuppressionUtility.cs b/Source/CombatExtended/CombatExtended/SuppressionUtility.cs index c88cea0785..6bbbf82560 100644 --- a/Source/CombatExtended/CombatExtended/SuppressionUtility.cs +++ b/Source/CombatExtended/CombatExtended/SuppressionUtility.cs @@ -24,8 +24,6 @@ public static class SuppressionUtility private static DangerTracker dangerTracker; - private static IEnumerable Interceptors(Thing pawn) => pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.ProjectileInterceptor).Select(t => t.TryGetComp()).Where(x => x.Props.interceptNonHostileProjectiles || !x.parent.HostileTo(pawn)); - public static bool TryRequestHelp(Pawn pawn) { //TODO: 1.5 @@ -150,7 +148,7 @@ private static float GetCellCoverRatingForPawn(Pawn pawn, IntVec3 cell, IntVec3 } } - float cellRating = 0f, bonusCellRating = 1f, distToSuppressor = (pawn.Position - shooterPos).LengthHorizontal, + float cellRating = 0f, bonusCellRating = 1f, pawnHeightFactor = CE_Utility.GetCollisionBodyFactors(pawn).y, pawnVisibleOverCoverFillPercent = pawnHeightFactor * (1f - CollisionVertical.BodyRegionMiddleHeight) + 0.01f, pawnLowestCrouchFillPercent = pawnHeightFactor * CollisionVertical.BodyRegionBottomHeight + pawnVisibleOverCoverFillPercent, @@ -209,7 +207,7 @@ private static float GetCellCoverRatingForPawn(Pawn pawn, IntVec3 cell, IntVec3 cellRating += 10f - (bonusCellRating * 10f); // If the cell is covered by a shield and there are no enemies inside, then increases by 15 (for each such shield) - cellRating += InterceptorZonesFor(pawn).Where(x => !IsOccupiedByEnemies(x, pawn)).Count(x => x.Contains(cell)) * 15; + cellRating += CalculateShieldRating(cell, pawn); // Avoid bullets and other danger sources; // Yet do not discard cover that is extremely good, even if it may be dangerous @@ -245,19 +243,78 @@ private static float GetCellCoverRatingForPawn(Pawn pawn, IntVec3 cell, IntVec3 } return cellRating; } + + /// + /// Calculate the additional cover rating from shields covering the given cell. + /// + /// The cell to compute the cover rating for. + /// The pawn seeking cover. + /// The computed cover rating (15 for each shield covering the cell). + private static int CalculateShieldRating(IntVec3 cell, Pawn pawn) + { + int rating = 0; + foreach (var zone in InterceptorZonesFor(pawn)) + { + foreach (var zoneCell in zone) + { + if (zoneCell == cell) + { + if (!IsOccupiedByEnemies(zone, pawn)) + { + rating += 15; + } + + break; + } + } + } + + return rating; + } + + /// + /// Get areas covered by a shield that may be suitable for protecting the given pawn. + /// + /// The pawn seeking cover. + /// An enumerator of areas covered by shields on the map that may protect the pawn. public static IEnumerable> InterceptorZonesFor(Pawn pawn) { - var result = Interceptors(pawn).Where(x => x.Active).Select(x => GenRadial.RadialCellsAround(x.parent.Position, x.Props.radius, true)); - var compatibilityZones = BlockerRegistry.ShieldZonesCallback(pawn); - if (compatibilityZones != null) + foreach (var interceptor in pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.ProjectileInterceptor)) + { + var comp = interceptor.TryGetComp(); + if (comp.Active && (comp.Props.interceptNonHostileProjectiles || !interceptor.HostileTo(pawn))) + { + yield return GenRadial.RadialCellsAround(interceptor.Position, comp.Props.radius, true); + } + } + + foreach (var zone in BlockerRegistry.ShieldZonesCallback(pawn)) { - result = result.Union(compatibilityZones); + yield return zone; } - return result; } + + /// + /// Check whether the given area contains any objects hostile to the given pawn. + /// + /// The area to scan for hostile objects. + /// The pawn. + /// true if the area contained any hostile objects, false otherwise. private static bool IsOccupiedByEnemies(IEnumerable cells, Pawn pawn) { - return cells.Any(cell => pawn.Map.thingGrid.ThingsListAt(cell).Any(thing => (thing.HostileTo(pawn)))); + foreach (var cell in cells) + { + var things = pawn.Map.thingGrid.ThingsListAt(cell); + foreach (var thing in things) + { + if (thing.HostileTo(pawn)) + { + return true; + } + } + } + + return false; } private static float GetCoverRating(Thing cover) { diff --git a/Source/CombatExtended/Compatibility/BlockerRegistry.cs b/Source/CombatExtended/Compatibility/BlockerRegistry.cs index 5489f34c28..3418a1dbdb 100644 --- a/Source/CombatExtended/Compatibility/BlockerRegistry.cs +++ b/Source/CombatExtended/Compatibility/BlockerRegistry.cs @@ -155,9 +155,16 @@ public static IEnumerable> ShieldZonesCallback(Thing thing) { if (!enabledSZ) { - return null; + yield break; + } + + foreach (var callback in shieldZonesCallback) + { + foreach (var zone in callback(thing)) + { + yield return zone; + } } - return shieldZonesCallback.SelectMany(cb => cb(thing)); } public static bool PawnUnsuppressableFromCallback(Pawn pawn, IntVec3 origin) {