using System.Linq; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; using Content.Shared.Physics; using Robust.Shared.Map; using Robust.Shared.Random; namespace Content.Shared.Maps { public static class TurfHelpers { /// /// Attempts to get the turf at map indices with grid id or null if no such turf is found. /// public static TileRef GetTileRef(this Vector2i vector2i, GridId gridId, IMapManager? mapManager = null) { if (!gridId.IsValid()) return default; mapManager ??= IoCManager.Resolve(); if (!mapManager.TryGetGrid(gridId, out var grid)) return default; if (!grid.TryGetTileRef(vector2i, out var tile)) return default; return tile; } /// /// Attempts to get the turf at a certain coordinates or null if no such turf is found. /// public static TileRef? GetTileRef(this EntityCoordinates coordinates, IEntityManager? entityManager = null, IMapManager? mapManager = null) { entityManager ??= IoCManager.Resolve(); if (!coordinates.IsValid(entityManager)) return null; mapManager ??= IoCManager.Resolve(); if (!mapManager.TryGetGrid(coordinates.GetGridId(entityManager), out var grid)) return null; if (!grid.TryGetTileRef(coordinates, out var tile)) return null; return tile; } public static bool TryGetTileRef(this EntityCoordinates coordinates, [NotNullWhen(true)] out TileRef? turf, IEntityManager? entityManager = null, IMapManager? mapManager = null) { return (turf = coordinates.GetTileRef(entityManager, mapManager)) != null; } /// /// Returns the content tile definition for a tile. /// public static ContentTileDefinition GetContentTileDefinition(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) { tileDefinitionManager ??= IoCManager.Resolve(); return (ContentTileDefinition)tileDefinitionManager[tile.TypeId]; } /// /// Returns whether a tile is considered space. /// public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) { return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace; } /// /// Returns the content tile definition for a tile ref. /// public static ContentTileDefinition GetContentTileDefinition(this TileRef tile, ITileDefinitionManager? tileDefinitionManager = null) { return tile.Tile.GetContentTileDefinition(tileDefinitionManager); } /// /// Returns whether a tile ref is considered space. /// public static bool IsSpace(this TileRef tile, ITileDefinitionManager? tileDefinitionManager = null) { return tile.Tile.IsSpace(tileDefinitionManager); } public static bool PryTile(this EntityCoordinates coordinates, IEntityManager? entityManager = null, IMapManager? mapManager = null) { entityManager ??= IoCManager.Resolve(); mapManager ??= IoCManager.Resolve(); return coordinates.ToVector2i(entityManager, mapManager).PryTile(coordinates.GetGridId(entityManager)); } public static bool PryTile(this Vector2i indices, GridId gridId, IMapManager? mapManager = null, ITileDefinitionManager? tileDefinitionManager = null, IEntityManager? entityManager = null) { mapManager ??= IoCManager.Resolve(); var grid = mapManager.GetGrid(gridId); var tileRef = grid.GetTileRef(indices); return tileRef.PryTile(mapManager, tileDefinitionManager, entityManager); } public static bool PryTile(this TileRef tileRef, IMapManager? mapManager = null, ITileDefinitionManager? tileDefinitionManager = null, IEntityManager? entityManager = null, IRobustRandom? robustRandom = null) { var tile = tileRef.Tile; var indices = tileRef.GridIndices; // If the arguments are null, resolve the needed dependencies. mapManager ??= IoCManager.Resolve(); tileDefinitionManager ??= IoCManager.Resolve(); entityManager ??= IoCManager.Resolve(); robustRandom ??= IoCManager.Resolve(); if (tile.IsEmpty) return false; var tileDef = (ContentTileDefinition) tileDefinitionManager[tile.TypeId]; if (!tileDef.CanCrowbar) return false; var mapGrid = mapManager.GetGrid(tileRef.GridIndex); var plating = tileDefinitionManager[tileDef.BaseTurfs[^1]]; mapGrid.SetTile(tileRef.GridIndices, new Tile(plating.TileId)); const float margin = 0.1f; var (x, y) = ((mapGrid.TileSize - 2 * margin) * robustRandom.NextFloat() + margin, (mapGrid.TileSize - 2 * margin) * robustRandom.NextFloat() + margin); //Actually spawn the relevant tile item at the right position and give it some random offset. var tileItem = entityManager.SpawnEntity(tileDef.ItemDropPrototypeName, indices.ToEntityCoordinates(tileRef.GridIndex, mapManager).Offset(new Vector2(x, y))); entityManager.GetComponent(tileItem).LocalRotation = robustRandom.NextDouble() * Math.Tau; return true; } /// /// Helper that returns all entities in a turf. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IEnumerable GetEntitiesInTile(this TileRef turf, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null) { lookupSystem ??= EntitySystem.Get(); if (!GetWorldTileBox(turf, out var worldBox)) return Enumerable.Empty(); return lookupSystem.GetEntitiesIntersecting(turf.GridIndex, worldBox, flags); } /// /// Helper that returns all entities in a turf. /// public static IEnumerable GetEntitiesInTile(this EntityCoordinates coordinates, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null) { var turf = coordinates.GetTileRef(); if (turf == null) return Enumerable.Empty(); return GetEntitiesInTile(turf.Value, flags, lookupSystem); } /// /// Helper that returns all entities in a turf. /// public static IEnumerable GetEntitiesInTile(this Vector2i indices, GridId gridId, LookupFlags flags = LookupFlags.Anchored, EntityLookupSystem? lookupSystem = null) { return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem); } /// /// Checks if a turf has something dense on it. /// public static bool IsBlockedTurf(this TileRef turf, bool filterMobs, EntityLookupSystem? physics = null, IEntitySystemManager? entSysMan = null) { // TODO: Deprecate this with entitylookup. if (physics == null) { IoCManager.Resolve(ref entSysMan); physics = entSysMan.GetEntitySystem(); } if (!GetWorldTileBox(turf, out var worldBox)) return false; var entManager = IoCManager.Resolve(); var query = physics.GetEntitiesIntersecting(turf.GridIndex, worldBox); foreach (var ent in query) { // Yes, this can fail. Welp! if (!entManager.TryGetComponent(ent, out PhysicsComponent? body)) continue; if (body.CanCollide && body.Hard && (body.CollisionLayer & (int) CollisionGroup.Impassable) != 0) return true; if (filterMobs && (body.CollisionLayer & (int) CollisionGroup.MobMask) != 0) return true; } return false; } public static EntityCoordinates GridPosition(this TileRef turf, IMapManager? mapManager = null) { mapManager ??= IoCManager.Resolve(); return turf.GridIndices.ToEntityCoordinates(turf.GridIndex, mapManager); } /// /// Creates a box the size of a tile, at the same position in the world as the tile. /// private static bool GetWorldTileBox(TileRef turf, out Box2Rotated res) { var map = IoCManager.Resolve(); if (map.TryGetGrid(turf.GridIndex, out var tileGrid)) { // This is scaled to 90 % so it doesn't encompass walls on other tiles. var tileBox = Box2.UnitCentered.Scale(0.9f); tileBox = tileBox.Scale(tileGrid.TileSize); var worldPos = tileGrid.GridTileToWorldPos(turf.GridIndices); tileBox = tileBox.Translated(worldPos); // Now tileBox needs to be rotated to match grid rotation res = new Box2Rotated(tileBox, tileGrid.WorldRotation, worldPos); return true; } // Have to "return something" res = Box2Rotated.UnitCentered; return false; } } }