Add types, abilities and evolutions

This commit is contained in:
TheThomaas 2026-09-29 16:48:32 +02:00
parent aee47dab71
commit bb75ce1b6c
20 changed files with 280 additions and 3 deletions

15
data/ability_data.gd Normal file
View file

@ -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)

1
data/ability_data.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bhjudhgrl7ohg

14
data/evolution_edge.gd Normal file
View file

@ -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

View file

@ -0,0 +1 @@
uid://bcfihr0vig1sm

7
data/evolution_node.gd Normal file
View file

@ -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] = []

View file

@ -0,0 +1 @@
uid://b364h4i46hwo1

7
data/pokemon_ability.gd Normal file
View file

@ -0,0 +1,7 @@
# data/pokemon_ability.gd
@tool
class_name PokemonAbility
extends Resource
@export var ability : AbilityData
@export var is_hidden : bool = false

View file

@ -0,0 +1 @@
uid://cpejtpjkcrvfy

View file

@ -8,7 +8,8 @@ extends Resource
@export var names : Dictionary = {} @export var names : Dictionary = {}
@export var descriptions : Dictionary = {} @export var descriptions : Dictionary = {}
@export var types : PackedStringArray @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 base_stats : Dictionary = {}
@export var height : float @export var height : float
@export var weight : float @export var weight : float

25
data/type_data.gd Normal file
View file

@ -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

1
data/type_data.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cvvmd5x42xk8n

View file

@ -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)

View file

@ -0,0 +1 @@
uid://dr3x526ada416

View file

@ -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

View file

@ -0,0 +1 @@
uid://dtlgaci7tl522

View file

@ -20,8 +20,11 @@ func _ready() -> void:
add_child(_client) add_child(_client)
_add_button("Importer toutes les capacités", _on_import_moves) _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()) add_child(HSeparator.new())
var label := Label.new() var label := Label.new()
label.text = "Pokémon (ids, noms, plages) :" label.text = "Pokémon (ids, noms, plages) :"
add_child(label) add_child(label)
@ -62,6 +65,24 @@ func _on_import_moves() -> void:
editor_filesystem.scan() editor_filesystem.scan()
_set_busy(false) _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: func _on_import_pokemon() -> void:
if _busy: return if _busy: return
var tokens := _parse_input(_input.text) var tokens := _parse_input(_input.text)

View file

@ -10,18 +10,24 @@ var _fs : EditorFileSystem
var _overwrite : bool var _overwrite : bool
var _languages : PackedStringArray var _languages : PackedStringArray
var _pending : Array = [] # pokémon récupérés, en attente de sauvegarde 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: func _init(client : PokeApiClient, fs : EditorFileSystem, overwrite : bool) -> void:
_client = client _client = client
_fs = fs _fs = fs
_overwrite = overwrite _overwrite = overwrite
_languages = PokeApiSettings.languages() _languages = PokeApiSettings.languages()
_evolution = PokeApiEvolutionImporter.new(_client, _overwrite)
func run(tokens : Array) -> void: func run(tokens : Array) -> void:
DirAccess.make_dir_recursive_absolute(PokeApiSettings.pokemon_dir()) DirAccess.make_dir_recursive_absolute(PokeApiSettings.pokemon_dir())
DirAccess.make_dir_recursive_absolute(PokeApiSettings.sprites_dir()) DirAccess.make_dir_recursive_absolute(PokeApiSettings.sprites_dir())
if !DirAccess.dir_exists_absolute(PokeApiSettings.moves_dir()): if !DirAccess.dir_exists_absolute(PokeApiSettings.moves_dir()):
message.emit("[color=orange]Aucune capacité importée : les learnsets seront vides.[/color]") 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() _pending.clear()
await _client.run_parallel(tokens, _fetch_pokemon) # phase 1 : téléchargement 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"]: for t in j["types"]:
d.types.append(t["type"]["name"]) d.types.append(t["type"]["name"])
for a in j["abilities"]: for a in j["abilities"]:
d.abilities.append(a["ability"]["name"]) d.abilities = _build_abilities(j["abilities"])
for s in j["stats"]: for s in j["stats"]:
d.base_stats[s["stat"]["name"]] = s["base_stat"] d.base_stats[s["stat"]["name"]] = s["base_stat"]
@ -60,6 +66,8 @@ func _fetch_pokemon(token : Variant) -> void:
if species != null: if species != null:
d.names = PokeApiText.names(species["names"], _languages) d.names = PokeApiText.names(species["names"], _languages)
d.descriptions = PokeApiText.flavors(species["flavor_text_entries"], _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 sprites : Dictionary = j["sprites"]
var art : Dictionary = sprites.get("other", {}).get("official-artwork", {}) var art : Dictionary = sprites.get("other", {}).get("official-artwork", {})
@ -123,3 +131,14 @@ func _build_learnset(raw_moves : Array) -> Array[LearnedMove]:
result.append(lm) result.append(lm)
result.sort_custom(func(a : LearnedMove, b : LearnedMove) -> bool: return a.level < b.level) result.sort_custom(func(a : LearnedMove, b : LearnedMove) -> bool: return a.level < b.level)
return result 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

61
editor/type_importer.gd Normal file
View file

@ -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

View file

@ -0,0 +1 @@
uid://ceb3knywwobbt

View file

@ -45,6 +45,9 @@ static func max_concurrent() -> int:
static func moves_dir() -> String: return data_dir() + "/moves/" static func moves_dir() -> String: return data_dir() + "/moves/"
static func pokemon_dir() -> String: return data_dir() + "/pokemon/" static func pokemon_dir() -> String: return data_dir() + "/pokemon/"
static func sprites_dir() -> String: return data_dir() + "/sprites/" 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" # nommage des fichiers pokémon : "0025_pikachu"
static func pokemon_stem(id : int, api_name : String) -> String: static func pokemon_stem(id : int, api_name : String) -> String: