205 lines
7.3 KiB
GDScript
205 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
|