made some adjustments to brights of rooms
This commit is contained in:
@@ -106,7 +106,7 @@ script = ExtResource("5")
|
||||
[node name="CanvasModulate" type="CanvasModulate" parent="." unique_id=948490815]
|
||||
light_mask = 1048575
|
||||
visibility_layer = 1048575
|
||||
color = Color(0.4140625, 0.4140625, 0.4140625, 1)
|
||||
color = Color(0.69140625, 0.69140625, 0.69140625, 1)
|
||||
|
||||
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="." unique_id=1141138343]
|
||||
stream = ExtResource("6_6c6v5")
|
||||
|
||||
@@ -573,12 +573,12 @@ scale = Vector2(1.984375, 2.0937502)
|
||||
texture = SubResource("GradientTexture2D_wnwbv")
|
||||
|
||||
[node name="ConeLight" type="PointLight2D" parent="." unique_id=120780131]
|
||||
blend_mode = 2
|
||||
blend_mode = 0
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="PointLight2D" type="PointLight2D" parent="." unique_id=1250823818]
|
||||
position = Vector2(-1, 0)
|
||||
blend_mode = 2
|
||||
blend_mode = 0
|
||||
shadow_enabled = true
|
||||
texture = SubResource("GradientTexture2D_f1ej7")
|
||||
|
||||
@@ -745,7 +745,7 @@ panning_strength = 1.11
|
||||
visible = false
|
||||
rotation = 3.1869712
|
||||
energy = 0.13
|
||||
blend_mode = 2
|
||||
blend_mode = 0
|
||||
shadow_enabled = true
|
||||
max_distance = 100.0
|
||||
|
||||
|
||||
@@ -478,7 +478,8 @@ func _show_damage_number(amount: float, from_position: Vector2, is_critical: boo
|
||||
damage_label.color = Color.GRAY
|
||||
else:
|
||||
damage_label.label = str(int(amount))
|
||||
damage_label.color = Color.ORANGE if is_critical else Color.RED
|
||||
damage_label.color = Color(1.0, 0.6, 0.2) if is_critical else Color(1.0, 0.35, 0.35) # Bright orange / bright red
|
||||
damage_label.z_index = 5
|
||||
|
||||
# Calculate direction from attacker (slight upward variation)
|
||||
var direction_from_attacker = (global_position - from_position).normalized()
|
||||
|
||||
@@ -63,6 +63,15 @@ var cached_corridor_player_tile: Vector2i = Vector2i(-1, -1)
|
||||
var cached_corridor_allowed_room_ids: Dictionary = {}
|
||||
var was_in_corridor: bool = false # Track previous corridor state to detect transitions
|
||||
var last_corridor_fog_update: float = 0.0 # Time of last fog update in corridor
|
||||
|
||||
# Torch-based CanvasModulate: only recalc on room/corridor transition, lerp over time
|
||||
var _torch_darken_initialized: bool = false
|
||||
var _torch_darken_in_room_last: bool = false
|
||||
var _torch_darken_last_room_id: String = ""
|
||||
var _torch_darken_target_scale: float = 1.0
|
||||
var _torch_darken_current_scale: float = 1.0
|
||||
const _TORCH_DARKEN_LERP_SPEED: float = 4.0
|
||||
const _TORCH_DARKEN_MIN_SCALE: float = 0.15 # Never go below this; allows player light to punch through
|
||||
var seen_by_player: Dictionary = {} # player_name -> PackedInt32Array (0 unseen, 1 seen)
|
||||
var combined_seen: PackedInt32Array = PackedInt32Array()
|
||||
var explored_map: PackedInt32Array = PackedInt32Array() # 0 unseen, 1 explored
|
||||
@@ -1127,7 +1136,7 @@ func _show_loot_floating_text(player: Node, text: String, color: Color, item_tex
|
||||
floating_text.global_position = Vector2(player.global_position.x, player.global_position.y - 20)
|
||||
floating_text.setup(text, color, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame)
|
||||
|
||||
func _apply_heal_spell_sync(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool):
|
||||
func _apply_heal_spell_sync(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false):
|
||||
var target: Node = null
|
||||
for p in get_tree().get_nodes_in_group("player"):
|
||||
if p.name == target_name and is_instance_valid(p):
|
||||
@@ -1137,28 +1146,38 @@ func _apply_heal_spell_sync(target_name: String, amount_to_apply: float, display
|
||||
return
|
||||
var me = multiplayer.get_unique_id()
|
||||
var tid = target.get_multiplayer_authority()
|
||||
if me == tid and target.has_method("heal") and amount_to_apply > 0:
|
||||
target.heal(amount_to_apply, allow_overheal)
|
||||
var entities = get_node_or_null("Entities")
|
||||
var parent = entities if entities else target.get_parent()
|
||||
if not parent:
|
||||
return
|
||||
var eff_scene = load("res://scenes/healing_effect.tscn") as PackedScene
|
||||
if eff_scene:
|
||||
var eff = eff_scene.instantiate()
|
||||
parent.add_child(eff)
|
||||
eff.global_position = target.global_position
|
||||
if eff.has_method("setup"):
|
||||
eff.setup(target)
|
||||
var prefix = ""
|
||||
if is_crit and is_overheal:
|
||||
prefix = "CRIT OVERHEAL! "
|
||||
elif is_crit:
|
||||
prefix = "CRIT! "
|
||||
elif is_overheal:
|
||||
prefix = "OVERHEAL! "
|
||||
var heal_text = prefix + "+" + str(display_amount) + " HP"
|
||||
_show_loot_floating_text(target, heal_text, Color.GREEN, null, 1, 1, 0)
|
||||
if is_revive:
|
||||
if me == tid and target.has_method("_revive_from_heal"):
|
||||
target._revive_from_heal(display_amount)
|
||||
elif tid != 0 and target.has_method("_revive_from_heal"):
|
||||
target._revive_from_heal.rpc_id(tid, display_amount)
|
||||
else:
|
||||
if me == tid and target.has_method("heal") and amount_to_apply > 0:
|
||||
target.heal(amount_to_apply, allow_overheal)
|
||||
# When revive, target's authority already spawned effect+text in _revive_from_heal; skip to avoid double spawn
|
||||
var skip_spawn = is_revive and me == tid
|
||||
if not skip_spawn:
|
||||
var entities = get_node_or_null("Entities")
|
||||
var parent = entities if entities else target.get_parent()
|
||||
if not parent:
|
||||
pass
|
||||
else:
|
||||
var eff_scene = load("res://scenes/healing_effect.tscn") as PackedScene
|
||||
if eff_scene:
|
||||
var eff = eff_scene.instantiate()
|
||||
parent.add_child(eff)
|
||||
eff.global_position = target.global_position
|
||||
if eff.has_method("setup"):
|
||||
eff.setup(target)
|
||||
var prefix = ""
|
||||
if is_crit and is_overheal:
|
||||
prefix = "CRIT OVERHEAL! "
|
||||
elif is_crit:
|
||||
prefix = "CRIT! "
|
||||
elif is_overheal:
|
||||
prefix = "OVERHEAL! "
|
||||
var heal_text = prefix + "+" + str(display_amount) + " HP"
|
||||
_show_loot_floating_text(target, heal_text, Color.GREEN, null, 1, 1, 0)
|
||||
|
||||
@rpc("authority", "unreliable")
|
||||
func _sync_enemy_position(enemy_name: String, enemy_index: int, pos: Vector2, vel: Vector2, z_pos: float, dir: int, frame: int, anim: String, frame_num: int, state_value: int):
|
||||
@@ -1645,6 +1664,7 @@ func _process(delta):
|
||||
# Update camera to follow local players
|
||||
_update_camera()
|
||||
_update_fog_of_war(delta)
|
||||
_update_canvas_modulate_by_torches()
|
||||
|
||||
# Periodic cleanup of disconnected peers (server only)
|
||||
if multiplayer.is_server() and multiplayer.has_multiplayer_peer():
|
||||
@@ -2124,7 +2144,7 @@ func _is_walkable_tile(tile_center: Vector2) -> bool:
|
||||
var v = grid[tile_x][tile_y]
|
||||
return v == 1 or v == 2 or v == 3
|
||||
|
||||
func _get_adjacent_valid_spell_tile_centers(center_world_pos: Vector2, player_pos: Vector2) -> Array:
|
||||
func _get_adjacent_valid_spell_tile_centers(center_world_pos: Vector2, _player_pos: Vector2) -> Array:
|
||||
var out: Array = []
|
||||
if not dungeon_tilemap_layer or dungeon_data.is_empty() or not dungeon_data.has("grid"):
|
||||
return out
|
||||
@@ -2172,6 +2192,12 @@ func _init_fog_of_war():
|
||||
cached_corridor_allowed_room_ids.clear()
|
||||
was_in_corridor = false
|
||||
last_corridor_fog_update = 0.0
|
||||
# Reset torch-based darkening state for new level
|
||||
_torch_darken_initialized = false
|
||||
_torch_darken_in_room_last = false
|
||||
_torch_darken_last_room_id = ""
|
||||
_torch_darken_target_scale = 1.0
|
||||
_torch_darken_current_scale = 1.0
|
||||
|
||||
func _update_fog_of_war(delta: float) -> void:
|
||||
if not fog_node or dungeon_data.is_empty() or not dungeon_data.has("map_size"):
|
||||
@@ -2450,6 +2476,104 @@ func _get_room_index_for_tile(tile: Vector2i) -> int:
|
||||
return i
|
||||
return -1
|
||||
|
||||
func _count_torches_in_room(room: Dictionary) -> int:
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("torches"):
|
||||
return 0
|
||||
var tile_size := 16
|
||||
var min_x: float = room.x * tile_size
|
||||
var min_y: float = room.y * tile_size
|
||||
var max_x: float = (room.x + room.w) * tile_size
|
||||
var max_y: float = (room.y + room.h) * tile_size
|
||||
var count := 0
|
||||
for t in dungeon_data.torches:
|
||||
var pos: Vector2 = t.get("position", Vector2.ZERO)
|
||||
if pos.x >= min_x and pos.x <= max_x and pos.y >= min_y and pos.y <= max_y:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
func _median_torch_scale_from_rooms(rooms: Array) -> float:
|
||||
if rooms.is_empty():
|
||||
return 0.0
|
||||
var counts: Array = []
|
||||
for r in rooms:
|
||||
counts.append(_count_torches_in_room(r))
|
||||
counts.sort()
|
||||
var n := counts.size()
|
||||
var mid := int(n / 2.0)
|
||||
var median: float
|
||||
if n % 2 == 1:
|
||||
median = float(counts[mid])
|
||||
else:
|
||||
median = (float(counts[mid - 1]) + float(counts[mid])) * 0.5
|
||||
median = clampf(median, 0.0, 4.0)
|
||||
return median / 4.0
|
||||
|
||||
func _update_canvas_modulate_by_torches() -> void:
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("torches"):
|
||||
return
|
||||
var cm = get_node_or_null("CanvasModulate")
|
||||
if not cm or not is_instance_valid(cm):
|
||||
return
|
||||
var local_list = player_manager.get_local_players() if player_manager else []
|
||||
if local_list.is_empty() or not local_list[0]:
|
||||
return
|
||||
var p = local_list[0]
|
||||
var p_tile = Vector2i(int(p.global_position.x / FOG_TILE_SIZE), int(p.global_position.y / FOG_TILE_SIZE))
|
||||
var current_room = _find_room_at_tile(p_tile)
|
||||
var in_room := not current_room.is_empty()
|
||||
var room_id := ""
|
||||
if in_room:
|
||||
room_id = str(current_room.x) + "," + str(current_room.y) + "," + str(current_room.w) + "," + str(current_room.h)
|
||||
var transition := not _torch_darken_initialized
|
||||
if _torch_darken_initialized:
|
||||
if in_room != _torch_darken_in_room_last:
|
||||
transition = true
|
||||
elif in_room and room_id != _torch_darken_last_room_id:
|
||||
transition = true
|
||||
if transition:
|
||||
_torch_darken_initialized = true
|
||||
_torch_darken_in_room_last = in_room
|
||||
_torch_darken_last_room_id = room_id
|
||||
if in_room:
|
||||
var tc = clampi(_count_torches_in_room(current_room), 0, 4)
|
||||
_torch_darken_target_scale = tc / 4.0
|
||||
else:
|
||||
_torch_darken_target_scale = _median_torch_scale_from_rooms(cached_corridor_rooms)
|
||||
var delta := get_process_delta_time()
|
||||
_torch_darken_current_scale = lerpf(_torch_darken_current_scale, _torch_darken_target_scale, clampf(delta * _TORCH_DARKEN_LERP_SPEED, 0.0, 1.0))
|
||||
var s := maxf(_torch_darken_current_scale, _TORCH_DARKEN_MIN_SCALE)
|
||||
cm.color = Color(s, s, s)
|
||||
|
||||
func _reapply_torch_darkening() -> void:
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("torches"):
|
||||
return
|
||||
var cm = get_node_or_null("CanvasModulate")
|
||||
if not cm or not is_instance_valid(cm):
|
||||
return
|
||||
var local_list = player_manager.get_local_players() if player_manager else []
|
||||
if local_list.is_empty() or not local_list[0]:
|
||||
return
|
||||
var p = local_list[0]
|
||||
var p_tile = Vector2i(int(p.global_position.x / FOG_TILE_SIZE), int(p.global_position.y / FOG_TILE_SIZE))
|
||||
var current_room = _find_room_at_tile(p_tile)
|
||||
var in_room := not current_room.is_empty()
|
||||
var target: float
|
||||
if in_room:
|
||||
var tc = clampi(_count_torches_in_room(current_room), 0, 4)
|
||||
target = tc / 4.0
|
||||
else:
|
||||
target = _median_torch_scale_from_rooms(cached_corridor_rooms)
|
||||
var t := maxf(target, _TORCH_DARKEN_MIN_SCALE)
|
||||
_torch_darken_target_scale = target
|
||||
_torch_darken_current_scale = target
|
||||
var room_id := ""
|
||||
if in_room:
|
||||
room_id = str(current_room.x) + "," + str(current_room.y) + "," + str(current_room.w) + "," + str(current_room.h)
|
||||
_torch_darken_initialized = true
|
||||
_torch_darken_in_room_last = in_room
|
||||
_torch_darken_last_room_id = room_id
|
||||
cm.color = Color(t, t, t)
|
||||
|
||||
func _get_view_angle_weight(view_dir: Vector2, ray_dir: Vector2) -> float:
|
||||
if view_dir.length() < 0.1 or ray_dir.length() < 0.1:
|
||||
return 1.0
|
||||
|
||||
@@ -24,7 +24,7 @@ var _indicators: Array = [] # { position: Vector2, color: Color, name: String,
|
||||
var _font: Font = null
|
||||
|
||||
func _ready() -> void:
|
||||
_update_size()
|
||||
call_deferred("_update_size")
|
||||
if get_viewport():
|
||||
get_viewport().size_changed.connect(_update_size)
|
||||
_game_world = get_tree().get_first_node_in_group("game_world")
|
||||
@@ -38,6 +38,7 @@ func _update_size() -> void:
|
||||
if not vp:
|
||||
return
|
||||
var rect = vp.get_visible_rect()
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
position = rect.position
|
||||
size = rect.size
|
||||
custom_minimum_size = rect.size
|
||||
|
||||
@@ -44,6 +44,7 @@ var grab_tap_threshold = 0.2 # Seconds to distinguish tap from hold
|
||||
var is_shielding: bool = false # True when holding grab with shield in offhand and nothing to grab/lift
|
||||
var is_reviving: bool = false # True when holding grab on a corpse and charging revive
|
||||
var revive_charge: float = 0.0
|
||||
var was_reviving_last_frame: bool = false
|
||||
const REVIVE_DURATION: float = 2.0 # Seconds holding grab to revive
|
||||
var last_movement_direction = Vector2.DOWN # Track last direction for placing objects
|
||||
var push_axis = Vector2.ZERO # Locked axis for pushing/pulling
|
||||
@@ -1782,165 +1783,180 @@ func _physics_process(delta):
|
||||
else:
|
||||
spell_charge_tint_pulse_time = 0.0
|
||||
|
||||
# Revive charge visuals - same as healing (healing_charging on AnimationIncantation)
|
||||
if is_reviving:
|
||||
was_reviving_last_frame = true
|
||||
if has_node("AnimationIncantation") and not is_charging_spell:
|
||||
$AnimationIncantation.play("healing_charging")
|
||||
elif was_reviving_last_frame:
|
||||
was_reviving_last_frame = false
|
||||
_stop_spell_charge_incantation()
|
||||
|
||||
if is_local_player and is_multiplayer_authority():
|
||||
# Skip all input and logic if dead
|
||||
# When dead: only corpse knockback friction + sync; no input or other logic
|
||||
if is_dead:
|
||||
return
|
||||
if is_knocked_back:
|
||||
knockback_time += delta
|
||||
if knockback_time >= knockback_duration:
|
||||
is_knocked_back = false
|
||||
knockback_time = 0.0
|
||||
else:
|
||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
|
||||
else:
|
||||
# Handle knockback timer (always handle knockback, even when controls are disabled)
|
||||
if is_knocked_back:
|
||||
knockback_time += delta
|
||||
if knockback_time >= knockback_duration:
|
||||
is_knocked_back = false
|
||||
knockback_time = 0.0
|
||||
|
||||
# Handle knockback timer (always handle knockback, even when controls are disabled)
|
||||
if is_knocked_back:
|
||||
knockback_time += delta
|
||||
if knockback_time >= knockback_duration:
|
||||
is_knocked_back = false
|
||||
knockback_time = 0.0
|
||||
# Update movement lock timer (for bow release)
|
||||
if movement_lock_timer > 0.0:
|
||||
movement_lock_timer -= delta
|
||||
if movement_lock_timer <= 0.0:
|
||||
movement_lock_timer = 0.0
|
||||
|
||||
# Update movement lock timer (for bow release)
|
||||
if movement_lock_timer > 0.0:
|
||||
movement_lock_timer -= delta
|
||||
if movement_lock_timer <= 0.0:
|
||||
movement_lock_timer = 0.0
|
||||
# Update direction lock timer (for attacks)
|
||||
if direction_lock_timer > 0.0:
|
||||
direction_lock_timer -= delta
|
||||
if direction_lock_timer <= 0.0:
|
||||
direction_lock_timer = 0.0
|
||||
|
||||
# Update direction lock timer (for attacks)
|
||||
if direction_lock_timer > 0.0:
|
||||
direction_lock_timer -= delta
|
||||
if direction_lock_timer <= 0.0:
|
||||
direction_lock_timer = 0.0
|
||||
# Update damage direction lock timer (block facing change when taking damage)
|
||||
if damage_direction_lock_timer > 0.0:
|
||||
damage_direction_lock_timer -= delta
|
||||
if damage_direction_lock_timer <= 0.0:
|
||||
damage_direction_lock_timer = 0.0
|
||||
|
||||
# Update damage direction lock timer (block facing change when taking damage)
|
||||
if damage_direction_lock_timer > 0.0:
|
||||
damage_direction_lock_timer -= delta
|
||||
if damage_direction_lock_timer <= 0.0:
|
||||
damage_direction_lock_timer = 0.0
|
||||
if shield_block_cooldown_timer > 0.0:
|
||||
shield_block_cooldown_timer -= delta
|
||||
if shield_block_cooldown_timer <= 0.0:
|
||||
shield_block_cooldown_timer = 0.0
|
||||
|
||||
if shield_block_cooldown_timer > 0.0:
|
||||
shield_block_cooldown_timer -= delta
|
||||
if shield_block_cooldown_timer <= 0.0:
|
||||
shield_block_cooldown_timer = 0.0
|
||||
# Update bow charge tint (when fully charged)
|
||||
if is_charging_bow:
|
||||
var charge_time = (Time.get_ticks_msec() / 1000.0) - bow_charge_start_time
|
||||
# Smooth curve: charge from 0.2s to 1.0s
|
||||
var charge_progress = clamp((charge_time - 0.2) / 0.8, 0.0, 1.0) # 0.0 at 0.2s, 1.0 at 1.0s
|
||||
|
||||
# Update bow charge tint (when fully charged)
|
||||
if is_charging_bow:
|
||||
var charge_time = (Time.get_ticks_msec() / 1000.0) - bow_charge_start_time
|
||||
# Smooth curve: charge from 0.2s to 1.0s
|
||||
var charge_progress = clamp((charge_time - 0.2) / 0.8, 0.0, 1.0) # 0.0 at 0.2s, 1.0 at 1.0s
|
||||
|
||||
# Update tint pulse timer when fully charged
|
||||
if charge_progress >= 1.0:
|
||||
# Use fast pulse speed when fully charged
|
||||
bow_charge_tint_pulse_time += delta * bow_charge_tint_pulse_speed_charged
|
||||
_apply_bow_charge_tint()
|
||||
# Update tint pulse timer when fully charged
|
||||
if charge_progress >= 1.0:
|
||||
# Use fast pulse speed when fully charged
|
||||
bow_charge_tint_pulse_time += delta * bow_charge_tint_pulse_speed_charged
|
||||
_apply_bow_charge_tint()
|
||||
else:
|
||||
bow_charge_tint_pulse_time = 0.0
|
||||
_clear_bow_charge_tint()
|
||||
else:
|
||||
# Reset pulse timer when not charging
|
||||
bow_charge_tint_pulse_time = 0.0
|
||||
_clear_bow_charge_tint()
|
||||
else:
|
||||
# Reset pulse timer when not charging
|
||||
bow_charge_tint_pulse_time = 0.0
|
||||
_clear_bow_charge_tint()
|
||||
|
||||
# Update burn debuff (works on both authority and clients)
|
||||
if burn_debuff_timer > 0.0:
|
||||
burn_debuff_timer -= delta
|
||||
# Update burn debuff (works on both authority and clients)
|
||||
if burn_debuff_timer > 0.0:
|
||||
burn_debuff_timer -= delta
|
||||
|
||||
# Only deal damage on authority (where we have authority)
|
||||
if is_multiplayer_authority():
|
||||
burn_damage_timer += delta
|
||||
# Only deal damage on authority (where we have authority)
|
||||
if is_multiplayer_authority():
|
||||
burn_damage_timer += delta
|
||||
|
||||
# Deal burn damage every second (no knockback)
|
||||
if burn_damage_timer >= 1.0:
|
||||
# Deal burn damage every second (no knockback)
|
||||
if burn_damage_timer >= 1.0:
|
||||
burn_damage_timer = 0.0
|
||||
# Deal burn damage directly (no knockback, no animation)
|
||||
if character_stats:
|
||||
var old_hp = character_stats.hp
|
||||
character_stats.modify_health(-burn_debuff_damage_per_second)
|
||||
if character_stats.hp <= 0:
|
||||
character_stats.no_health.emit()
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
var actual_damage = old_hp - character_stats.hp
|
||||
print(name, " takes ", actual_damage, " burn damage! Health: ", character_stats.hp, "/", character_stats.maxhp)
|
||||
# Show damage number for burn damage
|
||||
_show_damage_number(actual_damage, global_position, false, false, false) # Show burn damage number
|
||||
# Sync burn damage visual to other clients
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_damage", [actual_damage, global_position])
|
||||
|
||||
# Animate burn visual if it's a sprite (works on both authority and clients)
|
||||
if burn_debuff_visual and is_instance_valid(burn_debuff_visual):
|
||||
if burn_debuff_visual is Sprite2D:
|
||||
var sprite = burn_debuff_visual as Sprite2D
|
||||
var anim_timer = sprite.get_meta("burn_animation_timer", 0.0)
|
||||
anim_timer += delta
|
||||
if anim_timer >= 0.1: # ~10 FPS
|
||||
anim_timer = 0.0
|
||||
var frame = sprite.get_meta("burn_animation_frame", 0)
|
||||
frame = (frame + 1) % 16
|
||||
sprite.frame = frame
|
||||
sprite.set_meta("burn_animation_frame", frame)
|
||||
sprite.set_meta("burn_animation_timer", anim_timer)
|
||||
|
||||
# Remove burn debuff when timer expires (works on both authority and clients)
|
||||
if burn_debuff_timer <= 0.0:
|
||||
burn_debuff_timer = 0.0
|
||||
burn_damage_timer = 0.0
|
||||
# Deal burn damage directly (no knockback, no animation)
|
||||
if character_stats:
|
||||
var old_hp = character_stats.hp
|
||||
character_stats.modify_health(-burn_debuff_damage_per_second)
|
||||
if character_stats.hp <= 0:
|
||||
character_stats.no_health.emit()
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
var actual_damage = old_hp - character_stats.hp
|
||||
print(name, " takes ", actual_damage, " burn damage! Health: ", character_stats.hp, "/", character_stats.maxhp)
|
||||
# Show damage number for burn damage
|
||||
_show_damage_number(actual_damage, global_position, false, false, false) # Show burn damage number
|
||||
# Sync burn damage visual to other clients
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_damage", [actual_damage, global_position])
|
||||
_remove_burn_debuff()
|
||||
|
||||
# Animate burn visual if it's a sprite (works on both authority and clients)
|
||||
if burn_debuff_visual and is_instance_valid(burn_debuff_visual):
|
||||
if burn_debuff_visual is Sprite2D:
|
||||
var sprite = burn_debuff_visual as Sprite2D
|
||||
var anim_timer = sprite.get_meta("burn_animation_timer", 0.0)
|
||||
anim_timer += delta
|
||||
if anim_timer >= 0.1: # ~10 FPS
|
||||
anim_timer = 0.0
|
||||
var frame = sprite.get_meta("burn_animation_frame", 0)
|
||||
frame = (frame + 1) % 16
|
||||
sprite.frame = frame
|
||||
sprite.set_meta("burn_animation_frame", frame)
|
||||
sprite.set_meta("burn_animation_timer", anim_timer)
|
||||
# Skip input if controls are disabled (e.g., when inventory is open)
|
||||
# But still allow knockback to continue (handled above)
|
||||
var skip_input = controls_disabled
|
||||
if controls_disabled:
|
||||
if not is_knocked_back:
|
||||
# Immediately stop movement when controls are disabled (e.g., inventory opened)
|
||||
velocity = Vector2.ZERO
|
||||
# Reset animation to IDLE if not in a special state
|
||||
if current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF":
|
||||
if is_lifting:
|
||||
_set_animation("IDLE_HOLD")
|
||||
elif is_pushing:
|
||||
_set_animation("IDLE_PUSH")
|
||||
else:
|
||||
_set_animation("IDLE")
|
||||
|
||||
# Remove burn debuff when timer expires (works on both authority and clients)
|
||||
if burn_debuff_timer <= 0.0:
|
||||
burn_debuff_timer = 0.0
|
||||
burn_damage_timer = 0.0
|
||||
_remove_burn_debuff()
|
||||
# Check if being held by someone
|
||||
var being_held_by_someone = false
|
||||
for other_player in get_tree().get_nodes_in_group("player"):
|
||||
if other_player != self and other_player.held_object == self:
|
||||
being_held_by_someone = true
|
||||
being_held_by = other_player
|
||||
break
|
||||
|
||||
# Skip input if controls are disabled (e.g., when inventory is open)
|
||||
# But still allow knockback to continue (handled above)
|
||||
var skip_input = controls_disabled
|
||||
if controls_disabled:
|
||||
if not is_knocked_back:
|
||||
# Immediately stop movement when controls are disabled (e.g., inventory opened)
|
||||
velocity = Vector2.ZERO
|
||||
# Reset animation to IDLE if not in a special state
|
||||
if current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF":
|
||||
if is_lifting:
|
||||
_set_animation("IDLE_HOLD")
|
||||
elif is_pushing:
|
||||
_set_animation("IDLE_PUSH")
|
||||
else:
|
||||
_set_animation("IDLE")
|
||||
if being_held_by_someone:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Handle struggle mechanic
|
||||
_handle_struggle(delta)
|
||||
elif is_knocked_back:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# During knockback, no input control - just let velocity carry the player
|
||||
# Apply friction to slow down knockback
|
||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
|
||||
elif not is_airborne and not skip_input:
|
||||
# Normal input handling (only if controls are not disabled)
|
||||
struggle_time = 0.0 # Reset struggle timer
|
||||
struggle_direction = Vector2.ZERO
|
||||
_handle_input()
|
||||
_handle_movement(delta)
|
||||
_handle_interactions()
|
||||
else:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Reset struggle when airborne
|
||||
struggle_time = 0.0
|
||||
struggle_direction = Vector2.ZERO
|
||||
|
||||
# Check if being held by someone
|
||||
var being_held_by_someone = false
|
||||
for other_player in get_tree().get_nodes_in_group("player"):
|
||||
if other_player != self and other_player.held_object == self:
|
||||
being_held_by_someone = true
|
||||
being_held_by = other_player
|
||||
break
|
||||
# Update held object positions
|
||||
if is_lifting:
|
||||
_update_lifted_object()
|
||||
elif is_pushing:
|
||||
_update_pushed_object()
|
||||
|
||||
if being_held_by_someone:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Handle struggle mechanic
|
||||
_handle_struggle(delta)
|
||||
elif is_knocked_back:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# During knockback, no input control - just let velocity carry the player
|
||||
# Apply friction to slow down knockback
|
||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
|
||||
elif not is_airborne and not skip_input:
|
||||
# Normal input handling (only if controls are not disabled)
|
||||
struggle_time = 0.0 # Reset struggle timer
|
||||
struggle_direction = Vector2.ZERO
|
||||
_handle_input()
|
||||
_handle_movement(delta)
|
||||
_handle_interactions()
|
||||
else:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Reset struggle when airborne
|
||||
struggle_time = 0.0
|
||||
struggle_direction = Vector2.ZERO
|
||||
|
||||
# Update held object positions
|
||||
if is_lifting:
|
||||
_update_lifted_object()
|
||||
elif is_pushing:
|
||||
_update_pushed_object()
|
||||
|
||||
# Sync position, direction, and animation to other clients (unreliable broadcast)
|
||||
# Sync position, direction, and animation to other clients (unreliable broadcast)
|
||||
# Only send RPC if we're in the scene tree and ready to send RPCs (prevents errors when player hasn't spawned on all clients yet)
|
||||
# On server, also wait for all clients to be ready
|
||||
if multiplayer.has_multiplayer_peer() and is_inside_tree() and can_send_rpcs:
|
||||
@@ -4035,7 +4051,7 @@ func _cast_heal_spell(target: Node):
|
||||
var dungeon_seed: int = 0
|
||||
if gw and "dungeon_seed" in gw:
|
||||
dungeon_seed = gw.dungeon_seed
|
||||
var seed_val = dungeon_seed + hash(target.name) + int(global_position.x) * 31 + int(global_position.y) * 17 + int(Time.get_ticks_msec() / 50)
|
||||
var seed_val = dungeon_seed + hash(target.name) + int(global_position.x) * 31 + int(global_position.y) * 17 + int(Time.get_ticks_msec() / 50.0)
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed_val
|
||||
|
||||
@@ -4052,30 +4068,41 @@ func _cast_heal_spell(target: Node):
|
||||
if is_crit:
|
||||
amount = floor(amount * 2.0)
|
||||
|
||||
var overheal_chance_pct = 1.0 + lck_val * 0.3
|
||||
var can_overheal = target.character_stats and target.character_stats.hp <= target.character_stats.maxhp
|
||||
var is_overheal = can_overheal and rng.randf() * 100.0 < overheal_chance_pct
|
||||
|
||||
var is_revive = "is_dead" in target and target.is_dead
|
||||
var display_amount = int(amount)
|
||||
var actual_heal = amount
|
||||
var allow_overheal = false
|
||||
if is_overheal:
|
||||
allow_overheal = true
|
||||
var is_overheal = false
|
||||
|
||||
if is_revive:
|
||||
# Tome revive: no overheal, full amount revives
|
||||
actual_heal = amount
|
||||
else:
|
||||
var cap = 0.0
|
||||
if target.character_stats:
|
||||
cap = target.character_stats.maxhp - target.character_stats.hp
|
||||
actual_heal = min(amount, max(0.0, cap))
|
||||
var overheal_chance_pct = 1.0 + lck_val * 0.3
|
||||
var can_overheal = target.character_stats and target.character_stats.hp <= target.character_stats.maxhp
|
||||
is_overheal = can_overheal and rng.randf() * 100.0 < overheal_chance_pct
|
||||
if is_overheal:
|
||||
allow_overheal = true
|
||||
else:
|
||||
var cap = 0.0
|
||||
if target.character_stats:
|
||||
cap = target.character_stats.maxhp - target.character_stats.hp
|
||||
actual_heal = min(amount, max(0.0, cap))
|
||||
|
||||
var me = multiplayer.get_unique_id()
|
||||
var tid = target.get_multiplayer_authority()
|
||||
if me == tid and actual_heal > 0:
|
||||
target.heal(actual_heal, allow_overheal)
|
||||
if is_revive:
|
||||
if me == tid:
|
||||
target._revive_from_heal(display_amount)
|
||||
elif multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
target._revive_from_heal.rpc_id(tid, display_amount)
|
||||
else:
|
||||
if me == tid and actual_heal > 0:
|
||||
target.heal(actual_heal, allow_overheal)
|
||||
_spawn_heal_effect_and_text(target, display_amount, is_crit, is_overheal)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
if gw and gw.has_method("_apply_heal_spell_sync"):
|
||||
_rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, actual_heal, display_amount, is_crit, is_overheal, allow_overheal])
|
||||
print(name, " cast heal on ", target.name, " for ", display_amount, " HP", " (actual: ", actual_heal, ", crit: ", is_crit, ", overheal: ", is_overheal, ")")
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree() and gw and gw.has_method("_apply_heal_spell_sync"):
|
||||
_rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, actual_heal, display_amount, is_crit, is_overheal, allow_overheal, is_revive])
|
||||
print(name, " cast heal on ", target.name, " for ", display_amount, " HP", " (actual: ", actual_heal, ", crit: ", is_crit, ", overheal: ", is_overheal, ", revive: ", is_revive, ")")
|
||||
|
||||
func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: bool, is_overheal: bool):
|
||||
if not target or not is_instance_valid(target):
|
||||
@@ -4107,12 +4134,12 @@ func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: boo
|
||||
ft.setup(heal_text, Color.GREEN, 0.5, 0.5, null, 1, 1, 0)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_heal_spell_via_gw(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool):
|
||||
func _sync_heal_spell_via_gw(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false):
|
||||
if is_multiplayer_authority():
|
||||
return
|
||||
var gw = get_tree().get_first_node_in_group("game_world")
|
||||
if gw and gw.has_method("_apply_heal_spell_sync"):
|
||||
gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal)
|
||||
gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal, is_revive)
|
||||
|
||||
func _is_healing_spell() -> bool:
|
||||
if not character_stats or not character_stats.equipment.has("offhand"):
|
||||
@@ -4138,8 +4165,6 @@ func _get_heal_target() -> Node:
|
||||
for p in get_tree().get_nodes_in_group("player"):
|
||||
if not is_instance_valid(p):
|
||||
continue
|
||||
if "is_dead" in p and p.is_dead:
|
||||
continue
|
||||
var d = p.global_position.distance_to(mouse_world)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
@@ -5826,19 +5851,7 @@ func _die():
|
||||
# Wait for DIE animation to complete (200+200+200+800 = 1400ms = 1.4s)
|
||||
await get_tree().create_timer(1.4).timeout
|
||||
|
||||
# Fade out over 0.5 seconds (fade all sprite layers)
|
||||
var fade_tween = create_tween()
|
||||
fade_tween.set_parallel(true)
|
||||
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
if sprite_layer:
|
||||
fade_tween.tween_property(sprite_layer, "modulate:a", 0.0, 0.5)
|
||||
|
||||
# Wait for fade to finish
|
||||
await fade_tween.finished
|
||||
|
||||
# Force holder to drop us NOW (after fade, before respawn)
|
||||
# Force holder to drop us NOW (before respawn wait)
|
||||
# Search for any player holding us (don't rely on being_held_by)
|
||||
print(name, " searching for anyone holding us...")
|
||||
var found_holder = false
|
||||
@@ -5999,7 +6012,9 @@ func _respawn():
|
||||
global_position = new_respawn_pos
|
||||
respawn_point = new_respawn_pos # Update respawn point for next time
|
||||
|
||||
# Play idle animation
|
||||
# Clear concussion and play idle animation (server)
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
_set_animation("IDLE")
|
||||
|
||||
# Sync respawn over network (only authority sends)
|
||||
@@ -6049,7 +6064,10 @@ func _do_revive(corpse: Node):
|
||||
if character_stats:
|
||||
character_stats.hp = max(1, character_stats.hp - half_hp)
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
# Show -X HP on reviver (we "took" that much to revive)
|
||||
_show_revive_cost_number(half_hp)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_revive_cost", [half_hp])
|
||||
corpse._revive_from_player.rpc_id(corpse.get_multiplayer_authority(), half_hp)
|
||||
else:
|
||||
corpse._revive_from_player(half_hp)
|
||||
@@ -6074,6 +6092,42 @@ func _revive_from_player(hp_amount: int):
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
_set_animation("IDLE")
|
||||
# Same healing effect as Tome of Healing (green frames, pulse, +X HP)
|
||||
_spawn_heal_effect_and_text(self, hp_amount, false, false)
|
||||
# Clear concussion on all clients (authority already did above; broadcast for others)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_revived_clear_concussion", [name])
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_revived_clear_concussion(_player_name: String):
|
||||
# Received on each peer's copy of the revived player; clear our concussion (server + clients)
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _revive_from_heal(hp_amount: int):
|
||||
if not is_dead:
|
||||
return
|
||||
was_revived = true
|
||||
is_dead = false
|
||||
is_processing_death = false
|
||||
if character_stats:
|
||||
character_stats.hp = float(hp_amount)
|
||||
else:
|
||||
current_health = float(hp_amount)
|
||||
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
if sprite_layer:
|
||||
sprite_layer.modulate.a = 1.0
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
_set_animation("IDLE")
|
||||
_spawn_heal_effect_and_text(self, hp_amount, false, false)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_revived_clear_concussion", [name])
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_death():
|
||||
@@ -6098,11 +6152,16 @@ func _sync_respawn(spawn_pos: Vector2):
|
||||
|
||||
# Restore visibility
|
||||
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
if sprite_layer:
|
||||
sprite_layer.modulate.a = 1.0
|
||||
|
||||
# Clear concussion on clients (AnimationPlayerStatus -> idle)
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
|
||||
_set_animation("IDLE")
|
||||
|
||||
func add_coins(amount: int):
|
||||
@@ -6440,7 +6499,8 @@ func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool =
|
||||
damage_label.color = Color(0.4, 0.65, 1.0) # Light blue
|
||||
else:
|
||||
damage_label.label = str(int(amount))
|
||||
damage_label.color = Color.ORANGE if is_crit else Color.RED
|
||||
damage_label.color = Color(1.0, 0.6, 0.2) if is_crit else Color(1.0, 0.35, 0.35) # Bright orange / bright red
|
||||
damage_label.z_index = 5
|
||||
|
||||
# Calculate direction from attacker (slight upward variation)
|
||||
var direction_from_attacker = (global_position - from_position).normalized()
|
||||
@@ -6462,6 +6522,32 @@ func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool =
|
||||
get_tree().current_scene.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
|
||||
func _show_revive_cost_number(amount: int):
|
||||
var damage_number_scene = preload("res://scenes/damage_number.tscn")
|
||||
if not damage_number_scene:
|
||||
return
|
||||
var damage_label = damage_number_scene.instantiate()
|
||||
if not damage_label:
|
||||
return
|
||||
damage_label.label = "-" + str(amount) + " HP"
|
||||
damage_label.color = Color(1.0, 0.35, 0.35)
|
||||
damage_label.z_index = 5
|
||||
damage_label.direction = Vector2(0, -1)
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var parent = game_world.get_node_or_null("Entities") if game_world else get_parent()
|
||||
if parent:
|
||||
parent.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
else:
|
||||
get_tree().current_scene.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_revive_cost(amount: int):
|
||||
if is_multiplayer_authority():
|
||||
return
|
||||
_show_revive_cost_number(amount)
|
||||
|
||||
func _on_level_up_stats(stats_increased: Array):
|
||||
# Show floating text for level up - "LEVEL UP!" and stat increases
|
||||
# Use damage_number scene with damage_numbers font
|
||||
@@ -6526,6 +6612,20 @@ func _on_level_up_stats(stats_increased: Array):
|
||||
stat_text.global_position = global_position + Vector2(0, base_y_offset)
|
||||
base_y_offset -= y_spacing
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_apply_corpse_knockback(dir_x: float, dir_y: float, force: float):
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
if not is_dead:
|
||||
return
|
||||
var d = Vector2(dir_x, dir_y)
|
||||
if d.length_squared() < 0.01:
|
||||
return
|
||||
d = d.normalized()
|
||||
velocity = d * force
|
||||
is_knocked_back = true
|
||||
knockback_time = 0.0
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_damage(_amount: float, attacker_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false, is_blocked: bool = false):
|
||||
# This RPC only syncs visual effects, not damage application
|
||||
|
||||
@@ -232,4 +232,14 @@ func _on_body_entered(body):
|
||||
# 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
|
||||
if body.is_in_group("player") and "is_dead" in body and body.is_dead:
|
||||
const CORPSE_KNOCKBACK: float = 50.0
|
||||
var pid = body.get_multiplayer_authority()
|
||||
if pid == multiplayer.get_unique_id():
|
||||
body.rpc_apply_corpse_knockback(knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
elif pid != 0:
|
||||
body.rpc_apply_corpse_knockback.rpc_id(pid, knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
else:
|
||||
body.rpc_apply_corpse_knockback.rpc(knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
else:
|
||||
body.velocity = knockback_dir * 200.0
|
||||
|
||||
@@ -207,4 +207,14 @@ func _on_body_entered(body):
|
||||
# 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
|
||||
if body.is_in_group("player") and "is_dead" in body and body.is_dead:
|
||||
const CORPSE_KNOCKBACK: float = 50.0
|
||||
var pid = body.get_multiplayer_authority()
|
||||
if pid == multiplayer.get_unique_id():
|
||||
body.rpc_apply_corpse_knockback(knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
elif pid != 0:
|
||||
body.rpc_apply_corpse_knockback.rpc_id(pid, knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
else:
|
||||
body.rpc_apply_corpse_knockback.rpc(knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
else:
|
||||
body.velocity = knockback_dir * 200.0
|
||||
|
||||
@@ -80,4 +80,15 @@ func _on_body_entered(body):
|
||||
# Push the hit target away slightly
|
||||
if body is CharacterBody2D:
|
||||
var knockback_dir = (body.global_position - global_position).normalized()
|
||||
body.velocity = knockback_dir * 200.0
|
||||
if body.is_in_group("player") and "is_dead" in body and body.is_dead:
|
||||
# Corpse: reduced force, sync to victim's client so they see the push
|
||||
const CORPSE_KNOCKBACK: float = 50.0
|
||||
var pid = body.get_multiplayer_authority()
|
||||
if pid == multiplayer.get_unique_id():
|
||||
body.rpc_apply_corpse_knockback(knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
elif pid != 0:
|
||||
body.rpc_apply_corpse_knockback.rpc_id(pid, knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
else:
|
||||
body.rpc_apply_corpse_knockback.rpc(knockback_dir.x, knockback_dir.y, CORPSE_KNOCKBACK)
|
||||
else:
|
||||
body.velocity = knockback_dir * 200.0
|
||||
|
||||
Reference in New Issue
Block a user