228 lines
9.3 KiB
GDScript
228 lines
9.3 KiB
GDScript
# editor/pokemon_importer.gd
|
|
@tool
|
|
class_name PokeApiPokemonImporter
|
|
extends RefCounted
|
|
|
|
signal message(text : String)
|
|
|
|
var _client : PokeApiClient
|
|
var _fs : EditorFileSystem
|
|
var _overwrite : bool
|
|
var _languages : PackedStringArray
|
|
var _pending : Array = [] # pokémon récupérés, en attente de sauvegarde
|
|
var _evolution : PokeApiEvolutionImporter
|
|
|
|
func _init(client : PokeApiClient, fs : EditorFileSystem, overwrite : bool) -> void:
|
|
_client = client
|
|
_fs = fs
|
|
_overwrite = overwrite
|
|
_languages = PokeApiSettings.languages()
|
|
_evolution = PokeApiEvolutionImporter.new(_client, _overwrite)
|
|
|
|
func run(tokens : Array) -> void:
|
|
DirAccess.make_dir_recursive_absolute(PokeApiSettings.pokemon_dir())
|
|
DirAccess.make_dir_recursive_absolute(PokeApiSettings.sprites_dir())
|
|
DirAccess.make_dir_recursive_absolute(PokeApiSettings.cries_dir())
|
|
if !DirAccess.dir_exists_absolute(PokeApiSettings.moves_dir()):
|
|
message.emit("[color=orange]Aucune capacité importée : les learnsets seront vides.[/color]")
|
|
if !DirAccess.dir_exists_absolute(PokeApiSettings.abilities_dir()):
|
|
message.emit("[color=orange]Aucun talent importé : les talents des pokémon seront vides.[/color]")
|
|
if !DirAccess.dir_exists_absolute(PokeApiSettings.types_dir()):
|
|
message.emit("[color=orange]Aucun type importé : les types des pokémon seront vides.[/color]")
|
|
|
|
_pending.clear()
|
|
await _client.run_parallel(tokens, _fetch_pokemon) # phase 1 : téléchargement
|
|
await _save_all() # phase 2 : sprites + .tres
|
|
_pending.clear()
|
|
message.emit("Pokémon terminés.")
|
|
|
|
func _fetch_pokemon(token : Variant) -> void:
|
|
var j = await _client.get_json(PokeApiClient.API + "pokemon/" + str(token))
|
|
if j == null:
|
|
message.emit("[color=red]Introuvable : %s[/color]" % str(token))
|
|
return
|
|
|
|
var poke_name : String = j["name"]
|
|
var stem := PokeApiSettings.pokemon_stem(int(j["id"]), poke_name)
|
|
var path : String = PokeApiSettings.pokemon_dir() + stem + ".tres"
|
|
if !_overwrite and FileAccess.file_exists(path):
|
|
message.emit("Déjà présent : %s (coche « Écraser » pour rafraîchir aussi ses formes méga/gigamax)" % poke_name)
|
|
return
|
|
|
|
var species = await _client.get_json(j["species"]["url"])
|
|
var base_index : int = _pending.size()
|
|
_pending.append(await _build_entry(j, species, stem, path))
|
|
if species == null: return
|
|
|
|
for v in species.get("varieties", []):
|
|
var variety_name : String = v["pokemon"]["name"]
|
|
var kind := _form_kind(variety_name)
|
|
if kind == "": continue # formes régionales etc. : importe-les directement par leur nom (ex. "raichu-alola")
|
|
|
|
var jv = await _client.get_json(v["pokemon"]["url"])
|
|
if jv == null:
|
|
message.emit("[color=red]Forme introuvable : %s[/color]" % variety_name)
|
|
continue
|
|
|
|
var v_stem := PokeApiSettings.pokemon_stem(int(jv["id"]), variety_name)
|
|
var v_path := PokeApiSettings.pokemon_dir() + v_stem + ".tres"
|
|
var entry := await _build_entry(jv, species, v_stem, v_path)
|
|
entry["form_of"] = base_index
|
|
entry["form_kind"] = kind
|
|
entry["form_item_guess"] = _guess_item(variety_name, kind)
|
|
_pending.append(entry)
|
|
|
|
func _form_kind(variety_name : String) -> String:
|
|
if variety_name.ends_with("-mega-x"): return "mega-x"
|
|
if variety_name.ends_with("-mega-y"): return "mega-y"
|
|
if variety_name.ends_with("-mega"): return "mega"
|
|
if variety_name.ends_with("-gmax"): return "gmax"
|
|
return ""
|
|
|
|
func _guess_item(variety_name : String, kind : String) -> String:
|
|
if kind == "gmax": return "" # le gigamax ne consomme pas d'objet
|
|
var suffix := "-x" if kind == "mega-x" else ("-y" if kind == "mega-y" else "")
|
|
return variety_name.trim_suffix("-" + kind) + "ite" + suffix # convention Bulbapedia, approximative
|
|
|
|
## Construit une entrée _pending (utilisée pour l'espèce de base ET pour chaque forme)
|
|
func _build_entry(j : Dictionary, species : Variant, stem : String, path : String) -> Dictionary:
|
|
var d := PokemonData.new()
|
|
d.id = j["id"]
|
|
d.api_name = j["name"]
|
|
d.height = j["height"] / 10.0
|
|
d.weight = j["weight"] / 10.0
|
|
d.types = _resolve_types(j["types"])
|
|
d.abilities = _build_abilities(j["abilities"])
|
|
for s in j["stats"]:
|
|
d.base_stats[s["stat"]["name"]] = s["base_stat"]
|
|
|
|
d.names = {"en": String(j["name"]).capitalize()}
|
|
if species != null:
|
|
d.names = PokeApiText.names(species["names"], _languages)
|
|
d.descriptions = PokeApiText.flavors(species["flavor_text_entries"], _languages)
|
|
if species.get("evolution_chain") != null:
|
|
d.evolution_chain = await _evolution.get_or_build(species["evolution_chain"]["url"])
|
|
|
|
var sprites : Dictionary = j["sprites"]
|
|
var art : Dictionary = sprites.get("other", {}).get("official-artwork", {})
|
|
var sprite_url = art.get("front_default", sprites.get("front_default"))
|
|
var shiny_url = art.get("front_shiny", sprites.get("front_shiny"))
|
|
var sdir := PokeApiSettings.sprites_dir()
|
|
var cries : Dictionary = j.get("cries", {})
|
|
|
|
return {
|
|
"data": d, "path": path, "moves": j["moves"],
|
|
"png": await _client.download_file(sprite_url, sdir + stem + ".png"),
|
|
"shiny_png": await _client.download_file(shiny_url, sdir + stem + "_shiny.png"),
|
|
"cry": await _client.download_file(cries.get("latest"), PokeApiSettings.cries_dir() + stem + ".ogg"),
|
|
}
|
|
|
|
func _save_all() -> void:
|
|
var to_reimport := PackedStringArray()
|
|
for p in _pending:
|
|
for key in ["png", "shiny_png", "cry"]:
|
|
if p[key] != "": to_reimport.append(p[key])
|
|
if !to_reimport.is_empty():
|
|
_fs.scan()
|
|
while _fs.is_scanning():
|
|
await _client.get_tree().process_frame
|
|
_fs.reimport_files(to_reimport)
|
|
|
|
# étape 1 : sprite/cri/modèle/learnset pour toutes les entrées (bases et formes)
|
|
for p in _pending:
|
|
var d : PokemonData = p["data"]
|
|
d.menu_sprite = _load_texture(p["png"])
|
|
d.shiny_menu_sprite = _load_texture(p["shiny_png"])
|
|
d.cry_latest = _load_audio(p["cry"])
|
|
_link_models(d)
|
|
d.learnset = _build_learnset(p["moves"])
|
|
|
|
# étape 2 : formes d'abord, pour obtenir une référence externe (pas embarquée) depuis la base
|
|
for p in _pending:
|
|
if !p.has("form_of"): continue
|
|
var variant : PokemonData = p["data"]
|
|
ResourceSaver.save(variant, p["path"])
|
|
var base_d : PokemonData = _pending[p["form_of"]]["data"]
|
|
base_d.forms.append(_make_form(base_d, variant, p["form_kind"], p["form_item_guess"]))
|
|
|
|
# étape 3 : bases (et pokémon sans formes)
|
|
for p in _pending:
|
|
if p.has("form_of"): continue
|
|
var d : PokemonData = p["data"]
|
|
ResourceSaver.save(d, p["path"])
|
|
message.emit("OK : %s (%d capacités, %d formes)" % [d.get_display_name(), d.learnset.size(), d.forms.size()])
|
|
|
|
_fs.scan()
|
|
|
|
## Préserve une correction manuelle de required_item faite lors d'un import précédent.
|
|
func _make_form(base_d : PokemonData, variant : PokemonData, kind : String, item_guess : String) -> PokemonForm:
|
|
var form := PokemonForm.new()
|
|
form.kind = kind
|
|
form.variant = variant
|
|
form.required_item = item_guess
|
|
|
|
var old_path := PokeApiSettings.pokemon_dir() + PokeApiSettings.pokemon_stem(base_d.id, base_d.api_name) + ".tres"
|
|
if FileAccess.file_exists(old_path):
|
|
var old := ResourceLoader.load(old_path, "", ResourceLoader.CACHE_MODE_REPLACE) as PokemonData
|
|
if old:
|
|
for f in old.forms:
|
|
if f.kind == kind and f.required_item != "" and f.required_item != item_guess:
|
|
form.required_item = f.required_item
|
|
break
|
|
return form
|
|
|
|
func _load_texture(path : String) -> Texture2D:
|
|
if path.is_empty(): return null
|
|
return ResourceLoader.load(path, "Texture2D", ResourceLoader.CACHE_MODE_REPLACE) as Texture2D
|
|
|
|
func _load_audio(path : String) -> AudioStream:
|
|
if path.is_empty(): return null
|
|
return ResourceLoader.load(path, "AudioStream", ResourceLoader.CACHE_MODE_REPLACE) as AudioStream
|
|
|
|
func _link_models(d : PokemonData) -> void:
|
|
var dir := PokeApiSettings.models_dir()
|
|
if dir.is_empty(): return
|
|
var model_path := "%s/%s.glb" % [dir, d.api_name]
|
|
if ResourceLoader.exists(model_path):
|
|
d.model_scene = load(model_path) as PackedScene
|
|
var tex_path := "%s/%s_shiny.png" % [dir, d.api_name]
|
|
if ResourceLoader.exists(tex_path):
|
|
d.shiny_albedo = load(tex_path) as Texture2D
|
|
|
|
func _build_learnset(raw_moves : Array) -> Array[LearnedMove]:
|
|
var result : Array[LearnedMove] = []
|
|
for m in raw_moves:
|
|
var move_path : String = PokeApiSettings.moves_dir() + String(m["move"]["name"]) + ".tres"
|
|
if !FileAccess.file_exists(move_path): continue
|
|
var details : Array = m["version_group_details"]
|
|
var last : Dictionary = details[details.size() - 1] # jeu le plus récent
|
|
var lm := LearnedMove.new()
|
|
lm.move = load(move_path) as MoveData
|
|
lm.level = last["level_learned_at"]
|
|
lm.method = last["move_learn_method"]["name"]
|
|
result.append(lm)
|
|
result.sort_custom(func(a : LearnedMove, b : LearnedMove) -> bool: return a.level < b.level)
|
|
return result
|
|
|
|
func _build_abilities(raw_abilities : Array) -> Array[PokemonAbility]:
|
|
var result : Array[PokemonAbility] = []
|
|
for a in raw_abilities:
|
|
var path : String = PokeApiSettings.abilities_dir() + String(a["ability"]["name"]) + ".tres"
|
|
if !FileAccess.file_exists(path): continue
|
|
var pa := PokemonAbility.new()
|
|
pa.ability = load(path) as AbilityData
|
|
pa.is_hidden = a["is_hidden"]
|
|
result.append(pa)
|
|
return result
|
|
|
|
func _resolve_types(raw_types : Array) -> Array[TypeData]:
|
|
var result : Array[TypeData] = []
|
|
for t in raw_types:
|
|
var name : String = t["type"]["name"]
|
|
var path : String = PokeApiSettings.types_dir() + name + ".tres"
|
|
if !FileAccess.file_exists(path):
|
|
message.emit("[color=orange]Type introuvable : %s (importe les types d'abord)[/color]" % name)
|
|
continue
|
|
result.append(load(path) as TypeData)
|
|
return result
|