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

137 lines
3.5 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,
## Disable ally tower for [param damage] duration [br]Usable on [Boss] projectiles
DISABLING,
}
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 bodiesInRange : Array[Enemy]
var affectedTarget : Array[Enemy]
func _physics_process(_delta: float) -> void:
if shouldQueueFree():
queue_free()
return
var globalPos : Vector3 = vectorTarget if vectorTarget else target.global_position
if target is Tower:
globalPos.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
velocity = global_position.direction_to(globalPos) * speed
look_at(globalPos)
move_and_slide()
func shouldQueueFree() -> bool:
match type:
TYPE.PIERCING: return vectorTarget.distance_squared_to(global_position) < .4
_ when allowedTargets & TARGET_ALLY && target is Tower: return not target.visible
_ when !is_instance_valid(target): return true
return false
func onBodyEnteredDamageArea(body: Node3D) -> void:
if type != TYPE.BASIC && targetable(body):
if type == TYPE.PIERCING:
resolveContact(body)
elif not bodiesInRange.has(body):
bodiesInRange.append(body)
func onBodyCollideWithProjectile(body: Node3D) -> void:
if (body == target || type == TYPE.PIERCING && targetable(body)):
resolveContact(body)
func targetable(body: Node3D) -> bool:
if body is Enemy:
return TARGET_ENEMY & allowedTargets && not affectedTarget.has(body)
if body is Tower || body is TheCube:
return TARGET_ALLY & allowedTargets
return false
func resolveContact(body: Node3D) -> void:
if body is Enemy:
resolveEnemyDamages(body)
if body is Tower || body is TheCube:
resolveAllyEffects(body)
func resolveEnemyDamages(enemy: Enemy) -> void:
damageEnemy(enemy)
if type == TYPE.AOE:
for body in bodiesInRange:
if is_instance_valid(body):
damageEnemy(body)
if maxTargets < 1 || type == TYPE.AOE:
return queue_free()
if type == TYPE.BOUNCING:
bodiesInRange.erase(enemy)
if bodiesInRange.size():
target = bodiesInRange.pop_front()
else:
queue_free()
func resolveAllyEffects(ally: Node3D) -> void:
if ally is Tower && type == TYPE.DISABLING:
ally.disable(damage)
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, _target: PhysicsBody3D) -> void:
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
if [TYPE.AOE, TYPE.PIERCING, TYPE.BOUNCING].has(type) && resource.damageArea:
$DamageArea/ProjectileArea.shape = resource.damageArea
func removeTarget(body: Node3D) -> void:
bodiesInRange.erase(body)