From 0e89c5fa8514572b800cde53b790afe965898674 Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Sun, 4 Oct 2026 13:43:32 +0200 Subject: [PATCH] Import items --- data/item_data.gd | 19 +++++++ data/item_data.gd.uid | 1 + editor/api_text.gd | 12 ++++ editor/importer_dock.gd | 48 ++++++++++++++++ editor/item_importer.gd | 108 ++++++++++++++++++++++++++++++++++++ editor/item_importer.gd.uid | 1 + settings.gd | 1 + 7 files changed, 190 insertions(+) create mode 100644 data/item_data.gd create mode 100644 data/item_data.gd.uid create mode 100644 editor/item_importer.gd create mode 100644 editor/item_importer.gd.uid diff --git a/data/item_data.gd b/data/item_data.gd new file mode 100644 index 0000000..a6034e3 --- /dev/null +++ b/data/item_data.gd @@ -0,0 +1,19 @@ +# data/item_data.gd +@tool +class_name ItemData +extends Resource + +@export var id : int +@export var api_name : String +@export var category : String # "evolution", "mega-stones", "medicine", "berries"... +@export var names : Dictionary = {} +@export var descriptions : Dictionary = {} +@export var sprite : Texture2D +@export var cost : int +@export var fling_power : int + +func get_display_name() -> String: + return L10n.pick(names, api_name.capitalize()) + +func get_description() -> String: + return L10n.pick(descriptions) diff --git a/data/item_data.gd.uid b/data/item_data.gd.uid new file mode 100644 index 0000000..83db146 --- /dev/null +++ b/data/item_data.gd.uid @@ -0,0 +1 @@ +uid://lhn8jhdqstn7 diff --git a/editor/api_text.gd b/editor/api_text.gd index 7e7d0d1..b2fe2bf 100644 --- a/editor/api_text.gd +++ b/editor/api_text.gd @@ -17,3 +17,15 @@ static func flavors(entries : Array, languages : PackedStringArray) -> Dictionar if lang in languages: out[lang] = String(e["flavor_text"]).replace("\n", " ").replace("\f", " ") return out + +## Comme flavors(), mais pour les objets : la clé du texte est "text", pas "flavor_text". +static func item_flavors(entries : Array, languages : PackedStringArray) -> Dictionary: + var out := {} + for e in entries: + var lang_field : Variant = e.get("language") + if !(lang_field is Dictionary): continue + var lang : String = lang_field.get("name", "") + if lang in languages: + var text : String = String(e.get("text", "")).replace("\n", " ").replace("\f", " ") + out[lang] = text + return out diff --git a/editor/importer_dock.gd b/editor/importer_dock.gd index 8fa09f7..4f3a335 100644 --- a/editor/importer_dock.gd +++ b/editor/importer_dock.gd @@ -12,6 +12,11 @@ var _progress : ProgressBar var _log : RichTextLabel var _busy : bool = false +var _item_importer_ref : PokeApiItemImporter +var _category_list : ItemList +var _btn_load_categories : Button +var _btn_import_items : Button + func _ready() -> void: custom_minimum_size = Vector2(260, 0) @@ -48,6 +53,22 @@ func _ready() -> void: _log.scroll_following = true _log.selection_enabled = true add_child(_log) + add_child(HSeparator.new()) + var item_label := Label.new() + item_label.text = "Objets, par catégorie :" + add_child(item_label) + + _btn_load_categories = Button.new() + _btn_load_categories.text = "Lister les catégories" + _btn_load_categories.pressed.connect(_on_load_categories) + add_child(_btn_load_categories) + + _category_list = ItemList.new() + _category_list.custom_minimum_size = Vector2(0, 150) + _category_list.select_mode = ItemList.SELECT_MULTI + add_child(_category_list) + + _add_button("Importer les objets sélectionnés", _on_import_items) func _add_button(text : String, callback : Callable) -> void: var b := Button.new() @@ -95,6 +116,33 @@ func _on_import_pokemon() -> void: await importer.run(tokens) _set_busy(false) +func _on_load_categories() -> void: + if _busy: return + _set_busy(true) + var importer := PokeApiItemImporter.new(_client, editor_filesystem, _overwrite.button_pressed) + var categories := await importer.list_categories() + _category_list.clear() + for c in categories: + _category_list.add_item("%s (%d)" % [c["name"], c["count"]]) + _category_list.set_item_metadata(_category_list.item_count - 1, c["name"]) + _print("%d catégories chargées." % categories.size()) + _set_busy(false) + +func _on_import_items() -> void: + if _busy: return + var selected : Array = [] + for idx in _category_list.get_selected_items(): + selected.append(_category_list.get_item_metadata(idx)) # nom brut, sans le "(N)" + if selected.is_empty(): + _print("Sélectionne au moins une catégorie.") + return + + _set_busy(true) + var importer := PokeApiItemImporter.new(_client, editor_filesystem, _overwrite.button_pressed) + importer.message.connect(_print) + await importer.run(selected) + _set_busy(false) + func _parse_input(text : String) -> Array: var out : Array = [] for tok in text.split(",", false): diff --git a/editor/item_importer.gd b/editor/item_importer.gd new file mode 100644 index 0000000..4c53bd6 --- /dev/null +++ b/editor/item_importer.gd @@ -0,0 +1,108 @@ +# editor/item_importer.gd +@tool +class_name PokeApiItemImporter +extends RefCounted + +signal message(text : String) + +var _client : PokeApiClient +var _fs : EditorFileSystem +var _overwrite : bool +var _languages : PackedStringArray +var _pending : Array = [] # objets récupérés, en attente de sauvegarde des sprites + +func _init(client : PokeApiClient, fs : EditorFileSystem, overwrite : bool) -> void: + _client = client + _fs = fs + _overwrite = overwrite + _languages = PokeApiSettings.languages() + +## Retourne un tableau de {"name": String, "count": int}, trié par nom. +func list_categories() -> Array: + var list = await _client.get_json(PokeApiClient.API + "item-category?limit=100") + if list == null: return [] + + var names : Array = [] + for r in PokeApiJson.get_array(list, "results"): + names.append(PokeApiJson.get_str(r, "name")) + + var counts : Dictionary = {} # nom -> count, rempli par les tâches parallèles + await _client.run_parallel(names, func(cat_name : String) -> void: + var cat = await _client.get_json(PokeApiClient.API + "item-category/" + cat_name) + counts[cat_name] = PokeApiJson.get_array(cat, "items").size() if cat != null else 0) + + names.sort() + var out : Array = [] + for cat_name in names: + out.append({"name": cat_name, "count": counts.get(cat_name, 0)}) + return out + +func run(category_names : Array) -> void: + DirAccess.make_dir_recursive_absolute(PokeApiSettings.items_dir()) + _pending.clear() + + var entries : Array = [] + for cat_name in category_names: + var cat = await _client.get_json(PokeApiClient.API + "item-category/" + cat_name) + if cat == null: + message.emit("[color=red]Catégorie introuvable : %s[/color]" % cat_name) + continue + for r in cat["items"]: + entries.append(r) + + if entries.is_empty(): + message.emit("Rien à importer.") + return + + message.emit("%d objets à traiter..." % entries.size()) + await _client.run_parallel(entries, _fetch_item) + await _save_all() + _pending.clear() + message.emit("Objets terminés.") + +func _fetch_item(entry : Dictionary) -> void: + var path : String = PokeApiSettings.items_dir() + entry["name"] + ".tres" + if !_overwrite and FileAccess.file_exists(path): return + + var j = await _client.get_json(entry["url"]) + if j == null: + message.emit("[color=red]Échec : %s[/color]" % entry["name"]) + return + + var it := ItemData.new() + it.id = PokeApiJson.get_int(j, "id") + it.api_name = PokeApiJson.get_str(j, "name", entry["name"]) + it.category = PokeApiJson.get_dict_name(j, "category") + it.names = PokeApiText.names(PokeApiJson.get_array(j, "names"), _languages) + it.descriptions = PokeApiText.item_flavors(PokeApiJson.get_array(j, "flavor_text_entries"), _languages) + it.cost = PokeApiJson.get_int(j, "cost") + it.fling_power = PokeApiJson.get_int(j, "fling_power") + + var sprite_url := PokeApiJson.get_str(PokeApiJson.get_dict(j, "sprites"), "default") + var png_path := await _client.download_file(sprite_url if sprite_url != "" else null, PokeApiSettings.items_dir() + it.api_name + ".png") + + _pending.append({"data": it, "path": path, "png": png_path}) + +func _save_all() -> void: + if _pending.is_empty(): + message.emit("[color=orange]Rien à sauvegarder : tous les objets ont été ignorés ou ont échoué.[/color]") + return + + var pngs := PackedStringArray() + for p in _pending: + if p["png"] != "": pngs.append(p["png"]) + if !pngs.is_empty(): + _fs.scan() + while _fs.is_scanning(): + await _client.get_tree().process_frame + _fs.reimport_files(pngs) + + for p in _pending: + var it : ItemData = p["data"] + if p["png"] != "": + it.sprite = ResourceLoader.load(p["png"], "Texture2D", ResourceLoader.CACHE_MODE_REPLACE) + var err := ResourceSaver.save(it, p["path"]) + if err != OK: + message.emit("[color=red]Échec de sauvegarde : %s (code %d)[/color]" % [it.api_name, err]) + + _fs.scan() diff --git a/editor/item_importer.gd.uid b/editor/item_importer.gd.uid new file mode 100644 index 0000000..878bdf7 --- /dev/null +++ b/editor/item_importer.gd.uid @@ -0,0 +1 @@ +uid://bbptcyu26p85d diff --git a/settings.gd b/settings.gd index 1d082ce..95cc859 100644 --- a/settings.gd +++ b/settings.gd @@ -49,6 +49,7 @@ static func cries_dir() -> String: return data_dir() + "/cries/" static func types_dir() -> String: return data_dir() + "/types/" static func abilities_dir() -> String: return data_dir() + "/abilities/" static func evolution_dir() -> String: return data_dir() + "/evolution/" +static func items_dir() -> String: return data_dir() + "/items/" # nommage des fichiers pokémon : "0025_pikachu" static func pokemon_stem(id : int, api_name : String) -> String: