Compare commits
No commits in common. "f0964c811c11534a334217e06f5a4bc3469d475a" and "8ea4111149f4114cde938fba8267c14c3980f16c" have entirely different histories.
f0964c811c
...
8ea4111149
|
|
@ -1,57 +0,0 @@
|
|||
class_name AbilityRegistry
|
||||
extends RefCounted
|
||||
|
||||
#associe un talent (nom d'API) a son effet. Un talent sans script est simplement sans effet
|
||||
#(avertissement en debug, une seule fois). Pour en ajouter : cree une classe BattleEffect et declare-la ici.
|
||||
|
||||
static var _warned : Dictionary = {}
|
||||
|
||||
static func create(api_name : String) -> BattleEffect:
|
||||
match api_name:
|
||||
#entree en combat
|
||||
"intimidate": return IntimidateAbility.new()
|
||||
"drizzle": return OnEnterWeatherAbility.new(BattleField.Weather.RAIN)
|
||||
"drought": return OnEnterWeatherAbility.new(BattleField.Weather.SUN)
|
||||
"sand-stream": return OnEnterWeatherAbility.new(BattleField.Weather.SANDSTORM)
|
||||
"snow-warning": return OnEnterWeatherAbility.new(BattleField.Weather.SNOW)
|
||||
|
||||
#vitesse selon la meteo
|
||||
"swift-swim": return WeatherSpeedAbility.new(BattleField.Weather.RAIN)
|
||||
"chlorophyll": return WeatherSpeedAbility.new(BattleField.Weather.SUN)
|
||||
"sand-rush": return WeatherSpeedAbility.new(BattleField.Weather.SANDSTORM)
|
||||
"slush-rush": return WeatherSpeedAbility.new(BattleField.Weather.SNOW)
|
||||
|
||||
#immunites et absorptions
|
||||
"levitate": return TypeAbsorbAbility.new("ground", 0.0, "", true)
|
||||
"water-absorb": return TypeAbsorbAbility.new("water", 0.25)
|
||||
"volt-absorb": return TypeAbsorbAbility.new("electric", 0.25)
|
||||
"lightning-rod": return TypeAbsorbAbility.new("electric", 0.0, "special-attack")
|
||||
"storm-drain": return TypeAbsorbAbility.new("water", 0.0, "special-attack")
|
||||
"sap-sipper": return TypeAbsorbAbility.new("grass", 0.0, "attack")
|
||||
"motor-drive": return TypeAbsorbAbility.new("electric", 0.0, "speed")
|
||||
"flash-fire": return FlashFireAbility.new()
|
||||
|
||||
#contact
|
||||
"static": return ContactStatusAbility.new("paralysis")
|
||||
"poison-point": return ContactStatusAbility.new("poison")
|
||||
"flame-body": return ContactStatusAbility.new("burn")
|
||||
"rough-skin": return RoughSkinAbility.new()
|
||||
|
||||
#degats et stats
|
||||
"overgrow": return PinchBoostAbility.new("grass")
|
||||
"blaze": return PinchBoostAbility.new("fire")
|
||||
"torrent": return PinchBoostAbility.new("water")
|
||||
"swarm": return PinchBoostAbility.new("bug")
|
||||
"adaptability": return AdaptabilityAbility.new()
|
||||
"technician": return TechnicianAbility.new()
|
||||
"huge-power", "pure-power": return AttackBoostAbility.new(true)
|
||||
"guts": return AttackBoostAbility.new(false)
|
||||
"speed-boost": return SpeedBoostAbility.new()
|
||||
|
||||
_warn_missing(api_name)
|
||||
return null
|
||||
|
||||
static func _warn_missing(api_name : String) -> void:
|
||||
if !OS.is_debug_build() or _warned.has(api_name): return
|
||||
_warned[api_name] = true
|
||||
push_warning("[AbilityRegistry] talent sans script : '%s' (sans effet en combat)" % api_name)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://5bs0pyc6pr8w
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
class_name AdaptabilityAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Adaptabilite : le bonus de meme type (STAB) passe de x1.5 a x2
|
||||
|
||||
func _init() -> void:
|
||||
id = &"adaptability"
|
||||
|
||||
func modify_outgoing_damage(attacker : BattleMon, _defender : BattleMon, move : MoveData, damage : float) -> float:
|
||||
if attacker.get_types().has(move.type.to_lower()):
|
||||
return damage * (2.0 / DamageCalculator.STAB_MULTIPLIER)
|
||||
return damage
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://kb6pgmnvnman
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
class_name AttackBoostAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Force Pure / Cran : Attaque x2 (Force Pure, Coloforce) ou x1.5 avec un statut (Cran, qui annule aussi le malus de brulure)
|
||||
|
||||
var always_double : bool
|
||||
|
||||
func _init(p_always_double : bool) -> void:
|
||||
id = &"attack_boost"
|
||||
always_double = p_always_double
|
||||
|
||||
func modify_stat(mon : BattleMon, stat : PokemonNature.Stat, value : float) -> float:
|
||||
if stat != PokemonNature.Stat.ATTACK: return value
|
||||
if always_double: return value * 2.0
|
||||
if mon.pokemon.status == PokemonInstance.Status.NONE: return value
|
||||
var burn_undo : float = 2.0 if mon.pokemon.status == PokemonInstance.Status.BURN else 1.0
|
||||
return value * 1.5 * burn_undo
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dskstklaa3qo0
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
class_name ContactStatusAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Statik, Point Poison, Corps Ardent : chance d'infliger un statut a l'attaquant qui touche au contact.
|
||||
#PokeAPI ne dit pas si une capacite fait contact : on considere que toutes les capacites physiques le font.
|
||||
|
||||
var ailment : String
|
||||
var chance : int
|
||||
|
||||
func _init(p_ailment : String, p_chance : int = 30) -> void:
|
||||
id = &"contact_status"
|
||||
ailment = p_ailment
|
||||
chance = p_chance
|
||||
|
||||
func on_after_hit(battle : Battle, attacker : BattleSlot, defender : BattleSlot, move : MoveData, damage : int) -> void:
|
||||
if damage <= 0 or move.category != "physical" or attacker == defender or !attacker.is_active(): return
|
||||
|
||||
var status : int = StatusRegistry.from_ailment(ailment)
|
||||
if attacker.mon.pokemon.status != PokemonInstance.Status.NONE: return
|
||||
if StatusRegistry.is_immune(attacker.mon.get_types(), status): return
|
||||
if battle.rng.randi_range(1, 100) > chance: return
|
||||
|
||||
await battle.announce_ability(defender)
|
||||
await battle.inflict_status(defender, attacker, ailment, null, false)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://c5d33ammbuf5l
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
class_name FlashFireAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Torche : immunise contre le type Feu, puis renforce ses propres capacites Feu de 50 %
|
||||
|
||||
func _init() -> void:
|
||||
id = &"flash_fire"
|
||||
|
||||
func on_before_hit(battle : Battle, attacker : BattleSlot, defender : BattleSlot, move : MoveData) -> bool:
|
||||
if attacker == defender or move.type.to_lower() != "fire": return false
|
||||
await battle.announce_ability(defender)
|
||||
defender.mon.flags["flash_fire"] = true
|
||||
await battle.message("La puissance Feu de %s augmente !" % defender.mon.display_name())
|
||||
return true
|
||||
|
||||
func modify_outgoing_damage(attacker : BattleMon, _defender : BattleMon, move : MoveData, damage : float) -> float:
|
||||
if attacker.flags.get("flash_fire", false) and move.type.to_lower() == "fire":
|
||||
return damage * 1.5
|
||||
return damage
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dqaqq6ufk32kp
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
class_name IntimidateAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Intimidation : a l'entree en combat, baisse l'Attaque de tous les adversaires
|
||||
|
||||
func _init() -> void:
|
||||
id = &"intimidate"
|
||||
|
||||
func on_enter(battle : Battle, slot : BattleSlot) -> void:
|
||||
var targets : Array[BattleSlot] = battle.get_opponents(slot)
|
||||
if targets.is_empty(): return
|
||||
await battle.announce_ability(slot)
|
||||
for target in targets:
|
||||
await battle.change_stat(target, "attack", -1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://d25o30qt8rks2
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
class_name OnEnterWeatherAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Crachin, Sechesse, Sable Volant, Alerte Neige : change la meteo a l'entree en combat
|
||||
|
||||
var weather : BattleField.Weather
|
||||
|
||||
func _init(p_weather : BattleField.Weather) -> void:
|
||||
id = &"weather_setter"
|
||||
weather = p_weather
|
||||
|
||||
func on_enter(battle : Battle, slot : BattleSlot) -> void:
|
||||
if battle.field.weather == weather: return
|
||||
await battle.announce_ability(slot)
|
||||
await battle.set_weather(weather, slot)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://ddsup6cge6ys2
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
class_name PinchBoostAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Engrais, Brasier, Torrent, Essaim : capacites d'un type x1.5 quand les PV sont a 1/3 ou moins
|
||||
|
||||
var boosted_type : String
|
||||
|
||||
func _init(p_type : String) -> void:
|
||||
id = &"pinch_boost"
|
||||
boosted_type = p_type
|
||||
|
||||
func modify_outgoing_damage(attacker : BattleMon, _defender : BattleMon, move : MoveData, damage : float) -> float:
|
||||
if move.type.to_lower() != boosted_type: return damage
|
||||
if attacker.pokemon.current_hp * 3 <= attacker.pokemon.get_max_hp(): return damage * 1.5
|
||||
return damage
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dvudfonq4gidn
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
class_name RoughSkinAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Peau Dure : l'attaquant au contact perd 1/8 de ses PV
|
||||
|
||||
func _init() -> void:
|
||||
id = &"rough_skin"
|
||||
|
||||
func on_after_hit(battle : Battle, attacker : BattleSlot, defender : BattleSlot, move : MoveData, damage : int) -> void:
|
||||
if damage <= 0 or move.category != "physical" or attacker == defender or !attacker.is_active(): return
|
||||
await battle.announce_ability(defender)
|
||||
await battle.hurt(attacker, maxi(1, floori(attacker.mon.pokemon.get_max_hp() / 8.0)), "%s est blessé !" % attacker.mon.display_name())
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cvmilgxfovbed
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
class_name SpeedBoostAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Turbo : la Vitesse monte d'un cran a la fin de chaque tour
|
||||
|
||||
func _init() -> void:
|
||||
id = &"speed_boost"
|
||||
|
||||
func on_turn_end(battle : Battle, slot : BattleSlot) -> void:
|
||||
await battle.announce_ability(slot)
|
||||
await battle.change_stat(slot, "speed", 1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cl54nloqah31k
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
class_name TechnicianAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Technicien : capacites de puissance 60 ou moins x1.5
|
||||
|
||||
func _init() -> void:
|
||||
id = &"technician"
|
||||
|
||||
func modify_outgoing_damage(_attacker : BattleMon, _defender : BattleMon, move : MoveData, damage : float) -> float:
|
||||
return damage * 1.5 if move.power > 0 and move.power <= 60 else damage
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://ppremfcljor2
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
class_name TypeAbsorbAbility
|
||||
extends BattleEffect
|
||||
|
||||
#annule les capacites d'un type, avec un bonus eventuel :
|
||||
# Absorb Eau / Absorb Volt : soigne 1/4 des PV ; Paratonnerre / Lavabo : Att. Spe. +1 ;
|
||||
# Herbivore : Attaque +1 ; Motorisé : Vitesse +1 ; Levitation : aucun bonus (capacites de degats seulement)
|
||||
|
||||
var absorbed_type : String
|
||||
var heal_fraction : float
|
||||
var boost_stat : String
|
||||
var damaging_only : bool
|
||||
|
||||
func _init(p_type : String, p_heal_fraction : float = 0.0, p_boost_stat : String = "", p_damaging_only : bool = false) -> void:
|
||||
id = &"type_absorb"
|
||||
absorbed_type = p_type
|
||||
heal_fraction = p_heal_fraction
|
||||
boost_stat = p_boost_stat
|
||||
damaging_only = p_damaging_only
|
||||
|
||||
func on_before_hit(battle : Battle, attacker : BattleSlot, defender : BattleSlot, move : MoveData) -> bool:
|
||||
if attacker == defender or move.type.to_lower() != absorbed_type: return false
|
||||
if damaging_only and move.power <= 0: return false
|
||||
|
||||
await battle.announce_ability(defender)
|
||||
var defender_name : String = defender.mon.display_name()
|
||||
if heal_fraction > 0.0:
|
||||
var healed : int = await battle.heal(defender, maxi(1, floori(defender.mon.pokemon.get_max_hp() * heal_fraction)), "%s récupère des PV !" % defender_name)
|
||||
if healed == 0: await battle.message("Ça n'affecte pas %s..." % defender_name)
|
||||
elif boost_stat != "":
|
||||
await battle.change_stat(defender, boost_stat, 1)
|
||||
else:
|
||||
await battle.message("Ça n'affecte pas %s..." % defender_name)
|
||||
return true
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://b1k6et1vy3bfl
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
class_name WeatherSpeedAbility
|
||||
extends BattleEffect
|
||||
|
||||
#Glissade, Chlorophylle, Baigne Sable, Chasse-Neige : Vitesse x2 sous une meteo donnee
|
||||
|
||||
var weather : BattleField.Weather
|
||||
|
||||
func _init(p_weather : BattleField.Weather) -> void:
|
||||
id = &"weather_speed"
|
||||
weather = p_weather
|
||||
|
||||
func modify_stat(mon : BattleMon, stat : PokemonNature.Stat, value : float) -> float:
|
||||
if stat == PokemonNature.Stat.SPEED and mon.field != null and mon.field.weather == weather:
|
||||
return value * 2.0
|
||||
return value
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://caxdy1eiaw58j
|
||||
|
|
@ -28,7 +28,6 @@ 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] = []
|
||||
var field : BattleField = BattleField.new() #meteo (terrains et barrieres plus tard)
|
||||
|
||||
func _init(p_setup : BattleSetup, p_presenter : BattlePresenter = null) -> void:
|
||||
setup = p_setup
|
||||
|
|
@ -47,7 +46,7 @@ func _build_slots() -> void:
|
|||
slot.index = int(per_side_count.get(participant.side, 0))
|
||||
per_side_count[participant.side] = slot.index + 1
|
||||
slot.participant = participant
|
||||
_place(slot, pokemon)
|
||||
slot.set_pokemon(pokemon)
|
||||
slots.append(slot)
|
||||
|
||||
#---------- boucle principale ----------
|
||||
|
|
@ -56,8 +55,6 @@ func run() -> Outcome:
|
|||
await emit_event(BattleEvent.make(BattleEvent.Type.BATTLE_START))
|
||||
for slot in slots:
|
||||
await emit_event(BattleEvent.make(BattleEvent.Type.SENT_OUT, slot))
|
||||
for slot in _slots_by_speed():
|
||||
await _run_on_enter(slot) #talents d'entree (Intimidation, Crachin...)
|
||||
|
||||
if check_end():
|
||||
return await _finish()
|
||||
|
|
@ -146,21 +143,6 @@ func heal(slot : BattleSlot, amount : int, text : String = "") -> int:
|
|||
await emit_event(event)
|
||||
return healed
|
||||
|
||||
## Annonce qu'un talent se declenche.
|
||||
func announce_ability(slot : BattleSlot) -> void:
|
||||
if slot.mon == null or slot.mon.pokemon.ability == null: return
|
||||
await message("%s utilise son talent %s !" % [slot.mon.display_name(), slot.mon.pokemon.ability.get_display_name()])
|
||||
|
||||
## Change la meteo. Renvoie false si c'est deja la meteo en cours (la capacite ou le talent echoue).
|
||||
func set_weather(weather : BattleField.Weather, _source : BattleSlot = null, turns : int = BattleField.DEFAULT_WEATHER_TURNS) -> bool:
|
||||
if weather == BattleField.Weather.NONE or field.weather == weather: return false
|
||||
field.set_weather(weather, turns)
|
||||
var event := BattleEvent.make(BattleEvent.Type.WEATHER_CHANGED)
|
||||
event.amount = weather
|
||||
event.text = BattleField.start_message(weather)
|
||||
await emit_event(event)
|
||||
return true
|
||||
|
||||
## Tente d'infliger un statut (nom PokeAPI : "burn", "paralysis", "confusion"...). Renvoie true si applique.
|
||||
## announce_failure : afficher pourquoi ca echoue (capacite de statut), sinon echec silencieux (effet secondaire).
|
||||
func inflict_status(source : BattleSlot, target : BattleSlot, ailment : String, move : MoveData, announce_failure : bool) -> bool:
|
||||
|
|
@ -285,44 +267,28 @@ func _execute_switch(action : BattleAction) -> void:
|
|||
var slot : BattleSlot = action.slot
|
||||
if !get_replacement_options(slot).has(action.switch_to): return
|
||||
await emit_event(BattleEvent.make(BattleEvent.Type.WITHDRAWN, slot))
|
||||
_place(slot, action.switch_to)
|
||||
slot.set_pokemon(action.switch_to)
|
||||
slot.mon.acted_this_turn = true
|
||||
await emit_event(BattleEvent.make(BattleEvent.Type.SENT_OUT, slot))
|
||||
await _run_on_enter(slot)
|
||||
|
||||
#---------- fin de tour ----------
|
||||
|
||||
func _end_of_turn() -> void:
|
||||
#effets de fin de tour (brulure, poison...), du plus rapide au plus lent
|
||||
for slot in _slots_by_speed():
|
||||
var ordered : Array[BattleSlot] = get_active_slots()
|
||||
ordered.sort_custom(func(a : BattleSlot, b : BattleSlot) -> bool:
|
||||
return a.mon.get_stat(PokemonNature.Stat.SPEED) > b.mon.get_stat(PokemonNature.Stat.SPEED))
|
||||
|
||||
for slot in ordered:
|
||||
if !slot.is_active(): continue
|
||||
for effect in slot.mon.get_effects():
|
||||
if !slot.is_active(): break
|
||||
await effect.on_turn_end(self, slot)
|
||||
if check_end(): return
|
||||
|
||||
await _weather_end_of_turn()
|
||||
for slot in slots:
|
||||
if slot.mon != null: slot.mon.cleanup_volatile()
|
||||
|
||||
func _weather_end_of_turn() -> void:
|
||||
if field.weather == BattleField.Weather.NONE: return
|
||||
|
||||
if field.weather == BattleField.Weather.SANDSTORM:
|
||||
for slot in get_active_slots():
|
||||
if BattleField.is_sandstorm_immune(slot.mon.get_types()): continue
|
||||
await hurt(slot, maxi(1, floori(slot.mon.pokemon.get_max_hp() / 16.0)), "%s est frappé par la tempête de sable !" % slot.mon.display_name())
|
||||
if check_end(): return
|
||||
|
||||
field.weather_turns -= 1
|
||||
if field.weather_turns <= 0:
|
||||
var ended : BattleField.Weather = field.weather
|
||||
field.set_weather(BattleField.Weather.NONE)
|
||||
var event := BattleEvent.make(BattleEvent.Type.WEATHER_CHANGED)
|
||||
event.amount = BattleField.Weather.NONE
|
||||
event.text = BattleField.end_message(ended)
|
||||
await emit_event(event)
|
||||
|
||||
func _replace_fainted() -> void:
|
||||
for slot in slots:
|
||||
if slot.mon == null or !slot.mon.is_fainted(): continue
|
||||
|
|
@ -334,27 +300,8 @@ func _replace_fainted() -> void:
|
|||
|
||||
var chosen : PokemonInstance = await slot.participant.controller.choose_replacement(self, slot)
|
||||
if chosen == null or !options.has(chosen): chosen = options[0]
|
||||
_place(slot, chosen)
|
||||
slot.set_pokemon(chosen)
|
||||
await emit_event(BattleEvent.make(BattleEvent.Type.SENT_OUT, slot))
|
||||
await _run_on_enter(slot)
|
||||
|
||||
#---------- placement et entree en combat ----------
|
||||
|
||||
func _place(slot : BattleSlot, pokemon : PokemonInstance) -> void:
|
||||
slot.set_pokemon(pokemon)
|
||||
slot.mon.field = field
|
||||
|
||||
func _run_on_enter(slot : BattleSlot) -> void:
|
||||
if slot.mon == null: return
|
||||
for effect in slot.mon.get_effects():
|
||||
if !slot.is_active(): return
|
||||
await effect.on_enter(self, slot)
|
||||
|
||||
func _slots_by_speed() -> Array[BattleSlot]:
|
||||
var ordered : Array[BattleSlot] = get_active_slots()
|
||||
ordered.sort_custom(func(a : BattleSlot, b : BattleSlot) -> bool:
|
||||
return a.mon.get_stat(PokemonNature.Stat.SPEED) > b.mon.get_stat(PokemonNature.Stat.SPEED))
|
||||
return ordered
|
||||
|
||||
#---------- requetes (utilisees par les controleurs, les effets et TargetResolver) ----------
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ func _types_of(pokemon : PokemonInstance) -> Array[String]:
|
|||
func _score(battle : Battle, slot : BattleSlot, move : MoveData, candidate : Variant) -> float:
|
||||
var targets : Array[BattleSlot] = TargetResolver.resolve(battle, slot, move, candidate)
|
||||
if targets.is_empty(): return -1.0
|
||||
if move.power <= 0 or move.category == "status": return _status_score(battle, slot, move, targets)
|
||||
if move.power <= 0 or move.category == "status": return _status_score(slot, move, targets)
|
||||
|
||||
var accuracy : float = 1.0 if move.accuracy <= 0 else move.accuracy / 100.0
|
||||
var spread : bool = targets.size() > 1
|
||||
|
|
@ -155,15 +155,10 @@ func _score(battle : Battle, slot : BattleSlot, move : MoveData, candidate : Var
|
|||
if damage >= remaining: score += KILL_SCORE
|
||||
return score
|
||||
|
||||
func _status_score(battle : Battle, slot : BattleSlot, move : MoveData, targets : Array[BattleSlot]) -> float:
|
||||
func _status_score(slot : BattleSlot, move : MoveData, targets : Array[BattleSlot]) -> float:
|
||||
#valeur approximative d'une capacite de statut : toujours bien inferieure a une bonne attaque,
|
||||
#mais choisie quand elle apporte vraiment quelque chose (statut sur un adversaire sain, boost utile, soin)
|
||||
var score : float = 0.1
|
||||
|
||||
var weather : int = BattleField.weather_for_move(move.api_name)
|
||||
if weather != BattleField.Weather.NONE:
|
||||
return 6.0 if battle.field.weather != weather else 0.1
|
||||
|
||||
for t in targets:
|
||||
var enemy : bool = t.side != slot.side
|
||||
|
||||
|
|
|
|||
|
|
@ -184,8 +184,7 @@ func _seeded_team(rng : RandomNumberGenerator, ids : Array, size : int) -> Pokem
|
|||
for i in size:
|
||||
var species : PokemonData = Pokedex.get_pokemon(ids[rng.randi_range(0, ids.size() - 1)])
|
||||
if species == null: continue
|
||||
var ability : AbilityData = PokemonInstance.pick_default_ability(species, rng)
|
||||
team.add_pokemon(PokemonInstance.create(species, rng.randi_range(level_min, level_max), rng.randi_range(0, 24), null, ability))
|
||||
team.add_pokemon(PokemonInstance.create(species, rng.randi_range(level_min, level_max), rng.randi_range(0, 24)))
|
||||
return team
|
||||
|
||||
func _describe(setup : BattleSetup) -> String:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ enum Type {
|
|||
MOVE_USED, MOVE_MISSED, DAMAGE, CRITICAL,
|
||||
SUPER_EFFECTIVE, NOT_VERY_EFFECTIVE, NO_EFFECT,
|
||||
FAINTED, FLED, MESSAGE,
|
||||
STATUS_CHANGED, HEAL, WEATHER_CHANGED,
|
||||
STATUS_CHANGED, HEAL,
|
||||
}
|
||||
|
||||
var type : Type = Type.MESSAGE
|
||||
|
|
@ -52,5 +52,5 @@ func describe() -> String:
|
|||
Type.NO_EFFECT: return "Ça n'affecte pas %s..." % target_name
|
||||
Type.FAINTED: return "%s est K.O. !" % source_name
|
||||
Type.FLED: return "Vous prenez la fuite !"
|
||||
Type.MESSAGE, Type.STATUS_CHANGED, Type.HEAL, Type.WEATHER_CHANGED: return text
|
||||
Type.MESSAGE, Type.STATUS_CHANGED, Type.HEAL: return text
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
class_name BattleField
|
||||
extends RefCounted
|
||||
|
||||
#etat du terrain, commun aux deux camps : meteo (terrains et barrieres viendront plus tard).
|
||||
#les talents (Crachin, Sechesse...) et les capacites (Danse Pluie...) la modifient via Battle.set_weather().
|
||||
|
||||
enum Weather { NONE, SUN, RAIN, SANDSTORM, SNOW }
|
||||
|
||||
const DEFAULT_WEATHER_TURNS : int = 5
|
||||
|
||||
#capacites qui changent la meteo (nom d'API)
|
||||
const WEATHER_MOVES : Dictionary = {
|
||||
"sunny-day": Weather.SUN,
|
||||
"rain-dance": Weather.RAIN,
|
||||
"sandstorm": Weather.SANDSTORM,
|
||||
"hail": Weather.SNOW,
|
||||
"snowscape": Weather.SNOW,
|
||||
}
|
||||
|
||||
var weather : Weather = Weather.NONE
|
||||
var weather_turns : int = 0
|
||||
|
||||
static func weather_for_move(move_api_name : String) -> int:
|
||||
return int(WEATHER_MOVES.get(move_api_name, Weather.NONE))
|
||||
|
||||
func set_weather(new_weather : Weather, turns : int = DEFAULT_WEATHER_TURNS) -> void:
|
||||
weather = new_weather
|
||||
weather_turns = turns if new_weather != Weather.NONE else 0
|
||||
|
||||
## Multiplicateur de degats selon la meteo et le type de la capacite.
|
||||
func damage_multiplier(move_type : String) -> float:
|
||||
match weather:
|
||||
Weather.SUN:
|
||||
if move_type == "fire": return 1.5
|
||||
if move_type == "water": return 0.5
|
||||
Weather.RAIN:
|
||||
if move_type == "water": return 1.5
|
||||
if move_type == "fire": return 0.5
|
||||
return 1.0
|
||||
|
||||
## Bonus defensifs de la meteo : tempete de sable (Roche, Def. Spe. x1.5), neige (Glace, Defense x1.5).
|
||||
func modify_stat(mon : BattleMon, stat : PokemonNature.Stat, value : float) -> float:
|
||||
if weather == Weather.SANDSTORM and stat == PokemonNature.Stat.SP_DEFENSE and mon.get_types().has("rock"):
|
||||
return value * 1.5
|
||||
if weather == Weather.SNOW and stat == PokemonNature.Stat.DEFENSE and mon.get_types().has("ice"):
|
||||
return value * 1.5
|
||||
return value
|
||||
|
||||
static func is_sandstorm_immune(types : Array[String]) -> bool:
|
||||
return types.has("rock") or types.has("ground") or types.has("steel")
|
||||
|
||||
static func start_message(w : int) -> String:
|
||||
match w:
|
||||
Weather.SUN: return "Les rayons du soleil brillent !"
|
||||
Weather.RAIN: return "Il commence à pleuvoir !"
|
||||
Weather.SANDSTORM: return "Une tempête de sable se lève !"
|
||||
Weather.SNOW: return "Il commence à neiger !"
|
||||
return ""
|
||||
|
||||
static func end_message(w : int) -> String:
|
||||
match w:
|
||||
Weather.SUN: return "Le soleil se couche."
|
||||
Weather.RAIN: return "La pluie s'arrête."
|
||||
Weather.SANDSTORM: return "La tempête de sable se calme."
|
||||
Weather.SNOW: return "La neige s'arrête."
|
||||
return ""
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://thpl65yj8dpy
|
||||
|
|
@ -36,16 +36,11 @@ var volatile : Array[BattleEffect] = [] #effets temporaires : confusion, peur...
|
|||
var status_turns : int = 0 #sommeil : nombre de tentatives d'action restantes
|
||||
var toxic_counter : int = 0 #poison grave : nombre de tours ecoules
|
||||
var acted_this_turn : bool = false
|
||||
var field : BattleField = null #terrain du combat (meteo...), renseigne par Battle
|
||||
var flags : Dictionary = {} #petits etats libres pour les talents (ex. "flash_fire")
|
||||
var _ability_effect : BattleEffect = null
|
||||
|
||||
func _init(p_pokemon : PokemonInstance) -> void:
|
||||
pokemon = p_pokemon
|
||||
if pokemon.status == PokemonInstance.Status.SLEEP:
|
||||
status_turns = 2 #deja endormi en entrant en combat
|
||||
if pokemon.ability != null:
|
||||
_ability_effect = AbilityRegistry.create(pokemon.ability.api_name)
|
||||
|
||||
func display_name() -> String:
|
||||
return pokemon.get_display_name()
|
||||
|
|
@ -66,7 +61,6 @@ func get_effects() -> Array[BattleEffect]:
|
|||
var result : Array[BattleEffect] = []
|
||||
var status_effect : BattleEffect = StatusRegistry.effect_for(int(pokemon.status))
|
||||
if status_effect != null: result.append(status_effect)
|
||||
if _ability_effect != null: result.append(_ability_effect)
|
||||
result.append_array(volatile)
|
||||
return result
|
||||
|
||||
|
|
@ -112,7 +106,6 @@ func get_stat(stat : PokemonNature.Stat) -> float:
|
|||
var value : float = pokemon.get_stat(stat) * _stage_multiplier(get_stage(stat))
|
||||
for effect in get_effects():
|
||||
value = effect.modify_stat(self, stat, value)
|
||||
if field != null: value = field.modify_stat(self, stat, value)
|
||||
return value
|
||||
|
||||
## Multiplicateur de precision de cette attaque contre une cible (boost de precision - esquive).
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ static func calculate(attacker : BattleMon, defender : BattleMon, move : MoveDat
|
|||
var stab : float = STAB_MULTIPLIER if attacker.get_types().has(move.type.to_lower()) else 1.0
|
||||
|
||||
damage *= roll * stab * effectiveness
|
||||
if attacker.field != null: damage *= attacker.field.damage_multiplier(move.type.to_lower())
|
||||
for effect in attacker.get_effects():
|
||||
damage = effect.modify_outgoing_damage(attacker, defender, move, damage)
|
||||
if critical: damage *= CRIT_MULTIPLIER
|
||||
if spread: damage *= SPREAD_MULTIPLIER
|
||||
|
||||
|
|
|
|||
|
|
@ -15,22 +15,6 @@ func modify_stat(_mon : BattleMon, _stat : PokemonNature.Stat, value : float) ->
|
|||
func before_action(_battle : Battle, _slot : BattleSlot, _action : BattleAction) -> bool:
|
||||
return true
|
||||
|
||||
## Appele quand le pokemon entre en combat (Intimidation, Crachin...).
|
||||
func on_enter(_battle : Battle, _slot : BattleSlot) -> void:
|
||||
pass
|
||||
|
||||
## Appele sur le DEFENSEUR avant qu'une capacite ne le touche. Renvoyer true = la capacite est annulee (talent d'absorption...).
|
||||
func on_before_hit(_battle : Battle, _attacker : BattleSlot, _defender : BattleSlot, _move : MoveData) -> bool:
|
||||
return false
|
||||
|
||||
## Appele sur le DEFENSEUR apres chaque coup recu (talents de contact...).
|
||||
func on_after_hit(_battle : Battle, _attacker : BattleSlot, _defender : BattleSlot, _move : MoveData, _damage : int) -> void:
|
||||
pass
|
||||
|
||||
## Appele sur l'ATTAQUANT pendant le calcul des degats : renvoie les degats modifies (Technicien, Adaptabilite...).
|
||||
func modify_outgoing_damage(_attacker : BattleMon, _defender : BattleMon, _move : MoveData, damage : float) -> float:
|
||||
return damage
|
||||
|
||||
## Appele a la fin de chaque tour (degats de brulure, de poison...).
|
||||
func on_turn_end(_battle : Battle, _slot : BattleSlot) -> void:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ static func _use_damaging(battle : Battle, actor : BattleSlot, move : MoveData,
|
|||
if !_hits(battle, actor, target, move):
|
||||
await battle.emit_event(BattleEvent.make(BattleEvent.Type.MOVE_MISSED, actor, target))
|
||||
continue
|
||||
if await _blocked_before_hit(battle, actor, target, move): continue
|
||||
|
||||
var hits : int = _roll_hit_count(battle, move)
|
||||
var landed : int = 0
|
||||
|
|
@ -62,10 +61,8 @@ static func _use_damaging(battle : Battle, actor : BattleSlot, move : MoveData,
|
|||
var calc : Dictionary = DamageCalculator.calculate(actor.mon, target.mon, move, battle.rng, spread)
|
||||
effectiveness = calc["effectiveness"]
|
||||
if effectiveness == 0.0: break
|
||||
var dealt : int = await battle.deal_damage(actor, target, int(calc["damage"]))
|
||||
total_dealt += dealt
|
||||
total_dealt += await battle.deal_damage(actor, target, int(calc["damage"]))
|
||||
landed += 1
|
||||
await _after_hit(battle, actor, target, move, dealt)
|
||||
if calc["critical"]: await battle.emit_event(BattleEvent.make(BattleEvent.Type.CRITICAL, actor, target))
|
||||
|
||||
if landed == 0:
|
||||
|
|
@ -130,12 +127,6 @@ static func _secondary_effects(battle : Battle, actor : BattleSlot, target : Bat
|
|||
#---------- capacites de statut ----------
|
||||
|
||||
static func _use_status(battle : Battle, actor : BattleSlot, move : MoveData, targets : Array[BattleSlot]) -> void:
|
||||
#capacites de meteo : une seule fois, quelles que soient les cibles
|
||||
var weather : int = BattleField.weather_for_move(move.api_name)
|
||||
if weather != BattleField.Weather.NONE:
|
||||
if !await battle.set_weather(weather, actor): await battle.message("Mais cela échoue !")
|
||||
return
|
||||
|
||||
var handled : bool = false
|
||||
|
||||
for target in targets:
|
||||
|
|
@ -143,7 +134,6 @@ static func _use_status(battle : Battle, actor : BattleSlot, move : MoveData, ta
|
|||
if target != actor and !_hits(battle, actor, target, move):
|
||||
await battle.emit_event(BattleEvent.make(BattleEvent.Type.MOVE_MISSED, actor, target))
|
||||
continue
|
||||
if target != actor and await _blocked_before_hit(battle, actor, target, move): continue
|
||||
|
||||
if move.ailment != "none":
|
||||
handled = true
|
||||
|
|
@ -167,19 +157,6 @@ static func _use_status(battle : Battle, actor : BattleSlot, move : MoveData, ta
|
|||
if !handled:
|
||||
await battle.message("Mais cela n'a pas d'effet pour l'instant.")
|
||||
|
||||
#---------- crochets des talents ----------
|
||||
|
||||
static func _blocked_before_hit(battle : Battle, actor : BattleSlot, target : BattleSlot, move : MoveData) -> bool:
|
||||
#un talent du defenseur peut annuler la capacite (Absorb Eau, Levitation...)
|
||||
for effect in target.mon.get_effects():
|
||||
if await effect.on_before_hit(battle, actor, target, move): return true
|
||||
return false
|
||||
|
||||
static func _after_hit(battle : Battle, actor : BattleSlot, target : BattleSlot, move : MoveData, damage : int) -> void:
|
||||
#talents de contact du defenseur (Statik, Peau Dure...)
|
||||
for effect in target.mon.get_effects():
|
||||
await effect.on_after_hit(battle, actor, target, move, damage)
|
||||
|
||||
#---------- precision et coups multiples ----------
|
||||
|
||||
static func _hits(battle : Battle, actor : BattleSlot, target : BattleSlot, move : MoveData) -> bool:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ const MAX_LEVEL : int = 100
|
|||
@export var nature : PokemonNature.Nature = PokemonNature.Nature.HARDY
|
||||
@export var current_hp : int = 1
|
||||
@export var status : Status = Status.NONE
|
||||
@export var ability : AbilityData = null #talent (null = aucun)
|
||||
|
||||
@export_group("Capacités")
|
||||
@export var learned_moves : Array[MoveData] = [] #toutes les capacites debloquees
|
||||
|
|
@ -26,29 +25,16 @@ const MAX_LEVEL : int = 100
|
|||
|
||||
#---------- creation ----------
|
||||
|
||||
static func create(p_species : PokemonData, p_level : int, p_nature : int = -1, p_mount_data : MountProfile = null, p_ability : AbilityData = null) -> PokemonInstance:
|
||||
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.ability = p_ability if p_ability != null else pick_default_ability(p_species)
|
||||
p.fill_moves_for_level()
|
||||
p.current_hp = p.get_max_hp()
|
||||
return p
|
||||
|
||||
## Talent par defaut d'une espece : tire au hasard parmi les talents normaux (le talent cache seulement s'il n'y en a pas d'autre).
|
||||
## rng : generateur a utiliser pour un resultat reproductible (tests) ; sinon hasard global.
|
||||
static func pick_default_ability(p_species : PokemonData, rng : RandomNumberGenerator = null) -> AbilityData:
|
||||
var normal : Array[AbilityData] = []
|
||||
var hidden : Array[AbilityData] = []
|
||||
for entry in p_species.abilities:
|
||||
if entry == null or entry.ability == null: continue
|
||||
(hidden if entry.is_hidden else normal).append(entry.ability)
|
||||
var pool : Array[AbilityData] = normal if !normal.is_empty() else hidden
|
||||
if pool.is_empty(): return null
|
||||
return pool[rng.randi_range(0, pool.size() - 1)] if rng != null else pool.pick_random()
|
||||
|
||||
#---------- infos ----------
|
||||
|
||||
func get_display_name() -> String:
|
||||
|
|
@ -186,7 +172,6 @@ func to_dict() -> Dictionary:
|
|||
"learned_moves": learned,
|
||||
"active_moves": slots,
|
||||
"mount_data": mount_data.resource_path if mount_data else "",
|
||||
"ability": ability.api_name if ability else "",
|
||||
}
|
||||
|
||||
static func from_dict(data : Dictionary) -> PokemonInstance:
|
||||
|
|
@ -215,8 +200,5 @@ static func from_dict(data : Dictionary) -> PokemonInstance:
|
|||
if mount_path.begins_with("res://") and ResourceLoader.exists(mount_path):
|
||||
p.mount_data = load(mount_path) as MountProfile
|
||||
|
||||
var ability_name : String = data.get("ability", "")
|
||||
p.ability = Pokedex.get_ability(ability_name) if ability_name != "" else pick_default_ability(species_data)
|
||||
|
||||
p.current_hp = clampi(int(data.get("hp", p.get_max_hp())), 0, p.get_max_hp())
|
||||
return p
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ extends Resource
|
|||
@export var random_nature : bool = true
|
||||
@export var nature : PokemonNature.Nature = PokemonNature.Nature.HARDY
|
||||
|
||||
@export_group("Talent")
|
||||
## Vide = talent tire au hasard parmi les talents normaux de l'espece.
|
||||
@export var ability : AbilityData
|
||||
|
||||
@export_group("Capacités")
|
||||
## Vide = capacites automatiques selon le niveau. Sinon : ces capacites (max 4) sont equipees.
|
||||
@export var forced_moves : Array[MoveData] = []
|
||||
|
|
@ -22,8 +18,8 @@ extends Resource
|
|||
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, null, ability)
|
||||
var p := PokemonInstance.create(species, level, nature_value)
|
||||
p.nickname = nickname
|
||||
if !forced_moves.is_empty():
|
||||
p.set_active_moves(forced_moves)
|
||||
return p
|
||||
return p
|
||||
|
|
@ -324,7 +324,6 @@ func _update_info() -> void:
|
|||
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.ability != null: lines.append("Talent : %s" % p.ability.get_display_name())
|
||||
if p.can_be_mounted(): lines.append("Peut être monté")
|
||||
lines.append("")
|
||||
for slot in p.active_moves:
|
||||
|
|
|
|||
|
|
@ -231,7 +231,6 @@ func _update_info(index : int) -> void:
|
|||
var lines : Array[String] = []
|
||||
lines.append("%s Niv.%d" % [p.get_display_name(), p.level])
|
||||
lines.append("Nature : %s" % String(PokemonNature.Nature.keys()[p.nature]).capitalize())
|
||||
if p.ability != null: lines.append("Talent : %s" % p.ability.get_display_name())
|
||||
if p.can_be_mounted(): lines.append("Peut être monté")
|
||||
lines.append("")
|
||||
lines.append("PV : %d / %d" % [p.current_hp, p.get_max_hp()])
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
[ext_resource type="Resource" uid="uid://dlhup2358q214" path="res://data/pokeapi/moves/screech.tres" id="13_tfb7h"]
|
||||
[ext_resource type="Resource" uid="uid://cn40f7a0djras" path="res://data/pokeapi/pokemon/0124_jynx.tres" id="14_2rm86"]
|
||||
[ext_resource type="Script" uid="uid://chqysfkk1prdb" path="res://game/pokemon/trainer_data.gd" id="15_htcyl"]
|
||||
[ext_resource type="PackedScene" uid="uid://ca0wevg1lb4rx" path="res://utils/WaterMaker3D/WaterMaker3D.tscn" id="18_08mbr"]
|
||||
|
||||
[sub_resource type="Environment" id="Environment_l60uw"]
|
||||
background_mode = 1
|
||||
|
|
@ -33,43 +32,11 @@ glow_enabled = true
|
|||
fog_enabled = true
|
||||
fog_light_color = Color(1, 1, 1, 1)
|
||||
fog_density = 0.001
|
||||
volumetric_fog_enabled = true
|
||||
volumetric_fog_density = 0.0
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hh4m8"]
|
||||
albedo_texture = ExtResource("1_i3wuw")
|
||||
uv1_triplanar = true
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_08mbr"]
|
||||
resource_local_to_scene = true
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_tfb7h"]
|
||||
resource_local_to_scene = true
|
||||
noise = SubResource("FastNoiseLite_08mbr")
|
||||
seamless = true
|
||||
as_normal_map = true
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_2rm86"]
|
||||
resource_local_to_scene = true
|
||||
noise = SubResource("FastNoiseLite_08mbr")
|
||||
seamless = true
|
||||
as_normal_map = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_htcyl"]
|
||||
resource_local_to_scene = true
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
depth_draw_mode = 1
|
||||
albedo_color = Color(0.30980393, 0.5411765, 0.8666667, 0.3882353)
|
||||
roughness = 0.0
|
||||
normal_enabled = true
|
||||
normal_texture = SubResource("NoiseTexture2D_tfb7h")
|
||||
refraction_enabled = true
|
||||
refraction_texture = SubResource("NoiseTexture2D_2rm86")
|
||||
uv1_scale = Vector3(0.04, 0.04, 0.04)
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_5osju"]
|
||||
albedo_texture = ExtResource("2_rcccy")
|
||||
uv1_triplanar = true
|
||||
|
|
@ -88,11 +55,7 @@ albedo_texture = ExtResource("5_xly4w")
|
|||
[sub_resource type="Resource" id="Resource_yebv1"]
|
||||
script = ExtResource("8_tfb7h")
|
||||
species = ExtResource("10_htcyl")
|
||||
level = null
|
||||
nickname = "Testkatchu"
|
||||
random_nature = null
|
||||
nature = null
|
||||
forced_moves = null
|
||||
metadata/_custom_type_script = "uid://cqsokrfi2d05y"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_h63fm"]
|
||||
|
|
@ -110,9 +73,6 @@ albedo_color = Color(1, 1, 1, 0)
|
|||
script = ExtResource("8_tfb7h")
|
||||
species = ExtResource("14_2rm86")
|
||||
level = 13
|
||||
nickname = null
|
||||
random_nature = null
|
||||
nature = null
|
||||
forced_moves = Array[ExtResource("9_2rm86")]([ExtResource("11_g0lob"), ExtResource("12_08mbr"), ExtResource("13_tfb7h")])
|
||||
metadata/_custom_type_script = "uid://cqsokrfi2d05y"
|
||||
|
||||
|
|
@ -148,30 +108,20 @@ environment = SubResource("Environment_l60uw")
|
|||
|
||||
[node name="map" type="Node3D" parent="." unique_id=2118568493]
|
||||
|
||||
[node name="floor" type="CSGBox3D" parent="map" unique_id=1246319742]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.50647, -5.586426, 0)
|
||||
[node name="floor" type="Node3D" parent="map" unique_id=1571459167]
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="map/floor" unique_id=1246319742]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
|
||||
material_override = SubResource("StandardMaterial3D_hh4m8")
|
||||
use_collision = true
|
||||
size = Vector3(61.01294, 10.172852, 40)
|
||||
size = Vector3(40, 1, 40)
|
||||
|
||||
[node name="water_area" type="CSGBox3D" parent="map/floor" unique_id=884797986]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -19.150932, 1.770874, 0)
|
||||
material_override = SubResource("StandardMaterial3D_hh4m8")
|
||||
operation = 2
|
||||
use_collision = true
|
||||
size = Vector3(18.658081, 8.878174, 32.1709)
|
||||
|
||||
[node name="battle_area" type="CSGBox3D" parent="map/floor" unique_id=406023419]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.50647, 5.236426, 15.041)
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="map/floor" unique_id=406023419]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.35, 15.041)
|
||||
material_override = SubResource("StandardMaterial3D_hh4m8")
|
||||
use_collision = true
|
||||
size = Vector3(9, 0.3, 5)
|
||||
|
||||
[node name="WaterMaker3D" parent="map" unique_id=673111270 instance=ExtResource("18_08mbr")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -29.73397, -4.4205165, -0.010149956)
|
||||
size = Vector3(19.148346, 7.7365723, 32.207764)
|
||||
material = SubResource("StandardMaterial3D_htcyl")
|
||||
|
||||
[node name="box" type="CSGBox3D" parent="map" unique_id=498718915]
|
||||
transform = Transform3D(0.6727641, 0, -0.739857, 0, 1, 0, 0.739857, 0, 0.6727641, 3.424123, 0.049908042, 6.7458324)
|
||||
material_override = SubResource("StandardMaterial3D_5osju")
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2D screen_texture : hint_screen_texture;
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = SCREEN_UV;
|
||||
|
||||
// Ripple effect
|
||||
float rippleSpeed = 1.5;
|
||||
float rippleDensity = 10.0;
|
||||
float rippleStrength = 0.02;
|
||||
|
||||
// Calculate angle and distance from the center of the screen
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
vec2 delta = uv - center;
|
||||
float distance = length(delta);
|
||||
float angle = atan(delta.y, delta.x);
|
||||
|
||||
// Apply the ripple effect
|
||||
uv.x += cos(angle * rippleDensity + TIME * rippleSpeed) * rippleStrength * distance;
|
||||
uv.y += sin(angle * rippleDensity + TIME * rippleSpeed) * rippleStrength * distance;
|
||||
|
||||
// Simple blur effect
|
||||
vec4 color = vec4(0.0);
|
||||
float total = 0.0;
|
||||
for (float x = -2.0; x <= 2.0; x++) {
|
||||
for (float y = -2.0; y <= 2.0; y++) {
|
||||
vec2 samplePos = uv + vec2(x, y) * 0.001; // Blur radius
|
||||
color += texture(screen_texture, samplePos);
|
||||
total += 1.0;
|
||||
}
|
||||
}
|
||||
color /= total;
|
||||
|
||||
// Output the final color
|
||||
COLOR = color;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://co0evh1yuk11i
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
shader_type fog;
|
||||
|
||||
uniform float density : hint_range(0, 1, 0.0001) = 1.0;
|
||||
uniform vec4 albedo : source_color = vec4(1.0);
|
||||
uniform vec4 emission : source_color = vec4(0, 0, 0, 1);
|
||||
uniform float height_falloff = 0.0;
|
||||
uniform float edge_fade = 0.1;
|
||||
uniform sampler3D density_texture: hint_default_white;
|
||||
uniform vec4 fade_plane = vec4(0., 1., 0., -999.);
|
||||
|
||||
void fog() {
|
||||
DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0);
|
||||
DENSITY *= texture(density_texture, UVW).r;
|
||||
DENSITY *= pow(clamp(-2.0 * SDF / min(min(SIZE.x, SIZE.y), SIZE.z), 0.0, 1.0), edge_fade);
|
||||
ALBEDO = albedo.rgb;
|
||||
EMISSION = emission.rgb;
|
||||
|
||||
// Anything above the fade plane will be drawn. Anything below has its density set to 0.
|
||||
float fade_sharpness = 0.35; // Higher is faster fade
|
||||
vec3 fade_plane_origin = fade_plane.xyz * fade_plane.w;
|
||||
vec3 pos_rel_to_plane = WORLD_POSITION - fade_plane_origin;
|
||||
float fade_mult = max(0., min(1., dot(pos_rel_to_plane, fade_plane.xyz) * fade_sharpness));
|
||||
DENSITY *= fade_mult * albedo.a;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://7a8yp0mgu34o
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
extends FogVolume
|
||||
|
||||
@export var fade_distance := 5
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(_delta):
|
||||
var cam = get_viewport().get_camera_3d() if get_viewport() else null
|
||||
if cam:
|
||||
var fade_plane_normal = cam.global_transform.basis.z * -1
|
||||
var fade_plane_pos = cam.global_transform.origin + cam.global_transform.basis.z * -fade_distance
|
||||
var fade_plane_distance = fade_plane_pos.dot(fade_plane_normal)
|
||||
var fade_plane = Vector4(fade_plane_normal.x, fade_plane_normal.y, fade_plane_normal.z, fade_plane_distance)
|
||||
self.material.set_shader_parameter("fade_plane", fade_plane)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cshu5qhg3t5yc
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
@tool
|
||||
extends CSGBox3D
|
||||
|
||||
@export var water_texture_move_speed := Vector3(0.0025, 0.0025, 0.0025)
|
||||
@export var water_texture_uv_scale := 0.04
|
||||
@export var water_color := Color(0.3098039329052, 0.54117649793625, 0.86666667461395, 0.38823530077934)
|
||||
@export var fog_color := Color(0, 0.04313725605607, 0.15686275064945)
|
||||
@export_range(0.0, 250.0) var fog_fade_dist := 5.0
|
||||
|
||||
static var last_frame_drew_underwater_effect : int = -999
|
||||
|
||||
func _ready():
|
||||
self.process_priority = 999 # Call _process last to update move after any camera movement
|
||||
|
||||
# Track the current camera with an area so we can check if it is inside the water
|
||||
func should_draw_camera_underwater_effect():
|
||||
var camera := get_viewport().get_camera_3d() if get_viewport() else null
|
||||
if not camera: return false
|
||||
var aabb = self.global_transform * self.get_aabb().grow(0.025)
|
||||
if not aabb.has_point(camera.global_position): return false
|
||||
# Don't draw multiple overlays at once, incase 2 water bodies overlap
|
||||
if last_frame_drew_underwater_effect == Engine.get_process_frames(): return false
|
||||
|
||||
%CameraPosShapeCast3D.global_position = camera.global_position
|
||||
%CameraPosShapeCast3D.force_shapecast_update()
|
||||
var camera_is_in_water_area3d := false
|
||||
for i in %CameraPosShapeCast3D.get_collision_count():
|
||||
if %CameraPosShapeCast3D.get_collider(i) == %SwimmableArea3D:
|
||||
return true
|
||||
return false
|
||||
|
||||
func _update_mesh():
|
||||
if get_node_or_null("%CollisionShape3D"):
|
||||
%CollisionShape3D.shape.size = self.size
|
||||
|
||||
func _process(delta):
|
||||
_update_mesh()
|
||||
if self.material is StandardMaterial3D:
|
||||
if not Engine.is_editor_hint():
|
||||
self.material.uv1_offset += water_texture_move_speed * delta
|
||||
self.material.uv1_scale = Vector3(water_texture_uv_scale,water_texture_uv_scale,water_texture_uv_scale)
|
||||
self.material.albedo_color = water_color
|
||||
%FogVolume.material.set_shader_parameter("albedo", fog_color)
|
||||
%FogVolume.material.set_shader_parameter("emission", fog_color)
|
||||
%FogVolume.size = self.size
|
||||
%FogVolume.fade_distance = self.fog_fade_dist
|
||||
if not Engine.is_editor_hint():
|
||||
if should_draw_camera_underwater_effect():
|
||||
%WaterRippleOverlay.visible = true
|
||||
%FogVolume.material.set_shader_parameter("edge_fade", 0.1)
|
||||
last_frame_drew_underwater_effect = Engine.get_process_frames()
|
||||
else:
|
||||
%WaterRippleOverlay.visible = false
|
||||
%FogVolume.material.set_shader_parameter("edge_fade", 1.1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://c6jwvpteq2e1k
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
[gd_scene load_steps=14 format=3 uid="uid://ca0wevg1lb4rx"]
|
||||
|
||||
[ext_resource type="Script" path="res://utils/WaterMaker3D/WaterMaker3D.gd" id="1_7s6ww"]
|
||||
[ext_resource type="Shader" path="res://utils/WaterMaker3D/FogFade.gdshader" id="2_5f01f"]
|
||||
[ext_resource type="Shader" path="res://utils/WaterMaker3D/CameraWaterOverlay.gdshader" id="2_12cox"]
|
||||
[ext_resource type="Script" path="res://utils/WaterMaker3D/FogVolumeFadeScript.gd" id="3_b5ytn"]
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_1bimf"]
|
||||
resource_local_to_scene = true
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_mdx3j"]
|
||||
resource_local_to_scene = true
|
||||
seamless = true
|
||||
as_normal_map = true
|
||||
noise = SubResource("FastNoiseLite_1bimf")
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_xl6ub"]
|
||||
resource_local_to_scene = true
|
||||
seamless = true
|
||||
as_normal_map = true
|
||||
noise = SubResource("FastNoiseLite_1bimf")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gdho2"]
|
||||
resource_local_to_scene = true
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
depth_draw_mode = 1
|
||||
albedo_color = Color(0.309804, 0.541176, 0.866667, 0.388235)
|
||||
roughness = 0.0
|
||||
normal_enabled = true
|
||||
normal_texture = SubResource("NoiseTexture2D_mdx3j")
|
||||
refraction_enabled = true
|
||||
refraction_texture = SubResource("NoiseTexture2D_xl6ub")
|
||||
uv1_scale = Vector3(0.04, 0.04, 0.04)
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_om5f8"]
|
||||
resource_local_to_scene = true
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_cfplo"]
|
||||
size = Vector3(0.1, 0.1, 0.1)
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_wrt5a"]
|
||||
resource_local_to_scene = true
|
||||
shader = ExtResource("2_5f01f")
|
||||
shader_parameter/density = 1.0
|
||||
shader_parameter/albedo = Color(0, 0.0431373, 0.156863, 1)
|
||||
shader_parameter/emission = Color(0, 0.0431373, 0.156863, 1)
|
||||
shader_parameter/height_falloff = 0.0
|
||||
shader_parameter/edge_fade = 0.1
|
||||
shader_parameter/fade_plane = Vector4(0, 1, 0, -999)
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_ccyp1"]
|
||||
shader = ExtResource("2_12cox")
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_dcd7l"]
|
||||
|
||||
[node name="WaterMaker3D" type="CSGBox3D"]
|
||||
process_priority = 999
|
||||
material = SubResource("StandardMaterial3D_gdho2")
|
||||
script = ExtResource("1_7s6ww")
|
||||
|
||||
[node name="SwimmableArea3D" type="Area3D" parent="." groups=["water_area"]]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="SwimmableArea3D"]
|
||||
unique_name_in_owner = true
|
||||
shape = SubResource("BoxShape3D_om5f8")
|
||||
|
||||
[node name="CameraPosShapeCast3D" type="ShapeCast3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
shape = SubResource("BoxShape3D_cfplo")
|
||||
target_position = Vector3(0, 0, 0)
|
||||
collide_with_areas = true
|
||||
collide_with_bodies = false
|
||||
|
||||
[node name="FogVolume" type="FogVolume" parent="."]
|
||||
unique_name_in_owner = true
|
||||
size = Vector3(1, 1, 1)
|
||||
material = SubResource("ShaderMaterial_wrt5a")
|
||||
script = ExtResource("3_b5ytn")
|
||||
|
||||
[node name="WaterRippleOverlay" type="TextureRect" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = -10
|
||||
z_as_relative = false
|
||||
material = SubResource("ShaderMaterial_ccyp1")
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
texture = SubResource("PlaceholderTexture2D_dcd7l")
|
||||
Loading…
Reference in a new issue