fixat mer med traps och arrows och grejjer
This commit is contained in:
321
src/scripts/trap.gd
Normal file
321
src/scripts/trap.gd
Normal file
@@ -0,0 +1,321 @@
|
||||
extends Node2D
|
||||
|
||||
@onready var sprite = $Sprite2D
|
||||
@onready var animation_player = $AnimationPlayer
|
||||
@onready var activation_area = $ActivationArea
|
||||
@onready var disarm_area = $DisarmArea
|
||||
@onready var detection_area = $DetectionArea
|
||||
|
||||
# Trap state
|
||||
var is_detected: bool = false # Becomes true when any player detects it
|
||||
var is_disarmed: bool = false # True if trap has been disarmed
|
||||
var is_active: bool = false # True when trap is currently triggering
|
||||
var has_cooldown: bool = false # Some traps can reset
|
||||
var cooldown_time: float = 5.0 # Time until trap can re-activate
|
||||
var cooldown_timer: float = 0.0
|
||||
|
||||
# Trap properties
|
||||
var trap_damage: float = 15.0
|
||||
var trap_type: String = "Floor_Lance"
|
||||
|
||||
# Per-player detection tracking (Dictionary: peer_id -> has_attempted_detection)
|
||||
var player_detection_attempts: Dictionary = {}
|
||||
|
||||
# Disarm tracking
|
||||
var disarming_player: Node = null
|
||||
var disarm_progress: float = 0.0
|
||||
var disarm_duration: float = 1.0
|
||||
var disarm_label: Label = null
|
||||
|
||||
func _ready() -> void:
|
||||
# Add to trap group for detection by players
|
||||
add_to_group("trap")
|
||||
|
||||
# Randomize trap visual based on dungeon seed
|
||||
var highbox_seed = 0
|
||||
var world = get_tree().get_first_node_in_group("game_world")
|
||||
if world and "dungeon_seed" in world:
|
||||
highbox_seed = world.dungeon_seed
|
||||
highbox_seed += int(global_position.x) * 1000 + int(global_position.y)
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = highbox_seed
|
||||
var index = rng.randi() % 2
|
||||
if index == 0:
|
||||
sprite.texture = load("res://assets/gfx/traps/Floor_Lance.png")
|
||||
trap_type = "Floor_Lance"
|
||||
has_cooldown = true # Lance traps can reset
|
||||
|
||||
# Start hidden (invisible until detected)
|
||||
sprite.modulate.a = 0.0
|
||||
|
||||
# Setup detection area to check for players
|
||||
if detection_area:
|
||||
detection_area.body_entered.connect(_on_detection_area_body_entered)
|
||||
detection_area.body_exited.connect(_on_detection_area_body_exited)
|
||||
|
||||
# Setup disarm area to show "DISARM" text for dwarves
|
||||
if disarm_area:
|
||||
disarm_area.body_entered.connect(_on_disarm_area_body_entered)
|
||||
disarm_area.body_exited.connect(_on_disarm_area_body_exited)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# Handle cooldown timer for resetting traps
|
||||
if has_cooldown and not is_active and cooldown_timer > 0:
|
||||
cooldown_timer -= delta
|
||||
if cooldown_timer <= 0:
|
||||
# Trap has cooled down - ready to trigger again
|
||||
pass
|
||||
|
||||
# Handle disarm progress
|
||||
if disarming_player and is_instance_valid(disarming_player):
|
||||
# Check if player is still holding grab button
|
||||
if disarming_player.is_multiplayer_authority() and Input.is_action_pressed("grab"):
|
||||
# Play disarming sound (only if not already playing)
|
||||
if $SfxDisarming.playing == false:
|
||||
$SfxDisarming.play()
|
||||
|
||||
disarm_progress += delta
|
||||
_update_disarm_ui()
|
||||
|
||||
if disarm_progress >= disarm_duration:
|
||||
# Disarm complete!
|
||||
_complete_disarm()
|
||||
else:
|
||||
# Player released grab - cancel disarm
|
||||
_cancel_disarm()
|
||||
|
||||
func _on_detection_area_body_entered(body: Node) -> void:
|
||||
# When a player enters detection range, roll perception check (once per player per game)
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
|
||||
if is_detected or is_disarmed:
|
||||
return # Already detected or disarmed
|
||||
|
||||
# Get player peer ID
|
||||
var peer_id = body.get_multiplayer_authority()
|
||||
|
||||
# Check if this player has already attempted detection
|
||||
if player_detection_attempts.has(peer_id):
|
||||
return # Already tried once this game
|
||||
|
||||
# Mark that this player has attempted detection
|
||||
player_detection_attempts[peer_id] = true
|
||||
|
||||
# Roll perception check (only on server/authority)
|
||||
if multiplayer.is_server() or not multiplayer.has_multiplayer_peer():
|
||||
_roll_perception_check(body)
|
||||
|
||||
func _on_detection_area_body_exited(_body: Node) -> void:
|
||||
pass # Detection is permanent once attempted
|
||||
|
||||
func _roll_perception_check(player: Node) -> void:
|
||||
# Roll perception check for player
|
||||
if not player or not player.character_stats:
|
||||
return
|
||||
|
||||
var per_stat = player.character_stats.baseStats.per + player.character_stats.get_pass("per")
|
||||
|
||||
# Perception roll: d20 + PER modifier
|
||||
# Target DC: 15 (medium difficulty)
|
||||
var roll = randi() % 20 + 1 # 1d20
|
||||
var total = roll + int(per_stat / 2) - 5 # PER modifier: (PER - 10) / 2
|
||||
var dc = 15
|
||||
|
||||
print(player.name, " rolls Perception: ", roll, " + ", int(per_stat / 2) - 5, " = ", total, " vs DC ", dc)
|
||||
|
||||
if total >= dc:
|
||||
# Success! Player detects the trap
|
||||
_detect_trap(player)
|
||||
else:
|
||||
# Failure - trap remains hidden to this player
|
||||
print(player.name, " failed to detect trap")
|
||||
|
||||
func _detect_trap(detecting_player: Node) -> void:
|
||||
# Trap is detected - make it visible to ALL players
|
||||
is_detected = true
|
||||
|
||||
# Make trap visible
|
||||
sprite.modulate.a = 1.0
|
||||
|
||||
# Sync detection to all clients (including server with call_local)
|
||||
if multiplayer.has_multiplayer_peer() and is_inside_tree():
|
||||
if multiplayer.is_server():
|
||||
_sync_trap_detected.rpc()
|
||||
|
||||
print(detecting_player.name, " detected trap at ", global_position)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _sync_trap_detected() -> void:
|
||||
# Client receives trap detection notification
|
||||
is_detected = true
|
||||
sprite.modulate.a = 1.0
|
||||
|
||||
func _on_disarm_area_body_entered(body: Node) -> void:
|
||||
# Show "DISARM" text if player is Dwarf and trap is detected
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
|
||||
if not is_detected or is_disarmed:
|
||||
return
|
||||
|
||||
# Check if player is Dwarf
|
||||
if body.character_stats and body.character_stats.race == "Dwarf":
|
||||
_show_disarm_text(body)
|
||||
|
||||
func _on_disarm_area_body_exited(body: Node) -> void:
|
||||
# Hide disarm text when player leaves area
|
||||
if body == disarming_player:
|
||||
_cancel_disarm()
|
||||
_hide_disarm_text(body)
|
||||
|
||||
func _show_disarm_text(_player: Node) -> void:
|
||||
# Create "DISARM" label above trap
|
||||
if disarm_label:
|
||||
return # Already showing
|
||||
|
||||
disarm_label = Label.new()
|
||||
disarm_label.text = "DISARM"
|
||||
disarm_label.add_theme_font_size_override("font_size", 16)
|
||||
disarm_label.add_theme_color_override("font_color", Color.YELLOW)
|
||||
disarm_label.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
disarm_label.add_theme_constant_override("outline_size", 2)
|
||||
disarm_label.position = Vector2(-25, -30)
|
||||
disarm_label.z_index = 100
|
||||
add_child(disarm_label)
|
||||
|
||||
func _hide_disarm_text(_player: Node) -> void:
|
||||
if disarm_label:
|
||||
disarm_label.queue_free()
|
||||
disarm_label = null
|
||||
|
||||
func _update_disarm_ui() -> void:
|
||||
# Update disarm progress (could show radial timer here)
|
||||
if disarm_label:
|
||||
var progress_percent = int((disarm_progress / disarm_duration) * 100)
|
||||
disarm_label.text = "DISARM (" + str(progress_percent) + "%)"
|
||||
|
||||
func _cancel_disarm() -> void:
|
||||
disarming_player = null
|
||||
disarm_progress = 0.0
|
||||
# Stop disarming sound
|
||||
if $SfxDisarming.playing:
|
||||
$SfxDisarming.stop()
|
||||
if disarm_label:
|
||||
disarm_label.text = "DISARM"
|
||||
|
||||
func _complete_disarm() -> void:
|
||||
# Trap successfully disarmed!
|
||||
is_disarmed = true
|
||||
disarming_player = null
|
||||
disarm_progress = 0.0
|
||||
|
||||
# Stop disarming sound
|
||||
if $SfxDisarming.playing:
|
||||
$SfxDisarming.stop()
|
||||
|
||||
# Hide disarm text
|
||||
_hide_disarm_text(null)
|
||||
|
||||
# Disable activation area
|
||||
if activation_area:
|
||||
activation_area.monitoring = false
|
||||
|
||||
# Show "TRAP DISARMED" in chat
|
||||
var chat_ui = get_tree().get_first_node_in_group("chat_ui")
|
||||
if chat_ui and chat_ui.has_method("send_system_message"):
|
||||
chat_ui.send_system_message("Trap disarmed by Dwarf!")
|
||||
|
||||
# Show floating text "TRAP DISARMED"
|
||||
_show_floating_text("TRAP DISARMED", Color.YELLOW)
|
||||
|
||||
# Change trap visual to show it's disarmed (optional - could fade out or change color)
|
||||
sprite.modulate = Color(0.5, 0.5, 0.5, 0.5)
|
||||
|
||||
# Sync disarm to all clients
|
||||
if multiplayer.has_multiplayer_peer() and is_inside_tree():
|
||||
if multiplayer.is_server():
|
||||
_sync_trap_disarmed.rpc()
|
||||
|
||||
print("Trap disarmed!")
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _sync_trap_disarmed() -> void:
|
||||
# Client receives trap disarm notification
|
||||
is_disarmed = true
|
||||
sprite.modulate = Color(0.5, 0.5, 0.5, 0.5)
|
||||
if activation_area:
|
||||
activation_area.monitoring = false
|
||||
|
||||
func _show_floating_text(text: String, color: Color) -> void:
|
||||
var floating_text_scene = preload("res://scenes/floating_text.tscn")
|
||||
if floating_text_scene:
|
||||
var floating_text = floating_text_scene.instantiate()
|
||||
var parent = get_parent()
|
||||
if parent:
|
||||
parent.add_child(floating_text)
|
||||
floating_text.global_position = Vector2(global_position.x, global_position.y - 20)
|
||||
floating_text.setup(text, color, 0.5, 0.5, null, 1, 1, 0)
|
||||
|
||||
func _on_activation_area_body_shape_entered(_body_rid: RID, body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
|
||||
# Trap triggered!
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
|
||||
if is_disarmed or is_active:
|
||||
return # Can't trigger if disarmed or already active
|
||||
|
||||
if has_cooldown and cooldown_timer > 0:
|
||||
return # Still on cooldown
|
||||
|
||||
# Trigger trap
|
||||
is_active = true
|
||||
$SfxActivate.play()
|
||||
animation_player.play("activate")
|
||||
|
||||
# Trap is now visible to all players (once triggered)
|
||||
if not is_detected:
|
||||
is_detected = true
|
||||
sprite.modulate.a = 1.0
|
||||
if multiplayer.has_multiplayer_peer() and is_inside_tree():
|
||||
if multiplayer.is_server():
|
||||
_sync_trap_detected.rpc()
|
||||
|
||||
# Deal damage to player (with luck-based avoidance)
|
||||
_deal_trap_damage(body)
|
||||
|
||||
# Start cooldown if applicable
|
||||
if has_cooldown:
|
||||
cooldown_timer = cooldown_time
|
||||
await animation_player.animation_finished
|
||||
animation_player.play("reset")
|
||||
await animation_player.animation_finished
|
||||
is_active = false
|
||||
else:
|
||||
# One-time trap - stays triggered
|
||||
pass
|
||||
|
||||
func _deal_trap_damage(player: Node) -> void:
|
||||
if not player or not player.character_stats:
|
||||
return
|
||||
|
||||
# Luck-based avoidance check
|
||||
var luck_stat = player.character_stats.baseStats.lck + player.character_stats.get_pass("lck")
|
||||
var avoid_chance = luck_stat * 0.02 # 2% per luck point (e.g., 10 luck = 20% avoid)
|
||||
var avoid_roll = randf()
|
||||
|
||||
if avoid_roll < avoid_chance:
|
||||
$SfxAvoid.play()
|
||||
# Player avoided trap damage!
|
||||
print(player.name, " avoided trap damage! (", avoid_chance * 100, "% chance)")
|
||||
_show_floating_text("AVOIDED", Color.GREEN)
|
||||
return
|
||||
|
||||
# Apply trap damage (affected by player's defense)
|
||||
var final_damage = player.character_stats.calculate_damage(trap_damage, false, false)
|
||||
|
||||
if player.has_method("rpc_take_damage"):
|
||||
player.rpc_take_damage(trap_damage, global_position)
|
||||
|
||||
print(player.name, " took ", final_damage, " trap damage")
|
||||
Reference in New Issue
Block a user