Feat/2 weapons foundation #6
+12
-10
@@ -74,13 +74,16 @@ func _build_input_map() -> void:
|
||||
"move_right": [KEY_D],
|
||||
"weapon_next": [MOUSE_BUTTON_WHEEL_UP],
|
||||
"weapon_prev": [MOUSE_BUTTON_WHEEL_DOWN],
|
||||
"weapon_1": [KEY_1],
|
||||
"weapon_2": [KEY_2],
|
||||
"weapon_3": [KEY_3],
|
||||
}
|
||||
for action in bindings:
|
||||
if not InputMap.has_action(action):
|
||||
InputMap.add_action(action)
|
||||
InputMap.action_erase_events(action)
|
||||
for code in bindings[action]:
|
||||
if code in [KEY_SPACE, KEY_SHIFT, KEY_CTRL, KEY_R, KEY_F, KEY_E, KEY_W, KEY_S, KEY_A, KEY_D]:
|
||||
if code in [KEY_SPACE, KEY_SHIFT, KEY_CTRL, KEY_R, KEY_F, KEY_E, KEY_W, KEY_S, KEY_A, KEY_D, KEY_1, KEY_2, KEY_3]:
|
||||
var ev := InputEventKey.new()
|
||||
ev.keycode = code
|
||||
InputMap.action_add_event(action, ev)
|
||||
@@ -318,15 +321,14 @@ func _build_player() -> void:
|
||||
camera.fov = 90.0
|
||||
head_pivot.add_child(camera)
|
||||
|
||||
# ── Temporary Weapon Setup ──────────────────────────────────────────────────
|
||||
# TODO: Change how the player is given weapons in the future to a loadout schema.
|
||||
var shotgun_script = preload("res://weapons/double_barrel_shotgun.gd")
|
||||
if shotgun_script:
|
||||
var shotgun = shotgun_script.new()
|
||||
shotgun.name = "DoubleBarrelShotgun"
|
||||
shotgun.player = player
|
||||
shotgun.camera = camera
|
||||
camera.add_child(shotgun)
|
||||
# ── Weapon Setup ────────────────────────────────────────────────────────────
|
||||
var wmanager_script = preload("res://weapons/weapon_manager.gd")
|
||||
if wmanager_script:
|
||||
var wman = wmanager_script.new()
|
||||
wman.name = "WeaponManager"
|
||||
wman.player = player
|
||||
wman.camera = camera
|
||||
camera.add_child(wman)
|
||||
|
||||
var rig_script = preload("res://characters/player/fps_camera_rig.gd")
|
||||
if rig_script:
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
extends Node
|
||||
|
||||
const SAVE_PATH = "user://loadouts.json"
|
||||
|
||||
var weapon_db = {
|
||||
"none": {
|
||||
"name": "None",
|
||||
"category": "none",
|
||||
"script": ""
|
||||
},
|
||||
"double_barrel_shotgun": {
|
||||
"name": "Double Barrel Shotgun",
|
||||
"category": "primary",
|
||||
"script": "res://weapons/double_barrel_shotgun.gd"
|
||||
}
|
||||
}
|
||||
|
||||
var active_loadout_index: int = 0
|
||||
|
||||
var loadouts: Array = []
|
||||
|
||||
func _ready() -> void:
|
||||
_ensure_default_loadouts()
|
||||
load_loadouts()
|
||||
|
||||
func _ensure_default_loadouts() -> void:
|
||||
loadouts.clear()
|
||||
for i in range(5):
|
||||
loadouts.append({
|
||||
"name": "Loadout " + str(i + 1),
|
||||
"primary_1": "double_barrel_shotgun",
|
||||
"primary_2": "none",
|
||||
"special": "none"
|
||||
})
|
||||
|
||||
func get_active_loadout() -> Dictionary:
|
||||
if active_loadout_index >= 0 and active_loadout_index < loadouts.size():
|
||||
return loadouts[active_loadout_index]
|
||||
return loadouts[0]
|
||||
|
||||
func save_loadouts() -> void:
|
||||
var save_data = {
|
||||
"active_index": active_loadout_index,
|
||||
"loadouts": loadouts
|
||||
}
|
||||
var json_string = JSON.stringify(save_data)
|
||||
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||||
if file:
|
||||
file.store_string(json_string)
|
||||
file.close()
|
||||
|
||||
func load_loadouts() -> void:
|
||||
if FileAccess.file_exists(SAVE_PATH):
|
||||
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
|
||||
if file:
|
||||
var json_string = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var json = JSON.new()
|
||||
var error = json.parse(json_string)
|
||||
if error == OK:
|
||||
var data = json.get_data()
|
||||
if data is Dictionary:
|
||||
if data.has("active_index"):
|
||||
active_loadout_index = data["active_index"]
|
||||
if data.has("loadouts") and data["loadouts"] is Array:
|
||||
var saved_loadouts = data["loadouts"]
|
||||
# Overwrite defaults, up to 5
|
||||
for i in range(min(5, saved_loadouts.size())):
|
||||
var sl = saved_loadouts[i]
|
||||
if sl is Dictionary:
|
||||
if sl.has("name"): loadouts[i]["name"] = sl["name"]
|
||||
if sl.has("primary_1"): loadouts[i]["primary_1"] = sl["primary_1"]
|
||||
if sl.has("primary_2"): loadouts[i]["primary_2"] = sl["primary_2"]
|
||||
if sl.has("special"): loadouts[i]["special"] = sl["special"]
|
||||
@@ -0,0 +1 @@
|
||||
uid://ck4n30620akl4
|
||||
@@ -15,6 +15,11 @@ run/main_scene="res://scenes/test_level/test_level.tscn"
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
LoadoutManager="*res://globals/loadout_manager.gd"
|
||||
PauseMenu="*res://ui/pause_menu.gd"
|
||||
|
||||
[input]
|
||||
|
||||
move_forward={
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
extends CanvasLayer
|
||||
|
||||
var bg: ColorRect
|
||||
var main_vbox: VBoxContainer
|
||||
var resume_btn: Button
|
||||
var loadouts_btn: Button
|
||||
var exit_btn: Button
|
||||
|
||||
var loadout_editor: Control
|
||||
var loadout_list_vbox: VBoxContainer
|
||||
var edit_panel: PanelContainer
|
||||
var name_edit: LineEdit
|
||||
var primary1_opt: OptionButton
|
||||
var primary2_opt: OptionButton
|
||||
var special_opt: OptionButton
|
||||
var save_loadout_btn: Button
|
||||
var back_to_main_btn: Button
|
||||
|
||||
var editing_index: int = -1
|
||||
|
||||
var quit_dialog: ConfirmationDialog
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
layer = 100 # Ensure it's on top
|
||||
visible = false
|
||||
|
||||
_build_ui()
|
||||
_connect_signals()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
if visible:
|
||||
_resume()
|
||||
else:
|
||||
_pause()
|
||||
|
||||
func _pause() -> void:
|
||||
visible = true
|
||||
get_tree().paused = true
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
_show_main_menu()
|
||||
|
||||
func _resume() -> void:
|
||||
visible = false
|
||||
get_tree().paused = false
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
func _build_ui() -> void:
|
||||
bg = ColorRect.new()
|
||||
bg.color = Color(0, 0, 0, 0.7)
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(bg)
|
||||
|
||||
# Main Menu
|
||||
main_vbox = VBoxContainer.new()
|
||||
main_vbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||
main_vbox.add_theme_constant_override("separation", 20)
|
||||
bg.add_child(main_vbox)
|
||||
|
||||
var title = Label.new()
|
||||
title.text = "PAUSED"
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
title.add_theme_font_size_override("font_size", 48)
|
||||
main_vbox.add_child(title)
|
||||
|
||||
resume_btn = Button.new()
|
||||
resume_btn.text = "Resume"
|
||||
resume_btn.custom_minimum_size = Vector2(200, 50)
|
||||
main_vbox.add_child(resume_btn)
|
||||
|
||||
loadouts_btn = Button.new()
|
||||
loadouts_btn.text = "Loadouts"
|
||||
loadouts_btn.custom_minimum_size = Vector2(200, 50)
|
||||
main_vbox.add_child(loadouts_btn)
|
||||
|
||||
exit_btn = Button.new()
|
||||
exit_btn.text = "Exit Game"
|
||||
exit_btn.custom_minimum_size = Vector2(200, 50)
|
||||
main_vbox.add_child(exit_btn)
|
||||
|
||||
# Loadout Editor Root
|
||||
loadout_editor = Control.new()
|
||||
loadout_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
bg.add_child(loadout_editor)
|
||||
|
||||
var editor_hbox = HBoxContainer.new()
|
||||
editor_hbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||
editor_hbox.add_theme_constant_override("separation", 40)
|
||||
loadout_editor.add_child(editor_hbox)
|
||||
|
||||
# Loadout List
|
||||
var left_vbox = VBoxContainer.new()
|
||||
editor_hbox.add_child(left_vbox)
|
||||
|
||||
var list_title = Label.new()
|
||||
list_title.text = "Your Loadouts"
|
||||
list_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
left_vbox.add_child(list_title)
|
||||
|
||||
loadout_list_vbox = VBoxContainer.new()
|
||||
left_vbox.add_child(loadout_list_vbox)
|
||||
|
||||
back_to_main_btn = Button.new()
|
||||
back_to_main_btn.text = "Back to Menu"
|
||||
back_to_main_btn.custom_minimum_size = Vector2(0, 40)
|
||||
left_vbox.add_child(back_to_main_btn)
|
||||
|
||||
# Edit Panel
|
||||
edit_panel = PanelContainer.new()
|
||||
editor_hbox.add_child(edit_panel)
|
||||
|
||||
var edit_vbox = VBoxContainer.new()
|
||||
edit_vbox.add_theme_constant_override("separation", 10)
|
||||
edit_panel.add_child(edit_vbox)
|
||||
|
||||
var edit_title = Label.new()
|
||||
edit_title.text = "Edit Loadout"
|
||||
edit_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
edit_vbox.add_child(edit_title)
|
||||
|
||||
name_edit = LineEdit.new()
|
||||
name_edit.placeholder_text = "Loadout Name"
|
||||
name_edit.custom_minimum_size = Vector2(250, 0)
|
||||
edit_vbox.add_child(name_edit)
|
||||
|
||||
edit_vbox.add_child(_create_label("Primary 1:"))
|
||||
primary1_opt = OptionButton.new()
|
||||
edit_vbox.add_child(primary1_opt)
|
||||
|
||||
edit_vbox.add_child(_create_label("Primary 2:"))
|
||||
primary2_opt = OptionButton.new()
|
||||
edit_vbox.add_child(primary2_opt)
|
||||
|
||||
edit_vbox.add_child(_create_label("Special:"))
|
||||
special_opt = OptionButton.new()
|
||||
edit_vbox.add_child(special_opt)
|
||||
|
||||
save_loadout_btn = Button.new()
|
||||
save_loadout_btn.text = "Save Loadout"
|
||||
save_loadout_btn.custom_minimum_size = Vector2(0, 40)
|
||||
edit_vbox.add_child(save_loadout_btn)
|
||||
|
||||
# Quit Dialog
|
||||
quit_dialog = ConfirmationDialog.new()
|
||||
quit_dialog.title = "Quit Game"
|
||||
quit_dialog.dialog_text = "Are you sure you want to close the game?"
|
||||
add_child(quit_dialog)
|
||||
|
||||
func _create_label(text: String) -> Label:
|
||||
var l = Label.new()
|
||||
l.text = text
|
||||
return l
|
||||
|
||||
func _connect_signals() -> void:
|
||||
resume_btn.pressed.connect(_resume)
|
||||
loadouts_btn.pressed.connect(_show_loadouts)
|
||||
exit_btn.pressed.connect(_show_exit_dialog)
|
||||
back_to_main_btn.pressed.connect(_show_main_menu)
|
||||
save_loadout_btn.pressed.connect(_save_current_loadout)
|
||||
quit_dialog.confirmed.connect(_quit_game)
|
||||
|
||||
func _show_main_menu() -> void:
|
||||
main_vbox.visible = true
|
||||
loadout_editor.visible = false
|
||||
|
||||
func _show_loadouts() -> void:
|
||||
main_vbox.visible = false
|
||||
loadout_editor.visible = true
|
||||
edit_panel.visible = false
|
||||
_populate_loadout_list()
|
||||
|
||||
func _populate_loadout_list() -> void:
|
||||
for c in loadout_list_vbox.get_children():
|
||||
c.queue_free()
|
||||
|
||||
for i in range(LoadoutManager.loadouts.size()):
|
||||
var l = LoadoutManager.loadouts[i]
|
||||
var hbox = HBoxContainer.new()
|
||||
|
||||
var btn = Button.new()
|
||||
var is_active = (i == LoadoutManager.active_loadout_index)
|
||||
btn.text = l["name"] + (" (Active)" if is_active else "")
|
||||
btn.custom_minimum_size = Vector2(200, 40)
|
||||
btn.pressed.connect(func(): _edit_loadout(i))
|
||||
hbox.add_child(btn)
|
||||
|
||||
var equip_btn = Button.new()
|
||||
equip_btn.text = "Equip"
|
||||
equip_btn.disabled = is_active
|
||||
equip_btn.pressed.connect(func(): _equip_loadout(i))
|
||||
hbox.add_child(equip_btn)
|
||||
|
||||
loadout_list_vbox.add_child(hbox)
|
||||
|
||||
func _equip_loadout(idx: int) -> void:
|
||||
LoadoutManager.active_loadout_index = idx
|
||||
LoadoutManager.save_loadouts()
|
||||
_populate_loadout_list()
|
||||
|
||||
func _edit_loadout(idx: int) -> void:
|
||||
editing_index = idx
|
||||
var l = LoadoutManager.loadouts[idx]
|
||||
name_edit.text = l["name"]
|
||||
|
||||
_populate_options(primary1_opt, "primary", l["primary_1"])
|
||||
_populate_options(primary2_opt, "primary", l["primary_2"])
|
||||
_populate_options(special_opt, "special", l["special"])
|
||||
|
||||
edit_panel.visible = true
|
||||
|
||||
func _populate_options(opt: OptionButton, category: String, current_id: String) -> void:
|
||||
opt.clear()
|
||||
var idx = 0
|
||||
var select_idx = 0
|
||||
|
||||
# Always add "none" as first option
|
||||
opt.add_item("None", 0)
|
||||
opt.set_item_metadata(0, "none")
|
||||
if current_id == "none": select_idx = 0
|
||||
idx += 1
|
||||
|
||||
for key in LoadoutManager.weapon_db:
|
||||
if key == "none": continue
|
||||
var w = LoadoutManager.weapon_db[key]
|
||||
if w["category"] == category:
|
||||
opt.add_item(w["name"], idx)
|
||||
opt.set_item_metadata(idx, key)
|
||||
if key == current_id:
|
||||
select_idx = idx
|
||||
idx += 1
|
||||
|
||||
opt.select(select_idx)
|
||||
|
||||
func _save_current_loadout() -> void:
|
||||
if editing_index >= 0 and editing_index < LoadoutManager.loadouts.size():
|
||||
var l = LoadoutManager.loadouts[editing_index]
|
||||
l["name"] = name_edit.text
|
||||
l["primary_1"] = primary1_opt.get_item_metadata(primary1_opt.get_selected_id())
|
||||
l["primary_2"] = primary2_opt.get_item_metadata(primary2_opt.get_selected_id())
|
||||
l["special"] = special_opt.get_item_metadata(special_opt.get_selected_id())
|
||||
LoadoutManager.save_loadouts()
|
||||
_populate_loadout_list()
|
||||
edit_panel.visible = false
|
||||
|
||||
func _show_exit_dialog() -> void:
|
||||
quit_dialog.popup_centered()
|
||||
|
||||
func _quit_game() -> void:
|
||||
get_tree().quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcvjb1h4fxcct
|
||||
@@ -0,0 +1,78 @@
|
||||
extends Node3D
|
||||
class_name WeaponManager
|
||||
|
||||
var player: CharacterBody3D
|
||||
var camera: Camera3D
|
||||
|
||||
var active_slot: int = 1 # 1: Primary 1, 2: Primary 2, 3: Special
|
||||
var weapons: Dictionary = {} # slot_index: Node3D
|
||||
|
||||
func _ready() -> void:
|
||||
set_process_input(true)
|
||||
|
||||
# Wait one frame for LoadoutManager to be fully ready if needed
|
||||
await get_tree().process_frame
|
||||
_build_loadout()
|
||||
|
||||
func _build_loadout() -> void:
|
||||
# Clear existing weapons
|
||||
for w in weapons.values():
|
||||
if is_instance_valid(w):
|
||||
w.queue_free()
|
||||
weapons.clear()
|
||||
|
||||
var l = LoadoutManager.get_active_loadout()
|
||||
|
||||
_spawn_weapon(1, l["primary_1"])
|
||||
_spawn_weapon(2, l["primary_2"])
|
||||
_spawn_weapon(3, l["special"])
|
||||
|
||||
_equip_slot(1)
|
||||
|
||||
func _spawn_weapon(slot: int, weapon_id: String) -> void:
|
||||
if weapon_id == "" or weapon_id == "none" or not LoadoutManager.weapon_db.has(weapon_id):
|
||||
return
|
||||
|
||||
var w_data = LoadoutManager.weapon_db[weapon_id]
|
||||
var script_path = w_data["script"]
|
||||
if script_path == "":
|
||||
return
|
||||
|
||||
var script = load(script_path)
|
||||
if script:
|
||||
var w = script.new()
|
||||
w.name = "Weapon_" + str(slot) + "_" + weapon_id
|
||||
# Assuming all weapons have player and camera vars
|
||||
if "player" in w: w.player = player
|
||||
if "camera" in w: w.camera = camera
|
||||
add_child(w)
|
||||
weapons[slot] = w
|
||||
w.visible = false
|
||||
w.set_process_input(false)
|
||||
|
||||
func _equip_slot(slot: int) -> void:
|
||||
if weapons.has(active_slot):
|
||||
var w = weapons[active_slot]
|
||||
w.visible = false
|
||||
w.set_process_input(false)
|
||||
|
||||
active_slot = slot
|
||||
|
||||
if weapons.has(active_slot):
|
||||
var w = weapons[active_slot]
|
||||
w.visible = true
|
||||
w.set_process_input(true)
|
||||
print("Equipped slot ", slot)
|
||||
else:
|
||||
print("Slot ", slot, " is empty!")
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||
return
|
||||
|
||||
if event.is_action_pressed("weapon_1"):
|
||||
_equip_slot(1)
|
||||
elif event.is_action_pressed("weapon_2"):
|
||||
_equip_slot(2)
|
||||
elif event.is_action_pressed("weapon_3"):
|
||||
_equip_slot(3)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bp5ujeumiq5j
|
||||
Reference in New Issue
Block a user