Files
Papay-Shooter/ui/main_menu/main_menu.gd
T

553 lines
19 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)
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
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
var title = Label.new()
title.text = "Papaya Shooter"
title.add_theme_font_size_override("font_size", 72)
title.add_theme_color_override("font_color", Color(1, 0.6, 0.2)) # Papaya orange
title.add_theme_color_override("font_outline_color", Color(0, 0, 0))
title.add_theme_constant_override("outline_size", 12)
_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)
# ==========================================
# 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)
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 _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
var env = WorldEnvironment.new()
var environment = Environment.new()
environment.background_mode = Environment.BG_COLOR
environment.background_color = Color(0.1, 0.15, 0.2) # Dark stylish background
environment.tonemap_mode = Environment.TONE_MAPPER_ACES
env.environment = environment
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
var floor_mesh = MeshInstance3D.new()
floor_mesh.mesh = PlaneMesh.new()
floor_mesh.mesh.size = Vector2(20, 20)
var floor_mat = StandardMaterial3D.new()
floor_mat.albedo_color = Color(0.2, 0.2, 0.2)
floor_mesh.mesh.surface_set_material(0, floor_mat)
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)
# 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)
# Camera
var camera = Camera3D.new()
world.add_child(camera)
camera.position = Vector3(3, 1.5, 4)
camera.look_at(Vector3(0, 0.5, 0))