ani
This commit is contained in:
+274
-215
@@ -4,17 +4,13 @@ var bg: ColorRect
|
||||
var main_vbox: VBoxContainer
|
||||
var resume_btn: Button
|
||||
var respawn_btn: Button
|
||||
var character_btn: Button
|
||||
var loadouts_btn: Button
|
||||
var settings_btn: Button
|
||||
var return_btn: Button
|
||||
var exit_btn: Button
|
||||
|
||||
# Character picker
|
||||
var character_editor: Control
|
||||
var character_list: ItemList
|
||||
# Character preview integrated into the loadout screen.
|
||||
var character_desc: Label
|
||||
var character_back_btn: Button
|
||||
var _preview_viewport: SubViewport
|
||||
var _preview_model: SkinnedPlayerModel
|
||||
var _preview_pivot: Node3D
|
||||
@@ -29,6 +25,9 @@ var primary1_opt: OptionButton
|
||||
var primary2_opt: OptionButton
|
||||
var special_opt: OptionButton
|
||||
var melee_opt: OptionButton
|
||||
var skin_opt: OptionButton
|
||||
var loadout_summary: Label
|
||||
var edit_title_label: Label
|
||||
var save_loadout_btn: Button
|
||||
var back_to_main_btn: Button
|
||||
var editing_index: int = -1
|
||||
@@ -164,11 +163,6 @@ func _build_ui() -> void:
|
||||
respawn_btn.custom_minimum_size = Vector2(260, 52)
|
||||
main_vbox.add_child(respawn_btn)
|
||||
|
||||
character_btn = Button.new()
|
||||
character_btn.text = "Character"
|
||||
character_btn.custom_minimum_size = Vector2(260, 52)
|
||||
main_vbox.add_child(character_btn)
|
||||
|
||||
loadouts_btn = Button.new()
|
||||
loadouts_btn.text = "Loadouts"
|
||||
loadouts_btn.custom_minimum_size = Vector2(260, 52)
|
||||
@@ -189,76 +183,125 @@ func _build_ui() -> void:
|
||||
exit_btn.custom_minimum_size = Vector2(260, 52)
|
||||
main_vbox.add_child(exit_btn)
|
||||
|
||||
_build_character_editor()
|
||||
|
||||
# ── Loadout Editor Root ──
|
||||
loadout_editor = Control.new()
|
||||
loadout_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
bg.add_child(loadout_editor)
|
||||
|
||||
var editor_center = CenterContainer.new()
|
||||
editor_center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
loadout_editor.add_child(editor_center)
|
||||
|
||||
var editor_hbox = HBoxContainer.new()
|
||||
editor_hbox.add_theme_constant_override("separation", 40)
|
||||
editor_center.add_child(editor_hbox)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
var screen_fill := ColorRect.new()
|
||||
screen_fill.color = Color(0.025, 0.030, 0.060, 0.96)
|
||||
screen_fill.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
loadout_editor.add_child(screen_fill)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
for side in ["left", "right"]:
|
||||
margin.add_theme_constant_override("margin_" + side, 42)
|
||||
margin.add_theme_constant_override("margin_top", 30)
|
||||
margin.add_theme_constant_override("margin_bottom", 30)
|
||||
loadout_editor.add_child(margin)
|
||||
|
||||
var screen_vbox := VBoxContainer.new()
|
||||
screen_vbox.add_theme_constant_override("separation", 22)
|
||||
margin.add_child(screen_vbox)
|
||||
|
||||
var header := HBoxContainer.new()
|
||||
screen_vbox.add_child(header)
|
||||
header.add_child(UITheme.title("LOADOUTS", 54))
|
||||
var header_spacer := Control.new()
|
||||
header_spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(header_spacer)
|
||||
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 = PanelContainer.new()
|
||||
back_to_main_btn.text = "Back"
|
||||
back_to_main_btn.custom_minimum_size = Vector2(170, 46)
|
||||
header.add_child(back_to_main_btn)
|
||||
screen_vbox.add_child(UITheme.divider(0.82))
|
||||
|
||||
var editor_hbox := HBoxContainer.new()
|
||||
editor_hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
editor_hbox.add_theme_constant_override("separation", 22)
|
||||
screen_vbox.add_child(editor_hbox)
|
||||
|
||||
var left_vbox := VBoxContainer.new()
|
||||
left_vbox.custom_minimum_size = Vector2(380, 0)
|
||||
left_vbox.add_theme_constant_override("separation", 12)
|
||||
editor_hbox.add_child(left_vbox)
|
||||
left_vbox.add_child(UITheme.heading("YOUR LOADOUTS", 24))
|
||||
var loadout_scroll := ScrollContainer.new()
|
||||
loadout_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
left_vbox.add_child(loadout_scroll)
|
||||
loadout_list_vbox = VBoxContainer.new()
|
||||
loadout_list_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
loadout_list_vbox.add_theme_constant_override("separation", 10)
|
||||
loadout_scroll.add_child(loadout_list_vbox)
|
||||
|
||||
edit_panel = UITheme.card()
|
||||
edit_panel.custom_minimum_size = Vector2(500, 0)
|
||||
edit_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
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)
|
||||
|
||||
|
||||
var edit_margin := MarginContainer.new()
|
||||
edit_margin.add_theme_constant_override("margin_left", 24)
|
||||
edit_margin.add_theme_constant_override("margin_right", 24)
|
||||
edit_margin.add_theme_constant_override("margin_top", 20)
|
||||
edit_margin.add_theme_constant_override("margin_bottom", 20)
|
||||
edit_panel.add_child(edit_margin)
|
||||
var edit_vbox := VBoxContainer.new()
|
||||
edit_vbox.add_theme_constant_override("separation", 12)
|
||||
edit_margin.add_child(edit_vbox)
|
||||
edit_title_label = UITheme.heading("EDIT LOADOUT", 28)
|
||||
edit_vbox.add_child(edit_title_label)
|
||||
edit_vbox.add_child(UITheme.divider(0.72))
|
||||
edit_vbox.add_child(UITheme.caption("LOADOUT NAME", 16))
|
||||
name_edit = LineEdit.new()
|
||||
name_edit.placeholder_text = "Loadout Name"
|
||||
name_edit.custom_minimum_size = Vector2(250, 0)
|
||||
name_edit.custom_minimum_size = Vector2(0, 44)
|
||||
edit_vbox.add_child(name_edit)
|
||||
|
||||
edit_vbox.add_child(_create_label("Primary 1:"))
|
||||
|
||||
var options_grid := GridContainer.new()
|
||||
options_grid.columns = 2
|
||||
options_grid.add_theme_constant_override("h_separation", 16)
|
||||
options_grid.add_theme_constant_override("v_separation", 12)
|
||||
edit_vbox.add_child(options_grid)
|
||||
options_grid.add_child(_create_label("Character / Skin"))
|
||||
skin_opt = OptionButton.new()
|
||||
skin_opt.custom_minimum_size = Vector2(285, 42)
|
||||
options_grid.add_child(skin_opt)
|
||||
options_grid.add_child(_create_label("Primary 1"))
|
||||
primary1_opt = OptionButton.new()
|
||||
edit_vbox.add_child(primary1_opt)
|
||||
|
||||
edit_vbox.add_child(_create_label("Primary 2:"))
|
||||
primary1_opt.custom_minimum_size = Vector2(285, 42)
|
||||
options_grid.add_child(primary1_opt)
|
||||
options_grid.add_child(_create_label("Primary 2"))
|
||||
primary2_opt = OptionButton.new()
|
||||
edit_vbox.add_child(primary2_opt)
|
||||
|
||||
edit_vbox.add_child(_create_label("Special:"))
|
||||
primary2_opt.custom_minimum_size = Vector2(285, 42)
|
||||
options_grid.add_child(primary2_opt)
|
||||
options_grid.add_child(_create_label("Special"))
|
||||
special_opt = OptionButton.new()
|
||||
edit_vbox.add_child(special_opt)
|
||||
|
||||
var melee_lbl = Label.new()
|
||||
melee_lbl.text = "Melee"
|
||||
edit_vbox.add_child(melee_lbl)
|
||||
special_opt.custom_minimum_size = Vector2(285, 42)
|
||||
options_grid.add_child(special_opt)
|
||||
options_grid.add_child(_create_label("Melee"))
|
||||
melee_opt = OptionButton.new()
|
||||
edit_vbox.add_child(melee_opt)
|
||||
|
||||
melee_opt.custom_minimum_size = Vector2(285, 42)
|
||||
options_grid.add_child(melee_opt)
|
||||
|
||||
edit_vbox.add_child(UITheme.caption("SELECTED ITEMS", 16))
|
||||
loadout_summary = Label.new()
|
||||
loadout_summary.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
loadout_summary.custom_minimum_size = Vector2(0, 92)
|
||||
loadout_summary.add_theme_font_size_override("font_size", 18)
|
||||
edit_vbox.add_child(loadout_summary)
|
||||
var edit_spacer := Control.new()
|
||||
edit_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
edit_vbox.add_child(edit_spacer)
|
||||
save_loadout_btn = Button.new()
|
||||
save_loadout_btn.text = "Save Loadout"
|
||||
save_loadout_btn.custom_minimum_size = Vector2(0, 40)
|
||||
save_loadout_btn.custom_minimum_size = Vector2(0, 48)
|
||||
edit_vbox.add_child(save_loadout_btn)
|
||||
|
||||
var preview_card := UITheme.card()
|
||||
preview_card.custom_minimum_size = Vector2(440, 0)
|
||||
editor_hbox.add_child(preview_card)
|
||||
_build_loadout_character_preview(preview_card)
|
||||
|
||||
# ── Settings Editor Root ──
|
||||
settings_editor = Control.new()
|
||||
@@ -721,150 +764,63 @@ func _build_ui() -> void:
|
||||
# per-class outlines, the same cloth and hair on springs. A thumbnail would go
|
||||
# stale the first time a character was re-imported.
|
||||
|
||||
const PREVIEW_SIZE := Vector2i(520, 640)
|
||||
const PREVIEW_SIZE := Vector2i(390, 560)
|
||||
|
||||
func _build_character_editor() -> void:
|
||||
character_editor = Control.new()
|
||||
character_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
character_editor.visible = false
|
||||
bg.add_child(character_editor)
|
||||
|
||||
var center = CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
character_editor.add_child(center)
|
||||
|
||||
var row = HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 34)
|
||||
center.add_child(row)
|
||||
|
||||
# Left: the roster
|
||||
var left = VBoxContainer.new()
|
||||
left.add_theme_constant_override("separation", 14)
|
||||
left.custom_minimum_size = Vector2(340, 0)
|
||||
row.add_child(left)
|
||||
|
||||
var heading := UITheme.title("CHARACTER", 52)
|
||||
left.add_child(heading)
|
||||
left.add_child(UITheme.divider(0.28))
|
||||
|
||||
character_list = ItemList.new()
|
||||
character_list.custom_minimum_size = Vector2(340, 380)
|
||||
character_list.auto_height = false
|
||||
left.add_child(character_list)
|
||||
|
||||
character_desc = UITheme.heading("", 20)
|
||||
func _build_loadout_character_preview(parent: PanelContainer) -> void:
|
||||
var preview_margin := MarginContainer.new()
|
||||
preview_margin.add_theme_constant_override("margin_left", 18)
|
||||
preview_margin.add_theme_constant_override("margin_right", 18)
|
||||
preview_margin.add_theme_constant_override("margin_top", 18)
|
||||
preview_margin.add_theme_constant_override("margin_bottom", 18)
|
||||
parent.add_child(preview_margin)
|
||||
var column := VBoxContainer.new()
|
||||
column.add_theme_constant_override("separation", 12)
|
||||
preview_margin.add_child(column)
|
||||
column.add_child(UITheme.heading("CHARACTER PREVIEW", 24))
|
||||
character_desc = UITheme.caption("", 17)
|
||||
character_desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
character_desc.custom_minimum_size = Vector2(340, 60)
|
||||
left.add_child(character_desc)
|
||||
character_desc.custom_minimum_size = Vector2(0, 48)
|
||||
column.add_child(character_desc)
|
||||
|
||||
character_back_btn = Button.new()
|
||||
character_back_btn.text = "Back"
|
||||
character_back_btn.custom_minimum_size = Vector2(0, 46)
|
||||
left.add_child(character_back_btn)
|
||||
|
||||
# Right: the character, in a real 3D viewport
|
||||
var frame := UITheme.card()
|
||||
row.add_child(frame)
|
||||
|
||||
var vp_container = SubViewportContainer.new()
|
||||
var vp_container := SubViewportContainer.new()
|
||||
vp_container.stretch = true
|
||||
vp_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
vp_container.custom_minimum_size = Vector2(PREVIEW_SIZE)
|
||||
frame.add_child(vp_container)
|
||||
|
||||
column.add_child(vp_container)
|
||||
_preview_viewport = SubViewport.new()
|
||||
_preview_viewport.own_world_3d = true
|
||||
_preview_viewport.size = PREVIEW_SIZE
|
||||
_preview_viewport.msaa_3d = Viewport.MSAA_4X
|
||||
_preview_viewport.transparent_bg = true
|
||||
# The tree is paused while this menu is up. Without this the preview would
|
||||
# be a still frame: no turntable, and — worse — no spring solver, so the
|
||||
# character's hair and skirt would hang in their bind pose.
|
||||
_preview_viewport.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
vp_container.add_child(_preview_viewport)
|
||||
|
||||
var world = Node3D.new()
|
||||
var world := Node3D.new()
|
||||
_preview_viewport.add_child(world)
|
||||
|
||||
var env = WorldEnvironment.new()
|
||||
var env := WorldEnvironment.new()
|
||||
env.environment = LevelEnvironment.make_environment("sunset")
|
||||
world.add_child(env)
|
||||
|
||||
# Three-point-ish lighting, warm key and cool fill, which is what makes a
|
||||
# cel-shaded character read as rounded instead of as a sticker.
|
||||
var key = DirectionalLight3D.new()
|
||||
var key := DirectionalLight3D.new()
|
||||
key.rotation_degrees = Vector3(-28, 38, 0)
|
||||
key.light_color = Color(1.0, 0.93, 0.84)
|
||||
key.light_energy = 1.6
|
||||
world.add_child(key)
|
||||
|
||||
var fill = DirectionalLight3D.new()
|
||||
var fill := DirectionalLight3D.new()
|
||||
fill.rotation_degrees = Vector3(-16, -128, 0)
|
||||
fill.light_color = Color(0.48, 0.62, 1.0)
|
||||
fill.light_energy = 0.55
|
||||
world.add_child(fill)
|
||||
|
||||
_preview_pivot = Node3D.new()
|
||||
# Turned to face the camera. SkinnedPlayerModel yaws itself 180° on load
|
||||
# because glTF forward is +Z and players face -Z, which is right in a level
|
||||
# and means a preview camera sitting on +Z gets the back of the character's
|
||||
# head. The turntable starts from here.
|
||||
_preview_pivot.rotation_degrees.y = 180.0
|
||||
world.add_child(_preview_pivot)
|
||||
|
||||
var camera = Camera3D.new()
|
||||
var camera := Camera3D.new()
|
||||
camera.position = Vector3(0, 1.05, 2.65)
|
||||
camera.rotation_degrees = Vector3(-4, 0, 0)
|
||||
camera.fov = 38.0
|
||||
world.add_child(camera)
|
||||
|
||||
|
||||
func _show_character() -> void:
|
||||
main_vbox.visible = false
|
||||
loadout_editor.visible = false
|
||||
settings_editor.visible = false
|
||||
character_editor.visible = true
|
||||
_populate_character_list()
|
||||
|
||||
|
||||
func _populate_character_list() -> void:
|
||||
character_list.clear()
|
||||
var active: String = SkinManager.active_skin_id
|
||||
var selected := -1
|
||||
for id in SkinManager.get_skin_ids():
|
||||
var skin = SkinManager.get_skin(id)
|
||||
if not skin.is_unlocked:
|
||||
continue
|
||||
var idx := character_list.add_item(skin.skin_name)
|
||||
character_list.set_item_metadata(idx, id)
|
||||
if id == active:
|
||||
selected = idx
|
||||
if selected >= 0:
|
||||
character_list.select(selected)
|
||||
_on_character_selected(selected)
|
||||
elif character_list.item_count > 0:
|
||||
character_list.select(0)
|
||||
_on_character_selected(0)
|
||||
|
||||
|
||||
## Selecting a character equips it immediately — there is no separate confirm.
|
||||
##
|
||||
## A picker that needs a second click to take effect makes you compare a preview
|
||||
## against a memory of the last one. Applying on selection means the character
|
||||
## behind the menu changes as you arrow through the list, which IS the
|
||||
## comparison. Nothing here is destructive, so there is nothing to confirm.
|
||||
func _on_character_selected(index: int) -> void:
|
||||
if index < 0 or index >= character_list.item_count:
|
||||
return
|
||||
var id: String = character_list.get_item_metadata(index)
|
||||
var skin = SkinManager.get_skin(id)
|
||||
character_desc.text = skin.description if skin.description != "" else skin.skin_name
|
||||
_load_preview(id, skin)
|
||||
SkinManager.set_active_skin(id)
|
||||
var player := _local_player()
|
||||
if player and player.has_method("set_skin"):
|
||||
player.set_skin(id)
|
||||
|
||||
|
||||
## Build the turntable model for one skin. A colour-tint skin has no GLB, so the
|
||||
## preview is emptied rather than left showing whoever was selected before it.
|
||||
func _load_preview(id: String, skin) -> void:
|
||||
@@ -885,6 +841,7 @@ func _load_preview(id: String, skin) -> void:
|
||||
var model := SkinnedPlayerModel.new()
|
||||
model.model_path = skin.model_path
|
||||
model.skin_id = id
|
||||
model.mecha_theme = str(skin.get("mecha_theme"))
|
||||
# Not first-person and not shadows-only: this is the one place the local
|
||||
# player is supposed to see their own character in full.
|
||||
model.first_person_mode = false
|
||||
@@ -892,13 +849,14 @@ func _load_preview(id: String, skin) -> void:
|
||||
model.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_preview_pivot.add_child(model)
|
||||
_preview_model = model
|
||||
_refresh_preview_weapon()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# Turntable. Only while the picker is actually on screen — the rest of the
|
||||
# time this node is an invisible autoload and has no business spending
|
||||
# frames.
|
||||
if character_editor and character_editor.visible and is_instance_valid(_preview_pivot):
|
||||
if loadout_editor and loadout_editor.visible and is_instance_valid(_preview_pivot):
|
||||
_preview_pivot.rotate_y(delta * 0.45)
|
||||
|
||||
|
||||
@@ -962,9 +920,6 @@ func _create_label(text: String) -> Label:
|
||||
func _connect_signals() -> void:
|
||||
resume_btn.pressed.connect(_resume)
|
||||
respawn_btn.pressed.connect(_respawn)
|
||||
character_btn.pressed.connect(_show_character)
|
||||
character_back_btn.pressed.connect(_show_main_menu)
|
||||
character_list.item_selected.connect(_on_character_selected)
|
||||
loadouts_btn.pressed.connect(_show_loadouts)
|
||||
settings_btn.pressed.connect(_show_settings)
|
||||
return_btn.pressed.connect(_show_return_dialog)
|
||||
@@ -972,6 +927,9 @@ func _connect_signals() -> void:
|
||||
back_to_main_btn.pressed.connect(_show_main_menu)
|
||||
settings_back_btn.pressed.connect(_show_main_menu)
|
||||
save_loadout_btn.pressed.connect(_save_current_loadout)
|
||||
skin_opt.item_selected.connect(_on_skin_option_selected)
|
||||
for option in [primary1_opt, primary2_opt, special_opt, melee_opt]:
|
||||
option.item_selected.connect(func(_index: int): _refresh_loadout_summary())
|
||||
quit_dialog.confirmed.connect(_quit_game)
|
||||
return_dialog.confirmed.connect(_return_to_main_menu)
|
||||
|
||||
@@ -993,15 +951,14 @@ func _show_main_menu() -> void:
|
||||
main_vbox.visible = true
|
||||
loadout_editor.visible = false
|
||||
settings_editor.visible = false
|
||||
character_editor.visible = false
|
||||
|
||||
func _show_loadouts() -> void:
|
||||
main_vbox.visible = false
|
||||
settings_editor.visible = false
|
||||
character_editor.visible = false
|
||||
loadout_editor.visible = true
|
||||
edit_panel.visible = false
|
||||
_populate_loadout_list()
|
||||
var index := clampi(LoadoutManager.active_loadout_index, 0, LoadoutManager.loadouts.size() - 1)
|
||||
_edit_loadout(index)
|
||||
|
||||
func _populate_video_ui() -> void:
|
||||
if _display_mode_opt:
|
||||
@@ -1032,7 +989,6 @@ func _populate_audio_ui() -> void:
|
||||
func _show_settings() -> void:
|
||||
main_vbox.visible = false
|
||||
loadout_editor.visible = false
|
||||
character_editor.visible = false
|
||||
settings_editor.visible = true
|
||||
_populate_keybindings()
|
||||
_populate_video_ui()
|
||||
@@ -1102,43 +1058,142 @@ func _get_key_name(action: String) -> String:
|
||||
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()
|
||||
var loadout: Dictionary = LoadoutManager.loadouts[i]
|
||||
var loadout_index := i
|
||||
var is_active := loadout_index == LoadoutManager.active_loadout_index
|
||||
var card := UITheme.card()
|
||||
card.custom_minimum_size = Vector2(350, 138)
|
||||
loadout_list_vbox.add_child(card)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
for side in ["left", "right", "top", "bottom"]:
|
||||
margin.add_theme_constant_override("margin_" + side, 14)
|
||||
card.add_child(margin)
|
||||
var content := VBoxContainer.new()
|
||||
content.add_theme_constant_override("separation", 7)
|
||||
margin.add_child(content)
|
||||
|
||||
var heading := UITheme.heading(
|
||||
str(loadout.get("name", "Loadout")) + (" • ACTIVE" if is_active else ""), 21
|
||||
)
|
||||
content.add_child(heading)
|
||||
var details := Label.new()
|
||||
details.text = "%s\n%s · %s\n%s · %s" % [
|
||||
_skin_name(str(loadout.get("skin", "default"))),
|
||||
_weapon_name(str(loadout.get("primary_1", "none"))),
|
||||
_weapon_name(str(loadout.get("primary_2", "none"))),
|
||||
_weapon_name(str(loadout.get("special", "none"))),
|
||||
_weapon_name(str(loadout.get("melee", "none"))),
|
||||
]
|
||||
details.modulate = Color(0.82, 0.86, 0.94)
|
||||
content.add_child(details)
|
||||
|
||||
var actions := HBoxContainer.new()
|
||||
actions.add_theme_constant_override("separation", 8)
|
||||
content.add_child(actions)
|
||||
var edit_btn := Button.new()
|
||||
edit_btn.text = "Edit"
|
||||
edit_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
edit_btn.pressed.connect(func(): _edit_loadout(loadout_index))
|
||||
actions.add_child(edit_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)
|
||||
equip_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
equip_btn.pressed.connect(func(): _equip_loadout(loadout_index))
|
||||
actions.add_child(equip_btn)
|
||||
|
||||
func _equip_loadout(idx: int) -> void:
|
||||
LoadoutManager.active_loadout_index = idx
|
||||
LoadoutManager.save_loadouts()
|
||||
if not LoadoutManager.set_active_loadout(idx):
|
||||
return
|
||||
_apply_active_loadout_skin()
|
||||
_populate_loadout_list()
|
||||
_edit_loadout(idx)
|
||||
|
||||
func _edit_loadout(idx: int) -> void:
|
||||
if idx < 0 or idx >= LoadoutManager.loadouts.size():
|
||||
return
|
||||
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"])
|
||||
if l.has("melee"):
|
||||
_populate_options(melee_opt, "melee", l["melee"])
|
||||
|
||||
var loadout: Dictionary = LoadoutManager.loadouts[idx]
|
||||
name_edit.text = str(loadout.get("name", "Loadout"))
|
||||
edit_title_label.text = "EDIT " + name_edit.text.to_upper()
|
||||
_populate_skin_options(str(loadout.get("skin", "default")))
|
||||
_populate_options(primary1_opt, "primary", str(loadout.get("primary_1", "none")))
|
||||
_populate_options(primary2_opt, "primary", str(loadout.get("primary_2", "none")))
|
||||
_populate_options(special_opt, "special", str(loadout.get("special", "none")))
|
||||
_populate_options(melee_opt, "melee", str(loadout.get("melee", "none")))
|
||||
edit_panel.visible = true
|
||||
_on_skin_option_selected(skin_opt.selected)
|
||||
_refresh_loadout_summary()
|
||||
|
||||
func _populate_skin_options(current_id: String) -> void:
|
||||
skin_opt.clear()
|
||||
var skin_ids: Array = SkinManager.get_skin_ids()
|
||||
skin_ids.sort()
|
||||
var selected_index := 0
|
||||
for id in skin_ids:
|
||||
var skin = SkinManager.get_skin(id)
|
||||
if skin == null or not skin.is_unlocked:
|
||||
continue
|
||||
var item_index := skin_opt.item_count
|
||||
skin_opt.add_item(skin.skin_name)
|
||||
skin_opt.set_item_metadata(item_index, id)
|
||||
if id == current_id:
|
||||
selected_index = item_index
|
||||
if skin_opt.item_count > 0:
|
||||
skin_opt.select(selected_index)
|
||||
|
||||
func _on_skin_option_selected(index: int) -> void:
|
||||
if index < 0 or index >= skin_opt.item_count:
|
||||
return
|
||||
var skin_id := str(skin_opt.get_item_metadata(index))
|
||||
var skin = SkinManager.get_skin(skin_id)
|
||||
character_desc.text = skin.description if skin.description != "" else skin.skin_name
|
||||
_load_preview(skin_id, skin)
|
||||
_refresh_loadout_summary()
|
||||
|
||||
func _selected_metadata(option: OptionButton) -> String:
|
||||
if option == null or option.selected < 0 or option.selected >= option.item_count:
|
||||
return "none"
|
||||
return str(option.get_item_metadata(option.selected))
|
||||
|
||||
func _weapon_name(id: String) -> String:
|
||||
var weapon: Dictionary = LoadoutManager.weapon_db.get(id, LoadoutManager.weapon_db["none"])
|
||||
return str(weapon.get("name", id))
|
||||
|
||||
func _skin_name(id: String) -> String:
|
||||
var skin = SkinManager.get_skin(id)
|
||||
return skin.skin_name if skin != null else id
|
||||
|
||||
func _refresh_loadout_summary() -> void:
|
||||
if loadout_summary == null:
|
||||
return
|
||||
loadout_summary.text = "CHARACTER %s\nPRIMARY %s · %s\nSPECIAL %s\nMELEE %s" % [
|
||||
_skin_name(_selected_metadata(skin_opt)),
|
||||
_weapon_name(_selected_metadata(primary1_opt)),
|
||||
_weapon_name(_selected_metadata(primary2_opt)),
|
||||
_weapon_name(_selected_metadata(special_opt)),
|
||||
_weapon_name(_selected_metadata(melee_opt)),
|
||||
]
|
||||
_refresh_preview_weapon()
|
||||
|
||||
func _refresh_preview_weapon() -> void:
|
||||
if not is_instance_valid(_preview_model):
|
||||
return
|
||||
var weapon_id := _selected_metadata(primary1_opt)
|
||||
if weapon_id == "none":
|
||||
weapon_id = _selected_metadata(primary2_opt)
|
||||
var weapon: Dictionary = LoadoutManager.weapon_db.get(weapon_id, LoadoutManager.weapon_db["none"])
|
||||
_preview_model.set_weapon(str(weapon.get("script", "")))
|
||||
|
||||
func _apply_active_loadout_skin() -> void:
|
||||
var skin_id := LoadoutManager.get_active_skin_id()
|
||||
SkinManager.set_active_skin(skin_id)
|
||||
var player := _local_player()
|
||||
if player and player.has_method("set_skin"):
|
||||
player.set_skin(skin_id)
|
||||
|
||||
func _populate_options(opt: OptionButton, category: String, current_id: String) -> void:
|
||||
opt.clear()
|
||||
@@ -1164,17 +1219,21 @@ func _populate_options(opt: OptionButton, category: String, current_id: String)
|
||||
|
||||
func _save_current_loadout() -> void:
|
||||
if editing_index >= 0 and editing_index < LoadoutManager.loadouts.size():
|
||||
var l = LoadoutManager.loadouts[editing_index]
|
||||
var l: Dictionary = LoadoutManager.loadouts[editing_index]
|
||||
if name_edit.text.strip_edges() != "":
|
||||
l["name"] = name_edit.text.strip_edges()
|
||||
name_edit.text = l["name"]
|
||||
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())
|
||||
l["melee"] = melee_opt.get_item_metadata(melee_opt.get_selected_id())
|
||||
l["skin"] = _selected_metadata(skin_opt)
|
||||
l["primary_1"] = _selected_metadata(primary1_opt)
|
||||
l["primary_2"] = _selected_metadata(primary2_opt)
|
||||
l["special"] = _selected_metadata(special_opt)
|
||||
l["melee"] = _selected_metadata(melee_opt)
|
||||
LoadoutManager.save_loadouts()
|
||||
if editing_index == LoadoutManager.active_loadout_index:
|
||||
_apply_active_loadout_skin()
|
||||
_populate_loadout_list()
|
||||
edit_panel.visible = false
|
||||
edit_title_label.text = "EDIT " + str(l["name"]).to_upper()
|
||||
_refresh_loadout_summary()
|
||||
|
||||
func _show_exit_dialog() -> void:
|
||||
quit_dialog.popup_centered()
|
||||
|
||||
Reference in New Issue
Block a user