176 lines
7.8 KiB
GDScript
176 lines
7.8 KiB
GDScript
class_name MoveExecutor
|
|
extends RefCounted
|
|
|
|
#execute une capacite : precision, degats, coups multiples, puis effets guides par les donnees de MoveData
|
|
#(statut, changements de stats, vol de PV, contrecoup, soin, peur...).
|
|
#les cas tres particuliers (Abri, Vol, Clonage...) viendront en scripts a part.
|
|
|
|
static func execute(battle : Battle, action : BattleAction) -> void:
|
|
var actor : BattleSlot = action.slot
|
|
var move : MoveData = action.move
|
|
|
|
#crochets "avant l'action" : sommeil, gel, paralysie, peur, confusion... (pas de PP consommes si le pokemon n'agit pas)
|
|
if !await battle.run_before_action(action): return
|
|
|
|
if action.move_slot != null and !action.move_slot.use():
|
|
await battle.message("%s n'a plus de PP pour cette capacité !" % actor.mon.display_name())
|
|
return
|
|
|
|
var used := BattleEvent.make(BattleEvent.Type.MOVE_USED, actor)
|
|
used.move = move
|
|
await battle.emit_event(used)
|
|
|
|
var targets : Array[BattleSlot] = TargetResolver.resolve(battle, actor, move, action.chosen_target)
|
|
if targets.is_empty():
|
|
await battle.message("Mais cela échoue !")
|
|
return
|
|
|
|
var damaging : bool = (move.power > 0 or move.meta_category == "ohko") and move.category != "status"
|
|
if damaging:
|
|
await _use_damaging(battle, actor, move, targets)
|
|
else:
|
|
await _use_status(battle, actor, move, targets)
|
|
|
|
#Lutte : contrecoup pour l'utilisateur
|
|
if move.api_name == "struggle" and actor.is_active():
|
|
await battle.hurt(actor, maxi(1, floori(actor.mon.pokemon.get_max_hp() / 4.0)), "%s est blessé par le contrecoup !" % actor.mon.display_name())
|
|
|
|
#---------- capacites offensives ----------
|
|
|
|
static func _use_damaging(battle : Battle, actor : BattleSlot, move : MoveData, targets : Array[BattleSlot]) -> void:
|
|
var spread : bool = targets.size() > 1
|
|
var total_dealt : int = 0
|
|
|
|
for target in targets:
|
|
if !target.is_active(): continue
|
|
|
|
if move.meta_category == "ohko":
|
|
total_dealt += await _use_ohko(battle, actor, target, move)
|
|
if battle.check_end(): break
|
|
continue
|
|
|
|
if !_hits(battle, actor, target, move):
|
|
await battle.emit_event(BattleEvent.make(BattleEvent.Type.MOVE_MISSED, actor, target))
|
|
continue
|
|
|
|
var hits : int = _roll_hit_count(battle, move)
|
|
var landed : int = 0
|
|
var effectiveness : float = 1.0
|
|
for i in hits:
|
|
if !target.is_active(): break
|
|
var calc : Dictionary = DamageCalculator.calculate(actor.mon, target.mon, move, battle.rng, spread)
|
|
effectiveness = calc["effectiveness"]
|
|
if effectiveness == 0.0: break
|
|
total_dealt += await battle.deal_damage(actor, target, int(calc["damage"]))
|
|
landed += 1
|
|
if calc["critical"]: await battle.emit_event(BattleEvent.make(BattleEvent.Type.CRITICAL, actor, target))
|
|
|
|
if landed == 0:
|
|
if effectiveness == 0.0: await battle.emit_event(BattleEvent.make(BattleEvent.Type.NO_EFFECT, actor, target))
|
|
continue
|
|
|
|
if landed > 1: await battle.message("Touché %d fois !" % landed)
|
|
if effectiveness > 1.0: await battle.emit_event(BattleEvent.make(BattleEvent.Type.SUPER_EFFECTIVE, actor, target))
|
|
elif effectiveness < 1.0: await battle.emit_event(BattleEvent.make(BattleEvent.Type.NOT_VERY_EFFECTIVE, actor, target))
|
|
|
|
if target.mon.is_fainted():
|
|
await battle.emit_event(BattleEvent.make(BattleEvent.Type.FAINTED, target))
|
|
else:
|
|
await _secondary_effects(battle, actor, target, move)
|
|
|
|
if battle.check_end(): break
|
|
|
|
#vol de PV (drain > 0) ou contrecoup (drain < 0), proportionnels aux degats infliges
|
|
if total_dealt > 0 and move.drain != 0 and actor.is_active():
|
|
var amount : int = maxi(1, floori(total_dealt * absf(move.drain) / 100.0))
|
|
if move.drain > 0:
|
|
await battle.heal(actor, amount, "%s a absorbé de l'énergie !" % actor.mon.display_name())
|
|
else:
|
|
await battle.hurt(actor, amount, "%s est blessé par le contrecoup !" % actor.mon.display_name())
|
|
|
|
static func _use_ohko(battle : Battle, actor : BattleSlot, target : BattleSlot, move : MoveData) -> int:
|
|
#capacites K.O. en un coup : echouent si la cible est d'un niveau superieur ; precision 30 % + ecart de niveau
|
|
var level_gap : int = actor.mon.pokemon.level - target.mon.pokemon.level
|
|
if level_gap < 0:
|
|
await battle.message("Mais cela échoue !")
|
|
return 0
|
|
if TypeChart.get_multiplier(move.type, target.mon.get_types()) == 0.0:
|
|
await battle.emit_event(BattleEvent.make(BattleEvent.Type.NO_EFFECT, actor, target))
|
|
return 0
|
|
if battle.rng.randi_range(1, 100) > 30 + level_gap:
|
|
await battle.emit_event(BattleEvent.make(BattleEvent.Type.MOVE_MISSED, actor, target))
|
|
return 0
|
|
|
|
var dealt : int = await battle.deal_damage(actor, target, target.mon.pokemon.current_hp)
|
|
await battle.message("K.O. en un coup !")
|
|
if target.mon.is_fainted(): await battle.emit_event(BattleEvent.make(BattleEvent.Type.FAINTED, target))
|
|
return dealt
|
|
|
|
## Effets secondaires d'une capacite offensive, appliques a une cible encore en vie.
|
|
static func _secondary_effects(battle : Battle, actor : BattleSlot, target : BattleSlot, move : MoveData) -> void:
|
|
#statut
|
|
if move.ailment != "none" and move.ailment_chance > 0 and target.is_active():
|
|
if battle.rng.randi_range(1, 100) <= move.ailment_chance:
|
|
await battle.inflict_status(actor, target, move.ailment, move, false)
|
|
|
|
#peur : seulement si la cible n'a pas encore agi ce tour
|
|
if move.flinch_chance > 0 and target.is_active() and !target.mon.acted_this_turn:
|
|
if battle.rng.randi_range(1, 100) <= move.flinch_chance and !target.mon.has_volatile(&"flinch"):
|
|
target.mon.add_volatile(FlinchEffect.new())
|
|
|
|
#changements de stats : "damage+raise" touche l'utilisateur, "damage+lower" la cible
|
|
if !move.stat_changes.is_empty() and battle.rng.randi_range(1, 100) <= move.get_stat_chance():
|
|
var affected : BattleSlot = actor if move.meta_category == "damage+raise" else target
|
|
for sc in move.stat_changes:
|
|
await battle.change_stat(affected, sc.stat, sc.change)
|
|
|
|
#---------- capacites de statut ----------
|
|
|
|
static func _use_status(battle : Battle, actor : BattleSlot, move : MoveData, targets : Array[BattleSlot]) -> void:
|
|
var handled : bool = false
|
|
|
|
for target in targets:
|
|
if !target.is_active(): continue
|
|
if target != actor and !_hits(battle, actor, target, move):
|
|
await battle.emit_event(BattleEvent.make(BattleEvent.Type.MOVE_MISSED, actor, target))
|
|
continue
|
|
|
|
if move.ailment != "none":
|
|
handled = true
|
|
var chance : int = move.get_ailment_chance()
|
|
if battle.rng.randi_range(1, 100) <= chance:
|
|
await battle.inflict_status(actor, target, move.ailment, move, true)
|
|
|
|
if !move.stat_changes.is_empty():
|
|
handled = true
|
|
if battle.rng.randi_range(1, 100) <= move.get_stat_chance():
|
|
for sc in move.stat_changes:
|
|
await battle.change_stat(target, sc.stat, sc.change)
|
|
|
|
if move.healing > 0:
|
|
handled = true
|
|
var amount : int = maxi(1, floori(target.mon.pokemon.get_max_hp() * move.healing / 100.0))
|
|
var healed : int = await battle.heal(target, amount, "%s récupère des PV !" % target.mon.display_name())
|
|
if healed == 0:
|
|
await battle.message("Les PV de %s sont déjà au maximum !" % target.mon.display_name())
|
|
|
|
if !handled:
|
|
await battle.message("Mais cela n'a pas d'effet pour l'instant.")
|
|
|
|
#---------- precision et coups multiples ----------
|
|
|
|
static func _hits(battle : Battle, actor : BattleSlot, target : BattleSlot, move : MoveData) -> bool:
|
|
#precision 0 = ne rate jamais (valeur PokeAPI "null") ; sinon precision de la capacite x boosts de precision/esquive
|
|
if move.accuracy <= 0: return true
|
|
var chance : float = move.accuracy * actor.mon.accuracy_multiplier_against(target.mon)
|
|
return battle.rng.randi_range(1, 100) <= int(chance)
|
|
|
|
static func _roll_hit_count(battle : Battle, move : MoveData) -> int:
|
|
if move.max_hits <= 1: return 1
|
|
if move.min_hits >= move.max_hits: return move.max_hits
|
|
if move.min_hits == 2 and move.max_hits == 5:
|
|
#repartition officielle : 2 ou 3 coups 35 % chacun, 4 ou 5 coups 15 % chacun
|
|
var roll : int = battle.rng.randi_range(1, 100)
|
|
return 2 if roll <= 35 else (3 if roll <= 70 else (4 if roll <= 85 else 5))
|
|
return battle.rng.randi_range(move.min_hits, move.max_hits)
|