class_name Combatant extends Node2D signal healthChanged(health: float) signal stunned @onready var renderer: AnimatedSprite2D = $AnimatedSprite2D @onready var healthbar: HealthBar = $HealthBar @onready var data: Data = get_node("/root/Root/Data") @export var spellbook: Spellbook @export var maxHealth: float = 10 @export var health: float = maxHealth @export var player: bool = false var spell: Spell var spellIndex: int var anim: AnimationBase var casting: bool = false var castCooldown: float = 0 var castProgress: int = 0 var defending: Array[Spell] var availableSpells: Array[int] # Called when the node enters the scene tree for the first time. func _ready(): renderer.animation_finished.connect(animationFinished) renderer.play("idle") healthbar.maxHealth = maxHealth if !player: spellbook.initCooldowns() for spel: Spell in spellbook.spells: if (spel == null): continue if !data.animations.has(spel.animation): data.animations[spel.animation] = load(spel.animation) data.opponent = self renderer.flip_h = true healthbar.position.x *= -1 _finishedCasting() else: data.player = self # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): if !player: if (!casting): timing(delta) for i in range(spellbook.cooldowns.size()): spellbook.cooldowns[i] -= delta if spellbook.cooldowns[i] < 0: spellbook.cooldowns[i] = 0 func _finishedCasting() -> void: casting = false match data.difficulty: Data.Difficulty.EASY: castCooldown = randf_range(2.7, 3.3) Data.Difficulty.NORMAL: castCooldown = randf_range(0.8, 1.3) Data.Difficulty.HARD: castCooldown = randf_range(0.3, 0.7) Data.Difficulty.GAMER: castCooldown = 0 func timing(delta) -> void: castCooldown -= delta if castCooldown <= 0: cast() func cast() -> void: var spellOptions: Array[Spell] = spellbook.spells.filter(func(spell): return spellbook.cooldowns[spellbook.spells.find(spell)] == 0) if (spellOptions.size() == 0): return spell = spellOptions[randi_range(0, spellOptions.size() - 1)] spellIndex = spellbook.spells.find(spell) anim = data.animations[spell.animation].instantiate() anim.setProgress(0, spell.castCombo.size()) anim.inverted = true casting = true anim.spell = spell anim.connect("animationFinished", finalizeSpell) get_node("/root").add_child(anim) attemptCast() func attemptCast(): match data.difficulty: Data.Difficulty.EASY: await get_tree().create_timer(randf_range(0.75, 1.75)).timeout if (spell == null): return aiCast(90) Data.Difficulty.NORMAL: await get_tree().create_timer(randf_range(0.6, 1.2)).timeout if (spell == null): return aiCast(95) Data.Difficulty.HARD: await get_tree().create_timer(randf_range(0.3, 0.75)).timeout if (spell == null): return aiCast(98) Data.Difficulty.GAMER: await get_tree().create_timer(.2).timeout if (spell == null): return aiCast(100) func aiCast(chance: int): if randi_range(0, 99) < chance: castProgress += 1 if (is_instance_valid(anim)): anim.setProgress(castProgress) if (castProgress == spell.castCombo.size()): casting = false castProgress = 0 renderer.play("attack1") spellbook.cooldowns[spellIndex] = spell.cooldown defending.append(spell) _finishedCasting() return else: attemptCast() else: alterHealth(-spell.backfireStrength, true, spell.element) failCast() func alterHealth(change: float, stun: bool, element: Data.Element) -> void: for spel in defending: change *= spel.blockStrength[element] if (change == 0): return health += change if stun: casting = false renderer.play("hit") if player: stunned.emit() else: failCast() if (health <= 0): health = 0 healthChanged.emit(health) func failCast() -> void: casting = false if (is_instance_valid(anim)): anim.castFailed() castProgress = 0 if (spell != null): spellbook.cooldowns[spellIndex] = spell.cooldown * (float(castProgress) / float(spell.castCombo.size())) spell = null _finishedCasting() return func animationFinished() -> void: renderer.play("idle") func finalizeSpell(finish: Spell) -> void: defending.erase(finish) if (player): data.opponent.alterHealth(-finish.damage, finish.stunning, finish.element) else: data.player.alterHealth(-finish.damage, finish.stunning, finish.element)