@tool extends StaticBody3D class_name Tower enum STATE { BLUEPRINT, REST, ACTION, EXHAUSTED, DISABLED } signal state_changed 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 @export_group("Attack") @export var projectileRessource : ProjectileResource @export var towerRange : Shape3D @export var action_cooldown : float = 0.3 : set(value): action_cooldown = clamp(value, 0.3, 999) @export_group("Energy") @export var max_energy : float : set(value): var diff : float = value - max_energy max_energy = value if not Engine.is_editor_hint() && is_node_ready(): energy += diff energyBar.max_value = max_energy @export var energy_regen : float @export var energy_cost : float @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 state : STATE = STATE.BLUEPRINT : set(value): state = value state_changed.emit() var energy : float : set(value): energy = clampf(value, 0, max_energy) energyBar.value = energy energy_changed.emit() if not energy && state != STATE.BLUEPRINT: state = STATE.EXHAUSTED var availableTargets : Array[Enemy] var selectable : bool : get(): return state != STATE.DISABLED && (state != STATE.BLUEPRINT || Game.money >= price) @export_category("Upgrades") @export var upgrades : Array[TowerUpgrade] : set(value): upgrades = EnhancedResource.arrayValueChanged(value, TowerUpgrade.new) func _ready() -> void: # WARNING : Prevent .tscn file to be modified by the load of the scene in editor if not Engine.is_editor_hint(): energy = max_energy 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 state == STATE.ACTION && $AttackCooldown.is_stopped(): shoot() func changeState(newState : STATE) -> void: if [STATE.BLUEPRINT, STATE.DISABLED].has(state): return match newState: STATE.ACTION when state == STATE.REST: in_action() STATE.ACTION when not energy: newState = STATE.EXHAUSTED STATE.ACTION: pass STATE.REST: resting() _: return # NOTE Prevent changing of state state = newState 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 toggleConnection(false) energyRecoveryCooldown.start() func in_action() -> void: visible = true collision_layer = 0b100 collision_mask = 0b100 toggleConnection(true) 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 state != STATE.BLUEPRINT || not Game.spendMoney(price): return false sprite.modulate = "ffffffff" $EnergyBar3D.visible = true $PriceTag.visible = false state = STATE.ACTION in_action() 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() func disable(duration : float) -> void: state = STATE.DISABLED resting() await get_tree().create_timer(duration).timeout state = STATE.REST