180 lines
6.2 KiB
GDScript
180 lines
6.2 KiB
GDScript
extends CharacterBody2D
|
|
|
|
var speed = 300
|
|
var direction = Vector2.ZERO
|
|
var stick_duration = 3.0 # How long the arrow stays stuck to walls
|
|
var is_stuck = false
|
|
var stick_timer = 0.0
|
|
|
|
var initiated_by: Node2D = null
|
|
var player_owner: Node = null # Like sword_projectile
|
|
var hit_targets = {} # Track what we've already hit (Dictionary for O(1) lookup)
|
|
|
|
@onready var arrow_area = $ArrowArea # Assuming you have an Area2D node named ArrowArea
|
|
@onready var shadow = $Shadow # Assuming you have a Shadow node under the CharacterBody2D
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready() -> void:
|
|
arrow_area.set_deferred("monitoring", true)
|
|
# Connect area signals
|
|
if arrow_area:
|
|
arrow_area.body_entered.connect(_on_arrow_area_body_entered)
|
|
$SfxArrowFire.play()
|
|
call_deferred("_initialize_arrow")
|
|
|
|
func _initialize_arrow() -> void:
|
|
var angle = direction.angle()
|
|
self.rotation = angle - PI / 2 # Adjust for sprite orientation
|
|
# Set initial rotation based on direction
|
|
velocity = direction * speed # Set initial velocity to move the arrow
|
|
|
|
# Calculate the offset for the shadow position, which should be below the arrow
|
|
var shadow_offset = Vector2(0, 4) # Adjust the 16 to how far you want the shadow from the arrow (this is just an example)
|
|
|
|
# Apply the rotation of the arrow to the shadow offset
|
|
shadow.position += shadow_offset.rotated(-self.rotation)
|
|
if abs(direction.x) == 1:
|
|
shadow.scale.x = 0.26
|
|
shadow.scale.y = 0.062
|
|
|
|
elif abs(direction.x) > 0:
|
|
shadow.scale.x = 0.18
|
|
shadow.scale.y = 0.08
|
|
else:
|
|
shadow.scale.x = 0.1
|
|
shadow.scale.y = 0.1
|
|
|
|
# Calculate the shadow's scale based on the velocity or direction of the arrow
|
|
#var velocity_magnitude = velocity.length()
|
|
|
|
# Scale more in the horizontal direction if moving diagonally or horizontally
|
|
#var scale_factor = 0.28 + abs(velocity.x) / velocity_magnitude # Adjust the factor to your preference
|
|
|
|
# Apply the scaling to the shadow
|
|
shadow.rotation = -(angle - PI / 2)
|
|
|
|
func shoot(shoot_direction: Vector2, start_pos: Vector2, owner_player: Node = null) -> void:
|
|
direction = shoot_direction.normalized()
|
|
global_position = start_pos
|
|
player_owner = owner_player
|
|
initiated_by = owner_player
|
|
|
|
# Called every frame. 'delta' is the e lapsed time since the previous frame.
|
|
func _process(delta: float) -> void:
|
|
if is_stuck:
|
|
# Handle fade out here if it's stuck
|
|
stick_timer += delta
|
|
if stick_timer >= stick_duration:
|
|
# Start fading out after it sticks
|
|
modulate.a = max(0, 1 - (stick_timer - stick_duration) / 1.0) # Fade out over 1 second
|
|
if stick_timer >= stick_duration + 1.0: # Extra second for fade out
|
|
queue_free() # Remove the arrow after fade out
|
|
move_and_slide()
|
|
|
|
func _physics_process(_delta: float) -> void:
|
|
# If the arrow is stuck, stop it from moving
|
|
if is_stuck:
|
|
velocity = Vector2.ZERO # Stop movement
|
|
# Optional: disable further physics interaction by setting linear_velocity
|
|
# move_and_slide(Vector2.ZERO) # You can also use this to stop the character
|
|
|
|
func play_impact():
|
|
$SfxImpactSound.play()
|
|
|
|
# Called when the arrow hits a wall or another object (like sword_projectile)
|
|
func _on_arrow_area_body_entered(body: Node2D) -> void:
|
|
if is_stuck:
|
|
return
|
|
|
|
# Don't hit the owner
|
|
if body == player_owner or body == initiated_by:
|
|
return
|
|
|
|
# Don't hit the same target twice
|
|
if body in hit_targets:
|
|
return
|
|
|
|
# CRITICAL: Only the projectile owner (authority) should deal damage
|
|
if player_owner and not player_owner.is_multiplayer_authority():
|
|
return # Only the authority (creator) of the projectile can deal damage
|
|
|
|
# Add to hit_targets IMMEDIATELY to prevent multiple hits
|
|
hit_targets[body] = true
|
|
|
|
# Deal damage to players
|
|
if body.is_in_group("player") and body.has_method("rpc_take_damage"):
|
|
play_impact()
|
|
var attacker_pos = player_owner.global_position if player_owner else global_position
|
|
var player_peer_id = body.get_multiplayer_authority()
|
|
if player_peer_id != 0:
|
|
if multiplayer.is_server() and player_peer_id == multiplayer.get_unique_id():
|
|
body.rpc_take_damage(20.0, attacker_pos) # TODO: Get actual damage from player
|
|
else:
|
|
body.rpc_take_damage.rpc_id(player_peer_id, 20.0, attacker_pos)
|
|
else:
|
|
body.rpc_take_damage.rpc(20.0, attacker_pos)
|
|
_stick_to_target(body)
|
|
return
|
|
|
|
# Deal damage to enemies
|
|
if body.is_in_group("enemy") and body.has_method("rpc_take_damage"):
|
|
var attacker_pos = player_owner.global_position if player_owner else global_position
|
|
var damage = 20.0 # TODO: Get actual damage from player
|
|
if player_owner and player_owner.character_stats:
|
|
damage = player_owner.character_stats.damage
|
|
|
|
# Check hit chance (based on player's DEX stat)
|
|
var hit_roll = randf()
|
|
var hit_chance = 0.95
|
|
if player_owner and player_owner.character_stats:
|
|
hit_chance = player_owner.character_stats.hit_chance
|
|
var is_miss = hit_roll >= hit_chance
|
|
|
|
if is_miss:
|
|
if body.has_method("_show_damage_number"):
|
|
body._show_damage_number(0.0, attacker_pos, false, true, false) # is_miss = true
|
|
_stick_to_target(body)
|
|
return
|
|
|
|
play_impact()
|
|
var enemy_peer_id = body.get_multiplayer_authority()
|
|
if enemy_peer_id != 0:
|
|
if multiplayer.is_server() and enemy_peer_id == multiplayer.get_unique_id():
|
|
body.rpc_take_damage(damage, attacker_pos, false)
|
|
else:
|
|
body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, false)
|
|
else:
|
|
body.rpc_take_damage.rpc(damage, attacker_pos, false)
|
|
_stick_to_target(body)
|
|
return
|
|
|
|
# Hit wall or other object
|
|
$SfxImpactWall.play()
|
|
_stick_to_wall()
|
|
|
|
func _stick_to_target(target: Node2D):
|
|
# Stop the arrow
|
|
velocity = Vector2.ZERO
|
|
is_stuck = true
|
|
stick_timer = 0.0
|
|
arrow_area.set_deferred("monitoring", false)
|
|
|
|
# Calculate the collision point - move arrow slightly back from its direction
|
|
var collision_normal = -direction
|
|
var offset_distance = 8
|
|
var stick_position = global_position + (collision_normal * offset_distance)
|
|
|
|
# Make arrow a child of the target to stick to it
|
|
var global_rot = global_rotation
|
|
get_parent().call_deferred("remove_child", self)
|
|
target.call_deferred("add_child", self)
|
|
self.set_deferred("global_position", stick_position)
|
|
self.set_deferred("global_rotation", global_rot)
|
|
|
|
func _stick_to_wall():
|
|
# Stop the arrow
|
|
velocity = Vector2.ZERO
|
|
is_stuck = true
|
|
stick_timer = 0.0
|
|
arrow_area.set_deferred("monitoring", false)
|