Audio: - All ballistic weapons now fire real firearm recordings (The Free Firearm Sound Library, CC0): AK-47 uses actual AK-47 shots, M4=AR-15, MP7=PPSh, AWP=Mosin Nagant, DMR=Savage 10, shotgun=Mossberg/Nova, nailgun=Ruger .22, rocket/mortar layered with real booms. Plasma keeps its energy sound. - Reload is real lever-action foley instead of interface clicks - Projectile flight loops localized: unit_size 50->6, max_db 6->0, max_distance 45 — no more mortar/rocket drone in every player's ear; loops also got proper loop_end (was a zero-length loop) - Flight loops lowpassed into soft whooshes instead of sci-fi engine drones - Menu music converted to OGG with native full-track looping (47s seamless, replaces the broken manual WAV loop points) Animations (movement-shooter pass): - Rebaked miku.glb with 4 new clips from the CC0 library: Grapple (swim reach — reads as a swing), PistolIdle (armed idle with weapon up), PistolShoot, PistolReload - Armed idle: standing with a weapon now uses PistolIdle instead of arms-at-sides - Slide pose overhaul: legs kick out front (lead/trail thighs, knees straighten) — feet-first slide instead of "sitting in a crouch" - Wall run: forward drive pitch + inner arm reaches out to the wall - Per-transition blend times: dash/hit/jump cut fast (0.05-0.08s), locomotion cross-fades smoothly (0.2-0.25s); CrouchWalk speed-scales Fixes: - Third-person camera no longer renders the first-person viewmodel layer (the gun/arms floated in front of the third-person view) Verified: 11/11 movement tests, smoke test 0 failures (17 clips resolved), third-person run screenshot shows grounded animated model, no floating gun. Co-Authored-By: Claude Fable 5 <[email protected]>
603 lines
21 KiB
GDScript
603 lines
21 KiB
GDScript
extends Control
|
|
|
|
var _ui_vbox: VBoxContainer
|
|
var _level_selector_panel: VBoxContainer
|
|
var _multiplayer_panel: VBoxContainer
|
|
var _direct_connect_panel: VBoxContainer
|
|
var _host_lobby_panel: Control
|
|
var _host_lobby_players_list: ItemList
|
|
|
|
func _ready() -> void:
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
|
|
# Comic/cel theme for every Control in the game (root window inherits down)
|
|
UITheme.apply_global(get_tree())
|
|
|
|
# Menu music (scene-owned: stops automatically when a level loads).
|
|
# OGG loops natively over the full track — no manual loop points.
|
|
var music_path := "res://assets/sounds/music_menu.ogg"
|
|
if ResourceLoader.exists(music_path):
|
|
var music := AudioStreamPlayer.new()
|
|
music.stream = load(music_path)
|
|
if music.stream is AudioStreamOggVorbis:
|
|
music.stream.loop = true
|
|
music.bus = "Music"
|
|
music.volume_db = -8.0
|
|
music.autoplay = true
|
|
add_child(music)
|
|
|
|
var nm = get_node_or_null("/root/NetworkManager")
|
|
if nm:
|
|
nm.connection_succeeded.connect(_on_connection_succeeded)
|
|
|
|
# ==========================================
|
|
# BACKGROUND: 3D DIORAMA (Sliding Player)
|
|
# ==========================================
|
|
var right_panel = SubViewportContainer.new()
|
|
right_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
right_panel.stretch = true
|
|
right_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(right_panel)
|
|
|
|
var viewport = SubViewport.new()
|
|
viewport.own_world_3d = true
|
|
viewport.msaa_3d = Viewport.MSAA_4X
|
|
right_panel.add_child(viewport)
|
|
|
|
_build_3d_diorama(viewport)
|
|
|
|
# ==========================================
|
|
# FOREGROUND: UI (Title and Buttons)
|
|
# ==========================================
|
|
_ui_vbox = VBoxContainer.new()
|
|
_ui_vbox.add_theme_constant_override("separation", 30)
|
|
_ui_vbox.set_anchor_and_offset(SIDE_LEFT, 0.0, 50)
|
|
_ui_vbox.set_anchor_and_offset(SIDE_TOP, 0.0, 50)
|
|
_ui_vbox.set_anchor_and_offset(SIDE_RIGHT, 0.0, 600)
|
|
_ui_vbox.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -50)
|
|
add_child(_ui_vbox)
|
|
|
|
# Title — big comic wordmark with a slight kinetic tilt
|
|
var title = Label.new()
|
|
title.text = "PAPAYA SHOOTER"
|
|
title.add_theme_font_size_override("font_size", 92)
|
|
title.add_theme_color_override("font_color", UITheme.PAPAYA)
|
|
title.add_theme_color_override("font_outline_color", UITheme.INK)
|
|
title.add_theme_constant_override("outline_size", 16)
|
|
title.rotation_degrees = -2.5
|
|
_ui_vbox.add_child(title)
|
|
|
|
# Spacer
|
|
var spacer = Control.new()
|
|
spacer.custom_minimum_size = Vector2(0, 40)
|
|
_ui_vbox.add_child(spacer)
|
|
|
|
# Buttons
|
|
_add_button(_ui_vbox, "Singleplayer", _on_singleplayer_pressed)
|
|
_add_button(_ui_vbox, "Multiplayer", _on_multiplayer_pressed)
|
|
_add_button(_ui_vbox, "Loadouts", _on_loadouts_pressed)
|
|
_add_button(_ui_vbox, "Settings", _on_settings_pressed)
|
|
_add_button(_ui_vbox, "Exit Game", _on_exit_pressed)
|
|
|
|
# Skin selector (skins come from SkinManager: built-ins + skins.json)
|
|
_build_skin_selector(_ui_vbox)
|
|
|
|
# ==========================================
|
|
# LEVEL SELECTOR PANEL
|
|
# ==========================================
|
|
_level_selector_panel = VBoxContainer.new()
|
|
_level_selector_panel.add_theme_constant_override("separation", 20)
|
|
_level_selector_panel.set_anchor_and_offset(SIDE_LEFT, 0.0, 50)
|
|
_level_selector_panel.set_anchor_and_offset(SIDE_TOP, 0.0, 50)
|
|
_level_selector_panel.set_anchor_and_offset(SIDE_RIGHT, 0.0, 800)
|
|
_level_selector_panel.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -50)
|
|
_level_selector_panel.hide()
|
|
add_child(_level_selector_panel)
|
|
|
|
var ls_title = Label.new()
|
|
ls_title.text = "Select Level"
|
|
ls_title.add_theme_font_size_override("font_size", 64)
|
|
ls_title.add_theme_color_override("font_color", Color(1, 1, 1))
|
|
ls_title.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
|
ls_title.add_theme_constant_override("outline_size", 8)
|
|
_level_selector_panel.add_child(ls_title)
|
|
|
|
# Cards Container
|
|
var cards_hbox = HBoxContainer.new()
|
|
cards_hbox.add_theme_constant_override("separation", 30)
|
|
cards_hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
_level_selector_panel.add_child(cards_hbox)
|
|
|
|
_load_maps_modularly(cards_hbox, false, Vector2(300, 400))
|
|
|
|
var ls_spacer = Control.new()
|
|
ls_spacer.custom_minimum_size = Vector2(0, 20)
|
|
_level_selector_panel.add_child(ls_spacer)
|
|
|
|
_add_button(_level_selector_panel, "Back", _on_back_pressed)
|
|
|
|
# ==========================================
|
|
# MULTIPLAYER PANEL
|
|
# ==========================================
|
|
_multiplayer_panel = VBoxContainer.new()
|
|
_multiplayer_panel.add_theme_constant_override("separation", 20)
|
|
_multiplayer_panel.set_anchor_and_offset(SIDE_LEFT, 0.0, 50)
|
|
_multiplayer_panel.set_anchor_and_offset(SIDE_TOP, 0.0, 50)
|
|
_multiplayer_panel.set_anchor_and_offset(SIDE_RIGHT, 0.0, 600)
|
|
_multiplayer_panel.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -50)
|
|
_multiplayer_panel.hide()
|
|
add_child(_multiplayer_panel)
|
|
|
|
var mp_title = Label.new()
|
|
mp_title.text = "Multiplayer"
|
|
mp_title.add_theme_font_size_override("font_size", 64)
|
|
mp_title.add_theme_color_override("font_color", Color(1, 1, 1))
|
|
mp_title.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
|
mp_title.add_theme_constant_override("outline_size", 8)
|
|
_multiplayer_panel.add_child(mp_title)
|
|
|
|
var mp_spacer = Control.new()
|
|
mp_spacer.custom_minimum_size = Vector2(0, 20)
|
|
_multiplayer_panel.add_child(mp_spacer)
|
|
|
|
_add_button(_multiplayer_panel, "Host Game", _on_host_pressed)
|
|
_add_button(_multiplayer_panel, "Direct Connect", _on_direct_connect_pressed)
|
|
_add_button(_multiplayer_panel, "Back", _on_multiplayer_back_pressed)
|
|
|
|
# ==========================================
|
|
# DIRECT CONNECT PANEL
|
|
# ==========================================
|
|
_direct_connect_panel = VBoxContainer.new()
|
|
_direct_connect_panel.add_theme_constant_override("separation", 20)
|
|
_direct_connect_panel.set_anchor_and_offset(SIDE_LEFT, 0.0, 50)
|
|
_direct_connect_panel.set_anchor_and_offset(SIDE_TOP, 0.0, 50)
|
|
_direct_connect_panel.set_anchor_and_offset(SIDE_RIGHT, 0.0, 600)
|
|
_direct_connect_panel.set_anchor_and_offset(SIDE_BOTTOM, 1.0, -50)
|
|
_direct_connect_panel.hide()
|
|
add_child(_direct_connect_panel)
|
|
|
|
var dc_title = Label.new()
|
|
dc_title.text = "Direct Connect"
|
|
dc_title.add_theme_font_size_override("font_size", 64)
|
|
dc_title.add_theme_color_override("font_color", Color(1, 1, 1))
|
|
dc_title.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
|
dc_title.add_theme_constant_override("outline_size", 8)
|
|
_direct_connect_panel.add_child(dc_title)
|
|
|
|
var dc_ip_label = Label.new()
|
|
dc_ip_label.text = "IP Address:"
|
|
dc_ip_label.add_theme_font_size_override("font_size", 24)
|
|
_direct_connect_panel.add_child(dc_ip_label)
|
|
|
|
var dc_ip_input = LineEdit.new()
|
|
dc_ip_input.text = "127.0.0.1"
|
|
dc_ip_input.name = "IPInput"
|
|
dc_ip_input.add_theme_font_size_override("font_size", 24)
|
|
dc_ip_input.custom_minimum_size = Vector2(300, 50)
|
|
_direct_connect_panel.add_child(dc_ip_input)
|
|
|
|
var dc_port_label = Label.new()
|
|
dc_port_label.text = "Port:"
|
|
dc_port_label.add_theme_font_size_override("font_size", 24)
|
|
_direct_connect_panel.add_child(dc_port_label)
|
|
|
|
var dc_port_input = LineEdit.new()
|
|
dc_port_input.text = "31415" # Default Port
|
|
dc_port_input.name = "PortInput"
|
|
dc_port_input.add_theme_font_size_override("font_size", 24)
|
|
dc_port_input.custom_minimum_size = Vector2(300, 50)
|
|
_direct_connect_panel.add_child(dc_port_input)
|
|
|
|
var dc_spacer = Control.new()
|
|
dc_spacer.custom_minimum_size = Vector2(0, 20)
|
|
_direct_connect_panel.add_child(dc_spacer)
|
|
|
|
_add_button(_direct_connect_panel, "Connect", _on_connect_confirm_pressed)
|
|
_add_button(_direct_connect_panel, "Back", _on_dc_back_pressed)
|
|
|
|
# ==========================================
|
|
# HOST LOBBY PANEL
|
|
# ==========================================
|
|
_host_lobby_panel = Control.new()
|
|
_host_lobby_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_host_lobby_panel.hide()
|
|
add_child(_host_lobby_panel)
|
|
|
|
var hl_hbox = HBoxContainer.new()
|
|
hl_hbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
hl_hbox.add_theme_constant_override("separation", 50)
|
|
_host_lobby_panel.add_child(hl_hbox)
|
|
|
|
# Left Side (Level Selector)
|
|
var hl_left = VBoxContainer.new()
|
|
hl_left.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
hl_left.set_anchor_and_offset(SIDE_LEFT, 0.0, 50)
|
|
hl_left.set_anchor_and_offset(SIDE_TOP, 0.0, 50)
|
|
hl_left.add_theme_constant_override("separation", 20)
|
|
|
|
var hl_margin = MarginContainer.new()
|
|
hl_margin.add_theme_constant_override("margin_left", 50)
|
|
hl_margin.add_theme_constant_override("margin_top", 50)
|
|
hl_margin.add_theme_constant_override("margin_bottom", 50)
|
|
hl_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
hl_hbox.add_child(hl_margin)
|
|
hl_margin.add_child(hl_left)
|
|
|
|
var hl_title = Label.new()
|
|
hl_title.text = "Host Lobby - Select Level"
|
|
hl_title.add_theme_font_size_override("font_size", 64)
|
|
hl_title.add_theme_color_override("font_color", Color(1, 1, 1))
|
|
hl_title.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
|
hl_title.add_theme_constant_override("outline_size", 8)
|
|
hl_left.add_child(hl_title)
|
|
|
|
var hl_cards = HBoxContainer.new()
|
|
hl_cards.add_theme_constant_override("separation", 20)
|
|
hl_left.add_child(hl_cards)
|
|
|
|
_load_maps_modularly(hl_cards, true, Vector2(200, 250))
|
|
|
|
var mode_label = Label.new()
|
|
mode_label.text = "Gamemode"
|
|
mode_label.add_theme_font_size_override("font_size", 32)
|
|
hl_left.add_child(mode_label)
|
|
|
|
var _gamemode_opt = OptionButton.new()
|
|
_gamemode_opt.add_theme_font_size_override("font_size", 24)
|
|
_gamemode_opt.add_item("Deathmatch", 0)
|
|
_gamemode_opt.add_to_group("host_only_ui")
|
|
_gamemode_opt.item_selected.connect(func(index: int):
|
|
if get_node_or_null("/root/NetworkManager"):
|
|
get_node("/root/NetworkManager").current_gamemode = _gamemode_opt.get_item_text(index)
|
|
)
|
|
hl_left.add_child(_gamemode_opt)
|
|
|
|
var hl_spacer = Control.new()
|
|
hl_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
hl_left.add_child(hl_spacer)
|
|
|
|
_add_button(hl_left, "Disband & Back", _on_lobby_back_pressed)
|
|
|
|
# Right Side (Player List)
|
|
var hl_right = VBoxContainer.new()
|
|
hl_right.custom_minimum_size = Vector2(400, 0)
|
|
var hl_right_margin = MarginContainer.new()
|
|
hl_right_margin.add_theme_constant_override("margin_top", 50)
|
|
hl_right_margin.add_theme_constant_override("margin_right", 50)
|
|
hl_right_margin.add_theme_constant_override("margin_bottom", 50)
|
|
hl_hbox.add_child(hl_right_margin)
|
|
hl_right_margin.add_child(hl_right)
|
|
|
|
var pl_title = Label.new()
|
|
pl_title.text = "Players Connected"
|
|
pl_title.add_theme_font_size_override("font_size", 48)
|
|
pl_title.add_theme_color_override("font_outline_color", Color(0, 0, 0))
|
|
pl_title.add_theme_constant_override("outline_size", 8)
|
|
hl_right.add_child(pl_title)
|
|
|
|
var port_label = Label.new()
|
|
if get_node_or_null("/root/NetworkManager"):
|
|
port_label.text = "Port: " + str(get_node("/root/NetworkManager").DEFAULT_PORT)
|
|
port_label.add_theme_font_size_override("font_size", 24)
|
|
port_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
|
hl_right.add_child(port_label)
|
|
|
|
_host_lobby_players_list = ItemList.new()
|
|
_host_lobby_players_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
_host_lobby_players_list.add_theme_font_size_override("font_size", 24)
|
|
hl_right.add_child(_host_lobby_players_list)
|
|
|
|
# Hover/click sounds on every menu button
|
|
UITheme.wire_sounds(self)
|
|
|
|
func _load_maps_modularly(target_hbox: Container, is_multiplayer: bool, card_size: Vector2) -> void:
|
|
var maps_dir = "res://scenes/maps/"
|
|
var dir = DirAccess.open(maps_dir)
|
|
if not dir:
|
|
return
|
|
|
|
dir.list_dir_begin()
|
|
var folder_name = dir.get_next()
|
|
while folder_name != "":
|
|
if dir.current_is_dir() and not folder_name.begins_with("."):
|
|
var cfg_path = maps_dir + folder_name + "/map_meta.cfg"
|
|
if FileAccess.file_exists(cfg_path):
|
|
var cfg = ConfigFile.new()
|
|
if cfg.load(cfg_path) == OK:
|
|
var map_name = cfg.get_value("map", "name", "Unknown Map")
|
|
var scene_path = cfg.get_value("map", "scene_path", "")
|
|
var c1 = cfg.get_value("map", "color1", Color(0.5, 0.5, 0.5))
|
|
var c2 = cfg.get_value("map", "color2", Color(0.2, 0.2, 0.2))
|
|
|
|
if scene_path != "":
|
|
var grad = Gradient.new()
|
|
# A new Gradient has 2 points at 0.0 and 1.0 by default. Set their colors directly.
|
|
grad.set_color(0, c1)
|
|
grad.set_color(1, c2)
|
|
_build_level_card(target_hbox, map_name, scene_path, grad, is_multiplayer, card_size)
|
|
folder_name = dir.get_next()
|
|
|
|
func _build_level_card(parent: Container, title: String, scene_path: String, gradient: Gradient, is_multiplayer: bool = false, card_size: Vector2 = Vector2(300, 400)) -> void:
|
|
var btn = TextureButton.new()
|
|
btn.custom_minimum_size = card_size
|
|
|
|
var tex = GradientTexture2D.new()
|
|
tex.gradient = gradient
|
|
tex.width = int(card_size.x)
|
|
tex.height = int(card_size.y)
|
|
tex.fill_to = Vector2(1, 1)
|
|
btn.texture_normal = tex
|
|
|
|
var tex_hover = GradientTexture2D.new()
|
|
tex_hover.gradient = gradient
|
|
tex_hover.width = int(card_size.x)
|
|
tex_hover.height = int(card_size.y)
|
|
tex_hover.fill_to = Vector2(1, 1)
|
|
tex_hover.use_hdr = true # Slight bright effect on hover
|
|
btn.texture_hover = tex_hover
|
|
btn.modulate = Color(1, 1, 1)
|
|
|
|
# VBox to hold the label
|
|
var vbox = VBoxContainer.new()
|
|
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
vbox.alignment = BoxContainer.ALIGNMENT_END
|
|
btn.add_child(vbox)
|
|
|
|
# Dimming overlay at the bottom for text readability
|
|
var overlay = ColorRect.new()
|
|
overlay.color = Color(0, 0, 0, 0.6)
|
|
overlay.custom_minimum_size = Vector2(0, 60)
|
|
vbox.add_child(overlay)
|
|
|
|
var lbl = Label.new()
|
|
lbl.text = title
|
|
lbl.add_theme_font_size_override("font_size", 32)
|
|
lbl.add_theme_color_override("font_color", Color.WHITE)
|
|
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
lbl.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
overlay.add_child(lbl)
|
|
|
|
if is_multiplayer:
|
|
btn.add_to_group("host_only_ui")
|
|
btn.pressed.connect(func(): get_node("/root/NetworkManager").rpc_load_level.rpc(scene_path))
|
|
else:
|
|
btn.pressed.connect(func():
|
|
var nm = get_node_or_null("/root/NetworkManager")
|
|
if nm:
|
|
nm.start_singleplayer_match("Deathmatch")
|
|
get_tree().change_scene_to_file(scene_path)
|
|
)
|
|
|
|
parent.add_child(btn)
|
|
|
|
func _add_button(parent: Container, text: String, callback: Callable) -> void:
|
|
var btn = Button.new()
|
|
btn.text = text
|
|
btn.add_theme_font_size_override("font_size", 32)
|
|
btn.custom_minimum_size = Vector2(300, 60)
|
|
btn.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
|
btn.pressed.connect(callback)
|
|
parent.add_child(btn)
|
|
|
|
func _build_skin_selector(parent: Container) -> void:
|
|
var skin_mgr = get_node_or_null("/root/SkinManager")
|
|
if not skin_mgr:
|
|
return
|
|
var row = HBoxContainer.new()
|
|
row.add_theme_constant_override("separation", 12)
|
|
parent.add_child(row)
|
|
|
|
var lbl = Label.new()
|
|
lbl.text = "Skin"
|
|
lbl.add_theme_font_size_override("font_size", 24)
|
|
row.add_child(lbl)
|
|
|
|
var opt = OptionButton.new()
|
|
opt.custom_minimum_size = Vector2(240, 44)
|
|
var ids: Array = skin_mgr.get_skin_ids()
|
|
ids.sort()
|
|
for i in range(ids.size()):
|
|
var skin = skin_mgr.get_skin(ids[i])
|
|
opt.add_item(skin.skin_name, i)
|
|
opt.set_item_metadata(i, ids[i])
|
|
if ids[i] == skin_mgr.active_skin_id:
|
|
opt.select(i)
|
|
opt.item_selected.connect(func(idx: int):
|
|
skin_mgr.set_active_skin(opt.get_item_metadata(idx))
|
|
)
|
|
row.add_child(opt)
|
|
|
|
func _on_back_pressed() -> void:
|
|
_level_selector_panel.hide()
|
|
_ui_vbox.show()
|
|
|
|
func _on_singleplayer_pressed() -> void:
|
|
_ui_vbox.hide()
|
|
_level_selector_panel.show()
|
|
|
|
func _on_multiplayer_pressed() -> void:
|
|
_ui_vbox.hide()
|
|
_multiplayer_panel.show()
|
|
|
|
func _on_multiplayer_back_pressed() -> void:
|
|
_multiplayer_panel.hide()
|
|
_ui_vbox.show()
|
|
|
|
func _on_host_pressed() -> void:
|
|
var nm = get_node("/root/NetworkManager")
|
|
if nm.host_game() == OK:
|
|
_on_connection_succeeded()
|
|
|
|
func _on_direct_connect_pressed() -> void:
|
|
_multiplayer_panel.hide()
|
|
_direct_connect_panel.show()
|
|
|
|
func _on_dc_back_pressed() -> void:
|
|
_direct_connect_panel.hide()
|
|
_multiplayer_panel.show()
|
|
|
|
func _on_connect_confirm_pressed() -> void:
|
|
var ip = _direct_connect_panel.get_node("IPInput").text
|
|
var port = _direct_connect_panel.get_node("PortInput").text.to_int()
|
|
get_node("/root/NetworkManager").join_game(ip, port)
|
|
|
|
func _on_connection_succeeded() -> void:
|
|
_direct_connect_panel.hide()
|
|
_multiplayer_panel.hide()
|
|
_host_lobby_panel.show()
|
|
|
|
var nm = get_node("/root/NetworkManager")
|
|
var is_host = nm.multiplayer.is_server()
|
|
for node in get_tree().get_nodes_in_group("host_only_ui"):
|
|
if node is BaseButton:
|
|
node.disabled = not is_host
|
|
|
|
if not nm.player_connected.is_connected(_on_network_player_connected):
|
|
nm.player_connected.connect(_on_network_player_connected)
|
|
nm.player_disconnected.connect(_on_network_player_disconnected)
|
|
if not nm.stats_updated.is_connected(_update_lobby_players):
|
|
nm.stats_updated.connect(_update_lobby_players)
|
|
|
|
_update_lobby_players()
|
|
|
|
func _on_lobby_back_pressed() -> void:
|
|
_host_lobby_panel.hide()
|
|
_multiplayer_panel.show()
|
|
var nm = get_node("/root/NetworkManager")
|
|
nm.disconnect_game()
|
|
if nm.player_connected.is_connected(_on_network_player_connected):
|
|
nm.player_connected.disconnect(_on_network_player_connected)
|
|
nm.player_disconnected.disconnect(_on_network_player_disconnected)
|
|
if nm.stats_updated.is_connected(_update_lobby_players):
|
|
nm.stats_updated.disconnect(_update_lobby_players)
|
|
|
|
func _on_network_player_connected(_id: int) -> void:
|
|
_update_lobby_players()
|
|
|
|
func _on_network_player_disconnected(_id: int) -> void:
|
|
_update_lobby_players()
|
|
|
|
func _update_lobby_players() -> void:
|
|
if not _host_lobby_players_list: return
|
|
_host_lobby_players_list.clear()
|
|
var nm = get_node("/root/NetworkManager")
|
|
|
|
for pid in nm.player_stats.keys():
|
|
var p_name = nm.player_stats[pid].get("username", "Unknown")
|
|
var p_ping = nm.player_stats[pid].get("ping", 0)
|
|
|
|
if pid == 1:
|
|
_host_lobby_players_list.add_item("%s (Host) - %sms" % [p_name, p_ping])
|
|
else:
|
|
_host_lobby_players_list.add_item("%s - %sms" % [p_name, p_ping])
|
|
|
|
func _on_loadouts_pressed() -> void:
|
|
if has_node("/root/PauseMenu"):
|
|
var pm = get_node("/root/PauseMenu")
|
|
pm.visible = true
|
|
pm._show_loadouts()
|
|
|
|
func _on_settings_pressed() -> void:
|
|
if has_node("/root/PauseMenu"):
|
|
var pm = get_node("/root/PauseMenu")
|
|
pm.visible = true
|
|
pm._show_settings()
|
|
|
|
func _on_exit_pressed() -> void:
|
|
get_tree().quit()
|
|
|
|
# ==========================================
|
|
# 3D DIORAMA BUILDER
|
|
# ==========================================
|
|
func _build_3d_diorama(viewport: SubViewport) -> void:
|
|
var world = Node3D.new()
|
|
viewport.add_child(world)
|
|
|
|
# Lighting and Env — same stylized sunset sky as in-game for continuity
|
|
var env = WorldEnvironment.new()
|
|
env.environment = LevelEnvironment.make_environment("sunset")
|
|
world.add_child(env)
|
|
|
|
var sun = DirectionalLight3D.new()
|
|
sun.rotation_degrees = Vector3(-45, 45, 0)
|
|
sun.light_color = Color(1.0, 0.9, 0.8)
|
|
sun.light_energy = 1.5
|
|
sun.shadow_enabled = true
|
|
world.add_child(sun)
|
|
|
|
var fill = DirectionalLight3D.new()
|
|
fill.rotation_degrees = Vector3(-30, -135, 0)
|
|
fill.light_color = Color(0.5, 0.6, 1.0)
|
|
fill.light_energy = 0.5
|
|
world.add_child(fill)
|
|
|
|
# Floor — toon world-grid like the levels
|
|
var floor_mesh = MeshInstance3D.new()
|
|
floor_mesh.mesh = PlaneMesh.new()
|
|
floor_mesh.mesh.size = Vector2(20, 20)
|
|
floor_mesh.mesh.surface_set_material(0, LevelMaterials.tinted(Color(0.32, 0.3, 0.38)))
|
|
world.add_child(floor_mesh)
|
|
|
|
# Player Model (Sliding Pose)
|
|
var player_model = Node3D.new()
|
|
player_model.position = Vector3(0, 0.5, 0) # Lowered for slide
|
|
world.add_child(player_model)
|
|
|
|
var body_mat = StandardMaterial3D.new()
|
|
body_mat.albedo_color = Color(0.3, 0.6, 0.9)
|
|
# Toonified below via apply_toon_recursive once the model is assembled
|
|
|
|
# Torso (Capsule tilted back)
|
|
var torso = MeshInstance3D.new()
|
|
var t_mesh = CapsuleMesh.new()
|
|
t_mesh.radius = 0.4
|
|
t_mesh.height = 1.2
|
|
t_mesh.material = body_mat
|
|
torso.mesh = t_mesh
|
|
torso.rotation_degrees.x = -60 # Tilted back
|
|
torso.position = Vector3(0, 0.4, 0)
|
|
player_model.add_child(torso)
|
|
|
|
# Legs (Sticking forward)
|
|
var legs = MeshInstance3D.new()
|
|
var l_mesh = BoxMesh.new()
|
|
l_mesh.size = Vector3(0.6, 0.4, 1.2)
|
|
l_mesh.material = body_mat
|
|
legs.mesh = l_mesh
|
|
legs.position = Vector3(0, -0.2, 0.6)
|
|
player_model.add_child(legs)
|
|
|
|
# Arm holding shotgun
|
|
var arm = Node3D.new()
|
|
arm.position = Vector3(0.5, 0.6, 0.2)
|
|
player_model.add_child(arm)
|
|
|
|
var arm_mesh = MeshInstance3D.new()
|
|
var a_mesh = BoxMesh.new()
|
|
a_mesh.size = Vector3(0.2, 0.2, 0.8)
|
|
a_mesh.material = body_mat
|
|
arm_mesh.mesh = a_mesh
|
|
arm_mesh.position = Vector3(0, 0, 0.3)
|
|
arm.add_child(arm_mesh)
|
|
|
|
# Shotgun
|
|
var shotgun_script = load("res://weapons/double_barrel_shotgun.gd")
|
|
if shotgun_script:
|
|
var shotgun = shotgun_script.new()
|
|
arm_mesh.add_child(shotgun)
|
|
shotgun.position = Vector3(0, -0.1, 0.4)
|
|
shotgun.rotation_degrees = Vector3(0, 0, 0)
|
|
shotgun.set_process(false)
|
|
shotgun.set_physics_process(false)
|
|
if shotgun.has_method("set_process_input"):
|
|
shotgun.set_process_input(false)
|
|
|
|
# Cel-shade the diorama (no hull outlines — the FBX shotgun tears them)
|
|
LevelMaterials.apply_toon_recursive(player_model, 0.0)
|
|
|
|
# Camera
|
|
var camera = Camera3D.new()
|
|
world.add_child(camera)
|
|
camera.position = Vector3(3, 1.5, 4)
|
|
camera.look_at(Vector3(0, 0.5, 0))
|