TowerDefense/Towers/Projectiles/Projectile.gd
2025-09-03 00:03:15 +02:00

125 lines
3.2 KiB
GDScript

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
## 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,
}
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]
func _physics_process(delta: float) -> void:
if !is_instance_valid(target) && type != TYPE.PIERCING || vectorTarget.distance_squared_to(global_position) < .4:
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:
if type != TYPE.BASIC && targetable(body) && not affectedTarget.has(body):
if type == TYPE.PIERCING:
resolveDamages(body)
else:
addTarget(body)
func onBodyCollideWithProjectile(body: Node3D) -> void:
if (body == target || type == TYPE.PIERCING && targetable(body)) && not affectedTarget.has(body):
resolveDamages(body)
func targetable(body: Node3D) -> bool:
if body is Enemy:
return TARGET_ENEMY & allowedTargets
if body is Tower:
return TARGET_ALLY & allowedTargets
return false
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()
vectorTarget = target.global_position
else:
queue_free()
func damageEnemy(enemy: Enemy) -> void:
if not affectedTarget.has(enemy):
maxTargets -= 1
enemy.take_damage(damage)
affectedTarget.append(enemy)
func loadProjectile(resource: ProjectileResource, startPosition: Vector3, _target: PhysicsBody3D) -> void:
target = _target
global_position = startPosition
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
if type != TYPE.BASIC && resource.damageArea:
$DamageArea/ProjectileArea.shape = resource.damageArea
func addTarget(body: Node3D) -> void:
if body is Enemy && not enemiesInRange.has(body):
enemiesInRange.append(body)
if body is Tower && not allyInRange.has(body):
allyInRange.append(body)
func removeTarget(body: Node3D) -> void:
if body is Enemy:
enemiesInRange.erase(body)
if body is Tower:
allyInRange.erase(body)