2025-09-06 22:08:29 +02:00
|
|
|
@tool
|
2025-03-26 18:55:43 +01:00
|
|
|
extends StaticBody3D
|
|
|
|
|
class_name Tower
|
|
|
|
|
|
2025-08-29 12:11:51 +02:00
|
|
|
|
2025-09-09 23:54:59 +02:00
|
|
|
enum STATE { BLUEPRINT, REST, ACTION, EXHAUSTED, DISABLED }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
signal state_changed
|
2025-09-06 22:08:29 +02:00
|
|
|
signal energy_changed
|
|
|
|
|
signal changed
|
|
|
|
|
|
|
|
|
|
|
2025-08-31 21:26:02 +02:00
|
|
|
# DANGER "NONE" Should always be first
|
|
|
|
|
enum TYPE { NONE, PIERRE, ALINE, MAXENCE, VICTORIA, EVAN, ALEX, GERALDINE }
|
|
|
|
|
|
|
|
|
|
@export var tower_name : String = "None"
|
2025-09-04 17:59:23 +02:00
|
|
|
@export var type : TYPE
|
2025-09-02 19:49:40 +02:00
|
|
|
@export_group("Base data")
|
2025-08-25 23:42:09 +02:00
|
|
|
@export var icone : Texture2D
|
|
|
|
|
@export var bio : String
|
2025-09-09 23:54:59 +02:00
|
|
|
@export var price : int
|
2025-06-05 15:35:19 +02:00
|
|
|
|
2025-06-18 12:19:43 +02:00
|
|
|
@export_group("Attack")
|
2025-09-14 22:54:54 +02:00
|
|
|
@export var damage : int
|
|
|
|
|
@export var projectileScene : PackedScene
|
2025-09-02 19:49:40 +02:00
|
|
|
@export var towerRange : Shape3D
|
2025-09-09 23:54:59 +02:00
|
|
|
@export var action_cooldown : float = 0.3 :
|
2025-06-18 12:19:43 +02:00
|
|
|
set(value):
|
|
|
|
|
action_cooldown = clamp(value, 0.3, 999)
|
|
|
|
|
|
2025-06-05 15:35:19 +02:00
|
|
|
@export_group("Energy")
|
2025-09-09 23:54:59 +02:00
|
|
|
@export var max_energy : float :
|
2025-09-04 17:59:23 +02:00
|
|
|
set(value):
|
2025-09-09 23:54:59 +02:00
|
|
|
var diff : float = value - max_energy
|
2025-09-04 17:59:23 +02:00
|
|
|
max_energy = value
|
2025-09-06 22:08:29 +02:00
|
|
|
if not Engine.is_editor_hint() && is_node_ready():
|
2025-09-07 19:48:58 +02:00
|
|
|
energy += diff
|
2025-09-06 22:08:29 +02:00
|
|
|
energyBar.max_value = max_energy
|
2025-09-09 23:54:59 +02:00
|
|
|
@export var energy_regen : float
|
|
|
|
|
@export var energy_cost : float
|
2025-08-29 20:07:36 +02:00
|
|
|
|
|
|
|
|
@export_group("Button")
|
|
|
|
|
@export var buttonTooltip : String
|
|
|
|
|
|
|
|
|
|
|
2025-09-04 02:54:37 +02:00
|
|
|
@onready var energyBar : ProgressBar = $EnergyBar3D/SubViewport/EnergyBar2D
|
2025-08-27 13:27:32 +02:00
|
|
|
@onready var sprite : Sprite3D = $Sprite3D
|
2025-09-04 02:54:37 +02:00
|
|
|
@onready var energyRecoveryCooldown : Timer = $EnergyRecoveryCooldown
|
|
|
|
|
|
2025-06-05 15:35:19 +02:00
|
|
|
|
2025-09-09 23:54:59 +02:00
|
|
|
var state : STATE = STATE.BLUEPRINT :
|
|
|
|
|
set(value):
|
|
|
|
|
state = value
|
|
|
|
|
state_changed.emit()
|
2025-09-06 17:32:46 +02:00
|
|
|
var energy : float :
|
2025-06-05 15:35:19 +02:00
|
|
|
set(value):
|
2025-09-09 23:54:59 +02:00
|
|
|
energy = clampf(value, 0, max_energy)
|
|
|
|
|
energyBar.value = energy
|
2025-09-06 22:08:29 +02:00
|
|
|
energy_changed.emit()
|
2025-06-05 15:35:19 +02:00
|
|
|
|
2025-09-04 02:54:37 +02:00
|
|
|
var availableTargets : Array[Enemy]
|
2025-09-09 23:54:59 +02:00
|
|
|
var selectable : bool :
|
|
|
|
|
get():
|
|
|
|
|
return state != STATE.DISABLED && (state != STATE.BLUEPRINT || Game.money >= price)
|
2025-06-05 15:35:19 +02:00
|
|
|
|
2025-09-03 18:39:58 +02:00
|
|
|
|
2025-08-25 23:23:03 +02:00
|
|
|
@export_category("Upgrades")
|
2025-09-05 04:07:17 +02:00
|
|
|
@export var upgrades : Array[TowerUpgrade] :
|
|
|
|
|
set(value):
|
|
|
|
|
upgrades = EnhancedResource.arrayValueChanged(value, TowerUpgrade.new)
|
2025-08-25 23:23:03 +02:00
|
|
|
|
2025-06-18 12:19:43 +02:00
|
|
|
|
2025-09-02 19:49:40 +02:00
|
|
|
func _ready() -> void:
|
|
|
|
|
# WARNING : Prevent .tscn file to be modified by the load of the scene in editor
|
|
|
|
|
if not Engine.is_editor_hint():
|
2025-09-09 23:54:59 +02:00
|
|
|
energy = max_energy
|
2025-09-07 19:48:58 +02:00
|
|
|
energyRecoveryCooldown.timeout.connect(func(): energy += energy_regen + Game.energy_boost)
|
2025-09-04 02:54:37 +02:00
|
|
|
$PriceTag.text = str(price) + " €"
|
2025-09-02 19:49:40 +02:00
|
|
|
$Range/Range.shape = towerRange
|
|
|
|
|
|
|
|
|
|
|
2025-09-03 03:44:44 +02:00
|
|
|
func _process(_delta: float) -> void:
|
2025-09-09 23:54:59 +02:00
|
|
|
if state == STATE.ACTION && $AttackCooldown.is_stopped():
|
2025-09-04 02:54:37 +02:00
|
|
|
shoot()
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
2025-09-09 23:54:59 +02:00
|
|
|
func changeState(newState : STATE) -> void:
|
|
|
|
|
if [STATE.BLUEPRINT, STATE.DISABLED].has(state):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
match newState:
|
|
|
|
|
STATE.ACTION when state == STATE.REST: in_action()
|
|
|
|
|
STATE.REST: resting()
|
2025-09-14 01:31:18 +02:00
|
|
|
_: return # NOTE Prevent change of state
|
2025-09-09 23:54:59 +02:00
|
|
|
|
|
|
|
|
state = newState
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 18:55:43 +01:00
|
|
|
func shoot() -> void:
|
2025-09-14 01:31:18 +02:00
|
|
|
if energy < energy_cost:
|
|
|
|
|
state = STATE.EXHAUSTED
|
|
|
|
|
return
|
|
|
|
|
|
2025-09-07 23:45:43 +02:00
|
|
|
var target : Enemy = choose_target()
|
|
|
|
|
if not target:
|
|
|
|
|
return
|
|
|
|
|
|
2025-06-05 15:35:19 +02:00
|
|
|
energy -= energy_cost
|
2025-09-14 22:54:54 +02:00
|
|
|
var projectile : Projectile = projectileScene.instantiate()
|
|
|
|
|
projectile.amount = damage
|
2025-09-14 01:31:18 +02:00
|
|
|
projectile.shoot(target, $Aim.global_position)
|
2025-09-04 02:54:37 +02:00
|
|
|
$AttackCooldown.start(action_cooldown)
|
2025-03-26 18:55:43 +01:00
|
|
|
|
|
|
|
|
|
2025-06-05 15:35:19 +02:00
|
|
|
func resting() -> void:
|
2025-09-09 23:54:59 +02:00
|
|
|
toggleConnection(false)
|
2025-06-05 15:35:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func in_action() -> void:
|
2025-09-09 23:54:59 +02:00
|
|
|
toggleConnection(true)
|
2025-06-05 15:35:19 +02:00
|
|
|
|
|
|
|
|
|
2025-09-07 23:45:43 +02:00
|
|
|
func choose_target() -> Enemy:
|
|
|
|
|
var target : Enemy = null
|
2025-09-04 02:54:37 +02:00
|
|
|
for enemy in availableTargets:
|
2025-09-06 17:32:46 +02:00
|
|
|
if not target || enemy.path.progress > target.path.progress:
|
2025-09-04 02:54:37 +02:00
|
|
|
target = enemy
|
2025-08-26 13:03:22 +02:00
|
|
|
|
2025-09-07 23:45:43 +02:00
|
|
|
return target
|
|
|
|
|
|
2025-03-26 18:55:43 +01:00
|
|
|
|
2025-09-04 02:54:37 +02:00
|
|
|
func build() -> bool:
|
2025-09-09 23:54:59 +02:00
|
|
|
if state != STATE.BLUEPRINT || not Game.spendMoney(price):
|
2025-09-04 02:54:37 +02:00
|
|
|
return false
|
2025-06-05 15:35:19 +02:00
|
|
|
|
2025-09-02 19:49:40 +02:00
|
|
|
sprite.modulate = "ffffffff"
|
2025-09-04 17:59:23 +02:00
|
|
|
$EnergyBar3D.visible = true
|
2025-09-02 19:49:40 +02:00
|
|
|
$PriceTag.visible = false
|
2025-09-14 22:54:54 +02:00
|
|
|
energyBar.max_value = max_energy
|
2025-09-09 23:54:59 +02:00
|
|
|
state = STATE.ACTION
|
|
|
|
|
in_action()
|
2025-09-06 22:08:29 +02:00
|
|
|
changed.emit()
|
2025-09-04 02:54:37 +02:00
|
|
|
return true
|
2025-08-27 13:27:32 +02:00
|
|
|
|
|
|
|
|
|
2025-09-06 17:32:46 +02:00
|
|
|
func onBodyEntered(body: Node3D) -> void:
|
2025-03-26 18:55:43 +01:00
|
|
|
if body is Enemy:
|
2025-09-04 02:54:37 +02:00
|
|
|
availableTargets.append(body)
|
2025-03-26 18:55:43 +01:00
|
|
|
|
|
|
|
|
|
2025-09-06 17:32:46 +02:00
|
|
|
func onBodyExited(body: Node3D) -> void:
|
2025-03-26 18:55:43 +01:00
|
|
|
if body is Enemy:
|
2025-09-04 02:54:37 +02:00
|
|
|
availableTargets.erase(body)
|
2025-09-06 17:32:46 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func toggleConnection(activate : bool) -> void:
|
2025-09-14 12:52:01 +02:00
|
|
|
visible = activate
|
|
|
|
|
$Range.monitoring = activate
|
|
|
|
|
$Range.monitorable = activate
|
|
|
|
|
$CollisionShape3D.disabled = not activate
|
2025-09-14 11:38:05 +02:00
|
|
|
if activate:
|
2025-09-14 12:52:01 +02:00
|
|
|
energyRecoveryCooldown.stop()
|
2025-09-06 17:32:46 +02:00
|
|
|
else:
|
|
|
|
|
availableTargets.clear()
|
2025-09-14 12:52:01 +02:00
|
|
|
energyRecoveryCooldown.start()
|
2025-09-09 23:54:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func disable(duration : float) -> void:
|
|
|
|
|
state = STATE.DISABLED
|
|
|
|
|
resting()
|
|
|
|
|
await get_tree().create_timer(duration).timeout
|
|
|
|
|
state = STATE.REST
|