UI work, placing traps, removing traps, disarming traps, new sound fx.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
103
scripts/disarm_trap_modal.gd
Normal file
103
scripts/disarm_trap_modal.gd
Normal file
@@ -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)
|
||||
1
scripts/disarm_trap_modal.gd.uid
Normal file
1
scripts/disarm_trap_modal.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://5nrsscwod1xg
|
||||
8
scripts/one_shot.gd
Normal file
8
scripts/one_shot.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
extends AudioStreamPlayer
|
||||
|
||||
func _ready() -> void:
|
||||
$Timer.start(stream.get_length())
|
||||
|
||||
|
||||
func _on_timeout() -> void:
|
||||
queue_free()
|
||||
1
scripts/one_shot.gd.uid
Normal file
1
scripts/one_shot.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cb72nk0ddi3c
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
12
scripts/remove_trap_modal.gd
Normal file
12
scripts/remove_trap_modal.gd
Normal file
@@ -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
|
||||
1
scripts/remove_trap_modal.gd.uid
Normal file
1
scripts/remove_trap_modal.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bj1udbw3sm73f
|
||||
@@ -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)
|
||||
|
||||
54
scripts/trap.gd
Normal file
54
scripts/trap.gd
Normal file
@@ -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
|
||||
1
scripts/trap.gd.uid
Normal file
1
scripts/trap.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://yjsgte3x7jjw
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user