2025-09-02 19:49:40 +02:00
|
|
|
extends CharacterBody3D
|
|
|
|
|
class_name Projectile
|
|
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
enum MODE {
|
|
|
|
|
FOLLOW, ## Follow Entity
|
|
|
|
|
LOCATION, ## Go to entity location
|
|
|
|
|
HITSCAN, ## DANGER NOT implemented yet
|
|
|
|
|
}
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
enum TYPE { ## Types of projectiles
|
2025-09-14 22:54:54 +02:00
|
|
|
BASIC, ## One target
|
|
|
|
|
AOE, ## Multiple targets[br]work with [member damageArea]
|
|
|
|
|
PIERCING, ## Piercing through enemies[br]work with [member maxTarets] and [member damageArea]
|
|
|
|
|
BOUNCING, ## Bouncing over enemies[br]work with [member maxTarets] and [member damageArea]
|
|
|
|
|
DISABLING, ## Disable ally tower for [member damage] duration [br]Usable on [Boss] projectiles
|
|
|
|
|
}
|
2025-09-03 00:03:15 +02:00
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
@export var type : TYPE = TYPE.BASIC
|
|
|
|
|
@export var mode : MODE = MODE.FOLLOW
|
|
|
|
|
@export var speed : int
|
|
|
|
|
@export var maxTargets : int = 1
|
2025-09-14 01:31:18 +02:00
|
|
|
|
2025-09-02 19:49:40 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
var amount : float
|
2025-09-02 19:49:40 +02:00
|
|
|
var target : PhysicsBody3D
|
|
|
|
|
var vectorTarget : Vector3
|
2025-09-14 22:54:54 +02:00
|
|
|
var bodiesInRange : Array[Node3D]
|
2025-09-15 19:45:59 +02:00
|
|
|
var collidingBodies : Array[Node3D]
|
2025-09-14 22:54:54 +02:00
|
|
|
var affectedTarget : Array[Node3D]
|
2025-09-02 19:49:40 +02:00
|
|
|
|
2025-09-03 00:03:15 +02:00
|
|
|
|
2025-09-03 03:44:44 +02:00
|
|
|
func _physics_process(_delta: float) -> void:
|
2025-09-14 22:54:54 +02:00
|
|
|
if mode == MODE.LOCATION && vectorTarget.distance_squared_to(global_position) < .4:
|
|
|
|
|
resolveContact()
|
|
|
|
|
maxTargets = 0 # Ensure queue free in next if
|
|
|
|
|
|
2025-09-14 01:31:18 +02:00
|
|
|
if shouldQueueFree():
|
2025-09-15 19:45:59 +02:00
|
|
|
return queue_free()
|
|
|
|
|
|
|
|
|
|
if not collidingBodies.is_empty() && collidingBodies.has(target):
|
|
|
|
|
return onBodyCollideWithProjectile(target)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
var globalPos : Vector3 = vectorTarget if vectorTarget else target.global_position
|
2025-09-14 22:54:54 +02:00
|
|
|
if target:
|
2025-09-14 01:31:18 +02:00
|
|
|
globalPos.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
|
2025-09-02 19:49:40 +02:00
|
|
|
look_at(globalPos)
|
2025-09-14 22:54:54 +02:00
|
|
|
velocity = global_position.direction_to(globalPos) * speed
|
2025-09-02 19:49:40 +02:00
|
|
|
move_and_slide()
|
|
|
|
|
|
|
|
|
|
|
2025-09-14 01:31:18 +02:00
|
|
|
func shouldQueueFree() -> bool:
|
2025-09-14 22:54:54 +02:00
|
|
|
if maxTargets < 1:
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
if !is_instance_valid(target):
|
|
|
|
|
return mode == MODE.FOLLOW
|
|
|
|
|
elif target is Tower:
|
|
|
|
|
return not target.visible
|
2025-09-14 01:31:18 +02:00
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
2025-09-02 19:49:40 +02:00
|
|
|
func onBodyEnteredDamageArea(body: Node3D) -> void:
|
2025-09-03 00:03:15 +02:00
|
|
|
if type != TYPE.BASIC && targetable(body):
|
2025-09-14 22:54:54 +02:00
|
|
|
addBodyInRange(body)
|
2025-09-03 00:03:15 +02:00
|
|
|
if type == TYPE.PIERCING:
|
2025-09-14 22:54:54 +02:00
|
|
|
resolveContact()
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func onBodyCollideWithProjectile(body: Node3D) -> void:
|
2025-09-15 19:45:59 +02:00
|
|
|
if not collidingBodies.has(body):
|
|
|
|
|
collidingBodies.push_back(body)
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
if mode != MODE.LOCATION && (body == target || type == TYPE.PIERCING) && targetable(body):
|
|
|
|
|
addBodyInRange(body, true)
|
|
|
|
|
resolveContact()
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
func addBodyInRange(body: Node3D, pushFront: bool = false) -> void:
|
|
|
|
|
var idx : int = bodiesInRange.find(body)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
2025-09-15 19:45:59 +02:00
|
|
|
if idx == -1:
|
|
|
|
|
if pushFront:
|
|
|
|
|
bodiesInRange.push_front(body)
|
|
|
|
|
else:
|
|
|
|
|
bodiesInRange.push_back(body)
|
|
|
|
|
elif pushFront && idx != 0:
|
|
|
|
|
bodiesInRange.remove_at(idx)
|
2025-09-14 22:54:54 +02:00
|
|
|
bodiesInRange.push_front(body)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
func targetable(body: Node3D) -> bool:
|
|
|
|
|
return not affectedTarget.has(body)
|
2025-09-14 01:31:18 +02:00
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
func resolveContact() -> void:
|
|
|
|
|
if bodiesInRange.is_empty():
|
|
|
|
|
return
|
2025-09-03 00:03:15 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
resolveEffect(bodiesInRange[0])
|
2025-09-03 00:03:15 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
match type:
|
|
|
|
|
TYPE.AOE:
|
2025-09-15 19:45:59 +02:00
|
|
|
for bodyInRange in bodiesInRange:
|
|
|
|
|
if is_instance_valid(bodyInRange):
|
|
|
|
|
resolveEffect(bodyInRange, false)
|
2025-09-03 00:03:15 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
TYPE.BOUNCING:
|
|
|
|
|
target = null if bodiesInRange.is_empty() else bodiesInRange[0]
|
2025-09-03 00:03:15 +02:00
|
|
|
|
|
|
|
|
|
2025-09-15 19:45:59 +02:00
|
|
|
func resolveEffect(body : Node3D, erase : bool = true) -> void:
|
|
|
|
|
if erase:
|
|
|
|
|
bodiesInRange.erase(body)
|
2025-09-14 22:54:54 +02:00
|
|
|
if affectedTarget.has(body) || body is GameTile:
|
|
|
|
|
return
|
2025-09-14 01:31:18 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
if type == TYPE.DISABLING && body.has_method("disable"):
|
|
|
|
|
body.disable(amount)
|
|
|
|
|
elif body.has_method("take_damage"):
|
|
|
|
|
body.take_damage(amount)
|
2025-09-14 01:31:18 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
affectedTarget.append(body)
|
|
|
|
|
maxTargets -= 1
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
func removeTarget(body: Node3D) -> void:
|
|
|
|
|
bodiesInRange.erase(body)
|
|
|
|
|
|
|
|
|
|
|
2025-09-15 19:45:59 +02:00
|
|
|
func removeCollidingBody(body: Node3D) -> void:
|
|
|
|
|
collidingBodies.erase(body)
|
|
|
|
|
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
func shoot(_target: Node3D, globalPos: Vector3) -> void:
|
2025-09-02 19:49:40 +02:00
|
|
|
target = _target
|
|
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
match mode:
|
|
|
|
|
Projectile.MODE.HITSCAN:
|
|
|
|
|
globalPos = target.global_position
|
|
|
|
|
globalPos.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
|
|
|
|
|
Projectile.MODE.LOCATION:
|
|
|
|
|
vectorTarget = target.global_position
|
|
|
|
|
vectorTarget.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
2025-09-14 22:54:54 +02:00
|
|
|
EventBus.projectile_shooted.emit(self, globalPos)
|