199 lines
7.4 KiB
GDScript
199 lines
7.4 KiB
GDScript
class_name BattleWorldPresenter
|
|
extends BattlePresenter
|
|
|
|
#met le combat en scene dans le monde 3D :
|
|
#- place les pokemon (un BattleActor par emplacement) face a face, a l'endroit de la rencontre
|
|
#- deplace une camera de combat qui cadre l'ensemble
|
|
#- joue les animations et affiche les messages / PV via BattleUI
|
|
#
|
|
#le monde (hors combat) est en pause : tout ce qui est ici fonctionne en mode "toujours".
|
|
|
|
var ui : BattleUI
|
|
var battle : Battle
|
|
|
|
#formation
|
|
var side_distance : float = 7.0 #distance entre les deux camps
|
|
var lateral_spacing : float = 2.6 #ecart entre deux pokemon d'un meme camp
|
|
var ground_mask : int = 1
|
|
|
|
#camera
|
|
var camera_back : float = 4.5 #recul derriere le camp du joueur
|
|
var camera_height : float = 3.4
|
|
var camera_move_time : float = 0.9
|
|
|
|
var _actors : Dictionary = {} #id d'emplacement -> BattleActor
|
|
var _formation : Dictionary = {} #id d'emplacement -> position au sol
|
|
var _center : Vector3 = Vector3.ZERO
|
|
var _axis : Vector3 = Vector3.FORWARD #direction camp joueur -> camp adverse
|
|
var _camera : Camera3D = null
|
|
var _previous_camera : Camera3D = null
|
|
var _world_root : Node = null #ou ajouter camera et acteurs : le monde que le joueur voit
|
|
|
|
func _init(p_ui : BattleUI) -> void:
|
|
ui = p_ui
|
|
ui.highlight_requested.connect(_on_highlight_requested)
|
|
|
|
#---------- mise en place ----------
|
|
|
|
func setup(p_battle : Battle, player_pos : Vector3, enemy_pos : Vector3, p_world_root : Node, p_game_camera : Camera3D) -> void:
|
|
battle = p_battle
|
|
_world_root = p_world_root
|
|
_previous_camera = p_game_camera
|
|
|
|
_axis = enemy_pos - player_pos
|
|
_axis.y = 0.0
|
|
if _axis.length() < 0.1:
|
|
#pas de direction evidente : on utilise la direction de la camera du jeu
|
|
var cam : Camera3D = _previous_camera
|
|
_axis = -cam.global_basis.z if cam else Vector3.FORWARD
|
|
_axis.y = 0.0
|
|
_axis = _axis.normalized()
|
|
_center = (player_pos + enemy_pos) * 0.5
|
|
|
|
var lateral : Vector3 = _axis.cross(Vector3.UP).normalized()
|
|
|
|
var count_per_side : Dictionary = {0: 0, 1: 0}
|
|
for slot in battle.slots:
|
|
count_per_side[slot.side] += 1
|
|
|
|
for slot in battle.slots:
|
|
var n : int = count_per_side[slot.side]
|
|
var offset : float = (slot.index - (n - 1) * 0.5) * lateral_spacing
|
|
var side_sign : float = -1.0 if slot.side == 0 else 1.0
|
|
var pos : Vector3 = _center + _axis * side_sign * side_distance * 0.5 + lateral * offset
|
|
_formation[slot.id] = _snap_to_ground(pos)
|
|
|
|
_start_camera(maxi(count_per_side[0], count_per_side[1]))
|
|
|
|
func _start_camera(max_slots : int) -> void:
|
|
|
|
var back : float = camera_back + max_slots * 0.8
|
|
var cam_pos : Vector3 = _center - _axis * (side_distance * 0.5 + back) + Vector3.UP * (camera_height + max_slots * 0.3)
|
|
var look_target : Vector3 = _center + Vector3.UP * 1.0
|
|
var final_transform := Transform3D(Basis.looking_at(look_target - cam_pos, Vector3.UP), cam_pos)
|
|
|
|
_camera = Camera3D.new()
|
|
_world_root.add_child(_camera)
|
|
_camera.global_transform = _previous_camera.global_transform if _previous_camera else final_transform
|
|
if _previous_camera: _camera.fov = _previous_camera.fov
|
|
_camera.make_current()
|
|
print("[BattleCamera] camera du jeu : %s | camera de combat : parent=%s, current=%s" % [_previous_camera, _camera.get_parent().get_path(), _camera.current]) #suivi : a retirer ensuite
|
|
|
|
var tween : Tween = ui.create_tween()
|
|
tween.tween_property(_camera, "global_transform", final_transform, camera_move_time).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
|
|
func cleanup() -> void:
|
|
#fin du combat : ecran propre, camera du jeu reprise
|
|
ui.highlight_requested.emit([])
|
|
ui.hide_message()
|
|
ui.clear_panels()
|
|
for actor in _actors.values():
|
|
if is_instance_valid(actor): (actor as Node).queue_free()
|
|
_actors.clear()
|
|
|
|
if _camera != null:
|
|
if _previous_camera != null and is_instance_valid(_previous_camera):
|
|
var tween : Tween = ui.create_tween()
|
|
tween.tween_property(_camera, "global_transform", _previous_camera.global_transform, 0.6).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
await tween.finished
|
|
_previous_camera.make_current()
|
|
_camera.queue_free()
|
|
_camera = null
|
|
|
|
#---------- evenements ----------
|
|
|
|
func present(event : BattleEvent) -> void:
|
|
match event.type:
|
|
BattleEvent.Type.BATTLE_START:
|
|
await ui.show_message(event.describe(), 1.0)
|
|
|
|
BattleEvent.Type.SENT_OUT:
|
|
await _show_actor(event.source_slot)
|
|
await ui.show_message(event.describe(), 0.9)
|
|
|
|
BattleEvent.Type.WITHDRAWN:
|
|
await ui.show_message(event.describe(), 0.7)
|
|
await _remove_actor(event.source_slot)
|
|
|
|
BattleEvent.Type.MOVE_USED:
|
|
var actor : BattleActor = _actors.get(event.source_slot)
|
|
if actor: actor.play_attack(_opponents_center(event.source_slot)) #en parallele du message
|
|
await ui.show_message(event.describe(), 1.0)
|
|
|
|
BattleEvent.Type.DAMAGE:
|
|
var actor : BattleActor = _actors.get(event.target_slot)
|
|
if actor: actor.play_hit()
|
|
await ui.animate_hp(event.target_slot, event.hp, event.max_hp)
|
|
|
|
BattleEvent.Type.FAINTED:
|
|
var actor : BattleActor = _actors.get(event.source_slot)
|
|
if actor: actor.play_faint()
|
|
ui.remove_slot_panel(event.source_slot)
|
|
await ui.show_message(event.describe(), 1.0)
|
|
|
|
BattleEvent.Type.STATUS_CHANGED:
|
|
ui.set_slot_status(event.target_slot, event.amount)
|
|
if event.text != "": await ui.show_message(event.text, 1.0)
|
|
|
|
BattleEvent.Type.HEAL:
|
|
await ui.animate_hp(event.target_slot, event.hp, event.max_hp)
|
|
if event.text != "": await ui.show_message(event.text, 1.0)
|
|
|
|
BattleEvent.Type.TURN_START:
|
|
ui.hide_message() #pas de texte : les menus prennent la suite
|
|
|
|
_:
|
|
#MOVE_MISSED, CRITICAL, SUPER_EFFECTIVE, NOT_VERY_EFFECTIVE, NO_EFFECT, FLED, MESSAGE, BATTLE_END...
|
|
var text : String = event.describe()
|
|
if text != "": await ui.show_message(text, 1.0)
|
|
|
|
func _show_actor(slot_id : int) -> void:
|
|
var slot : BattleSlot = battle.get_slot(slot_id)
|
|
if slot == null or slot.mon == null: return
|
|
|
|
if _actors.has(slot_id):
|
|
(_actors[slot_id] as Node).queue_free()
|
|
_actors.erase(slot_id)
|
|
|
|
var actor := BattleActor.new()
|
|
_world_root.add_child(actor)
|
|
actor.setup(slot.mon.pokemon, slot_id)
|
|
var facing : Vector3 = _center + _axis * (side_distance if slot.side == 0 else -side_distance)
|
|
actor.place(_formation.get(slot_id, _center), facing)
|
|
_actors[slot_id] = actor
|
|
|
|
ui.set_slot_pokemon(slot)
|
|
await actor.play_enter()
|
|
|
|
func _remove_actor(slot_id : int) -> void:
|
|
ui.remove_slot_panel(slot_id)
|
|
var actor : BattleActor = _actors.get(slot_id)
|
|
if actor == null: return
|
|
await actor.play_leave()
|
|
actor.queue_free()
|
|
_actors.erase(slot_id)
|
|
|
|
func _on_highlight_requested(slot_ids : Array) -> void:
|
|
for id in _actors:
|
|
(_actors[id] as BattleActor).set_highlight(slot_ids.has(id))
|
|
|
|
#---------- utilitaires ----------
|
|
|
|
func _opponents_center(slot_id : int) -> Vector3:
|
|
var slot : BattleSlot = battle.get_slot(slot_id)
|
|
if slot == null: return _center
|
|
var total : Vector3 = Vector3.ZERO
|
|
var count : int = 0
|
|
for other in battle.slots:
|
|
if other.side != slot.side and _actors.has(other.id):
|
|
total += (_actors[other.id] as BattleActor).home
|
|
count += 1
|
|
return total / count if count > 0 else _center
|
|
|
|
func _snap_to_ground(point : Vector3) -> Vector3:
|
|
var world : World3D = _previous_camera.get_world_3d() if _previous_camera != null else _world_root.get_viewport().world_3d
|
|
var space : PhysicsDirectSpaceState3D = world.direct_space_state
|
|
var query := PhysicsRayQueryParameters3D.create(point + Vector3.UP * 3.0, point + Vector3.DOWN * 8.0, ground_mask)
|
|
var hit : Dictionary = space.intersect_ray(query)
|
|
return hit["position"] if !hit.is_empty() else point
|