fix spider bat boss alittle
This commit is contained in:
@@ -72,6 +72,10 @@ var is_knocked_back: bool = false
|
||||
var knockback_time: float = 0.0
|
||||
var knockback_duration: float = 0.3 # How long knockback lasts
|
||||
|
||||
# Web net (boss): when netted_by_web != null, player is stuck and cannot attack with main weapon
|
||||
var netted_by_web: Node = null
|
||||
var netted_overlay_sprite: Sprite2D = null # Frame 679 for netted visual
|
||||
|
||||
# Attack/Combat
|
||||
var can_attack: bool = true
|
||||
var attack_cooldown: float = 0.0 # No cooldown - instant attacks!
|
||||
@@ -182,6 +186,7 @@ const HELD_POSITION_Z: float = 12.0 # Z height when held/lifted (above ground; i
|
||||
@onready var sfx_die = $SfxDie
|
||||
@onready var sfx_look_out = $SfxLookOut
|
||||
@onready var sfx_ahaa = $SfxAhaa
|
||||
@onready var sfx_secret_found = $SfxSecretFound
|
||||
|
||||
# Alert indicator (exclamation mark)
|
||||
var alert_indicator: Sprite2D = null
|
||||
@@ -1493,32 +1498,23 @@ func _can_place_down_at(place_pos: Vector2, placed_obj: Node) -> bool:
|
||||
placed_shape_transform = Transform2D(0.0, place_pos)
|
||||
|
||||
# Check if the placed object's collision shape would collide with anything
|
||||
# This includes: walls, other objects, and players
|
||||
# This includes: walls, other objects, and players (not Area2D triggers - those don't block placement)
|
||||
var params = PhysicsShapeQueryParameters2D.new()
|
||||
params.shape = placed_shape
|
||||
params.transform = placed_shape_transform
|
||||
params.collision_mask = 1 | 2 | 64 # Players (layer 1), objects (layer 2), walls (layer 7 = bit 6 = 64)
|
||||
params.collide_with_areas = false # Only solid bodies block; ignore trigger areas (e.g. door key zones)
|
||||
params.collide_with_bodies = true
|
||||
|
||||
# CRITICAL: Exclude self, the object being placed, and make sure to exclude it properly
|
||||
# The object might still be in the scene tree with collision disabled, so we need to exclude it
|
||||
var exclude_list = [self]
|
||||
if placed_obj and is_instance_valid(placed_obj):
|
||||
exclude_list.append(placed_obj)
|
||||
# CRITICAL: Exclude using RIDs so the physics engine actually excludes them (Node refs may not work)
|
||||
var exclude_list: Array[RID] = [get_rid()]
|
||||
if placed_obj and is_instance_valid(placed_obj) and placed_obj is CollisionObject2D:
|
||||
exclude_list.append(placed_obj.get_rid())
|
||||
params.exclude = exclude_list
|
||||
|
||||
# Test the actual collision shape at the placement position
|
||||
var hits = space_state.intersect_shape(params, 32) # Check up to 32 collisions
|
||||
|
||||
# Debug: Log what we found
|
||||
if hits.size() > 0:
|
||||
print("DEBUG: Placement blocked - found ", hits.size(), " collisions at ", place_pos)
|
||||
for i in min(hits.size(), 3): # Log first 3 collisions
|
||||
var hit = hits[i]
|
||||
if hit.has("collider"):
|
||||
print(" - Collision with: ", hit.collider, " (", hit.collider.name if hit.collider else "null", ")")
|
||||
if hit.has("rid"):
|
||||
print(" - RID: ", hit.rid)
|
||||
|
||||
# If any collisions found, placement is invalid
|
||||
return hits.size() == 0
|
||||
|
||||
@@ -1964,6 +1960,8 @@ func _physics_process(delta):
|
||||
const MANA_REGEN_RATE = 2.0 # mana per second
|
||||
if character_stats.mp < character_stats.maxmp:
|
||||
character_stats.restore_mana(MANA_REGEN_RATE * delta)
|
||||
# Tick down temporary buffs (e.g. dodge potion)
|
||||
character_stats.tick_buffs(delta)
|
||||
|
||||
# Spell charge visuals (incantation, tint pulse) - run for ALL players so others see the pulse
|
||||
if is_charging_spell:
|
||||
@@ -2496,8 +2494,8 @@ func _handle_input():
|
||||
if new_direction != current_direction:
|
||||
current_direction = new_direction
|
||||
_update_cone_light_rotation()
|
||||
elif is_pushing:
|
||||
# Keep locked direction when pushing
|
||||
elif is_pushing or (held_object and not is_lifting):
|
||||
# Keep direction from when grab started (don't turn to face the object)
|
||||
if push_direction_locked != current_direction:
|
||||
current_direction = push_direction_locked as Direction
|
||||
_update_cone_light_rotation()
|
||||
@@ -2539,9 +2537,10 @@ func _handle_input():
|
||||
elif is_lifting:
|
||||
if current_animation != "LIFT" and current_animation != "IDLE_HOLD":
|
||||
_set_animation("IDLE_HOLD")
|
||||
elif is_pushing:
|
||||
_set_animation("IDLE_PUSH")
|
||||
# Keep locked direction when pushing
|
||||
elif is_pushing or (held_object and not is_lifting):
|
||||
if is_pushing:
|
||||
_set_animation("IDLE_PUSH")
|
||||
# Keep direction from when grab started
|
||||
if push_direction_locked != current_direction:
|
||||
current_direction = push_direction_locked as Direction
|
||||
_update_cone_light_rotation()
|
||||
@@ -2598,8 +2597,8 @@ func _handle_input():
|
||||
if character_stats and character_stats.is_over_encumbered():
|
||||
current_speed = base_speed * 0.25
|
||||
|
||||
# Lock movement if movement_lock_timer is active or reviving a corpse
|
||||
if movement_lock_timer > 0.0 or is_reviving:
|
||||
# Lock movement if movement_lock_timer is active, reviving a corpse, or netted by web
|
||||
if movement_lock_timer > 0.0 or is_reviving or netted_by_web:
|
||||
velocity = Vector2.ZERO
|
||||
else:
|
||||
velocity = input_vector * current_speed
|
||||
@@ -2623,6 +2622,32 @@ func _handle_walking_sfx():
|
||||
if sfx_walk and sfx_walk.playing:
|
||||
sfx_walk.stop()
|
||||
|
||||
func _web_net_apply(web_node: Node) -> void:
|
||||
netted_by_web = web_node
|
||||
|
||||
func _web_net_release(_web_node: Node) -> void:
|
||||
netted_by_web = null
|
||||
_web_net_show_netted_frame(false)
|
||||
|
||||
func _web_net_show_netted_frame(show_net: bool) -> void:
|
||||
if show_net:
|
||||
if netted_overlay_sprite == null:
|
||||
netted_overlay_sprite = Sprite2D.new()
|
||||
var tex = load("res://assets/gfx/fx/shade_spell_effects.png") as Texture2D
|
||||
if tex:
|
||||
netted_overlay_sprite.texture = tex
|
||||
netted_overlay_sprite.hframes = 105
|
||||
netted_overlay_sprite.vframes = 79
|
||||
netted_overlay_sprite.frame = 679
|
||||
netted_overlay_sprite.centered = true
|
||||
netted_overlay_sprite.z_index = 5
|
||||
add_child(netted_overlay_sprite)
|
||||
if netted_overlay_sprite:
|
||||
netted_overlay_sprite.visible = true
|
||||
else:
|
||||
if netted_overlay_sprite:
|
||||
netted_overlay_sprite.visible = false
|
||||
|
||||
func _handle_interactions():
|
||||
var grab_button_down = false
|
||||
var grab_just_pressed = false
|
||||
@@ -3192,7 +3217,7 @@ func _handle_interactions():
|
||||
|
||||
# Handle bow charging
|
||||
if has_bow_and_arrows and not is_lifting and not is_pushing:
|
||||
if attack_just_pressed and can_attack and not is_charging_bow and not spawn_landing:
|
||||
if attack_just_pressed and can_attack and not is_charging_bow and not spawn_landing and not netted_by_web:
|
||||
if !$SfxBuckleBow.playing:
|
||||
$SfxBuckleBow.play()
|
||||
# Start charging bow
|
||||
@@ -3260,7 +3285,7 @@ func _handle_interactions():
|
||||
|
||||
# Normal attack (non-bow or no arrows)
|
||||
# Also allow throwing when lifting (even if bow is equipped). Block during spawn fall.
|
||||
if attack_just_pressed and can_attack and not spawn_landing:
|
||||
if attack_just_pressed and can_attack and not spawn_landing and not netted_by_web:
|
||||
if is_lifting:
|
||||
# Attack while lifting -> throw immediately in facing direction
|
||||
_force_throw_held_object(facing_direction_vector)
|
||||
@@ -3382,11 +3407,12 @@ func _try_grab():
|
||||
# Store the distance from player to object when grabbed (for placement)
|
||||
grab_distance = global_position.distance_to(closest_body.global_position)
|
||||
|
||||
# Calculate push axis from grab direction (but don't move the object yet)
|
||||
var grab_direction = grab_offset.normalized()
|
||||
# Use player's current facing when grab started (do not turn to face the object)
|
||||
var grab_direction = facing_direction_vector.normalized() if facing_direction_vector.length() > 0.1 else last_movement_direction.normalized()
|
||||
if grab_direction.length() < 0.1:
|
||||
grab_direction = last_movement_direction
|
||||
grab_direction = Vector2.DOWN
|
||||
push_axis = _snap_to_8_directions(grab_direction)
|
||||
push_direction_locked = _get_direction_from_vector(push_axis) as Direction
|
||||
|
||||
# Disable collision with players and other objects when grabbing
|
||||
# But keep collision with walls (layer 7) enabled for pushing
|
||||
@@ -3503,15 +3529,11 @@ func _start_pushing():
|
||||
is_pushing = true
|
||||
is_lifting = false
|
||||
|
||||
# Lock to the direction we're facing when we start pushing
|
||||
var initial_direction = grab_offset.normalized()
|
||||
# Keep the direction we had when we started the grab (do not face the object)
|
||||
var initial_direction = facing_direction_vector.normalized() if facing_direction_vector.length() > 0.1 else last_movement_direction.normalized()
|
||||
if initial_direction.length() < 0.1:
|
||||
initial_direction = last_movement_direction.normalized()
|
||||
|
||||
# Snap to one of 8 directions
|
||||
initial_direction = Vector2.DOWN
|
||||
push_axis = _snap_to_8_directions(initial_direction)
|
||||
|
||||
# Lock the facing direction (for both animation and attacks)
|
||||
push_direction_locked = _get_direction_from_vector(push_axis)
|
||||
facing_direction_vector = push_axis.normalized()
|
||||
|
||||
@@ -4045,7 +4067,7 @@ func _place_down_object():
|
||||
print("Placed down ", placed_obj.name, " at ", place_pos)
|
||||
|
||||
func _perform_attack():
|
||||
if not can_attack or is_attacking or spawn_landing:
|
||||
if not can_attack or is_attacking or spawn_landing or netted_by_web:
|
||||
return
|
||||
|
||||
can_attack = false
|
||||
@@ -6387,6 +6409,13 @@ func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool
|
||||
# Invulnerable during fallout sink (can't take damage from anything while falling)
|
||||
if fallout_state:
|
||||
return
|
||||
# Taking damage while webbed immediately frees you from the web
|
||||
if netted_by_web:
|
||||
var web = netted_by_web
|
||||
netted_by_web = null
|
||||
_web_net_show_netted_frame(false)
|
||||
if web and is_instance_valid(web) and web.has_method("cut_by_attack"):
|
||||
web.cut_by_attack(null)
|
||||
|
||||
# Cancel bow charging when taking damage
|
||||
if is_charging_bow:
|
||||
@@ -6552,10 +6581,9 @@ func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_damage", [actual_damage, attacker_position])
|
||||
|
||||
# Check if dead - but wait for damage animation to play first
|
||||
# Use small epsilon to handle floating point precision issues (HP might be 0.0000001 instead of exactly 0.0)
|
||||
# Check if dead - below 1 HP must always trigger death (trap, etc.)
|
||||
var health = character_stats.hp if character_stats else current_health
|
||||
if health <= 0.001: # Use epsilon to catch values very close to 0
|
||||
if health < 1.0:
|
||||
if character_stats:
|
||||
character_stats.hp = 0.0 # Clamp to exactly 0
|
||||
else:
|
||||
@@ -6563,7 +6591,8 @@ func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool
|
||||
is_dead = true # Set flag immediately to prevent more damage
|
||||
# Wait a bit for damage animation and knockback to show
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
_die()
|
||||
if is_instance_valid(self) and is_dead:
|
||||
_die()
|
||||
|
||||
func _die():
|
||||
# Already processing death - prevent multiple concurrent death sequences
|
||||
@@ -7575,6 +7604,10 @@ func _on_level_up_stats(stats_increased: Array):
|
||||
if not character_stats:
|
||||
return
|
||||
|
||||
# Play level-up fanfare locally only when this player (you) gained the level
|
||||
if is_multiplayer_authority() and has_node("SfxLevelUp"):
|
||||
$SfxLevelUp.play()
|
||||
|
||||
# Stat name to display name mapping
|
||||
var stat_display_names = {
|
||||
"str": "STR",
|
||||
@@ -7651,6 +7684,17 @@ func rpc_apply_corpse_knockback(dir_x: float, dir_y: float, force: float):
|
||||
is_knocked_back = true
|
||||
knockback_time = 0.0
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _on_attack_blocked_by_enemy(blocker_position: Vector2):
|
||||
# Called when this player's attack was blocked by an enemy (e.g. humanoid with shield). Show BLOCKED and small knockback.
|
||||
var dir_away = (global_position - blocker_position).normalized()
|
||||
if dir_away.length() < 0.01:
|
||||
dir_away = Vector2.RIGHT
|
||||
velocity = dir_away * 75.0
|
||||
is_knocked_back = true
|
||||
knockback_time = 0.0
|
||||
_show_damage_number(0.0, blocker_position, false, false, false, true)
|
||||
|
||||
@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
|
||||
@@ -7796,6 +7840,26 @@ func _sync_trap_detected_alert():
|
||||
if sfx_look_out:
|
||||
sfx_look_out.play()
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _on_cracked_floor_detected():
|
||||
# Called when this player detects a cracked floor (perception roll success). Only the detecting player plays SfxLookOut and sees alert.
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
_show_alert_indicator()
|
||||
if sfx_look_out:
|
||||
sfx_look_out.play()
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _on_secret_chest_detected():
|
||||
# Called when this player detects a hidden chest (perception roll success). Only the detecting player plays SfxAhaa + SfxSecretFound and sees alert.
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
_show_alert_indicator()
|
||||
if sfx_ahaa:
|
||||
sfx_ahaa.play()
|
||||
if sfx_secret_found:
|
||||
sfx_secret_found.play()
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_exit_found_alert():
|
||||
# Sync exit found alert to all clients
|
||||
|
||||
Reference in New Issue
Block a user