diff --git a/README.md b/README.md index a551b9d..a7ae95f 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,15 @@ D:\Godot_v4.7.1\Godot_v4.7.1-stable_mono_win64.exe Главное меню (`scenes/MainMenu.tscn`) ведёт на три сцены (назад в меню — `Esc`): 1. **Подземелье** (`scenes/Dungeon.tscn`) — разрез убежища à la Fallout Shelter: сетка 6×5 - комнат, **тёмная шахта** (колонка тёмных комнат друг над другом = скрытая лестница; - персонаж заходит и проходит по ней на другой этаж, анимация — боевой `walk_0`), - **объекты** (фонари/свет) и **несколько персонажей** на этажах. - ЛКМ по персонажу — выбрать/тащить, ЛКМ по фону/WASD — панорама, колесо — зум, `R` — сброс. - **Тематические комнаты** (Спальня/Логово/Дойка) имеют слоты: при перетаскивании - персонажа в такую комнату он занимает слот, **меняет скелет на состояние комнаты** - и играет анимацию — **одиночную** (1 в комнате) или **групповую** (2+). + комнат + объекты (фонари/свет). Карта стартует пустой; персонажи создаются **спавнером** + (панель внизу: `+ Раб / Монстр / Ребёнок / Игрок`) и **стоят в idle**, пока их не перетащат. + - **Типы персонажей** (Игрок/Раб/Монстр/Ребёнок) + генерация имён. + - **Тематические комнаты со слотами по типу**: Дойка = 2 раба + 4 ребёнка, + Логово = 1 раб + 1 монстр. Перетаскивание в подходящий слот меняет скелет на + состояние комнаты и играет одиночную/групповую анимацию. Над комнатами — окна с именами. + - **ЛКМ** — выбрать/тащить (в комнату или на пол — там персонаж просто стоит). + **ПКМ** — убрать персонажа в **инвентарь** (панель справа; клик — вернуть). + - `WASD`/ЛКМ-фон — панорама, колесо — зум, `R` — сброс, `Esc` — меню. 2. **Просмотр спрайтов** (`scenes/SpriteViewer.tscn`) — галерея вытащенных изображений (UI, фоны, эффекты, нарезанные спрайты). `A/D` — картинка, `W/S` — категория, колесо — зум. 3. **Нативные анимации** (`scenes/NativeViewer.tscn`) — браузер всех 38 персонажей нативно: diff --git a/src/RoomMap.cs b/src/RoomMap.cs index 6f8159e..a1da0f0 100644 --- a/src/RoomMap.cs +++ b/src/RoomMap.cs @@ -21,7 +21,6 @@ public partial class RoomMap : Node2D private Vector2 _homePos; private static float FloorY(int row) => row * Cell + Cell * 0.9f; - private float _shaftX; // X тёмной шахты (комнаты друг над другом = скрытая лестница) private readonly List _pawns = new(); private System.Random _rng; @@ -31,7 +30,7 @@ public partial class RoomMap : Node2D private PanelContainer _infoPanel; private Label _infoLabel; - private enum PState { Idle, Walk, GoShaft, Teleport, Held, Assigned } + private enum PState { Idle, Held, Assigned } private class Pawn { @@ -80,6 +79,12 @@ public partial class RoomMap : Node2D private static readonly string[] MonsterNames = { "Грок", "Зуг", "Морг", "Рарг", "Тхок", "Гнар", "Урук", "Драк" }; private static readonly string[] ChildNames = { "Пип", "Тимо", "Лулу", "Бэн", "Ния" }; + // ростеры для спавнера + private static readonly string[] SlaveRoster = { "elf1", "sister", "satyress", "knight", "shortstack" }; + private static readonly string[] MonsterRoster = { "orc", "oni", "man" }; + private static readonly string[] ChildRoster = { "baby" }; + private static readonly string[] PlayerRoster = { "player" }; + // инвентарь снятых персонажей private class InvItem { public string Name, Scene; public CType Type; } private readonly List _inventory = new(); @@ -108,9 +113,56 @@ public partial class RoomMap : Node2D BuildInfoPanel(); BuildInventoryPanel(); + BuildSpawner(); ResetView(); } + // ---------- спавнер персонажей ---------- + private void BuildSpawner() + { + var bar = new HBoxContainer { Position = new Vector2(16, GetViewportRect().Size.Y - 46) }; + bar.AddThemeConstantOverride("separation", 8); + var lbl = new Label { Text = "Спавн:" }; + lbl.AddThemeColorOverride("font_color", Colors.White); + lbl.AddThemeConstantOverride("outline_size", 3); + lbl.AddThemeColorOverride("font_outline_color", Colors.Black); + bar.AddChild(lbl); + foreach (var t in new[] { CType.Slave, CType.Monster, CType.Child, CType.Player }) + { + var tt = t; + var b = new Button { Text = "+ " + TypeName(t) }; + b.Pressed += () => SpawnCharacter(tt); + bar.AddChild(b); + } + AddChild(bar); + } + + private void SpawnCharacter(CType type) + { + string[] roster = type == CType.Monster ? MonsterRoster : type == CType.Child ? ChildRoster + : type == CType.Player ? PlayerRoster : SlaveRoster; + string name = roster[_rng.Next(roster.Length)]; + string path = ResolveScene(name); + if (path == null) return; + var p = SpawnPawn(path, _rng.Next(Rows)); + if (p != null) ShowInfo(p); + } + + private static string ResolveScene(string name) + { + if (name == "baby") return "res://characters/baby/human_baby_native.tscn"; + foreach (var s in new[] { "combat", "stand" }) + { + string path = $"res://characters/{name}/{name}_{s}_native.tscn"; + if (ResourceLoader.Exists(path)) return path; + } + var dir = DirAccess.Open($"res://characters/{name}"); + if (dir != null) + foreach (var f in dir.GetFiles()) + if (f.EndsWith("_native.tscn")) return $"res://characters/{name}/{f}"; + return null; + } + private void BuildRoomHeaders() { foreach (var r in _rooms) @@ -185,6 +237,7 @@ public partial class RoomMap : Node2D p.Node.QueueFree(); _inventory.Add(new InvItem { Name = p.DisplayName, Type = p.Type, Scene = p.BaseScene }); RefreshInventory(); + UpdateLabel(); } private void BuildInfoPanel() @@ -252,25 +305,8 @@ public partial class RoomMap : Node2D for (int c = 0; c < Cols; c++) AddCell("res://assets/room_sliced/Frame.png", c, r, 1, 1, new Color(1, 1, 1, 0.5f)); - BuildDarkRooms(); BuildObjects(); - BuildPawns(); - } - - // ---------- тёмная шахта: колонка тёмных комнат друг над другом (скрытая лестница) ---------- - private void BuildDarkRooms() - { - const int col = 5; // одна колонка на всю высоту - _shaftX = col * Cell + Cell * 0.5f; - for (int r = 0; r < Rows; r++) - { - // затемнение интерьера ячейки - AddRect(new Rect2(col * Cell + 8, r * Cell + 8, Cell - 16, Cell - 16), new Color(0.02f, 0.02f, 0.05f, 0.94f)); - // слабое свечение по центру — вход в шахту - AddTex("res://assets/room_sliced/Light.png", - new Rect2(col * Cell + Cell * 0.3f, r * Cell + Cell * 0.3f, Cell * 0.4f, Cell * 0.4f), - _world, new Color(0.45f, 0.38f, 0.9f, 0.4f)); - } + _rng = new System.Random(20260803); } private void AddRect(Rect2 r, Color c) @@ -300,23 +336,6 @@ public partial class RoomMap : Node2D new Rect2(c * Cell + Cell * 0.28f, r * Cell + Cell * 0.02f, Cell * 0.44f, Cell * 0.44f), _world, new Color(1, 1, 1, 0.75f)); } - // ---------- персонажи, гуляющие по этажам ---------- - private void BuildPawns() - { - _rng = new System.Random(20260803); - (string path, int row)[] picks = - { - ("res://characters/elf1/elf1_combat_native.tscn", 0), // раб - ("res://characters/sister/sister_combat_native.tscn", 1), // раб - ("res://characters/satyress/satyress_combat_native.tscn", 2), // раб - ("res://characters/knight/knight_combat_native.tscn", 3), // раб - ("res://characters/orc/orc_combat_native.tscn", 2), // монстр - ("res://characters/baby/human_baby_native.tscn", 1), // ребёнок - ("res://characters/baby/human_baby_native.tscn", 3), // ребёнок - }; - foreach (var (path, row) in picks) SpawnPawn(path, row); - } - private CType TypeOf(string name) => name == "player" ? CType.Player : name == "baby" ? CType.Child : Monsters.Contains(name) ? CType.Monster : CType.Slave; @@ -354,8 +373,10 @@ public partial class RoomMap : Node2D WalkAnim = Pick(anims, "walk_0"), IdleAnim = Pick(anims, "idle_0"), Type = t, DisplayName = name ?? GenName(t) }; - Decide(p); + p.State = PState.Idle; // просто стоит (никакого блуждания) + PlayIdle(p); _pawns.Add(p); + UpdateLabel(); return p; } @@ -365,90 +386,8 @@ public partial class RoomMap : Node2D return anims.Length > 0 ? anims[0] : null; } - private void PlayWalk(Pawn p) { if (p.Ap != null && p.WalkAnim != null) p.Ap.Play(p.WalkAnim); } private void PlayIdle(Pawn p) { if (p.Ap != null && p.IdleAnim != null) p.Ap.Play(p.IdleAnim); } - // решить: гулять по этажу или уйти в тёмную шахту и перейти на другой этаж - private void Decide(Pawn p) - { - if (Rows > 1 && _rng.NextDouble() < 0.45) - { - p.TargetX = _shaftX; - p.State = PState.GoShaft; - PlayWalk(p); - } - else - { - p.TargetX = (float)(_rng.NextDouble() * (p.MaxX - p.MinX) + p.MinX); - p.State = PState.Walk; - PlayWalk(p); - } - } - - private void UpdatePawns(float dt) - { - foreach (var p in _pawns) - { - switch (p.State) - { - case PState.Held: - case PState.Assigned: - break; - - case PState.Walk: - case PState.GoShaft: - { - float d = Mathf.Sign(p.TargetX - p.X); - p.X += d * p.Speed * dt; - p.Node.Scale = new Vector2(d >= 0 ? 1f : -1f, 1f); - p.Node.Position = new Vector2(p.X, FloorY(p.Row)); - if (Mathf.Abs(p.TargetX - p.X) < 4f) - { - if (p.State == PState.GoShaft) - { - p.X = _shaftX; - do { p.TargetRow = _rng.Next(Rows); } while (p.TargetRow == p.Row); - p.State = PState.Teleport; - p.FadeT = 0f; p.Swapped = false; - PlayIdle(p); - } - else { p.State = PState.Idle; p.IdleTimer = 0.8f + (float)_rng.NextDouble() * 2.2f; PlayIdle(p); } - } - break; - } - - case PState.Teleport: // исчезнуть в шахте на одном этаже и появиться на другом - { - const float dur = 0.5f; - p.FadeT += dt / dur; - if (!p.Swapped && p.FadeT >= 0.5f) - { - p.Swapped = true; - p.Row = p.TargetRow; - p.X = _shaftX; - p.Node.Position = new Vector2(_shaftX, FloorY(p.Row)); - if (_selected == p) ShowInfo(p); - } - float a = p.FadeT < 0.5f ? 1f - p.FadeT * 2f : (p.FadeT - 0.5f) * 2f; - p.Node.Modulate = new Color(1f, 1f, 1f, Mathf.Clamp(a, 0f, 1f)); - if (p.FadeT >= 1f) - { - p.Node.Modulate = Colors.White; - p.State = PState.Idle; - p.IdleTimer = 0.4f + (float)_rng.NextDouble() * 1.5f; - PlayIdle(p); - } - break; - } - - case PState.Idle: - p.IdleTimer -= dt; - if (p.IdleTimer <= 0f) Decide(p); - break; - } - } - } - private Pawn PawnAt(Vector2 worldPos) { float w = Cell * 0.5f, h = Cell * 0.85f; @@ -487,7 +426,7 @@ public partial class RoomMap : Node2D p.Row = row; p.X = Mathf.Clamp(col * Cell + Cell * 0.5f, p.MinX, p.MaxX); p.Node.Position = new Vector2(p.X, FloorY(row)); - p.State = PState.Idle; p.IdleTimer = 0.5f; PlayIdle(p); + p.State = PState.Idle; PlayIdle(p); if (_selected == p) ShowInfo(p); } @@ -638,8 +577,8 @@ public partial class RoomMap : Node2D private void UpdateLabel() { _label.Text = - $"Карта убежища — {Cols}×{Rows} комнат, тёмная шахта (скрытая лестница), {_pawns.Count} персонажа\n" + - $"ЛКМ по персонажу — выбрать/тащить · ЛКМ по фону/WASD — панорама · колесо — зум · R — сброс · Esc — меню"; + $"Карта убежища — {Cols}×{Rows} комнат · персонажей на карте: {_pawns.Count}\n" + + $"Спавн внизу · ЛКМ тащить в комнату/на пол · ПКМ → инвентарь · WASD/ЛКМ-фон — панорама · колесо — зум · Esc — меню"; } public override void _Process(double delta) @@ -653,8 +592,6 @@ public partial class RoomMap : Node2D if (Input.IsKeyPressed(Key.S) || Input.IsKeyPressed(Key.Down)) dir.Y -= 1; if (dir != Vector2.Zero) _world.Position += dir * (float)delta * 600f; - - UpdatePawns((float)delta); } public override void _UnhandledInput(InputEvent @event)