Tales-of-PKMN/game/battle/battle_launcher.gd

117 lines
5.3 KiB
GDScript

class_name BattleLauncher
extends Node
#lance les combats dans le monde. A ajouter UNE FOIS a ta scene de jeu (node "BattleLauncher").
#
#- ecoute GameData.battle_requested (lancer d'un pokemon sur un AimTarget qui a un "Encounter")
#- start_encounter() sert aussi a un dresseur qui defie le joueur (dialogue, zone de detection...)
#
#pendant le combat : le monde est en pause (GameData.in_battle = true), une camera de combat prend le relais,
#puis tout est restitue a la fin.
signal battle_started
signal battle_ended(outcome : int)
@export var heal_team_on_defeat : bool = true #provisoire : evite de bloquer le joueur si toute son equipe est K.O.
var _running : bool = false
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
GameData.battle_requested.connect(_on_battle_requested)
func _on_battle_requested(sent : SentPokemon, target : Node) -> void:
if _running:
push_warning("[BattleLauncher] un combat est deja en cours")
return
if !(target is AimTarget):
push_warning("[BattleLauncher] la cible n'est pas un AimTarget")
return
var aim_target : AimTarget = target
if aim_target.encounter == null:
push_warning("[BattleLauncher] cet AimTarget n'a pas d'Encounter (inspecteur : Battle > Encounter)")
return
await start_encounter(aim_target.encounter, aim_target.global_position, sent, aim_target.world_actor, aim_target)
func start_encounter(encounter : BattleEncounter, enemy_pos : Vector3, sent : SentPokemon = null, world_actor : Node3D = null, target : AimTarget = null) -> Battle.Outcome:
#sent = pokemon lance par le joueur (il passe en premier) ; null si le joueur est defie : l'ordre de l'equipe s'applique
if _running or encounter == null:
push_warning("[BattleLauncher] start_encounter ignore (combat deja en cours ou Encounter vide)")
return Battle.Outcome.NONE
#camera et acteurs doivent etre dans le monde que le joueur voit (SubViewport eventuel du jeu)
var world_viewport : Viewport = get_viewport()
var world_root : Node = world_viewport if world_viewport is SubViewport else get_tree().current_scene
var game_camera : Camera3D = world_viewport.get_camera_3d()
var player_pos : Vector3 = sent.global_position if (sent != null and is_instance_valid(sent)) else _find_player_position()
var ui := BattleUI.new()
get_tree().root.add_child(ui) #directement sous la fenetre : toujours affiche par-dessus tout
var setup := BattleSetup.new()
setup.can_flee = encounter.can_flee
var lead : PokemonInstance = sent.pokemon if (sent != null and is_instance_valid(sent)) else null
setup.add(BattleSetup.player_participant(GameData.player_team, encounter.player_slots, lead, PlayerBattleController.new(ui)))
for participant in encounter.build_participants():
setup.add(participant)
if !setup.is_valid():
var details : PackedStringArray = PackedStringArray()
for participant in setup.participants:
details.append("%s (camp %d, %d pokemon aptes)" % [participant.name, participant.side, participant.team.get_able_pokemon().size()])
push_warning("[BattleLauncher] combat invalide : chaque camp doit avoir un participant avec un pokemon apte. Participants : " + ", ".join(details) + ". Verifie que l'Encounter contient bien une espece (wild_pokemon / trainer) et que ton equipe a un pokemon non K.O.")
ui.queue_free()
return Battle.Outcome.NONE
_running = true
GameData.in_battle = true
GameData.battle_started.emit() #les elements d'interface (TeamHUD...) se cachent ici, avant la pause du monde
get_tree().paused = true
battle_started.emit()
#les pokemon sortis et l'adversaire du monde sont caches : les acteurs du combat les remplacent
var hidden : Array[Node3D] = []
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
if node is Node3D and node.visible:
node.visible = false
hidden.append(node)
if world_actor != null and world_actor.visible:
world_actor.visible = false
hidden.append(world_actor)
var presenter := BattleWorldPresenter.new(ui)
var battle := Battle.new(setup, presenter)
presenter.setup(battle, player_pos, enemy_pos, world_root, game_camera)
var outcome : Battle.Outcome = await battle.run()
await presenter.cleanup()
ui.queue_free()
#retour au monde : tous les pokemon sortis rentrent dans leur balle ; l'adversaire du monde reapparait
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
if node is SentPokemon: node.recall()
for node in hidden:
if is_instance_valid(node) and !(node is SentPokemon): node.visible = true
if world_actor != null and is_instance_valid(world_actor):
if outcome == Battle.Outcome.PLAYER_WON and encounter.remove_world_actor_on_win:
world_actor.queue_free()
if outcome == Battle.Outcome.PLAYER_LOST and heal_team_on_defeat:
GameData.player_team.heal_all()
GameData.in_battle = false
get_tree().paused = false
_running = false
if target != null and is_instance_valid(target): target.battle_finished.emit(outcome)
GameData.battle_finished.emit(outcome, target)
battle_ended.emit(outcome)
return outcome
func _find_player_position() -> Vector3:
#ajoute ton PlayerCharacter au groupe "player" (sinon la camera du jeu sert de repere)
var player : Node3D = get_tree().get_first_node_in_group("player") as Node3D
if player != null: return player.global_position
var cam : Camera3D = get_viewport().get_camera_3d()
return cam.global_position if cam != null else Vector3.ZERO