From bb75ce1b6cc8e1636b7622c514ca06048aea2b8d Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Tue, 29 Sep 2026 16:48:32 +0200 Subject: [PATCH] Add types, abilities and evolutions --- data/ability_data.gd | 15 ++++++++ data/ability_data.gd.uid | 1 + data/evolution_edge.gd | 14 ++++++++ data/evolution_edge.gd.uid | 1 + data/evolution_node.gd | 7 ++++ data/evolution_node.gd.uid | 1 + data/pokemon_ability.gd | 7 ++++ data/pokemon_ability.gd.uid | 1 + data/pokemon_data.gd | 3 +- data/type_data.gd | 25 +++++++++++++ data/type_data.gd.uid | 1 + editor/ability_importer.gd | 44 +++++++++++++++++++++++ editor/ability_importer.gd.uid | 1 + editor/evolution_importer.gd | 52 +++++++++++++++++++++++++++ editor/evolution_importer.gd.uid | 1 + editor/importer_dock.gd | 21 +++++++++++ editor/pokemon_importer.gd | 23 ++++++++++-- editor/type_importer.gd | 61 ++++++++++++++++++++++++++++++++ editor/type_importer.gd.uid | 1 + settings.gd | 3 ++ 20 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 data/ability_data.gd create mode 100644 data/ability_data.gd.uid create mode 100644 data/evolution_edge.gd create mode 100644 data/evolution_edge.gd.uid create mode 100644 data/evolution_node.gd create mode 100644 data/evolution_node.gd.uid create mode 100644 data/pokemon_ability.gd create mode 100644 data/pokemon_ability.gd.uid create mode 100644 data/type_data.gd create mode 100644 data/type_data.gd.uid create mode 100644 editor/ability_importer.gd create mode 100644 editor/ability_importer.gd.uid create mode 100644 editor/evolution_importer.gd create mode 100644 editor/evolution_importer.gd.uid create mode 100644 editor/type_importer.gd create mode 100644 editor/type_importer.gd.uid diff --git a/data/ability_data.gd b/data/ability_data.gd new file mode 100644 index 0000000..94d1ea1 --- /dev/null +++ b/data/ability_data.gd @@ -0,0 +1,15 @@ +# data/ability_data.gd +@tool +class_name AbilityData +extends Resource + +@export var id : int +@export var api_name : String +@export var names : Dictionary = {} +@export var descriptions : Dictionary = {} + +func get_display_name() -> String: + return L10n.pick(names, api_name.capitalize()) + +func get_description() -> String: + return L10n.pick(descriptions) diff --git a/data/ability_data.gd.uid b/data/ability_data.gd.uid new file mode 100644 index 0000000..4b2f087 --- /dev/null +++ b/data/ability_data.gd.uid @@ -0,0 +1 @@ +uid://bhjudhgrl7ohg diff --git a/data/evolution_edge.gd b/data/evolution_edge.gd new file mode 100644 index 0000000..11d9b4c --- /dev/null +++ b/data/evolution_edge.gd @@ -0,0 +1,14 @@ +# data/evolution_edge.gd +@tool +class_name EvolutionEdge +extends Resource + +@export var trigger : String # "level-up", "trade", "use-item", "shed"... +@export var min_level : int = 0 # 0 = pas de condition de niveau +@export var item : String # objet à utiliser, "" si aucun +@export var held_item : String # objet à tenir, "" si aucun +@export var known_move : String # capacité requise, "" si aucune +@export var min_happiness : int = 0 +@export var time_of_day : String # "day", "night", "" si indifférent +@export var trade_species : String # échange contre cette espèce précise, "" sinon +@export var target : EvolutionNode diff --git a/data/evolution_edge.gd.uid b/data/evolution_edge.gd.uid new file mode 100644 index 0000000..98142f1 --- /dev/null +++ b/data/evolution_edge.gd.uid @@ -0,0 +1 @@ +uid://bcfihr0vig1sm diff --git a/data/evolution_node.gd b/data/evolution_node.gd new file mode 100644 index 0000000..35f901b --- /dev/null +++ b/data/evolution_node.gd @@ -0,0 +1,7 @@ +# data/evolution_node.gd +@tool +class_name EvolutionNode +extends Resource + +@export var species_name : String +@export var evolves_to : Array[EvolutionEdge] = [] diff --git a/data/evolution_node.gd.uid b/data/evolution_node.gd.uid new file mode 100644 index 0000000..8493908 --- /dev/null +++ b/data/evolution_node.gd.uid @@ -0,0 +1 @@ +uid://b364h4i46hwo1 diff --git a/data/pokemon_ability.gd b/data/pokemon_ability.gd new file mode 100644 index 0000000..69092c0 --- /dev/null +++ b/data/pokemon_ability.gd @@ -0,0 +1,7 @@ +# data/pokemon_ability.gd +@tool +class_name PokemonAbility +extends Resource + +@export var ability : AbilityData +@export var is_hidden : bool = false diff --git a/data/pokemon_ability.gd.uid b/data/pokemon_ability.gd.uid new file mode 100644 index 0000000..9889de8 --- /dev/null +++ b/data/pokemon_ability.gd.uid @@ -0,0 +1 @@ +uid://cpejtpjkcrvfy diff --git a/data/pokemon_data.gd b/data/pokemon_data.gd index 59c0890..9c092f8 100644 --- a/data/pokemon_data.gd +++ b/data/pokemon_data.gd @@ -8,7 +8,8 @@ extends Resource @export var names : Dictionary = {} @export var descriptions : Dictionary = {} @export var types : PackedStringArray -@export var abilities : PackedStringArray +@export var abilities : Array[PokemonAbility] = [] # remplace l'ancien PackedStringArray +@export var evolution_chain : EvolutionNode @export var base_stats : Dictionary = {} @export var height : float @export var weight : float diff --git a/data/type_data.gd b/data/type_data.gd new file mode 100644 index 0000000..6f1809a --- /dev/null +++ b/data/type_data.gd @@ -0,0 +1,25 @@ +# data/type_data.gd +@tool +class_name TypeData +extends Resource + +@export var id : int +@export var api_name : String +@export var names : Dictionary = {} + +@export var double_damage_to : PackedStringArray +@export var double_damage_from : PackedStringArray +@export var half_damage_to : PackedStringArray +@export var half_damage_from : PackedStringArray +@export var no_damage_to : PackedStringArray +@export var no_damage_from : PackedStringArray + +func get_display_name() -> String: + return L10n.pick(names, api_name.capitalize()) + +## Multiplicateur de dégâts quand ce type attaque `defending_type_name`. +func get_multiplier_against(defending_type_name : String) -> float: + if defending_type_name in no_damage_to: return 0.0 + if defending_type_name in double_damage_to: return 2.0 + if defending_type_name in half_damage_to: return 0.5 + return 1.0 diff --git a/data/type_data.gd.uid b/data/type_data.gd.uid new file mode 100644 index 0000000..252d051 --- /dev/null +++ b/data/type_data.gd.uid @@ -0,0 +1 @@ +uid://cvvmd5x42xk8n diff --git a/editor/ability_importer.gd b/editor/ability_importer.gd new file mode 100644 index 0000000..04d27c4 --- /dev/null +++ b/editor/ability_importer.gd @@ -0,0 +1,44 @@ +# editor/ability_importer.gd +@tool +class_name PokeApiAbilityImporter +extends RefCounted + +signal message(text : String) + +var _client : PokeApiClient +var _overwrite : bool +var _languages : PackedStringArray +var _dir : String + +func _init(client : PokeApiClient, overwrite : bool) -> void: + _client = client + _overwrite = overwrite + _languages = PokeApiSettings.languages() + _dir = PokeApiSettings.abilities_dir() + +func run() -> void: + DirAccess.make_dir_recursive_absolute(_dir) + var list = await _client.get_json(PokeApiClient.API + "ability?limit=1000") + if list == null: + message.emit("[color=red]Impossible de récupérer la liste des talents.[/color]") + return + + message.emit("%d talents à traiter..." % list["results"].size()) + await _client.run_parallel(list["results"], _import_ability) + message.emit("Talents terminés.") + +func _import_ability(entry : Dictionary) -> void: + var path : String = _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 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) + ResourceSaver.save(a, path) diff --git a/editor/ability_importer.gd.uid b/editor/ability_importer.gd.uid new file mode 100644 index 0000000..9de2199 --- /dev/null +++ b/editor/ability_importer.gd.uid @@ -0,0 +1 @@ +uid://dr3x526ada416 diff --git a/editor/evolution_importer.gd b/editor/evolution_importer.gd new file mode 100644 index 0000000..9cbee98 --- /dev/null +++ b/editor/evolution_importer.gd @@ -0,0 +1,52 @@ +# editor/evolution_importer.gd +@tool +class_name PokeApiEvolutionImporter +extends RefCounted + +var _client : PokeApiClient +var _overwrite : bool +var _cache : Dictionary = {} # id de chaîne -> EvolutionNode, pour ce run + +func _init(client : PokeApiClient, overwrite : bool) -> void: + _client = client + _overwrite = overwrite + DirAccess.make_dir_recursive_absolute(PokeApiSettings.evolution_dir()) + +## Retourne la racine de la chaîne (déjà sauvegardée sur disque au retour). +func get_or_build(chain_url : String) -> EvolutionNode: + var chain_id := int(String(chain_url).trim_suffix("/").get_file()) + if _cache.has(chain_id): return _cache[chain_id] + + var path := PokeApiSettings.evolution_dir() + str(chain_id) + ".tres" + if !_overwrite and ResourceLoader.exists(path): + var existing := load(path) as EvolutionNode + _cache[chain_id] = existing + return existing + + var j = await _client.get_json(chain_url) + if j == null: return null + + var root := _build_node(j["chain"]) + ResourceSaver.save(root, path) + _cache[chain_id] = root + return root + +func _build_node(link : Dictionary) -> EvolutionNode: + var node := EvolutionNode.new() + node.species_name = link["species"]["name"] + for evo in link["evolves_to"]: + var edge := EvolutionEdge.new() + var details : Array = evo["evolution_details"] + if !details.is_empty(): + var det : Dictionary = details[0] # première méthode si plusieurs sont possibles + edge.trigger = det["trigger"]["name"] if det["trigger"] != null else "" + edge.min_level = int(det["min_level"]) if det["min_level"] != null else 0 + edge.item = det["item"]["name"] if det["item"] != null else "" + edge.held_item = det["held_item"]["name"] if det["held_item"] != null else "" + edge.known_move = det["known_move"]["name"] if det["known_move"] != null else "" + edge.min_happiness = int(det["min_happiness"]) if det["min_happiness"] != null else 0 + edge.time_of_day = String(det["time_of_day"]) if det["time_of_day"] != null else "" + edge.trade_species = det["trade_species"]["name"] if det["trade_species"] != null else "" + edge.target = _build_node(evo) + node.evolves_to.append(edge) + return node diff --git a/editor/evolution_importer.gd.uid b/editor/evolution_importer.gd.uid new file mode 100644 index 0000000..72f8c99 --- /dev/null +++ b/editor/evolution_importer.gd.uid @@ -0,0 +1 @@ +uid://dtlgaci7tl522 diff --git a/editor/importer_dock.gd b/editor/importer_dock.gd index 3ea20c0..8fa09f7 100644 --- a/editor/importer_dock.gd +++ b/editor/importer_dock.gd @@ -20,7 +20,10 @@ func _ready() -> void: add_child(_client) _add_button("Importer toutes les capacités", _on_import_moves) + _add_button("Importer les types", _on_import_types) + _add_button("Importer les talents", _on_import_abilities) add_child(HSeparator.new()) + var label := Label.new() label.text = "Pokémon (ids, noms, plages) :" @@ -62,6 +65,24 @@ func _on_import_moves() -> void: editor_filesystem.scan() _set_busy(false) +func _on_import_types() -> void: + if _busy: return + _set_busy(true) + var importer := PokeApiTypeImporter.new(_client, _overwrite.button_pressed) + importer.message.connect(_print) + await importer.run() + editor_filesystem.scan() + _set_busy(false) + +func _on_import_abilities() -> void: + if _busy: return + _set_busy(true) + var importer := PokeApiAbilityImporter.new(_client, _overwrite.button_pressed) + importer.message.connect(_print) + await importer.run() + editor_filesystem.scan() + _set_busy(false) + func _on_import_pokemon() -> void: if _busy: return var tokens := _parse_input(_input.text) diff --git a/editor/pokemon_importer.gd b/editor/pokemon_importer.gd index 6f3cb6f..49534bd 100644 --- a/editor/pokemon_importer.gd +++ b/editor/pokemon_importer.gd @@ -10,18 +10,24 @@ 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()) 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.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]") _pending.clear() await _client.run_parallel(tokens, _fetch_pokemon) # phase 1 : téléchargement @@ -52,7 +58,7 @@ func _fetch_pokemon(token : Variant) -> void: for t in j["types"]: d.types.append(t["type"]["name"]) for a in j["abilities"]: - d.abilities.append(a["ability"]["name"]) + d.abilities = _build_abilities(j["abilities"]) for s in j["stats"]: d.base_stats[s["stat"]["name"]] = s["base_stat"] @@ -60,7 +66,9 @@ func _fetch_pokemon(token : Variant) -> void: 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")) @@ -123,3 +131,14 @@ func _build_learnset(raw_moves : Array) -> Array[LearnedMove]: 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 diff --git a/editor/type_importer.gd b/editor/type_importer.gd new file mode 100644 index 0000000..ac9b9cd --- /dev/null +++ b/editor/type_importer.gd @@ -0,0 +1,61 @@ +# editor/type_importer.gd +@tool +class_name PokeApiTypeImporter +extends RefCounted + +signal message(text : String) + +var _client : PokeApiClient +var _overwrite : bool +var _languages : PackedStringArray +var _dir : String + +func _init(client : PokeApiClient, overwrite : bool) -> void: + _client = client + _overwrite = overwrite + _languages = PokeApiSettings.languages() + _dir = PokeApiSettings.types_dir() + +func run() -> void: + DirAccess.make_dir_recursive_absolute(_dir) + var list = await _client.get_json(PokeApiClient.API + "type?limit=100") + if list == null: + message.emit("[color=red]Impossible de récupérer la liste des types.[/color]") + return + + var entries : Array = [] + for r in list["results"]: + var id : int = int(String(r["url"]).trim_suffix("/").get_file()) + if id < 10000: entries.append(r) # ignore "unknown"/"shadow" (10001, 10002) + + message.emit("%d types à traiter..." % entries.size()) + await _client.run_parallel(entries, _import_type) + message.emit("Types terminés.") + +func _import_type(entry : Dictionary) -> void: + var path : String = _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 t := TypeData.new() + t.id = j["id"] + t.api_name = j["name"] + t.names = PokeApiText.names(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"]) + ResourceSaver.save(t, path) + +func _names_of(entries : Array) -> PackedStringArray: + var out := PackedStringArray() + for e in entries: out.append(e["name"]) + return out diff --git a/editor/type_importer.gd.uid b/editor/type_importer.gd.uid new file mode 100644 index 0000000..a59dc03 --- /dev/null +++ b/editor/type_importer.gd.uid @@ -0,0 +1 @@ +uid://ceb3knywwobbt diff --git a/settings.gd b/settings.gd index 97e55e7..840c557 100644 --- a/settings.gd +++ b/settings.gd @@ -45,6 +45,9 @@ static func max_concurrent() -> int: static func moves_dir() -> String: return data_dir() + "/moves/" static func pokemon_dir() -> String: return data_dir() + "/pokemon/" static func sprites_dir() -> String: return data_dir() + "/sprites/" +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/" # nommage des fichiers pokémon : "0025_pikachu" static func pokemon_stem(id : int, api_name : String) -> String: