using System; using System.Collections.Generic; using System.Linq; using Cthangover.Core.Mods.Resolvers; using Cthangover.Mods.Rpg; using Cthangover.Tools.Common; using Godot; namespace Cthangover.Tools.RpgEditor { /// /// The map's own properties in the right column: id (the scene name, read /// only — it is the file name), display name key, tileset, size, zoom, spawn /// and random encounters. The size is applied with a button, because a /// smaller size cuts cells off. Encounter groups are written as /// slime; wolf,slime — groups split by semicolons. /// /// The first edit after raises /// once, so typing into a field is one undo step, not one per letter. /// public partial class RpgMapPropertiesPanel : VBoxContainer { private readonly List tilesets = new(); private readonly Label idLabel; private readonly LineEdit nameEdit; private readonly OptionButton tilesetPicker; private readonly SpinBox widthSpin; private readonly SpinBox heightSpin; private readonly SpinBox zoomSpin; private readonly SpinBox spawnX; private readonly SpinBox spawnY; private readonly OptionButton spawnDir; private readonly SpinBox stepsSpin; private readonly LineEdit groupsEdit; private RpgMapDocument document; private bool suppress; private bool editing; /// Raised once before the first change after a bind. public event Action EditStarting; /// A property changed. public event Action Changed; /// The tileset changed: the window reloads the atlas. public event Action TilesetChanged; /// The author asked for a new size. public event Action ResizeRequested; /// Builds the fields. public RpgMapPropertiesPanel() { AddChild(ToolUi.Section("tools/rpg_editor/section_map")); idLabel = ToolUi.Field(this, "tools/rpg_editor/map_id", new Label(), "tools/rpg_editor/map_id_tip"); nameEdit = ToolUi.Field(this, "tools/rpg_editor/map_name", ToolUi.Text("rpg/map/name", OnNameChanged), "tools/rpg_editor/map_name_tip"); tilesetPicker = ToolUi.Field(this, "tools/rpg_editor/tileset", new OptionButton()); tilesetPicker.ItemSelected += OnTilesetPicked; widthSpin = ToolUi.Field(this, "tools/rpg_editor/width", ToolUi.Number(1, 256, 1, null)); heightSpin = ToolUi.Field(this, "tools/rpg_editor/height", ToolUi.Number(1, 256, 1, null)); AddChild(ToolUi.Button("tools/rpg_editor/resize", OnResizePressed)); zoomSpin = ToolUi.Field(this, "tools/rpg_editor/zoom", ToolUi.Number(1, 4, 1, OnZoomChanged), "tools/rpg_editor/zoom_tip"); AddChild(ToolUi.Section("tools/rpg_editor/section_spawn")); spawnX = ToolUi.Field(this, "tools/rpg_editor/x", ToolUi.Number(0, 255, 1, OnSpawnChanged)); spawnY = ToolUi.Field(this, "tools/rpg_editor/y", ToolUi.Number(0, 255, 1, OnSpawnChanged)); spawnDir = ToolUi.Field(this, "tools/rpg_editor/dir", RpgEditorFields.Direction(OnSpawnDirPicked)); AddChild(ToolUi.Section("tools/rpg_editor/section_encounters")); stepsSpin = ToolUi.Field(this, "tools/rpg_editor/encounter_steps", ToolUi.Number(0, 999, 1, OnEncountersChanged), "tools/rpg_editor/encounter_steps_tip"); groupsEdit = ToolUi.Field(this, "tools/rpg_editor/encounter_groups", ToolUi.Text("slime; wolf,slime", OnGroupsChanged), "tools/rpg_editor/encounter_groups_tip"); } /// Shows a document's map (null clears). public void Bind(RpgMapDocument value) { document = value; editing = false; suppress = true; tilesets.Clear(); tilesets.AddRange(Entities.Collect().Keys.OrderBy(k => k)); tilesetPicker.Clear(); foreach (var id in tilesets) tilesetPicker.AddItem(id); var map = value?.Map; Visible = map != null; if (map != null) { idLabel.Text = map.ID; nameEdit.Text = map.Name ?? string.Empty; tilesetPicker.Select(tilesets.IndexOf(map.Tileset ?? string.Empty)); widthSpin.Value = value.Grid.Width; heightSpin.Value = value.Grid.Height; zoomSpin.Value = map.Zoom; spawnX.Value = map.Spawn.X; spawnY.Value = map.Spawn.Y; spawnDir.Select((int)map.Spawn.Dir); stepsSpin.Value = map.Encounters?.Steps ?? 0; groupsEdit.Text = string.Join("; ", map.Encounters?.Groups ?? new List()); } suppress = false; } private bool Begin() { if (suppress || document == null) return false; if (!editing) { EditStarting?.Invoke(); editing = true; } return true; } private void OnNameChanged(string text) { if (!Begin()) return; document.Map.Name = string.IsNullOrWhiteSpace(text) ? null : text.Trim(); Changed?.Invoke(); } private void OnTilesetPicked(long index) { if (!Begin() || index < 0 || index >= tilesets.Count) return; document.Map.Tileset = tilesets[(int)index]; Changed?.Invoke(); TilesetChanged?.Invoke(); } private void OnResizePressed() { if (document != null) ResizeRequested?.Invoke((int)widthSpin.Value, (int)heightSpin.Value); } private void OnZoomChanged(double value) { if (!Begin()) return; document.Map.Zoom = (int)value; Changed?.Invoke(); } private void OnSpawnChanged(double value) { if (!Begin()) return; document.Map.Spawn.X = Math.Clamp((int)spawnX.Value, 0, document.Grid.Width - 1); document.Map.Spawn.Y = Math.Clamp((int)spawnY.Value, 0, document.Grid.Height - 1); Changed?.Invoke(); } private void OnSpawnDirPicked(RpgDirection dir) { if (!Begin()) return; document.Map.Spawn.Dir = dir; Changed?.Invoke(); } private void OnEncountersChanged(double value) => WriteEncounters(); private void OnGroupsChanged(string text) => WriteEncounters(); private void WriteEncounters() { if (!Begin()) return; var groups = groupsEdit.Text.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(g => g.Replace(" ", string.Empty)) .Where(g => g.Length > 0) .ToList(); var steps = (int)stepsSpin.Value; document.Map.Encounters = steps > 0 || groups.Count > 0 ? new RpgEncounterTable { Steps = steps, Groups = groups } : null; Changed?.Invoke(); } } }