Map editor: wheel/MMB scrolling, characters and items tabs to place NPCs, enemies and pickups, events saved to map_events with other files and mods shown locked, LF output
This commit is contained in:
1 parent
eaf58c0d38
commit
1d6904bb16
14 files changed
+863
-67
No files matched your search
@@ -16,10 +16,13 @@ namespace Cthangover.Tools.RpgEditor.Tests
|
||||
/// <summary>
|
||||
/// Drives the RPG map editor through its controls like an author: layer
|
||||
/// buttons are pressed, the palette and the canvas get mouse events through
|
||||
/// their <c>gui_input</c> signal, Ctrl+Z goes into the window. That window is
|
||||
/// their <c>gui_input</c> signal, Ctrl+Z goes into the window, a slime found in
|
||||
/// the characters tab is placed as an enemy and branches from the items tab as
|
||||
/// a pickup. That window is
|
||||
/// freed without saving, so the demo maps never change. A second window then
|
||||
/// creates a new map through the file selector's "+" and saves it: the game must
|
||||
/// know the map's scene at once (no mod reload); its files are deleted again.
|
||||
/// know the map's scene and its event (saved apart, into map_events) at once,
|
||||
/// with no mod reload; its files are deleted again.
|
||||
/// Result line: <c>[TEST_RESULT] name=rpg_editor_edit passed=N failed=M</c>.
|
||||
/// </summary>
|
||||
public partial class RpgEditorTestDriver : Node
|
||||
@@ -31,6 +34,8 @@ namespace Cthangover.Tools.RpgEditor.Tests
|
||||
|
||||
private static readonly Vector2I PaintCell = new(3, 5);
|
||||
private static readonly Vector2I EventCell = new(2, 6);
|
||||
private static readonly Vector2I CharacterCell = new(4, 6);
|
||||
private static readonly Vector2I ItemCell = new(5, 6);
|
||||
|
||||
private readonly string shotsDir;
|
||||
private readonly List<string> failures = new();
|
||||
@@ -95,6 +100,8 @@ namespace Cthangover.Tools.RpgEditor.Tests
|
||||
if (canvas?.Document == null || canvas.Tileset == null)
|
||||
return;
|
||||
Check(canvas.Document.Map.ID.StartsWith("rpg_"), $"the editor opened a demo map ({canvas.Document.Map.ID})");
|
||||
Check(canvas.Document.Events.Count > 0 && canvas.Document.LockedEvents.Count == 0,
|
||||
$"its events come from the mod's map_events file ({canvas.Document.Events.Count} own, {canvas.Document.LockedEvents.Count} locked)");
|
||||
Shot("editor");
|
||||
Press("tools/rpg_editor/layer_main");
|
||||
Check(canvas.ActiveLayer == RpgEditLayer.Main, "the layer button switches the brush to the main layer");
|
||||
@@ -110,14 +117,15 @@ namespace Cthangover.Tools.RpgEditor.Tests
|
||||
window.PushInput(new InputEventKey { Keycode = Key.Z, PhysicalKeycode = Key.Z, CtrlPressed = true, Pressed = true });
|
||||
Check(canvas.Document.Grid.GetTile(RpgLayer.Main, PaintCell.X, PaintCell.Y) == before, "Ctrl+Z undoes the stroke");
|
||||
Press("tools/rpg_editor/layer_events");
|
||||
eventsBefore = canvas.Document.Map.Events.Count;
|
||||
eventsBefore = canvas.Document.Events.Count;
|
||||
ClickCanvas(EventCell, true);
|
||||
Check(canvas.Document.Map.Events.Count == eventsBefore + 1 && canvas.SelectedEvent != null, "a double click adds and selects an event");
|
||||
Check(canvas.Document.Events.Count == eventsBefore + 1 && canvas.SelectedEvent != null, "a double click adds and selects an event");
|
||||
step++;
|
||||
return;
|
||||
case 3:
|
||||
// Screenshots one tick after the action, when the frame shows it.
|
||||
Shot("events");
|
||||
PlaceSlime();
|
||||
Press("tools/rpg_editor/layer_collision");
|
||||
step++;
|
||||
return;
|
||||
@@ -149,8 +157,12 @@ namespace Cthangover.Tools.RpgEditor.Tests
|
||||
return;
|
||||
try
|
||||
{
|
||||
canvas.Document.AddEvent(1, 1).Sprite = "village/elder";
|
||||
Press("tools/common/save");
|
||||
Check(File.Exists(ModFileService.GetWritablePath(newMapMod, "scenes/" + NewMap + ".json")), "the first save of a new map writes its scene");
|
||||
Check(!ModFileService.ReadText(newMapMod, "maps/" + NewMap + ".json").Contains("\"Events\""), "the map file holds no events");
|
||||
Check(RpgMapEvents.Of(NewMap).Any(e => e.Id == "event_1" && e.ModId == newMapMod && e.File == RpgMapDocument.EventsPath(NewMap)),
|
||||
"the event is saved into map_events and the game reads it at once");
|
||||
Check(GetTree().Root.GetNodeOrNull<SceneManager>("SceneManager")?.HasScene(NewMap) == true, "the game knows the new map's scene without a mod reload");
|
||||
}
|
||||
finally
|
||||
@@ -164,16 +176,74 @@ namespace Cthangover.Tools.RpgEditor.Tests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Characters tab: search "slime", pick it, "place as enemy", click a cell; items tab: two bundles of branches.</summary>
|
||||
private void PlaceSlime()
|
||||
{
|
||||
var picker = Pick("Characters", 1, "slime", "rpg_slime");
|
||||
if (picker == null)
|
||||
return;
|
||||
Check(picker.Selected.Look == "village/slime" && picker.Selected.Icon != null, $"the slime is picked with its charset ({picker.Selected.Look})");
|
||||
|
||||
var count = canvas.Document.Events.Count;
|
||||
Press("tools/rpg_editor/place_enemy");
|
||||
Check(canvas.Placing && canvas.ActiveLayer == RpgEditLayer.Events, "placing waits for a cell on the events layer");
|
||||
ClickCanvas(CharacterCell, false);
|
||||
var placed = canvas.SelectedEvent;
|
||||
Check(canvas.Document.Events.Count == count + 1 && placed?.Sprite == "village/slime" && placed.X == CharacterCell.X && placed.Y == CharacterCell.Y,
|
||||
"a click on the map puts the slime there");
|
||||
Check(placed?.Commands?.FirstOrDefault()?.StartsWith("action rpg.battle enemies=rpg_slime ") == true && !canvas.Placing, "the placed enemy starts a battle with it");
|
||||
|
||||
picker = Pick("Items", 2, "branches", "rpg_demo/branches");
|
||||
if (picker == null)
|
||||
return;
|
||||
Check(picker.Selected.Look == "rpg_demo/branch_bundle" && picker.Selected.Icon != null, $"the branches are picked with their picture ({picker.Selected.Look})");
|
||||
var countSpin = picker.FindChildren("*", "", true, false).OfType<SpinBox>().First();
|
||||
countSpin.Value = 2;
|
||||
Press("tools/rpg_editor/place_item");
|
||||
ClickCanvas(ItemCell, false);
|
||||
placed = canvas.SelectedEvent;
|
||||
Check(placed?.Icon == "rpg_demo/branch_bundle" && placed.X == ItemCell.X && placed.Trigger == RpgEventTrigger.Touch && placed.Solid == false,
|
||||
"a click on the map lays the branches there, picked up by stepping on them");
|
||||
Check(placed?.Commands?.Contains("action item.add id=rpg_demo/branches count=2") == true, "the pickup gives the chosen count");
|
||||
}
|
||||
|
||||
/// <summary>Opens the side tab, types the query, checks every row matches it and selects <paramref name="id"/>; null when it is not listed.</summary>
|
||||
private RpgEntityPicker Pick(string name, int tab, string query, string id)
|
||||
{
|
||||
window.FindChildren("*", "", true, false).OfType<TabContainer>().First(t => t.GetTabCount() == 3).CurrentTab = tab;
|
||||
var picker = window.FindChildren("*", "", true, false).OfType<RpgEntityPicker>().First(p => p.Name == name);
|
||||
var search = picker.FindChildren("*", "", true, false).OfType<LineEdit>().First(e => e.GetParent() == picker);
|
||||
search.Text = query;
|
||||
search.EmitSignal(LineEdit.SignalName.TextChanged, query);
|
||||
var list = picker.FindChildren("*", "", true, false).OfType<ItemList>().First();
|
||||
Check(list.ItemCount >= 1 && Enumerable.Range(0, list.ItemCount).All(i => list.GetItemText(i).Contains(query, System.StringComparison.OrdinalIgnoreCase)),
|
||||
$"the {name} search keeps only '{query}' ({list.ItemCount} rows)");
|
||||
for (var i = 0; i < list.ItemCount; i++)
|
||||
{
|
||||
if (!list.GetItemText(i).EndsWith("· " + id))
|
||||
continue;
|
||||
list.Select(i);
|
||||
list.EmitSignal(ItemList.SignalName.ItemSelected, i);
|
||||
return picker;
|
||||
}
|
||||
Fail($"'{id}' is not in the {name} list");
|
||||
return null;
|
||||
}
|
||||
|
||||
private ModFileSelector Selector() => window.FindChildren("*", "", true, false).OfType<ModFileSelector>().First();
|
||||
|
||||
private void DeleteNewMapFiles()
|
||||
{
|
||||
foreach (var file in new[] { "maps/" + NewMap + ".json", "scenes/" + NewMap + ".json" })
|
||||
foreach (var file in new[] { "maps/" + NewMap + ".json", "scenes/" + NewMap + ".json", RpgMapDocument.EventsPath(NewMap) })
|
||||
{
|
||||
var path = ModFileService.GetWritablePath(newMapMod, file);
|
||||
if (path != null && File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
var eventsDir = Path.GetDirectoryName(ModFileService.GetWritablePath(newMapMod, RpgMapDocument.EventsPath(NewMap)) ?? "");
|
||||
if (!string.IsNullOrEmpty(eventsDir) && Directory.Exists(eventsDir) && !Directory.EnumerateFileSystemEntries(eventsDir).Any())
|
||||
Directory.Delete(eventsDir);
|
||||
RpgMapEvents.Invalidate();
|
||||
}
|
||||
|
||||
private void Press(string key)
|
||||
|
||||
Reference in new issue
Block a user