Event light, map ambient, DSL highlighting, autotile palette, action buttons on top

This commit is contained in:
cthangover committed 2026-10-24 12:56:09 +02:00
1 parent ac5b21e4c3
commit dbe46a05bf
12 files changed
+146 -19

No files matched your search

+11 -7
View File
@@ -28,9 +28,19 @@ namespace Cthangover.Tools.RpgEditor
private readonly HBoxContainer buttons;
private List<RpgPickerEntry> all = new();
/// <summary>Builds the search field, the list, the option and button rows and the hint.</summary>
/// <summary>
/// Builds the option and button rows with their hint on top — the list grows with the
/// entries (<c>AutoHeight</c>), so buttons below it would scroll out of sight — then the
/// search field and the list.
/// </summary>
public RpgEntityPicker(string searchKey, string hintKey)
{
options = new HBoxContainer();
AddChild(options);
buttons = new HBoxContainer();
AddChild(buttons);
AddChild(ToolUi.Hint(ToolUi.Tr(hintKey)));
search = ToolUi.Text(ToolUi.Tr(searchKey), OnSearchChanged);
AddChild(search);
@@ -43,12 +53,6 @@ namespace Cthangover.Tools.RpgEditor
};
list.ItemSelected += OnItemSelected;
AddChild(list);
options = new HBoxContainer();
AddChild(options);
buttons = new HBoxContainer();
AddChild(buttons);
AddChild(ToolUi.Hint(ToolUi.Tr(hintKey)));
}
/// <summary>Reads the entries; called by <see cref="Reload"/>.</summary>
+17 -1
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Cthangover.Mods.Rpg;
using Cthangover.Tools.Common;
using Cthangover.Tools.Services;
using Godot;
namespace Cthangover.Tools.RpgEditor
@@ -10,7 +11,8 @@ namespace Cthangover.Tools.RpgEditor
/// <summary>
/// Properties of the selected map event: id, cell, look (charset path, tileset index
/// or item picture), facing, trigger, solidity, what it runs (scenario path,
/// inline DSL lines, or a teleport) and the condition that shows it.
/// inline DSL lines with highlighting, or a teleport), the light it gives off (radius 0 = none)
/// and the condition that shows it.
/// <para/>
/// A teleport is on when its map or cell is filled in; clearing the teleport
/// map and both coordinates turns it off again. Solidity has a third state,
@@ -40,6 +42,8 @@ namespace Cthangover.Tools.RpgEditor
private readonly SpinBox teleportX;
private readonly SpinBox teleportY;
private readonly OptionButton teleportDir;
private readonly SpinBox lightRadius;
private readonly LineEdit lightColor;
private RpgMapEvent mapEvent;
private bool suppress;
private bool editing;
@@ -78,10 +82,15 @@ namespace Cthangover.Tools.RpgEditor
body.AddChild(ToolUi.Hint(ToolUi.Tr("tools/rpg_editor/commands")));
commandsEdit = ToolUi.Code();
commandsEdit.CustomMinimumSize = new Vector2(0, 90);
commandsEdit.SyntaxHighlighter = ScenarioSyntaxService.CreateHighlighter();
commandsEdit.SizeFlagsVertical = SizeFlags.Fill;
commandsEdit.TextChanged += OnCommandsChanged;
body.AddChild(commandsEdit);
body.AddChild(ToolUi.Section("tools/rpg_editor/section_light"));
lightRadius = ToolUi.Field(body, "tools/rpg_editor/light_radius", ToolUi.Number(0, 16, 0.5, OnNumberChanged), "tools/rpg_editor/light_radius_tip");
lightColor = ToolUi.Field(body, "tools/rpg_editor/light_color", ToolUi.Text("#ffd8a0", OnTextChanged), "tools/rpg_editor/light_color_tip");
body.AddChild(ToolUi.Section("tools/rpg_editor/section_teleport"));
teleportMap = ToolUi.Field(body, "tools/rpg_editor/teleport_map", ToolUi.Text("", OnTextChanged), "tools/rpg_editor/teleport_map_tip");
teleportX = ToolUi.Field(body, "tools/rpg_editor/x", ToolUi.Number(-1, 255, 1, OnNumberChanged));
@@ -119,6 +128,8 @@ namespace Cthangover.Tools.RpgEditor
teleportX.Value = value.Teleport?.X ?? -1;
teleportY.Value = value.Teleport?.Y ?? -1;
teleportDir.Select((int)(value.Teleport?.Dir ?? RpgDirection.Down));
lightRadius.Value = value.Light?.Radius ?? 0;
lightColor.Text = value.Light?.Color ?? string.Empty;
suppress = false;
}
@@ -183,6 +194,11 @@ namespace Cthangover.Tools.RpgEditor
Dir = (RpgDirection)teleportDir.Selected,
}
: null;
// A radius of 0 means no light; an empty colour keeps the warm default.
mapEvent.Light = lightRadius.Value > 0
? new RpgLight { Radius = (float)lightRadius.Value, Color = NullIfEmpty(lightColor.Text) ?? new RpgLight().Color }
: null;
Changed?.Invoke();
}
+7 -1
View File
@@ -157,8 +157,14 @@ namespace Cthangover.Tools.RpgEditor
for (var x = 0; x < grid.Width; x++)
{
var index = grid.GetTile(layer, x, y);
if (index >= 0)
if (index < 0)
continue;
// Autotiles are assembled from their neighbours exactly like in the game.
var autotile = Tileset.AutotileOf(index);
if (autotile == null)
DrawTextureRectRegion(Atlas, CellRect(x, y), TileRegion(index), modulate);
else if (textures.Tileset(autotile.Autotile) is { } block)
RpgAutotiles.Draw(this, block, CellRect(x, y), RpgAutotiles.QuartersAt(grid, layer, x, y, index), Tileset.TileSize, modulate);
}
}
}
+13 -1
View File
@@ -10,7 +10,8 @@ namespace Cthangover.Tools.RpgEditor
{
/// <summary>
/// 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
/// only — it is the file name), display name key, tileset, size, zoom, fixed
/// ambient light (empty: the time of day lights the map), spawn
/// and random encounters. The size is applied with a button, because a
/// smaller size cuts cells off. Encounter groups are written as
/// <c>slime; wolf,slime</c> — groups split by semicolons.
@@ -27,6 +28,7 @@ namespace Cthangover.Tools.RpgEditor
private readonly SpinBox widthSpin;
private readonly SpinBox heightSpin;
private readonly SpinBox zoomSpin;
private readonly LineEdit ambientEdit;
private readonly SpinBox spawnX;
private readonly SpinBox spawnY;
private readonly OptionButton spawnDir;
@@ -61,6 +63,7 @@ namespace Cthangover.Tools.RpgEditor
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");
ambientEdit = ToolUi.Field(this, "tools/rpg_editor/ambient", ToolUi.Text("#e0d2b8", OnAmbientChanged), "tools/rpg_editor/ambient_tip");
AddChild(ToolUi.Section("tools/rpg_editor/section_spawn"));
spawnX = ToolUi.Field(this, "tools/rpg_editor/x", ToolUi.Number(0, 255, 1, OnSpawnChanged));
@@ -94,6 +97,7 @@ namespace Cthangover.Tools.RpgEditor
widthSpin.Value = value.Grid.Width;
heightSpin.Value = value.Grid.Height;
zoomSpin.Value = map.Zoom;
ambientEdit.Text = map.Ambient ?? string.Empty;
spawnX.Value = map.Spawn.X;
spawnY.Value = map.Spawn.Y;
spawnDir.Select((int)map.Spawn.Dir);
@@ -123,6 +127,14 @@ namespace Cthangover.Tools.RpgEditor
Changed?.Invoke();
}
private void OnAmbientChanged(string text)
{
if (!Begin())
return;
document.Map.Ambient = string.IsNullOrWhiteSpace(text) ? null : text.Trim();
Changed?.Invoke();
}
private void OnTilesetPicked(long index)
{
if (!Begin() || index < 0 || index >= tilesets.Count)
+42 -9
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Cthangover.Core.UI.Theming;
using Cthangover.Mods.Rpg;
@@ -10,16 +11,20 @@ namespace Cthangover.Tools.RpgEditor
/// Brush picker of the editor's left column. For a tile layer it shows the
/// tileset atlas at one and a half times the tile size with the selected tile framed and the
/// solid tiles marked by a red corner; a click picks a tile, the tooltip
/// names it (index, tag, solid). For the collision layer it shows the three
/// collision codes instead.
/// names it (index, tag, solid). The tileset's autotiles follow in a row under the
/// atlas, each shown by its block's preview tile (<see cref="RpgAutotiles"/>). For the
/// collision layer it shows the three collision codes instead.
/// </summary>
public partial class RpgTilePalette : Control
{
private const float Scale = 1.5f;
private const float CodeHeight = 44f;
private const float AutotileGap = 8f;
private static readonly char[] Codes = { RpgCollision.Block, RpgCollision.Open, RpgCollision.Auto };
private readonly RpgTextures textures = new();
private readonly List<RpgTileInfo> autotiles = new();
private RpgTileset tileset;
private Texture2D atlas;
private bool collisionMode;
@@ -49,6 +54,9 @@ namespace Cthangover.Tools.RpgEditor
{
tileset = value;
atlas = image;
autotiles.Clear();
if (value != null)
autotiles.AddRange(value.Tiles.Where(t => !string.IsNullOrEmpty(t.Autotile)));
Relayout();
}
@@ -80,15 +88,23 @@ namespace Cthangover.Tools.RpgEditor
var cell = tileset.TileSize * Scale;
DrawTextureRect(atlas, new Rect2(Vector2.Zero, atlas.GetSize() * Scale), false);
foreach (var tile in tileset.Tiles.Where(t => t.Solid))
foreach (var tile in tileset.Tiles.Where(t => t.Solid && string.IsNullOrEmpty(t.Autotile)))
DrawSolidMark((Vector2)RpgTileSetBuilder.AtlasCoords(tileset, tile.Index) * cell, cell);
for (var i = 0; i < autotiles.Count; i++)
{
var position = (Vector2)RpgTileSetBuilder.AtlasCoords(tileset, tile.Index) * cell;
DrawColoredPolygon(new[] { position, position + new Vector2(cell * 0.25f, 0f), position + new Vector2(0f, cell * 0.25f) }, new Color(0.9f, 0.2f, 0.2f, 0.9f));
if (textures.Tileset(autotiles[i].Autotile) is { } block)
DrawTextureRectRegion(block, new Rect2(Slot(i), new Vector2(cell, cell)), RpgAutotiles.PreviewRect(tileset.TileSize));
if (autotiles[i].Solid)
DrawSolidMark(Slot(i), cell);
}
var selected = (Vector2)RpgTileSetBuilder.AtlasCoords(tileset, SelectedTile) * cell;
var slot = autotiles.FindIndex(t => t.Index == SelectedTile);
var selected = slot >= 0 ? Slot(slot) : (Vector2)RpgTileSetBuilder.AtlasCoords(tileset, SelectedTile) * cell;
DrawRect(new Rect2(selected, new Vector2(cell, cell)), UiTokens.AccentBright, false, 3f);
}
private void DrawSolidMark(Vector2 position, float cell)
=> DrawColoredPolygon(new[] { position, position + new Vector2(cell * 0.25f, 0f), position + new Vector2(0f, cell * 0.25f) }, new Color(0.9f, 0.2f, 0.2f, 0.9f));
private void DrawCode(int i)
{
var rect = new Rect2(0f, i * (CodeHeight + 6f), Size.X, CodeHeight);
@@ -110,7 +126,7 @@ namespace Cthangover.Tools.RpgEditor
if (collisionMode)
CustomMinimumSize = new Vector2(200f, Codes.Length * (CodeHeight + 6f));
else
CustomMinimumSize = atlas != null ? atlas.GetSize() * Scale : Vector2.Zero;
CustomMinimumSize = atlas != null && tileset != null ? new Vector2(atlas.GetWidth() * Scale, AutotileTop + AutotileRows * tileset.TileSize * Scale) : Vector2.Zero;
TooltipText = string.Empty;
QueueRedraw();
}
@@ -137,7 +153,7 @@ namespace Cthangover.Tools.RpgEditor
else if (tileset != null && atlas != null)
{
var index = IndexAt(button.Position);
if (index >= 0 && index < RpgTileSetBuilder.TileCount(tileset, atlas))
if (index >= 0 && (index < RpgTileSetBuilder.TileCount(tileset, atlas) || tileset.AutotileOf(index) != null))
{
SelectedTile = index;
TileSelected?.Invoke(index);
@@ -147,9 +163,25 @@ namespace Cthangover.Tools.RpgEditor
AcceptEvent();
}
private float AutotileTop => atlas == null ? 0f : atlas.GetHeight() * Scale + (autotiles.Count > 0 ? AutotileGap : 0f);
private int AutotileRows => (autotiles.Count + Math.Max(1, tileset.Columns) - 1) / Math.Max(1, tileset.Columns);
private Vector2 Slot(int i)
{
var cell = tileset.TileSize * Scale;
var columns = Math.Max(1, tileset.Columns);
return new Vector2(i % columns * cell, AutotileTop + i / columns * cell);
}
private int IndexAt(Vector2 position)
{
var cell = tileset.TileSize * Scale;
if (autotiles.Count > 0 && position.Y >= AutotileTop)
{
var slot = (int)((position.Y - AutotileTop) / cell) * Math.Max(1, tileset.Columns) + (int)(position.X / cell);
return slot >= 0 && slot < autotiles.Count && position.X < tileset.Columns * cell ? autotiles[slot].Index : -1;
}
var column = (int)(position.X / cell);
var row = (int)(position.Y / cell);
return column < 0 || column >= tileset.Columns || row < 0 ? -1 : row * tileset.Columns + column;
@@ -159,7 +191,8 @@ namespace Cthangover.Tools.RpgEditor
{
var info = tileset.Tiles.FirstOrDefault(t => t.Index == index);
var solid = info?.Solid == true ? " · " + TranslationServer.Translate("tools/rpg_editor/solid_tile") : string.Empty;
return $"#{index} {info?.Tag}{solid}";
var auto = string.IsNullOrEmpty(info?.Autotile) ? string.Empty : " · " + TranslationServer.Translate("tools/rpg_editor/autotile");
return $"#{index} {info?.Tag}{solid}{auto}";
}
}
}