From 55eb37ca74b67b590ff82643b3ee6363cdf86046 Mon Sep 17 00:00:00 2001 From: Bo Thompson Date: Thu, 25 Dec 2025 17:52:30 -0500 Subject: [PATCH] UI work, placing traps, removing traps, disarming traps, new sound fx. --- project.godot | 15 ++ scenes/test_level.tscn | 32 +---- scripts/autos/game.gd | 18 +-- scripts/autos/multiplayer.gd | 2 +- scripts/disarm_trap_modal.gd | 103 ++++++++++++++ scripts/disarm_trap_modal.gd.uid | 1 + scripts/one_shot.gd | 8 ++ scripts/one_shot.gd.uid | 1 + scripts/player.gd | 133 ++++++++++++++++-- scripts/player_data.gd | 4 +- scripts/remove_trap_modal.gd | 12 ++ scripts/remove_trap_modal.gd.uid | 1 + scripts/test_level.gd | 65 +++++++-- scripts/trap.gd | 54 +++++++ scripts/trap.gd.uid | 1 + scripts/trap_icon.gd | 14 +- templates/disarm_icon.tscn | 11 ++ templates/disarm_trap_modal.tscn | 110 +++++++++++++++ templates/one_shot.tscn | 10 ++ templates/remove_trap_modal.tscn | 51 +++++++ templates/singleplayer_pc.tscn | 9 +- templates/trap.tscn | 43 ++++++ visuals/images/icons/t-bomb.png | Bin 0 -> 4662 bytes visuals/images/icons/t-bomb.png.import | 41 ++++++ visuals/images/icons/t-force_panel.png | Bin 0 -> 5585 bytes visuals/images/icons/t-force_panel.png.import | 40 ++++++ visuals/images/icons/t-gas.png | Bin 0 -> 3962 bytes visuals/images/icons/t-gas.png.import | 40 ++++++ visuals/images/icons/t-mine.png | Bin 0 -> 4350 bytes visuals/images/icons/t-mine.png.import | 40 ++++++ visuals/images/icons/t-pitfall.png | Bin 0 -> 5080 bytes visuals/images/icons/t-pitfall.png.import | 40 ++++++ visuals/images/icons/t-switch.png | Bin 0 -> 4002 bytes visuals/images/icons/t-switch.png.import | 40 ++++++ 34 files changed, 867 insertions(+), 72 deletions(-) create mode 100644 scripts/disarm_trap_modal.gd create mode 100644 scripts/disarm_trap_modal.gd.uid create mode 100644 scripts/one_shot.gd create mode 100644 scripts/one_shot.gd.uid create mode 100644 scripts/remove_trap_modal.gd create mode 100644 scripts/remove_trap_modal.gd.uid create mode 100644 scripts/trap.gd create mode 100644 scripts/trap.gd.uid create mode 100644 templates/disarm_icon.tscn create mode 100644 templates/disarm_trap_modal.tscn create mode 100644 templates/one_shot.tscn create mode 100644 templates/remove_trap_modal.tscn create mode 100644 templates/trap.tscn create mode 100644 visuals/images/icons/t-bomb.png create mode 100644 visuals/images/icons/t-bomb.png.import create mode 100644 visuals/images/icons/t-force_panel.png create mode 100644 visuals/images/icons/t-force_panel.png.import create mode 100644 visuals/images/icons/t-gas.png create mode 100644 visuals/images/icons/t-gas.png.import create mode 100644 visuals/images/icons/t-mine.png create mode 100644 visuals/images/icons/t-mine.png.import create mode 100644 visuals/images/icons/t-pitfall.png create mode 100644 visuals/images/icons/t-pitfall.png.import create mode 100644 visuals/images/icons/t-switch.png create mode 100644 visuals/images/icons/t-switch.png.import diff --git a/project.godot b/project.godot index bc3a5ad..ae0e618 100644 --- a/project.godot +++ b/project.godot @@ -86,3 +86,18 @@ detect={ "events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":true,"script":null) ] } +"lay trap"={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":true,"script":null) +] +} +attack={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null) +] +} +detonate={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) +] +} diff --git a/scenes/test_level.tscn b/scenes/test_level.tscn index c8d1622..68cbf7f 100644 --- a/scenes/test_level.tscn +++ b/scenes/test_level.tscn @@ -1,22 +1,11 @@ -[gd_scene load_steps=11 format=3 uid="uid://by6suwmds7xq2"] +[gd_scene load_steps=6 format=3 uid="uid://by6suwmds7xq2"] [ext_resource type="Script" uid="uid://d3t381vws7vns" path="res://scripts/test_level.gd" id="1_qcd3b"] [ext_resource type="PackedScene" uid="uid://bgocskbofewsr" path="res://templates/HUD.tscn" id="1_x4b8f"] [ext_resource type="Script" uid="uid://cymi1n4gavixy" path="res://scripts/level_camera.gd" id="3_qcd3b"] -[ext_resource type="ArrayMesh" uid="uid://bih57xe642hrc" path="res://models/trap.obj" id="4_tmr53"] -[ext_resource type="Texture2D" uid="uid://dri0a20l6kpbj" path="res://visuals/images/icon.svg" id="6_ahbqi"] [ext_resource type="MeshLibrary" uid="uid://bhpyvhf36jl0f" path="res://testing.tres" id="7_88ety"] [ext_resource type="MeshLibrary" uid="uid://cvhm40o2uw5mr" path="res://testing markers.tres" id="7_ahbqi"] -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qcd3b"] -albedo_color = Color(0, 0, 1, 1) - -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_88ety"] -albedo_color = Color(1, 0, 0, 1) - -[sub_resource type="BoxShape3D" id="BoxShape3D_dw7u0"] -size = Vector3(0.8, 1, 0.8) - [node name="Node3D" type="Node3D"] script = ExtResource("1_qcd3b") @@ -31,25 +20,6 @@ projection = 1 size = 5.0 script = ExtResource("3_qcd3b") -[node name="Area3D" type="Area3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.496, 0, 0.479267) - -[node name="Trap" type="MeshInstance3D" parent="Area3D"] -transform = Transform3D(0.25, 0, 0, 0, 0.25, 0, 0, 0, 0.25, 0, 0, 0) -material_overlay = SubResource("StandardMaterial3D_qcd3b") -mesh = ExtResource("4_tmr53") -skeleton = NodePath("") -surface_material_override/0 = SubResource("StandardMaterial3D_88ety") - -[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) -shape = SubResource("BoxShape3D_dw7u0") - -[node name="Sprite3D" type="Sprite3D" parent="Area3D"] -transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0.05, 0) -visible = false -texture = ExtResource("6_ahbqi") - [node name="OmniLight3D" type="OmniLight3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 18.508709, 0) omni_range = 25.142 diff --git a/scripts/autos/game.gd b/scripts/autos/game.gd index 46da4e7..10f133d 100644 --- a/scripts/autos/game.gd +++ b/scripts/autos/game.gd @@ -1,14 +1,8 @@ extends Node -enum TrapType{ - BOMB, - MINE, - GAS, - FORCE_PANEL, - SWITCH, - PITFALL -} + const blinder_template = preload("res://templates/blinder.tscn") +const one_shot_template = preload("res://templates/one_shot.tscn") const vs_man_level = preload("res://scenes/multiplayer_test.tscn") @@ -41,3 +35,11 @@ func start_vs_man() -> void: var spawner = get_tree().get_first_node_in_group("level_spawner") as MultiplayerSpawner spawner.add_child(level) + +func oneshot(stream : AudioStream) -> void: + var shot = one_shot_template.instantiate() + shot.stream = stream + if level: + level.add_child(shot) + else: + add_child(shot) diff --git a/scripts/autos/multiplayer.gd b/scripts/autos/multiplayer.gd index 117533d..760d476 100644 --- a/scripts/autos/multiplayer.gd +++ b/scripts/autos/multiplayer.gd @@ -4,7 +4,7 @@ extends Node const SERVER_PORT = 8080 const SERVER_IP = "127.0.0.1" var handle : String -var id : int +var id : int = 1 var players : Dictionary = {} signal client_added(handle : String, id : int) diff --git a/scripts/disarm_trap_modal.gd b/scripts/disarm_trap_modal.gd new file mode 100644 index 0000000..3ecebb0 --- /dev/null +++ b/scripts/disarm_trap_modal.gd @@ -0,0 +1,103 @@ +class_name DisarmTrapModal extends Control + + +const action_value : Dictionary[String, int] = { + "detonate" : 0, + "lay trap" : 1, + "detect" : 2, + "attack" : 3 +} + +const icon_textures : Array = [ + { + "normal" : preload("res://visuals/images/icons/switch button.png"), + "pressed" : preload("res://visuals/images/icons/lit switch button.png") + }, + { + "normal" : preload("res://visuals/images/icons/trap button.png"), + "pressed" : preload("res://visuals/images/icons/lit trap button.png") + }, + { + "normal" : preload("res://visuals/images/icons/detect button.png"), + "pressed" : preload("res://visuals/images/icons/lit detect button.png") + }, + { + "normal" : preload("res://visuals/images/icons/ranged attack button.png"), + "pressed" : preload("res://visuals/images/icons/lit ranged attack button.png") + }, +] + +const disarm_icon_template = preload("res://templates/disarm_icon.tscn") +const success_sound = preload("res://audio/sounds/menu select.wav") +const fail_sound = preload("res://audio/sounds/134688__otbtechno__bike-bell.wav") +const progress_sound = preload("res://audio/sounds/BUTTON Generic Metal Medium High Mechanics 02.wav") + +@onready var confirmation_dialog : ConfirmationDialog = %ConfirmationDialog +@onready var disarm_window : PanelContainer = %DisarmWindow +@onready var disarm_button_container : HBoxContainer = %DisarmContainer +@onready var timer : Timer = %Timer +@onready var timer_label : Label = %TimerLabel + + +var square : Vector3i +var disarm_icons : Array[TextureButton] +var disarm_buttons : Array[int] +var progress : int +var disarming : bool + +var difficulty : int = 1 + + +func _process(delta: float) -> void: + timer_label.text = "%02d.3" % timer.time_left + +func start_disarming() -> void: + disarming = true + progress = 0 + disarm_icons = [] + disarm_buttons = [] + for i in range(difficulty): + var btn = randi_range(0,3) + var icon : TextureButton = disarm_icon_template.instantiate() + icon.texture_normal = icon_textures[btn].normal + icon.texture_pressed = icon_textures[btn].pressed + disarm_buttons.append(btn) + disarm_icons.append(icon) + disarm_button_container.add_child(icon) + confirmation_dialog.visible = false + disarm_window.visible = true + timer.start(3 + (2 * len(disarm_buttons) * randf_range(.75, 1.25) )) + +func try_advance(btn : int) -> void: + + if btn == disarm_buttons[progress]: + disarm_icons[progress].set_pressed_no_signal(true) + progress += 1 + if progress >= len(disarm_buttons): + Game.player.close_modal() + Game.oneshot(success_sound) + Game.level.disarm_trap(square) + else: + Game.oneshot(progress_sound) + else: + Game.player.close_modal() + Game.oneshot(fail_sound) + Game.level.activate_trap(square) + + +func button_pressed(event : InputEventAction) -> void: + if disarming: + try_advance(action_value[event.action]) + else: + match(event.action): + "detonate": + start_disarming() + "attack": + Game.player.close_modal() + _: return + + +func _on_timer_timeout() -> void: + Game.player.close_modal() + Game.oneshot(fail_sound) + Game.level.activate_trap(square) diff --git a/scripts/disarm_trap_modal.gd.uid b/scripts/disarm_trap_modal.gd.uid new file mode 100644 index 0000000..bca239d --- /dev/null +++ b/scripts/disarm_trap_modal.gd.uid @@ -0,0 +1 @@ +uid://5nrsscwod1xg diff --git a/scripts/one_shot.gd b/scripts/one_shot.gd new file mode 100644 index 0000000..fa0ffa0 --- /dev/null +++ b/scripts/one_shot.gd @@ -0,0 +1,8 @@ +extends AudioStreamPlayer + +func _ready() -> void: + $Timer.start(stream.get_length()) + + +func _on_timeout() -> void: + queue_free() diff --git a/scripts/one_shot.gd.uid b/scripts/one_shot.gd.uid new file mode 100644 index 0000000..279b35b --- /dev/null +++ b/scripts/one_shot.gd.uid @@ -0,0 +1 @@ +uid://cb72nk0ddi3c diff --git a/scripts/player.gd b/scripts/player.gd index 2c03eef..a76aa56 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -1,21 +1,34 @@ 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 data : PlayerData = $Data +@onready var trap_sound : AudioStreamPlayer3D = $TrapSound +var button_actions : Dictionary[int, String] var current_square : Vector3i -var detecting : bool +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 @@ -24,18 +37,70 @@ func _physics_process(delta: float) -> void: 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.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 !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() @@ -68,9 +133,30 @@ func update_detecting() -> void: 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 @@ -92,6 +178,10 @@ func start_detecting() -> void: 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 @@ -103,6 +193,17 @@ 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 @@ -114,3 +215,11 @@ func cycle_active_trap(dir) -> void: 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 diff --git a/scripts/player_data.gd b/scripts/player_data.gd index d21d59e..6c3caa6 100644 --- a/scripts/player_data.gd +++ b/scripts/player_data.gd @@ -1,11 +1,11 @@ class_name PlayerData extends Node class TrapData: - var type : Game.TrapType + var type : Trap.Type var quantity : int var max : int - func _init(type : Game.TrapType, quantity : int, max : int) -> void: + func _init(type : Trap.Type, quantity : int, max : int) -> void: self.type = type self.quantity = quantity self.max = max diff --git a/scripts/remove_trap_modal.gd b/scripts/remove_trap_modal.gd new file mode 100644 index 0000000..aa53c4d --- /dev/null +++ b/scripts/remove_trap_modal.gd @@ -0,0 +1,12 @@ +class_name RemoveTrapModal extends ConfirmationDialog + +var square : Vector3i + +func button_pressed(event : InputEventAction) -> void: + match(event.action): + "detonate": + Game.player.remove_trap_at(square) + Game.player.close_modal() + "attack": + Game.player.close_modal() + _: return diff --git a/scripts/remove_trap_modal.gd.uid b/scripts/remove_trap_modal.gd.uid new file mode 100644 index 0000000..8f8d730 --- /dev/null +++ b/scripts/remove_trap_modal.gd.uid @@ -0,0 +1 @@ +uid://bj1udbw3sm73f diff --git a/scripts/test_level.gd b/scripts/test_level.gd index 794feac..dbca205 100644 --- a/scripts/test_level.gd +++ b/scripts/test_level.gd @@ -1,26 +1,71 @@ class_name Level extends Node3D const player_controller = preload("res://templates/singleplayer_pc.tscn") +const trap_template = preload("res://templates/trap.tscn") @onready var floor_layer : GridMap = %Floor @onready var marker_layer : GridMap = %Markers +@export var difficulty : int = 1 + +var traps : Dictionary[Vector3i, Trap] = {} + +func _ready() -> void: + Game.level = self + var player = player_controller.instantiate() + var traps : Array[PlayerData.TrapData] = [ + PlayerData.TrapData.new(Trap.Type.BOMB, 3, 3), + PlayerData.TrapData.new(Trap.Type.SWITCH, 3, 3), + PlayerData.TrapData.new(Trap.Type.MINE, 3, 3) + ] + add_child(player) + generate_trap(Trap.Type.MINE, Vector3i(5,0,2)) + player.setup(traps) + +func is_square_detected(crd) -> bool: + return marker_layer.get_cell_item(crd + Vector3i(0,-1,0)) != GridMap.INVALID_CELL_ITEM + func detect_square(crd : Vector3i, mark : bool) -> bool: var cell = floor_layer.get_cell_item(crd) if cell == GridMap.INVALID_CELL_ITEM: return false marker_layer.set_cell_item(crd, 0 if mark else GridMap.INVALID_CELL_ITEM) + if mark: + var trap_crd = crd + Vector3i(0,1,0) + if traps.has(trap_crd): + var trap : Trap = traps[trap_crd] + if trap.trap_owner != Multiplayer.id: + trap.reveal() + return true + +func add_trap(trap : Trap, crd : Vector3i) -> void: + trap.square = crd + traps[crd] = trap + trap.global_position = Vector3(crd) + Vector3(.5, 0, .5) + add_child(trap) + pass + +func get_square_trap(crd : Vector3i) -> Trap: + if traps.has(crd): + return traps[crd] + else: + return null + +func is_valid_trap_square(crd : Vector3i) -> bool: + if floor_layer.get_cell_item(crd + Vector3i(0,-1,0)) == GridMap.INVALID_CELL_ITEM: + return false + if traps.has(crd): + return false return true -func _ready() -> void: - Game.level = self - var player = player_controller.instantiate() - var traps : Array[PlayerData.TrapData] = [ - PlayerData.TrapData.new(Game.TrapType.BOMB, 3, 3), - PlayerData.TrapData.new(Game.TrapType.SWITCH, 3, 3), - PlayerData.TrapData.new(Game.TrapType.MINE, 3, 3) - ] - add_child(player) - player.setup(traps) +func generate_trap(type : Trap.Type, square : Vector3i): + var trap = trap_template.instantiate() + trap.setup(type, -1) + add_trap(trap, square) + +func disarm_trap(crd : Vector3i) -> void: + var trap = traps[crd] + trap.disarm() + traps.erase(crd) diff --git a/scripts/trap.gd b/scripts/trap.gd new file mode 100644 index 0000000..0c85bde --- /dev/null +++ b/scripts/trap.gd @@ -0,0 +1,54 @@ +class_name Trap extends Area3D + +enum Type{ + BOMB, + MINE, + GAS, + FORCE_PANEL, + SWITCH, + PITFALL +} + +const trap_icons : Dictionary = { + Trap.Type.BOMB : preload("res://visuals/images/icons/t-bomb.png"), + Trap.Type.GAS : preload("res://visuals/images/icons/t-gas.png"), + Trap.Type.PITFALL : preload("res://visuals/images/icons/t-pitfall.png"), + Trap.Type.FORCE_PANEL : preload("res://visuals/images/icons/t-force_panel.png"), + Trap.Type.SWITCH : preload("res://visuals/images/icons/t-switch.png"), + Trap.Type.MINE : preload("res://visuals/images/icons/t-mine.png"), +} + +@onready var model : MeshInstance3D = %Model +@onready var icon : Sprite3D = %Icon +@onready var material : StandardMaterial3D = model.get_surface_override_material(0) +@onready var reveal_timer : Timer = %RevealTimer +var type : Type +var square : Vector3i +var trap_owner : int +signal disarmed(type : Trap.Type) + + +func setup(type : Type, trap_owner : int) -> void: + self.type = type + self.trap_owner = trap_owner + +func disarm() -> void: + disarmed.emit(type) + queue_free() + +func reveal() -> void: + model.visible = true + reveal_timer.start(5) + +func _on_reveal_timeout() -> void: + if Game.level.is_square_detected(square): + reveal_timer.start(5) + else: + model.visible = false + +func _ready() -> void: + var owns_trap = trap_owner == Multiplayer.id + icon.texture = trap_icons[type] + model.visible = owns_trap + icon.visible = owns_trap + material.albedo_color = Color.YELLOW if owns_trap else Color.RED diff --git a/scripts/trap.gd.uid b/scripts/trap.gd.uid new file mode 100644 index 0000000..98c217b --- /dev/null +++ b/scripts/trap.gd.uid @@ -0,0 +1 @@ +uid://yjsgte3x7jjw diff --git a/scripts/trap_icon.gd b/scripts/trap_icon.gd index 96bb7f9..5f4f68f 100644 --- a/scripts/trap_icon.gd +++ b/scripts/trap_icon.gd @@ -1,18 +1,18 @@ class_name TrapIcon extends Control const trap_icons : Dictionary = { - Game.TrapType.BOMB : preload("res://visuals/images/icons/bomb.png"), - Game.TrapType.GAS : preload("res://visuals/images/icons/gas.png"), - Game.TrapType.PITFALL : preload("res://visuals/images/icons/pitfall.png"), - Game.TrapType.FORCE_PANEL : preload("res://visuals/images/icons/force_panel.png"), - Game.TrapType.SWITCH : preload("res://visuals/images/icons/switch.png"), - Game.TrapType.MINE : preload("res://visuals/images/icons/mine.png"), + Trap.Type.BOMB : preload("res://visuals/images/icons/bomb.png"), + Trap.Type.GAS : preload("res://visuals/images/icons/gas.png"), + Trap.Type.PITFALL : preload("res://visuals/images/icons/pitfall.png"), + Trap.Type.FORCE_PANEL : preload("res://visuals/images/icons/force_panel.png"), + Trap.Type.SWITCH : preload("res://visuals/images/icons/switch.png"), + Trap.Type.MINE : preload("res://visuals/images/icons/mine.png"), } @onready var icon_image : TextureRect = %Icon @onready var qty_label : Label = %Label -func setup(type : Game.TrapType, qty : int) -> void: +func setup(type : Trap.Type, qty : int) -> void: icon_image.texture = trap_icons[type] set_quantity(qty) diff --git a/templates/disarm_icon.tscn b/templates/disarm_icon.tscn new file mode 100644 index 0000000..953db07 --- /dev/null +++ b/templates/disarm_icon.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=3 format=3 uid="uid://c5ienwpgidanx"] + +[ext_resource type="Texture2D" uid="uid://bxs0jj4y4clvv" path="res://visuals/images/icons/detect button.png" id="1_tmc0a"] +[ext_resource type="Texture2D" uid="uid://bxxm3c2lpmr4f" path="res://visuals/images/icons/lit detect button.png" id="2_75gk1"] + +[node name="DisarmIcon" type="TextureButton"] +custom_minimum_size = Vector2(32, 32) +texture_normal = ExtResource("1_tmc0a") +texture_pressed = ExtResource("2_75gk1") +ignore_texture_size = true +stretch_mode = 0 diff --git a/templates/disarm_trap_modal.tscn b/templates/disarm_trap_modal.tscn new file mode 100644 index 0000000..603e769 --- /dev/null +++ b/templates/disarm_trap_modal.tscn @@ -0,0 +1,110 @@ +[gd_scene load_steps=4 format=3 uid="uid://v12wymuy2xk6"] + +[ext_resource type="Script" uid="uid://5nrsscwod1xg" path="res://scripts/disarm_trap_modal.gd" id="1_eo0gq"] +[ext_resource type="Texture2D" uid="uid://barbcaa2xvgkk" path="res://visuals/images/icons/switch button.png" id="2_cgabh"] +[ext_resource type="Texture2D" uid="uid://diyks5oxgidoo" path="res://visuals/images/icons/ranged attack button.png" id="3_06srb"] + +[node name="DisarmTrapModal" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_eo0gq") + +[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +oversampling_override = 1.0 +initial_position = 1 +visible = true +transient = false +borderless = true +always_on_top = true +unfocusable = true +ok_button_text = "Disarm" +dialog_text = "Disarm Trap?" + +[node name="Control" type="Control" parent="ConfirmationDialog"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = 192.0 +offset_bottom = 51.0 + +[node name="TextureRect" type="TextureRect" parent="ConfirmationDialog/Control"] +z_index = 10 +layout_mode = 0 +offset_left = 36.0 +offset_top = 29.0 +offset_right = 68.0 +offset_bottom = 61.0 +texture = ExtResource("2_cgabh") +expand_mode = 1 + +[node name="TextureRect2" type="TextureRect" parent="ConfirmationDialog/Control"] +z_index = 10 +layout_mode = 0 +offset_left = 120.0 +offset_top = 29.0 +offset_right = 152.0 +offset_bottom = 61.0 +texture = ExtResource("3_06srb") +expand_mode = 1 + +[node name="Timer" type="Timer" parent="."] +unique_name_in_owner = true + +[node name="DisarmSound" type="AudioStreamPlayer" parent="."] + +[node name="SuccessSound" type="AudioStreamPlayer" parent="."] + +[node name="AudioStreamPlayer3" type="AudioStreamPlayer" parent="."] + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="DisarmWindow" type="PanelContainer" parent="CanvasLayer"] +unique_name_in_owner = true +visible = false +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -96.0 +offset_top = -56.0 +offset_right = 96.0 +offset_bottom = 56.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/DisarmWindow"] +layout_mode = 2 +alignment = 1 + +[node name="PressToDisarmHeader" type="Label" parent="CanvasLayer/DisarmWindow/VBoxContainer"] +layout_mode = 2 +text = "PRESS TO DISARM" +horizontal_alignment = 1 + +[node name="Panel" type="PanelContainer" parent="CanvasLayer/DisarmWindow/VBoxContainer"] +layout_mode = 2 + +[node name="DisarmContainer" type="HBoxContainer" parent="CanvasLayer/DisarmWindow/VBoxContainer/Panel"] +unique_name_in_owner = true +layout_mode = 2 +alignment = 1 + +[node name="TimeRemainingLabel" type="Label" parent="CanvasLayer/DisarmWindow/VBoxContainer"] +layout_mode = 2 +text = "TIME REMAINING: " +horizontal_alignment = 1 + +[node name="TimerLabel" type="Label" parent="CanvasLayer/DisarmWindow/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "00.000" +horizontal_alignment = 1 + +[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] diff --git a/templates/one_shot.tscn b/templates/one_shot.tscn new file mode 100644 index 0000000..505aadd --- /dev/null +++ b/templates/one_shot.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://cmw77i7e7jxkn"] + +[ext_resource type="Script" uid="uid://cb72nk0ddi3c" path="res://scripts/one_shot.gd" id="1_k1ss2"] + +[node name="OneShot" type="AudioStreamPlayer"] +script = ExtResource("1_k1ss2") + +[node name="Timer" type="Timer" parent="."] + +[connection signal="timeout" from="Timer" to="." method="_on_timeout"] diff --git a/templates/remove_trap_modal.tscn b/templates/remove_trap_modal.tscn new file mode 100644 index 0000000..2717404 --- /dev/null +++ b/templates/remove_trap_modal.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=4 format=3 uid="uid://bwj76wt4pab4t"] + +[ext_resource type="Script" uid="uid://bj1udbw3sm73f" path="res://scripts/remove_trap_modal.gd" id="1_27jge"] +[ext_resource type="Texture2D" uid="uid://barbcaa2xvgkk" path="res://visuals/images/icons/switch button.png" id="2_dmpk7"] +[ext_resource type="Texture2D" uid="uid://diyks5oxgidoo" path="res://visuals/images/icons/ranged attack button.png" id="3_42gya"] + +[node name="RemoveTrapModal" type="ConfirmationDialog"] +handle_input_locally = false +canvas_item_default_texture_filter = 2 +gui_disable_input = true +oversampling_override = 1.0 +title = "Remove Trap?" +initial_position = 1 +visible = true +transient = false +borderless = true +always_on_top = true +unfocusable = true +content_scale_mode = 1 +content_scale_aspect = 2 +ok_button_text = "Remove" +dialog_text = "Remove Trap?" +script = ExtResource("1_27jge") + +[node name="Control" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = 192.0 +offset_bottom = 51.0 + +[node name="TextureRect" type="TextureRect" parent="Control"] +z_index = 10 +layout_mode = 0 +offset_left = 36.0 +offset_top = 29.0 +offset_right = 68.0 +offset_bottom = 61.0 +texture = ExtResource("2_dmpk7") +expand_mode = 1 + +[node name="TextureRect2" type="TextureRect" parent="Control"] +z_index = 10 +layout_mode = 0 +offset_left = 120.0 +offset_top = 29.0 +offset_right = 152.0 +offset_bottom = 61.0 +texture = ExtResource("3_42gya") +expand_mode = 1 diff --git a/templates/singleplayer_pc.tscn b/templates/singleplayer_pc.tscn index 3abd200..d5a0a02 100644 --- a/templates/singleplayer_pc.tscn +++ b/templates/singleplayer_pc.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=6 format=3 uid="uid://c8xf3qawk5c6u"] +[gd_scene load_steps=7 format=3 uid="uid://c8xf3qawk5c6u"] [ext_resource type="Script" uid="uid://bcs7ygh6s3l35" path="res://scripts/player.gd" id="1_a5wj7"] [ext_resource type="Script" uid="uid://6w608y2grdqb" path="res://scripts/player_data.gd" id="2_sfa2f"] +[ext_resource type="AudioStream" uid="uid://cjrlb4qiy23sj" path="res://audio/sounds/impact_deep_thud_bounce_09.wav" id="3_sfa2f"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_y646j"] albedo_color = Color(0.08468992, 0.08468992, 0.08468992, 1) @@ -38,3 +39,9 @@ shape = SubResource("CapsuleShape3D_a5wj7") [node name="Data" type="Node" parent="."] script = ExtResource("2_sfa2f") + +[node name="TrapSound" type="AudioStreamPlayer3D" parent="."] +stream = ExtResource("3_sfa2f") + +[node name="AudioListener3D" type="AudioListener3D" parent="."] +current = true diff --git a/templates/trap.tscn b/templates/trap.tscn new file mode 100644 index 0000000..3fba576 --- /dev/null +++ b/templates/trap.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=7 format=3 uid="uid://bk3yqawritfnj"] + +[ext_resource type="Script" uid="uid://yjsgte3x7jjw" path="res://scripts/trap.gd" id="1_6h4aj"] +[ext_resource type="ArrayMesh" uid="uid://bih57xe642hrc" path="res://models/trap.obj" id="2_oj6ox"] +[ext_resource type="Texture2D" uid="uid://cmlp8tn6mnbd" path="res://visuals/images/icons/t-bomb.png" id="3_mxvh5"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qcd3b"] +albedo_color = Color(0, 0, 1, 1) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_88ety"] +resource_local_to_scene = true +albedo_color = Color(1, 1, 0, 1) + +[sub_resource type="BoxShape3D" id="BoxShape3D_dw7u0"] +size = Vector3(0.8, 1, 0.8) + +[node name="Trap" type="Area3D"] +script = ExtResource("1_6h4aj") + +[node name="Model" type="MeshInstance3D" parent="."] +unique_name_in_owner = true +transform = Transform3D(0.25, 0, 0, 0, 0.25, 0, 0, 0, 0.25, 0, 0, 0) +material_overlay = SubResource("StandardMaterial3D_qcd3b") +mesh = ExtResource("2_oj6ox") +skeleton = NodePath("") +surface_material_override/0 = SubResource("StandardMaterial3D_88ety") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) +shape = SubResource("BoxShape3D_dw7u0") + +[node name="Icon" type="Sprite3D" parent="."] +unique_name_in_owner = true +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0) +pixel_size = 0.0075 +axis = 1 +double_sided = false +texture = ExtResource("3_mxvh5") + +[node name="RevealTimer" type="Timer" parent="."] +unique_name_in_owner = true + +[connection signal="timeout" from="RevealTimer" to="." method="_on_reveal_timeout"] diff --git a/visuals/images/icons/t-bomb.png b/visuals/images/icons/t-bomb.png new file mode 100644 index 0000000000000000000000000000000000000000..fe28874d2b07a32582de0b2d02f3a40e31ebf1d6 GIT binary patch literal 4662 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 literal 0 HcmV?d00001 diff --git a/visuals/images/icons/t-force_panel.png.import b/visuals/images/icons/t-force_panel.png.import new file mode 100644 index 0000000..fa20c80 --- /dev/null +++ b/visuals/images/icons/t-force_panel.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxbdj3448tcse" +path="res://.godot/imported/t-force_panel.png-8187f2672c6b23f490483f18d6c18d70.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visuals/images/icons/t-force_panel.png" +dest_files=["res://.godot/imported/t-force_panel.png-8187f2672c6b23f490483f18d6c18d70.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/visuals/images/icons/t-gas.png b/visuals/images/icons/t-gas.png new file mode 100644 index 0000000000000000000000000000000000000000..ea6a42a1975f3336dd3fbd4b67b785adaafec25d GIT binary patch literal 3962 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