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 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)