diff --git a/manifest.json b/manifest.json
index be820cb..e27943c 100644
--- a/manifest.json
+++ b/manifest.json
@@ -3,7 +3,7 @@
"name": "RPG World",
"version": "1.0.0",
"author": "ct",
- "description": "Top-down tile world in the spirit of RPG Maker on top of the novel engine: maps of square tiles in three layers (back, main, front) with a collision layer, walking on the grid with WASD or the arrows, events that talk, teleport or start battles with the action key E/F or on touch, random encounters. A map is a logical scene, so entry scenarios, ambience, saving and the dialog box work on maps unchanged; battles run on the turn-based platform with the game's battle core and return to the same cell. Scenario actions: rpg.battle, rpg.teleport.",
+ "description": "Top-down tile world in the spirit of RPG Maker on top of the 2D engine mods: maps of square tiles in three layers (back, main, front) with a collision layer, walking on the grid with WASD or the arrows, events that talk, teleport or start battles with the action key E/F or on touch, random encounters. A map is a logical scene, so entry scenarios, ambience, saving and the dialog box work on maps unchanged; battles run on the turn-based platform with the game's battle core and return to the same cell. Scenario actions: rpg.battle, rpg.teleport.",
"category": "gameplay",
"sources": [
"src/**/*.cs"
diff --git a/src/Data/RpgMap.cs b/src/Data/RpgMap.cs
index 4552d9f..143fac7 100644
--- a/src/Data/RpgMap.cs
+++ b/src/Data/RpgMap.cs
@@ -9,7 +9,7 @@ namespace Cthangover.Mods.Rpg
/// scene that shows it: entering scene X (any switch_scene X, a
/// teleport, the return from a battle, loading a save) mounts map X
/// over the shell. Because the scene stays an ordinary scene, everything the
- /// novel engine attaches to scenes works on a map unchanged — entry
+ /// engine attaches to scenes works on a map unchanged — entry
/// scenarios, ambience and music from scenes/X.json, the default
/// background (which also becomes the backdrop of battles started here),
/// saving rules.
diff --git a/src/World/RpgWorldHost.cs b/src/World/RpgWorldHost.cs
index 3cc942c..75290b9 100644
--- a/src/World/RpgWorldHost.cs
+++ b/src/World/RpgWorldHost.cs
@@ -14,7 +14,7 @@ namespace Cthangover.Mods.Rpg
/// It follows the scene lifecycle: entering a scene whose name is a map id
/// mounts that map () and shows the dark backdrop;
/// any other scene leaves the host empty and invisible, so a game can mix
- /// novel scenes and map scenes freely. The shell is rebuilt after battles,
+ /// picture-and-dialogue scenes and map scenes freely. The shell is rebuilt after battles,
/// so the host is too; the position survives in .
///
/// is the host of the live shell, used by teleports
diff --git a/test_suites/walk/src/RpgWalkTestDriver.cs b/test_suites/walk/src/RpgWalkTestDriver.cs
index 7f3649a..f4452cc 100644
--- a/test_suites/walk/src/RpgWalkTestDriver.cs
+++ b/test_suites/walk/src/RpgWalkTestDriver.cs
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using Cthangover.Core.Battle;
using Cthangover.Core.Bootstrap;
+using Cthangover.Core.Characters;
+using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.Games;
using Cthangover.Core.Scenes;
using Cthangover.Core.Settings;
using Cthangover.Core.Utils;
@@ -26,6 +29,7 @@ namespace Cthangover.Mods.Rpg.Tests
private const string Village = "rpg_village";
private const string House = "rpg_elder_house";
private const string WinFlag = "rpg_test_win";
+ private const string SaveSlot = "slot_9901";
private readonly string shotsDir;
private readonly List failures = new();
@@ -83,6 +87,7 @@ namespace Cthangover.Mods.Rpg.Tests
return;
Check(GameData.Instance.Runtime.CharacterData.Characters.ContainsKey("rpg_hero"), "party.add put the hero into the party");
Check(View.Player.Cell == new Vector2I(15, 14), $"player at the spawn (15,14), got {View.Player.Cell}");
+ CheckActionTexts();
Shot("village");
Press(Key.W, true);
Next();
@@ -188,11 +193,46 @@ namespace Cthangover.Mods.Rpg.Tests
Godot.Engine.TimeScale = 1;
Check(View.Player.Cell == new Vector2I(6, 8), $"the battle returns to the same cell, got {View.Player.Cell}");
Check(GameData.Instance.Runtime.Flags.TryGetValue(WinFlag, out var won) && won == "1", "the win flag is set after the victory");
+ // The menu's path: save in the game, go to the main menu, load.
+ SaveService.Save(SaveSlot);
+ GameSession.OpenMainMenu(GetTree());
+ Next();
+ return;
+ case 16:
+ if (RpgWorldHost.Current != null || ++waitTicks < 5)
+ return;
+ Check(GameSession.LoadAndEnter(this, SaveSlot), "the save loads");
+ Next();
+ return;
+ case 17:
+ if (View?.Map?.ID != House || GodotSceneService.IsTransitioning || ++waitTicks < 5)
+ return;
+ Shot("loaded");
+ var dialog = RpgInput.FindDialogBox();
+ Check(dialog != null && !dialog.IsRunning && !dialog.IsVisibleInTree(), $"no dialog box after loading (running={dialog?.IsRunning}, visible={dialog?.IsVisibleInTree()})");
+ Check(View.Player.Cell == new Vector2I(6, 8), $"the loaded game puts the player on the saved cell, got {View.Player.Cell}");
+ SaveService.DeleteSave(SaveSlot);
Finish();
return;
}
}
+ ///
+ /// The battle shows the names and descriptions of the hero's action cards:
+ /// their keys must be translated by a mod of this game, not only by
+ /// another game's content mod.
+ ///
+ private void CheckActionTexts()
+ {
+ var hero = CharacterFactory.Instance.Get("rpg_hero");
+ Check(hero?.Actions?.Count > 0, "the hero has action cards");
+ foreach (var action in hero?.Actions ?? new List())
+ {
+ foreach (var key in new[] { action.Name, action.Description })
+ Check(!string.IsNullOrEmpty(key) && TranslationServer.Translate(key) != key, $"action text '{key}' is translated");
+ }
+ }
+
/// Advances a running dialog with the action key; true while it is still running.
private bool AdvanceDialog()
{