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): if type == TYPE.PIERCING: resolveDamages(body) else: addTarget(body) func onBodyCollideWithProjectile(body: Node3D) -> void: if (body == target || type == TYPE.PIERCING && targetable(body)): resolveDamages(body) func targetable(body: Node3D) -> bool: if affectedTarget.has(body): return false 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)