Event light, map ambient, DSL highlighting, autotile palette, action buttons on top
This commit is contained in:
1 parent
ac5b21e4c3
commit
dbe46a05bf
12 files changed
+146
-19
No files matched your search
+11
-7
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user