class_name Player extends CharacterBody3D const trap_template = preload("res://templates/trap.tscn") const remove_trap_modal = preload("res://templates/remove_trap_modal.tscn") const disarm_trap_modal = preload("res://templates/disarm_trap_modal.tscn") @export var speed : float = 10 @onready var body = $Body @onready var data : PlayerData = $Data @onready var trap_sound : AudioStreamPlayer3D = $TrapSound var button_actions : Dictionary[int, String] var current_square : Vector3i var detecting : bool = false var detect_squares : Dictionary[Vector3i, bool] = {} var input_locked : bool = false var action_tween : Tween = null var modal = null signal trap_cycled(trap_index) signal trap_quantity_changed(trap_index, quantity) signal trap_list_changed(traps) func _physics_process(delta: float) -> void: var dir = Input.get_vector("west", "east", "north", "south") dir = Vector3(dir.x, 0, dir.y) if input_locked or modal != null: dir = Vector3.ZERO if dir.length_squared() > 0: body.look_at(body.global_position - dir) velocity = speed * dir if detecting: velocity /= 3 if !is_on_floor(): velocity += get_gravity() move_and_slide() elif !is_on_floor(): velocity = get_gravity() move_and_slide() if detecting: update_detecting() if !input_locked: if modal: var evt = InputEventAction.new() var buttons = [ "lay trap", "detect", "attack", "detonate" ] for button in buttons: if Input.is_action_just_pressed(button): evt.action = button evt.pressed = true modal.button_pressed(evt) else: if Input.is_action_just_pressed("left cycle trap"): cycle_active_trap(-1) if Input.is_action_just_pressed("right cycle trap"): cycle_active_trap(1) if Input.is_action_just_pressed("detect"): start_detecting() elif Input.is_action_just_released("detect"): stop_detecting() if !detecting and Input.is_action_just_pressed("lay trap"): try_lay_trap() func try_lay_trap() -> void: if !is_on_floor(): return if data.traps[data.active_trap].quantity < 1: return var square : Vector3i = (global_position - Vector3.ONE * .5).round() if !Game.level.is_valid_trap_square(square): return action_tween = create_tween() input_locked = true action_tween.tween_interval(.2) action_tween.tween_callback(Callable(lay_trap).bind(square, data.active_trap)) action_tween.tween_interval(.25) action_tween.tween_callback(clear_action) func lay_trap(square : Vector3i, idx : int) -> void: var type : Trap.Type = data.traps[idx].type var trap = trap_template.instantiate() trap.setup(type, Multiplayer.id) trap.disarmed.connect(_on_trap_disarmed) data.traps[idx].quantity -= 1 trap_quantity_changed.emit(idx, data.traps[idx].quantity) Game.level.add_trap(trap, square) trap_sound.play() func clear_action() -> void: input_locked = false action_tween = null func update_detecting() -> void: var new_square : Vector3i = (global_position - Vector3.ONE * .5).round() if new_square == current_square: return current_square = new_square var new_squares : Dictionary[Vector3i, bool] = {} for i in range(-2, 3): for j in range(-2, 3): for k in range(-2, 2): if abs(i) + abs(j) < 3: var sq = current_square + Vector3i(i, k, j) new_squares[sq] = true for sq in detect_squares.keys(): if !new_squares.has(sq): Game.level.detect_square(sq, false) var remove_list = [] for sq in new_squares.keys(): if detect_squares.has(sq): continue if !Game.level.detect_square(sq, true): remove_list.append(sq) detect_squares = new_squares for key in remove_list: detect_squares.erase(key) var trap : Trap = Game.level.get_square_trap(current_square) if trap != null: if trap.trap_owner == Multiplayer.id: show_remove_trap_modal() else: show_disarm_trap_modal() func close_modal() -> void: modal.queue_free() modal = null func show_remove_trap_modal() -> void: stop_detecting() modal = remove_trap_modal.instantiate() modal.square = current_square Game.level.add_child(modal) func show_disarm_trap_modal() -> void: stop_detecting() modal = disarm_trap_modal.instantiate() modal.difficulty = Game.level.difficulty modal.square = current_square Game.level.add_child(modal) func start_detecting() -> void: detecting = true current_square = (global_position - Vector3.ONE * .5).round() detect_squares = {} for i in range(-2, 3): for j in range(-2, 3): for k in range(-2, 2): if abs(i) + abs(j) < 3: var sq = current_square + Vector3i(i, k, j) detect_squares[sq] = true var remove_list = [] for sq in detect_squares.keys(): if !Game.level.detect_square(sq, true): remove_list.append(sq) for key in remove_list: detect_squares.erase(key) var trap : Trap = Game.level.get_square_trap(current_square) if trap != null: if trap.trap_owner == Multiplayer.id: show_remove_trap_modal() func stop_detecting() -> void: detecting = false for sq in detect_squares.keys(): Game.level.detect_square(sq, false) detect_squares = {} func setup(traps) -> void: $Data.traps = traps Game.setup_player(self) func remove_trap_at(square) -> void: var trap : Trap = Game.level.traps[square] for i in range(len(data.traps)): var d = data.traps[i] if d.type == trap.type: d.quantity += 1 trap_quantity_changed.emit(i, d.quantity) break trap.queue_free() Game.level.traps.erase(square) func cycle_active_trap(dir) -> void: var prev = data.active_trap data.active_trap += dir if data.active_trap < 0: data.active_trap = 0 if data.active_trap >= len(data.traps): data.active_trap = len(data.traps) - 1 if prev != data.active_trap: trap_cycled.emit(data.active_trap) func _on_trap_disarmed(type : Trap.Type) -> void: for i in range(len(data.traps)): var d = data.traps[i] if d.type == type: d.max -= 1 trap_quantity_changed.emit(i, d.quantity) break