extends CharacterBody3D class_name Projectile enum Mode { FOLLOW, ## Follow target LOCATION, ## Go to target location SPAWN_ON_TARGET, ## Spawn on target location HITSCAN, ## Spawn on target location } enum Duration { ONE_HIT, ## Make damage on hit DAMAGE_OVER_TIME, ## Make damage every tick for specified duration[br]work with [member maxTargets] for number of tick } enum Type { ## Types of projectiles BASIC, ## One defined target AOE, ## Multiple targets BOUNCING, ## Bouncing over enemies[br]work with [member maxTargets] DISABLING, ## Disable ally tower for [member amount] duration [br]Usable on [Boss] projectiles } @export var mode : Mode = Mode.FOLLOW @export var duration : Duration = Duration.ONE_HIT @export var type : Type = Type.BASIC @export var speed : int ## Usefull when [enum Type] is not [constant BASIC][br] ## [code]-1[/code] for no maximum ## Used as time when [enum Duration] is [constant HITSCAN] @export var maxTargets : int = 1 var amount : float var target : PhysicsBody3D var vectorTarget : Vector3 var bodiesInRange : Array[Node3D] var collidingBodies : Array[Node3D] var affectedTarget : Array[Node3D] func _ready() -> void: $HitBox.body_entered.connect(collidingBodies.append) #$HitBox.body_entered.connect(test) $HitBox.body_exited.connect(collidingBodies.erase) $EffectArea.body_entered.connect(bodiesInRange.append) $EffectArea.body_exited.connect(bodiesInRange.erase) func test(body): print(body) var firstTick : bool = true func _physics_process(_delta: float) -> void: if firstTick: firstTick = false return resolveContact() if shouldQueueFree(): queue_free.call_deferred() return if mode == Mode.FOLLOW: var globalPos : Vector3 = target.global_position globalPos.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER) look_at(globalPos) velocity = global_position.direction_to(globalPos) * speed move_and_slide() func shouldQueueFree() -> bool: if maxTargets == 0 || !is_instance_valid(target) && mode == Mode.FOLLOW: return true elif mode != Mode.FOLLOW: return isOnTarget() elif target is Tower: return not target.visible return false func isOnTarget() -> bool: match mode: Mode.LOCATION, Mode.SPAWN_ON_TARGET: return vectorTarget.distance_squared_to(global_position) < .4 Mode.FOLLOW: return collidingBodies.has(target) Mode.HITSCAN: return true _: return false func resolveContact() -> void: if collidingBodies.is_empty() || not isOnTarget(): return match type: Type.AOE: collidingBodies.map(resolveEffect) _ when collidingBodies.has(target): resolveEffect(target) if type == Type.BOUNCING: target = null if bodiesInRange.is_empty() else bodiesInRange[0] func resolveEffect(body : Node3D) -> void: if affectedTarget.has(body) || body is GameTile || maxTargets == 0: return if type == Type.DISABLING && body.has_method("disable"): body.disable(amount) elif body.has_method("take_damage"): body.take_damage(amount) affectedTarget.append(body) maxTargets -= 1 func shoot(_target: Node3D, globalPos: Vector3) -> void: target = _target var transform3D : Transform3D = Transform3D() transform3D.origin = globalPos var targetPosition : Vector3 = target.global_position targetPosition.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER) transform3D = transform3D.looking_at(targetPosition) match mode: Mode.SPAWN_ON_TARGET: transform3D.origin = targetPosition transform3D = transform3D.looking_at(globalPos) vectorTarget = targetPosition Mode.LOCATION: vectorTarget = targetPosition velocity = transform3D.origin.direction_to(vectorTarget) * speed print(transform3D) EventBus.projectile_shooted.emit(self, transform3D)