diff --git a/locale/ui_en.properties b/locale/ui_en.properties index d9ee13d..c333178 100644 --- a/locale/ui_en.properties +++ b/locale/ui_en.properties @@ -1,7 +1,7 @@ mod_rpg_editor_name = RPG Map Editor mod_rpg_editor_desc = Editor of tile maps for the RPG world: three tile layers, the collision layer and map events, with undo and a live preview in the running game. tools/rpg_editor/title = RPG Map Editor -tools/rpg_editor/hint = LMB — paint · RMB — erase · Alt+LMB — pick tile · events: click — select/drag, double-click — add, Del — delete · 1–5 layers · B/E/G brush · Ctrl+wheel zoom · Ctrl+Z/Y · Ctrl+S +tools/rpg_editor/hint = LMB — paint · RMB — erase · Alt+LMB — pick tile · events: click — select/drag, double-click — add, Del — delete · 1–5 layers · B/E/G brush · wheel/Shift+wheel, MMB drag — scroll · Ctrl+wheel zoom · Ctrl+Z/Y · Ctrl+S tools/rpg_editor/layer_back = Back tools/rpg_editor/layer_main = Main tools/rpg_editor/layer_front = Front @@ -77,3 +77,28 @@ tools/rpg_editor/collision_auto = Auto — from the tiles tools/rpg_editor/problem_tileset = Map "{0}" has no tileset. tools/rpg_editor/problem_event_id = The event at {0} has no id. tools/rpg_editor/problem_event_duplicate = Two events share the id "{0}". +tools/rpg_editor/tab_tiles = Tiles +tools/rpg_editor/tab_characters = Characters +tools/rpg_editor/character_search = Search by name or id +tools/rpg_editor/place_npc = Place as NPC +tools/rpg_editor/place_npc_tip = Put the character on the map as an event talking on E/F; write its lines in the scenario lines +tools/rpg_editor/place_enemy = Place as enemy +tools/rpg_editor/place_enemy_tip = Put the character on the map as an enemy: E/F starts a battle with it, it vanishes once beaten +tools/rpg_editor/characters_hint = Pick a character, press a button and click a cell on the map (RMB cancels). The charset is found by the character's Charset field or by its portrait path. +tools/rpg_editor/place_status = Click the cell to put “{0}” on (RMB cancels) +tools/rpg_editor/place_cancelled = Placing the character cancelled +tools/rpg_editor/placed_npc = “{0}” placed as an NPC: event {1} +tools/rpg_editor/placed_enemy = “{0}” placed as an enemy: event {1} +tools/rpg_editor/icon = Icon +tools/rpg_editor/icon_tip = Picture in the items group (like an item's Sprite) when there is neither a charset nor a tile +tools/rpg_editor/tab_items = Items +tools/rpg_editor/item_search = Search by name or id +tools/rpg_editor/items_hint = Pick an item, set the count, press "Place" and click a cell (RMB cancels). The player picks it up by stepping on the cell; then it is gone. +tools/rpg_editor/item_count = Count +tools/rpg_editor/item_count_tip = How many the player gets +tools/rpg_editor/place_item = Place +tools/rpg_editor/place_item_tip = Lay the item on the map: stepping on the cell picks it up +tools/rpg_editor/item_found_line = Found: {0} ×{1} +tools/rpg_editor/placed_item = “{0}” lies on the map: event {1} +tools/rpg_editor/picker_no_look = No map picture found, a marker is shown +tools/rpg_editor/problem_event_locked = Event id {0} is taken by an event of another file or mod — pick another one diff --git a/locale/ui_ru.properties b/locale/ui_ru.properties index ab3799a..66d8ce1 100644 --- a/locale/ui_ru.properties +++ b/locale/ui_ru.properties @@ -1,7 +1,7 @@ mod_rpg_editor_name = Редактор RPG-карт mod_rpg_editor_desc = Редактор тайловых карт RPG-мира: три слоя тайлов, слой коллизий и события на карте, с отменой и живым предпросмотром в запущенной игре. tools/rpg_editor/title = Редактор RPG-карт -tools/rpg_editor/hint = ЛКМ — рисовать · ПКМ — стирать · Alt+ЛКМ — взять тайл · события: клик — выбрать/тащить, двойной клик — добавить, Del — удалить · 1–5 слои · B/E/G кисть · Ctrl+колесо масштаб · Ctrl+Z/Y · Ctrl+S +tools/rpg_editor/hint = ЛКМ — рисовать · ПКМ — стирать · Alt+ЛКМ — взять тайл · события: клик — выбрать/тащить, двойной клик — добавить, Del — удалить · 1–5 слои · B/E/G кисть · колесо/Shift+колесо, СКМ — прокрутка · Ctrl+колесо масштаб · Ctrl+Z/Y · Ctrl+S tools/rpg_editor/layer_back = Фон tools/rpg_editor/layer_main = Основной tools/rpg_editor/layer_front = Передний @@ -54,7 +54,7 @@ tools/rpg_editor/event_id = Id tools/rpg_editor/sprite = Чарсет tools/rpg_editor/sprite_tip = Путь к картинке в группе charsets: 3 кадра × 4 направления tools/rpg_editor/tile = Тайл -tools/rpg_editor/tile_tip = Номер тайла, если чарсета нет; -1 — ничего +tools/rpg_editor/tile_tip = Номер тайла, если чарсета нет; -1 — нет тайла tools/rpg_editor/trigger = Запуск tools/rpg_editor/trigger_action = Клавиша действия (E/F) tools/rpg_editor/trigger_touch = Шаг на клетку @@ -77,3 +77,28 @@ tools/rpg_editor/collision_auto = Авто — по тайлам tools/rpg_editor/problem_tileset = У карты «{0}» не выбран тайлсет. tools/rpg_editor/problem_event_id = У события в клетке {0} нет id. tools/rpg_editor/problem_event_duplicate = Два события с одинаковым id «{0}». +tools/rpg_editor/tab_tiles = Тайлы +tools/rpg_editor/tab_characters = Персонажи +tools/rpg_editor/character_search = Поиск по имени или id +tools/rpg_editor/place_npc = На карту: NPC +tools/rpg_editor/place_npc_tip = Поставить персонажа как событие с разговором на E/F; реплики пишутся в «Строках сценария» +tools/rpg_editor/place_enemy = На карту: враг +tools/rpg_editor/place_enemy_tip = Поставить персонажа как врага: E/F начинает с ним бой, после победы он исчезает +tools/rpg_editor/characters_hint = Выберите персонажа, нажмите кнопку и кликните клетку на карте (ПКМ — отмена). Чарсет ищется по полю Charset персонажа или по пути его портрета. +tools/rpg_editor/place_status = Кликните клетку, куда поставить «{0}» (ПКМ — отмена) +tools/rpg_editor/place_cancelled = Постановка персонажа отменена +tools/rpg_editor/placed_npc = «{0}» поставлен как NPC: событие {1} +tools/rpg_editor/placed_enemy = «{0}» поставлен как враг: событие {1} +tools/rpg_editor/icon = Иконка +tools/rpg_editor/icon_tip = Картинка из группы items (как Sprite предмета), если нет ни чарсета, ни тайла +tools/rpg_editor/tab_items = Предметы +tools/rpg_editor/item_search = Поиск по названию или id +tools/rpg_editor/items_hint = Выберите предмет, задайте количество, нажмите «На карту» и кликните клетку (ПКМ — отмена). Игрок подбирает предмет, наступив на клетку; потом предмет исчезает. +tools/rpg_editor/item_count = Количество +tools/rpg_editor/item_count_tip = Сколько штук получит игрок +tools/rpg_editor/place_item = На карту +tools/rpg_editor/place_item_tip = Положить предмет на карту: шаг на клетку подбирает его +tools/rpg_editor/item_found_line = Найдено: {0} ×{1} +tools/rpg_editor/placed_item = «{0}» лежит на карте: событие {1} +tools/rpg_editor/picker_no_look = Картинка для карты не найдена, будет маркер +tools/rpg_editor/problem_event_locked = Id события {0} уже занят событием из другого файла или мода — выберите другой diff --git a/manifest.json b/manifest.json index 758367c..799a081 100644 --- a/manifest.json +++ b/manifest.json @@ -13,6 +13,8 @@ ], "depends": [ "rpg>=1", + "characters_base>=1", + "items_base>=1", "editor_tools>=1", "ui_base>=1" ], diff --git a/src/RpgCharacterCatalog.cs b/src/RpgCharacterCatalog.cs new file mode 100644 index 0000000..4446135 --- /dev/null +++ b/src/RpgCharacterCatalog.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cthangover.Core.Characters; +using Cthangover.Core.Mods.Resolvers; +using Cthangover.Mods.Rpg; +using Godot; + +namespace Cthangover.Tools.RpgEditor +{ + /// + /// The characters the running game knows (Kind: character of every + /// loaded mod), sorted by display name, for placing them on a map. + /// + /// A character has no map look of its own, so its charset is found by name: + /// an explicit "Charset" field of the character (kept by the + /// character's extra properties; the character editor sets it) wins, otherwise the + /// portrait path in the characters group (characters/village/slime or + /// village/slime → village/slime), as village_pack names both. Only charsets that exist are used: the texture + /// loader logs an error for a missing one. The row picture is the middle frame + /// of the "down" row, how the character stands on the map. + /// + public static class RpgCharacterCatalog + { + private const string ImageGroup = "characters"; + + /// Reads the character list anew. + public static List Load(RpgTextures textures) + { + var charsets = RpgItemCatalog.GroupFiles(RpgTextures.CharsetGroup); + return (Entities.Collect()?.Values ?? Enumerable.Empty()) + .Where(info => !string.IsNullOrEmpty(info.ID)) + .Select(info => Entry(info, CharsetOf(info, charsets), textures)) + .OrderBy(entry => entry.Name, StringComparer.CurrentCultureIgnoreCase) + .ToList(); + } + + private static RpgPickerEntry Entry(CharacterInfo info, string charset, RpgTextures textures) => new() + { + Id = info.ID, + Name = string.IsNullOrEmpty(info.Name) ? info.ID : TranslationServer.Translate(info.Name).ToString(), + Look = charset, + Icon = FrontFrame(charset == null ? null : textures.Charset(charset)) + }; + + private static string CharsetOf(CharacterInfo info, HashSet charsets) + { + object explicitCharset = null; + info.ExtraProperties?.TryGetValue("Charset", out explicitCharset); + var image = info.Image ?? string.Empty; + if (image.StartsWith(ImageGroup + "/", StringComparison.OrdinalIgnoreCase)) + image = image.Substring(ImageGroup.Length + 1); + var candidates = new[] { explicitCharset?.ToString(), image }; + return candidates.FirstOrDefault(path => !string.IsNullOrEmpty(path) && charsets.Contains(path)); + } + + private static Texture2D FrontFrame(Texture2D image) + { + if (image == null) + return null; + var frame = new Vector2(image.GetWidth() / 3f, image.GetHeight() / 4f); + return new AtlasTexture { Atlas = image, Region = new Rect2(new Vector2(frame.X, 0f), frame) }; + } + } +} diff --git a/src/RpgEntityPicker.cs b/src/RpgEntityPicker.cs new file mode 100644 index 0000000..b759461 --- /dev/null +++ b/src/RpgEntityPicker.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cthangover.Mods.Interface.Theming; +using Cthangover.Tools.Common; +using Godot; + +namespace Cthangover.Tools.RpgEditor +{ + /// + /// A tab of the editor's left column listing entities of the game (the + /// characters, the items) with a picture, a search over name and id and + /// buttons that act on the selected row. The window decides what the + /// entries are () and what the buttons do + /// (); placing on the map is then a click on a cell. + /// + /// The list is read again on every (the window calls it + /// whenever the tab is opened), so entities added to the game's data while + /// the editor is open show up. + /// + public partial class RpgEntityPicker : VBoxContainer + { + private readonly List shown = new(); + private readonly List