aboutsummaryrefslogtreecommitdiff
path: root/client/gui/menus/main
diff options
context:
space:
mode:
Diffstat (limited to 'client/gui/menus/main')
-rw-r--r--client/gui/menus/main/about.gd169
-rw-r--r--client/gui/menus/main/about.gd.uid1
-rw-r--r--client/gui/menus/main/about.tscn93
-rw-r--r--client/gui/menus/main/background.gd50
-rw-r--r--client/gui/menus/main/background.gd.uid1
-rw-r--r--client/gui/menus/main/background.tscn71
-rw-r--r--client/gui/menus/main/clouds.gdshader36
-rw-r--r--client/gui/menus/main/clouds.gdshader.uid1
-rw-r--r--client/gui/menus/main/main.gd44
-rw-r--r--client/gui/menus/main/main.gd.uid1
-rw-r--r--client/gui/menus/main/main.tscn94
-rw-r--r--client/gui/menus/main/play.gd208
-rw-r--r--client/gui/menus/main/play.gd.uid1
-rw-r--r--client/gui/menus/main/play.tscn149
-rw-r--r--client/gui/menus/main/server_list_item.gd38
-rw-r--r--client/gui/menus/main/server_list_item.gd.uid1
-rw-r--r--client/gui/menus/main/server_list_item.tscn39
17 files changed, 997 insertions, 0 deletions
diff --git a/client/gui/menus/main/about.gd b/client/gui/menus/main/about.gd
new file mode 100644
index 00000000..b56d3941
--- /dev/null
+++ b/client/gui/menus/main/about.gd
@@ -0,0 +1,169 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2025 Hurry Curry! contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, version 3 of the License only.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+
+extends Menu
+
+var authors := ["metamuffin", "nokoe", "tpart"]
+var contributors := ["sofviic", "BigBrotherNii", "Miner34"]
+const cc_by_4 := "CC-BY 4.0"
+const cc_by_3 := "CC-BY 3.0"
+const cc0 := "CC0"
+
+const AGPL_NOTICE := """This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, version 3 of the License only.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>."""
+
+const SOURCE_CODE := "https://codeberg.org/hurrycurry/hurrycurry"
+
+func _ready() -> void:
+ super()
+ $side/margin/options/first/source.tooltip_text = SOURCE_CODE
+
+var credits := [
+ [tr("c.credits.models"), [
+ ["kenney.nl", "Various Models", cc0],
+ ["Kay Lousberg", "Kitchen tiles", cc0],
+ ["Poly by Google", "Strawberry", cc_by_3],
+ ["Poly by Google", "Fish", cc_by_3],
+ ["jeremy", "Propeller hat", cc_by_3]
+ ]],
+ [tr("c.credits.sounds"), [
+ ["Dryoma", "Footstep sounds", cc_by_4],
+ ["Koops", "Page_Turn_24.wav", cc_by_4],
+ ["InspectorJ", "Pencil, Writing, Close, A.wav", cc_by_4],
+ ["Dillon Becker", "Super Dialogue Audio Pack", cc_by_4],
+ ["Ellr", "Universal UI/Menu Soundpack", cc_by_4],
+ ["toyoto", "Woosh Fleuret Escrime B.wav", cc_by_4],
+ ["deleted_user_877451", "Game_over.wav", cc_by_3],
+ ["Quaternius", "Someting, dont remember...", cc0],
+ ["Dillon Becker", "Super Dialogue Audio Pack V1", cc_by_4]
+ ]],
+ [tr("c.credits.translations"), {
+ tr("c.settings.ui.language.zh_Hans"): ["Outbreak2096"],
+ tr("c.settings.ui.language.zh_Hant"): ["hugoalh"],
+ tr("c.settings.ui.language.nl"): ["Vistaus"],
+ tr("c.settings.ui.language.it"): ["Miner34", "solemden"],
+ tr("c.settings.ui.language.eu"): ["josuigoa"],
+ tr("c.settings.ui.language.fr"): ["fnetX", "lejun"],
+ tr("c.settings.ui.language.pl"): ["tranzystorekk"],
+ tr("c.settings.ui.language.he"): ["RustyStriker"],
+ tr("c.settings.ui.language.el"): ["n0toose"],
+ tr("c.settings.ui.language.ja"): ["BigBrotherNii"],
+ tr("c.settings.ui.language.ar"): ["sofviic"],
+ tr("c.settings.ui.language.tr"): ["furkanunsalan", "tekrei"],
+ tr("c.settings.ui.language.ru"): ["0ko"],
+ }]
+]
+
+func _menu_cover(state):
+ $side.visible = not state
+
+func credits_text() -> String:
+ var text = "[center]"
+ authors.shuffle()
+ contributors.shuffle()
+
+ text += "\n\n\n[b]%s[/b]\n\n%s\n\n[b]%s[/b]\n\n%s\n\n[b]%s[/b]\n\n\n" % [
+ tr("c.credits.title"),
+ tr("c.credits.developed_by"),
+ "\n".join(authors),
+ tr("c.credits.contributors"),
+ ", ".join(contributors),
+ ]
+
+ for section in credits:
+ text += "[b]%s[/b]\n\n" % section[0]
+ if typeof(section[1]) == TYPE_DICTIONARY:
+ text += "[table=2]"
+ for key in section[1]:
+ text += "[cell][right]%s[/right][/cell]" % key
+ text += "[cell][left]%s[/left][/cell]" % ", ".join(section[1][key])
+ text += "[/table]"
+ else:
+ text += "[table=3]"
+ for entry in section[1]:
+ text += "[cell][right]%s[/right][/cell]" % entry[0]
+ text += "[cell][left]%s[/left][/cell]" % entry[1]
+ text += "[cell][left]%s[/left][/cell]" % entry[2]
+ text += "[/table]"
+ text += "\n\n\n"
+
+ text += "\n[b]%s[/b]\n\n\n[/center]" % tr("c.credits.thanks")
+ return text
+
+func legal_text() -> String:
+ var all: Array[String] = []
+ var translators: Array[String] = []
+ for c in credits[2][1].values():
+ translators.append_array(c)
+ translators.shuffle()
+ authors.shuffle()
+ contributors.shuffle()
+ all.append_array(authors)
+ all.append_array(contributors)
+ all.append_array(translators)
+
+ var text := "Hurry Curry! - a game about cooking\n"
+ text += "[code]Copyright 2024, 2025 %s\n\n" % ", ".join(dedup_array(all))
+ text += "%s[/code]\n\n" % AGPL_NOTICE
+ text += tr("c.legal.using_godot")
+ text += "\n\n[code]%s[/code]" % Engine.get_license_text()
+ return text
+
+func version_text() -> String:
+ var text := "[center][b]Hurry Curry![/b]\n\n"
+ OS.get_version()
+ text += "[table=2]"
+ var row = "[cell][right]%s[/right][/cell][cell][left]%s[/left][/cell]"
+ text += row % [tr("c.version.game"), Global.VERSION]
+ text += row % [tr("c.version.protocol"), "%s.%s" % [Multiplayer.VERSION_MAJOR, Multiplayer.VERSION_MINOR]]
+ text += row % [tr("c.version.godot"), Engine.get_version_info().string]
+ text += row % [tr("c.version.os"), OS.get_name()]
+ text += row % [tr("c.version.arch"), Engine.get_architecture_name()]
+ text += row % [tr("c.version.distribution"), Global.DISTRIBUTION]
+
+ text += "[/table]"
+ text += "[/center]"
+ return text
+
+func _on_credits_pressed() -> void:
+ submenu("res://gui/menus/popup_large.tscn", credits_text())
+
+func _on_legal_pressed() -> void:
+ submenu("res://gui/menus/popup_large.tscn", legal_text())
+
+func _on_version_pressed() -> void:
+ submenu("res://gui/menus/popup_large.tscn", version_text())
+
+func _on_back_pressed() -> void:
+ exit()
+
+
+func _on_source_pressed() -> void:
+ OS.shell_open(SOURCE_CODE)
+
+func dedup_array(a: Array) -> Array:
+ var b = []
+ for x in a: if not b.has(x): b.append(x)
+ return b
diff --git a/client/gui/menus/main/about.gd.uid b/client/gui/menus/main/about.gd.uid
new file mode 100644
index 00000000..3929f2c3
--- /dev/null
+++ b/client/gui/menus/main/about.gd.uid
@@ -0,0 +1 @@
+uid://pcu87stpkgd8
diff --git a/client/gui/menus/main/about.tscn b/client/gui/menus/main/about.tscn
new file mode 100644
index 00000000..006b61fe
--- /dev/null
+++ b/client/gui/menus/main/about.tscn
@@ -0,0 +1,93 @@
+[gd_scene load_steps=7 format=3 uid="uid://bpaenm8c6nmo8"]
+
+[ext_resource type="Script" uid="uid://pcu87stpkgd8" path="res://gui/menus/main/about.gd" id="1_0acu0"]
+[ext_resource type="Material" uid="uid://2j8a0c0a2ta5" path="res://gui/resources/materials/blur_material.tres" id="1_ai5pk"]
+[ext_resource type="StyleBox" uid="uid://bw4jamyna1top" path="res://gui/resources/style/panel_style_sidebar.tres" id="2_pya1x"]
+[ext_resource type="FontFile" uid="uid://bo4vh5xkpvrh1" path="res://gui/resources/fonts/font-sansita-swashed.woff2" id="4_kx3j7"]
+[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="4_t51wf"]
+
+[sub_resource type="FontVariation" id="FontVariation_o2r3e"]
+base_font = ExtResource("4_kx3j7")
+variation_embolden = 0.5
+
+[node name="AboutMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_0acu0")
+support_anim = false
+
+[node name="side" type="PanelContainer" parent="."]
+material = ExtResource("1_ai5pk")
+layout_mode = 1
+anchors_preset = 9
+anchor_bottom = 1.0
+offset_right = 294.0
+grow_vertical = 2
+theme_override_styles/panel = ExtResource("2_pya1x")
+
+[node name="margin" type="MarginContainer" parent="side"]
+layout_mode = 2
+theme_override_constants/margin_left = 20
+theme_override_constants/margin_top = 20
+theme_override_constants/margin_right = 20
+theme_override_constants/margin_bottom = 20
+script = ExtResource("4_t51wf")
+
+[node name="options" type="VBoxContainer" parent="side/margin"]
+layout_mode = 2
+
+[node name="title" type="Label" parent="side/margin/options"]
+auto_translate_mode = 2
+layout_mode = 2
+theme_override_colors/font_outline_color = Color(0.566408, 0.208917, 0.266045, 1)
+theme_override_constants/outline_size = 10
+theme_override_fonts/font = SubResource("FontVariation_o2r3e")
+theme_override_font_sizes/font_size = 48
+text = "Hurry Curry!"
+
+[node name="spacer" type="Control" parent="side/margin/options"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="first" type="VBoxContainer" parent="side/margin/options"]
+layout_mode = 2
+
+[node name="credits" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.about.credits"
+alignment = 0
+
+[node name="version" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.about.version"
+alignment = 0
+
+[node name="legal" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.about.legal"
+alignment = 0
+
+[node name="source" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.about.source"
+alignment = 0
+
+[node name="first2" type="VBoxContainer" parent="side/margin/options"]
+layout_mode = 2
+size_flags_vertical = 3
+alignment = 2
+
+[node name="back" type="Button" parent="side/margin/options/first2"]
+layout_mode = 2
+text = "c.menu.back"
+alignment = 0
+
+[connection signal="pressed" from="side/margin/options/first/credits" to="." method="_on_credits_pressed"]
+[connection signal="pressed" from="side/margin/options/first/version" to="." method="_on_version_pressed"]
+[connection signal="pressed" from="side/margin/options/first/legal" to="." method="_on_legal_pressed"]
+[connection signal="pressed" from="side/margin/options/first/source" to="." method="_on_source_pressed"]
+[connection signal="pressed" from="side/margin/options/first2/back" to="." method="_on_back_pressed"]
diff --git a/client/gui/menus/main/background.gd b/client/gui/menus/main/background.gd
new file mode 100644
index 00000000..4abb84b4
--- /dev/null
+++ b/client/gui/menus/main/background.gd
@@ -0,0 +1,50 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2025 Hurry Curry! contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, version 3 of the License only.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+extends Node3D
+
+const CRATES = ["tomato-crate", "steak-crate", "cheese-crate", "lettuce-crate", "flour-crate", "coconut-crate"]
+const TOOLS = ["stove", "stove", "stove", "sink", "cuttingboard", "sink", "cuttingboard", "oven", "freezer"]
+
+@onready var environment: WorldEnvironment = $Environment
+@onready var map: Map = $Map
+
+func _ready():
+ if !Global.on_vulkan():
+ environment.environment.tonemap_exposure = 0.25
+
+ var tiles = {}
+ for x in range(-10, 11):
+ for y in range(-10, 11):
+ var w = exp(-sqrt(x * x + y * y) * 0.15)
+ var k = randf() * w
+ var tn = null
+ if k > 0.25: tn = "floor"
+ if k > 0.4: tn = choose(CRATES) if randf() > 0.3 else "counter"
+ if k > 0.6: tn = choose(TOOLS)
+ if tn != null: tiles[str(Vector2i(x,y))] = [tn,[x,y]]
+
+ var gt = func (cs):
+ var t = tiles.get(str(Vector2i(cs[0],cs[1])))
+ return null if t == null else t[0]
+ for pk in tiles.keys():
+ var x = tiles[pk][1][0]
+ var y = tiles[pk][1][1]
+ var t = gt.call([x,y])
+ if t != null: map.set_tile(Vector2i(x,y), t, [[x,y-1],[x-1,y],[x,y+1],[x+1,y]].map(gt))
+
+ map.flush()
+
+func choose(a): return a[floor(a.size() * randf())]
diff --git a/client/gui/menus/main/background.gd.uid b/client/gui/menus/main/background.gd.uid
new file mode 100644
index 00000000..7d61d488
--- /dev/null
+++ b/client/gui/menus/main/background.gd.uid
@@ -0,0 +1 @@
+uid://b2tq5rcjjcxdg
diff --git a/client/gui/menus/main/background.tscn b/client/gui/menus/main/background.tscn
new file mode 100644
index 00000000..2bd4ee04
--- /dev/null
+++ b/client/gui/menus/main/background.tscn
@@ -0,0 +1,71 @@
+[gd_scene load_steps=12 format=3 uid="uid://l4vm07dtda4j"]
+
+[ext_resource type="Script" uid="uid://b2tq5rcjjcxdg" path="res://gui/menus/main/background.gd" id="1_pgu7b"]
+[ext_resource type="Script" uid="uid://cwg7wympevxs4" path="res://map/auto_setup/environment_setup.gd" id="2_7dwbj"]
+[ext_resource type="Shader" uid="uid://b1k6ipo0sagli" path="res://gui/menus/main/clouds.gdshader" id="3_lapmn"]
+[ext_resource type="PackedScene" uid="uid://b4gone8fu53r7" path="res://map/map.tscn" id="4_nslxb"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_uw50b"]
+sky_top_color = Color(0.55, 0.55, 0.55, 1)
+
+[sub_resource type="Sky" id="Sky_utrtx"]
+sky_material = SubResource("ProceduralSkyMaterial_uw50b")
+
+[sub_resource type="Environment" id="Environment_slkjl"]
+background_mode = 1
+background_color = Color(0.517035, 0.49506, 0.878906, 1)
+sky = SubResource("Sky_utrtx")
+tonemap_mode = 2
+tonemap_exposure = 0.6
+tonemap_white = 0.9
+ssao_enabled = true
+
+[sub_resource type="QuadMesh" id="QuadMesh_fvp2p"]
+size = Vector2(100, 100)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_e3eby"]
+noise_type = 2
+frequency = 0.008
+fractal_octaves = 7
+fractal_gain = 0.72
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_s4fnp"]
+width = 1024
+height = 1024
+generate_mipmaps = false
+seamless = true
+noise = SubResource("FastNoiseLite_e3eby")
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_gd87g"]
+render_priority = 0
+shader = ExtResource("3_lapmn")
+shader_parameter/noise = SubResource("NoiseTexture2D_s4fnp")
+shader_parameter/ccloud = Color(0.835938, 0.835938, 0.835938, 1)
+shader_parameter/csky = Color(0.329412, 0.333333, 0.8, 1)
+
+[node name="MenuBackground" type="Node3D"]
+script = ExtResource("1_pgu7b")
+
+[node name="Camera" type="Camera3D" parent="."]
+transform = Transform3D(0.614606, 0.499662, -0.610408, -0.00282255, 0.775198, 0.631712, 0.78883, -0.386531, 0.477852, -9.13611, 4.90356, 1.22532)
+projection = 1
+current = true
+size = 8.0
+far = 100.0
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(0.290334, 0.904946, -0.311092, 0.336606, 0.207739, 0.918445, 0.895769, -0.371371, -0.244296, 0, 7, 0)
+light_energy = 1.25
+shadow_enabled = true
+
+[node name="Environment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_slkjl")
+script = ExtResource("2_7dwbj")
+allow_sdfgi = false
+
+[node name="the-sky-tm" type="MeshInstance3D" parent="."]
+transform = Transform3D(0.614606, 0.499662, -0.610408, -0.00282255, 0.775198, 0.631712, 0.78883, -0.386531, 0.477851, 6, -13, -11)
+mesh = SubResource("QuadMesh_fvp2p")
+surface_material_override/0 = SubResource("ShaderMaterial_gd87g")
+
+[node name="Map" parent="." instance=ExtResource("4_nslxb")]
diff --git a/client/gui/menus/main/clouds.gdshader b/client/gui/menus/main/clouds.gdshader
new file mode 100644
index 00000000..8103f691
--- /dev/null
+++ b/client/gui/menus/main/clouds.gdshader
@@ -0,0 +1,36 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, version 3 of the License only.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+*/
+shader_type spatial;
+render_mode unshaded;
+
+uniform sampler2D noise : source_color;
+uniform vec3 ccloud : source_color;
+uniform vec3 csky : source_color;
+
+void fragment() {
+ vec2 uv = UV * 0.9;
+ uv += TIME * vec2(0.001,0.002);
+
+ float f = texture(noise, uv).x;
+ f = 1. - f;
+ f = pow(f, 1.5);
+ f = floor(f*5.)/5.;
+ f = pow(f, 2.);
+
+ ALBEDO = mix(csky, ccloud, f);
+}
diff --git a/client/gui/menus/main/clouds.gdshader.uid b/client/gui/menus/main/clouds.gdshader.uid
new file mode 100644
index 00000000..00c2d21a
--- /dev/null
+++ b/client/gui/menus/main/clouds.gdshader.uid
@@ -0,0 +1 @@
+uid://b1k6ipo0sagli
diff --git a/client/gui/menus/main/main.gd b/client/gui/menus/main/main.gd
new file mode 100644
index 00000000..423f756e
--- /dev/null
+++ b/client/gui/menus/main/main.gd
@@ -0,0 +1,44 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2025 Hurry Curry! contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, version 3 of the License only.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+extends Menu
+
+@onready var quit_button = $side/margin/options/first/quit
+@onready var margin_container: MarginContainer = $side/margin
+
+func _ready():
+ super()
+ if OS.has_feature("web"):
+ quit_button.hide()
+ Sound.play_music("MainMenu")
+ ServerList.one_shot()
+
+func _menu_cover(state):
+ $side.visible = not state
+
+func _on_quit_pressed():
+ quit()
+
+func _on_about_pressed():
+ submenu("res://gui/menus/main/about.tscn")
+
+func _on_change_character_pressed():
+ replace_menu("res://gui/menus/character.tscn", null, "res://gui/menus/main.tscn")
+
+func _on_settings_pressed():
+ submenu("res://gui/menus/settings/settings.tscn")
+
+func _on_play_pressed():
+ submenu("res://gui/menus/main/play.tscn")
diff --git a/client/gui/menus/main/main.gd.uid b/client/gui/menus/main/main.gd.uid
new file mode 100644
index 00000000..dc2cecd3
--- /dev/null
+++ b/client/gui/menus/main/main.gd.uid
@@ -0,0 +1 @@
+uid://bpiynadrmdd37
diff --git a/client/gui/menus/main/main.tscn b/client/gui/menus/main/main.tscn
new file mode 100644
index 00000000..81595966
--- /dev/null
+++ b/client/gui/menus/main/main.tscn
@@ -0,0 +1,94 @@
+[gd_scene load_steps=10 format=3 uid="uid://dbj8508whxgwv"]
+
+[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_3qfu3"]
+[ext_resource type="Script" uid="uid://bpiynadrmdd37" path="res://gui/menus/main/main.gd" id="2_xjnc3"]
+[ext_resource type="PackedScene" uid="uid://l4vm07dtda4j" path="res://gui/menus/main/background.tscn" id="3_4evao"]
+[ext_resource type="Material" uid="uid://2j8a0c0a2ta5" path="res://gui/resources/materials/blur_material.tres" id="4_nx4vf"]
+[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="5_0mn56"]
+[ext_resource type="FontFile" uid="uid://bo4vh5xkpvrh1" path="res://gui/resources/fonts/font-sansita-swashed.woff2" id="5_k7bqq"]
+[ext_resource type="StyleBox" uid="uid://bw4jamyna1top" path="res://gui/resources/style/panel_style_sidebar.tres" id="5_qlyeo"]
+[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="7_btdj1"]
+
+[sub_resource type="FontVariation" id="FontVariation_htgmg"]
+base_font = ExtResource("5_k7bqq")
+variation_embolden = 0.5
+
+[node name="MainMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_3qfu3")
+script = ExtResource("2_xjnc3")
+
+[node name="MenuBackground" parent="." instance=ExtResource("3_4evao")]
+
+[node name="side" type="PanelContainer" parent="."]
+material = ExtResource("4_nx4vf")
+layout_mode = 1
+anchors_preset = 9
+anchor_bottom = 1.0
+offset_right = 294.0
+grow_vertical = 2
+theme_override_styles/panel = ExtResource("5_qlyeo")
+script = ExtResource("5_0mn56")
+
+[node name="margin" type="MarginContainer" parent="side"]
+layout_mode = 2
+theme_override_constants/margin_left = 20
+theme_override_constants/margin_top = 20
+theme_override_constants/margin_right = 20
+theme_override_constants/margin_bottom = 20
+script = ExtResource("7_btdj1")
+
+[node name="options" type="VBoxContainer" parent="side/margin"]
+layout_mode = 2
+
+[node name="title" type="Label" parent="side/margin/options"]
+auto_translate_mode = 2
+layout_mode = 2
+theme_override_colors/font_outline_color = Color(0.566408, 0.208917, 0.266045, 1)
+theme_override_constants/outline_size = 10
+theme_override_fonts/font = SubResource("FontVariation_htgmg")
+theme_override_font_sizes/font_size = 48
+text = "Hurry Curry!"
+
+[node name="spacer" type="Control" parent="side/margin/options"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="first" type="VBoxContainer" parent="side/margin/options"]
+layout_mode = 2
+
+[node name="play" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.play"
+alignment = 0
+
+[node name="change_character" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.my_chef"
+alignment = 0
+
+[node name="settings" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.settings"
+alignment = 0
+
+[node name="about" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.about"
+alignment = 0
+
+[node name="quit" type="Button" parent="side/margin/options/first"]
+layout_mode = 2
+text = "c.menu.quit"
+alignment = 0
+
+[connection signal="pressed" from="side/margin/options/first/play" to="." method="_on_play_pressed"]
+[connection signal="pressed" from="side/margin/options/first/change_character" to="." method="_on_change_character_pressed"]
+[connection signal="pressed" from="side/margin/options/first/settings" to="." method="_on_settings_pressed"]
+[connection signal="pressed" from="side/margin/options/first/about" to="." method="_on_about_pressed"]
+[connection signal="pressed" from="side/margin/options/first/quit" to="." method="_on_quit_pressed"]
diff --git a/client/gui/menus/main/play.gd b/client/gui/menus/main/play.gd
new file mode 100644
index 00000000..c9cbee2f
--- /dev/null
+++ b/client/gui/menus/main/play.gd
@@ -0,0 +1,208 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2025 Hurry Curry! contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, version 3 of the License only.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+extends Menu
+
+var server_list_item: PackedScene = preload("res://gui/menus/main/server_list_item.tscn")
+var url_regex: RegEx = RegEx.new()
+
+@onready var server_list: VBoxContainer = $side/margin/options/second/ScrollContainerCustom/ServerList
+@onready var server_list_loading: Label = $side/margin/options/second/Loading
+@onready var server_list_empty: Label = $side/margin/options/second/NoServers
+@onready var connect_uri = $side/margin/options/second/connect/uri
+@onready var server = $side/margin/options/second/server
+@onready var server_control = $side/margin/options/second/server/control
+@onready var server_connect = $side/margin/options/second/server/connect
+@onready var editor_control = $side/margin/options/second/editor/control
+@onready var editor_connect = $side/margin/options/second/editor/connect
+@onready var editor_container = $side/margin/options/second/editor
+
+
+func _ready():
+ url_regex.compile("^(?:(ws|wss)://)?([^:]+)(?::([0-9]+))?$")
+ if OS.has_feature("web"):
+ server.hide()
+ connect_uri.text = Global.get_profile("last_server_url")
+ Sound.play_music("MainMenu")
+
+ ServerList.update_server_list.connect(update_server_list)
+ ServerList.update_loading.connect(update_server_list_loading)
+ update_server_list(ServerList.current_list)
+ update_server_list_loading(ServerList.loading)
+
+ super()
+ if not Global.get_profile("registry_asked"):
+ var popup_data := MenuPopup.Data.new()
+ popup_data.text = tr("c.menu.play.allow_query_registry").format([Global.get_setting("online.registry_url")])
+ var allow_button := Button.new()
+ allow_button.text = tr("c.menu.accept")
+ var deny_button := Button.new()
+ deny_button.text = tr("c.menu.deny")
+ allow_button.pressed.connect(func(): Global.set_setting("online.use_registry", true))
+ deny_button.pressed.connect(func(): Global.set_setting("online.use_registry", false))
+ popup_data.buttons = [allow_button, deny_button]
+ await submenu("res://gui/menus/popup.tscn", popup_data)
+ Global.set_profile("registry_asked", true)
+ Global.save_settings()
+ Global.save_profile()
+
+ ServerList.start()
+
+func update_server_list(lists: Array[Array]):
+ # Find out the index of the currently focused server in the list
+ var prev_selected_idx := -1
+ for i in range(server_list.get_children().size()):
+ if server_list.get_child(i).button.has_focus():
+ prev_selected_idx = i
+ break
+
+ for c in server_list.get_children():
+ c.queue_free()
+
+ var idx := 0
+ for l in lists:
+ for i in l:
+ var server_item: ServerListItem = server_list_item.instantiate()
+ server_list.add_child(server_item)
+ # TODO: Implement fallback address correctly
+ server_item.setup(i.name, roundi(i.players_online), i.version)
+ server_item.button.pressed.connect(connect_to.bind(i.address[0]))
+ # Focus the same server with the same index as the previously focused one
+ if idx == prev_selected_idx:
+ server_item.button.grab_focus()
+ idx += 1
+
+ if prev_selected_idx > idx:
+ # Same index cannot be focused, since number of servers decreased
+ if idx - 1 < 0:
+ connect_uri.grab_focus()
+ else:
+ server_list.get_child(idx - 1).button.grab_focus()
+
+ # Show message if no servers available
+ server_list_empty.visible = idx == 0
+
+func update_server_list_loading(status: bool):
+ server_list_loading.visible = status
+
+func _menu_cover(state):
+ $side.visible = not state
+
+func _on_connect_pressed():
+ var url = connect_uri.text
+ var result := url_regex.search(url)
+ if result != null:
+ if result.get_string(1) == "":
+ url = "ws://" + url
+ # only set default port for non-tls websocket connections
+ if result.get_string(3) == "" and result.get_string(1) != "wss":
+ url = url + ":27032"
+ connect_uri.text = url
+ Global.set_profile("last_server_url", url)
+ Global.save_profile()
+ connect_to(url)
+
+func _on_quick_connect_pressed():
+ if OS.has_feature("web"):
+ connect_to(JavaScriptBridge.eval("""
+ window.location.protocol.endsWith("s:")
+ ? `wss://${window.location.host}/`
+ : `ws://${window.location.hostname}:27032/`
+ """))
+ else:
+ connect_to("wss://hurrycurry.metamuffin.org/")
+
+func connect_to(url: String):
+ print("Connecting to %s" % url)
+ get_parent().replace_menu("res://gui/menus/game.tscn", url)
+
+func _on_server_control_pressed():
+ match Server.state:
+ Service.State.RUNNING: Server.stop()
+ Service.State.STOPPED: Server.start()
+ Service.State.FAILED: Server.start()
+
+func _on_editor_control_pressed():
+ match Editor.state:
+ Service.State.RUNNING: Editor.stop()
+ Service.State.STOPPED: Editor.start(); Server.start()
+ Service.State.FAILED: Editor.start()
+
+func _on_server_connect_pressed():
+ connect_to("ws://%s:%d" % [Server.connect_address(), Global.get_setting("server.bind_port")])
+
+func _on_editor_connect_pressed():
+ connect_to("ws://[::1]:27032/")
+
+func _process(_delta):
+ server_control.disabled = false
+ server_connect.visible = Server.state == Service.State.RUNNING
+ server_control.modulate = Color.WHITE
+ match Server.state:
+ Service.State.RUNNING:
+ server_control.text = tr("c.menu.play.server_stop")
+ server_control.modulate = Color.AQUAMARINE
+ Service.State.TESTING:
+ server_control.text = tr("c.menu.play.server_testing")
+ server_control.disabled = true
+ Service.State.STARTING:
+ server_control.text = tr("c.menu.play.server_starting")
+ server_control.disabled = true
+ Service.State.STOPPED:
+ server_control.text = tr("c.menu.play.server_start")
+ Service.State.FAILED:
+ server_control.text = tr("c.menu.play.server_failed")
+ server_control.modulate = Color(1, 0.4, 0.5)
+ server_control.tooltip_text = tr("c.menu.play.server_failed_tooltip")
+ Service.State.UNAVAILABLE:
+ server_control.text = tr("c.menu.play.server_unavailable")
+ server_control.disabled = true
+ server_control.tooltip_text = tr("c.menu.play.server_binary_not_found")
+
+ editor_control.disabled = false
+ editor_connect.visible = Editor.state == Service.State.RUNNING
+ editor_control.modulate = Color.WHITE
+ editor_container.visible = Editor.state != Service.State.UNAVAILABLE
+ match Editor.state:
+ Service.State.RUNNING:
+ editor_control.text = tr("c.menu.play.editor_stop")
+ editor_control.modulate = Color.AQUAMARINE
+ Service.State.TESTING:
+ editor_control.text = tr("c.menu.play.editor_testing")
+ editor_control.disabled = true
+ Service.State.STARTING:
+ editor_control.text = tr("c.menu.play.editor_starting")
+ editor_control.disabled = true
+ Service.State.STOPPED:
+ editor_control.text = tr("c.menu.play.editor_start")
+ Service.State.FAILED:
+ editor_control.text = tr("c.menu.play.editor_failed")
+ editor_control.modulate = Color(1, 0.4, 0.5)
+ editor_control.tooltip_text = tr("c.menu.play.server_failed_tooltip")
+ Service.State.UNAVAILABLE:
+ editor_control.text = tr("c.menu.play.editor_unavailable")
+ editor_control.disabled = true
+ editor_control.tooltip_text = tr("c.menu.play.server_binary_not_found")
+
+
+func _on_uri_text_changed(new_text):
+ connect_uri.modulate = Color.WHITE if url_regex.search(new_text) else Color.RED
+
+func _on_back_pressed():
+ exit()
+
+func _menu_exit():
+ ServerList.stop()
+ super()
diff --git a/client/gui/menus/main/play.gd.uid b/client/gui/menus/main/play.gd.uid
new file mode 100644
index 00000000..d8ca168f
--- /dev/null
+++ b/client/gui/menus/main/play.gd.uid
@@ -0,0 +1 @@
+uid://b126k2228nj4s
diff --git a/client/gui/menus/main/play.tscn b/client/gui/menus/main/play.tscn
new file mode 100644
index 00000000..441024a2
--- /dev/null
+++ b/client/gui/menus/main/play.tscn
@@ -0,0 +1,149 @@
+[gd_scene load_steps=9 format=3 uid="uid://c8url5fpttbem"]
+
+[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_cckds"]
+[ext_resource type="Script" uid="uid://b126k2228nj4s" path="res://gui/menus/main/play.gd" id="2_phxx0"]
+[ext_resource type="Material" uid="uid://2j8a0c0a2ta5" path="res://gui/resources/materials/blur_material.tres" id="3_fsbt7"]
+[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="4_gst6r"]
+[ext_resource type="Script" uid="uid://bd7bylb2t2m0" path="res://gui/components/touch_scroll_container.gd" id="5_cm120"]
+[ext_resource type="FontFile" uid="uid://bo4vh5xkpvrh1" path="res://gui/resources/fonts/font-sansita-swashed.woff2" id="5_ojpbf"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ukani"]
+
+[sub_resource type="FontVariation" id="FontVariation_htgmg"]
+base_font = ExtResource("5_ojpbf")
+variation_embolden = 0.5
+
+[node name="PlayMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_cckds")
+script = ExtResource("2_phxx0")
+support_anim = false
+
+[node name="side" type="PanelContainer" parent="."]
+material = ExtResource("3_fsbt7")
+layout_mode = 1
+anchors_preset = 9
+anchor_bottom = 1.0
+offset_right = 294.0
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_ukani")
+
+[node name="margin" type="MarginContainer" parent="side"]
+layout_mode = 2
+theme_override_constants/margin_left = 20
+theme_override_constants/margin_top = 20
+theme_override_constants/margin_right = 20
+theme_override_constants/margin_bottom = 20
+script = ExtResource("4_gst6r")
+
+[node name="options" type="VBoxContainer" parent="side/margin"]
+layout_mode = 2
+
+[node name="title" type="Label" parent="side/margin/options"]
+auto_translate_mode = 2
+layout_mode = 2
+theme_override_colors/font_outline_color = Color(0.566408, 0.208917, 0.266045, 1)
+theme_override_constants/outline_size = 10
+theme_override_fonts/font = SubResource("FontVariation_htgmg")
+theme_override_font_sizes/font_size = 48
+text = "Hurry Curry!"
+
+[node name="spacer" type="Control" parent="side/margin/options"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="second" type="VBoxContainer" parent="side/margin/options"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="Loading" type="Label" parent="side/margin/options/second"]
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "c.menu.play.fetching_list"
+horizontal_alignment = 1
+
+[node name="NoServers" type="Label" parent="side/margin/options/second"]
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "c.menu.play.no_servers"
+horizontal_alignment = 1
+
+[node name="ScrollContainerCustom" type="ScrollContainer" parent="side/margin/options/second"]
+layout_mode = 2
+size_flags_vertical = 3
+horizontal_scroll_mode = 0
+script = ExtResource("5_cm120")
+
+[node name="ServerList" type="VBoxContainer" parent="side/margin/options/second/ScrollContainerCustom"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="spacer" type="Control" parent="side/margin/options/second"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="connect" type="HBoxContainer" parent="side/margin/options/second"]
+layout_mode = 2
+
+[node name="uri" type="LineEdit" parent="side/margin/options/second/connect"]
+auto_translate_mode = 2
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "wss://example.org"
+
+[node name="connect" type="Button" parent="side/margin/options/second/connect"]
+layout_mode = 2
+text = "c.menu.play.connect"
+
+[node name="server" type="HBoxContainer" parent="side/margin/options/second"]
+layout_mode = 2
+
+[node name="control" type="Button" parent="side/margin/options/second/server"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "c.menu.play.server"
+alignment = 0
+
+[node name="connect" type="Button" parent="side/margin/options/second/server"]
+layout_mode = 2
+text = "c.menu.play.connect"
+
+[node name="editor" type="HBoxContainer" parent="side/margin/options/second"]
+layout_mode = 2
+
+[node name="control" type="Button" parent="side/margin/options/second/editor"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "c.menu.play.editor"
+alignment = 0
+
+[node name="connect" type="Button" parent="side/margin/options/second/editor"]
+layout_mode = 2
+text = "c.menu.play.connect"
+
+[node name="spacer2" type="Control" parent="side/margin/options/second"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="back" type="Button" parent="side/margin/options/second"]
+layout_mode = 2
+text = "c.menu.back"
+alignment = 0
+
+[node name="VBoxContainer" type="VBoxContainer" parent="side/margin/options/second"]
+layout_mode = 2
+
+[connection signal="text_changed" from="side/margin/options/second/connect/uri" to="." method="_on_uri_text_changed"]
+[connection signal="pressed" from="side/margin/options/second/connect/connect" to="." method="_on_connect_pressed"]
+[connection signal="pressed" from="side/margin/options/second/server/control" to="." method="_on_server_control_pressed"]
+[connection signal="pressed" from="side/margin/options/second/server/connect" to="." method="_on_server_connect_pressed"]
+[connection signal="pressed" from="side/margin/options/second/editor/control" to="." method="_on_editor_control_pressed"]
+[connection signal="pressed" from="side/margin/options/second/editor/connect" to="." method="_on_editor_connect_pressed"]
+[connection signal="pressed" from="side/margin/options/second/back" to="." method="_on_back_pressed"]
diff --git a/client/gui/menus/main/server_list_item.gd b/client/gui/menus/main/server_list_item.gd
new file mode 100644
index 00000000..0cffdd72
--- /dev/null
+++ b/client/gui/menus/main/server_list_item.gd
@@ -0,0 +1,38 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2025 Hurry Curry! contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, version 3 of the License only.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+class_name ServerListItem
+extends PanelContainer
+
+var error_style_focus: StyleBoxFlat = preload("res://gui/resources/style/error_focus_style.tres")
+@onready var title: Label = $MarginContainer/VBoxContainer/Title
+@onready var info: Label = $MarginContainer/VBoxContainer/Info
+@onready var button: Button = $Button
+
+func setup(name_: String, online_players: int, version: Array):
+ title.text = name_
+ if version[0] != Multiplayer.VERSION_MAJOR or version[1] > Multiplayer.VERSION_MINOR:
+ button.disabled = true
+ button.add_theme_stylebox_override("focus", error_style_focus)
+ info.text = tr("c.menu.play.server_version_mismatch")
+ info.add_theme_color_override("font_color", Color("ff2222"))
+ return
+ info.text = tr("c.menu.play.server_players").format([online_players])
+
+ # This node is in group not no_click_sound, so sounds won't be automatically connected
+ # by menu system. Reason: These nodes are deleted and re-created every few seconds
+ # in server list, and signals are only connected on ready.
+ button.pressed.connect(Sound.play_click)
+ button.mouse_entered.connect(Sound.play_hover)
diff --git a/client/gui/menus/main/server_list_item.gd.uid b/client/gui/menus/main/server_list_item.gd.uid
new file mode 100644
index 00000000..276bb06f
--- /dev/null
+++ b/client/gui/menus/main/server_list_item.gd.uid
@@ -0,0 +1 @@
+uid://xr5oigbgd0aw
diff --git a/client/gui/menus/main/server_list_item.tscn b/client/gui/menus/main/server_list_item.tscn
new file mode 100644
index 00000000..40e9eedb
--- /dev/null
+++ b/client/gui/menus/main/server_list_item.tscn
@@ -0,0 +1,39 @@
+[gd_scene load_steps=3 format=3 uid="uid://t2h60dhuvfsk"]
+
+[ext_resource type="Script" uid="uid://xr5oigbgd0aw" path="res://gui/menus/main/server_list_item.gd" id="1_1n1yg"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1n1yg"]
+
+[node name="ServerListItem" type="PanelContainer"]
+offset_right = 400.0
+offset_bottom = 40.0
+size_flags_horizontal = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_1n1yg")
+script = ExtResource("1_1n1yg")
+
+[node name="Button" type="Button" parent="." groups=["no_click_sound"]]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="."]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 10
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="Title" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.87451, 0.87451, 0.87451, 1)
+theme_override_font_sizes/font_size = 18
+text = "Example Server"
+
+[node name="Info" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.749781, 0.74978, 0.74978, 1)
+theme_override_font_sizes/font_size = 14
+text = "5 players online"