Compare commits
No commits in common. "a0c32cba4d506ac29dcf55419379d3ed3c843c16" and "53bd8b701fd723ae22723b513c321d40a6393b85" have entirely different histories.
a0c32cba4d
...
53bd8b701f
|
|
@ -2,26 +2,13 @@ extends Node
|
||||||
# Autoload : GameData
|
# Autoload : GameData
|
||||||
|
|
||||||
var story_progress : int = 0 # 0 = début, 1 = après 1er badge, 2 = après 2e badge, etc.
|
var story_progress : int = 0 # 0 = début, 1 = après 1er badge, 2 = après 2e badge, etc.
|
||||||
var player_team : PokemonTeam = PokemonTeam.new()
|
|
||||||
var player_storage : PokemonStorage = PokemonStorage.new()
|
|
||||||
|
|
||||||
func give_pokemon(pokemon : PokemonInstance) -> PokemonStorage.ReceiveResult:
|
|
||||||
#point d'entree unique pour obtenir un pokemon (starter, cadeau, capture...) :
|
|
||||||
#equipe si elle a de la place, sinon PC. A l'appelant d'informer le joueur selon le resultat.
|
|
||||||
return player_storage.receive_pokemon(player_team, pokemon)
|
|
||||||
|
|
||||||
func save() -> void:
|
func save() -> void:
|
||||||
var config := ConfigFile.new()
|
var config := ConfigFile.new()
|
||||||
config.set_value("progress", "story_progress", story_progress)
|
config.set_value("progress", "story_progress", story_progress)
|
||||||
config.set_value("team", "data", player_team.to_dict())
|
|
||||||
config.set_value("storage", "data", player_storage.to_dict())
|
|
||||||
config.save("user://save.cfg")
|
config.save("user://save.cfg")
|
||||||
|
|
||||||
func load_data() -> void:
|
func load_data() -> void:
|
||||||
var config := ConfigFile.new()
|
var config := ConfigFile.new()
|
||||||
if config.load("user://save.cfg") == OK:
|
if config.load("user://save.cfg") == OK:
|
||||||
story_progress = config.get_value("progress", "story_progress", 0)
|
story_progress = config.get_value("progress", "story_progress", 0)
|
||||||
var team_data : Variant = config.get_value("team", "data", {})
|
|
||||||
player_team = PokemonTeam.from_dict(team_data) if team_data is Dictionary else PokemonTeam.new()
|
|
||||||
var storage_data : Variant = config.get_value("storage", "data", {})
|
|
||||||
player_storage = PokemonStorage.from_dict(storage_data) if storage_data is Dictionary else PokemonStorage.new()
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
class_name MoveSlot
|
|
||||||
extends Resource
|
|
||||||
|
|
||||||
#une capacite equipee (active) d'un pokemon, avec ses PP actuels
|
|
||||||
|
|
||||||
@export var move : MoveData
|
|
||||||
@export var current_pp : int = 0
|
|
||||||
|
|
||||||
var max_pp : int:
|
|
||||||
get: return move.pp if move else 0
|
|
||||||
|
|
||||||
func _init(p_move : MoveData = null) -> void:
|
|
||||||
move = p_move
|
|
||||||
if move: current_pp = move.pp
|
|
||||||
|
|
||||||
func has_pp() -> bool:
|
|
||||||
return current_pp > 0
|
|
||||||
|
|
||||||
func use() -> bool:
|
|
||||||
if current_pp <= 0: return false
|
|
||||||
current_pp -= 1
|
|
||||||
return true
|
|
||||||
|
|
||||||
func restore() -> void:
|
|
||||||
current_pp = max_pp
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
class_name PokemonNature
|
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
enum Stat { HP, ATTACK, DEFENSE, SP_ATTACK, SP_DEFENSE, SPEED }
|
|
||||||
|
|
||||||
#meme ordre que dans les jeux officiels (25 natures)
|
|
||||||
enum Nature {
|
|
||||||
HARDY, LONELY, BRAVE, ADAMANT, NAUGHTY,
|
|
||||||
BOLD, DOCILE, RELAXED, IMPISH, LAX,
|
|
||||||
TIMID, HASTY, SERIOUS, JOLLY, NAIVE,
|
|
||||||
MODEST, MILD, QUIET, BASHFUL, RASH,
|
|
||||||
CALM, GENTLE, SASSY, CAREFUL, QUIRKY,
|
|
||||||
}
|
|
||||||
|
|
||||||
#ordre des stats dans la table des natures : index // 5 = stat augmentee, index % 5 = stat diminuee
|
|
||||||
#(si les deux sont identiques, la nature est neutre)
|
|
||||||
const _STAT_ORDER : Array[int] = [Stat.ATTACK, Stat.DEFENSE, Stat.SPEED, Stat.SP_ATTACK, Stat.SP_DEFENSE]
|
|
||||||
|
|
||||||
static func get_multiplier(nature : Nature, stat : Stat) -> float:
|
|
||||||
var up_idx : int = floori(int(nature) / 5.0)
|
|
||||||
var down_idx : int = int(nature) % 5
|
|
||||||
if up_idx == down_idx: return 1.0
|
|
||||||
if stat == _STAT_ORDER[up_idx]: return 1.1
|
|
||||||
if stat == _STAT_ORDER[down_idx]: return 0.9
|
|
||||||
return 1.0
|
|
||||||
|
|
||||||
static func random_nature() -> Nature:
|
|
||||||
return Nature.values().pick_random() as Nature
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
class_name PokemonTeam
|
|
||||||
extends Resource
|
|
||||||
|
|
||||||
signal team_changed
|
|
||||||
signal pokemon_added(pokemon : PokemonInstance, index : int)
|
|
||||||
signal pokemon_removed(pokemon : PokemonInstance, index : int)
|
|
||||||
|
|
||||||
const MAX_SIZE : int = 6
|
|
||||||
|
|
||||||
@export var members : Array[PokemonInstance] = []
|
|
||||||
|
|
||||||
#---------- infos ----------
|
|
||||||
|
|
||||||
func size() -> int:
|
|
||||||
return members.size()
|
|
||||||
|
|
||||||
func is_empty() -> bool:
|
|
||||||
return members.is_empty()
|
|
||||||
|
|
||||||
func is_full() -> bool:
|
|
||||||
return members.size() >= MAX_SIZE
|
|
||||||
|
|
||||||
func get_pokemon(index : int) -> PokemonInstance:
|
|
||||||
return members[index] if _is_valid_index(index) else null
|
|
||||||
|
|
||||||
#---------- gestion ----------
|
|
||||||
|
|
||||||
func add_pokemon(pokemon : PokemonInstance) -> bool:
|
|
||||||
#renvoie false si l'equipe est pleine (l'appelant decide quoi en faire)
|
|
||||||
if pokemon == null or is_full() or members.has(pokemon): return false
|
|
||||||
members.append(pokemon)
|
|
||||||
pokemon_added.emit(pokemon, members.size() - 1)
|
|
||||||
team_changed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
func remove_at(index : int) -> PokemonInstance:
|
|
||||||
if !_is_valid_index(index): return null
|
|
||||||
var pokemon : PokemonInstance = members[index]
|
|
||||||
members.remove_at(index)
|
|
||||||
pokemon_removed.emit(pokemon, index)
|
|
||||||
team_changed.emit()
|
|
||||||
return pokemon
|
|
||||||
|
|
||||||
func swap(index_a : int, index_b : int) -> bool:
|
|
||||||
if index_a == index_b or !_is_valid_index(index_a) or !_is_valid_index(index_b): return false
|
|
||||||
var tmp : PokemonInstance = members[index_a]
|
|
||||||
members[index_a] = members[index_b]
|
|
||||||
members[index_b] = tmp
|
|
||||||
team_changed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
func replace_at(index : int, pokemon : PokemonInstance) -> PokemonInstance:
|
|
||||||
#remplace le pokemon a cet index et renvoie l'ancien (utilise pour les echanges avec le PC)
|
|
||||||
if pokemon == null or !_is_valid_index(index) or members.has(pokemon): return null
|
|
||||||
var old : PokemonInstance = members[index]
|
|
||||||
members[index] = pokemon
|
|
||||||
pokemon_removed.emit(old, index)
|
|
||||||
pokemon_added.emit(pokemon, index)
|
|
||||||
team_changed.emit()
|
|
||||||
return old
|
|
||||||
|
|
||||||
#---------- combat ----------
|
|
||||||
|
|
||||||
func get_first_able_index() -> int:
|
|
||||||
for i in members.size():
|
|
||||||
if !members[i].is_fainted(): return i
|
|
||||||
return -1
|
|
||||||
|
|
||||||
func get_first_able() -> PokemonInstance:
|
|
||||||
return get_pokemon(get_first_able_index())
|
|
||||||
|
|
||||||
func has_able_pokemon() -> bool:
|
|
||||||
return get_first_able_index() != -1
|
|
||||||
|
|
||||||
func get_able_pokemon() -> Array[PokemonInstance]:
|
|
||||||
var result : Array[PokemonInstance] = []
|
|
||||||
for p in members:
|
|
||||||
if !p.is_fainted(): result.append(p)
|
|
||||||
return result
|
|
||||||
|
|
||||||
func heal_all() -> void:
|
|
||||||
for p in members: p.heal_full()
|
|
||||||
|
|
||||||
#---------- montures ----------
|
|
||||||
|
|
||||||
func get_mountable() -> Array[PokemonInstance]:
|
|
||||||
var result : Array[PokemonInstance] = []
|
|
||||||
for p in members:
|
|
||||||
if p.can_be_mounted(): result.append(p)
|
|
||||||
return result
|
|
||||||
|
|
||||||
func has_mountable() -> bool:
|
|
||||||
for p in members:
|
|
||||||
if p.can_be_mounted(): return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
#---------- interne ----------
|
|
||||||
|
|
||||||
func _is_valid_index(index : int) -> bool:
|
|
||||||
return index >= 0 and index < members.size()
|
|
||||||
|
|
||||||
#---------- sauvegarde ----------
|
|
||||||
|
|
||||||
func to_dict() -> Dictionary:
|
|
||||||
var list : Array = []
|
|
||||||
for p in members: list.append(p.to_dict())
|
|
||||||
return {"members": list}
|
|
||||||
|
|
||||||
static func from_dict(data : Dictionary) -> PokemonTeam:
|
|
||||||
var team := PokemonTeam.new()
|
|
||||||
for member_data in data.get("members", []):
|
|
||||||
var p := PokemonInstance.from_dict(member_data)
|
|
||||||
if p: team.add_pokemon(p)
|
|
||||||
return team
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
class_name TrainerData
|
|
||||||
extends Resource
|
|
||||||
|
|
||||||
#modele d'un dresseur PNJ : l'equipe reelle est fabriquee au debut du combat
|
|
||||||
|
|
||||||
@export var id : StringName
|
|
||||||
@export var names : Dictionary = {} # {"fr": "Pierre", "en": "Brock"}
|
|
||||||
@export var party : Array[TrainerPartyEntry] = []
|
|
||||||
|
|
||||||
func get_display_name() -> String:
|
|
||||||
return L10n.pick(names, String(id))
|
|
||||||
|
|
||||||
func create_team() -> PokemonTeam:
|
|
||||||
var team := PokemonTeam.new()
|
|
||||||
if party.size() > PokemonTeam.MAX_SIZE:
|
|
||||||
push_warning("TrainerData '%s' : %d pokémon dans le modèle, seuls les %d premiers seront utilisés." % [id, party.size(), PokemonTeam.MAX_SIZE])
|
|
||||||
for entry in party:
|
|
||||||
if entry == null: continue
|
|
||||||
var p := entry.create_instance()
|
|
||||||
if p and !team.add_pokemon(p): break #equipe pleine
|
|
||||||
return team
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
class_name TrainerPartyEntry
|
|
||||||
extends Resource
|
|
||||||
|
|
||||||
#un pokemon de l'equipe d'un dresseur PNJ (modele, pas un pokemon reel)
|
|
||||||
|
|
||||||
@export var species : PokemonData
|
|
||||||
@export_range(1, 100) var level : int = 5
|
|
||||||
@export var nickname : String = ""
|
|
||||||
|
|
||||||
@export_group("Nature")
|
|
||||||
@export var random_nature : bool = true
|
|
||||||
@export var nature : PokemonNature.Nature = PokemonNature.Nature.HARDY
|
|
||||||
|
|
||||||
@export_group("Capacités")
|
|
||||||
## Vide = capacites automatiques selon le niveau. Sinon : ces capacites (max 4) sont equipees.
|
|
||||||
@export var forced_moves : Array[MoveData] = []
|
|
||||||
|
|
||||||
func create_instance() -> PokemonInstance:
|
|
||||||
if species == null: return null
|
|
||||||
var nature_value : int = -1 if random_nature else int(nature)
|
|
||||||
var p := PokemonInstance.create(species, level, nature_value)
|
|
||||||
p.nickname = nickname
|
|
||||||
if !forced_moves.is_empty():
|
|
||||||
p.set_active_moves(forced_moves)
|
|
||||||
return p
|
|
||||||
|
|
@ -1,484 +0,0 @@
|
||||||
class_name PCScreen
|
|
||||||
extends Control
|
|
||||||
|
|
||||||
#interface du PC : equipe a gauche, boite courante au centre, fiche du pokemon a droite.
|
|
||||||
#manette d'abord (navigation par focus), clavier et souris fonctionnent aussi.
|
|
||||||
#
|
|
||||||
#usage :
|
|
||||||
# var pc := PCScreen.new()
|
|
||||||
# canvas_layer.add_child(pc)
|
|
||||||
# pc.setup(GameData.player_team, GameData.player_storage)
|
|
||||||
# pc.closed.connect(pc.queue_free)
|
|
||||||
|
|
||||||
signal closed
|
|
||||||
|
|
||||||
const ControllerIconTextureScript = preload("res://addons/controller_icons/objects/ControllerIconTexture.gd")
|
|
||||||
|
|
||||||
const KIND_TEAM : int = 0
|
|
||||||
const KIND_BOX : int = 1
|
|
||||||
const NO_LOC : Vector3i = Vector3i(-1, -1, -1) #(type, index/boite, emplacement)
|
|
||||||
const GRID_COLUMNS : int = 6
|
|
||||||
|
|
||||||
#actions propres au PC : creees automatiquement si elles n'existent pas dans l'Input Map
|
|
||||||
@export var prev_box_action : StringName = &"pc_prev_box" #LB / PageUp
|
|
||||||
@export var next_box_action : StringName = &"pc_next_box" #RB / PageDown
|
|
||||||
@export var release_action : StringName = &"pc_release" #Y / Suppr
|
|
||||||
|
|
||||||
var _team : PokemonTeam
|
|
||||||
var _storage : PokemonStorage
|
|
||||||
var _box_index : int = 0
|
|
||||||
|
|
||||||
var _held : PokemonInstance = null #pokemon "dans la main" (pris, pas encore pose)
|
|
||||||
var _held_loc : Vector3i = NO_LOC
|
|
||||||
var _focus_loc : Vector3i = NO_LOC
|
|
||||||
var _confirming : bool = false
|
|
||||||
var _release_loc : Vector3i = NO_LOC
|
|
||||||
|
|
||||||
var _team_buttons : Array[Button] = []
|
|
||||||
var _box_buttons : Array[Button] = []
|
|
||||||
|
|
||||||
var _grid : GridContainer
|
|
||||||
var _box_label : Label
|
|
||||||
var _info_sprite : TextureRect
|
|
||||||
var _info_label : Label
|
|
||||||
var _message_label : Label
|
|
||||||
var _message_tween : Tween
|
|
||||||
var _accept_hint : Button
|
|
||||||
var _cancel_hint : Button
|
|
||||||
var _confirm_overlay : CenterContainer
|
|
||||||
var _confirm_label : Label
|
|
||||||
var _confirm_no : Button
|
|
||||||
var _confirm_yes : Button
|
|
||||||
|
|
||||||
var _prev_mouse_mode : Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
|
||||||
var _mouse_saved : bool = false
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
process_mode = Node.PROCESS_MODE_ALWAYS #fonctionne meme si le jeu est en pause
|
|
||||||
_ensure_actions()
|
|
||||||
_build_ui()
|
|
||||||
|
|
||||||
#le curseur doit etre visible pour la souris (le jeu le capture peut-etre pour la camera)
|
|
||||||
_prev_mouse_mode = Input.mouse_mode
|
|
||||||
_mouse_saved = true
|
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
_restore_mouse()
|
|
||||||
|
|
||||||
func setup(team : PokemonTeam, storage : PokemonStorage) -> void:
|
|
||||||
if !is_node_ready(): await ready
|
|
||||||
_team = team
|
|
||||||
_storage = storage
|
|
||||||
_team.team_changed.connect(_refresh)
|
|
||||||
_storage.storage_changed.connect(_refresh)
|
|
||||||
_rebuild_grid()
|
|
||||||
_refresh()
|
|
||||||
_team_buttons[0].grab_focus.call_deferred()
|
|
||||||
|
|
||||||
#---------- actions d'entree ----------
|
|
||||||
|
|
||||||
func _ensure_actions() -> void:
|
|
||||||
_ensure_action(prev_box_action, [_joy_event(JOY_BUTTON_LEFT_SHOULDER), _key_event(KEY_PAGEUP)])
|
|
||||||
_ensure_action(next_box_action, [_joy_event(JOY_BUTTON_RIGHT_SHOULDER), _key_event(KEY_PAGEDOWN)])
|
|
||||||
_ensure_action(release_action, [_joy_event(JOY_BUTTON_Y), _key_event(KEY_DELETE)])
|
|
||||||
|
|
||||||
func _ensure_action(action : StringName, events : Array[InputEvent]) -> void:
|
|
||||||
if InputMap.has_action(action): return
|
|
||||||
InputMap.add_action(action)
|
|
||||||
for e in events: InputMap.action_add_event(action, e)
|
|
||||||
|
|
||||||
func _joy_event(button : JoyButton) -> InputEvent:
|
|
||||||
var e := InputEventJoypadButton.new()
|
|
||||||
e.button_index = button
|
|
||||||
return e
|
|
||||||
|
|
||||||
func _key_event(key : Key) -> InputEvent:
|
|
||||||
var e := InputEventKey.new()
|
|
||||||
e.physical_keycode = key
|
|
||||||
return e
|
|
||||||
|
|
||||||
func _unhandled_input(event : InputEvent) -> void:
|
|
||||||
if !visible or _team == null: return
|
|
||||||
|
|
||||||
if _confirming:
|
|
||||||
if event.is_action_pressed(&"ui_cancel"):
|
|
||||||
_close_confirm()
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
return
|
|
||||||
|
|
||||||
if event.is_action_pressed(&"ui_cancel"): _on_cancel()
|
|
||||||
elif event.is_action_pressed(prev_box_action): _change_box(-1)
|
|
||||||
elif event.is_action_pressed(next_box_action): _change_box(1)
|
|
||||||
elif event.is_action_pressed(release_action): _ask_release()
|
|
||||||
else: return
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
|
|
||||||
#---------- construction de l'interface ----------
|
|
||||||
|
|
||||||
func _build_ui() -> void:
|
|
||||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
||||||
|
|
||||||
var bg := ColorRect.new()
|
|
||||||
bg.color = Color(0, 0, 0, 0.8)
|
|
||||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
||||||
add_child(bg)
|
|
||||||
|
|
||||||
var margin := MarginContainer.new()
|
|
||||||
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
||||||
for side in ["left", "top", "right", "bottom"]:
|
|
||||||
margin.add_theme_constant_override("margin_" + side, 40)
|
|
||||||
add_child(margin)
|
|
||||||
|
|
||||||
var root := VBoxContainer.new()
|
|
||||||
root.add_theme_constant_override("separation", 12)
|
|
||||||
margin.add_child(root)
|
|
||||||
|
|
||||||
var columns := HBoxContainer.new()
|
|
||||||
columns.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
||||||
columns.add_theme_constant_override("separation", 24)
|
|
||||||
root.add_child(columns)
|
|
||||||
|
|
||||||
#equipe
|
|
||||||
var team_col := VBoxContainer.new()
|
|
||||||
columns.add_child(team_col)
|
|
||||||
team_col.add_child(_make_title("Équipe"))
|
|
||||||
for i in PokemonTeam.MAX_SIZE:
|
|
||||||
var b := _make_slot_button(Vector2(300, 76))
|
|
||||||
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
|
||||||
b.pressed.connect(_on_team_slot_pressed.bind(i))
|
|
||||||
b.focus_entered.connect(_on_team_slot_focused.bind(i))
|
|
||||||
team_col.add_child(b)
|
|
||||||
_team_buttons.append(b)
|
|
||||||
|
|
||||||
#boite
|
|
||||||
var box_col := VBoxContainer.new()
|
|
||||||
box_col.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
||||||
columns.add_child(box_col)
|
|
||||||
_box_label = _make_title("")
|
|
||||||
_box_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
box_col.add_child(_box_label)
|
|
||||||
_grid = GridContainer.new()
|
|
||||||
_grid.columns = GRID_COLUMNS
|
|
||||||
box_col.add_child(_grid)
|
|
||||||
|
|
||||||
#fiche
|
|
||||||
var info_panel := PanelContainer.new()
|
|
||||||
info_panel.custom_minimum_size = Vector2(280, 0)
|
|
||||||
columns.add_child(info_panel)
|
|
||||||
var info_box := VBoxContainer.new()
|
|
||||||
info_panel.add_child(info_box)
|
|
||||||
_info_sprite = TextureRect.new()
|
|
||||||
_info_sprite.custom_minimum_size = Vector2(160, 160)
|
|
||||||
_info_sprite.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
||||||
_info_sprite.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
||||||
info_box.add_child(_info_sprite)
|
|
||||||
_info_label = Label.new()
|
|
||||||
_info_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
||||||
info_box.add_child(_info_label)
|
|
||||||
|
|
||||||
#message temporaire + aide des touches
|
|
||||||
_message_label = Label.new()
|
|
||||||
_message_label.modulate.a = 0.0
|
|
||||||
root.add_child(_message_label)
|
|
||||||
|
|
||||||
var hints := HBoxContainer.new()
|
|
||||||
hints.add_theme_constant_override("separation", 24)
|
|
||||||
root.add_child(hints)
|
|
||||||
_accept_hint = _make_hint(hints, &"ui_accept", "Prendre")
|
|
||||||
_accept_hint.mouse_filter = Control.MOUSE_FILTER_IGNORE #la souris clique directement sur les emplacements
|
|
||||||
_cancel_hint = _make_hint(hints, &"ui_cancel", "Fermer", _on_cancel)
|
|
||||||
_make_hint(hints, prev_box_action, "Boîte précédente", _change_box.bind(-1))
|
|
||||||
_make_hint(hints, next_box_action, "Boîte suivante", _change_box.bind(1))
|
|
||||||
_make_hint(hints, release_action, "Relâcher", _ask_release)
|
|
||||||
|
|
||||||
#confirmation du relachement (focus sur "Non" par defaut)
|
|
||||||
_confirm_overlay = CenterContainer.new()
|
|
||||||
_confirm_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
||||||
_confirm_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
||||||
_confirm_overlay.hide()
|
|
||||||
add_child(_confirm_overlay)
|
|
||||||
var confirm_panel := PanelContainer.new()
|
|
||||||
_confirm_overlay.add_child(confirm_panel)
|
|
||||||
var confirm_box := VBoxContainer.new()
|
|
||||||
confirm_box.add_theme_constant_override("separation", 12)
|
|
||||||
confirm_panel.add_child(confirm_box)
|
|
||||||
_confirm_label = Label.new()
|
|
||||||
_confirm_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
confirm_box.add_child(_confirm_label)
|
|
||||||
var confirm_buttons := HBoxContainer.new()
|
|
||||||
confirm_buttons.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
||||||
confirm_buttons.add_theme_constant_override("separation", 16)
|
|
||||||
confirm_box.add_child(confirm_buttons)
|
|
||||||
_confirm_no = Button.new()
|
|
||||||
_confirm_no.text = "Non"
|
|
||||||
_confirm_no.custom_minimum_size = Vector2(120, 44)
|
|
||||||
_confirm_no.pressed.connect(_close_confirm)
|
|
||||||
confirm_buttons.add_child(_confirm_no)
|
|
||||||
_confirm_yes = Button.new()
|
|
||||||
_confirm_yes.text = "Oui"
|
|
||||||
_confirm_yes.custom_minimum_size = Vector2(120, 44)
|
|
||||||
_confirm_yes.pressed.connect(_confirm_release)
|
|
||||||
confirm_buttons.add_child(_confirm_yes)
|
|
||||||
|
|
||||||
func _make_title(text : String) -> Label:
|
|
||||||
var l := Label.new()
|
|
||||||
l.text = text
|
|
||||||
l.add_theme_font_size_override("font_size", 24)
|
|
||||||
return l
|
|
||||||
|
|
||||||
func _make_slot_button(min_size : Vector2) -> Button:
|
|
||||||
var b := Button.new()
|
|
||||||
b.custom_minimum_size = min_size
|
|
||||||
b.expand_icon = true
|
|
||||||
b.clip_text = true
|
|
||||||
b.add_theme_constant_override("icon_max_width", int(min_size.y) - 12)
|
|
||||||
b.mouse_entered.connect(b.grab_focus) #la souris deplace le focus (la fiche suit le survol)
|
|
||||||
return b
|
|
||||||
|
|
||||||
func _make_hint(parent : Control, action : StringName, text : String, callback : Callable = Callable()) -> Button:
|
|
||||||
#icone qui s'adapte au peripherique (addon controller_icons) + texte ; cliquable a la souris
|
|
||||||
var b := Button.new()
|
|
||||||
b.flat = true
|
|
||||||
b.focus_mode = Control.FOCUS_NONE
|
|
||||||
b.text = text
|
|
||||||
var tex : Texture2D = ControllerIconTextureScript.new()
|
|
||||||
tex.set("path", String(action))
|
|
||||||
b.icon = tex
|
|
||||||
b.expand_icon = true
|
|
||||||
b.add_theme_constant_override("icon_max_width", 32)
|
|
||||||
if callback.is_valid(): b.pressed.connect(callback)
|
|
||||||
parent.add_child(b)
|
|
||||||
return b
|
|
||||||
|
|
||||||
func _rebuild_grid() -> void:
|
|
||||||
#recree les emplacements si la boite courante n'a pas la meme taille
|
|
||||||
var box : PokemonBox = _storage.get_box(_box_index)
|
|
||||||
if box == null or _box_buttons.size() == box.size(): return
|
|
||||||
|
|
||||||
var focus_owner : Control = get_viewport().gui_get_focus_owner()
|
|
||||||
var had_focus : bool = focus_owner != null and _grid.is_ancestor_of(focus_owner)
|
|
||||||
|
|
||||||
for b in _box_buttons:
|
|
||||||
_grid.remove_child(b)
|
|
||||||
b.queue_free()
|
|
||||||
_box_buttons.clear()
|
|
||||||
|
|
||||||
for i in box.size():
|
|
||||||
var b := _make_slot_button(Vector2(76, 76))
|
|
||||||
b.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
b.pressed.connect(_on_box_slot_pressed.bind(i))
|
|
||||||
b.focus_entered.connect(_on_box_slot_focused.bind(i))
|
|
||||||
_grid.add_child(b)
|
|
||||||
_box_buttons.append(b)
|
|
||||||
|
|
||||||
if had_focus and !_box_buttons.is_empty():
|
|
||||||
_box_buttons[0].grab_focus.call_deferred()
|
|
||||||
|
|
||||||
#---------- affichage ----------
|
|
||||||
|
|
||||||
func _refresh() -> void:
|
|
||||||
if _team == null: return
|
|
||||||
|
|
||||||
for i in _team_buttons.size():
|
|
||||||
_style_slot(_team_buttons[i], _team.get_pokemon(i), true)
|
|
||||||
|
|
||||||
var box : PokemonBox = _storage.get_box(_box_index)
|
|
||||||
if box:
|
|
||||||
var title : String = box.box_name if box.box_name != "" else "Boîte %d" % (_box_index + 1)
|
|
||||||
_box_label.text = "%s (%d/%d)" % [title, box.count(), box.size()]
|
|
||||||
for i in _box_buttons.size():
|
|
||||||
_style_slot(_box_buttons[i], box.get_pokemon(i), false)
|
|
||||||
|
|
||||||
_accept_hint.text = "Poser" if _held else "Prendre"
|
|
||||||
_cancel_hint.text = "Annuler" if _held else "Fermer"
|
|
||||||
_update_info()
|
|
||||||
|
|
||||||
func _style_slot(btn : Button, p : PokemonInstance, is_team : bool) -> void:
|
|
||||||
btn.modulate = Color.WHITE
|
|
||||||
if p == null:
|
|
||||||
btn.icon = null
|
|
||||||
btn.text = "Vide" if is_team else ""
|
|
||||||
btn.tooltip_text = ""
|
|
||||||
return
|
|
||||||
|
|
||||||
btn.icon = p.species.get_menu_sprite(false)
|
|
||||||
btn.tooltip_text = "%s Niv.%d" % [p.get_display_name(), p.level]
|
|
||||||
if is_team:
|
|
||||||
btn.text = "%s Niv.%d\nPV %d/%d" % [p.get_display_name(), p.level, p.current_hp, p.get_max_hp()]
|
|
||||||
else:
|
|
||||||
btn.text = ""
|
|
||||||
|
|
||||||
if p.is_fainted(): btn.modulate = Color(1.0, 0.55, 0.55)
|
|
||||||
if p == _held: btn.modulate = Color(btn.modulate, 0.35) #pris : on le voit "souleve"
|
|
||||||
|
|
||||||
func _update_info() -> void:
|
|
||||||
var p : PokemonInstance = _get_at(_focus_loc)
|
|
||||||
if p == null:
|
|
||||||
_info_sprite.texture = null
|
|
||||||
_info_label.text = ""
|
|
||||||
return
|
|
||||||
|
|
||||||
_info_sprite.texture = p.species.get_menu_sprite(false)
|
|
||||||
var lines : Array[String] = []
|
|
||||||
lines.append("%s Niv.%d" % [p.get_display_name(), p.level])
|
|
||||||
lines.append("PV %d / %d" % [p.current_hp, p.get_max_hp()])
|
|
||||||
lines.append("Nature : %s" % String(PokemonNature.Nature.keys()[p.nature]).capitalize())
|
|
||||||
if p.can_be_mounted(): lines.append("Peut être monté")
|
|
||||||
lines.append("")
|
|
||||||
for slot in p.active_moves:
|
|
||||||
lines.append("%s %d/%d" % [slot.move.get_display_name(), slot.current_pp, slot.max_pp])
|
|
||||||
_info_label.text = "\n".join(lines)
|
|
||||||
|
|
||||||
func _show_message(text : String) -> void:
|
|
||||||
_message_label.text = text
|
|
||||||
_message_label.modulate.a = 1.0
|
|
||||||
if _message_tween: _message_tween.kill()
|
|
||||||
_message_tween = create_tween()
|
|
||||||
_message_tween.tween_interval(2.5)
|
|
||||||
_message_tween.tween_property(_message_label, "modulate:a", 0.0, 0.5)
|
|
||||||
|
|
||||||
#---------- emplacements ----------
|
|
||||||
|
|
||||||
func _get_at(loc : Vector3i) -> PokemonInstance:
|
|
||||||
if _team == null: return null
|
|
||||||
match loc.x:
|
|
||||||
KIND_TEAM: return _team.get_pokemon(loc.y)
|
|
||||||
KIND_BOX: return _storage.get_pokemon(loc.y, loc.z)
|
|
||||||
return null
|
|
||||||
|
|
||||||
func _focus_button_for(loc : Vector3i) -> Button:
|
|
||||||
if loc.x == KIND_TEAM and loc.y >= 0 and loc.y < _team_buttons.size(): return _team_buttons[loc.y]
|
|
||||||
if loc.x == KIND_BOX and loc.y == _box_index and loc.z >= 0 and loc.z < _box_buttons.size(): return _box_buttons[loc.z]
|
|
||||||
return null
|
|
||||||
|
|
||||||
func _set_slots_focusable(on : bool) -> void:
|
|
||||||
var mode : Control.FocusMode = Control.FOCUS_ALL if on else Control.FOCUS_NONE
|
|
||||||
for b in _team_buttons: b.focus_mode = mode
|
|
||||||
for b in _box_buttons: b.focus_mode = mode
|
|
||||||
|
|
||||||
func _on_team_slot_focused(i : int) -> void:
|
|
||||||
_focus_loc = Vector3i(KIND_TEAM, i, 0)
|
|
||||||
_update_info()
|
|
||||||
|
|
||||||
func _on_box_slot_focused(i : int) -> void:
|
|
||||||
_focus_loc = Vector3i(KIND_BOX, _box_index, i)
|
|
||||||
_update_info()
|
|
||||||
|
|
||||||
func _on_team_slot_pressed(i : int) -> void:
|
|
||||||
_on_slot_pressed(Vector3i(KIND_TEAM, i, 0))
|
|
||||||
|
|
||||||
func _on_box_slot_pressed(i : int) -> void:
|
|
||||||
_on_slot_pressed(Vector3i(KIND_BOX, _box_index, i))
|
|
||||||
|
|
||||||
#---------- prendre / poser ----------
|
|
||||||
|
|
||||||
func _on_slot_pressed(loc : Vector3i) -> void:
|
|
||||||
if _confirming: return
|
|
||||||
var target : PokemonInstance = _get_at(loc)
|
|
||||||
|
|
||||||
if _held == null:
|
|
||||||
if target == null: return
|
|
||||||
_held = target
|
|
||||||
_held_loc = loc
|
|
||||||
_refresh()
|
|
||||||
return
|
|
||||||
|
|
||||||
if loc == _held_loc:
|
|
||||||
_cancel_hold()
|
|
||||||
return
|
|
||||||
|
|
||||||
if _attempt_move(_held_loc, loc):
|
|
||||||
_held = null
|
|
||||||
_held_loc = NO_LOC
|
|
||||||
_refresh()
|
|
||||||
|
|
||||||
func _cancel_hold() -> void:
|
|
||||||
_held = null
|
|
||||||
_held_loc = NO_LOC
|
|
||||||
_refresh()
|
|
||||||
|
|
||||||
func _attempt_move(from : Vector3i, to : Vector3i) -> bool:
|
|
||||||
var ok : bool = false
|
|
||||||
if from.x == KIND_TEAM and to.x == KIND_TEAM:
|
|
||||||
if _team.get_pokemon(to.y) == null:
|
|
||||||
_show_message("Choisis un Pokémon de l'équipe avec qui échanger.")
|
|
||||||
return false
|
|
||||||
ok = _team.swap(from.y, to.y)
|
|
||||||
elif from.x == KIND_TEAM and to.x == KIND_BOX:
|
|
||||||
if _storage.get_pokemon(to.y, to.z) == null:
|
|
||||||
ok = _storage.deposit(_team, from.y, to.y, to.z)
|
|
||||||
else:
|
|
||||||
ok = _storage.swap_with_team(_team, from.y, to.y, to.z)
|
|
||||||
elif from.x == KIND_BOX and to.x == KIND_TEAM:
|
|
||||||
if _team.get_pokemon(to.y) == null:
|
|
||||||
ok = _storage.withdraw(_team, from.y, from.z)
|
|
||||||
else:
|
|
||||||
ok = _storage.swap_with_team(_team, to.y, from.y, from.z)
|
|
||||||
else:
|
|
||||||
ok = _storage.move_slot(from.y, from.z, to.y, to.z)
|
|
||||||
|
|
||||||
if !ok:
|
|
||||||
if from.x == KIND_TEAM or to.x == KIND_TEAM:
|
|
||||||
_show_message("Impossible : l'équipe doit garder au moins un Pokémon apte au combat.")
|
|
||||||
else:
|
|
||||||
_show_message("Déplacement impossible.")
|
|
||||||
return ok
|
|
||||||
|
|
||||||
#---------- boites ----------
|
|
||||||
|
|
||||||
func _change_box(direction : int) -> void:
|
|
||||||
if _confirming or _storage == null or _storage.box_count() == 0: return
|
|
||||||
_box_index = posmod(_box_index + direction, _storage.box_count())
|
|
||||||
_rebuild_grid()
|
|
||||||
#si le focus est sur la grille, _focus_loc doit suivre la nouvelle boite
|
|
||||||
if _focus_loc.x == KIND_BOX: _focus_loc = Vector3i(KIND_BOX, _box_index, _focus_loc.z)
|
|
||||||
_refresh()
|
|
||||||
|
|
||||||
#---------- relacher ----------
|
|
||||||
|
|
||||||
func _ask_release() -> void:
|
|
||||||
if _confirming or _held != null: return
|
|
||||||
var p : PokemonInstance = _get_at(_focus_loc)
|
|
||||||
if p == null: return
|
|
||||||
if _focus_loc.x == KIND_TEAM:
|
|
||||||
_show_message("Dépose-le d'abord dans le PC pour le relâcher.")
|
|
||||||
return
|
|
||||||
|
|
||||||
_release_loc = _focus_loc
|
|
||||||
_confirm_label.text = "Relâcher %s ?\nIl ne reviendra pas." % p.get_display_name()
|
|
||||||
_confirming = true
|
|
||||||
_set_slots_focusable(false)
|
|
||||||
_confirm_overlay.show()
|
|
||||||
_confirm_no.grab_focus()
|
|
||||||
|
|
||||||
func _confirm_release() -> void:
|
|
||||||
var released : PokemonInstance = _storage.release(_release_loc.y, _release_loc.z)
|
|
||||||
_close_confirm()
|
|
||||||
if released: _show_message("%s a été relâché." % released.get_display_name())
|
|
||||||
|
|
||||||
func _close_confirm() -> void:
|
|
||||||
_confirming = false
|
|
||||||
_confirm_overlay.hide()
|
|
||||||
_set_slots_focusable(true)
|
|
||||||
var b : Button = _focus_button_for(_release_loc)
|
|
||||||
if b == null: b = _team_buttons[0]
|
|
||||||
b.grab_focus.call_deferred()
|
|
||||||
|
|
||||||
#---------- fermeture ----------
|
|
||||||
|
|
||||||
func _on_cancel() -> void:
|
|
||||||
if _held != null:
|
|
||||||
_cancel_hold()
|
|
||||||
else:
|
|
||||||
_close()
|
|
||||||
|
|
||||||
func _close() -> void:
|
|
||||||
_restore_mouse()
|
|
||||||
hide()
|
|
||||||
closed.emit()
|
|
||||||
|
|
||||||
func _restore_mouse() -> void:
|
|
||||||
if !_mouse_saved: return
|
|
||||||
Input.mouse_mode = _prev_mouse_mode
|
|
||||||
_mouse_saved = false
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
class_name PokemonBox
|
|
||||||
extends Resource
|
|
||||||
|
|
||||||
#une boite du PC : un nombre fixe d'emplacements, chacun vide (null) ou occupe
|
|
||||||
|
|
||||||
var box_name : String = "" #vide = nom par defaut, a gerer par l'UI ("Boite 1"...)
|
|
||||||
var slots : Array[PokemonInstance] = []
|
|
||||||
|
|
||||||
func _init(p_size : int = 30) -> void:
|
|
||||||
slots.resize(p_size)
|
|
||||||
|
|
||||||
func size() -> int:
|
|
||||||
return slots.size()
|
|
||||||
|
|
||||||
func get_pokemon(slot : int) -> PokemonInstance:
|
|
||||||
return slots[slot] if slot >= 0 and slot < slots.size() else null
|
|
||||||
|
|
||||||
func first_free_slot() -> int:
|
|
||||||
for i in slots.size():
|
|
||||||
if slots[i] == null: return i
|
|
||||||
return -1
|
|
||||||
|
|
||||||
func is_full() -> bool:
|
|
||||||
return first_free_slot() == -1
|
|
||||||
|
|
||||||
func count() -> int:
|
|
||||||
var n : int = 0
|
|
||||||
for p in slots:
|
|
||||||
if p != null: n += 1
|
|
||||||
return n
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
class_name PokemonStorage
|
|
||||||
extends Resource
|
|
||||||
|
|
||||||
signal storage_changed
|
|
||||||
|
|
||||||
enum ReceiveResult { TEAM, BOX, FULL }
|
|
||||||
|
|
||||||
const NO_SLOT : Vector2i = Vector2i(-1, -1) #(boite, emplacement)
|
|
||||||
|
|
||||||
var boxes : Array[PokemonBox] = []
|
|
||||||
|
|
||||||
func _init(p_box_count : int = 8, p_box_size : int = 30) -> void:
|
|
||||||
for i in p_box_count:
|
|
||||||
boxes.append(PokemonBox.new(p_box_size))
|
|
||||||
|
|
||||||
#---------- infos ----------
|
|
||||||
|
|
||||||
func box_count() -> int:
|
|
||||||
return boxes.size()
|
|
||||||
|
|
||||||
func get_box(box_index : int) -> PokemonBox:
|
|
||||||
return boxes[box_index] if box_index >= 0 and box_index < boxes.size() else null
|
|
||||||
|
|
||||||
func get_pokemon(box_index : int, slot : int) -> PokemonInstance:
|
|
||||||
var box : PokemonBox = get_box(box_index)
|
|
||||||
return box.get_pokemon(slot) if box else null
|
|
||||||
|
|
||||||
func get_total_count() -> int:
|
|
||||||
var n : int = 0
|
|
||||||
for box in boxes: n += box.count()
|
|
||||||
return n
|
|
||||||
|
|
||||||
func is_full() -> bool:
|
|
||||||
return find_free_slot() == NO_SLOT
|
|
||||||
|
|
||||||
func find_free_slot() -> Vector2i:
|
|
||||||
for b in boxes.size():
|
|
||||||
var s : int = boxes[b].first_free_slot()
|
|
||||||
if s != -1: return Vector2i(b, s)
|
|
||||||
return NO_SLOT
|
|
||||||
|
|
||||||
#---------- reception d'un nouveau pokemon ----------
|
|
||||||
|
|
||||||
func receive_pokemon(team : PokemonTeam, pokemon : PokemonInstance) -> ReceiveResult:
|
|
||||||
#equipe d'abord ; si elle est pleine, premiere place libre du PC
|
|
||||||
if pokemon == null: return ReceiveResult.FULL
|
|
||||||
if team.add_pokemon(pokemon): return ReceiveResult.TEAM
|
|
||||||
var dest : Vector2i = find_free_slot()
|
|
||||||
if dest == NO_SLOT: return ReceiveResult.FULL
|
|
||||||
boxes[dest.x].slots[dest.y] = pokemon
|
|
||||||
storage_changed.emit()
|
|
||||||
return ReceiveResult.BOX
|
|
||||||
|
|
||||||
#---------- equipe <-> PC ----------
|
|
||||||
|
|
||||||
func deposit(team : PokemonTeam, team_index : int, box_index : int = -1, slot : int = -1) -> bool:
|
|
||||||
#equipe -> PC (box_index = -1 : premiere place libre)
|
|
||||||
if team.get_pokemon(team_index) == null: return false
|
|
||||||
if !_team_stays_valid(team, team_index, null): return false
|
|
||||||
|
|
||||||
var dest : Vector2i = find_free_slot() if box_index < 0 else Vector2i(box_index, slot)
|
|
||||||
if dest == NO_SLOT or get_box(dest.x) == null: return false
|
|
||||||
if dest.y < 0 or dest.y >= boxes[dest.x].size() or boxes[dest.x].slots[dest.y] != null: return false
|
|
||||||
|
|
||||||
var pokemon : PokemonInstance = team.remove_at(team_index)
|
|
||||||
boxes[dest.x].slots[dest.y] = pokemon
|
|
||||||
storage_changed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
func withdraw(team : PokemonTeam, box_index : int, slot : int) -> bool:
|
|
||||||
#PC -> equipe (a la fin) ; impossible si l'equipe est pleine
|
|
||||||
var pokemon : PokemonInstance = get_pokemon(box_index, slot)
|
|
||||||
if pokemon == null or team.is_full(): return false
|
|
||||||
if !team.add_pokemon(pokemon): return false
|
|
||||||
boxes[box_index].slots[slot] = null
|
|
||||||
storage_changed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
func swap_with_team(team : PokemonTeam, team_index : int, box_index : int, slot : int) -> bool:
|
|
||||||
#echange un pokemon de l'equipe avec celui d'un emplacement occupe du PC
|
|
||||||
var from_team : PokemonInstance = team.get_pokemon(team_index)
|
|
||||||
var from_box : PokemonInstance = get_pokemon(box_index, slot)
|
|
||||||
if from_team == null or from_box == null: return false
|
|
||||||
if !_team_stays_valid(team, team_index, from_box): return false
|
|
||||||
if team.replace_at(team_index, from_box) == null: return false
|
|
||||||
boxes[box_index].slots[slot] = from_team
|
|
||||||
storage_changed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
#---------- deplacements dans le PC ----------
|
|
||||||
|
|
||||||
func move_slot(box_a : int, slot_a : int, box_b : int, slot_b : int) -> bool:
|
|
||||||
#deplace (ou echange) le contenu d'un emplacement vers un autre, destination vide ou non
|
|
||||||
var source : PokemonInstance = get_pokemon(box_a, slot_a)
|
|
||||||
if source == null: return false
|
|
||||||
var dest_box : PokemonBox = get_box(box_b)
|
|
||||||
if dest_box == null or slot_b < 0 or slot_b >= dest_box.size(): return false
|
|
||||||
if box_a == box_b and slot_a == slot_b: return false
|
|
||||||
boxes[box_a].slots[slot_a] = dest_box.slots[slot_b]
|
|
||||||
dest_box.slots[slot_b] = source
|
|
||||||
storage_changed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
func release(box_index : int, slot : int) -> PokemonInstance:
|
|
||||||
#retire definitivement un pokemon du PC (relacher) ; renvoie le pokemon retire
|
|
||||||
var pokemon : PokemonInstance = get_pokemon(box_index, slot)
|
|
||||||
if pokemon == null: return null
|
|
||||||
boxes[box_index].slots[slot] = null
|
|
||||||
storage_changed.emit()
|
|
||||||
return pokemon
|
|
||||||
|
|
||||||
#---------- regle d'equipe ----------
|
|
||||||
|
|
||||||
func _team_stays_valid(team : PokemonTeam, removed_index : int, replacement : PokemonInstance) -> bool:
|
|
||||||
#l'equipe ne doit jamais etre vide, ni perdre son dernier pokemon apte
|
|
||||||
#(sauf si elle n'en avait deja aucun : tous K.O.)
|
|
||||||
var size_after : int = team.size() - (1 if replacement == null else 0)
|
|
||||||
if size_after < 1: return false
|
|
||||||
if !team.has_able_pokemon(): return true
|
|
||||||
for i in team.size():
|
|
||||||
if i != removed_index and !team.members[i].is_fainted(): return true
|
|
||||||
return replacement != null and !replacement.is_fainted()
|
|
||||||
|
|
||||||
#---------- sauvegarde ----------
|
|
||||||
|
|
||||||
func to_dict() -> Dictionary:
|
|
||||||
var boxes_data : Array = []
|
|
||||||
for box in boxes:
|
|
||||||
var slots_data : Array = []
|
|
||||||
for p in box.slots:
|
|
||||||
slots_data.append(p.to_dict() if p != null else null)
|
|
||||||
boxes_data.append({"name": box.box_name, "slots": slots_data})
|
|
||||||
return {"boxes": boxes_data}
|
|
||||||
|
|
||||||
static func from_dict(data : Dictionary) -> PokemonStorage:
|
|
||||||
var storage := PokemonStorage.new(0)
|
|
||||||
for box_data in data.get("boxes", []):
|
|
||||||
var slots_data : Array = box_data.get("slots", [])
|
|
||||||
var box := PokemonBox.new(slots_data.size())
|
|
||||||
box.box_name = box_data.get("name", "")
|
|
||||||
for i in slots_data.size():
|
|
||||||
if slots_data[i] is Dictionary:
|
|
||||||
box.slots[i] = PokemonInstance.from_dict(slots_data[i])
|
|
||||||
storage.boxes.append(box)
|
|
||||||
if storage.boxes.is_empty(): return PokemonStorage.new()
|
|
||||||
return storage
|
|
||||||
Loading…
Reference in a new issue