Update battle system
This commit is contained in:
parent
f2013efadc
commit
f4cc553f35
|
|
@ -12,7 +12,7 @@ extends RefCounted
|
|||
|
||||
signal event_emitted(event : BattleEvent)
|
||||
|
||||
enum Outcome { NONE, PLAYER_WON, PLAYER_LOST, FLED }
|
||||
enum Outcome { NONE, PLAYER_WON, PLAYER_LOST, FLED, DRAW }
|
||||
|
||||
const PLAYER_SIDE : int = 0
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ var setup : BattleSetup
|
|||
var presenter : BattlePresenter
|
||||
var rng : RandomNumberGenerator = RandomNumberGenerator.new()
|
||||
var turn : int = 0
|
||||
var max_turns : int = 0 #0 = illimite ; sinon le combat s'arrete en match nul apres ce nombre de tours (utile pour les tests)
|
||||
var outcome : Outcome = Outcome.NONE
|
||||
var slots : Array[BattleSlot] = []
|
||||
|
||||
|
|
@ -56,6 +57,9 @@ func run() -> Outcome:
|
|||
return await _finish()
|
||||
|
||||
while outcome == Outcome.NONE:
|
||||
if max_turns > 0 and turn >= max_turns:
|
||||
outcome = Outcome.DRAW
|
||||
break
|
||||
turn += 1
|
||||
var turn_event := BattleEvent.make(BattleEvent.Type.TURN_START)
|
||||
turn_event.amount = turn
|
||||
|
|
@ -79,6 +83,7 @@ func _finish() -> Outcome:
|
|||
Outcome.PLAYER_WON: end_event.text = "Victoire !"
|
||||
Outcome.PLAYER_LOST: end_event.text = "Défaite..."
|
||||
Outcome.FLED: end_event.text = "Vous avez pris la fuite."
|
||||
Outcome.DRAW: end_event.text = "Match nul (limite de tours atteinte)."
|
||||
await _emit(end_event)
|
||||
return outcome
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ var hp_threshold : float = 0.4 #ne change que si ses PV sont sous ce ratio
|
|||
var matchup_threshold : float = -1.0 #ne change que si le rapport de types est pire que cette valeur
|
||||
var min_improvement : float = 1.0 #le remplacant doit etre meilleur d'au moins cette valeur
|
||||
var min_replacement_hp : float = 0.3 #ne fait pas entrer un pokemon quasi K.O.
|
||||
var switch_cooldown : int = 2 #tours minimum entre deux changements volontaires d'un meme emplacement
|
||||
|
||||
const KILL_SCORE : float = 20.0 #bonus de score d'une attaque qui acheve un adversaire (cf. _score)
|
||||
|
||||
var _claimed : Array[PokemonInstance] = [] #remplacants deja choisis ce tour (evite 2 emplacements sur le meme)
|
||||
var _claimed_turn : int = -1
|
||||
var _last_switch : Dictionary = {} #id d'emplacement -> dernier tour ou il a change volontairement
|
||||
|
||||
func choose_action(battle : Battle, slot : BattleSlot) -> BattleAction:
|
||||
if _claimed_turn != battle.turn:
|
||||
|
|
@ -71,9 +73,13 @@ func choose_replacement(battle : Battle, slot : BattleSlot) -> PokemonInstance:
|
|||
func _consider_switch(battle : Battle, slot : BattleSlot, best_attack_score : float, no_usable_move : bool) -> BattleAction:
|
||||
if !can_switch: return null
|
||||
|
||||
#remplacants possibles : pas deja choisis ce tour ; et, si le pokemon actuel n'a plus de capacite,
|
||||
#changer n'a de sens que si le remplacant en a une (sinon on tournerait en rond sans jamais attaquer)
|
||||
var bench : Array[PokemonInstance] = []
|
||||
for p in battle.get_replacement_options(slot):
|
||||
if !_claimed.has(p): bench.append(p)
|
||||
if _claimed.has(p): continue
|
||||
if no_usable_move and !_has_usable_move(p): continue
|
||||
bench.append(p)
|
||||
if bench.is_empty(): return null
|
||||
|
||||
var opponents : Array[BattleSlot] = battle.get_opponents(slot)
|
||||
|
|
@ -83,6 +89,7 @@ func _consider_switch(battle : Battle, slot : BattleSlot, best_attack_score : fl
|
|||
if best_attack_score >= KILL_SCORE: return null #peut achever un adversaire : on attaque
|
||||
var hp_ratio : float = float(slot.mon.pokemon.current_hp) / float(slot.mon.pokemon.get_max_hp())
|
||||
if hp_ratio > hp_threshold or current > matchup_threshold: return null
|
||||
if battle.turn - int(_last_switch.get(slot.id, -999)) < switch_cooldown: return null
|
||||
if battle.rng.randf() > switch_chance: return null
|
||||
|
||||
var best : PokemonInstance = null
|
||||
|
|
@ -97,6 +104,7 @@ func _consider_switch(battle : Battle, slot : BattleSlot, best_attack_score : fl
|
|||
if !no_usable_move and best_value < current + min_improvement: return null
|
||||
|
||||
_claimed.append(best)
|
||||
_last_switch[slot.id] = battle.turn
|
||||
return BattleAction.switch_pokemon(slot, best)
|
||||
|
||||
func _matchup(my_types : Array[String], opponents : Array[BattleSlot]) -> float:
|
||||
|
|
@ -115,6 +123,11 @@ func _matchup(my_types : Array[String], opponents : Array[BattleSlot]) -> float:
|
|||
total += offense - defense
|
||||
return total / opponents.size()
|
||||
|
||||
func _has_usable_move(pokemon : PokemonInstance) -> bool:
|
||||
for ms in pokemon.active_moves:
|
||||
if ms.has_pp(): return true
|
||||
return false
|
||||
|
||||
func _types_of(pokemon : PokemonInstance) -> Array[String]:
|
||||
var result : Array[String] = []
|
||||
for t in pokemon.species.types:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
class_name BattleDebug
|
||||
extends Node
|
||||
|
||||
#banc d'essai : simule un combat complet IA contre IA et ecrit tout dans la console.
|
||||
#banc d'essai : simule un combat complet IA contre IA.
|
||||
#le journal n'est PAS ecrit dans la console (trop lent) : il est garde en memoire, puis
|
||||
#affiche a l'ecran et/ou ecrit dans un fichier a la fin du combat. La console ne recoit qu'un resume.
|
||||
#ajoute ce node a une scene, regle le format dans l'inspecteur, lance la scene.
|
||||
#utilise des equipes aleatoires (par defaut) : n'abime pas ton equipe de jeu.
|
||||
|
||||
|
|
@ -12,6 +14,14 @@ extends Node
|
|||
@export_range(1, 6) var team_size : int = 3
|
||||
@export_range(1, 100) var level : int = 10
|
||||
@export var use_game_team : bool = false #true : utilise GameData.player_team (qui sera modifiee !)
|
||||
@export var max_turns : int = 100 #securite : match nul au-dela (0 = illimite)
|
||||
|
||||
@export_group("Sortie")
|
||||
@export var show_log_on_screen : bool = true
|
||||
@export var write_log_file : bool = true
|
||||
const LOG_PATH : String = "user://battle_debug.log"
|
||||
|
||||
var _log_layer : CanvasLayer = null
|
||||
|
||||
func _ready() -> void:
|
||||
if run_on_ready:
|
||||
|
|
@ -35,10 +45,18 @@ func run_test() -> void:
|
|||
push_error("BattleDebug : combat invalide (une equipe n'a aucun pokemon apte)")
|
||||
return
|
||||
|
||||
print("===== Combat : %d contre %d =====" % [player_slots, enemy_count * enemy_slots_each])
|
||||
var battle := Battle.new(setup, BattlePrintPresenter.new())
|
||||
var presenter := BattleLogPresenter.new()
|
||||
var battle := Battle.new(setup, presenter)
|
||||
battle.max_turns = max_turns
|
||||
var outcome : Battle.Outcome = await battle.run()
|
||||
print("===== Resultat : %s apres %d tours =====" % [Battle.Outcome.keys()[outcome], battle.turn])
|
||||
|
||||
var header : String = "===== Combat : %d contre %d =====" % [player_slots, enemy_count * enemy_slots_each]
|
||||
var summary : String = "Résultat : %s après %d tours (%d lignes de journal)" % [Battle.Outcome.keys()[outcome], battle.turn, presenter.lines.size()]
|
||||
var text : String = header + "\n" + "\n".join(presenter.lines) + "\n===== " + summary + " ====="
|
||||
|
||||
print("[BattleDebug] " + summary) #une seule ligne dans la console
|
||||
if write_log_file: _write_log(text)
|
||||
if show_log_on_screen: _show_log(text, summary)
|
||||
|
||||
func _random_team(size : int) -> PokemonTeam:
|
||||
var team := PokemonTeam.new()
|
||||
|
|
@ -50,3 +68,47 @@ func _random_team(size : int) -> PokemonTeam:
|
|||
var species : PokemonData = Pokedex.get_pokemon(ids.pick_random())
|
||||
if species: team.add_pokemon(PokemonInstance.create(species, level))
|
||||
return team
|
||||
|
||||
#---------- sortie ----------
|
||||
|
||||
func _write_log(text : String) -> void:
|
||||
var file := FileAccess.open(LOG_PATH, FileAccess.WRITE)
|
||||
if file == null:
|
||||
push_warning("BattleDebug : impossible d'ecrire le journal (%s)" % LOG_PATH)
|
||||
return
|
||||
file.store_string(text)
|
||||
file.close()
|
||||
print("[BattleDebug] Journal ecrit dans : " + ProjectSettings.globalize_path(LOG_PATH))
|
||||
|
||||
func _show_log(text : String, summary : String) -> void:
|
||||
#panneau plein ecran avec le journal defilant, ferme par un bouton (ou B / Echap)
|
||||
if _log_layer != null: _log_layer.queue_free()
|
||||
_log_layer = CanvasLayer.new()
|
||||
_log_layer.layer = 100
|
||||
add_child(_log_layer)
|
||||
|
||||
var panel := PanelContainer.new()
|
||||
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_log_layer.add_child(panel)
|
||||
|
||||
var box := VBoxContainer.new()
|
||||
panel.add_child(box)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = summary
|
||||
box.add_child(title)
|
||||
|
||||
var log_label := RichTextLabel.new()
|
||||
log_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
log_label.selection_enabled = true
|
||||
log_label.scroll_active = true
|
||||
log_label.text = text
|
||||
box.add_child(log_label)
|
||||
|
||||
var close := Button.new()
|
||||
close.text = "Fermer"
|
||||
close.pressed.connect(func() -> void:
|
||||
_log_layer.queue_free()
|
||||
_log_layer = null)
|
||||
box.add_child(close)
|
||||
close.grab_focus()
|
||||
Loading…
Reference in a new issue