fixed finally webrtc
This commit is contained in:
235
src/scripts/staff_projectile.gd
Normal file
235
src/scripts/staff_projectile.gd
Normal file
@@ -0,0 +1,235 @@
|
||||
extends Node2D
|
||||
|
||||
# Staff Projectile - Travels away from player and deals damage (magic ball)
|
||||
|
||||
@export var damage: float = 20.0
|
||||
@export var initial_speed: float = 300.0 # Faster than sword projectile
|
||||
@export var deceleration: float = 600.0 # Slower deceleration (travels further)
|
||||
@export var lifetime: float = 0.8 # Longer lifetime
|
||||
@export var max_distance: float = 200.0 # Travels further
|
||||
|
||||
var current_speed: float = 0.0
|
||||
|
||||
var travel_direction: Vector2 = Vector2.RIGHT
|
||||
var elapsed_time: float = 0.0
|
||||
var distance_traveled: float = 0.0
|
||||
var player_owner: Node = null
|
||||
var hit_targets = {} # Track what we've already hit (Dictionary for O(1) lookup)
|
||||
var color_replacements: Array = [] # Color replacements from staff item
|
||||
|
||||
@onready var sprite = $Sprite2D
|
||||
@onready var hit_area = $Area2D
|
||||
|
||||
func _ready():
|
||||
# Apply color replacements if available
|
||||
_apply_color_replacements()
|
||||
$SfxSwosh.play()
|
||||
$AnimationPlayer.play("flying")
|
||||
# Connect area signals
|
||||
if hit_area:
|
||||
hit_area.body_entered.connect(_on_body_entered)
|
||||
|
||||
func setup(direction: Vector2, owner_player: Node, damage_value: float = 20.0, staff_item: Item = null):
|
||||
travel_direction = direction.normalized()
|
||||
player_owner = owner_player
|
||||
damage = damage_value # Set damage from player
|
||||
current_speed = initial_speed
|
||||
|
||||
# Store color replacements from staff
|
||||
if staff_item and staff_item.colorReplacements:
|
||||
color_replacements = staff_item.colorReplacements
|
||||
|
||||
# Rotate sprite to face travel direction
|
||||
rotation = direction.angle()
|
||||
|
||||
# Apply color replacements after setup (in case sprite wasn't ready yet)
|
||||
_apply_color_replacements()
|
||||
|
||||
func _apply_color_replacements():
|
||||
# Apply color replacements to projectile sprite using shader parameters
|
||||
if not sprite or not sprite.material or not sprite.material is ShaderMaterial:
|
||||
return
|
||||
|
||||
if color_replacements.size() == 0:
|
||||
return
|
||||
|
||||
var shader_material = sprite.material as ShaderMaterial
|
||||
# Filter for "magic" colors only (RGB 174,39,30; RGB 109,29,32; RGB 246,57,48)
|
||||
# These are the colors that should be replaced on the projectile
|
||||
var magic_colors = [
|
||||
Color(174/255.0, 39/255.0, 30/255.0),
|
||||
Color(109/255.0, 29/255.0, 32/255.0),
|
||||
Color(246/255.0, 57/255.0, 48/255.0)
|
||||
]
|
||||
|
||||
var replacement_index = 0
|
||||
for color_replacement in color_replacements:
|
||||
if color_replacement.has("original") and color_replacement.has("replace"):
|
||||
var original_color = color_replacement["original"] as Color
|
||||
# Only apply replacements for magic colors
|
||||
for magic_color in magic_colors:
|
||||
# Check if this replacement matches a magic color (with some tolerance)
|
||||
if _colors_similar(original_color, magic_color, 0.1):
|
||||
var replace_color = color_replacement["replace"] as Color
|
||||
shader_material.set_shader_parameter("original_" + str(replacement_index), original_color)
|
||||
shader_material.set_shader_parameter("replace_" + str(replacement_index), replace_color)
|
||||
replacement_index += 1
|
||||
break # Found match, move to next replacement
|
||||
|
||||
func _colors_similar(color1: Color, color2: Color, tolerance: float = 0.1) -> bool:
|
||||
# Check if two colors are similar within tolerance
|
||||
var r_diff = abs(color1.r - color2.r)
|
||||
var g_diff = abs(color1.g - color2.g)
|
||||
var b_diff = abs(color1.b - color2.b)
|
||||
return r_diff <= tolerance and g_diff <= tolerance and b_diff <= tolerance
|
||||
|
||||
func _physics_process(delta):
|
||||
elapsed_time += delta
|
||||
|
||||
# Check lifetime
|
||||
if elapsed_time >= lifetime or distance_traveled >= max_distance:
|
||||
$Area2D.set_deferred("monitoring", false)
|
||||
self.visible = false
|
||||
if $SfxImpactWall.playing:
|
||||
await $SfxImpactWall.finished
|
||||
if $SfxImpact.playing:
|
||||
await $SfxImpact.finished
|
||||
queue_free()
|
||||
return
|
||||
|
||||
# Decelerate
|
||||
current_speed -= deceleration * delta
|
||||
current_speed = max(0.0, current_speed) # Don't go negative
|
||||
|
||||
# Move in travel direction
|
||||
var movement = travel_direction * current_speed * delta
|
||||
global_position += movement
|
||||
distance_traveled += movement.length()
|
||||
|
||||
# Fade out (based on speed)
|
||||
var alpha = current_speed / initial_speed # 1.0 at start, 0.0 when stopped
|
||||
if sprite:
|
||||
sprite.modulate.a = alpha
|
||||
|
||||
func _on_body_entered(body):
|
||||
# Don't hit the owner
|
||||
if body == player_owner:
|
||||
return
|
||||
|
||||
# Don't hit the same target twice - use Dictionary for O(1) lookup to prevent race conditions
|
||||
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 (mark as hit before processing)
|
||||
hit_targets[body] = true
|
||||
|
||||
# Deal damage to players - call RPC to let victim apply damage on their client
|
||||
if body.is_in_group("player") and body.has_method("rpc_take_damage"):
|
||||
$SfxImpact.play()
|
||||
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(damage, attacker_pos)
|
||||
else:
|
||||
body.rpc_take_damage.rpc_id(player_peer_id, damage, attacker_pos)
|
||||
else:
|
||||
body.rpc_take_damage.rpc(damage, attacker_pos)
|
||||
print("Staff projectile hit player: ", body.name, " for ", damage, " damage!")
|
||||
|
||||
# Deal damage to enemies - only authority (creator) deals damage
|
||||
elif 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 is_crit = get_meta("is_crit") if has_meta("is_crit") else false
|
||||
|
||||
# Check hit chance (based on player's DEX stat)
|
||||
var hit_roll = randf()
|
||||
var hit_chance = 0.95 # Base hit chance
|
||||
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:
|
||||
# Attack missed
|
||||
print("Player MISSED enemy: ", body.name, "! (hit chance: ", hit_chance * 100.0, "%)")
|
||||
if body.has_method("_show_damage_number"):
|
||||
body._show_damage_number(0.0, attacker_pos, false, true, false) # is_miss = true
|
||||
return
|
||||
|
||||
# Hit successful
|
||||
$SfxImpact.play()
|
||||
|
||||
# Use game_world to route damage request
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var enemy_name = body.name
|
||||
var enemy_index = body.get_meta("enemy_index") if body.has_meta("enemy_index") else -1
|
||||
|
||||
if game_world and game_world.has_method("_request_enemy_damage"):
|
||||
if multiplayer.is_server():
|
||||
game_world._request_enemy_damage(enemy_name, enemy_index, damage, attacker_pos, is_crit)
|
||||
else:
|
||||
game_world._request_enemy_damage.rpc_id(1, enemy_name, enemy_index, damage, attacker_pos, is_crit)
|
||||
else:
|
||||
# Fallback
|
||||
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, is_crit)
|
||||
else:
|
||||
body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, is_crit)
|
||||
else:
|
||||
body.rpc_take_damage.rpc(damage, attacker_pos, is_crit)
|
||||
|
||||
var owner_name: String = "none"
|
||||
var is_authority: bool = false
|
||||
if player_owner:
|
||||
owner_name = str(player_owner.name)
|
||||
is_authority = player_owner.is_multiplayer_authority()
|
||||
print("Staff projectile hit enemy: ", body.name, " for ", damage, " damage! (owner: ", owner_name, " is_authority: ", is_authority, ")")
|
||||
return
|
||||
|
||||
# Deal damage to boxes or other damageable objects
|
||||
elif "health" in body:
|
||||
body.health -= damage
|
||||
$SfxImpact.play()
|
||||
if body.health <= 0:
|
||||
# Get object identifier
|
||||
var obj_name = body.name
|
||||
var obj_index = -1
|
||||
|
||||
if body.has_meta("object_index"):
|
||||
obj_index = body.get_meta("object_index")
|
||||
if obj_index >= 0:
|
||||
obj_name = "InteractableObject_%d" % obj_index
|
||||
|
||||
if not obj_name.begins_with("InteractableObject_") and obj_index < 0:
|
||||
print("Staff projectile: Warning - object ", body.name, " doesn't have consistent naming!")
|
||||
|
||||
# Sync break to server
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if game_world and is_instance_valid(game_world) and game_world.is_inside_tree() and game_world.has_method("_sync_object_break"):
|
||||
if multiplayer.is_server():
|
||||
if game_world.has_method("_rpc_to_ready_peers"):
|
||||
game_world._rpc_to_ready_peers("_sync_object_break", [obj_name])
|
||||
print("Staff projectile synced box break to all clients: ", obj_name)
|
||||
else:
|
||||
game_world._sync_object_break.rpc_id(1, obj_name)
|
||||
print("Staff projectile requested box break on server: ", obj_name, " (index: ", obj_index, ")")
|
||||
else:
|
||||
print("Staff projectile: GameWorld not ready, skipping box break sync for ", obj_name)
|
||||
|
||||
# Break locally AFTER syncing
|
||||
if body.has_method("_break_into_pieces"):
|
||||
body._break_into_pieces()
|
||||
print("Staff projectile broke box locally: ", body.name)
|
||||
print("Staff projectile hit object: ", body.name)
|
||||
|
||||
# Push the hit target away slightly (only for non-enemies)
|
||||
if body is CharacterBody2D and not body.is_in_group("enemy"):
|
||||
var knockback_dir = (body.global_position - global_position).normalized()
|
||||
body.velocity = knockback_dir * 200.0
|
||||
Reference in New Issue
Block a user