173 lines
5.2 KiB
GDScript
173 lines
5.2 KiB
GDScript
extends Area2D
|
|
|
|
# Web shot from boss: frame 568 in shade_spell_effects (105h x 79v). 8x8 collision.
|
|
# Flies to target; on hit player or arrival: animate 568->573.
|
|
# If arrived at point: stay 30s, fade out at end (net doesn't stick during fade).
|
|
# If hit player: player stuck 5s (frame 679 netted), fade last 0.3s; other player can cut net.
|
|
# Netted player cannot attack with main weapon.
|
|
|
|
var target_position: Vector2 = Vector2.ZERO
|
|
var speed: float = 140.0
|
|
var state: String = "flying" # flying | impact_anim | landed_net | hit_player
|
|
var impact_frame: int = 568
|
|
var impact_frame_end: int = 573
|
|
var landed_lifetime: float = 30.0
|
|
var landed_timer: float = 0.0
|
|
var fade_start: float = 4.0 # Start fade this many seconds before end
|
|
var fired_by_boss: Node = null
|
|
var hit_player: Node = null # When state == hit_player, the netted player
|
|
var netted_duration: float = 5.0
|
|
var netted_timer: float = 0.0
|
|
var netted_fade_start: float = 0.3
|
|
|
|
const TILE_SIZE: int = 16
|
|
const HFRAMES: int = 105
|
|
const VFRAMES: int = 79
|
|
|
|
@onready var sprite: Sprite2D = $Sprite2D
|
|
@onready var sfx_webbed: AudioStreamPlayer2D = $SfxWebbed
|
|
@onready var anim_timer: float = 0.0
|
|
var anim_frame: int = 568
|
|
|
|
|
|
func _ready() -> void:
|
|
# Collision: detect players (layer 1)
|
|
collision_layer = 0
|
|
collision_mask = 1
|
|
body_entered.connect(_on_body_entered)
|
|
anim_frame = impact_frame
|
|
if sprite:
|
|
sprite.hframes = HFRAMES
|
|
sprite.vframes = VFRAMES
|
|
sprite.frame = impact_frame
|
|
sprite.centered = true
|
|
|
|
|
|
func set_target(pos: Vector2) -> void:
|
|
target_position = pos
|
|
|
|
|
|
func set_fired_by_boss(boss: Node) -> void:
|
|
fired_by_boss = boss
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if state == "flying":
|
|
var dir = (target_position - global_position).normalized()
|
|
var dist = global_position.distance_to(target_position)
|
|
if dist <= speed * delta:
|
|
global_position = target_position
|
|
_impact_at_point()
|
|
else:
|
|
global_position += dir * speed * delta
|
|
return
|
|
if state == "impact_anim":
|
|
anim_timer += delta
|
|
var frame_count = impact_frame_end - impact_frame + 1
|
|
var duration = 0.25
|
|
var t = clamp(anim_timer / duration, 0.0, 1.0)
|
|
var idx = impact_frame + int(t * (frame_count - 1))
|
|
if sprite:
|
|
sprite.frame = idx
|
|
if anim_timer >= duration:
|
|
state = "landed_net"
|
|
landed_timer = 0.0
|
|
collision_layer = 1 # Can be hit by other players to cut?
|
|
collision_mask = 1 # Detect players (for cutting / fade stick logic)
|
|
return
|
|
if state == "landed_net":
|
|
landed_timer += delta
|
|
# Fade during last fade_start seconds
|
|
if landed_timer >= landed_lifetime - fade_start:
|
|
var fade_t = (landed_timer - (landed_lifetime - fade_start)) / fade_start
|
|
if sprite:
|
|
sprite.modulate.a = 1.0 - fade_t
|
|
# During fade, net doesn't stick - no collision with player for stick
|
|
if landed_timer >= landed_lifetime:
|
|
queue_free()
|
|
return
|
|
if state == "hit_player":
|
|
netted_timer += delta
|
|
# Keep net at player position
|
|
if hit_player and is_instance_valid(hit_player):
|
|
global_position = hit_player.global_position
|
|
# Fade last 0.3s
|
|
if netted_timer >= netted_duration - netted_fade_start:
|
|
var fade_t = (netted_timer - (netted_duration - netted_fade_start)) / netted_fade_start
|
|
if sprite:
|
|
sprite.modulate.a = 1.0 - fade_t
|
|
if netted_timer >= netted_duration:
|
|
_release_netted_player()
|
|
queue_free()
|
|
else:
|
|
queue_free()
|
|
|
|
|
|
func _on_body_entered(body: Node2D) -> void:
|
|
if state == "flying":
|
|
if body.is_in_group("player"):
|
|
_impact_on_player(body)
|
|
return
|
|
# Reached target (or hit wall) - treat as impact at point
|
|
if global_position.distance_to(target_position) < 24.0:
|
|
_impact_at_point()
|
|
return
|
|
if state == "landed_net" and body.is_in_group("player"):
|
|
_impact_on_player(body)
|
|
|
|
|
|
func _impact_at_point() -> void:
|
|
state = "impact_anim"
|
|
anim_timer = 0.0
|
|
# Stop moving; play 568->573
|
|
collision_layer = 0
|
|
collision_mask = 0
|
|
# After anim, stay 30s then fade (handled in _process)
|
|
|
|
|
|
func _impact_on_player(player: Node2D) -> void:
|
|
if not player.is_in_group("player"):
|
|
return
|
|
# Already netted?
|
|
if "netted_by_web" in player and player.netted_by_web != null:
|
|
return
|
|
state = "hit_player"
|
|
hit_player = player
|
|
netted_timer = 0.0
|
|
if sfx_webbed:
|
|
sfx_webbed.play()
|
|
# Netted: cannot move? User said "player gets stuck" - so disable movement and main weapon
|
|
if player.has_method("_web_net_apply"):
|
|
player._web_net_apply(self)
|
|
else:
|
|
# Fallback: set a flag player script can check
|
|
player.set_meta("netted_by_web", self)
|
|
# Show net on player (frame 679) - player script will show overlay
|
|
if player.has_method("_web_net_show_netted_frame"):
|
|
player._web_net_show_netted_frame(true)
|
|
# Impact anim 568->573 briefly
|
|
anim_timer = 0.0
|
|
if sprite:
|
|
sprite.frame = impact_frame
|
|
# Layer 8 so player attack areas (punch/sword/axe mask 8) can hit and cut the net
|
|
collision_layer = 8
|
|
collision_mask = 0
|
|
|
|
|
|
func _release_netted_player() -> void:
|
|
if hit_player and is_instance_valid(hit_player):
|
|
if hit_player.has_method("_web_net_release"):
|
|
hit_player._web_net_release(self)
|
|
else:
|
|
hit_player.remove_meta("netted_by_web")
|
|
if hit_player.has_method("_web_net_show_netted_frame"):
|
|
hit_player._web_net_show_netted_frame(false)
|
|
hit_player = null
|
|
|
|
|
|
# Called when another player attacks this web (to cut the net)
|
|
func cut_by_attack(_from_player: Node2D) -> void:
|
|
if state == "hit_player":
|
|
_release_netted_player()
|
|
queue_free()
|