@tool extends StaticBody3D class_name Tower signal energy_changed signal changed # DANGER "NONE" Should always be first enum TYPE { NONE, PIERRE, ALINE, MAXENCE, VICTORIA, EVAN, ALEX, GERALDINE } var projectileScene : PackedScene = preload("res://Towers/Projectiles/projectile.tscn") @export var tower_name : String = "None" @export var type : TYPE @export_group("Base data") @export var icone : Texture2D @export var bio : String @export var price : int = 100 @export_group("Attack") @export var projectileRessource : ProjectileResource @export var towerRange : Shape3D @export var action_cooldown : float = 1.5: set(value): action_cooldown = clamp(value, 0.3, 999) @export_group("Energy") @export var max_energy : float = 100.0 : set(value): var diff : int = value - max_energy max_energy = value if not Engine.is_editor_hint() && is_node_ready(): energy += diff energyBar.max_value = max_energy changed.emit() @export var energy_regen : float = 10.0 @export var energy_cost : float = 50.0 @export_group("Button") @export var buttonTooltip : String @onready var energyBar : ProgressBar = $EnergyBar3D/SubViewport/EnergyBar2D @onready var sprite : Sprite3D = $Sprite3D @onready var energyRecoveryCooldown : Timer = $EnergyRecoveryCooldown var energy : float : set(value): energyBar.value = value energy = clampf(value, 0.0, max_energy) is_exhausted = energy < energy_cost energy_changed.emit() var availableTargets : Array[Enemy] var is_exhausted : bool = false var is_rest : bool : get(): return not energyRecoveryCooldown.is_stopped() var builded : bool = false @export_category("Upgrades") @export var upgrades : Array[TowerUpgrade] : set(value): upgrades = EnhancedResource.arrayValueChanged(value, TowerUpgrade.new) func _ready() -> void: energy = max_energy # WARNING : Prevent .tscn file to be modified by the load of the scene in editor if not Engine.is_editor_hint(): energyRecoveryCooldown.timeout.connect(func(): energy += energy_regen + Game.energy_boost) collision_layer = 0 collision_mask = 0 $PriceTag.text = str(price) + " €" $Range/Range.shape = towerRange func _process(_delta: float) -> void: if visible && $AttackCooldown.is_stopped() && builded: shoot() func shoot() -> void: var target : Enemy = choose_target() if not target: return energy -= energy_cost var projectile : Projectile = projectileScene.instantiate() projectile.loadProjectile(projectileRessource, target) EventBus.projectile_shooted.emit(projectile, $Aim.global_position) $AttackCooldown.start(action_cooldown) func resting() -> void: visible = false collision_layer = 0 collision_mask = 0 if builded: toggleConnection(false) energyRecoveryCooldown.start() func in_action() -> void: visible = true if builded: toggleConnection(true) collision_layer = 0b100 collision_mask = 0b100 energyRecoveryCooldown.stop() func choose_target() -> Enemy: var target : Enemy = null for enemy in availableTargets: if not target || enemy.path.progress > target.path.progress: target = enemy return target func build() -> bool: if builded || not Game.spendMoney(price): return false sprite.modulate = "ffffffff" $EnergyBar3D.visible = true builded = true $PriceTag.visible = false changed.emit() return true func onBodyEntered(body: Node3D) -> void: if body is Enemy: availableTargets.append(body) func onBodyExited(body: Node3D) -> void: if body is Enemy: availableTargets.erase(body) func toggleConnection(activate : bool) -> void: if activate && not $Range.body_entered.is_connected(onBodyEntered): $Range.body_entered.connect(onBodyEntered) $Range.body_exited.connect(onBodyExited) else: if $Range.body_entered.is_connected(onBodyEntered): $Range.body_entered.disconnect(onBodyEntered) $Range.body_exited.disconnect(onBodyExited) availableTargets.clear()