2025-09-02 19:49:40 +02:00
|
|
|
extends CharacterBody3D
|
|
|
|
|
class_name Projectile
|
|
|
|
|
|
|
|
|
|
const TARGET_ENEMY : int = 1 ## Flag to target enemy
|
|
|
|
|
const TARGET_ALLY : int = 2 ## Flag to target ally
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum TYPE { ## Types of projectiles
|
2025-09-03 00:03:15 +02:00
|
|
|
|
|
|
|
|
## One target
|
|
|
|
|
BASIC,
|
|
|
|
|
|
|
|
|
|
## Multiple targets[br]work with [member ProjectileResource.damageArea]
|
|
|
|
|
AOE,
|
|
|
|
|
|
|
|
|
|
## Piercing through enemies[br]work with [member ProjectileResource.maxTarets] and [member ProjectileResource.damageArea]
|
|
|
|
|
PIERCING,
|
|
|
|
|
|
|
|
|
|
## Bouncing over enemies[br]work with [member ProjectileResource.maxTarets] and [member ProjectileResource.damageArea]
|
|
|
|
|
BOUNCING,
|
2025-09-02 19:49:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var type : TYPE = TYPE.BASIC
|
|
|
|
|
var speed : int = 20
|
|
|
|
|
var allowedTargets : int = TARGET_ENEMY
|
|
|
|
|
var target : PhysicsBody3D
|
|
|
|
|
var vectorTarget : Vector3
|
|
|
|
|
var maxTargets : int = 1
|
|
|
|
|
var damage : int = 1
|
|
|
|
|
var enemiesInRange : Array[Enemy]
|
|
|
|
|
var affectedTarget : Array[Enemy]
|
|
|
|
|
var allyInRange : Array[Tower]
|
|
|
|
|
|
2025-09-03 00:03:15 +02:00
|
|
|
|
2025-09-03 03:44:44 +02:00
|
|
|
func _physics_process(_delta: float) -> void:
|
2025-09-03 00:03:15 +02:00
|
|
|
if !is_instance_valid(target) && type != TYPE.PIERCING || vectorTarget.distance_squared_to(global_position) < .4:
|
2025-09-02 19:49:40 +02:00
|
|
|
queue_free()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var globalPos : Vector3 = vectorTarget if vectorTarget else target.global_position
|
|
|
|
|
velocity = global_position.direction_to(globalPos) * speed
|
|
|
|
|
look_at(globalPos)
|
|
|
|
|
move_and_slide()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func onBodyEnteredDamageArea(body: Node3D) -> void:
|
2025-09-03 00:03:15 +02:00
|
|
|
if type != TYPE.BASIC && targetable(body):
|
|
|
|
|
if type == TYPE.PIERCING:
|
|
|
|
|
resolveDamages(body)
|
|
|
|
|
else:
|
|
|
|
|
addTarget(body)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func onBodyCollideWithProjectile(body: Node3D) -> void:
|
2025-09-03 00:03:15 +02:00
|
|
|
if (body == target || type == TYPE.PIERCING && targetable(body)):
|
|
|
|
|
resolveDamages(body)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func targetable(body: Node3D) -> bool:
|
|
|
|
|
if body is Enemy:
|
2025-09-03 03:44:44 +02:00
|
|
|
return TARGET_ENEMY & allowedTargets && not affectedTarget.has(body)
|
2025-09-02 19:49:40 +02:00
|
|
|
if body is Tower:
|
|
|
|
|
return TARGET_ALLY & allowedTargets
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
2025-09-03 00:03:15 +02:00
|
|
|
func resolveDamages(body: Node3D) -> void:
|
|
|
|
|
damageEnemy(body)
|
|
|
|
|
|
|
|
|
|
if type == TYPE.AOE:
|
|
|
|
|
for enemy in enemiesInRange:
|
|
|
|
|
if is_instance_valid(enemy):
|
|
|
|
|
damageEnemy(enemy)
|
|
|
|
|
|
|
|
|
|
if maxTargets < 1 || type == TYPE.AOE:
|
|
|
|
|
return queue_free()
|
|
|
|
|
|
|
|
|
|
if type == TYPE.BOUNCING:
|
|
|
|
|
enemiesInRange.erase(body)
|
|
|
|
|
if enemiesInRange.size():
|
|
|
|
|
target = enemiesInRange.pop_front()
|
|
|
|
|
else:
|
|
|
|
|
queue_free()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func damageEnemy(enemy: Enemy) -> void:
|
|
|
|
|
if not affectedTarget.has(enemy):
|
|
|
|
|
maxTargets -= 1
|
|
|
|
|
enemy.take_damage(damage)
|
|
|
|
|
affectedTarget.append(enemy)
|
2025-09-02 19:49:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-09-03 03:44:44 +02:00
|
|
|
func loadProjectile(resource: ProjectileResource, _target: PhysicsBody3D) -> void:
|
2025-09-02 19:49:40 +02:00
|
|
|
target = _target
|
|
|
|
|
type = resource.type
|
|
|
|
|
if type == TYPE.PIERCING:
|
|
|
|
|
vectorTarget = target.global_position
|
|
|
|
|
# NOTE removing colision layer for pierce effect
|
|
|
|
|
$HitBox.collision_layer = 0
|
|
|
|
|
speed = resource.speed
|
|
|
|
|
maxTargets = resource.maxTargets
|
|
|
|
|
damage = resource.damage
|
|
|
|
|
allowedTargets = resource.allowedTargets
|
|
|
|
|
$Sprite3D.texture = resource.sprite
|
2025-09-03 00:03:15 +02:00
|
|
|
if type != TYPE.BASIC && resource.damageArea:
|
2025-09-02 19:49:40 +02:00
|
|
|
$DamageArea/ProjectileArea.shape = resource.damageArea
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func addTarget(body: Node3D) -> void:
|
2025-09-03 00:03:15 +02:00
|
|
|
if body is Enemy && not enemiesInRange.has(body):
|
2025-09-02 19:49:40 +02:00
|
|
|
enemiesInRange.append(body)
|
2025-09-03 00:03:15 +02:00
|
|
|
if body is Tower && not allyInRange.has(body):
|
2025-09-02 19:49:40 +02:00
|
|
|
allyInRange.append(body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func removeTarget(body: Node3D) -> void:
|
|
|
|
|
if body is Enemy:
|
|
|
|
|
enemiesInRange.erase(body)
|
|
|
|
|
if body is Tower:
|
|
|
|
|
allyInRange.erase(body)
|