From bd8f2bbcfbea346dbe46e43f7ca9a65854042aca Mon Sep 17 00:00:00 2001 From: Aristophan_ Date: Tue, 27 Jan 2026 20:43:10 +0400 Subject: [PATCH 1/6] first commit --- .../Procedural/DungeonGenerationParams.cs | 29 ++++++ .../DungeonGenerationSystem.ItemEntitites.cs | 7 ++ .../DungeonGenerationSystem.Rooms.cs | 7 ++ .../Procedural/DungeonGenerationSystem.cs | 96 +++++++++++++++++++ .../Prototypes/Imperial/Medieval/medieval.yml | 19 ++++ 5 files changed, 158 insertions(+) create mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs create mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs create mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs create mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs new file mode 100644 index 0000000000..dc9baa5d00 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs @@ -0,0 +1,29 @@ +namespace Content.Server.Imperial.Medieval.Procedural; + +/// +/// Used for setting params for dungeon to generate. +/// +public record struct DungeonGenerationParams +{ + /// + /// Count of rooms, which sets width of the dungeon. + /// + public int Width; + + /// + /// Count of rooms, which sets height of the dungeon. + /// + public int Height; + + /// + /// Roomsize, which can be different for different dungeons. In one dungeon roomsize is similar for + /// + public Vector2i RoomSize; + + public DungeonGenerationParams(int width, int height, Vector2i roomSize) + { + Width = width; + Height = height; + RoomSize = roomSize; + } +} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs new file mode 100644 index 0000000000..8f4d32bca8 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs @@ -0,0 +1,7 @@ + +namespace Content.Server.Imperial.Medieval.Procedural; + +public sealed partial class DungeonGenerationSystem +{ + +} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs new file mode 100644 index 0000000000..8f4d32bca8 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs @@ -0,0 +1,7 @@ + +namespace Content.Server.Imperial.Medieval.Procedural; + +public sealed partial class DungeonGenerationSystem +{ + +} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs new file mode 100644 index 0000000000..fd75333eb4 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs @@ -0,0 +1,96 @@ +using System.Linq; +using System.Collections.Generic; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Random; +using Content.Shared.Maps; +using Content.Shared.Parallax.Biomes; +using Content.Server.Parallax; +using Robust.Server.GameObjects; + +namespace Content.Server.Imperial.Medieval.Procedural; + +public sealed partial class DungeonGenerationSystem : EntitySystem +{ + [Dependency] private readonly MapSystem _mapSys = default!; + [Dependency] private readonly BiomeSystem _biome = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; + [Dependency] private readonly TileSystem _tileSys = default!; + [Dependency] private readonly IConsoleHost _console = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + private readonly ProtoId _biomeProtoId = "Empty"; + private readonly ProtoId _floorProtoId = "MedievalFloorStone9"; + private readonly EntProtoId _wallProtoId = "MedievalStoneBrickWallIndestructable"; + + public override void Initialize() + { + base.Initialize(); + _console.RegisterCommand("impdungen", "Generate dungeon instantly", "impdungen", GenerateCommand); + } + + private void GenerateCommand(IConsoleShell shell, string argstr, string[] args) + { + var random = new Random(); + var mapId = _mapSys.CreateMap(); + var grid = _mapManager.CreateGridEntity(mapId); + + if (_proto.TryIndex(_biomeProtoId, out var biomeProto)) + { + _biome.EnsurePlanet(grid.Owner, biomeProto); + } + + var width = 10; + var height = 10; + var roomSize = new Vector2i(10, 10); // Итоговый размер 100x100 тайлов + + var tileDef = (ContentTileDefinition)_tileDefMan[_floorProtoId]; + + var tilesToSet = new List<(Vector2i, Tile)>(); + + // Границы: от -100 до 0 + var minX = -(width * roomSize.X); + var minY = -(height * roomSize.Y); + var box = new Box2i(minX, minY, 0, 0); + + for (var x = box.Left; x < box.Right; x++) + { + for (var y = box.Bottom; y < box.Top; y++) + { + // Тут мы просто генерируем Variant, это быстро + var tile = _tileSys.GetVariantTile(tileDef, random); + tilesToSet.Add((new Vector2i(x, y), tile)); + } + } + + if (TryComp(grid.Owner, out var gridComp)) + { + _mapSys.SetTiles(grid.Owner, gridComp, tilesToSet); + } + + for (var x = box.Left; x <= box.Right; x++) + { + SpawnWall(grid.Owner, x, box.Bottom); + if (box.Top != box.Bottom) SpawnWall(grid.Owner, x, box.Top); + } + + for (var y = box.Bottom + 1; y < box.Top; y++) + { + SpawnWall(grid.Owner, box.Left, y); + if (box.Right != box.Left) SpawnWall(grid.Owner, box.Right, y); + } + + shell.WriteLine($"Dungeon generated on Map {mapId} (Grid {grid.Owner})!"); + } + + private void SpawnWall(EntityUid gridUid, int x, int y) + { + var coords = new EntityCoordinates(gridUid, new Vector2i(x, y)); + Spawn(_wallProtoId, coords); + } +} diff --git a/Resources/Prototypes/Imperial/Medieval/medieval.yml b/Resources/Prototypes/Imperial/Medieval/medieval.yml index 24186e7566..04f021efc0 100644 --- a/Resources/Prototypes/Imperial/Medieval/medieval.yml +++ b/Resources/Prototypes/Imperial/Medieval/medieval.yml @@ -25,6 +25,25 @@ - type: ExplosionResistance damageCoefficient: 19 +- type: entity + parent: BaseWall + id: MedievalStoneBrickWallIndestructable + name: stone wall + components: + - type: Tag + tags: + - Wall + - type: Sprite + sprite: Imperial/Medieval/Walls/stone_brick.rsi + - type: Icon + sprite: Imperial/Medieval/Walls/stone_brick.rsi + - type: Damageable + damageContainer: StructuralInorganic + damageModifierSet: StructuralMetallicStrong + - type: IconSmooth + key: walls + base: brick + - type: entity parent: BarricadeMedieval id: MedievalStockade From 257756aba7da07d9ed243a3e1c4857db83250a18 Mon Sep 17 00:00:00 2001 From: Aristophan_ Date: Sun, 1 Feb 2026 00:30:33 +0400 Subject: [PATCH 2/6] continuing --- .../Procedural/DungeonGenerationSystem.cs | 240 +++++++++++++++--- 1 file changed, 210 insertions(+), 30 deletions(-) diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs index fd75333eb4..e21075bcd5 100644 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs @@ -1,32 +1,38 @@ using System.Linq; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.Maths; -using Robust.Shared.Random; using Content.Shared.Maps; using Content.Shared.Parallax.Biomes; using Content.Server.Parallax; using Robust.Server.GameObjects; +using Robust.Shared.Random; namespace Content.Server.Imperial.Medieval.Procedural; public sealed partial class DungeonGenerationSystem : EntitySystem { [Dependency] private readonly MapSystem _mapSys = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly BiomeSystem _biome = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly TileSystem _tileSys = default!; [Dependency] private readonly IConsoleHost _console = default!; - [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + // Константы и прототипы тайлов/стен private readonly ProtoId _biomeProtoId = "Empty"; private readonly ProtoId _floorProtoId = "MedievalFloorStone9"; private readonly EntProtoId _wallProtoId = "MedievalStoneBrickWallIndestructable"; + private readonly EntProtoId _doorProtoId = "MedievalAirlock"; + + // Размер одной ячейки (комнаты) + private readonly Vector2i _size = new Vector2i(15, 15); public override void Initialize() { @@ -36,61 +42,235 @@ public override void Initialize() private void GenerateCommand(IConsoleShell shell, string argstr, string[] args) { + if (!GenerateDungeon(5, 5, out var grid)) + { + shell.WriteLine("Failed to generate dungeon."); + } + else + { + shell.WriteLine($"Created bio-dungeon on Map {grid.Value.Owner}"); + } + } + + public bool GenerateDungeon(int width, int height, [NotNullWhen(true)] out Entity? dungeonGrid) + { + dungeonGrid = null; var random = new Random(); - var mapId = _mapSys.CreateMap(); - var grid = _mapManager.CreateGridEntity(mapId); + + var tileDef = (ContentTileDefinition)_tileDefMan[_floorProtoId]; + + // Расчет размеров: (Кол-во * (Размер - 1)) + 1. + // -1 нужно для наложения стен соседних комнат друг на друга. + var totalPixelWidth = width * (_size.X - 1) + 1; + var totalPixelHeight = height * (_size.Y - 1) + 1; + + var minX = -totalPixelWidth / 2; + var minY = -totalPixelHeight / 2; + var maxX = minX + totalPixelWidth; + var maxY = minY + totalPixelHeight; + + var dungeonBox = new Box2i(minX, minY, maxX, maxY); + + // 1. Создаем карту и базовый грид (пол + периметр) + if (!PrepareDungeonBase(dungeonBox, tileDef, random, out dungeonGrid, out var mapId)) + { + return false; + } + + var gridUid = dungeonGrid.Value.Owner; + var gridComp = dungeonGrid.Value.Comp; + + // 2. Генерируем логику лабиринта (только стены и двери) + var layout = GenerateLogicalLayout(width, height, random); + + // 3. Строим внутренние стены и двери (физическое разделение) + BuildInternalStructure(gridUid, gridComp, layout, dungeonBox.BottomLeft, width, height); + + return true; + } + + private bool PrepareDungeonBase( + Box2i box, + ContentTileDefinition tileDef, + Random random, + [NotNullWhen(true)] out Entity? dungeonGrid, + out MapId mapId) + { + dungeonGrid = null; + var mapEntity = _mapSys.CreateMap(out mapId); if (_proto.TryIndex(_biomeProtoId, out var biomeProto)) { - _biome.EnsurePlanet(grid.Owner, biomeProto); + _biome.EnsurePlanet(mapEntity, biomeProto); } - var width = 10; - var height = 10; - var roomSize = new Vector2i(10, 10); // Итоговый размер 100x100 тайлов + if (!TryComp(mapEntity, out var gridComp)) + { + mapId = MapId.Nullspace; + return false; + } - var tileDef = (ContentTileDefinition)_tileDefMan[_floorProtoId]; + dungeonGrid = (mapEntity, gridComp); var tilesToSet = new List<(Vector2i, Tile)>(); - // Границы: от -100 до 0 - var minX = -(width * roomSize.X); - var minY = -(height * roomSize.Y); - var box = new Box2i(minX, minY, 0, 0); - + // Заполняем пол for (var x = box.Left; x < box.Right; x++) { for (var y = box.Bottom; y < box.Top; y++) { - // Тут мы просто генерируем Variant, это быстро var tile = _tileSys.GetVariantTile(tileDef, random); tilesToSet.Add((new Vector2i(x, y), tile)); } } + _mapSys.SetTiles(mapEntity, gridComp, tilesToSet); + + // Строим стены по периметру + var minX = box.Left; + var minY = box.Bottom; + var maxX = box.Right - 1; + var maxY = box.Top - 1; - if (TryComp(grid.Owner, out var gridComp)) + for (var x = minX; x <= maxX; x++) + { + SpawnWall(mapEntity, gridComp, new Vector2i(x, minY)); + SpawnWall(mapEntity, gridComp, new Vector2i(x, maxY)); + } + for (var y = minY; y <= maxY; y++) { - _mapSys.SetTiles(grid.Owner, gridComp, tilesToSet); + SpawnWall(mapEntity, gridComp, new Vector2i(minX, y)); + SpawnWall(mapEntity, gridComp, new Vector2i(maxX, y)); } - for (var x = box.Left; x <= box.Right; x++) + return true; + } + + private void BuildInternalStructure(EntityUid gridUid, MapGridComponent grid, DungeonLayout layout, Vector2i startPos, int width, int height) + { + var stepX = _size.X - 1; + var stepY = _size.Y - 1; + + // Вертикальные перегородки + for (var i = 1; i < width; i++) { - SpawnWall(grid.Owner, x, box.Bottom); - if (box.Top != box.Bottom) SpawnWall(grid.Owner, x, box.Top); + var wallX = startPos.X + (i * stepX); + for (var y = 0; y < height; y++) + { + var roomStartY = startPos.Y + (y * stepY); + var roomEndY = roomStartY + stepY; + var doorPos = roomStartY + (stepY / 2); + + bool isConnected = layout.HorizontalDoors[i - 1, y]; + + for (var wy = roomStartY; wy <= roomEndY; wy++) + { + var pos = new Vector2i(wallX, wy); + + if (isConnected && wy == doorPos) + { + Spawn(_doorProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); + } + else + { + SpawnWall(gridUid, grid, pos); + } + } + } } - for (var y = box.Bottom + 1; y < box.Top; y++) + // Горизонтальные перегородки + for (var j = 1; j < height; j++) { - SpawnWall(grid.Owner, box.Left, y); - if (box.Right != box.Left) SpawnWall(grid.Owner, box.Right, y); + var wallY = startPos.Y + (j * stepY); + for (var x = 0; x < width; x++) + { + var roomStartX = startPos.X + (x * stepX); + var roomEndX = roomStartX + stepX; + var doorPos = roomStartX + (stepX / 2); + + bool isConnected = layout.VerticalDoors[x, j - 1]; + + for (var wx = roomStartX; wx <= roomEndX; wx++) + { + var pos = new Vector2i(wx, wallY); + + // ИСПРАВЛЕНО: Аналогично, строго позиция двери + if (isConnected && wx == doorPos) + { + Spawn(_doorProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); + } + else + { + SpawnWall(gridUid, grid, pos); + } + } + } } + } + + private void SpawnWall(EntityUid gridUid, MapGridComponent grid, Vector2i pos) + { + // Проверяем, нет ли уже стены (чтобы на углах не было дубликатов) + // Не самый эффективный метод GetAnchoredEntities для массовой генерации, + // но при небольших размерах данжа приемлемо. + if (_mapSys.GetAnchoredEntities(gridUid, grid, pos).Any()) + return; - shell.WriteLine($"Dungeon generated on Map {mapId} (Grid {grid.Owner})!"); + Spawn(_wallProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); } - private void SpawnWall(EntityUid gridUid, int x, int y) + private DungeonLayout GenerateLogicalLayout(int width, int height, Random random) { - var coords = new EntityCoordinates(gridUid, new Vector2i(x, y)); - Spawn(_wallProtoId, coords); + // Теперь здесь не выбираются прототипы комнат, только создается граф дверей + var layout = new DungeonLayout + { + HorizontalDoors = new bool[width - 1, height], + VerticalDoors = new bool[width, height - 1] + }; + GenerateMaze(width, height, random, ref layout); + return layout; + } + + private void GenerateMaze(int width, int height, Random random, ref DungeonLayout layout) + { + var visited = new bool[width, height]; + var stack = new Stack(); + var startX = random.Next(width); + var startY = random.Next(height); + stack.Push(new Vector2i(startX, startY)); + visited[startX, startY] = true; + var directions = new Vector2i[] { new(0, 1), new(0, -1), new(1, 0), new(-1, 0) }; + + while (stack.Count > 0) + { + var current = stack.Peek(); + var unvisited = new List(); + foreach (var dir in directions) + { + var n = current + dir; + if (n.X >= 0 && n.X < width && n.Y >= 0 && n.Y < height && !visited[n.X, n.Y]) + unvisited.Add(n); + } + + if (unvisited.Count > 0) + { + var next = unvisited[random.Next(unvisited.Count)]; + if (next.X > current.X) layout.HorizontalDoors[current.X, current.Y] = true; + else if (next.X < current.X) layout.HorizontalDoors[next.X, current.Y] = true; + else if (next.Y > current.Y) layout.VerticalDoors[current.X, current.Y] = true; + else if (next.Y < current.Y) layout.VerticalDoors[current.X, next.Y] = true; + + visited[next.X, next.Y] = true; + stack.Push(next); + } + else stack.Pop(); + } } } + +// Структура упрощена: убрано поле Rooms[,] +public struct DungeonLayout +{ + public bool[,] HorizontalDoors; + public bool[,] VerticalDoors; +} From a2119e37bc7946cb9a8ba5c82b16b63ca964117b Mon Sep 17 00:00:00 2001 From: Aristophan_ Date: Sun, 1 Feb 2026 01:01:12 +0400 Subject: [PATCH 3/6] =?UTF-8?q?=D1=82=D0=B0=D0=BA,=20=D0=BF=D0=BE=D1=87?= =?UTF-8?q?=D1=82=D0=B8=20=D0=B7=D0=B0=D0=BA=D0=BE=D0=BD=D1=87=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=BA=D1=83?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Procedural/DungeonGenerationSystem.cs | 234 ++++++++++++++---- 1 file changed, 183 insertions(+), 51 deletions(-) diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs index e21075bcd5..69385ce2c9 100644 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs @@ -1,6 +1,5 @@ using System.Linq; using System.Diagnostics.CodeAnalysis; -using System.Numerics; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; @@ -9,29 +8,23 @@ using Content.Shared.Parallax.Biomes; using Content.Server.Parallax; using Robust.Server.GameObjects; -using Robust.Shared.Random; namespace Content.Server.Imperial.Medieval.Procedural; public sealed partial class DungeonGenerationSystem : EntitySystem { [Dependency] private readonly MapSystem _mapSys = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly BiomeSystem _biome = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly TileSystem _tileSys = default!; [Dependency] private readonly IConsoleHost _console = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - // Константы и прототипы тайлов/стен private readonly ProtoId _biomeProtoId = "Empty"; private readonly ProtoId _floorProtoId = "MedievalFloorStone9"; private readonly EntProtoId _wallProtoId = "MedievalStoneBrickWallIndestructable"; private readonly EntProtoId _doorProtoId = "MedievalAirlock"; - // Размер одной ячейки (комнаты) private readonly Vector2i _size = new Vector2i(15, 15); public override void Initialize() @@ -52,15 +45,15 @@ private void GenerateCommand(IConsoleShell shell, string argstr, string[] args) } } + /// + /// Основная точка входа. Рассчитывает размеры грида, создает карту и запускает этапы генерации. + /// public bool GenerateDungeon(int width, int height, [NotNullWhen(true)] out Entity? dungeonGrid) { dungeonGrid = null; var random = new Random(); - var tileDef = (ContentTileDefinition)_tileDefMan[_floorProtoId]; - // Расчет размеров: (Кол-во * (Размер - 1)) + 1. - // -1 нужно для наложения стен соседних комнат друг на друга. var totalPixelWidth = width * (_size.X - 1) + 1; var totalPixelHeight = height * (_size.Y - 1) + 1; @@ -68,41 +61,30 @@ public bool GenerateDungeon(int width, int height, [NotNullWhen(true)] out Entit var minY = -totalPixelHeight / 2; var maxX = minX + totalPixelWidth; var maxY = minY + totalPixelHeight; - var dungeonBox = new Box2i(minX, minY, maxX, maxY); - // 1. Создаем карту и базовый грид (пол + периметр) - if (!PrepareDungeonBase(dungeonBox, tileDef, random, out dungeonGrid, out var mapId)) - { + if (!PrepareDungeonBase(dungeonBox, tileDef, random, out dungeonGrid, out _)) return false; - } var gridUid = dungeonGrid.Value.Owner; var gridComp = dungeonGrid.Value.Comp; - // 2. Генерируем логику лабиринта (только стены и двери) var layout = GenerateLogicalLayout(width, height, random); - - // 3. Строим внутренние стены и двери (физическое разделение) BuildInternalStructure(gridUid, gridComp, layout, dungeonBox.BottomLeft, width, height); return true; } - private bool PrepareDungeonBase( - Box2i box, - ContentTileDefinition tileDef, - Random random, - [NotNullWhen(true)] out Entity? dungeonGrid, - out MapId mapId) + /// + /// Создает карту, устанавливает биом, заливает тайлы пола и строит внешние стены по периметру. + /// + private bool PrepareDungeonBase(Box2i box, ContentTileDefinition tileDef, Random random, [NotNullWhen(true)] out Entity? dungeonGrid, out MapId mapId) { dungeonGrid = null; var mapEntity = _mapSys.CreateMap(out mapId); if (_proto.TryIndex(_biomeProtoId, out var biomeProto)) - { _biome.EnsurePlanet(mapEntity, biomeProto); - } if (!TryComp(mapEntity, out var gridComp)) { @@ -111,10 +93,8 @@ private bool PrepareDungeonBase( } dungeonGrid = (mapEntity, gridComp); - var tilesToSet = new List<(Vector2i, Tile)>(); - // Заполняем пол for (var x = box.Left; x < box.Right; x++) { for (var y = box.Bottom; y < box.Top; y++) @@ -125,7 +105,6 @@ private bool PrepareDungeonBase( } _mapSys.SetTiles(mapEntity, gridComp, tilesToSet); - // Строим стены по периметру var minX = box.Left; var minY = box.Bottom; var maxX = box.Right - 1; @@ -145,6 +124,9 @@ private bool PrepareDungeonBase( return true; } + /// + /// Размещает физические стены и двери внутри данжа на основе логической схемы. + /// private void BuildInternalStructure(EntityUid gridUid, MapGridComponent grid, DungeonLayout layout, Vector2i startPos, int width, int height) { var stepX = _size.X - 1; @@ -165,15 +147,11 @@ private void BuildInternalStructure(EntityUid gridUid, MapGridComponent grid, Du for (var wy = roomStartY; wy <= roomEndY; wy++) { var pos = new Vector2i(wallX, wy); - + // Строгая проверка (wy == doorPos) гарантирует отсутствие дыр вокруг двери. if (isConnected && wy == doorPos) - { Spawn(_doorProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); - } else - { SpawnWall(gridUid, grid, pos); - } } } } @@ -193,16 +171,10 @@ private void BuildInternalStructure(EntityUid gridUid, MapGridComponent grid, Du for (var wx = roomStartX; wx <= roomEndX; wx++) { var pos = new Vector2i(wx, wallY); - - // ИСПРАВЛЕНО: Аналогично, строго позиция двери if (isConnected && wx == doorPos) - { Spawn(_doorProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); - } else - { SpawnWall(gridUid, grid, pos); - } } } } @@ -210,35 +182,42 @@ private void BuildInternalStructure(EntityUid gridUid, MapGridComponent grid, Du private void SpawnWall(EntityUid gridUid, MapGridComponent grid, Vector2i pos) { - // Проверяем, нет ли уже стены (чтобы на углах не было дубликатов) - // Не самый эффективный метод GetAnchoredEntities для массовой генерации, - // но при небольших размерах данжа приемлемо. - if (_mapSys.GetAnchoredEntities(gridUid, grid, pos).Any()) - return; - + if (_mapSys.GetAnchoredEntities(gridUid, grid, pos).Any()) return; Spawn(_wallProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); } + /// + /// Генерирует абстрактную схему дверей: создает остовное дерево, добавляет циклы и ищет альтернативные пути. + /// private DungeonLayout GenerateLogicalLayout(int width, int height, Random random) { - // Теперь здесь не выбираются прототипы комнат, только создается граф дверей var layout = new DungeonLayout { HorizontalDoors = new bool[width - 1, height], - VerticalDoors = new bool[width, height - 1] + VerticalDoors = new bool[width, height - 1], + SpecialPairs = new List<(Vector2i Room, Vector2i DoorPos)>() }; + GenerateMaze(width, height, random, ref layout); + AddLoops(width, height, random, ref layout); + FindSpecialPairs(width, height, random, ref layout); + return layout; } + /// + /// Алгоритм Recursive Backtracker для создания идеального лабиринта (гарантия связности). + /// private void GenerateMaze(int width, int height, Random random, ref DungeonLayout layout) { var visited = new bool[width, height]; var stack = new Stack(); - var startX = random.Next(width); - var startY = random.Next(height); + + var startX = 0; + var startY = 0; stack.Push(new Vector2i(startX, startY)); visited[startX, startY] = true; + var directions = new Vector2i[] { new(0, 1), new(0, -1), new(1, 0), new(-1, 0) }; while (stack.Count > 0) @@ -255,6 +234,7 @@ private void GenerateMaze(int width, int height, Random random, ref DungeonLayou if (unvisited.Count > 0) { var next = unvisited[random.Next(unvisited.Count)]; + if (next.X > current.X) layout.HorizontalDoors[current.X, current.Y] = true; else if (next.X < current.X) layout.HorizontalDoors[next.X, current.Y] = true; else if (next.Y > current.Y) layout.VerticalDoors[current.X, current.Y] = true; @@ -266,11 +246,163 @@ private void GenerateMaze(int width, int height, Random random, ref DungeonLayou else stack.Pop(); } } + + /// + /// Добавляет случайные проходы в стенах, соблюдая ограничение максимум 3 двери на комнату. + /// + private void AddLoops(int width, int height, Random random, ref DungeonLayout layout) + { + var potentialWalls = new List<(Vector2i CellA, Vector2i CellB, bool IsHorizontal)>(); + + for (var x = 0; x < width - 1; x++) + for (var y = 0; y < height; y++) + if (!layout.HorizontalDoors[x, y]) + potentialWalls.Add((new Vector2i(x, y), new Vector2i(x + 1, y), true)); + + for (var x = 0; x < width; x++) + for (var y = 0; y < height - 1; y++) + if (!layout.VerticalDoors[x, y]) + potentialWalls.Add((new Vector2i(x, y), new Vector2i(x, y + 1), false)); + + var walls = potentialWalls.OrderBy(_ => random.Next()).ToList(); + + foreach (var (a, b, isHor) in walls) + { + int doorsA = CountDoors(a, width, height, layout); + int doorsB = CountDoors(b, width, height, layout); + + // Если у комнат уже 3 двери - пропускаем. Это также обеспечивает приоритет 1 > 2 > 3. + if (doorsA >= 3 || doorsB >= 3) continue; + + if (random.NextDouble() < 0.20) + { + if (isHor) + layout.HorizontalDoors[Math.Min(a.X, b.X), a.Y] = true; + else + layout.VerticalDoors[a.X, Math.Min(a.Y, b.Y)] = true; + } + } + } + + private int CountDoors(Vector2i cell, int w, int h, DungeonLayout layout) + { + int count = 0; + if (cell.X > 0 && layout.HorizontalDoors[cell.X - 1, cell.Y]) count++; + if (cell.X < w - 1 && layout.HorizontalDoors[cell.X, cell.Y]) count++; + if (cell.Y > 0 && layout.VerticalDoors[cell.X, cell.Y - 1]) count++; + if (cell.Y < h - 1 && layout.VerticalDoors[cell.X, cell.Y]) count++; + return count; + } + + /// + /// Находит пары (Комната, Дверь), где в комнату можно попасть от старта, не используя эту конкретную дверь. + /// + private void FindSpecialPairs(int width, int height, Random random, ref DungeonLayout layout) + { + var targetCount = (width * height) / 5; + if (targetCount < 1) targetCount = 1; + + var allDoors = new List<(Vector2i CellA, Vector2i CellB, Vector2i DoorGridIndex, bool IsHor)>(); + + for (var x = 0; x < width - 1; x++) + for (var y = 0; y < height; y++) + if (layout.HorizontalDoors[x, y]) + allDoors.Add((new Vector2i(x, y), new Vector2i(x + 1, y), new Vector2i(x, y), true)); + + for (var x = 0; x < width; x++) + for (var y = 0; y < height - 1; y++) + if (layout.VerticalDoors[x, y]) + allDoors.Add((new Vector2i(x, y), new Vector2i(x, y + 1), new Vector2i(x, y), false)); + + var shuffledDoors = allDoors.OrderBy(_ => random.Next()).ToList(); + var startNode = new Vector2i(0, 0); + + foreach (var (a, b, doorIdx, isHor) in shuffledDoors) + { + if (layout.SpecialPairs.Count >= targetCount) break; + + // Проверяем возможность пути в B, заблокировав текущую дверь + if (b != startNode) + { + if (HasPath(startNode, b, doorIdx, isHor, width, height, layout)) + { + layout.SpecialPairs.Add((b, doorIdx)); + continue; + } + } + + // Проверяем возможность пути в A, заблокировав текущую дверь + if (a != startNode) + { + if (HasPath(startNode, a, doorIdx, isHor, width, height, layout)) + { + layout.SpecialPairs.Add((a, doorIdx)); + continue; + } + } + } + } + + /// + /// BFS поиск пути с учетом "виртуально закрытой" двери. + /// + private bool HasPath(Vector2i start, Vector2i end, Vector2i blockedDoorIdx, bool blockedDoorIsHor, int w, int h, DungeonLayout layout) + { + var q = new Queue(); + var visited = new HashSet(); + + q.Enqueue(start); + visited.Add(start); + + while (q.Count > 0) + { + var curr = q.Dequeue(); + if (curr == end) return true; + + // Вверх (y+1) + if (curr.Y < h - 1 && layout.VerticalDoors[curr.X, curr.Y]) + { + if (blockedDoorIsHor || blockedDoorIdx.X != curr.X || blockedDoorIdx.Y != curr.Y) + { + var next = new Vector2i(curr.X, curr.Y + 1); + if (visited.Add(next)) q.Enqueue(next); + } + } + // Вниз (y-1) + if (curr.Y > 0 && layout.VerticalDoors[curr.X, curr.Y - 1]) + { + if (blockedDoorIsHor || blockedDoorIdx.X != curr.X || blockedDoorIdx.Y != curr.Y - 1) + { + var next = new Vector2i(curr.X, curr.Y - 1); + if (visited.Add(next)) q.Enqueue(next); + } + } + // Вправо (x+1) + if (curr.X < w - 1 && layout.HorizontalDoors[curr.X, curr.Y]) + { + if (!blockedDoorIsHor || blockedDoorIdx.X != curr.X || blockedDoorIdx.Y != curr.Y) + { + var next = new Vector2i(curr.X + 1, curr.Y); + if (visited.Add(next)) q.Enqueue(next); + } + } + // Влево (x-1) + if (curr.X > 0 && layout.HorizontalDoors[curr.X - 1, curr.Y]) + { + if (!blockedDoorIsHor || blockedDoorIdx.X != curr.X - 1 || blockedDoorIdx.Y != curr.Y) + { + var next = new Vector2i(curr.X - 1, curr.Y); + if (visited.Add(next)) q.Enqueue(next); + } + } + } + return false; + } } -// Структура упрощена: убрано поле Rooms[,] public struct DungeonLayout { public bool[,] HorizontalDoors; public bool[,] VerticalDoors; + public List<(Vector2i Room, Vector2i DoorPos)> SpecialPairs; } From 4b3fd8b90a29b98587812cbed040f299fcddcf6d Mon Sep 17 00:00:00 2001 From: Aristophan_ Date: Mon, 2 Feb 2026 23:59:55 +0400 Subject: [PATCH 4/6] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4=D0=B2=D0=B8=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BA=D0=B5=20=D0=BB=D0=B0=D0=B1=D0=B8=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B0.=20=D0=BD=D0=B0=D0=B4=D0=BE=20=D1=81=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D1=82=D1=8C=20=D0=BD=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D0=B2=D1=81=D1=91.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DungeonGenerationSystem.ItemEntitites.cs | 7 - .../DungeonGenerationSystem.MazeLogic.cs | 201 +++++++++ .../DungeonGenerationSystem.Rooms.cs | 7 - .../Procedural/DungeonGenerationSystem.cs | 385 +++++++----------- .../Imperial/Medieval/Procedural/rooms.yml | 12 + .../Prototypes/Imperial/Medieval/decor.yml | 12 + .../Prototypes/Imperial/Medieval/doors.yml | 67 +++ .../Prototypes/Imperial/Medieval/keys.yml | 98 +++++ .../Prototypes/Imperial/Medieval/tags.yml | 11 +- 9 files changed, 538 insertions(+), 262 deletions(-) delete mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs create mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.MazeLogic.cs delete mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs create mode 100644 Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs deleted file mode 100644 index 8f4d32bca8..0000000000 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.ItemEntitites.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace Content.Server.Imperial.Medieval.Procedural; - -public sealed partial class DungeonGenerationSystem -{ - -} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.MazeLogic.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.MazeLogic.cs new file mode 100644 index 0000000000..9e4297a56e --- /dev/null +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.MazeLogic.cs @@ -0,0 +1,201 @@ +using System.Linq; + +namespace Content.Server.Imperial.Medieval.Procedural; + +public sealed partial class DungeonGenerationSystem +{ + // Структура данных + public struct DungeonLayout + { + public bool[,] HorizontalDoors; + public bool[,] VerticalDoors; + public List StartRooms; + public List<(Vector2i Room, Vector2i DoorIdx, bool IsHor)> SpecialPairs; + } + + private DungeonLayout GenerateLogicalLayout(int width, int height, Random random) + { + var layout = new DungeonLayout + { + HorizontalDoors = new bool[width - 1, height], + VerticalDoors = new bool[width, height - 1], + StartRooms = new List(), + SpecialPairs = new List<(Vector2i Room, Vector2i DoorIdx, bool IsHor)>() + }; + + GenerateMaze(width, height, random, ref layout); + AddLoops(width, height, random, ref layout); + ChooseStartRooms(width, height, random, ref layout); + FindSpecialPairs(width, height, random, ref layout); + + return layout; + } + + private void GenerateMaze(int width, int height, Random random, ref DungeonLayout layout) + { + var visited = new bool[width, height]; + var stack = new Stack(); + stack.Push(new Vector2i(0, 0)); + visited[0, 0] = true; + + var directions = new Vector2i[] { new(0, 1), new(0, -1), new(1, 0), new(-1, 0) }; + + while (stack.Count > 0) + { + var current = stack.Peek(); + var unvisited = new List(); + foreach (var dir in directions) + { + var n = current + dir; + if (n.X >= 0 && n.X < width && n.Y >= 0 && n.Y < height && !visited[n.X, n.Y]) + unvisited.Add(n); + } + + if (unvisited.Count > 0) + { + var next = unvisited[random.Next(unvisited.Count)]; + if (next.X > current.X) layout.HorizontalDoors[current.X, current.Y] = true; + else if (next.X < current.X) layout.HorizontalDoors[next.X, current.Y] = true; + else if (next.Y > current.Y) layout.VerticalDoors[current.X, current.Y] = true; + else if (next.Y < current.Y) layout.VerticalDoors[current.X, next.Y] = true; + + visited[next.X, next.Y] = true; + stack.Push(next); + } + else stack.Pop(); + } + } + + private void AddLoops(int width, int height, Random random, ref DungeonLayout layout) + { + var potentialWalls = new List<(Vector2i CellA, Vector2i CellB, bool IsHorizontal)>(); + + for (var x = 0; x < width - 1; x++) + for (var y = 0; y < height; y++) + if (!layout.HorizontalDoors[x, y]) + potentialWalls.Add((new Vector2i(x, y), new Vector2i(x + 1, y), true)); + + for (var x = 0; x < width; x++) + for (var y = 0; y < height - 1; y++) + if (!layout.VerticalDoors[x, y]) + potentialWalls.Add((new Vector2i(x, y), new Vector2i(x, y + 1), false)); + + var walls = potentialWalls.OrderBy(_ => random.Next()).ToList(); + + foreach (var (a, b, isHor) in walls) + { + if (CountDoors(a, width, height, layout) >= 3 || CountDoors(b, width, height, layout) >= 3) + continue; + + if (random.NextDouble() < 0.20) + { + if (isHor) layout.HorizontalDoors[Math.Min(a.X, b.X), a.Y] = true; + else layout.VerticalDoors[a.X, Math.Min(a.Y, b.Y)] = true; + } + } + } + + private int CountDoors(Vector2i cell, int w, int h, DungeonLayout layout) + { + int count = 0; + if (cell.X > 0 && layout.HorizontalDoors[cell.X - 1, cell.Y]) count++; + if (cell.X < w - 1 && layout.HorizontalDoors[cell.X, cell.Y]) count++; + if (cell.Y > 0 && layout.VerticalDoors[cell.X, cell.Y - 1]) count++; + if (cell.Y < h - 1 && layout.VerticalDoors[cell.X, cell.Y]) count++; + return count; + } + + private void ChooseStartRooms(int width, int height, Random random, ref DungeonLayout layout) + { + var allRooms = new List(); + for (var x = 0; x < width; x++) + for (var y = 0; y < height; y++) + allRooms.Add(new Vector2i(x, y)); + + for (int i = 0; i < 4 && allRooms.Count > 0; i++) + { + var idx = random.Next(allRooms.Count); + layout.StartRooms.Add(allRooms[idx]); + allRooms.RemoveAt(idx); + } + } + + private void FindSpecialPairs(int width, int height, Random random, ref DungeonLayout layout) + { + var targetCount = width * height / 5; + if (targetCount < 1) targetCount = 1; + + var candidateDoors = new List<(Vector2i Idx, bool IsHor)>(); + for (var x = 0; x < width - 1; x++) + for (var y = 0; y < height; y++) + if (layout.HorizontalDoors[x, y]) candidateDoors.Add((new Vector2i(x, y), true)); + + for (var x = 0; x < width; x++) + for (var y = 0; y < height - 1; y++) + if (layout.VerticalDoors[x, y]) candidateDoors.Add((new Vector2i(x, y), false)); + + candidateDoors = candidateDoors.OrderBy(_ => random.Next()).ToList(); + var lockedDoorsSet = new HashSet<(Vector2i, bool)>(); + + foreach (var (doorIdx, isHor) in candidateDoors) + { + if (layout.SpecialPairs.Count >= targetCount) break; + + lockedDoorsSet.Add((doorIdx, isHor)); + var reachableRooms = GetReachableRooms(layout.StartRooms, lockedDoorsSet, width, height, layout); + + if (reachableRooms.Count > 0) + { + var triggerRoom = reachableRooms[random.Next(reachableRooms.Count)]; + layout.SpecialPairs.Add((triggerRoom, doorIdx, isHor)); + } + else + { + lockedDoorsSet.Remove((doorIdx, isHor)); + } + } + } + + private List GetReachableRooms(List starts, HashSet<(Vector2i, bool)> lockedDoors, int w, int h, DungeonLayout layout) + { + var visited = new HashSet(); + var queue = new Queue(); + var result = new List(); + + foreach (var start in starts) + { + if (visited.Add(start)) + { + queue.Enqueue(start); + result.Add(start); + } + } + + while (queue.Count > 0) + { + var curr = queue.Dequeue(); + + if (curr.Y < h - 1 && layout.VerticalDoors[curr.X, curr.Y] && !lockedDoors.Contains((new Vector2i(curr.X, curr.Y), false))) + { + var next = new Vector2i(curr.X, curr.Y + 1); + if (visited.Add(next)) { queue.Enqueue(next); result.Add(next); } + } + if (curr.Y > 0 && layout.VerticalDoors[curr.X, curr.Y - 1] && !lockedDoors.Contains((new Vector2i(curr.X, curr.Y - 1), false))) + { + var next = new Vector2i(curr.X, curr.Y - 1); + if (visited.Add(next)) { queue.Enqueue(next); result.Add(next); } + } + if (curr.X < w - 1 && layout.HorizontalDoors[curr.X, curr.Y] && !lockedDoors.Contains((new Vector2i(curr.X, curr.Y), true))) + { + var next = new Vector2i(curr.X + 1, curr.Y); + if (visited.Add(next)) { queue.Enqueue(next); result.Add(next); } + } + if (curr.X > 0 && layout.HorizontalDoors[curr.X - 1, curr.Y] && !lockedDoors.Contains((new Vector2i(curr.X - 1, curr.Y), true))) + { + var next = new Vector2i(curr.X - 1, curr.Y); + if (visited.Add(next)) { queue.Enqueue(next); result.Add(next); } + } + } + return result; + } +} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs deleted file mode 100644 index 8f4d32bca8..0000000000 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.Rooms.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace Content.Server.Imperial.Medieval.Procedural; - -public sealed partial class DungeonGenerationSystem -{ - -} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs index 69385ce2c9..3a30d05d62 100644 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs @@ -8,6 +8,10 @@ using Content.Shared.Parallax.Biomes; using Content.Server.Parallax; using Robust.Server.GameObjects; +using Robust.Shared.Random; +using Content.Server.Storage.EntitySystems; +using Content.Shared.Tag; +using Content.Shared.Storage.Components; namespace Content.Server.Imperial.Medieval.Procedural; @@ -19,11 +23,25 @@ public sealed partial class DungeonGenerationSystem : EntitySystem [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly TileSystem _tileSys = default!; [Dependency] private readonly IConsoleHost _console = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityStorageSystem _entityStorage = default!; + [Dependency] private readonly TagSystem _tag = default!; + // --- Прототипы --- private readonly ProtoId _biomeProtoId = "Empty"; private readonly ProtoId _floorProtoId = "MedievalFloorStone9"; - private readonly EntProtoId _wallProtoId = "MedievalStoneBrickWallIndestructable"; - private readonly EntProtoId _doorProtoId = "MedievalAirlock"; + private readonly EntProtoId _defaultDoorProtoId = "MedievalAirlock"; + private readonly EntProtoId _centerEntityProtoId = "MedievalDungeonRoomMarker"; + + // Тег для поиска сундуков + private readonly ProtoId _chestTag = "MedievalChest"; + + private readonly EntProtoId[] _wallProtoIds = + [ + "MedievalStoneBrickWallIndestructable", + "MedievalStoneBrickWallIndestructable", //here I need to replace with other 2 walls, which are not sprited now + "MedievalStoneBrickWallIndestructable" + ]; private readonly Vector2i _size = new Vector2i(15, 15); @@ -41,44 +59,42 @@ private void GenerateCommand(IConsoleShell shell, string argstr, string[] args) } else { - shell.WriteLine($"Created bio-dungeon on Map {grid.Value.Owner}"); + shell.WriteLine($"Created dungeon on Map {grid.Value.Owner}"); } } - /// - /// Основная точка входа. Рассчитывает размеры грида, создает карту и запускает этапы генерации. - /// public bool GenerateDungeon(int width, int height, [NotNullWhen(true)] out Entity? dungeonGrid) { dungeonGrid = null; var random = new Random(); - var tileDef = (ContentTileDefinition)_tileDefMan[_floorProtoId]; + // 1. Логика + var layout = GenerateLogicalLayout(width, height, random); + + // 2. Инициализация карты var totalPixelWidth = width * (_size.X - 1) + 1; var totalPixelHeight = height * (_size.Y - 1) + 1; + var dungeonBox = new Box2i(-totalPixelWidth / 2, -totalPixelHeight / 2, -totalPixelWidth / 2 + totalPixelWidth, -totalPixelHeight / 2 + totalPixelHeight); - var minX = -totalPixelWidth / 2; - var minY = -totalPixelHeight / 2; - var maxX = minX + totalPixelWidth; - var maxY = minY + totalPixelHeight; - var dungeonBox = new Box2i(minX, minY, maxX, maxY); - - if (!PrepareDungeonBase(dungeonBox, tileDef, random, out dungeonGrid, out _)) + if (!PrepareDungeonBase(dungeonBox, random, out dungeonGrid, out _)) return false; var gridUid = dungeonGrid.Value.Owner; var gridComp = dungeonGrid.Value.Comp; - var layout = GenerateLogicalLayout(width, height, random); + // 3. Стены и двери BuildInternalStructure(gridUid, gridComp, layout, dungeonBox.BottomLeft, width, height); + // 4. Декор (спавн мебели, сундуков и т.д.) + SpawnRoomDecoration(gridUid, gridComp, dungeonBox.BottomLeft, width, height); + + // 5. Поиск сундуков и раскидывание ключей + SpawnKeysInExistingChests(gridUid, gridComp, dungeonBox.BottomLeft, layout.SpecialPairs); + return true; } - /// - /// Создает карту, устанавливает биом, заливает тайлы пола и строит внешние стены по периметру. - /// - private bool PrepareDungeonBase(Box2i box, ContentTileDefinition tileDef, Random random, [NotNullWhen(true)] out Entity? dungeonGrid, out MapId mapId) + private bool PrepareDungeonBase(Box2i box, Random random, [NotNullWhen(true)] out Entity? dungeonGrid, out MapId mapId) { dungeonGrid = null; var mapEntity = _mapSys.CreateMap(out mapId); @@ -93,6 +109,7 @@ private bool PrepareDungeonBase(Box2i box, ContentTileDefinition tileDef, Random } dungeonGrid = (mapEntity, gridComp); + var tileDef = (ContentTileDefinition)_tileDefMan[_floorProtoId]; var tilesToSet = new List<(Vector2i, Tile)>(); for (var x = box.Left; x < box.Right; x++) @@ -105,304 +122,178 @@ private bool PrepareDungeonBase(Box2i box, ContentTileDefinition tileDef, Random } _mapSys.SetTiles(mapEntity, gridComp, tilesToSet); - var minX = box.Left; - var minY = box.Bottom; - var maxX = box.Right - 1; - var maxY = box.Top - 1; + var minX = box.Left; var minY = box.Bottom; + var maxX = box.Right - 1; var maxY = box.Top - 1; - for (var x = minX; x <= maxX; x++) - { - SpawnWall(mapEntity, gridComp, new Vector2i(x, minY)); - SpawnWall(mapEntity, gridComp, new Vector2i(x, maxY)); - } - for (var y = minY; y <= maxY; y++) - { - SpawnWall(mapEntity, gridComp, new Vector2i(minX, y)); - SpawnWall(mapEntity, gridComp, new Vector2i(maxX, y)); - } + for (var x = minX; x <= maxX; x++) { SpawnWall(mapEntity, gridComp, new Vector2i(x, minY)); SpawnWall(mapEntity, gridComp, new Vector2i(x, maxY)); } + for (var y = minY; y <= maxY; y++) { SpawnWall(mapEntity, gridComp, new Vector2i(minX, y)); SpawnWall(mapEntity, gridComp, new Vector2i(maxX, y)); } return true; } - /// - /// Размещает физические стены и двери внутри данжа на основе логической схемы. - /// private void BuildInternalStructure(EntityUid gridUid, MapGridComponent grid, DungeonLayout layout, Vector2i startPos, int width, int height) { var stepX = _size.X - 1; var stepY = _size.Y - 1; - // Вертикальные перегородки + var specialDoorLookup = new Dictionary<(Vector2i, bool), int>(); + for (int i = 0; i < layout.SpecialPairs.Count; i++) + { + var pair = layout.SpecialPairs[i]; + specialDoorLookup[(pair.DoorIdx, pair.IsHor)] = i; + } + for (var i = 1; i < width; i++) { var wallX = startPos.X + (i * stepX); for (var y = 0; y < height; y++) { var roomStartY = startPos.Y + (y * stepY); - var roomEndY = roomStartY + stepY; var doorPos = roomStartY + (stepY / 2); - bool isConnected = layout.HorizontalDoors[i - 1, y]; + int? specialIndex = null; + if (specialDoorLookup.TryGetValue((new Vector2i(i - 1, y), true), out var idx)) specialIndex = idx; - for (var wy = roomStartY; wy <= roomEndY; wy++) + for (var wy = roomStartY; wy <= roomStartY + stepY; wy++) { var pos = new Vector2i(wallX, wy); - // Строгая проверка (wy == doorPos) гарантирует отсутствие дыр вокруг двери. if (isConnected && wy == doorPos) - Spawn(_doorProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); - else - SpawnWall(gridUid, grid, pos); + { + var proto = _defaultDoorProtoId; + if (specialIndex.HasValue) proto = $"MedievalAirlockDungeon{GetLetter(specialIndex.Value)}"; + Spawn(proto, _mapSys.ToCoordinates(gridUid, pos, grid)); + } + else SpawnWall(gridUid, grid, pos); } } } - // Горизонтальные перегородки for (var j = 1; j < height; j++) { var wallY = startPos.Y + (j * stepY); for (var x = 0; x < width; x++) { var roomStartX = startPos.X + (x * stepX); - var roomEndX = roomStartX + stepX; var doorPos = roomStartX + (stepX / 2); - bool isConnected = layout.VerticalDoors[x, j - 1]; + int? specialIndex = null; + if (specialDoorLookup.TryGetValue((new Vector2i(x, j - 1), false), out var idx)) specialIndex = idx; - for (var wx = roomStartX; wx <= roomEndX; wx++) + for (var wx = roomStartX; wx <= roomStartX + stepX; wx++) { var pos = new Vector2i(wx, wallY); if (isConnected && wx == doorPos) - Spawn(_doorProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); - else - SpawnWall(gridUid, grid, pos); + { + var proto = _defaultDoorProtoId; + if (specialIndex.HasValue) proto = $"MedievalAirlockDungeon{GetLetter(specialIndex.Value)}"; + Spawn(proto, _mapSys.ToCoordinates(gridUid, pos, grid)); + } + else SpawnWall(gridUid, grid, pos); } } } } - private void SpawnWall(EntityUid gridUid, MapGridComponent grid, Vector2i pos) - { - if (_mapSys.GetAnchoredEntities(gridUid, grid, pos).Any()) return; - Spawn(_wallProtoId, _mapSys.ToCoordinates(gridUid, pos, grid)); - } - - /// - /// Генерирует абстрактную схему дверей: создает остовное дерево, добавляет циклы и ищет альтернативные пути. - /// - private DungeonLayout GenerateLogicalLayout(int width, int height, Random random) - { - var layout = new DungeonLayout - { - HorizontalDoors = new bool[width - 1, height], - VerticalDoors = new bool[width, height - 1], - SpecialPairs = new List<(Vector2i Room, Vector2i DoorPos)>() - }; - - GenerateMaze(width, height, random, ref layout); - AddLoops(width, height, random, ref layout); - FindSpecialPairs(width, height, random, ref layout); - - return layout; - } - - /// - /// Алгоритм Recursive Backtracker для создания идеального лабиринта (гарантия связности). - /// - private void GenerateMaze(int width, int height, Random random, ref DungeonLayout layout) - { - var visited = new bool[width, height]; - var stack = new Stack(); - - var startX = 0; - var startY = 0; - stack.Push(new Vector2i(startX, startY)); - visited[startX, startY] = true; - - var directions = new Vector2i[] { new(0, 1), new(0, -1), new(1, 0), new(-1, 0) }; - - while (stack.Count > 0) - { - var current = stack.Peek(); - var unvisited = new List(); - foreach (var dir in directions) - { - var n = current + dir; - if (n.X >= 0 && n.X < width && n.Y >= 0 && n.Y < height && !visited[n.X, n.Y]) - unvisited.Add(n); - } - - if (unvisited.Count > 0) - { - var next = unvisited[random.Next(unvisited.Count)]; - - if (next.X > current.X) layout.HorizontalDoors[current.X, current.Y] = true; - else if (next.X < current.X) layout.HorizontalDoors[next.X, current.Y] = true; - else if (next.Y > current.Y) layout.VerticalDoors[current.X, current.Y] = true; - else if (next.Y < current.Y) layout.VerticalDoors[current.X, next.Y] = true; - - visited[next.X, next.Y] = true; - stack.Push(next); - } - else stack.Pop(); - } - } - - /// - /// Добавляет случайные проходы в стенах, соблюдая ограничение максимум 3 двери на комнату. - /// - private void AddLoops(int width, int height, Random random, ref DungeonLayout layout) + private void SpawnRoomDecoration(EntityUid gridUid, MapGridComponent grid, Vector2i startPos, int width, int height) { - var potentialWalls = new List<(Vector2i CellA, Vector2i CellB, bool IsHorizontal)>(); - - for (var x = 0; x < width - 1; x++) - for (var y = 0; y < height; y++) - if (!layout.HorizontalDoors[x, y]) - potentialWalls.Add((new Vector2i(x, y), new Vector2i(x + 1, y), true)); + var stepX = _size.X - 1; + var stepY = _size.Y - 1; + var halfX = _size.X / 2; + var halfY = _size.Y / 2; for (var x = 0; x < width; x++) - for (var y = 0; y < height - 1; y++) - if (!layout.VerticalDoors[x, y]) - potentialWalls.Add((new Vector2i(x, y), new Vector2i(x, y + 1), false)); - - var walls = potentialWalls.OrderBy(_ => random.Next()).ToList(); - - foreach (var (a, b, isHor) in walls) { - int doorsA = CountDoors(a, width, height, layout); - int doorsB = CountDoors(b, width, height, layout); - - // Если у комнат уже 3 двери - пропускаем. Это также обеспечивает приоритет 1 > 2 > 3. - if (doorsA >= 3 || doorsB >= 3) continue; - - if (random.NextDouble() < 0.20) - { - if (isHor) - layout.HorizontalDoors[Math.Min(a.X, b.X), a.Y] = true; - else - layout.VerticalDoors[a.X, Math.Min(a.Y, b.Y)] = true; - } - } - } - - private int CountDoors(Vector2i cell, int w, int h, DungeonLayout layout) - { - int count = 0; - if (cell.X > 0 && layout.HorizontalDoors[cell.X - 1, cell.Y]) count++; - if (cell.X < w - 1 && layout.HorizontalDoors[cell.X, cell.Y]) count++; - if (cell.Y > 0 && layout.VerticalDoors[cell.X, cell.Y - 1]) count++; - if (cell.Y < h - 1 && layout.VerticalDoors[cell.X, cell.Y]) count++; - return count; - } - - /// - /// Находит пары (Комната, Дверь), где в комнату можно попасть от старта, не используя эту конкретную дверь. - /// - private void FindSpecialPairs(int width, int height, Random random, ref DungeonLayout layout) - { - var targetCount = (width * height) / 5; - if (targetCount < 1) targetCount = 1; - - var allDoors = new List<(Vector2i CellA, Vector2i CellB, Vector2i DoorGridIndex, bool IsHor)>(); - - for (var x = 0; x < width - 1; x++) for (var y = 0; y < height; y++) - if (layout.HorizontalDoors[x, y]) - allDoors.Add((new Vector2i(x, y), new Vector2i(x + 1, y), new Vector2i(x, y), true)); - - for (var x = 0; x < width; x++) - for (var y = 0; y < height - 1; y++) - if (layout.VerticalDoors[x, y]) - allDoors.Add((new Vector2i(x, y), new Vector2i(x, y + 1), new Vector2i(x, y), false)); - - var shuffledDoors = allDoors.OrderBy(_ => random.Next()).ToList(); - var startNode = new Vector2i(0, 0); - - foreach (var (a, b, doorIdx, isHor) in shuffledDoors) - { - if (layout.SpecialPairs.Count >= targetCount) break; - - // Проверяем возможность пути в B, заблокировав текущую дверь - if (b != startNode) { - if (HasPath(startNode, b, doorIdx, isHor, width, height, layout)) - { - layout.SpecialPairs.Add((b, doorIdx)); - continue; - } - } + var roomCenterX = startPos.X + (x * stepX) + halfX; + var roomCenterY = startPos.Y + (y * stepY) + halfY; - // Проверяем возможность пути в A, заблокировав текущую дверь - if (a != startNode) - { - if (HasPath(startNode, a, doorIdx, isHor, width, height, layout)) + Spawn(_centerEntityProtoId, _mapSys.ToCoordinates(gridUid, new Vector2i(roomCenterX, roomCenterY), grid)); + + // Пример: Спавним сундук с шансом 70% + // Если не заспавнится, ключ будет лежать на полу. + if (_random.NextFloat() > 0.3f) { - layout.SpecialPairs.Add((a, doorIdx)); - continue; + var chestPos = new Vector2i(roomCenterX + 2, roomCenterY + 2); + var chest = Spawn("MedievalChest", _mapSys.ToCoordinates(gridUid, chestPos, grid)); + _tag.AddTag(chest, _chestTag); } } } } - /// - /// BFS поиск пути с учетом "виртуально закрытой" двери. - /// - private bool HasPath(Vector2i start, Vector2i end, Vector2i blockedDoorIdx, bool blockedDoorIsHor, int w, int h, DungeonLayout layout) + private void SpawnKeysInExistingChests(EntityUid gridUid, MapGridComponent grid, Vector2i startPos, List<(Vector2i Room, Vector2i DoorIdx, bool IsHor)> specialPairs) { - var q = new Queue(); - var visited = new HashSet(); - - q.Enqueue(start); - visited.Add(start); + var stepX = _size.X - 1; + var stepY = _size.Y - 1; + var halfX = _size.X / 2; + var halfY = _size.Y / 2; - while (q.Count > 0) + for (int i = 0; i < specialPairs.Count; i++) { - var curr = q.Dequeue(); - if (curr == end) return true; - - // Вверх (y+1) - if (curr.Y < h - 1 && layout.VerticalDoors[curr.X, curr.Y]) + var pair = specialPairs[i]; + var letter = GetLetter(i); + var keyProto = $"MedievalKeyDungeon{letter}"; + + var roomIdx = pair.Room; + // Границы тайлов внутри комнаты (исключая стены) + var roomStartX = startPos.X + (roomIdx.X * stepX) + 1; + var roomStartY = startPos.Y + (roomIdx.Y * stepY) + 1; + var roomEndX = roomStartX + stepX - 2; + var roomEndY = roomStartY + stepY - 2; + + EntityUid? targetChest = null; + var validTiles = new List(); // Список всех тайлов комнаты, где можно заспавнить ключ + + // Сканируем комнату + for (var x = roomStartX; x <= roomEndX; x++) { - if (blockedDoorIsHor || blockedDoorIdx.X != curr.X || blockedDoorIdx.Y != curr.Y) + for (var y = roomStartY; y <= roomEndY; y++) { - var next = new Vector2i(curr.X, curr.Y + 1); - if (visited.Add(next)) q.Enqueue(next); + var pos = new Vector2i(x, y); + validTiles.Add(pos); // Запоминаем тайл как потенциальное место спавна + + var entities = _mapSys.GetAnchoredEntities(gridUid, grid, pos); + foreach (var ent in entities) + { + if (HasComp(ent) && _tag.HasTag(ent, _chestTag)) + { + targetChest = ent; + break; + } + } + if (targetChest != null) break; } + if (targetChest != null) break; } - // Вниз (y-1) - if (curr.Y > 0 && layout.VerticalDoors[curr.X, curr.Y - 1]) + + if (targetChest != null && TryComp(targetChest, out var storage)) { - if (blockedDoorIsHor || blockedDoorIdx.X != curr.X || blockedDoorIdx.Y != curr.Y - 1) - { - var next = new Vector2i(curr.X, curr.Y - 1); - if (visited.Add(next)) q.Enqueue(next); - } + // Вариант 1: Сундук найден, кладем ключ внутрь + var keyUid = Spawn(keyProto, Transform(targetChest.Value).Coordinates); + _entityStorage.Insert(keyUid, targetChest.Value, storage); } - // Вправо (x+1) - if (curr.X < w - 1 && layout.HorizontalDoors[curr.X, curr.Y]) + else { - if (!blockedDoorIsHor || blockedDoorIdx.X != curr.X || blockedDoorIdx.Y != curr.Y) - { - var next = new Vector2i(curr.X + 1, curr.Y); - if (visited.Add(next)) q.Enqueue(next); - } - } - // Влево (x-1) - if (curr.X > 0 && layout.HorizontalDoors[curr.X - 1, curr.Y]) - { - if (!blockedDoorIsHor || blockedDoorIdx.X != curr.X - 1 || blockedDoorIdx.Y != curr.Y) + // Вариант 2: Сундука нет, спавним на случайном тайле + var spawnPos = new Vector2i(startPos.X + (roomIdx.X * stepX) + halfX, startPos.Y + (roomIdx.Y * stepY) + halfY); // По дефолту центр + + if (validTiles.Count > 0) { - var next = new Vector2i(curr.X - 1, curr.Y); - if (visited.Add(next)) q.Enqueue(next); + spawnPos = _random.Pick(validTiles); } + + Spawn(keyProto, _mapSys.ToCoordinates(gridUid, spawnPos, grid)); } } - return false; } -} -public struct DungeonLayout -{ - public bool[,] HorizontalDoors; - public bool[,] VerticalDoors; - public List<(Vector2i Room, Vector2i DoorPos)> SpecialPairs; + private void SpawnWall(EntityUid gridUid, MapGridComponent grid, Vector2i pos) + { + if (_mapSys.GetAnchoredEntities(gridUid, grid, pos).Any()) return; + var wallProto = _random.Pick(_wallProtoIds); + Spawn(wallProto, _mapSys.ToCoordinates(gridUid, pos, grid)); + } + + private char GetLetter(int index) => (char)('A' + (index % 26)); } diff --git a/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml b/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml new file mode 100644 index 0000000000..7c9fd09f24 --- /dev/null +++ b/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml @@ -0,0 +1,12 @@ +- type: entity + id: MedievalDungeonRoomMarker + parent: BaseRoomMarker + name: marker + components: + - type: RoomFill + size: 13,13 + roomWhitelist: + tags: + - MedievalDungeonRoom + +# add room prototypes here under this comment and add MedievalDungeonRoom tag to them diff --git a/Resources/Prototypes/Imperial/Medieval/decor.yml b/Resources/Prototypes/Imperial/Medieval/decor.yml index ac20c5c72c..d53f05f9b2 100644 --- a/Resources/Prototypes/Imperial/Medieval/decor.yml +++ b/Resources/Prototypes/Imperial/Medieval/decor.yml @@ -259,6 +259,9 @@ node: cratelivestock containers: - entity_storage + - type: Tag + tags: + - MedievalChest - type: entity parent: CrateGeneric @@ -308,6 +311,9 @@ node: cratelivestock containers: - entity_storage + - type: Tag + tags: + - MedievalChest - type: entity parent: CrateGeneric @@ -357,6 +363,9 @@ node: cratelivestock containers: - entity_storage + - type: Tag + tags: + - MedievalChest - type: entity parent: CrateGeneric @@ -404,6 +413,9 @@ - type: Construction graph: MedievalChest4 node: MedievalChest4 + - type: Tag + tags: + - MedievalChest - type: construction id: MedievalChest4 diff --git a/Resources/Prototypes/Imperial/Medieval/doors.yml b/Resources/Prototypes/Imperial/Medieval/doors.yml index 3ef793fe09..c2e9dee8f3 100644 --- a/Resources/Prototypes/Imperial/Medieval/doors.yml +++ b/Resources/Prototypes/Imperial/Medieval/doors.yml @@ -461,6 +461,73 @@ accessLists: - RebelTavern8 +# Procedural generated doors start +- type: entity + parent: MedievalAirlock + id: MedievalAirlockDungeonA + name: dungeon door + components: + - type: DoorBolt + boltsDown: True + - type: LockDoor + accessLists: + - DungeonA + +- type: entity + parent: MedievalAirlock + id: MedievalAirlockDungeonB + name: dungeon door + components: + - type: DoorBolt + boltsDown: True + - type: LockDoor + accessLists: + - DungeonB + +- type: entity + parent: MedievalAirlock + id: MedievalAirlockDungeonC + name: dungeon door + components: + - type: DoorBolt + boltsDown: True + - type: LockDoor + accessLists: + - DungeonC + +- type: entity + parent: MedievalAirlock + id: MedievalAirlockDungeonD + name: dungeon door + components: + - type: DoorBolt + boltsDown: True + - type: LockDoor + accessLists: + - DungeonD + +- type: entity + parent: MedievalAirlock + id: MedievalAirlockDungeonE + name: dungeon door + components: + - type: DoorBolt + boltsDown: True + - type: LockDoor + accessLists: + - DungeonE + +- type: entity + parent: MedievalAirlock + id: MedievalAirlockDungeonF + name: dungeon door + components: + - type: DoorBolt + boltsDown: True + - type: LockDoor + accessLists: + - DungeonF +# Procedural generated doors end - type: entity id: MedievalAirlock diff --git a/Resources/Prototypes/Imperial/Medieval/keys.yml b/Resources/Prototypes/Imperial/Medieval/keys.yml index 65ee370859..088e2daa52 100644 --- a/Resources/Prototypes/Imperial/Medieval/keys.yml +++ b/Resources/Prototypes/Imperial/Medieval/keys.yml @@ -842,6 +842,104 @@ tags: - LegionnaireKey +# Procedural generated doorkeys start +- type: entity + parent: MedievalKeyBase + id: MedievalKeyDungeonA + name: dungeon key + components: + - type: Sprite + sprite: Imperial/Medieval/Misc/keys.rsi + state: tavern + - type: Key + accesses: + - DungeonA + - type: Tag + tags: + - MedievalKey + - ProceduralDoorKey + +- type: entity + parent: MedievalKeyBase + id: MedievalKeyDungeonB + name: dungeon key + components: + - type: Sprite + sprite: Imperial/Medieval/Misc/keys.rsi + state: tavern + - type: Key + accesses: + - DungeonB + - type: Tag + tags: + - MedievalKey + - ProceduralDoorKey + +- type: entity + parent: MedievalKeyBase + id: MedievalKeyDungeonC + name: dungeon key + components: + - type: Sprite + sprite: Imperial/Medieval/Misc/keys.rsi + state: tavern + - type: Key + accesses: + - DungeonC + - type: Tag + tags: + - MedievalKey + - ProceduralDoorKey + +- type: entity + parent: MedievalKeyBase + id: MedievalKeyDungeonD + name: dungeon key + components: + - type: Sprite + sprite: Imperial/Medieval/Misc/keys.rsi + state: tavern + - type: Key + accesses: + - DungeonD + - type: Tag + tags: + - MedievalKey + - ProceduralDoorKey + +- type: entity + parent: MedievalKeyBase + id: MedievalKeyDungeonE + name: dungeon key + components: + - type: Sprite + sprite: Imperial/Medieval/Misc/keys.rsi + state: tavern + - type: Key + accesses: + - DungeonE + - type: Tag + tags: + - MedievalKey + - ProceduralDoorKey + +- type: entity + parent: MedievalKeyBase + id: MedievalKeyDungeonF + name: dungeon key + components: + - type: Sprite + sprite: Imperial/Medieval/Misc/keys.rsi + state: tavern + - type: Key + accesses: + - DungeonF + - type: Tag + tags: + - MedievalKey + - ProceduralDoorKey +# Procedural generated doorkeys end + - type: playTimeTracker id: Medieval1 diff --git a/Resources/Prototypes/Imperial/Medieval/tags.yml b/Resources/Prototypes/Imperial/Medieval/tags.yml index e4552eb1ea..a4ac8612c6 100644 --- a/Resources/Prototypes/Imperial/Medieval/tags.yml +++ b/Resources/Prototypes/Imperial/Medieval/tags.yml @@ -131,4 +131,13 @@ id: CultConductor - type: Tag - id: CultConductorRod \ No newline at end of file + id: CultConductorRod + +- type: Tag + id: ProceduralDoorKey + +- type: Tag + id: MedievalDungeonRoom + +- type: Tag + id: MedievalChest From de12adfc76e658ccab58dd089883b370c04bd758 Mon Sep 17 00:00:00 2001 From: Aristophan_ Date: Thu, 19 Feb 2026 22:25:08 +0400 Subject: [PATCH 5/6] =?UTF-8?q?=D0=BD=D0=B5=D0=B4=D0=BE=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=B4,=20=D0=BA?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B6=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8E...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MagicDungeon/DungeonPortalSystem.cs | 7 + .../MagicDungeon/DungeonPortalSystem.cs | 18 + .../Procedural/DungeonGenerationParams.cs | 29 - .../Procedural/DungeonGenerationSystem.cs | 111 +-- .../DungeonPortalFrameComponent.cs | 59 ++ .../MagicDungeon/DungeonShardComponent.cs | 13 + .../SharedDungeonPortalSystem.Escape.cs | 37 + .../MagicDungeon/SharedDungeonPortalSystem.cs | 142 ++++ Resources/Maps/Dungeon/govno.yml | 735 ++++++++++++++++++ .../Imperial/Medieval/MagicDungeon/portal.yml | 97 +++ .../Imperial/Medieval/Procedural/rooms.yml | 20 +- .../Prototypes/Imperial/Medieval/keys.yml | 12 +- .../MagicDungeon/frame.rsi/blue_state.png | Bin 0 -> 2276 bytes .../Medieval/MagicDungeon/frame.rsi/frame.png | Bin 0 -> 1884 bytes .../frame.rsi/left_crystal_blue.png | Bin 0 -> 194 bytes .../frame.rsi/left_crystal_green.png | Bin 0 -> 192 bytes .../frame.rsi/left_crystal_pink.png | Bin 0 -> 193 bytes .../frame.rsi/left_crystal_purple.png | Bin 0 -> 206 bytes .../frame.rsi/left_crystal_red.png | Bin 0 -> 186 bytes .../frame.rsi/left_crystal_yellow.png | Bin 0 -> 192 bytes .../Medieval/MagicDungeon/frame.rsi/meta.json | 74 ++ .../MagicDungeon/frame.rsi/red_state.png | Bin 0 -> 2219 bytes .../frame.rsi/right_crystal_blue.png | Bin 0 -> 204 bytes .../frame.rsi/right_crystal_green.png | Bin 0 -> 189 bytes .../frame.rsi/right_crystal_pink.png | Bin 0 -> 204 bytes .../frame.rsi/right_crystal_purple.png | Bin 0 -> 205 bytes .../frame.rsi/right_crystal_red.png | Bin 0 -> 187 bytes .../frame.rsi/right_crystal_yellow.png | Bin 0 -> 174 bytes .../Medieval/MagicDungeon/shard.rsi/meta.json | 29 + .../MagicDungeon/shard.rsi/shard_blue.png | Bin 0 -> 1765 bytes .../MagicDungeon/shard.rsi/shard_green.png | Bin 0 -> 1766 bytes .../MagicDungeon/shard.rsi/shard_pink.png | Bin 0 -> 1780 bytes .../MagicDungeon/shard.rsi/shard_purple.png | Bin 0 -> 1760 bytes .../MagicDungeon/shard.rsi/shard_red.png | Bin 0 -> 1756 bytes .../MagicDungeon/shard.rsi/shard_yellow.png | Bin 0 -> 1751 bytes .../Imperial/Medieval/Misc/keys.rsi/doorA.png | Bin 0 -> 326 bytes .../Imperial/Medieval/Misc/keys.rsi/doorB.png | Bin 0 -> 325 bytes .../Imperial/Medieval/Misc/keys.rsi/doorC.png | Bin 0 -> 326 bytes .../Imperial/Medieval/Misc/keys.rsi/doorD.png | Bin 0 -> 329 bytes .../Imperial/Medieval/Misc/keys.rsi/doorE.png | Bin 0 -> 326 bytes .../Imperial/Medieval/Misc/keys.rsi/doorF.png | Bin 0 -> 328 bytes .../Imperial/Medieval/Misc/keys.rsi/meta.json | 18 + 42 files changed, 1315 insertions(+), 86 deletions(-) create mode 100644 Content.Client/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs create mode 100644 Content.Server/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs delete mode 100644 Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs create mode 100644 Content.Shared/Imperial/Medieval/MagicDungeon/DungeonPortalFrameComponent.cs create mode 100644 Content.Shared/Imperial/Medieval/MagicDungeon/DungeonShardComponent.cs create mode 100644 Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.Escape.cs create mode 100644 Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.cs create mode 100644 Resources/Maps/Dungeon/govno.yml create mode 100644 Resources/Prototypes/Imperial/Medieval/MagicDungeon/portal.yml create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/blue_state.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/frame.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_blue.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_green.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_pink.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_purple.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_red.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_yellow.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/meta.json create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/red_state.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_blue.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_green.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_pink.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_purple.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_red.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_yellow.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/meta.json create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_blue.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_green.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_pink.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_purple.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_red.png create mode 100644 Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_yellow.png create mode 100644 Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorA.png create mode 100644 Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorB.png create mode 100644 Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorC.png create mode 100644 Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorD.png create mode 100644 Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorE.png create mode 100644 Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorF.png diff --git a/Content.Client/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs b/Content.Client/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs new file mode 100644 index 0000000000..73494d6a78 --- /dev/null +++ b/Content.Client/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Imperial.Medieval.MagicDungeon; + +namespace Content.Server.Imperial.Medieval.MagicDungeon; + +public sealed class DungeonPortalSystem : SharedDungeonPortalSystem +{ +} diff --git a/Content.Server/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs b/Content.Server/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs new file mode 100644 index 0000000000..f5bd18d3a5 --- /dev/null +++ b/Content.Server/Imperial/Medieval/MagicDungeon/DungeonPortalSystem.cs @@ -0,0 +1,18 @@ +using Content.Server.Imperial.Medieval.Procedural; +using Content.Shared.Imperial.Medieval.MagicDungeon; + +namespace Content.Server.Imperial.Medieval.MagicDungeon; + +public sealed class DungeonPortalSystem : SharedDungeonPortalSystem +{ + [Dependency] private readonly DungeonGenerationSystem _generationSystem = default!; + + protected override void SpawnPortal(Entity portalFrame) + { + base.SpawnPortal(portalFrame); + + var generationSize = portalFrame.Comp.BaseSize; + _generationSystem.GenerateDungeon(generationSize.X, generationSize.Y, out portalFrame.Comp.DungeonMap, out portalFrame.Comp.CoordForSpawnList); + Dirty(portalFrame); + } +} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs deleted file mode 100644 index dc9baa5d00..0000000000 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationParams.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Content.Server.Imperial.Medieval.Procedural; - -/// -/// Used for setting params for dungeon to generate. -/// -public record struct DungeonGenerationParams -{ - /// - /// Count of rooms, which sets width of the dungeon. - /// - public int Width; - - /// - /// Count of rooms, which sets height of the dungeon. - /// - public int Height; - - /// - /// Roomsize, which can be different for different dungeons. In one dungeon roomsize is similar for - /// - public Vector2i RoomSize; - - public DungeonGenerationParams(int width, int height, Vector2i roomSize) - { - Width = width; - Height = height; - RoomSize = roomSize; - } -} diff --git a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs index 3a30d05d62..53400de996 100644 --- a/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs +++ b/Content.Server/Imperial/Medieval/Procedural/DungeonGenerationSystem.cs @@ -22,7 +22,6 @@ public sealed partial class DungeonGenerationSystem : EntitySystem [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly TileSystem _tileSys = default!; - [Dependency] private readonly IConsoleHost _console = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly TagSystem _tag = default!; @@ -39,59 +38,34 @@ public sealed partial class DungeonGenerationSystem : EntitySystem private readonly EntProtoId[] _wallProtoIds = [ "MedievalStoneBrickWallIndestructable", - "MedievalStoneBrickWallIndestructable", //here I need to replace with other 2 walls, which are not sprited now - "MedievalStoneBrickWallIndestructable" + "WallReinforced", //here I need to replace with other 2 walls, which are not sprited now + "WallReinforcedRust" ]; private readonly Vector2i _size = new Vector2i(15, 15); - public override void Initialize() + public void GenerateDungeon(int width, int height, out MapId mapId, out List toSpawn) { - base.Initialize(); - _console.RegisterCommand("impdungen", "Generate dungeon instantly", "impdungen", GenerateCommand); - } - - private void GenerateCommand(IConsoleShell shell, string argstr, string[] args) - { - if (!GenerateDungeon(5, 5, out var grid)) - { - shell.WriteLine("Failed to generate dungeon."); - } - else - { - shell.WriteLine($"Created dungeon on Map {grid.Value.Owner}"); - } - } - - public bool GenerateDungeon(int width, int height, [NotNullWhen(true)] out Entity? dungeonGrid) - { - dungeonGrid = null; + toSpawn = new(); var random = new Random(); - - // 1. Логика var layout = GenerateLogicalLayout(width, height, random); - - // 2. Инициализация карты var totalPixelWidth = width * (_size.X - 1) + 1; var totalPixelHeight = height * (_size.Y - 1) + 1; var dungeonBox = new Box2i(-totalPixelWidth / 2, -totalPixelHeight / 2, -totalPixelWidth / 2 + totalPixelWidth, -totalPixelHeight / 2 + totalPixelHeight); - if (!PrepareDungeonBase(dungeonBox, random, out dungeonGrid, out _)) - return false; + if (!PrepareDungeonBase(dungeonBox, random, out var dungeonGrid, out mapId)) + return; var gridUid = dungeonGrid.Value.Owner; var gridComp = dungeonGrid.Value.Comp; - // 3. Стены и двери BuildInternalStructure(gridUid, gridComp, layout, dungeonBox.BottomLeft, width, height); - // 4. Декор (спавн мебели, сундуков и т.д.) SpawnRoomDecoration(gridUid, gridComp, dungeonBox.BottomLeft, width, height); - // 5. Поиск сундуков и раскидывание ключей SpawnKeysInExistingChests(gridUid, gridComp, dungeonBox.BottomLeft, layout.SpecialPairs); - return true; + GetPlayerSpawns(gridUid, gridComp, dungeonBox.BottomLeft, layout.StartRooms, out toSpawn); } private bool PrepareDungeonBase(Box2i box, Random random, [NotNullWhen(true)] out Entity? dungeonGrid, out MapId mapId) @@ -209,15 +183,6 @@ private void SpawnRoomDecoration(EntityUid gridUid, MapGridComponent grid, Vecto var roomCenterY = startPos.Y + (y * stepY) + halfY; Spawn(_centerEntityProtoId, _mapSys.ToCoordinates(gridUid, new Vector2i(roomCenterX, roomCenterY), grid)); - - // Пример: Спавним сундук с шансом 70% - // Если не заспавнится, ключ будет лежать на полу. - if (_random.NextFloat() > 0.3f) - { - var chestPos = new Vector2i(roomCenterX + 2, roomCenterY + 2); - var chest = Spawn("MedievalChest", _mapSys.ToCoordinates(gridUid, chestPos, grid)); - _tag.AddTag(chest, _chestTag); - } } } } @@ -236,24 +201,28 @@ private void SpawnKeysInExistingChests(EntityUid gridUid, MapGridComponent grid, var keyProto = $"MedievalKeyDungeon{letter}"; var roomIdx = pair.Room; - // Границы тайлов внутри комнаты (исключая стены) - var roomStartX = startPos.X + (roomIdx.X * stepX) + 1; - var roomStartY = startPos.Y + (roomIdx.Y * stepY) + 1; + var roomStartX = startPos.X + roomIdx.X * stepX + 1; + var roomStartY = startPos.Y + roomIdx.Y * stepY + 1; var roomEndX = roomStartX + stepX - 2; var roomEndY = roomStartY + stepY - 2; EntityUid? targetChest = null; - var validTiles = new List(); // Список всех тайлов комнаты, где можно заспавнить ключ + var validTiles = new List(); // Сканируем комнату for (var x = roomStartX; x <= roomEndX; x++) { for (var y = roomStartY; y <= roomEndY; y++) { - var pos = new Vector2i(x, y); - validTiles.Add(pos); // Запоминаем тайл как потенциальное место спавна - + var posCoords = new Vector2i(startPos.X + roomIdx.X * stepX + x, startPos.Y + roomIdx.Y * stepY + y); + var pos = _mapSys.ToCoordinates(gridUid, posCoords, grid); var entities = _mapSys.GetAnchoredEntities(gridUid, grid, pos); + if (!entities.Any()) + { + validTiles.Add(posCoords); + continue; + } + foreach (var ent in entities) { if (HasComp(ent) && _tag.HasTag(ent, _chestTag)) @@ -276,7 +245,7 @@ private void SpawnKeysInExistingChests(EntityUid gridUid, MapGridComponent grid, else { // Вариант 2: Сундука нет, спавним на случайном тайле - var spawnPos = new Vector2i(startPos.X + (roomIdx.X * stepX) + halfX, startPos.Y + (roomIdx.Y * stepY) + halfY); // По дефолту центр + var spawnPos = new Vector2i(startPos.X + roomIdx.X * stepX + halfX, startPos.Y + roomIdx.Y * stepY + halfY); // По дефолту центр if (validTiles.Count > 0) { @@ -288,6 +257,48 @@ private void SpawnKeysInExistingChests(EntityUid gridUid, MapGridComponent grid, } } + private void GetPlayerSpawns(EntityUid gridUid, MapGridComponent grid, Vector2i startPos, List roomCoords, out List toSpawn) + { + toSpawn = new(); + + var stepX = _size.X - 1; + var stepY = _size.Y - 1; + var halfX = _size.X / 2; + var halfY = _size.Y / 2; + + foreach (var roomIdx in roomCoords) + { + var roomStartX = startPos.X + roomIdx.X * stepX + 1; + var roomStartY = startPos.Y + roomIdx.Y * stepY + 1; + var roomEndX = roomStartX + stepX - 2; + var roomEndY = roomStartY + stepY - 2; + + var validTiles = new List(); + + for (var x = roomStartX; x <= roomEndX; x++) + { + for (var y = roomStartY; y <= roomEndY; y++) + { + var posCoords = new Vector2i(startPos.X + roomIdx.X * stepX + x, startPos.Y + roomIdx.Y * stepY + y); + var pos = _mapSys.ToCoordinates(gridUid, posCoords, grid); + var entities = _mapSys.GetAnchoredEntities(gridUid, grid, pos); + if (!entities.Any()) + { + validTiles.Add(posCoords); + } + } + } + var spawnPos = new Vector2i(startPos.X + roomIdx.X * stepX + halfX, startPos.Y + roomIdx.Y * stepY + halfY); + if (validTiles.Count > 0) + { + spawnPos = _random.Pick(validTiles); + } + + var spawnCoords = _mapSys.ToCoordinates(gridUid, spawnPos, grid); + toSpawn.Add(spawnCoords); + } + } + private void SpawnWall(EntityUid gridUid, MapGridComponent grid, Vector2i pos) { if (_mapSys.GetAnchoredEntities(gridUid, grid, pos).Any()) return; diff --git a/Content.Shared/Imperial/Medieval/MagicDungeon/DungeonPortalFrameComponent.cs b/Content.Shared/Imperial/Medieval/MagicDungeon/DungeonPortalFrameComponent.cs new file mode 100644 index 0000000000..5c6d364467 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/MagicDungeon/DungeonPortalFrameComponent.cs @@ -0,0 +1,59 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared.Imperial.Medieval.MagicDungeon; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class DungeonPortalFrameComponent : Component +{ + [ViewVariables] + public bool IsActive; + + [ViewVariables] + public bool IsOpened; + + [ViewVariables] + public HashSet PlayersInside; + + [ViewVariables, AutoNetworkedField] + public MapId DungeonMap; + + [ViewVariables, AutoNetworkedField] + public List CoordForSpawnList = new(); + + [ViewVariables] + public EntityUid? PortalId; + + [DataField] + public Vector2i BaseSize = (6, 6); + + [DataField("shardSlot1")] + public string ShardSlotId1 = "shard1"; + + [ViewVariables(VVAccess.ReadWrite)] + public ItemSlot ShardSlot1 = default!; + + [DataField("shardSlot2")] + public string ShardSlotId2 = "shard2"; + + [ViewVariables(VVAccess.ReadWrite)] + public ItemSlot ShardSlot2 = default!; +} + +[Serializable, NetSerializable] +public enum DungeonPortalVisualLayers : byte +{ + IsOpened, + LeftPart, + RightPart +} + +[Serializable, NetSerializable] +public enum DungeonPortalVisuals : byte +{ + LeftState, + RightState, + OpenState +} diff --git a/Content.Shared/Imperial/Medieval/MagicDungeon/DungeonShardComponent.cs b/Content.Shared/Imperial/Medieval/MagicDungeon/DungeonShardComponent.cs new file mode 100644 index 0000000000..2ee93fd78b --- /dev/null +++ b/Content.Shared/Imperial/Medieval/MagicDungeon/DungeonShardComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Imperial.Medieval.MagicDungeon; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DungeonShardComponent : Component +{ + [ViewVariables] + public Vector2i SizeModifier = (0, 0); + + [DataField] + public int ColorInt; +} diff --git a/Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.Escape.cs b/Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.Escape.cs new file mode 100644 index 0000000000..8dd894698f --- /dev/null +++ b/Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.Escape.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; + +namespace Content.Shared.Imperial.Medieval.MagicDungeon; + +public abstract partial class SharedDungeonPortalSystem +{ + private void InitEscape() + { + SubscribeLocalEvent(OnMobStateChanged); + } + + private void OnMobStateChanged(EntityUid uid, MobStateComponent mobStateComponent, MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead) + return; + + var map = Transform(uid).MapID; + var query = EntityQueryEnumerator(); + Entity? portalFrame = null; + + while (query.MoveNext(out var portal, out var comp)) + { + if (map == comp.DungeonMap) + { + portalFrame = (portal, comp); + break; + } + } + + if (portalFrame == null || CheckPortalShouldBeDeactivated(portalFrame.Value)) + return; + + DeactivatePortal(portalFrame.Value); + } +} diff --git a/Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.cs b/Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.cs new file mode 100644 index 0000000000..41194f4466 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/MagicDungeon/SharedDungeonPortalSystem.cs @@ -0,0 +1,142 @@ +using System.Linq; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Materials; +using Content.Shared.Mind; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.Imperial.Medieval.MagicDungeon; + +public abstract partial class SharedDungeonPortalSystem : EntitySystem +{ + [Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnShardInserted); + SubscribeLocalEvent(OnShardInsertAttempt); + SubscribeLocalEvent(OnPortalInit); + SubscribeLocalEvent(OnMapInit); + + InitEscape(); + } + + private void OnMapInit(Entity shard, ref MapInitEvent args) + { + var x = _robustRandom.Next(-1, 3); + var y = _robustRandom.Next(-1, 3); + shard.Comp.SizeModifier = (x, y); + Dirty(shard); + } + + private void OnPortalInit(EntityUid uid, DungeonPortalFrameComponent component, ComponentInit args) + { + if (!TryComp(uid, out var itemSlots)) + return; + + if (_itemSlots.TryGetSlot(uid, component.ShardSlotId1, out var slot1, itemSlots)) + component.ShardSlot1 = slot1; + else + _itemSlots.AddItemSlot(uid, component.ShardSlotId1, component.ShardSlot1, itemSlots); + + if (_itemSlots.TryGetSlot(uid, component.ShardSlotId2, out var slot2, itemSlots)) + component.ShardSlot2 = slot2; + else + _itemSlots.AddItemSlot(uid, component.ShardSlotId2, component.ShardSlot2, itemSlots); + } + + private void OnShardInsertAttempt(Entity portalFrame, ref ContainerIsInsertingAttemptEvent args) + { + if (portalFrame.Comp.IsActive) + return; + + args.Cancel(); + } + + private void OnShardInserted(Entity portalFrame, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != portalFrame.Comp.ShardSlotId1 && args.Container.ID != portalFrame.Comp.ShardSlotId2) + return; + + if (!TryComp(portalFrame, out var itemSlots)) + return; + + if (_itemSlots.TryGetSlot(portalFrame, args.Container.ID, out var shardSlot, itemSlots) || shardSlot == null) + return; + + var shard = args.Entity; + var shardComp = EnsureComp(shard); + portalFrame.Comp.BaseSize += shardComp.SizeModifier; + Dirty(portalFrame); + + _itemSlots.SetLock(portalFrame, shardSlot, true, itemSlots); + ChangeShardHolesAppearance(portalFrame); + if (!_itemSlots.CanInsert(portalFrame, shard, null, portalFrame.Comp.ShardSlot1) && !_itemSlots.CanInsert(portalFrame, shard, null, portalFrame.Comp.ShardSlot2)) + { + OpenPortal(portalFrame); + } + } + + private void OpenPortal(Entity portalFrame) + { + SpawnPortal(portalFrame); + portalFrame.Comp.IsOpened = true; + ChangePortalAppearance(portalFrame); + Dirty(portalFrame); + } + + private void ClosePortal(Entity portalFrame) + { + portalFrame.Comp.IsOpened = false; + ChangePortalAppearance(portalFrame); + } + + private void DeactivatePortal(Entity portalFrame) + { + portalFrame.Comp.IsActive = false; + portalFrame.Comp.IsOpened = false; + ChangePortalAppearance(portalFrame); + } + + protected virtual void SpawnPortal(Entity portalFrame) + { + portalFrame.Comp.IsActive = true; + } + + private bool CheckPortalShouldBeDeactivated(Entity portalFrame) + { + if (_mindSystem.GetAliveHumans().Any(c => Transform(c).MapID == portalFrame.Comp.DungeonMap)) + return false; + return true; + } + + private void ChangeShardHolesAppearance(Entity portalFrame) + { + if (portalFrame.Comp.ShardSlot1.Item is { } shard1) + { + var shardComp = EnsureComp(shard1); + _appearanceSystem.SetData(portalFrame, DungeonPortalVisuals.LeftState, shardComp.ColorInt); + } + if (portalFrame.Comp.ShardSlot1.Item is { } shard2) + { + var shardComp = EnsureComp(shard2); + _appearanceSystem.SetData(portalFrame, DungeonPortalVisuals.RightState, shardComp.ColorInt); + } + } + + private void ChangePortalAppearance(Entity portalFrame) + { + int value = !portalFrame.Comp.IsActive ? 0 + : !portalFrame.Comp.IsOpened ? 1 + : 2; + _appearanceSystem.SetData(portalFrame, DungeonPortalVisuals.OpenState, value); + } +} diff --git a/Resources/Maps/Dungeon/govno.yml b/Resources/Maps/Dungeon/govno.yml new file mode 100644 index 0000000000..be511b4020 --- /dev/null +++ b/Resources/Maps/Dungeon/govno.yml @@ -0,0 +1,735 @@ +meta: + format: 7 + category: Map + engineVersion: 267.1.0 + forkId: "" + forkVersion: "" + time: 02/07/2026 10:13:21 + entityCount: 127 +maps: +- 1 +grids: +- 1 +orphans: [] +nullspace: [] +tilemap: + 0: Space + 22: FloorCave + 23: FloorCaveDrought + 40: FloorDirt + 4: FloorGrayConcrete + 3: FloorGrayConcreteMono + 66: FloorMining + 68: FloorMiningLight + 71: FloorOldConcreteMono + 72: FloorOldConcreteSmooth + 1: FloorPlanetDirt + 82: FloorShuttleOrange + 2: FloorShuttlePurple + 118: FloorWood + 5: MedievalFloorStone1 + 7: MedievalFloorStone3 + 9: MedievalFloorStone5 + 6: MedievalFloorStone6 + 8: MedievalFloorStone9 + 121: Plating + 124: PlatingDamaged +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + - type: Transform + - type: Map + mapPaused: True + - type: GridTree + - type: Broadphase + - type: OccluderTree + - type: MapGrid + chunks: + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAA== + version: 7 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + version: 7 + 3,0: + ind: 3,0 + tiles: AgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + version: 7 + 3,-1: + ind: 3,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + version: 7 + 2,-1: + ind: 2,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAA== + version: 7 + 2,0: + ind: 2,0 + tiles: AgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + version: 7 + 1,0: + ind: 1,0 + tiles: AQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAAIAAAAAAAACAAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAQAAAAAAAAEAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAAIAAAAAAAAAQAAAAAAAAgAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAIAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + version: 7 + 1,-1: + ind: 1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAA== + version: 7 + 0,0: + ind: 0,0 + tiles: AQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAIAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAACAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAQAAAAAAAAgAAAAAAAAIAAAAAAAAAgAAAAAAAAEAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAIAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAACAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAgAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAIAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAAIAAAAAAAAAQAAAAAAAAgAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAACAAAAAAAACAAAAAAAAAEAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAEAAAAAAAAIAAAAAAAAAgAAAAAAAAgAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAACAAAAAAAAAIAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAABAAAAAAAAAQAAAAAAAAgAAAAAAAACAAAAAAAACAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAQAAAAAAAAEAAAAAAAAIAAAAAAAAAgAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAIAAAAAAAABAAAAAAAAAQAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + version: 7 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAACAAAAAAAAAgAAAAAAAA== + version: 7 + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: [] + - type: SpreaderGrid + - type: GridPathfinding + - type: RadiationGridResistance +- proto: MedievalChest4 + entities: + - uid: 38 + components: + - type: Transform + pos: 2.5,11.5 + parent: 1 + - uid: 88 + components: + - type: Transform + pos: 16.5,11.5 + parent: 1 + - uid: 89 + components: + - type: Transform + pos: 21.5,6.5 + parent: 1 + - uid: 90 + components: + - type: Transform + pos: 16.5,6.5 + parent: 1 + - uid: 98 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 117 + components: + - type: Transform + pos: 9.5,9.5 + parent: 1 + - uid: 120 + components: + - type: Transform + pos: 10.5,3.5 + parent: 1 + - uid: 127 + components: + - type: Transform + pos: 23.5,10.5 + parent: 1 +- proto: MedievalStoneBrickWallIndestructable + entities: + - uid: 2 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 3 + components: + - type: Transform + pos: 5.5,1.5 + parent: 1 + - uid: 4 + components: + - type: Transform + pos: 3.5,3.5 + parent: 1 + - uid: 5 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 + - uid: 6 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 + - uid: 7 + components: + - type: Transform + pos: 3.5,2.5 + parent: 1 + - uid: 8 + components: + - type: Transform + pos: 5.5,9.5 + parent: 1 + - uid: 9 + components: + - type: Transform + pos: 1.5,10.5 + parent: 1 + - uid: 10 + components: + - type: Transform + pos: 1.5,9.5 + parent: 1 + - uid: 11 + components: + - type: Transform + pos: 6.5,7.5 + parent: 1 + - uid: 12 + components: + - type: Transform + pos: 7.5,7.5 + parent: 1 + - uid: 13 + components: + - type: Transform + pos: 8.5,7.5 + parent: 1 + - uid: 14 + components: + - type: Transform + pos: 8.5,8.5 + parent: 1 + - uid: 15 + components: + - type: Transform + pos: 4.5,11.5 + parent: 1 + - uid: 16 + components: + - type: Transform + pos: 5.5,10.5 + parent: 1 + - uid: 17 + components: + - type: Transform + pos: 3.5,11.5 + parent: 1 + - uid: 18 + components: + - type: Transform + pos: 2.5,10.5 + parent: 1 + - uid: 19 + components: + - type: Transform + pos: 8.5,10.5 + parent: 1 + - uid: 20 + components: + - type: Transform + pos: 8.5,11.5 + parent: 1 + - uid: 21 + components: + - type: Transform + pos: 8.5,9.5 + parent: 1 + - uid: 22 + components: + - type: Transform + pos: 2.5,7.5 + parent: 1 + - uid: 23 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 + - uid: 24 + components: + - type: Transform + pos: 3.5,8.5 + parent: 1 + - uid: 25 + components: + - type: Transform + pos: 3.5,7.5 + parent: 1 + - uid: 26 + components: + - type: Transform + pos: 1.5,4.5 + parent: 1 + - uid: 27 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: 0.5,7.5 + parent: 1 + - uid: 29 + components: + - type: Transform + pos: 3.5,9.5 + parent: 1 + - uid: 30 + components: + - type: Transform + pos: 7.5,1.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: 15.5,5.5 + parent: 1 + - uid: 32 + components: + - type: Transform + pos: 8.5,5.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: 7.5,3.5 + parent: 1 + - uid: 34 + components: + - type: Transform + pos: 7.5,5.5 + parent: 1 + - uid: 35 + components: + - type: Transform + pos: 9.5,2.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: 9.5,3.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: 7.5,4.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: 11.5,4.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: 10.5,1.5 + parent: 1 + - uid: 41 + components: + - type: Transform + pos: 11.5,3.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: 11.5,2.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: 7.5,2.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: 10.5,5.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: 7.5,0.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: 17.5,1.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: 21.5,0.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: 22.5,2.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: 25.5,3.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: 25.5,1.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: 25.5,2.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: 18.5,1.5 + parent: 1 + - uid: 53 + components: + - type: Transform + pos: 15.5,1.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: 19.5,1.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: 17.5,5.5 + parent: 1 + - uid: 56 + components: + - type: Transform + pos: 17.5,3.5 + parent: 1 + - uid: 57 + components: + - type: Transform + pos: 17.5,4.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: 15.5,7.5 + parent: 1 + - uid: 59 + components: + - type: Transform + pos: 25.5,4.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: 25.5,5.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: 19.5,0.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: 21.5,2.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: 23.5,3.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: 21.5,1.5 + parent: 1 + - uid: 65 + components: + - type: Transform + pos: 23.5,2.5 + parent: 1 + - uid: 66 + components: + - type: Transform + pos: 25.5,6.5 + parent: 1 + - uid: 67 + components: + - type: Transform + pos: 16.5,1.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: 16.5,7.5 + parent: 1 + - uid: 69 + components: + - type: Transform + pos: 17.5,7.5 + parent: 1 + - uid: 70 + components: + - type: Transform + pos: 22.5,6.5 + parent: 1 + - uid: 71 + components: + - type: Transform + pos: 22.5,11.5 + parent: 1 + - uid: 72 + components: + - type: Transform + pos: 21.5,11.5 + parent: 1 + - uid: 73 + components: + - type: Transform + pos: 19.5,11.5 + parent: 1 + - uid: 74 + components: + - type: Transform + pos: 20.5,11.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: 22.5,5.5 + parent: 1 + - uid: 76 + components: + - type: Transform + pos: 20.5,6.5 + parent: 1 + - uid: 77 + components: + - type: Transform + pos: 20.5,7.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: 21.5,5.5 + parent: 1 + - uid: 79 + components: + - type: Transform + pos: 20.5,5.5 + parent: 1 + - uid: 80 + components: + - type: Transform + pos: 16.5,10.5 + parent: 1 + - uid: 81 + components: + - type: Transform + pos: 15.5,11.5 + parent: 1 + - uid: 82 + components: + - type: Transform + pos: 17.5,11.5 + parent: 1 + - uid: 83 + components: + - type: Transform + pos: 15.5,10.5 + parent: 1 + - uid: 84 + components: + - type: Transform + pos: 25.5,9.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: 25.5,8.5 + parent: 1 + - uid: 86 + components: + - type: Transform + pos: 24.5,7.5 + parent: 1 + - uid: 87 + components: + - type: Transform + pos: 22.5,7.5 + parent: 1 + - uid: 91 + components: + - type: Transform + pos: 9.5,5.5 + parent: 1 + - uid: 92 + components: + - type: Transform + pos: 9.5,11.5 + parent: 1 + - uid: 93 + components: + - type: Transform + pos: 10.5,11.5 + parent: 1 + - uid: 94 + components: + - type: Transform + pos: 22.5,9.5 + parent: 1 + - uid: 95 + components: + - type: Transform + pos: 22.5,10.5 + parent: 1 + - uid: 96 + components: + - type: Transform + pos: 23.5,9.5 + parent: 1 + - uid: 97 + components: + - type: Transform + pos: 11.5,5.5 + parent: 1 + - uid: 99 + components: + - type: Transform + pos: 15.5,6.5 + parent: 1 + - uid: 100 + components: + - type: Transform + pos: 10.5,2.5 + parent: 1 + - uid: 101 + components: + - type: Transform + pos: 25.5,7.5 + parent: 1 + - uid: 102 + components: + - type: Transform + pos: 17.5,2.5 + parent: 1 + - uid: 103 + components: + - type: Transform + pos: 17.5,10.5 + parent: 1 + - uid: 104 + components: + - type: Transform + pos: 23.5,7.5 + parent: 1 + - uid: 105 + components: + - type: Transform + pos: 18.5,11.5 + parent: 1 + - uid: 106 + components: + - type: Transform + pos: 24.5,9.5 + parent: 1 + - uid: 107 + components: + - type: Transform + pos: 11.5,8.5 + parent: 1 + - uid: 108 + components: + - type: Transform + pos: 9.5,8.5 + parent: 1 + - uid: 109 + components: + - type: Transform + pos: 12.5,11.5 + parent: 1 + - uid: 110 + components: + - type: Transform + pos: 11.5,11.5 + parent: 1 + - uid: 111 + components: + - type: Transform + pos: 2.5,4.5 + parent: 1 + - uid: 112 + components: + - type: Transform + pos: 7.5,11.5 + parent: 1 + - uid: 113 + components: + - type: Transform + pos: 5.5,7.5 + parent: 1 + - uid: 114 + components: + - type: Transform + pos: 1.5,7.5 + parent: 1 + - uid: 115 + components: + - type: Transform + pos: 11.5,9.5 + parent: 1 + - uid: 116 + components: + - type: Transform + pos: 3.5,10.5 + parent: 1 + - uid: 118 + components: + - type: Transform + pos: 15.5,2.5 + parent: 1 + - uid: 119 + components: + - type: Transform + pos: 22.5,12.5 + parent: 1 + - uid: 121 + components: + - type: Transform + pos: 14.5,5.5 + parent: 1 + - uid: 122 + components: + - type: Transform + pos: 5.5,11.5 + parent: 1 + - uid: 123 + components: + - type: Transform + pos: 10.5,8.5 + parent: 1 + - uid: 124 + components: + - type: Transform + pos: 4.5,1.5 + parent: 1 + - uid: 125 + components: + - type: Transform + pos: 17.5,6.5 + parent: 1 + - uid: 126 + components: + - type: Transform + pos: 20.5,10.5 + parent: 1 +... diff --git a/Resources/Prototypes/Imperial/Medieval/MagicDungeon/portal.yml b/Resources/Prototypes/Imperial/Medieval/MagicDungeon/portal.yml new file mode 100644 index 0000000000..6c12b8a7e7 --- /dev/null +++ b/Resources/Prototypes/Imperial/Medieval/MagicDungeon/portal.yml @@ -0,0 +1,97 @@ +- type: entity + id: MagicPortalFrame + parent: BaseStructure + name: magic portal + description: This portal can send you somewhere. But what's inside? + components: + - type: Sprite + sprite: Imperial/Medieval/MagicDungeon/frame.rsi + layers: + - state: frame + - map: ["enum.DungeonPortalVisualLayers.IsOpened"] + visible: false + shader: unshaded + - visible: false + map: ["enum.DungeonPortalVisualLayers.LeftPart"] + shader: unshaded + - visible: false + map: ["enum.DungeonPortalVisualLayers.RightPart"] + shader: unshaded + - type: GenericVisualizer + visuals: + enum.DungeonPortalVisuals.OpenState: + enum.DungeonPortalVisualLayers.IsOpened: + 0: { visible: false } + 1: { visible: true, state: red_state } + 2: { visible: true, state: blue_state } + enum.DungeonPortalVisuals.LeftState: + enum.DungeonPortalVisualLayers.LeftPart: + 0: { visible: false } + 1: { visible: true, state: left_crystal_blue } + 2: { visible: true, state: left_crystal_green } + 3: { visible: true, state: left_crystal_pink } + 4: { visible: true, state: left_crystal_purple } + 5: { visible: true, state: left_crystal_red } + 6: { visible: true, state: left_crystal_yellow } + enum.DungeonPortalVisuals.RightState: + enum.DungeonPortalVisualLayers.RightPart: + 0: { visible: false } + 1: { visible: true, state: right_crystal_blue } + 2: { visible: true, state: right_crystal_green } + 3: { visible: true, state: right_crystal_pink } + 4: { visible: true, state: right_crystal_purple } + 5: { visible: true, state: right_crystal_red } + 6: { visible: true, state: right_crystal_yellow } + - type: Physics + bodyType: Static + - type: Transform + noRot: true + - type: Fixtures + fixtures: + portalFixture: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.48,0.25,0.48" + mask: + - FullTileMask + layer: + - WallLayer + hard: false + - type: Appearance + - type: ItemSlots + slots: + shard1: + name: ShardSlot1 + insertSound: + path: /Audio/Machines/terminal_insert_disc.ogg + ejectSound: + path: /Audio/Machines/terminal_insert_disc.ogg + whitelist: + components: + - DungeonShard + shard2: + name: ShardSlot2 + insertSound: + path: /Audio/Machines/terminal_insert_disc.ogg + ejectSound: + path: /Audio/Machines/terminal_insert_disc.ogg + whitelist: + components: + - DungeonShard + - type: ContainerContainer + containers: + shard1: !type:ContainerSlot {} + shard2: !type:ContainerSlot {} + +- type: entity + parent: BaseItem + id: BaseShard + abstract: true + name: base shard + description: This shiny shard of magic looks so beatiful... + components: + - type: Item + sprite: Imperial/Medieval/MagicDungeon/shard.rsi + size: Tiny + - type: Sprite + sprite: Imperial/Medieval/MagicDungeon/shard.rsi diff --git a/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml b/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml index 7c9fd09f24..093da779b6 100644 --- a/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml +++ b/Resources/Prototypes/Imperial/Medieval/Procedural/rooms.yml @@ -4,9 +4,27 @@ name: marker components: - type: RoomFill - size: 13,13 + minSize: 13,13 + maxSize: 13,13 roomWhitelist: tags: - MedievalDungeonRoom # add room prototypes here under this comment and add MedievalDungeonRoom tag to them +- type: dungeonRoom + id: MedievalDungeon13x13a + ignoreTile: FloorShuttlePurple + size: 13,13 + atlas: /Maps/Dungeon/govno.yml + offset: 0,0 + tags: + - MedievalDungeonRoom + +- type: dungeonRoom + id: MedievalDungeon13x13b + ignoreTile: FloorShuttlePurple + size: 13,13 + atlas: /Maps/Dungeon/govno.yml + offset: 14,0 + tags: + - MedievalDungeonRoom diff --git a/Resources/Prototypes/Imperial/Medieval/keys.yml b/Resources/Prototypes/Imperial/Medieval/keys.yml index 088e2daa52..870ab6a17a 100644 --- a/Resources/Prototypes/Imperial/Medieval/keys.yml +++ b/Resources/Prototypes/Imperial/Medieval/keys.yml @@ -850,7 +850,7 @@ components: - type: Sprite sprite: Imperial/Medieval/Misc/keys.rsi - state: tavern + state: doorA - type: Key accesses: - DungeonA @@ -866,7 +866,7 @@ components: - type: Sprite sprite: Imperial/Medieval/Misc/keys.rsi - state: tavern + state: doorB - type: Key accesses: - DungeonB @@ -882,7 +882,7 @@ components: - type: Sprite sprite: Imperial/Medieval/Misc/keys.rsi - state: tavern + state: doorC - type: Key accesses: - DungeonC @@ -898,7 +898,7 @@ components: - type: Sprite sprite: Imperial/Medieval/Misc/keys.rsi - state: tavern + state: doorD - type: Key accesses: - DungeonD @@ -914,7 +914,7 @@ components: - type: Sprite sprite: Imperial/Medieval/Misc/keys.rsi - state: tavern + state: doorE - type: Key accesses: - DungeonE @@ -930,7 +930,7 @@ components: - type: Sprite sprite: Imperial/Medieval/Misc/keys.rsi - state: tavern + state: doorF - type: Key accesses: - DungeonF diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/blue_state.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/blue_state.png new file mode 100644 index 0000000000000000000000000000000000000000..7dfbe9564cefa58ce25be522c02bbb8bf7241a28 GIT binary patch literal 2276 zcmYjSc~lZu7YE0MG8HwKaLrsY6U#mK1*aTKi#9b}8u!%HTyO(X%hYLcM@zvhb@~j< z%w5F7G@~$MCR_uz(jg=$QWSCM`~LWP=e&FGdFS2xe($~W``x0GzDE^yLUsZG00r-3 zFu24IN+t#*E3rXWBV#2Fm<&Ja0U*+}R{#JI&Ku@_CcTz3d)QCQd$-&dxg$HkCSW)O zxnE&KZKbxr;5k1i_bL2R%B1mG7ofR0UjHoQF=C%rgZ+hivzZd8m%Ux7qn&lVzv7Fw z^Q=!!u47ZO+}Eo#agGXn7mdhV&9Z7|PX-xC&iyeSHzQ(Uu~~A;ix_$jhq&2|4m=yCe6A{)&Y^Fez!ny&Gkl%~1nxcH zw)f=_xi4B-dHmff?F*ME|7_(|lacG!4v?hKjqv$c1ZY4VXtF))MV$a7?(IG(6=ha7 zO70GZ+}#`}+8tG)bQx4viC!}d3VqO3=alUOMxQQ54XEe(ienTo30{ZHg#i$+P?Su0 z8Pi(!8vzu~_d9?d=54~I+l^h5gQRIqo+CzN@tWk05==ywD%E~^DxVqj^w;09+?uNW z8J4kpy8fzfmuqKGTw7q!%m#H4o15B;GH96F16CpBe%`^Kl0SZFOQ2{g$jji$gtNCf z%mi40I&Q@vgEuX2Lc)~mx|!rOPwN?z(QmlCC8-^!*<+3u#LK}YPAf9@=TsE#`Jy4# zfG2CnzvcbS0FxU&mOPs+J2AN(=kiA7@|H~v7(S!OP1y_!xwDk(#B9|9>Tay)6`P{( zVyS^#VEEheGW;m)OQQzM1J|h#{&_%0ZZzJlfng^QL&N+8cUTPw`OOMV)CBYiE5a$s zS$|=zS5x+gDd04<*OMfr8lq7!=7KQ!}jF0XY8Et;@t;m zAFdM~njirBh(S|9P@}HXBbA;iSpFG=?LuX|H^~&71ZX|`{-?FkDG;No1D~8d2 z9dgl(yJ4f;`^`hkl(fG;gOe%C&BtZ$ z*LI5EQIXe`{iLVS+i8mlO)D%I?7W_Dp{(k2{=|mQQsBPK*KsG|svrAn6yN+kw&;sb zamFYeCjpA?VHPES*sW1*)YLuM(8(%3B$w#rpW6xw>o(S8NS{8{x+AjNOnP;vyBEn! znu5wr4AB=O=T_wn=V*!DR<+r6@9X9U%PuvRO`JXM4W(B04<&G{ql0ZR=4LG|HHa#C zcQ6b~T^g!F_8^+{L5~(Om6uI+QI@~;!dlVUO&^6q1pCi(2gaAK)O>K_zT+i*zwUi; z{m@fK659R~Dl5&J5%qE8`ap&_vA;wR4=%WvBKSnnKyxy!O4o%q=DsscibnA1N`s@@ z8F#0WLEsHdSo#qNG zUENC-I@QR8h@T|n|6=k}WX6D$_S-!`bNU)a@J8eZZE#f9?MKX2itJYHV)ylepSA)9 z=`7PyQ`1aQz?|rA=m*|3mw8JhF5cfDW#}=JTqax_7{S}n@VhNkncwOcTa-*b7z0g& z^O-Sm|9JL&&~~w(W~{froGhps9V=>0+Pm&AEg{oA&`k1^(o{b3zdWY}BXZIoEJwz? zpc+&g3HKBanzOYgDn$MH9WMlBNG~u})T>cJaoe5i_#U^r^@I-^@(omX*)^@O;(XF5 zbLIPW(!CoQv1EtOYR{l8GWANCCPw5HX9?0_oXu!{2Pyw$g~*cUy*uwAD9$N)5Bka# z$eCN4F$L}Vb(itB7J>UHdHt3~IodiBnd&Ifknag6$Q8zJEhtJyba6c`9(N+a&c?!; zhbC?op|=HL0MbG#e7WvTwu5^5Z`uPxxKx^Ao_Fo4p^bW#<1{wk6&jf_*s=}HF+_#l zU|-`T!2wPWO1pe(Dn zT#A+CjDJV>(l5s|7pQHRqLsgleUVL+m6Yfs#E_}kdnYJyCqstC+3hWo%-+7}@yM8n z@MSyGxt^rAH>C{6GL`BtWc65K(~!d-2M^&3V17ju>_j^)5SX2yV}U@~3wsQBM(1b1 zZexm#+J1$7&W?d^UtD(hKAtky#XiyN?D#53lra|$8lu!C*eSJg6M7B*@cjBvfL+^K zWVrl-#fB^bHhs?GTPocp+O)=(0udxxA14AH!=Ys z)x{XtJpC#gI0!{3e*_RgR479AGl;l@W;~Z>T5n0m&nGCCr)QY_>mGr#uxjEqd`m?f885#e1>zb&@Qx`UHMu!@!VlhG1-N?Q9V zuyj$^Ia_SqReeyy6~aYl@fAYpWPHidBE{Sdl5RZbyri;Gk3XC0E0qJC=226DKXdD+ ac=!1Buy$l0JUnFK$EvrNFO29Bn)ffe@>LH2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/frame.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/frame.png new file mode 100644 index 0000000000000000000000000000000000000000..2adab06467fa1e97eab8b833561cb0ad00df915d GIT binary patch literal 1884 zcmV-i2c!6jP)Px+7D+@wRCt{2TRn5)NDytV?hlCQP_#u6S!CeInF~VSMVI=2UF!6r+YLoI+h`#} zY{F7Rk|XpJbb18lJ~voiVjDC)-EVrjds<_W8-8tce#WGO{#{{xzTT{^uFvn+ zo^a5=E23c}yc7UT#W#%de9A=&jj5-2J-&gwTQS10fYWs5p)B|ce#XN zC~2BXvP>izMj|4TEE5U3frMcw`7W=BxCRMF6X4p%7WAs;yIe#>(lo7o7KUMM47{NY zjwS1$L2NWuOz&m+*w2-LVHirXOlspqq#Dc5gPdp>)yDJldM+L(2a?h1K^Q)nPDMmy zv6xp%K}l9X41%&Otw_N4qtUpM$KyFjS_TSv73%?cvp8-|g!cg$4u!B%YzUwyv$hCU zl~Az$n6lIbM?f?)hPqcZ9NDLtS`_MqQB zD;%P`GYeoaue@&sd4e{|t~aYrG>ls1{QL1kp<_TBZ^D?M8;ES|XG{VH{ksDBSPRmT zUk5kfuRs4T{`>r|*RZDVfhs*JBXmWn119V5T9n^hld}7W$9T@=Lb2l#(5s%NX%(2I z41Qr4*6I#kNDEE^Sb#Mlv#ps$@HrRKG_4g$Qf`}kmzz3Sy1U>cfSJT(>;Yigg*KD7 zg-@2H6E6xEUKX4LSXGx^Rm=UYlioSd>xB{xBXb-0{uYNVfOiWpb6}L+O^gVB3FBR| z+vwz5een^X_tT(%S6J?5rK4FKpS5OKZ&n?Yw~RJA=|HLBtOF2uc>n2iXQjVuqe-Wx zfb1jy%(6ghf8%?kKOfttBFoafUV%?1p>5*|q^*PCdb8?S0Y|&~In$qpmfcn-UtDTz zh?MoZv+UFrmha$k3f7xdXCU<=%T*5ZDYgmTERbbsjlD1oYc8%MY*WNN2Yhp8S-oK5 zBfJZZS%3v-chjzQ$rRXjPS(+~LBksZMx${h5N_RM&>bYbc5p3+dxP8Q)?4%A*9iibLGNb zV9ECBq`T7=yr)4PXysfz@wtz|8rHGc3WDq*))(J|kHC?kTm028n_3`!urF3gwd28*rOKt%jms~pKyS(k9O@x$4Qq5JVeVeMs~POQ5R&NQgo;w9TPqjW&}HvihmcX=_HPS2hVdcDwWv{-cj z4o+-~3ym*EK*B)mejh8qZ+oy3r=ARXm2om##p|16Wyq!WxksSW@MO=oXjeJT5bS(YA60*pr_2e>or(}|5gx+^T|{Q&m^^x0dC z^LMgLPO263Yd77W@afdF@_?5BtIXsMEZmQes{E!Gb-^)dG3W->y8tQO9b~XqxpZ>b zps^POwJTdfHDEliHnIiC58yZhFPKcHVztT{Y`hgfO#<{lxRt)d zB&GWyNNQy#kB`N6`&N;MvpI<1;)?S&pW!if^7vTHUSB($ilL)RCu|M&4(l9Y5`f7O zKu}93%fu9-jemZAE?!<tdN>54J4*VoR|(^K*C@?w(U(#cxL z)*iqlU|=+is@q)a%}Un)tmJZZI_aF!9_j~d~ zL`0YbMES*HF5l)~-<6KvWk$nDl4VkP+repYv6xr;hXWgo2darh1gj+Pdv?!2tO5d9 zG>jw+-)oI%7zqlVUN5W>0vV85dRJlTu->d{6(8H+Iue^6z{LX}FD+oNma8QqBHQg- z?I6X0l06Hq%H7^9ORE)RPuXtYD*5mT7xeMn_j+tECGqt*QTKT1MhKoY6>!zw`sxaU zZ-%N9)Sl$~xA!>I&)?OP WXIjP+2r6y>0000Gye+% zwZeeMq&MfMN4<*hmVJFER_(j(zl!~T|1wTn+qh49)4jY;I{jbnewZC59JPflZ1>^k juh+j`X~_@N`?;F=lZ?{CHR1AGK_+>+`njxgN@xNA$GA$0 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_green.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_green.png new file mode 100644 index 0000000000000000000000000000000000000000..90ebab5317d0e86d9c72db78bea2e1c50cac5882 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|sytmBLn`LH zy||F~P=E;Q1#hPhJ`Kt2$6ktgs2D$Zt!?EbT$UlaNUHy*MbqCVGnMB*X5aZ8#{|>~ z1D=zX<(~?9W;Au@n!Blm_aEy&e*2z(!&=@F=h=rpPB~uTZ@y)J=GFzixe4|6YWMxy gvv1XBpw1y>o(s7r%ZM^Q|vEWvSP#ru$eb*8ceGk3Fd| u+qHjBc7C@&XV&}M_wP@CV+%Cr&3k5(o$~50UhG-`GS$=7&t;ucLK6VXuS|IW literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_red.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_red.png new file mode 100644 index 0000000000000000000000000000000000000000..4ba1bb568c65469c35b9c152e131783455742acd GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|N<3X0Ln`LH zy||I{u!8{00pl6@ZWFnKe}n#O+i8(I?8;&1Hj25N$VhK`O3 z9_O^RGq*mw8O!(Gwodx^e)|Jk%`aRN%ANOkr_RM|pJs=#@A|^_?&-ImzrNI!K4)ZL bcu-?4d_m!C=nm5*Aaguj{an^LB{Ts5J|RR| literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_yellow.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/left_crystal_yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..d72ebf7f84cc4d830b729520a3b5a01ce452e9ba GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|sytmBLn`LH zy||E<$x(#mf}-M<#%3;!4!sE;cOs>B@M%1hS;+8CBzH}u%)xaJ^i|9&&CiDq`bo?d<Ep)z4*}Q$iB}Vdq2h literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/meta.json b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/meta.json new file mode 100644 index 0000000000..4bc74a7e08 --- /dev/null +++ b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/meta.json @@ -0,0 +1,74 @@ +{ + "version": 1, + "license": "this content is under ICLA licence, read more on https://wiki.imperialspace.net/icla", + "copyright": "sprited by @gag08984", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "blue_state", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "frame" + }, + { + "name": "left_crystal_blue" + }, + { + "name": "left_crystal_green" + }, + { + "name": "left_crystal_pink" + }, + { + "name": "left_crystal_purple" + }, + { + "name": "left_crystal_red" + }, + { + "name": "left_crystal_yellow" + }, + { + "name": "red_state", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "right_crystal_blue" + }, + { + "name": "right_crystal_green" + }, + { + "name": "right_crystal_pink" + }, + { + "name": "right_crystal_purple" + }, + { + "name": "right_crystal_red" + }, + { + "name": "right_crystal_yellow" + } + ] + } diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/red_state.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/red_state.png new file mode 100644 index 0000000000000000000000000000000000000000..3aec8efbbd6d66bdef7346a104d1db9b006a2450 GIT binary patch literal 2219 zcmZvd2{0Sl8po4xuar`0DXPTIrDADmYF9}sii9YweW|L4+G?pSsa?ExYix0ASEaP} zS}P4zOX#&lDOJ=`)z}hSLh$0gx$|b;yl>`w-#K&U`_7s9&i{8(FqSv@c`xt+004fJ zDZ-i)J2(Rm{*~jM;_v%#0x;P6=5;{z;KdaH0PKlE7~6#Bt$ii?ESL)MY!VwfxIN6R z;}b3i2BrlaKhcG&-`D-b1G8D3txGsc7vj_KC4Gf}_;PV`X2yj0i}5NT1@O6HO}h_6 z2UjF1tYR_L^9`QUbya;aqmh?o?;0j^PCT)Wg3<}>))9if^%z%@?u^blCK`S}q}Z|< ztsC1rRm*XEZO#BxO>-6?nXF6a`cKk{$BRdXL~lY5U4jjxqHyfyrVZ;P=BBy`E($kW z!Q6qqvy5TCl#f}4Rw}+5+`B&a0X-`H6>|fD=oK2K%zayl9^x}rIC8%tBU8c(YJEH# zA`L&Rb)pPKsm3xlF_aGzfznKS?mTwtNZ)5bfON|xkQY=g<2VOIX!tUH5U4&VpXiUQ zRbl;6gT!RAUsuZnFr?q67qaZRBN!Y107R+8a*F*_0@0wDY<8JOkv~!@gZ!F#m0Gi~ z_X1py%l;SwVY>%^x3*%?aJ`eN64<$)KG!-`oX|(z4>D7oEE`m;rWRW%Xj)6gqn1WT z-#UB1^!~siQAHn>lv&zUHQ@=-3+rl4Y1t{?(M%&;09^A+7ibP8Hl<82$R>p~bP_BZ z-cEaH-|?HzPH;ca11jp{&0|K-y5L}`9Xw^@g$@YTaHKJ#RAlZB>Pmc2-@x|)&+2c% zpZM;m04DcuY0^XXBmLHYxkpo@#HSH#!yO`lSX#|f&~{Q@&uukV5>J{jQQj~kW$u<* zM?$-gSl{R9F9PWUctnl=&qJr4bzj-fa)S>^D28TD%1F|Z44-okc8p82V7yzs4+YGz zH)u85&RPpn!&~f&9<}PbPU=mq%Ig=YH0}bN80o?^6M93lP;Kh`O(5KTYRjtojm2Iz zy*>5-X>0)KuQS~OGB zg>w4mjQ@Z!VpQySP4np~DjY@uz69LJ;eyM)?(&M)$UFDX;Mj^+%iNb8g+X=+Mb*y2 zvESN^T3~|5Ut4J)REy@t>eGsa6Cs}P+I!mhwxQ>-SzAFbROt?1_@}aeaUD2v9qxPF zDwtTT)7D<;8*`IrCzR+G86-uJ(b_=U2&d@gT7GIbw6YRvbO$1=O5sP!4-Y9@-&nR= zs%t4FrgSIhD?;J2m+Vo0BwmSNv%4g&9~F*X77_86oXB@^Ts>J$68*=vxsyvwqjhwj zqrJQki0#=8GU;)F-kAb_D)EVpC%dZSlm9|l@ATO#TEO?}Hf#yMM-y(M53~KSNnGux zeY^cVX0mnwZlaaS4hH$Yr*fYG5NN|6Rikehtm@G>bRKZfbklsZmj8dT@qcHoQN_sE7nEAJLsI}a>XVE5!!;Xa(&QVukd;6SLS@E1L zrtP5~URVwz5L5iytQfs>`OT;f3eFtZJ?Xbjf{Xe;%v<`sjG1M#NlV1l(3KrTM z=*-_(p7R+>+*rt?6}=Q4_zG~brEpwz{^Z&9ogXT9?#N^#&5?l6yDIpSaqr+_S~o z_YWA-E8;z(yps#1fN)dUjO(-DF}vMh?VK-_55VB{Yiq!l_b#nc(|D%39s@rTZ|R8X z`I2Bb09Qaa3b@%~Akc$Ilu4ruQJM9suJ1M0BluT?pLqs38oI!|HTGG}-p{mCVd^+S3Uy2F~brX9PD3sXEih$Y$$K;)k~1sm;M&d|3o}& Y9||)UiuH`~l{(o%AuSQr*Ii@(4G6&}_y7O^ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_blue.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c31f304ba6a878d53e7744d3bc1d5af484bdde GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|+B{txLn`LH zy||H+$x*=J;#B9}H;0VQtUav9bKPYfQ?|kNBQZSsCKekQv<3LCnHr0wymz^Gn*HCI z7NBN0uwQ;s)7$SA@3VEGHb1v{H1+b=AA%cs!wD(s-VBm*U z?ANQI@n<)#T)3MjsruG+ZS|gemtHli%`VgxU+t6rc}sigq$hiJ+w6J$od3kzmg>4R d8=&6*uegj4D&BCr!uB0xlBcVm%Q~loCIIQoNr(Ud literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_pink.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_pink.png new file mode 100644 index 0000000000000000000000000000000000000000..04cb5f2c6d5eb5d0f9b591dc9beabd482890b2f7 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|+B{txLn`LH zy=chC7%0&2aQn>*7CqcMx(?}1SzXL@Q9D$~U0|;6l)MEO-%UF4;)+n^Zl(Lz{Xd=e z@d9dwfe*!t@|0hhecN36gmvv4FO#m(+RLB4T literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_purple.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_purple.png new file mode 100644 index 0000000000000000000000000000000000000000..63888f44b2b7e00d3442a01c5598692ddd78981a GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|+C5zyLn`LH zy`U(>$SA@3z&*%Cynt=-i>?)xO%HhT7IgY4PAqe~y<}GtcTRNcIX>&2_ulnC)9+Pt z0yV=x&D=$6UIEe1oN)7>@AugU?f!P$ds52QxFsh!=K8l}T`t+M_V?qsw#k9#W(QAt sQV?r*_w{r36SXbJ|9Vvd4f%JK_w`FDp;LMZA3;WXy85}Sb4q9e0G0(%5dZ)H literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_red.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/frame.rsi/right_crystal_red.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e7dadfe1ed9143752439fee6b011d3143df229 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|N^=-s4hQz?e?5LpES*_Y zdRp~c?z#B+7qU+7^0~R)a_zJ&QFc~|Hu`U?@44I0`ztmZsK??mcMP{$SJaLVPeCSl My85}Sb4q9e0FoX(R{#J2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/meta.json b/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/meta.json new file mode 100644 index 0000000000..76e26a6e9a --- /dev/null +++ b/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "license": "this content is under ICLA licence, read more on https://wiki.imperialspace.net/icla", + "copyright": "sprited by @gag08984", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "shard_blue" + }, + { + "name": "shard_green" + }, + { + "name": "shard_red" + }, + { + "name": "shard_pink" + }, + { + "name": "shard_purple" + }, + { + "name": "shard_yellow" + } + ] + } diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_blue.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..5db69199367edba00d196fe1c7f9ef0971f2cf8e GIT binary patch literal 1765 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Xs4mP03*$GQm0V&4fAa^H*b?0PWGBB{s_jGX# zshIQj?)~iWQkml)-!Bo%xy#ee*W?l5=pz2?t=N^LY? zE=52W<}_UpUR7QjlKbaFYG&S#51p6hYQ5uo>FbBXM^K|?E7)cgRBha{@WVs(eDgog z(#(x!efwg3cHe5#>|iZ+uYRe$X;rmgA!mUDnVgs=J@N5eSO}P zXS-?-FaGsBLQY=$EU&!X#>4uHc2mLsm6u|^uIp!aS1bGB`h}T5MEFjS zD}DCs?4>)Oenc#OzxJMCQ0UzcEB;lk_R~8*{h8h#yW20SU+Uox?lp~m^$h(|Qgd(Q zeX4w4_q8x{-OJvIt6sGo|G9L%bnW}S-rP%fzLHfAo&7qy?%6Iv5inQ5_SM0OotN$$ zeY`x2Unj5U&F%UHCf`4tdwVJ7vb5ae^?UEU_`K@>-0fl0rE2x74|C#9;K2Aw6<%^W z%(VdR~=rwD>A6`O)oGc+|9FRo2*@V^403U zIqwKL50tmQ>h zgu`_E&V6P9QhV;)TGzvS{_LACljSy+?z-7K5txp8@7JxhEjS~q{Pzr@*lw8@u|N4w|+3;l=v| zLay%g+`B=yC0jM67Y2Ob6Mw<&;$THewC*H literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_green.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_green.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f1a9b1904ef362f1004d05e3f7d54f471eb344 GIT binary patch literal 1766 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Xs4mP03*$GQm0V&4fAa^H*b?0PWGBB_$@N{tu zshIQj?*8o9QiTFdp)rCq|;#Y2}hR42>xeHPf(Z|0h*dd>(rCtbOdP=vb@hS+{poU4MUqrI-oLE>v@N3b!oWvh^Y3?Sq`yod#8& zD%`R$VDIb2ufC;U_bN6%&OhZ{NMZSjiX!~R-(-;t-h6uRrM5$}*Vk|OD{t&1H@j}n zTmOU}3r3uw0kSfu$v|Yb*#G=p`%m9hOg=%#aVdxXZv0rCnRmt9W|jT%pL^B4L2k_X z*gb>A4@-C>g~4nE)`W&V^XIH?i-#?~a?%g;2;v@W~%*y)B{A2mcf9u+P?eZTU zvR=A#nYr7lRnl_e_V$-wROjo)>7NaKFR}6#C|%s)D8wIeJB3qxN`HU%E?(`Op3T1| zcH+gC?`CSAwcSy<+vNI}w&TL*_g;AMdDZ{9?Ao(|j+^@szvF;jOj=d$=2Jdvul15$ z6E_yD+Gcd=THEpU_v>P-A1qv3d%iwq=IXPuG3)>B-f5k}PB3-&?6}wY^U|Ha()*`8 zJ6GC2?OB+$_tKrm%x${v)qQZy%qyrU&&>OFY3{AhUcqlf)EyLX1wJtMO{->CyHm2i ztlXnC`N^}>)4hw&$JvF=%C0)Rcvoal>5~6hneYBezrQ9|QLy({*8bVn2k<9@r+xx$ zL3_QEfr)Y7?ee`QHA;Gn-A}nu8k`_{P_6${i|#X;y?0<9GfJJTykLq>uEKP#Q%r=ZNd4Vxlw?MU20AWHvywK}YISN76QqwwmeLgnQDw}A2aDpr2;^mkL9aN7bhX)OSWh}HD#_)|d10!k0KTvf7 a%U@RRn@1*A)HZv7G<&-GxvXJb!#!2N18(p|N115Fy9O{+WF++OYB3FTk*==lEN9OkCC3QG7DK70z zOHz5KmK7?nB}4O!4x8I8m&QN|w<9`AEiy?$xpTKv=ojz#KJRz^ysxJ%(=XM1)~)$D z3*-ROz_IIBR@gV3+;U;|Zu741^X|rcDxb9}ckw@lhCo3Fg=q{-8(9Q8R2v+07+I1y z6$HE)8Y7rEj@+_)#h+laW$(_9pQ6`q`?+Hmx9`j68w{Nuvv8;gh1I-|3#6yQ^fE$@WbfpZ%Z6`HY3*h!AcQiqtunC9k%z+i>`yy9rg- zY=si1uj~3(J$)TN?b)y2{nOrs6qb9KTQXwz5L9zcQ^^&l{B>Ivet2fBF8}9v&%{{% z_i|s=FDMeUj_1g-qyJ~${J3Vfv(*VsEMbOlU#f7*p}W7b_nMf$la{+W|M7)CjhABT z|6kOt6P$d2pw=@X@26Q5|NVREbKUP(3oqSyzrQAWmh{x)^DH9f{8@hW`?+^_f@l4H z^KJdwebXNnFd;`9!U@nAy~(mLlkL_PiQeP!t8yzs-v64h^K;3^v)j9iOC}1#V;e&o zFd>{)^Kg&-{bBc|I}3j@uidx3>bOTJFlK*!4wJRs88NH+?;~xX{U0CUkIqe;mjajT z&DA?QH~XIU*-&%0RkNi-uk!Zm>(#`~nw^oi&w(U8Y>ok?N;5~R ztY?0?j~Bk}o#?*J78nGd-+f=Y^HrPs)qm>o8DA@}{y&#)K0T^d|L{XXjxtIMGrP9T ztJ!*1b?(0HyCxRCd6KmH{OVPC6*2!`&VD33|MsQtcitClj+qsm`|GOx_FMOvw;drE zafi-=VrF;c>Ti2q3Lm{c^Lcji+x&_vfBsy&d+E;AvyYeVRNi}f>CUTXFG)BP5pGX+ zJI}gxS8?<1ENSJ?#kW4J*jN7Z_0&sumgk?J@+|K6f^CMEOuUa;wjCtou$IP<_m?ED z0s~6j{*CV0ukmYa^S8bXkB`0kAd!P$s7svq(s}FsndGxcneR^hEZ_da^?&Hy4-LmS z1w7PnXVFyQmd2Wzf_*06|42WNs?EPGEm!`{HgwLP-6mTv%+@|T_il{VzUheslkdy| zZRzabqZg~EF1$IndfPY0?>l!@?z#Fr)ZAv<{T~~DxRm$!7^&kfPe7^3SHNmvUAz0) ztjqdmV=d$Ny=UIj1R(2^+4u{$Rbd)NfsMds43u|FZ9T!Tb{^ z81A~hbWh6z{#{e7d737Auses}Q0?Y(>Wf%3cP-z0`QPupzyDe>=YGxp_|3L|WxhST zoW%&Vm?UuEfz<2o>;|)LJzoCq`{TWH&#M0S58k%;zC6PPeI|}0LJkVk7??J)2z016 zIOs64BylPTcr!Fc@PGZyEWzWOpZBKz{Z}>l@bYz`zlv{2EPTqsp(2b!574+zw?8Pa z+x76`muJW3+UJ@6+Oz7U`aHd}u@?1vOt$aa_$WQ4#gY+=bqJT-;dBcBwe=z6?Sq`? zZbB#mIyY6gWnsYH*Na~r+j`!+xcZx{?%kD--c5M;gax;!fCg;hG+F`-3NL9c{kjj& zzSW;$sC;tr_^-d5Jc8=Dy#)#-Ux8gommmH2^*yu8I)#IPFLIiCcG%bNkDVp`g}49J z{Ks!L&Rx3G_IF?2$8HH$Ka`M0@f9c%W-Bl+tJ;2KX?FM0=e2*%9$vb0{{H_Sx=%0<<|2cB;`=#b{fU$*P1|lNO95c4=y0zs+|Iz8$(sEaK+kLw? zSNH7HYef=K1l{5M>WKUPY0u8d-M;yvdUbhENT|8Zs``7Ym``&r-Kj5jKjX_@9x?pE zb}eGReFHl%(9EiMk7mEGiI|mLmAqutu7`zR?@aIA_x)1u(MvI3`zEgX+AC^b%zOlY zq(AkWpt$w>yPV8-hkiajs(#JPt<2_sNankH{QIXqn|1HQ4b#2*FFp_bZ(VLSU8+`p z??Zx#A>mZHo6i=hz1B;1O)N~PTnN++^kiP#{-~Nq-qFA2{kJIDX|~-e{BLz}xd*=m z{EJMy3_ddR^M6IUd4R9 zT4z>$fKbp0>cW>Nf3DfRh_^C6o7WR!Zd3Q`-SJz@>30l`P7%xk5?o)cZMwpq zdnJcoJ5^pAVxA|VKp^$+-|M&CtGs;+U+g;DwL2zm{Czii>CRWN@^80xFSV4GTYQ#4 zaBshJQ*UeUM7zxX-LIK}fjk3SDGLp?`$fy|OWl>&_|Or5ZYH_#XGmc9%bc!yU{^+O R@fT3F;pyt?fAk9q(A literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_red.png b/Resources/Textures/Imperial/Medieval/MagicDungeon/shard.rsi/shard_red.png new file mode 100644 index 0000000000000000000000000000000000000000..79c9a18a99b7e6dad8ccb80ca962af253051fe87 GIT binary patch literal 1756 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Xs4mP03*$GQm0V&4fAa^H*b?0PWGBB`B^K@|x zshIQj?#ArcQiZ3kMF@))9WuY z0_`OUH2gEY^o`wM&NR2@e77pzfBLuU|1!<$*^UZ(lS=D;_^3t96Ca=8oe(CqBb&1l0+3!%dcl0k>{_Xn6d91?&t| zS%eEWaUODb;dlMruNA*u{@i@&&eG4~cVvx@%z?Wft41?Nwkwxj#AG@req7xzRhz&4 z;&RjcM`vbkvu|X4M9{*8H@5t!zq?Vhyq!lu-9Z7X4UoW_<~u=g`|nqwnd|PG+pMxb z{^pcd=DSNjx!wOLwKWPO2Q!8tr`0^>ue_&Q`1@zp%S!$C_w~>2t@yRYbO`H=l`*|)d|KI^QxM1kL6|MCl0yf z0z+mkZ~v?6>g=tdQ{PRuzaF>eS7z?IZ4rk!3km0;Rppsmwmu9r{rJBB=*j4%D}SEd z`)c)SX`49=grZ)6FZI{6mzVA&J$KA}cW>@$_dkoaUp2Qmz$w5%Ami)y_?!LOQ$0UM z^K9(TXNim7=T;qleCT)Z(w+Oe&qqtGy&Jh{&;2*CnlinFvcQQ6yFVV+SDw1_-Hj`b zsq0>zo%C+!S-1J&z{KJb!(o=a{Unp!jIBSJMO3}n)FOMB`Z0F+3^K{tKoU$T!`;~>x)7Yk8 zE7RcPo2WLs*;)K$icw^tU`s>L>{TkP+)X7MS1iJ1?s@wapWFNY`ONrpmbquo=bt!z z{WT-dW|DwIdGFT0j6KVizWn&t?B4E8Tc7XGJT))tH#5U6r3MEbMwTQ_1p#k{#t0^k zBSH=e(-@dGvIum1^|>wYU~aN^=f`_X`)Ak0+-+UG)V{q}u*$E&!3c*QRiJVEdF>yq zl9uz$&wF$4|K^L&LuKv1#bmzwbEx;yoo9z-?Uj|!a0+yQRN^q?shnS<^VRJ+U^+pb*tQjzKW@bFjvd4Gr}3|7!l@CueS6 zy7T7C8OHq`fe%=a!x`0ez$nCWo=f1_q)pMLUM|0f>kpWxd)P3C_6c9ZbEuNT|@T=3)B;-i*r6NRzF zI>I%l)lN+K_3vBy(w&oy)1_-p%dJF3>1nE!t^y?5XAyh8lp07`i_ z>*`aI_MdznyKiH_tn8}fC98Hl{CKr>b??6Km)6F66<@V>TV*Ey?#Im60_yPOhj0i| z%CvTD3#tF)2u)2-Z$-Sn@ppu&#(SxyFTZw>907xc0ykC zI6mcl&yMSF?kZmG?Kyh2xYuqy$Z?Oi+nfKox2pH)tvhdy#(lki_O4?40YU+ta^&n+ z`Kiygy}vfky!4UJ$Ft#H!MV8=Isfa{nv`elE4zOD?ADKuX5W>)q)H&Aedp`h@o(ZS zkfSCZ)vwbyyH#oep7K98;cf9jy!3;S2na=^sP zB8)qhHgO(uST`T+0S{4TEJG?mKTT*Ub z=3B4KywvRV+X|hRTj2NnpKn&pT6bkPZH%doC{=!ZE^l*r?VT8D{j)beNaPZTy!}tl zO%A#xk$L*k7GD8i%p{Px$07*naR9J=Wl`)EgKoo|*gfv@C<6b~vdW*q6;2}IpZeo`Ucmq!$7FKBlWl51V zHZG(LrpjPKV8_gmX6Gvh=6%0!W}e{jcs&1|m5U-usp-{vUDtos-`L8flv9>vR8<8) z6h#1(Wofj3Z5se!7zP0L+nC+&Y0+Nm4ghprN7FQ1zrVN~e<;s~&jtVxLa?pO#m)i< zApi)&@L|30d!zkUuRtj^rD>{ba YH=@0J9`2(CNdN!<07*qoM6N<$f)Wgf`v3p{ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorB.png b/Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorB.png new file mode 100644 index 0000000000000000000000000000000000000000..e33a9b613e050b9a1e08b716453ae173c5dff4e0 GIT binary patch literal 325 zcmV-L0lNN)P)Px#|4BqaR9J=Wl`(FEFcgNrM%le^0+2jbVsJPC>KV94=4=_ebA%4ufD<4Fb|k2} zv#>Q2nc|SBQtjBv5`Uwm_x{fh+dv=?2)sIPKAI?{mcQ29w*9yN)-_y8xfDe~(=-4i zNdiD!*GBalR{?-&ngIAZ{Br#Gw5oNU0KhN|bX^D24}71DckfLAKnTHZ|88C9EFgpc zAdcgk^>G}H>YZ+ZQfkTb{Q3!2^nGvC`(c6maMM690=~i@67U&rE#NyGEFPkDc$Q_Z zxf-J#tivx)Z4^b6W$8QxqY2FOd~?;Rs?u-2D@OKGO8vJ>(-c6Px$07*naR9J=Wl`(FFFc3vwR_U!soB$&C6c#7o5F8vNPqLfigu1T1r{FY!d7d{{t!Wzj_WR;wFQv49yFAYUtjtXh;a(=hA&f*K YKM@6c4-8;lx&QzG07*qoM6N<$f~e(*Qvd(} literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorD.png b/Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorD.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef8927f7c11f08854025a317051d817906a387e GIT binary patch literal 329 zcmV-P0k-~$P)Px$14%?dR9J=Wl`)EgKoo|*Ea|851Pap|7;FMtd53s|9AaUw;So{@Ie;e+3n8VT zT{Y= z07Ov)Kv|YX^pGgI;dH&yo-f9`cP0QJgn-{~HVf;l z1%wa)gkkuw-uJyxz11yHN=<2+>Q6AIZCj(>FAF?}n+7@&a25WNfXnd30Xw;sZ085f7p bPx$07*naR9J=Wm9dJ0Fc`;wcXZF2Fo1R|WfV3tk= z7YAoMHP}*UY{D&<-x&D5|1Y0Ogg_t=`0sr9Xrh!_o~^fS`?CMeHC#%$6h%SPGyo(? z0zh5YR`s8*0s!MU0`T*7WH;F9vuH&1T%X#Kk+wI<)0DusJlRj*&a~2Rn z01(IV-MZG=s@~}qD5aJ>&&?-T)Azkq@3#dW!)*h-2>1$rOTcINW&z*fu;C$Ehi6&l znrpDy!8-i*)J9Q6S(eUIu$sU$O?OwVsw(sLyJBT8r8IxLG))1F%%2{@M;RZ7Fc1h_ Y0T+CE+2kNja{vGU07*qoM6N<$g4x)M-T(jq literal 0 HcmV?d00001 diff --git a/Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorF.png b/Resources/Textures/Imperial/Medieval/Misc/keys.rsi/doorF.png new file mode 100644 index 0000000000000000000000000000000000000000..1b24f7f94cfa9709ee8f288121e44ca90e91606e GIT binary patch literal 328 zcmV-O0k{5%P)Px$0!c(cR9J=Wl|7DwFc3yxqV#S!0Z8sCyp*XZas;licgP)R(x>18E1v+N6+IH1 z07aq8R&2F|M3G`*E6w_qmS(=k_y_QKJf8o~%0&~U)c9zbi!@8GgRd`t)7_03ie)yAz-m)maM& zApi)&@MgX1I-`24TcDI0(=@$q!HlM9jC#K;a35|O=tRI(_)7vV!xsy<4u=H~(L6j! z65HGfqc50;|2?%q5Rm7&^%RUIFbu=ZRV#{Odi!lLvX@d$f4ex20VbKZ9>RMW7l+W} a@%#eD*?M|G$L_8G0000 Date: Sat, 21 Feb 2026 21:01:54 +0400 Subject: [PATCH 6/6] =?UTF-8?q?2=20md=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20?= =?UTF-8?q?=D1=81=20=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1=D0=BD=D1=8B=D0=BC?= =?UTF-8?q?=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D1=8C=D0=BD=D0=B5=D0=B9=D1=88=D0=B8=D1=85=20?= =?UTF-8?q?=D1=82=D1=80=D1=83=D0=B4=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Imperial/Medieval/Procedural/dock.md | 14 ++++++++++++++ .../Imperial/Medieval/MagicDungeon/dock.md | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Content.Server/Imperial/Medieval/Procedural/dock.md create mode 100644 Content.Shared/Imperial/Medieval/MagicDungeon/dock.md diff --git a/Content.Server/Imperial/Medieval/Procedural/dock.md b/Content.Server/Imperial/Medieval/Procedural/dock.md new file mode 100644 index 0000000000..ee6ffb7fa1 --- /dev/null +++ b/Content.Server/Imperial/Medieval/Procedural/dock.md @@ -0,0 +1,14 @@ +## Моё творение - генерация. +Я угрохал сюда неимоверное количество времени, весь январь, как я помню, даже чуть-чуть февраля.
+Система всё еще немного недоделана, сюда осталось добавить спавн выхода, но это не страшно, там логика схожа с добавлением спавнов игроков.
+ +Основная логика работы содержится в `DungeonGenerationSystem.cs` Публичный метод ровно один - `GenerateDungeon`, отвечает за генерацию самого данжа поэтапно. Давайте на его логике и будем рассматривать её. + +Сначала мы генерируем Layout нашего данжа. Здесь код вы можете видеть весь реализован в файле `DungeonGenerationSystem.MazeLogic.cs`. Мы сначала генерируем сам данж со всеми стенами, используя математику в виде векторов и Box2. Потом мы должны в каждую комнату вставить дверь так, чтобы она была досягаяема из любой другой комнаты. Также мы должны еще выбрать комнаты старта для игроков. Потом часть дверей мы делаем запертыми на ключ, причем так, чтобы игроки могли каким-то нереальным образом достичь друг друга, используя эти ключи. В теории может сгенерироваться данж-головоломка, который заставит игроков думать (сложная вещь это ваше думать). + +После всех этих сложных матопераций обхода графов мы строим внешнюю и внутренню оболочку данжа, используя стены, двери и прочее. Далее спавним нужные нам ключи в нужных нам комнатах. А потом еще передаем в основную функцию `GenerateDungeon` список координат спавна игроков. + + В принципе, по описанию, несложно, но жуть как мне надоело делать. + +### TODO +Осталось вам только выбрать комнату (или сразу конкретные координаты) спавна портала выхода, тут логику можете тоже скопипастить, думаю разберетесь и оно несложно будет для вас, я сам забыл это сделать в январе). diff --git a/Content.Shared/Imperial/Medieval/MagicDungeon/dock.md b/Content.Shared/Imperial/Medieval/MagicDungeon/dock.md new file mode 100644 index 0000000000..b031f5d807 --- /dev/null +++ b/Content.Shared/Imperial/Medieval/MagicDungeon/dock.md @@ -0,0 +1,12 @@ +## Что делать дальше? + +### Lethal Company + +- Сделать сам интерфейс вот этой штуки на корабле из леталки на основе консоли мониторинга экипажа из ванильной сборки, добавив туда на необходимые координаты кнопки деактивации ловушек. В принципе система несложная, у вас есть буквально референс как надо делать лично от виззардов. +- Сделать ловушки, желательно сделать их универсальными, чтобы создание ловушек поддавалось дальнейшему расширению. Как пример: PressurePlateComponent и PressurePlateRecieverComponent будут задаваься для всех ловушек, активирующихся по нажатии на нажимную плиту; ProjectileShooterComponent будет работать над тем, чтобы выстреливать проджектайл. Эти два компонента являются разными по типу: первый - триггер, второй - действие. Триггер-компоненты должны иметь отдельную абстрактную систему работы, у которой будет одинаковый для всех механ отправки триггер ивента на все компоненты-действия. Соответственно компоненты-действия принимают общим для всех методом триггер и триггерятся. В принципе универсализация - залог успеха, если это сделать, то прототиперы даже сами смогут ловушки ставить, лол. + +### Остальное, что я не успел доделать + +- Необходимо закончить систему входа-выхода из данжа, закрытия данжа, а также проработать момент с тем, как и в каком состоянии люди туда будут попадать. +- Необходимо сделать рандомный спавн шардов/сделать им руду/что-то такое, чтобы их можно было найти.
+