using Content.Server.Destructible; using Content.Server.NPC.Components; using Content.Server.NPC.Pathfinding; using Content.Shared.CombatMode; using Content.Shared.Doors.Components; using Content.Shared.NPC; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; namespace Content.Server.NPC.Systems; public sealed partial class NPCSteeringSystem { /* * For any custom path handlers, e.g. destroying walls, opening airlocks, etc. * Putting it onto steering seemed easier than trying to make a custom compound task for it. * I also considered task interrupts although the problem is handling stuff like pathfinding overlaps * Ideally we could do interrupts but that's TODO. */ /* * TODO: * - Add path cap * - Circle cast BFS in LOS to determine targets. * - Store last known coordinates of X targets. * - Require line of sight for melee * - Add new behavior where they move to melee target's last known position (diffing theirs and current) * then do the thing like from dishonored where it gets passed to a search system that opens random stuff. * * Also need to make sure it picks nearest obstacle path so it starts smashing in front of it. */ private SteeringObstacleStatus TryHandleFlags(EntityUid uid, NPCSteeringComponent component, PathPoly poly, EntityQuery bodyQuery) { DebugTools.Assert(!poly.Data.IsFreeSpace); // TODO: Store PathFlags on the steering comp // and be able to re-check it. var layer = 0; var mask = 0; if (TryComp(uid, out var manager)) { (layer, mask) = _physics.GetHardCollision(uid, manager); } else { return SteeringObstacleStatus.Failed; } // TODO: Should cache the fact we're doing this somewhere. // See https://github.com/space-wizards/space-station-14/issues/11475 if ((poly.Data.CollisionLayer & mask) != 0x0 || (poly.Data.CollisionMask & layer) != 0x0) { var obstacleEnts = new List(); GetObstacleEntities(poly, mask, layer, bodyQuery, obstacleEnts); var isDoor = (poly.Data.Flags & PathfindingBreadcrumbFlag.Door) != 0x0; var isAccess = (poly.Data.Flags & PathfindingBreadcrumbFlag.Access) != 0x0; // Just walk into it stupid if (isDoor && !isAccess) { var doorQuery = GetEntityQuery(); // ... At least if it's not a bump open. foreach (var ent in obstacleEnts) { if (!doorQuery.TryGetComponent(ent, out var door)) continue; if (!door.BumpOpen && (component.Flags & PathFlags.Interact) != 0x0) { if (door.State != DoorState.Opening) { _interaction.InteractionActivate(uid, ent); return SteeringObstacleStatus.Continuing; } } else { return SteeringObstacleStatus.Failed; } } return SteeringObstacleStatus.Completed; } if ((component.Flags & PathFlags.Prying) != 0x0 && isAccess && isDoor) { var doorQuery = GetEntityQuery(); // Get the relevant obstacle foreach (var ent in obstacleEnts) { if (doorQuery.TryGetComponent(ent, out var door) && door.State != DoorState.Open) { // TODO: Use the verb. if (door.State != DoorState.Opening) _doors.TryPryDoor(ent, uid, uid, door, true); return SteeringObstacleStatus.Continuing; } } if (obstacleEnts.Count == 0) return SteeringObstacleStatus.Completed; } // Try smashing obstacles. else if ((component.Flags & PathFlags.Smashing) != 0x0) { if (_melee.TryGetWeapon(uid, out var meleeUid, out var meleeWeapon) && meleeWeapon.NextAttack <= _timing.CurTime && TryComp(uid, out var combatMode)) { _combat.SetInCombatMode(uid, true, combatMode); var destructibleQuery = GetEntityQuery(); // TODO: This is a hack around grilles and windows. _random.Shuffle(obstacleEnts); foreach (var ent in obstacleEnts) { // TODO: Validate we can damage it if (destructibleQuery.HasComponent(ent)) { _melee.AttemptLightAttack(uid, uid, meleeWeapon, ent); break; } } _combat.SetInCombatMode(uid, false, combatMode); if (obstacleEnts.Count == 0) return SteeringObstacleStatus.Completed; return SteeringObstacleStatus.Continuing; } } return SteeringObstacleStatus.Failed; } return SteeringObstacleStatus.Completed; } private void GetObstacleEntities(PathPoly poly, int mask, int layer, EntityQuery bodyQuery, List ents) { // TODO: Can probably re-use this from pathfinding or something if (!_mapManager.TryGetGrid(poly.GraphUid, out var grid)) { return; } foreach (var ent in grid.GetLocalAnchoredEntities(poly.Box)) { if (!bodyQuery.TryGetComponent(ent, out var body) || !body.Hard || !body.CanCollide || (body.CollisionMask & layer) == 0x0 && (body.CollisionLayer & mask) == 0x0) { continue; } ents.Add(ent); } } private enum SteeringObstacleStatus : byte { Completed, Failed, Continuing } }