Tales-of-PKMN/game/pokemon/pokemon_instance.gd

204 lines
7.3 KiB
GDScript

class_name PokemonInstance
extends Resource
signal hp_changed(new_hp : int, max_hp : int)
signal fainted
enum Status { NONE, BURN, FREEZE, PARALYSIS, POISON, BAD_POISON, SLEEP }
const MAX_ACTIVE_MOVES : int = 4
const MAX_LEVEL : int = 100
@export var species : PokemonData
@export var nickname : String = ""
@export_range(1, 100) var level : int = 1
@export var nature : PokemonNature.Nature = PokemonNature.Nature.HARDY
@export var current_hp : int = 1
@export var status : Status = Status.NONE
@export_group("Capacités")
@export var learned_moves : Array[MoveData] = [] #toutes les capacites debloquees
@export var active_moves : Array[MoveSlot] = [] #les 4 equipees pour le combat (tirees de learned_moves)
@export_group("Monture")
@export var mount_data : MountProfile = null #null = ne peut pas etre monte
#---------- creation ----------
static func create(p_species : PokemonData, p_level : int, p_nature : int = -1, p_mount_data : MountProfile = null) -> PokemonInstance:
var p := PokemonInstance.new()
p.species = p_species
p.level = clampi(p_level, 1, MAX_LEVEL)
p.nature = (p_nature as PokemonNature.Nature) if p_nature >= 0 else PokemonNature.random_nature()
p.mount_data = p_mount_data
p.fill_moves_for_level()
p.current_hp = p.get_max_hp()
return p
#---------- infos ----------
func get_display_name() -> String:
if nickname != "": return nickname
return species.get_display_name()
func is_fainted() -> bool:
return current_hp <= 0
func can_be_mounted() -> bool:
return mount_data != null and !is_fainted()
#---------- stats ----------
#cles de PokemonData.base_stats (noms PokeAPI) -- A ADAPTER si tes cles sont differentes
const STAT_KEYS : Dictionary = {
PokemonNature.Stat.HP: "hp",
PokemonNature.Stat.ATTACK: "attack",
PokemonNature.Stat.DEFENSE: "defense",
PokemonNature.Stat.SP_ATTACK: "special-attack",
PokemonNature.Stat.SP_DEFENSE: "special-defense",
PokemonNature.Stat.SPEED: "speed",
}
func get_base_stat(stat : PokemonNature.Stat) -> int:
return int(species.base_stats.get(STAT_KEYS[stat], 0))
func get_max_hp() -> int:
#formule officielle avec IV = 0 et EV = 0
return floori(2.0 * get_base_stat(PokemonNature.Stat.HP) * level / 100.0) + level + 10
func get_stat(stat : PokemonNature.Stat) -> int:
if stat == PokemonNature.Stat.HP: return get_max_hp()
var raw : int = floori(2.0 * get_base_stat(stat) * level / 100.0) + 5
return floori(raw * PokemonNature.get_multiplier(nature, stat))
func set_level(new_level : int) -> void:
var old_max : int = get_max_hp()
level = clampi(new_level, 1, MAX_LEVEL)
#un pokemon K.O. le reste ; sinon il gagne la difference de PV max
if current_hp > 0:
current_hp = clampi(current_hp + get_max_hp() - old_max, 1, get_max_hp())
hp_changed.emit(current_hp, get_max_hp())
#---------- PV ----------
func take_damage(amount : int) -> void:
if is_fainted() or amount <= 0: return
current_hp = maxi(current_hp - amount, 0)
hp_changed.emit(current_hp, get_max_hp())
if current_hp == 0: fainted.emit()
func heal(amount : int) -> void:
if is_fainted() or amount <= 0: return #un K.O. se soigne avec revive()
current_hp = mini(current_hp + amount, get_max_hp())
hp_changed.emit(current_hp, get_max_hp())
func revive(hp_ratio : float = 0.5) -> void:
if !is_fainted(): return
current_hp = maxi(1, floori(get_max_hp() * hp_ratio))
status = Status.NONE
hp_changed.emit(current_hp, get_max_hp())
func heal_full() -> void:
current_hp = get_max_hp()
status = Status.NONE
for slot in active_moves: slot.restore()
hp_changed.emit(current_hp, get_max_hp())
#---------- capacites ----------
func learn_move(move : MoveData) -> void:
#debloque la capacite ; l'equipe automatiquement s'il reste une place
if move == null or learned_moves.has(move): return
learned_moves.append(move)
if active_moves.size() < MAX_ACTIVE_MOVES:
active_moves.append(MoveSlot.new(move))
func fill_moves_for_level() -> void:
#debloque toutes les capacites apprises par niveau jusqu'au niveau actuel,
#puis equipe les 4 dernieres (comme un pokemon sauvage ou un dresseur dans les jeux)
learned_moves.clear()
var entries : Array[LearnedMove] = []
for lm in species.learnset:
if lm.move != null and lm.method == "level-up" and lm.level <= level:
entries.append(lm)
entries.sort_custom(func(a : LearnedMove, b : LearnedMove) -> bool: return a.level < b.level)
for lm in entries:
if !learned_moves.has(lm.move): learned_moves.append(lm.move)
var picked : Array[MoveData] = []
for i in range(maxi(0, learned_moves.size() - MAX_ACTIVE_MOVES), learned_moves.size()):
picked.append(learned_moves[i])
set_active_moves(picked)
func set_active_moves(moves : Array[MoveData]) -> void:
#remplace les capacites equipees (max 4) ; les capacites inconnues sont debloquees au passage
active_moves.clear()
for m in moves:
if active_moves.size() >= MAX_ACTIVE_MOVES: break
if m == null or is_move_equipped(m): continue
if !learned_moves.has(m): learned_moves.append(m)
active_moves.append(MoveSlot.new(m))
func is_move_equipped(move : MoveData) -> bool:
for slot in active_moves:
if slot.move == move: return true
return false
func equip_move(move : MoveData, slot_index : int) -> bool:
#equipe une capacite deja debloquee dans un des 4 emplacements
if !learned_moves.has(move) or is_move_equipped(move): return false
if slot_index < 0 or slot_index >= MAX_ACTIVE_MOVES: return false
if slot_index >= active_moves.size():
active_moves.append(MoveSlot.new(move))
else:
active_moves[slot_index] = MoveSlot.new(move)
return true
#---------- sauvegarde ----------
#on ne stocke que des ids/noms d'API ; Pokedex retrouve les ressources au chargement
func to_dict() -> Dictionary:
var learned : Array = []
for m in learned_moves: learned.append(m.api_name)
var slots : Array = []
for s in active_moves: slots.append({"move": s.move.api_name, "pp": s.current_pp})
return {
"species": species.api_name,
"nickname": nickname,
"level": level,
"nature": int(nature),
"hp": current_hp,
"status": int(status),
"learned_moves": learned,
"active_moves": slots,
"mount_data": mount_data.resource_path if mount_data else "",
}
static func from_dict(data : Dictionary) -> PokemonInstance:
var species_data : PokemonData = Pokedex.get_pokemon(data.get("species", ""))
if species_data == null: return null
var p := PokemonInstance.new()
p.species = species_data
p.nickname = data.get("nickname", "")
p.level = clampi(int(data.get("level", 1)), 1, MAX_LEVEL)
p.nature = clampi(int(data.get("nature", 0)), 0, PokemonNature.Nature.size() - 1) as PokemonNature.Nature
p.status = clampi(int(data.get("status", 0)), 0, Status.size() - 1) as Status
for move_name in data.get("learned_moves", []):
var m : MoveData = Pokedex.get_move(str(move_name))
if m: p.learned_moves.append(m)
for slot_data in data.get("active_moves", []):
var m : MoveData = Pokedex.get_move(str(slot_data.get("move", "")))
if m == null: continue
var slot := MoveSlot.new(m)
slot.current_pp = clampi(int(slot_data.get("pp", m.pp)), 0, m.pp)
p.active_moves.append(slot)
#seuls les chemins res:// sont charges (jamais de ressource depuis user://)
var mount_path : String = data.get("mount_data", "")
if mount_path.begins_with("res://") and ResourceLoader.exists(mount_path):
p.mount_data = load(mount_path) as MountProfile
p.current_hp = clampi(int(data.get("hp", p.get_max_hp())), 0, p.get_max_hp())
return p