Compare commits
4 commits
4a1f82dd02
...
734ef61226
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
734ef61226 | ||
|
|
9c4d3fbc1c | ||
|
|
0e89c5fa85 | ||
|
|
8a87337c81 |
19
data/item_data.gd
Normal file
19
data/item_data.gd
Normal file
|
|
@ -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)
|
||||
1
data/item_data.gd.uid
Normal file
1
data/item_data.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://lhn8jhdqstn7
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# data/l10n.gd
|
||||
@tool
|
||||
class_name L10n
|
||||
|
||||
extends RefCounted
|
||||
|
||||
const FALLBACK := "en"
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ func _import_ability(entry : Dictionary) -> void:
|
|||
return
|
||||
|
||||
var a := AbilityData.new()
|
||||
a.id = j["id"]
|
||||
a.api_name = j["name"]
|
||||
a.names = PokeApiText.names(j["names"], _languages)
|
||||
a.descriptions = PokeApiText.flavors(j["flavor_text_entries"], _languages)
|
||||
a.id = PokeApiJson.get_int(j, "id")
|
||||
a.api_name = PokeApiJson.get_str(j, "name", entry["name"])
|
||||
a.names = PokeApiText.names(PokeApiJson.get_array(j, "names"), _languages)
|
||||
a.descriptions = PokeApiText.flavors(PokeApiJson.get_array(j, "flavor_text_entries"), _languages)
|
||||
ResourceSaver.save(a, path)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
29
editor/import_log.gd
Normal file
29
editor/import_log.gd
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# editor/import_log.gd
|
||||
@tool
|
||||
class_name PokeApiImportLog
|
||||
extends RefCounted
|
||||
|
||||
const LOG_PATH := "user://pokeapi_import.log"
|
||||
const MAX_SIZE := 1 << 20 # 1 Mo : au-delà, on repart d'un fichier vide plutôt que de l'alourdir indéfiniment
|
||||
|
||||
static func append(text : String) -> void:
|
||||
var stamp := Time.get_datetime_string_from_system()
|
||||
var plain := _strip_bbcode(text)
|
||||
|
||||
if FileAccess.file_exists(LOG_PATH) and FileAccess.get_file_as_bytes(LOG_PATH).size() > MAX_SIZE:
|
||||
var f := FileAccess.open(LOG_PATH, FileAccess.WRITE)
|
||||
if f: f.close()
|
||||
|
||||
var f := FileAccess.open(LOG_PATH, FileAccess.READ_WRITE if FileAccess.file_exists(LOG_PATH) else FileAccess.WRITE)
|
||||
if f == null: return
|
||||
f.seek_end()
|
||||
f.store_line("[%s] %s" % [stamp, plain])
|
||||
f.close()
|
||||
|
||||
static func _strip_bbcode(text : String) -> String:
|
||||
var regex := RegEx.new()
|
||||
regex.compile("\\[/?color[^\\]]*\\]")
|
||||
return regex.sub(text, "", true)
|
||||
|
||||
static func path() -> String:
|
||||
return ProjectSettings.globalize_path(LOG_PATH)
|
||||
1
editor/import_log.gd.uid
Normal file
1
editor/import_log.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bdaspaenqgeoo
|
||||
|
|
@ -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,29 @@ func _ready() -> void:
|
|||
_log.scroll_following = true
|
||||
_log.selection_enabled = true
|
||||
add_child(_log)
|
||||
|
||||
var btn_log := Button.new()
|
||||
btn_log.text = "Ouvrir le dossier du journal"
|
||||
btn_log.pressed.connect(func() -> void:
|
||||
OS.shell_show_in_file_manager(PokeApiImportLog.path()))
|
||||
add_child(btn_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 +123,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):
|
||||
|
|
@ -119,3 +174,4 @@ func _set_busy(value : bool) -> void:
|
|||
|
||||
func _print(msg : String) -> void:
|
||||
_log.append_text(msg + "\n")
|
||||
PokeApiImportLog.append(msg)
|
||||
|
|
|
|||
108
editor/item_importer.gd
Normal file
108
editor/item_importer.gd
Normal file
|
|
@ -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()
|
||||
1
editor/item_importer.gd.uid
Normal file
1
editor/item_importer.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bbptcyu26p85d
|
||||
|
|
@ -42,15 +42,15 @@ func _import_move(entry : Dictionary) -> void:
|
|||
return
|
||||
|
||||
var m := MoveData.new()
|
||||
m.id = j["id"]
|
||||
m.api_name = j["name"]
|
||||
m.names = PokeApiText.names(j["names"], _languages)
|
||||
m.descriptions = PokeApiText.flavors(j["flavor_text_entries"], _languages)
|
||||
m.type = j["type"]["name"]
|
||||
m.category = j["damage_class"]["name"]
|
||||
m.power = int(j["power"]) if j["power"] != null else 0
|
||||
m.accuracy = int(j["accuracy"]) if j["accuracy"] != null else 0
|
||||
m.pp = int(j["pp"]) if j["pp"] != null else 0
|
||||
m.priority = j["priority"]
|
||||
m.target = j["target"]["name"]
|
||||
m.id = PokeApiJson.get_int(j, "id")
|
||||
m.api_name = PokeApiJson.get_str(j, "name", entry["name"])
|
||||
m.names = PokeApiText.names(PokeApiJson.get_array(j, "names"), _languages)
|
||||
m.descriptions = PokeApiText.flavors(PokeApiJson.get_array(j, "flavor_text_entries"), _languages)
|
||||
m.type = PokeApiJson.get_dict_name(j, "type")
|
||||
m.category = PokeApiJson.get_dict_name(j, "damage_class")
|
||||
m.power = PokeApiJson.get_int(j, "power")
|
||||
m.accuracy = PokeApiJson.get_int(j, "accuracy")
|
||||
m.pp = PokeApiJson.get_int(j, "pp")
|
||||
m.priority = PokeApiJson.get_int(j, "priority")
|
||||
m.target = PokeApiJson.get_dict_name(j, "target")
|
||||
ResourceSaver.save(m, path)
|
||||
|
|
|
|||
35
editor/pokeapi_json.gd
Normal file
35
editor/pokeapi_json.gd
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# editor/pokeapi_json.gd
|
||||
@tool
|
||||
class_name PokeApiJson
|
||||
extends RefCounted
|
||||
|
||||
## Tous les accesseurs tolèrent un dictionnaire absent, une clé manquante,
|
||||
## ou une valeur JSON null — ils renvoient le défaut plutôt que de planter.
|
||||
|
||||
static func get_str(j : Dictionary, key : String, default : String = "") -> String:
|
||||
var v : Variant = j.get(key)
|
||||
return String(v) if v != null else default
|
||||
|
||||
static func get_int(j : Dictionary, key : String, default : int = 0) -> int:
|
||||
var v : Variant = j.get(key)
|
||||
return int(v) if v != null else default
|
||||
|
||||
static func get_bool(j : Dictionary, key : String, default : bool = false) -> bool:
|
||||
var v : Variant = j.get(key)
|
||||
return bool(v) if v != null else default
|
||||
|
||||
static func get_dict(j : Dictionary, key : String) -> Dictionary:
|
||||
var v : Variant = j.get(key)
|
||||
return v if v is Dictionary else {}
|
||||
|
||||
static func get_array(j : Dictionary, key : String) -> Array:
|
||||
var v : Variant = j.get(key)
|
||||
return v if v is Array else []
|
||||
|
||||
## Cas très fréquent dans PokeAPI : {"name": "...", "url": "..."} imbriqué, parfois null.
|
||||
## get_dict_name({"type": {"name": "electric"}}, "type") -> "electric"
|
||||
static func get_dict_name(j : Dictionary, key : String, default : String = "") -> String:
|
||||
return get_str(get_dict(j, key), "name", default)
|
||||
|
||||
static func get_dict_url(j : Dictionary, key : String, default : String = "") -> String:
|
||||
return get_str(get_dict(j, key), "url", default)
|
||||
1
editor/pokeapi_json.gd.uid
Normal file
1
editor/pokeapi_json.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b3ynf743w7rss
|
||||
|
|
@ -48,17 +48,17 @@ func _import_type(entry : Dictionary) -> void:
|
|||
if t == null:
|
||||
t = TypeData.new()
|
||||
|
||||
t.id = j["id"]
|
||||
t.api_name = j["name"]
|
||||
t.names = PokeApiText.names(j["names"], _languages)
|
||||
t.id = PokeApiJson.get_int(j, "id")
|
||||
t.api_name = PokeApiJson.get_str(j, "name", entry["name"])
|
||||
t.names = PokeApiText.names(PokeApiJson.get_array(j, "names"), _languages)
|
||||
|
||||
var rel : Dictionary = j["damage_relations"]
|
||||
t.double_damage_to = _names_of(rel["double_damage_to"])
|
||||
t.double_damage_from = _names_of(rel["double_damage_from"])
|
||||
t.half_damage_to = _names_of(rel["half_damage_to"])
|
||||
t.half_damage_from = _names_of(rel["half_damage_from"])
|
||||
t.no_damage_to = _names_of(rel["no_damage_to"])
|
||||
t.no_damage_from = _names_of(rel["no_damage_from"])
|
||||
var rel := PokeApiJson.get_dict(j, "damage_relations")
|
||||
t.double_damage_to = _names_of(PokeApiJson.get_array(rel, "double_damage_to"))
|
||||
t.double_damage_from = _names_of(PokeApiJson.get_array(rel, "double_damage_from"))
|
||||
t.half_damage_to = _names_of(PokeApiJson.get_array(rel, "half_damage_to"))
|
||||
t.half_damage_from = _names_of(PokeApiJson.get_array(rel, "half_damage_from"))
|
||||
t.no_damage_to = _names_of(PokeApiJson.get_array(rel, "no_damage_to"))
|
||||
t.no_damage_from = _names_of(PokeApiJson.get_array(rel, "no_damage_from"))
|
||||
# t.icon n'est jamais touché ici
|
||||
ResourceSaver.save(t, path)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue