diff --git a/README.md b/README.md index f0aa270..3fb58c8 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,17 @@ Current heroes: - CARITAS (Margarine) - SUPERBIA (Build portals) -Ideas for Heroes: +Ideas for Heroes / Abilities: - More active - - Blink (Tracer) - no cooldown, but loses all speed on hitting walls + - Blink (like tracer) costs charge - Heavy guy - Slow, but very heavy for the see-saw - Climb and glide abilities - JUMPING - More supportive + - Switch places with a TEAMMATE + - This is awesomely self-regulating because it's zero-sum + - However, it can't really gain charge because it can still be abused - Boost - Area of effect or zarya-like cast, speeds people up - Flop - Changes side of see-saw for each team (should it be mechanic instead??) - Build - Build Zineth-like rails for anyone to use @@ -42,6 +45,7 @@ Ideas for Heroes: - (Combine with LUSSURIA?) Hook and swing on terrain - More offensive - Lay traps + - Freeze a hero. A teammate of that hero must unfreeze them! (Or *n* seconds or whatever) - (Combine with PAZIENZA?) Destroy buildings - (Combine wit LUSSURIA?) Hold, then release to explode enemies away diff --git a/assets/theme.tres b/assets/theme.tres new file mode 100644 index 0000000..f1d737a --- /dev/null +++ b/assets/theme.tres @@ -0,0 +1,16 @@ +[gd_resource type="Theme" load_steps=3 format=2] + +[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=1] + +[sub_resource type="DynamicFont" id=1] + +size = 18 +use_mipmaps = false +use_filter = false +font_data = ExtResource( 1 ) +_sections_unfolded = [ "Font", "Settings" ] + +[resource] + +default_font = SubResource( 1 ) + diff --git a/plans.md b/plans.md index e127ec1..b857052 100644 --- a/plans.md +++ b/plans.md @@ -8,6 +8,7 @@ Big TODOs: - ^ I think you need more abilities for heroes, that combine well - Like, you could combine PAZIENZA with [destroys buildings] - Start making maps and figuring out what works / what you like + - Make just one new main level with the lessons you've learned from this one - I'd love to do a payload map! - More platforming for everyone (It's industria only rn tbh) - Give portals a tilted wall to fuck with acceleration @@ -25,6 +26,8 @@ Smaller TODOs: - Ira is OP? - Nerfed - 5 walls - Make motion more reactive? +- Remember some player settings, probably in a player.json or something + - I'm mostly thinking of nickname, but also SR eventually Bugs: diff --git a/project.godot b/project.godot index 5d343c5..7ba129f 100644 --- a/project.godot +++ b/project.godot @@ -11,12 +11,13 @@ config_version=3 [application] config/name="nv-moba" -run/main_scene="res://scenes/lobby.tscn" +run/main_scene="res://scenes/menu.tscn" config/icon="res://icon.png" [autoload] util="*res://scripts/util.gd" +networking="*res://scripts/networking.gd" [input] diff --git a/scenes/custom_game.tscn b/scenes/custom_game.tscn new file mode 100644 index 0000000..1bc0d28 --- /dev/null +++ b/scenes/custom_game.tscn @@ -0,0 +1,232 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://assets/theme.tres" type="Theme" id=1] +[ext_resource path="res://scripts/custom_game.gd" type="Script" id=2] +[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=3] + +[sub_resource type="DynamicFont" id=1] + +size = 30 +use_mipmaps = false +use_filter = false +font_data = ExtResource( 3 ) + +[node name="CustomGame" type="Control"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 40.0 +margin_bottom = 40.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +theme = ExtResource( 1 ) +script = ExtResource( 2 ) + +[node name="VSeparator" type="VSeparator" parent="." index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 498.0 +margin_top = 140.0 +margin_right = 518.0 +margin_bottom = 481.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 + +[node name="Label" type="Label" parent="." index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 54.0 +margin_top = 66.0 +margin_right = 395.0 +margin_bottom = 102.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +custom_fonts/font = SubResource( 1 ) +text = "Custom Game" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="Server" type="Button" parent="." index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 73.0 +margin_top = 195.0 +margin_right = 366.0 +margin_bottom = 252.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Host Game" +flat = false +align = 1 + +[node name="Client" type="Button" parent="." index="3"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 588.0 +margin_top = 258.0 +margin_right = 890.0 +margin_bottom = 315.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Join Game" +flat = false +align = 1 + +[node name="IPLabel" type="Label" parent="." index="4"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 592.0 +margin_top = 205.0 +margin_right = 625.0 +margin_bottom = 227.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +text = "IP:" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="IP" type="TextEdit" parent="." index="5"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 632.0 +margin_top = 203.0 +margin_right = 891.0 +margin_bottom = 232.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +text = "127.0.0.1" +readonly = false +highlight_current_line = false +syntax_highlighting = false +show_line_numbers = false +highlight_all_occurrences = false +override_selected_font_color = false +context_menu_enabled = true +smooth_scrolling = false +v_scroll_speed = 80.0 +hiding_enabled = 0 +wrap_lines = false +caret_block_mode = false +caret_blink = false +caret_blink_speed = 0.65 +caret_moving_by_right_click = true + +[node name="Label2" type="Label" parent="." index="6"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 72.0 +margin_top = 145.0 +margin_right = 171.0 +margin_bottom = 167.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +text = "Host game" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="Label3" type="Label" parent="." index="7"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 577.0 +margin_top = 147.0 +margin_right = 617.0 +margin_bottom = 169.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +text = "Join Game" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="Back" type="Button" parent="." index="8"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 437.0 +margin_top = 509.0 +margin_right = 581.0 +margin_bottom = 537.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Back to menu" +flat = false +align = 1 + + diff --git a/scenes/heroes/5.tscn b/scenes/heroes/5.tscn index 1b5507c..3a57297 100644 --- a/scenes/heroes/5.tscn +++ b/scenes/heroes/5.tscn @@ -30,7 +30,7 @@ margin_left = -160.0 margin_top = -112.0 margin_right = -149.0 margin_bottom = -103.0 -cost = 20 +cost = 75 ability_name = "Build Portal" action = "hero_5_place_portal" diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn index 36112b9..d590f9b 100644 --- a/scenes/lobby.tscn +++ b/scenes/lobby.tscn @@ -1,29 +1,27 @@ -[gd_scene load_steps=7 format=2] +[gd_scene load_steps=8 format=2] -[ext_resource path="res://scripts/lobby.gd" type="Script" id=1] -[ext_resource path="res://scripts/hero_select.gd" type="Script" id=2] +[ext_resource path="res://assets/theme.tres" type="Theme" id=1] +[ext_resource path="res://scripts/lobby.gd" type="Script" id=2] +[ext_resource path="res://scenes/hero_select.tscn" type="PackedScene" id=3] +[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=4] -[sub_resource type="DynamicFontData" id=1] - -font_path = "res://assets/DejaVuSansMono.ttf" - -[sub_resource type="DynamicFont" id=2] +[sub_resource type="DynamicFont" id=1] size = 30 use_mipmaps = false use_filter = false -font_data = SubResource( 1 ) +font_data = ExtResource( 4 ) -[sub_resource type="DynamicFontData" id=3] +[sub_resource type="DynamicFontData" id=2] font_path = "res://assets/DejaVuSansMono.ttf" -[sub_resource type="DynamicFont" id=4] +[sub_resource type="DynamicFont" id=3] size = 16 use_mipmaps = false use_filter = false -font_data = SubResource( 3 ) +font_data = SubResource( 2 ) [node name="Lobby" type="Control"] @@ -31,45 +29,6 @@ anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 80.0 -margin_top = 99.0 -margin_right = 120.0 -margin_bottom = 139.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -script = ExtResource( 1 ) - -[node name="Title" type="Label" parent="." index="0"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 7.0 -margin_top = -72.0 -margin_right = 195.0 -margin_bottom = -32.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 2 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 4 -custom_fonts/font = SubResource( 2 ) -text = "VANAGLORIA" -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="GameBrowser" type="Control" parent="." index="1"] - -editor/display_folded = true -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 margin_right = 40.0 margin_bottom = 40.0 rect_pivot_offset = Vector2( 0, 0 ) @@ -77,158 +36,67 @@ mouse_filter = 0 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 +theme = ExtResource( 1 ) +script = ExtResource( 2 ) -[node name="Play" type="Button" parent="GameBrowser" index="0"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 9.0 -margin_top = -2.0 -margin_right = 405.0 -margin_bottom = 53.0 -rect_pivot_offset = Vector2( 0, 0 ) -focus_mode = 2 -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -toggle_mode = false -enabled_focus_mode = 2 -shortcut = null -group = null -text = "Just, Play!" -flat = false -align = 1 - -[node name="PickAGameLabel" type="Label" parent="GameBrowser" index="1"] +[node name="HeroSelect" parent="." index="0" instance=ExtResource( 3 )] -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 10.0 -margin_top = 65.0 -margin_right = 114.0 -margin_bottom = 79.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 2 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 4 -text = "Or, pick a game:" -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 +margin_left = 30.0 +margin_top = 69.0 +margin_right = 30.0 +margin_bottom = 69.0 +color = Color( 0.097229, 0.104696, 0.105469, 0 ) -[node name="GameList" type="ItemList" parent="GameBrowser" index="2"] +[node name="Hero" parent="HeroSelect" index="0"] -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 8.0 -margin_top = 87.0 -margin_right = 411.0 -margin_bottom = 208.0 -rect_pivot_offset = Vector2( 0, 0 ) -rect_clip_content = true -focus_mode = 2 -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -items = [ "No Games Running", null, false ] -select_mode = 0 -icon_mode = 1 -fixed_icon_size = Vector2( 0, 0 ) +margin_left = -2.0 +margin_top = 41.0 +margin_right = 370.0 +margin_bottom = 108.0 -[node name="Join" type="Button" parent="GameBrowser" index="3"] +[node name="Confirm" parent="HeroSelect" index="1"] -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 9.0 -margin_top = 217.0 -margin_right = 163.0 -margin_bottom = 244.0 -rect_pivot_offset = Vector2( 0, 0 ) -focus_mode = 2 -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -toggle_mode = false -enabled_focus_mode = 2 -shortcut = null -group = null -text = "Join Selected Game" -flat = false -align = 1 - -[node name="VSeparator" type="VSeparator" parent="." index="2"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 431.0 -margin_top = -3.0 -margin_right = 436.0 -margin_bottom = 234.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 +visible = false -[node name="PlayerSettings" type="Control" parent="." index="3"] +[node name="HeroDescription" parent="HeroSelect" index="2"] -editor/display_folded = true -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_right = 40.0 -margin_bottom = 40.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 +margin_left = -2.0 +margin_top = 125.0 +margin_right = 366.0 +margin_bottom = 288.0 +text = "L Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth oaentuh Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth " -[node name="PlayerSettingsLabel" type="Label" parent="PlayerSettings" index="0"] +[node name="Title" type="Label" parent="HeroSelect" index="3"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 459.0 -margin_top = -6.0 -margin_right = 564.0 -margin_bottom = 13.0 +margin_left = 6.0 +margin_top = 9.0 +margin_right = 294.0 +margin_bottom = 45.0 rect_pivot_offset = Vector2( 0, 0 ) mouse_filter = 2 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 4 -text = "Player Settings" +custom_fonts/font = SubResource( 1 ) +text = "Select your hero" percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="Username" type="TextEdit" parent="PlayerSettings" index="1"] +[node name="Username" type="TextEdit" parent="." index="1"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 457.0 -margin_top = 27.0 -margin_right = 636.0 -margin_bottom = 47.0 +margin_left = 43.0 +margin_top = 391.0 +margin_right = 384.0 +margin_bottom = 424.0 rect_pivot_offset = Vector2( 0, 0 ) focus_mode = 2 mouse_filter = 0 @@ -252,37 +120,40 @@ caret_blink = false caret_blink_speed = 0.65 caret_moving_by_right_click = true -[node name="HeroSelectLabel" type="Label" parent="PlayerSettings" index="2"] +[node name="Spectating" type="CheckButton" parent="." index="2"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 460.0 -margin_top = 66.0 -margin_right = 538.0 -margin_bottom = 80.0 +margin_left = 36.0 +margin_top = 439.0 +margin_right = 237.0 +margin_bottom = 479.0 rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 2 +focus_mode = 2 +mouse_filter = 0 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 -size_flags_vertical = 4 -text = "Hero Select:" -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 +size_flags_vertical = 1 +toggle_mode = true +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Spectating " +flat = false +align = 0 -[node name="HeroSelect" type="OptionButton" parent="PlayerSettings" index="3"] +[node name="LevelSelect" type="OptionButton" parent="." index="3"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 457.0 -margin_top = 94.0 -margin_right = 645.0 -margin_bottom = 114.0 -rect_rotation = -0.0115615 +margin_left = 45.0 +margin_top = 529.0 +margin_right = 411.0 +margin_bottom = 570.0 rect_pivot_offset = Vector2( 0, 0 ) focus_mode = 2 mouse_filter = 0 @@ -294,288 +165,127 @@ action_mode = 0 enabled_focus_mode = 2 shortcut = null group = null +text = "Platform map" flat = false align = 0 -selected = -1 -items = [ ] -script = ExtResource( 2 ) +selected = 0 +items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ] -[node name="TeamLabel" type="Label" parent="PlayerSettings" index="4"] +[node name="Label" type="Label" parent="LevelSelect" index="1"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 459.0 -margin_top = 132.0 -margin_right = 558.0 -margin_bottom = 146.0 +margin_left = -5.0 +margin_top = -37.0 +margin_right = 39.0 +margin_bottom = -15.0 rect_pivot_offset = Vector2( 0, 0 ) mouse_filter = 2 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 4 -text = "Assigned team:" +text = "Map:" percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="Team" type="Label" parent="PlayerSettings" index="5"] +[node name="TeamLabel" type="Label" parent="." index="4"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 460.0 -margin_top = 157.0 -margin_right = 500.0 -margin_bottom = 171.0 +margin_left = 498.0 +margin_top = 87.0 +margin_right = 666.0 +margin_bottom = 111.0 rect_pivot_offset = Vector2( 0, 0 ) mouse_filter = 2 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 4 +text = "Your team:" percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="HeroDescriptionLabel" type="Label" parent="PlayerSettings" index="6"] +[node name="Team" type="Label" parent="." index="5"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 677.0 -margin_top = -5.0 -margin_right = 790.0 -margin_bottom = 9.0 +margin_left = 625.0 +margin_top = 87.0 +margin_right = 665.0 +margin_bottom = 111.0 rect_pivot_offset = Vector2( 0, 0 ) mouse_filter = 2 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 4 -text = "Hero Description:" percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="HeroDescription" type="RichTextLabel" parent="PlayerSettings" index="7"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 675.0 -margin_top = 15.0 -margin_right = 918.0 -margin_bottom = 245.0 -rect_pivot_offset = Vector2( 0, 0 ) -rect_clip_content = true -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -bbcode_enabled = false -bbcode_text = "" -visible_characters = -1 -percent_visible = 1.0 -meta_underlined = true -tab_size = 4 -text = "" -scroll_active = true -scroll_following = false -selection_enabled = false -override_selected_font_color = false - -[node name="HSeparator3" type="HSeparator" parent="." index="4"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 19.0 -margin_top = 271.0 -margin_right = 645.0 -margin_bottom = 275.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 - -[node name="CustomGame" type="Control" parent="." index="5"] +[node name="PlayerList" type="Label" parent="." index="6"] -editor/display_folded = true -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_right = 40.0 -margin_bottom = 40.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 - -[node name="Label" type="Label" parent="CustomGame" index="0"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 671.0 -margin_top = 275.0 -margin_right = 874.0 -margin_bottom = 289.0 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = 480.0 +margin_top = 130.0 +margin_right = 981.0 +margin_bottom = 408.0 rect_pivot_offset = Vector2( 0, 0 ) mouse_filter = 2 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 4 -text = "Or, host or join a custom game:" +custom_fonts/font = SubResource( 3 ) +text = "Waiting for players to connect...." percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="Server" type="Button" parent="CustomGame" index="1"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 665.0 -margin_top = 300.0 -margin_right = 752.0 -margin_bottom = 335.0 -rect_pivot_offset = Vector2( 0, 0 ) -focus_mode = 2 -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -toggle_mode = false -enabled_focus_mode = 2 -shortcut = null -group = null -text = "Server" -flat = false -align = 1 - -[node name="LevelSelect" type="OptionButton" parent="CustomGame" index="2"] +[node name="Ready" type="CheckButton" parent="." index="7"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 770.0 -margin_top = 300.0 -margin_right = 895.0 -margin_bottom = 335.0 +margin_left = 493.0 +margin_top = 436.0 +margin_right = 628.0 +margin_bottom = 476.0 rect_pivot_offset = Vector2( 0, 0 ) focus_mode = 2 mouse_filter = 0 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -toggle_mode = false -action_mode = 0 +toggle_mode = true enabled_focus_mode = 2 shortcut = null group = null -text = "Platform map" +text = "Ready " flat = false align = 0 -selected = 0 -items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ] - -[node name="Client" type="Button" parent="CustomGame" index="3"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 667.0 -margin_top = 354.0 -margin_right = 753.0 -margin_bottom = 393.0 -rect_pivot_offset = Vector2( 0, 0 ) -focus_mode = 2 -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -toggle_mode = false -enabled_focus_mode = 2 -shortcut = null -group = null -text = "Client" -flat = false -align = 1 -[node name="IPLabel" type="Label" parent="CustomGame" index="4"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 673.0 -margin_top = 403.0 -margin_right = 698.0 -margin_bottom = 419.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 2 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 4 -text = "IP:" -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="IP" type="TextEdit" parent="CustomGame" index="5"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 707.0 -margin_top = 401.0 -margin_right = 851.0 -margin_bottom = 419.0 -rect_pivot_offset = Vector2( 0, 0 ) -focus_mode = 2 -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 -text = "127.0.0.1" -readonly = false -highlight_current_line = false -syntax_highlighting = false -show_line_numbers = false -highlight_all_occurrences = false -override_selected_font_color = false -context_menu_enabled = true -smooth_scrolling = false -v_scroll_speed = 80.0 -hiding_enabled = 0 -wrap_lines = false -caret_block_mode = false -caret_blink = false -caret_blink_speed = 0.65 -caret_moving_by_right_click = true - -[node name="Singleplayer" type="Button" parent="CustomGame" index="6"] +[node name="StartGame" type="Button" parent="." index="8"] +visible = false anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 668.0 -margin_top = 431.0 -margin_right = 760.0 -margin_bottom = 470.0 +margin_left = 496.0 +margin_top = 491.0 +margin_right = 618.0 +margin_bottom = 531.0 rect_pivot_offset = Vector2( 0, 0 ) focus_mode = 2 mouse_filter = 0 @@ -586,93 +296,36 @@ toggle_mode = false enabled_focus_mode = 2 shortcut = null group = null -text = "Singleplayer" +text = "Start!" flat = false align = 1 -[node name="VSeparator2" type="VSeparator" parent="." index="6"] +[node name="VSeparator" type="VSeparator" parent="." index="9"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 653.0 -margin_top = 273.0 -margin_right = 657.0 -margin_bottom = 467.0 +margin_left = 453.0 +margin_top = 50.0 +margin_right = 471.0 +margin_bottom = 566.0 rect_pivot_offset = Vector2( 0, 0 ) mouse_filter = 0 mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -[node name="JoinedGameLobby" type="Control" parent="." index="7"] - -editor/display_folded = true -visible = false -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_right = 40.0 -margin_bottom = 40.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 0 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 1 - -[node name="PlayerListLabel" type="Label" parent="JoinedGameLobby" index="0"] - -anchor_left = 0.0 -anchor_top = 0.0 -anchor_right = 0.0 -anchor_bottom = 0.0 -margin_left = 14.0 -margin_top = 287.0 -margin_right = 136.0 -margin_bottom = 301.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 2 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 4 -text = "Connected Players:" -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="PlayerList" type="Label" parent="JoinedGameLobby" index="1"] - -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -margin_left = -7.0 -margin_top = 287.0 -margin_right = 639.0 -margin_bottom = 433.0 -rect_pivot_offset = Vector2( 0, 0 ) -mouse_filter = 2 -mouse_default_cursor_shape = 0 -size_flags_horizontal = 1 -size_flags_vertical = 4 -custom_fonts/font = SubResource( 4 ) -text = "Waiting for players to connect...." -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="StartGame" type="Button" parent="JoinedGameLobby" index="2"] +[node name="Back" type="Button" parent="." index="10"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 13.0 +margin_left = 834.0 margin_top = 443.0 -margin_right = 135.0 -margin_bottom = 483.0 +margin_right = 978.0 +margin_bottom = 471.0 rect_pivot_offset = Vector2( 0, 0 ) focus_mode = 2 mouse_filter = 0 @@ -683,8 +336,9 @@ toggle_mode = false enabled_focus_mode = 2 shortcut = null group = null -text = "Start game" +text = "Exit to menu" flat = false align = 1 +[editable path="HeroSelect"] diff --git a/scenes/menu.tscn b/scenes/menu.tscn new file mode 100644 index 0000000..ce87646 --- /dev/null +++ b/scenes/menu.tscn @@ -0,0 +1,166 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://assets/theme.tres" type="Theme" id=1] +[ext_resource path="res://scripts/menu.gd" type="Script" id=2] + +[sub_resource type="DynamicFontData" id=1] + +font_path = "res://assets/DejaVuSansMono.ttf" + +[sub_resource type="DynamicFont" id=2] + +size = 30 +use_mipmaps = false +use_filter = false +font_data = SubResource( 1 ) + +[node name="Menu" type="Control"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 1024.0 +margin_bottom = 600.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +theme = ExtResource( 1 ) +script = ExtResource( 2 ) + +[node name="Title" type="Label" parent="." index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 60.0 +margin_top = 50.0 +margin_right = 248.0 +margin_bottom = 90.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +custom_fonts/font = SubResource( 2 ) +text = "VANAGLORIA" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="Center" type="Control" parent="." index="1"] + +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -20.0 +margin_top = -20.0 +margin_right = 20.0 +margin_bottom = 20.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 + +[node name="Play" type="Button" parent="Center" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = -250.0 +margin_top = -70.0 +margin_right = 298.0 +margin_bottom = -10.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Quick Play" +flat = false +align = 1 + +[node name="CustomGame" type="Button" parent="Center" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = -252.0 +margin_top = 14.0 +margin_right = 300.0 +margin_bottom = 75.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Custom Game" +flat = false +align = 1 + +[node name="Singleplayer" type="Button" parent="Center" index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = -251.0 +margin_top = 104.0 +margin_right = 299.0 +margin_bottom = 170.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Practice Range" +flat = false +align = 1 + +[node name="Quit" type="Button" parent="Center" index="3"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = -252.0 +margin_top = 200.0 +margin_right = 298.0 +margin_bottom = 266.0 +rect_pivot_offset = Vector2( 0, 0 ) +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Quit" +flat = false +align = 1 + + diff --git a/scenes/singleplayer_lobby.tscn b/scenes/singleplayer_lobby.tscn new file mode 100644 index 0000000..80910e0 --- /dev/null +++ b/scenes/singleplayer_lobby.tscn @@ -0,0 +1,87 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://scenes/lobby.tscn" type="PackedScene" id=1] +[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=2] + +[sub_resource type="DynamicFont" id=1] + +size = 40 +use_mipmaps = false +use_filter = false +font_data = ExtResource( 2 ) + +[node name="Lobby" instance=ExtResource( 1 )] + +[node name="Label" type="Label" parent="." index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 602.0 +margin_top = 276.0 +margin_right = 938.0 +margin_bottom = 324.0 +rect_pivot_offset = Vector2( 0, 0 ) +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +custom_fonts/font = SubResource( 1 ) +text = "Practice Range" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="HeroSelect" parent="." index="1"] + +margin_top = 62.0 +margin_bottom = 62.0 + +[node name="Username" parent="." index="2"] + +visible = false +margin_top = 308.0 +margin_bottom = 341.0 + +[node name="Spectating" parent="." index="3"] + +visible = false + +[node name="LevelSelect" parent="." index="4"] + +margin_top = 401.0 +margin_bottom = 442.0 +items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ] + +[node name="TeamLabel" parent="." index="5"] + +visible = false + +[node name="Team" parent="." index="6"] + +visible = false + +[node name="PlayerList" parent="." index="7"] + +visible = false + +[node name="Ready" parent="." index="8"] + +visible = false + +[node name="StartGame" parent="." index="9"] + +visible = true +margin_left = 41.0 +margin_top = 486.0 +margin_right = 163.0 +margin_bottom = 526.0 +text = "Play!" + +[node name="VSeparator" parent="." index="10"] + +visible = false + + +[editable path="HeroSelect"] diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd new file mode 100644 index 0000000..996d758 --- /dev/null +++ b/scripts/custom_game.gd @@ -0,0 +1,14 @@ +extends Control + +func _ready(): + get_node("Server").connect("pressed", self, "_start_server") + get_node("Client").connect("pressed", self, "_start_client") + get_node("Back").connect("pressed", get_tree(), "change_scene", ["res://scenes/menu.tscn"]) + +func _start_server(): + # Custom Game can assume we're playing as well + networking.start_server() + +func _start_client(): + var ip = get_node("IP").text + networking.start_client(ip) diff --git a/scripts/hero_select.gd b/scripts/hero_select.gd index 5fd34df..ebb7b3c 100644 --- a/scripts/hero_select.gd +++ b/scripts/hero_select.gd @@ -22,3 +22,14 @@ func _ready(): for hero_index in range(hero_names.size()): add_item(hero_names[hero_index], hero_index) + connect("item_selected", self, "set_hero") + +func set_hero(hero): + select(hero) + networking.set_info_from_server("hero", hero) + +func random_hero(): + var hero = randi() % hero_names.size() + set_hero(hero) + return hero + diff --git a/scripts/heroes/3.gd b/scripts/heroes/3.gd index b5603af..7f5229e 100644 --- a/scripts/heroes/3.gd +++ b/scripts/heroes/3.gd @@ -90,7 +90,7 @@ func set_boosted(node, is_boosted): sync func merge(node_name): set_boosting(true) - var other = $"/root/Level/Players".get_node(node_name) + var other = util.get_player(node_name) set_boosted(other, true) merged = other diff --git a/scripts/heroes/4.gd b/scripts/heroes/4.gd index 179792a..b76049c 100644 --- a/scripts/heroes/4.gd +++ b/scripts/heroes/4.gd @@ -60,7 +60,7 @@ func _process(delta): sync func stun(net_id, position): # Stun the thing! - var player = get_node("/root/Level/Players/%s" % net_id) + var player = util.get_player(net_id) player.set_linear_velocity(Vector3()) # Show the beam! diff --git a/scripts/heroes/5.gd b/scripts/heroes/5.gd index 72fd2e8..565cc37 100644 --- a/scripts/heroes/5.gd +++ b/scripts/heroes/5.gd @@ -1,6 +1,7 @@ extends "res://scripts/player.gd" onready var placement = preload("res://scripts/placement.gd").new(self, "res://scenes/heroes/5_portal.tscn") +onready var portal_ability = get_node("MasterOnly/Portal") onready var teleport_ability = get_node("MasterOnly/Teleport") var radius = 15 @@ -8,7 +9,6 @@ var radius = 15 var first_crosshair = " [..." var second_crosshair = "...] " var no_portal_crosshair = "+" -var portal_cost = 20 var flicking = null var flick_charge = 3 @@ -27,11 +27,11 @@ func _process(delta): if is_network_master(): var is_second = placement.placed.size() % 2 != 0 var portal_crosshair = second_crosshair if is_second else first_crosshair - var crosshair = no_portal_crosshair if switch_charge < portal_cost else portal_crosshair + var crosshair = no_portal_crosshair if switch_charge < portal_ability.cost else portal_crosshair get_node("MasterOnly/Crosshair").set_text(crosshair) - var can_build = switch_charge > portal_cost + var can_build = switch_charge > portal_ability.cost if placement.place_input(radius, can_build, true) and is_second: - switch_charge -= portal_cost + switch_charge -= portal_ability.cost teleport_ability.disabled = placement.placed.size() <= 1 @@ -66,7 +66,7 @@ func flick_input(): build_charge(flick_charge) sync func flick(player_id, towards): - var who = $"/root/Level/Players".get_node(player_id) + var who = util.get_player(player_id) if who.is_network_master(): var direction = towards - who.translation var impulse = direction.normalized() * flick_strength * who.get_mass() diff --git a/scripts/heroes/5_portal.gd b/scripts/heroes/5_portal.gd index da361b2..757f7d9 100644 --- a/scripts/heroes/5_portal.gd +++ b/scripts/heroes/5_portal.gd @@ -25,7 +25,7 @@ func _exit_tree(): other.queue_free() func init(maker): - + index = maker.placement.placed.size() # If index is odd, we're the second (1, 3...), if even, first (0, 4...) diff --git a/scripts/lobby.gd b/scripts/lobby.gd index 4f37dc1..72abc8a 100644 --- a/scripts/lobby.gd +++ b/scripts/lobby.gd @@ -1,275 +1,114 @@ -extends "res://scripts/args.gd" +extends Control -# class member variables go here, for example: -# var a = 2 -# var b = "textvar" -var SERVER_PORT = 54672 -var MAX_PLAYERS = 10 - -var player_info = {} -var my_info = {} -var begun = false -var server_playing = true -var global_server_ip = "nv.cosinegaming.com" -var players_done = [] -var is_connected = false # Technically this can be done with ENetcetera but it's easier this way - -func setup_options(): - var opts = Options.new() - opts.set_banner(('A non-violent MOBA inspired by Overwatch and Zineth')) - opts.add('-singleplayer', false, 'Whether to run singeplayer, starting immediately') - opts.add('-server', false, 'Whether to run as server') - opts.add('-client', false, 'Immediately connect as client') - opts.add('-silent', false, 'If the server is not playing, merely serving') - opts.add('-hero', 'r', 'Your choice of hero (index)') - opts.add('-level', 'r', 'Your choice of level (index) - server only!') - opts.add('-start-game', false, 'Join as a client and immediately start the game') - opts.add('-ai', true, 'Run this client as AI') - opts.add('-no-record', true, "Don't record this play for AI later") - opts.add('-h', false, "Print help") - return opts - -func option_sel(button_name, option): - var button = get_node(button_name) - if option == "r": - option = randi() % button.get_item_count() - else: - option = int(option) - button.select(option) +onready var hero_select = get_node("HeroSelect/Hero") +onready var level_select = get_node("LevelSelect") +onready var start_game_button = get_node("StartGame") +onready var ready_button = get_node("Ready") func _ready(): - my_info.version = [0,0,0] # Semantic versioning: [0].[1].[2] - - randomize() - - get_node("GameBrowser/Play").connect("pressed", self, "connect_global_server") - get_node("PlayerSettings/HeroSelect").connect("item_selected", self, "select_hero") - get_node("PlayerSettings/Username").connect("text_changed", self, "resend_name") - get_node("JoinedGameLobby/StartGame").connect("pressed", self, "start_game") - get_node("CustomGame/Server").connect("pressed", self, "_server_init") - get_node("CustomGame/Client").connect("pressed", self, "_client_init") - get_node("CustomGame/Singleplayer").connect("pressed", self, "_singleplayer_init") - get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") - - var o = setup_options() - o.parse() - - if o.get_value("-silent"): - server_playing = false # TODO: Uncaps :( - if o.get_value("-hero"): - var hero = o.get_value("-hero") - option_sel("PlayerSettings/HeroSelect", hero) - # For some reason, calling option_sel doesn't trigger the actual selection - select_hero(get_node("PlayerSettings/HeroSelect").get_selected_id()) - if o.get_value("-level"): - option_sel("CustomGame/LevelSelect", o.get_value("-level")) - if o.get_value("-server"): - call_deferred("_server_init") - if o.get_value("-client"): - call_deferred("_client_init") - if o.get_value("-start-game"): - my_info.start_game = true - call_deferred("_client_init") - if o.get_value("-singleplayer"): - call_deferred("_singleplayer_init") - if o.get_value("-ai"): - my_info.is_ai = true - if not o.get_value("-no-record") and not o.get_value("-ai"): - my_info.record = true - if o.get_value('-h'): - o.print_help() - quit() - - get_tree().connect("network_peer_connected", self, "_player_connected") - get_tree().connect("network_peer_disconnected", self, "_player_disconnected") - get_tree().connect("connected_to_server", self, "_connected_ok") - -func connect_global_server(): - _client_init(global_server_ip) - -func _client_init(ip=null): - collect_info() - var peer = NetworkedMultiplayerENet.new() - if not ip: - ip = get_node("CustomGame/IP").get_text() - ip = IP.resolve_hostname(ip) - peer.create_client(ip, SERVER_PORT) - get_tree().set_network_peer(peer) - get_node("CustomGame/Client").set_text("Clienting!") - -func _singleplayer_init(): - collect_info() - var peer = NetworkedMultiplayerENet.new() - peer.create_server(SERVER_PORT, 1) - get_tree().set_network_peer(peer) - player_info[1] = my_info - start_game() - -func _server_init(): - collect_info() - var peer = NetworkedMultiplayerENet.new() - peer.create_server(SERVER_PORT, MAX_PLAYERS) - get_tree().set_network_peer(peer) - is_connected = true - get_node("CustomGame/Server").set_text("Serving!") - get_node("JoinedGameLobby").show() - if server_playing: - player_info[1] = my_info + # Connect (to networking) + get_node("Username").connect("text_changed", self, "_send_name") + get_node("Spectating").connect("toggled", self, "_set_info_callback", ["spectating"]) + ready_button.connect("toggled", self, "_set_info_callback", ["ready"]) + start_game_button.connect("pressed", networking, "start_game") + # Connect (from networking) + networking.connect("info_updated", self, "_render_player_list") + get_tree().connect("connected_to_server", self, "_connected") + + # Connect (static) + get_node("Back").connect("pressed", self, "_exit_to_menu") + + var spectating = util.args.get_value("-silent") + get_node("Spectating").pressed = spectating + # Shown, maybe, in _check_begun + start_game_button.hide() + if get_tree().is_network_server(): + start_game_button.show() -func _player_connected(id): - pass + if get_tree().is_network_server(): + # We put level in our players dict because it's automatically broadcast to other players + var level = util.args.get_value("-level") + if level == "r": + level = randi() % level_select.get_item_count() + level = int(level) + _set_level(level) + + level_select.show() + level_select.select(level) + level_select.connect("item_selected", self, "_set_level") + else: + level_select.hide() -func _player_disconnected(id): if get_tree().is_network_server(): - rpc("unregister_player", id) - call_deferred("render_player_list") + _connected() -func _connected_ok(): - rpc("register_player", get_tree().get_network_unique_id(), my_info) - if "start_game" in my_info and my_info.start_game: - rpc_id(1, "start_game") - get_node("JoinedGameLobby").show() - is_connected = true +func _connected(): -func collect_info(): - if not "username" in my_info: - my_info.username = get_node("PlayerSettings/Username").get_text() - if not "hero" in my_info: - my_info.hero = get_node("PlayerSettings/HeroSelect").get_selected_id() - if not "is_right_team" in my_info: - my_info.is_right_team = false # Server assigns team, wait for that + _send_name() + networking.set_info_from_server("ready", false) + networking.set_info_from_server("spectating", util.args.get_value("-silent")) + networking.set_info_from_server("begun", false) + if util.args.get_value("-hero") == "r": + hero_select.random_hero() + else: + hero_select.set_hero(int(util.args.get_value("-hero"))) -remote func register_player(new_peer, info): - player_info[new_peer] = info - render_player_list() - if (get_tree().is_network_server()): - var right_team_count = 0 - # Send current players' info to new player - for old_peer in player_info: - # Send new player, old player's info - rpc_id(new_peer, "register_player", old_peer, player_info[old_peer]) - if old_peer != new_peer: - # We need to assign team later, so count current - if player_info[old_peer].is_right_team: - right_team_count += 1 - # You'd think this part could be met with a simple `rpc(`, but actually it can't - # My best guess is this is because we haven't registered the names yet, but I'm not sure (TODO) - if old_peer != 1: - # Send old player, new player's info (not us, no infinite loop) - rpc_id(old_peer, "register_player", new_peer, info) - if begun: - rpc_id(old_peer, "spawn_player", new_peer) - rpc_id(old_peer, "begin_player_deferred", new_peer) # Spawning is deferred - if not server_playing: - # We didn't catch this in player_info - rpc_id(1, "spawn_player", new_peer) - rpc_id(1, "begin_player_deferred", new_peer) # Spawning is deferred - var assign_right_team = right_team_count * 2 < player_info.size() - rpc("assign_team", new_peer, assign_right_team) - if not begun and player_info.size() == MAX_PLAYERS: - start_game() - if begun: - rpc_id(new_peer, "pre_configure_game", my_info.level) - rpc_id(new_peer, "post_configure_game") + if util.args.get_value("-start-game"): + networking.start_game() -sync func unregister_player(peer): - player_info.erase(peer) - get_node("/root/Level/Players/%d" % peer).queue_free() +func _set_level(level): + networking.level = level -func select_hero(hero): - var description = get_node("PlayerSettings/HeroSelect").hero_text[hero] - get_node("PlayerSettings/HeroDescription").set_text(description) - if is_connected: - rpc("set_hero", get_tree().get_network_unique_id(), hero) +# Because of the annoying way callbacks work (automatic parameter, optional parameter) +# We need a utility function for making these kinds of callbacks for set_info +func _set_info_callback(value, key): + networking.set_info_from_server(key, value) sync func set_hero(peer, hero): - player_info[peer].hero = hero - render_player_list() - -func resend_name(): - if is_connected: - var name = get_node("PlayerSettings/Username").get_text() - rpc("set_name", get_tree().get_network_unique_id(), name) - -sync func set_name(peer, name): - player_info[peer].username = name - render_player_list() - -sync func assign_team(peer, is_right_team): - player_info[peer].is_right_team = is_right_team - if peer == get_tree().get_network_unique_id(): - if is_right_team: - get_node("PlayerSettings/Team").set_text("Right Team") - else: - get_node("PlayerSettings/Team").set_text("Left Team") - render_player_list() - -func render_player_list(): - if has_node("PlayerSettings"): - var list = "" - var hero_names = get_node("PlayerSettings/HeroSelect").hero_names - for p in player_info: - list += "%-15s" % player_info[p].username - list += "%-20s" % hero_names[player_info[p].hero] - if player_info[p].is_right_team: - list += "Right Team" - else: - list += "Left Team" + networking.players[peer].hero = hero + _render_player_list() + +func _send_name(): + var name = get_node("Username").text + networking.set_info_from_server("username", name) + +func _check_begun(): + if networking.players.has(1) and networking.players[1].has("begun"): + var game_started = networking.players[1].begun + if game_started: + start_game_button.show() + start_game_button.text = "Join game" + # The "Ready" toggle doesn't really make sense on a started game + ready_button.hide() + +func _render_player_list(): + _check_begun() + var list = "" + var hero_names = hero_select.hero_names + for p in networking.players: + var player = networking.players[p] + # A spectating server is just a dedicated server, ignore it + if p and player.has("spectating") and not (player.spectating and p == 1): + var username = player.username if player.has("username") else "Loading..." + list += "%-15s " % username + var hero = hero_names[player.hero] if player.has("hero") else "Loading..." + list += "%-10s " % hero + var team = "Loading..." + if player.has("is_right_team"): + if player.is_right_team: + team = "Right Team" + else: + team = "Left Team" + list += "%-11s" % team + var ready_text = "Ready" if player.has("ready") and player.ready else "" + list += "%-6s" % ready_text + if player.has("spectating") and player.spectating: + list += "Spectating" list += "\n" - get_node("JoinedGameLobby/PlayerList").set_text(list) - -sync func start_game(): - my_info.level = get_node("CustomGame/LevelSelect").get_selected_id() - rpc("pre_configure_game", my_info.level) - -sync func done_preconfiguring(who): - players_done.append(who) - if players_done.size() == player_info.size(): - # We call deferred in case singleplayer has placing the player in queue still - call_deferred("rpc", "post_configure_game") - -sync func spawn_player(p): - var hero = player_info[p].hero - var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance() - player.set_name(str(p)) - player.set_network_master(p) - player.player_info = player_info[p] - get_node("/root/Level/Players").call_deferred("add_child", player) - -sync func pre_configure_game(level): - begun = true - my_info.level = level # Remember the level for future player registration - - var self_peer_id = get_tree().get_network_unique_id() - - # Remove the interface so as to not fuck with things - # But we still need the lobby alive to deal with networking! - for element in get_node("/root/Lobby").get_children(): - element.queue_free() - - var world = load("res://scenes/levels/%d.tscn" % level).instance() - get_node("/root").add_child(world) - - # Load all players (including self) - for p in player_info: - player_info[p].level = level - spawn_player(p) - - rpc_id(1, "done_preconfiguring", self_peer_id) - -func begin_player(peer): - get_node("/root/Level/Players/%d" % peer).begin() - -remote func begin_player_deferred(peer): - call_deferred("begin_player", peer) - -sync func reset_state(): - players_done = [] - get_node("/root/Level").queue_free() + get_node("PlayerList").set_text(list) -sync func post_configure_game(): - # Begin all players (including self) - for p in player_info: - begin_player_deferred(p) +func _exit_to_menu(): + get_tree().network_peer.close_connection() + get_tree().change_scene("res://scenes/menu.tscn") diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd new file mode 100644 index 0000000..2f40444 --- /dev/null +++ b/scripts/matchmaking.gd @@ -0,0 +1,105 @@ +extends Node + +var SERVER_TO_SERVER_PORT = 54671 +var MATCHMAKING_PORT = 54672 +var GAME_SIZE = 6 +# Number of games we can make without blowing up the computer +var MAX_GAMES = 50 # Based on how many ports I decided to forward + +var next_port = 54673 + +# Filled with queue info which contains +# { "netid" } +var skirmishing_players = [] +var skirmish +# To avoid the confusion of the gameSERVERS being CLIENTS, +# we just call them games whenever possible +# var games = [] +var game_connections = [] +var game_streams = [] + +# Matchmaker to game servers +var matchmaker_to_games + +enum messages { + ready_to_connect, +} + +onready var lobby = get_node("..") + +func _ready(): + # By default, having this node doesn't do naything + # You must call start_matchmaker to enable it + # If not called, don't call _process (= don't matchmake) + set_process(false) + +func start_matchmaker(): + # Actually run the matchmaker + set_process(true) + + # Setup skirmish server + skirmish = spawn_server(true) + + # Set up communication between GAMESERVERS + # This is necessary for eg, when a player leaves to backfill + matchmaker_to_games = TCP_Server.new() + if matchmaker_to_games.listen(SERVER_TO_SERVER_PORT): + print("Error, could not listen") + + # Use ENet for matchmaker because we can (makes client code cleaner) + var matchmaker_to_players = NetworkedMultiplayerENet.new() + print("Starting matchmaker on port " + str(MATCHMAKING_PORT)) + matchmaker_to_players.create_server(MATCHMAKING_PORT, MAX_GAMES) + get_tree().set_network_peer(matchmaker_to_players) + matchmaker_to_players.connect("peer_connected", self, "queue") + +func _process(delta): + # Manage connection to GAMESERVERS (not clients) + if matchmaker_to_games.is_connection_available(): # check if a gameserver's trying to connect + var game = matchmaker_to_games.take_connection() # accept connection + game_connections.append(game) # store the connection + var stream = PacketPeerStream.new() + stream.set_stream_peer(game) # bind peerstream to new client + game_streams.append(stream) # make new data transfer object for game + for stream in game_streams: + if stream.get_available_packet_count(): + var message = stream.get_var() + if message == messages.ready_to_connect: + var port = stream.get_var() + print("Server " + str(port) + " has requested connection") + skirmish_to_game(port, GAME_SIZE) + +func queue(netid): + print("Player " + str(netid) + " connected.") + add_to_game(netid, skirmish) + skirmishing_players.append(netid) + check_queue() + +func add_to_game(netid, port): + networking.rpc_id(netid, "reconnect", networking.global_server_ip, port) + +func skirmish_to_game(port, count=1): + for i in range(count): + if not skirmishing_players.size(): + return false + print(skirmishing_players[0]) + print("to") + print(port) + add_to_game(skirmishing_players[0], port) + return true + +func check_queue(): + # Prefer making a full game to backfilling + if skirmishing_players.size() >= GAME_SIZE: + spawn_server() + # games.append(port) + +func spawn_server(skirmish=false): + var args = ['-port='+str(next_port)] + if skirmish: + # Begin skirmish immediately, so players "join" instead of "ready" + args.append("-start-game") + OS.execute("util/server.sh", args, false) + next_port += 1 + return (next_port - 1) # Return original port + diff --git a/scripts/menu.gd b/scripts/menu.gd new file mode 100644 index 0000000..3ec2398 --- /dev/null +++ b/scripts/menu.gd @@ -0,0 +1,56 @@ +extends Control + +func _ready(): + randomize() + _gui_setup() + _arg_actions() + +# GUI + +func _gui_setup(): + get_node("Center/Play").connect("pressed", self, "_find_game") + get_node("Center/CustomGame").connect("pressed", self, "_custom_game") + get_node("Center/Singleplayer").connect("pressed", self, "_singleplayer") + get_node("Center/Quit").connect("pressed", get_tree(), "quit") + +func _find_game(): + var ip = networking.global_server_ip + # var ip = util.args.get_value("-ip") + var port = networking.matchmaking.MATCHMAKING_PORT + networking.start_client(ip, port) + +func _custom_game(): + get_tree().change_scene("res://scenes/custom_game.tscn") + +func _singleplayer(): + networking.start_server() + get_tree().change_scene("res://scenes/singleplayer_lobby.tscn") + +# Command line + +func _option_sel(button_name, option): + var button = get_node(button_name) + if option == "r": + option = randi() % button.get_item_count() + else: + option = int(option) + button.select(option) + +func _arg_actions(): + var o = util.args + # if o.get_value("-ai"): + # my_info.is_ai = true + # if not o.get_value("-no-record") and not o.get_value("-ai"): + # my_info.record = true + if o.get_value("-server"): + networking.start_server() + if o.get_value("-matchmaker"): + networking.matchmaking.start_matchmaker() + if o.get_value("-client"): + networking.start_client() + if o.get_value("-singleplayer"): + _singleplayer() + if o.get_value('-h'): + o.print_help() + get_tree().quit() + diff --git a/scripts/networking.gd b/scripts/networking.gd new file mode 100644 index 0000000..14d15f7 --- /dev/null +++ b/scripts/networking.gd @@ -0,0 +1,202 @@ +extends Node + +# Public variables +# ================ + +onready var matchmaking = preload("res://scripts/matchmaking.gd").new() + +remote var players = {} +var global_server_ip = "nv.cosinegaming.com" +var matchmaker_tcp + +var level + +signal info_updated + +# Public methods +# ============== + +func start_client(ip="", port=0): + if not ip: + ip = util.args.get_value("-ip") + ip = IP.resolve_hostname(ip) + if not port: + port = util.args.get_value("-port") + var peer = NetworkedMultiplayerENet.new() + print("Connecting to " + ip + ":" + str(port)) + peer.create_client(ip, port) + get_tree().set_network_peer(peer) + get_tree().change_scene("res://scenes/lobby.tscn") + +remote func reconnect(ip, port): + # Reset previously known players + players = {} + start_client(ip, port) + +func start_server(port=0): + if not port: + port = util.args.get_value("-port") + var peer = NetworkedMultiplayerENet.new() + print("Starting server on port " + str(port)) + peer.create_server(port, matchmaking.GAME_SIZE) + get_tree().set_network_peer(peer) + # As soon as we're listening, let the matchmaker know + _connect_to_matchmaker(port) + _register_player(get_tree().get_network_unique_id()) + if util.args.get_value("-silent"): + set_info("spectating", true) + get_tree().change_scene("res://scenes/lobby.tscn") + +master func set_info(key, value, peer=0): + if not peer: + peer = get_tree().get_network_unique_id() + rpc("_set_info", str(key), value, peer) + +# When connectivity is not yet guaranteed, the only one we know is always +# connected to everyone is the server. So in initial handshakes, it's better to +# tell the server what to tell everyone to do +func set_info_from_server(key, value, peer=0): + if not peer: + peer = get_tree().get_network_unique_id() + rpc_id(1, "set_info", key, value, peer) + +func start_game(): + rpc_id(1, "_start_game") + +sync func reset_state(): + for p in players: + players[p].begun = false + # TODO: Do I in fact want to unready everyone automatically? + players[p].ready = false + get_node("/root/Level").queue_free() + + +# Private methods +# =============== + +func _ready(): + add_child(matchmaking) + + get_tree().connect("network_peer_disconnected", self, "_unregister_player") + get_tree().connect("network_peer_connected", self, "_register_player") + get_tree().connect("connected_to_server", self, "_on_connect") + + connect("info_updated", self, "_check_info") + +remote func _register_player(new_peer): + if get_tree().is_network_server(): + # I tell new player about all the existing people + _send_all_info(new_peer) + set_info("is_right_team", _right_team_next(), new_peer) + +sync func _unregister_player(peer): + players.erase(peer) + var p = util.get_player(peer) + if p: + p.queue_free() + emit_signal("info_updated") + +func _connect_to_matchmaker(game_port): + var matchmaker_peer = StreamPeerTCP.new() + matchmaker_peer.connect_to_host("127.0.0.1", matchmaking.SERVER_TO_SERVER_PORT) + var matchmaker_tcp = PacketPeerStream.new() + matchmaker_tcp.set_stream_peer(matchmaker_peer) + matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect) + matchmaker_tcp.put_var(game_port) + +master func _start_game(): + rpc("_pre_configure_game", level) + +func _send_all_info(new_peer): + for p in players: + if p != new_peer: + for key in players[p]: + var val = players[p][key] + # TODO: This broadcasts every connected peer, + # which isn't really a problem but it's lazy + set_info(key, val, p) + +func _right_team_next(): + var right_team_count = 0 + for p in players: + var player = players[p] + if player.has("is_right_team") and player.is_right_team: + right_team_count += 1 + return (right_team_count <= players.size() / 2) + +sync func _set_info(key, value, peer): + if not players.has(peer): + players[peer] = {} + players[peer][key] = value + emit_signal("info_updated") + +func _on_connect(): + _register_player(get_tree().get_network_unique_id()) + emit_signal("info_updated") + +func _check_info(): + # Check for "everyone is ready" + # Only have 1 person check this, might as well be server + if get_tree().is_network_server(): + var ready = true + var all_done = true + for p in players: + if not players[p].has("spectating") or not players[p].spectating: + if not players[p].has("ready") or not players[p].ready: + ready = false + if not players[p].has("begun") or not players[p].begun: + all_done = false + if all_done: + rpc("_post_configure_game") + elif ready: + # If we're all done, then we don't need to even check a start_game + start_game() + +sync func _spawn_player(p): + var hero = 0 + if players[p].has("hero"): + hero = players[p].hero + var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance() + player.set_name(str(p)) + player.set_network_master(p) + player.player_info = players[p] + get_node("/root/Level/Players").add_child(player) + +func _begin_player(p): + var player = util.get_player(p) + player.begin() + +sync func _pre_configure_game(level): + + var self_peer_id = get_tree().get_network_unique_id() + var self_begun = players[self_peer_id].begun + + if not self_begun: + get_node("/root/Lobby").hide() + + var world = load("res://scenes/levels/%d.tscn" % level).instance() + get_node("/root").add_child(world) + + # Load all players (including self) + for p in players: + if not players[p].spectating: + var existing_player = util.get_player(p) + if not self_begun or not existing_player: + _spawn_player(p) + for p in players: + if not players[p].spectating: + # Begin requires all players + _begin_player(p) + + # Why do we check first? Weird error. It's because set_info triggers a + # start_game if everyone is ready + # This causes a stack overflow if we call it from here repeatedly + # So we only change it once, only start_game twice, and avoida segfault + if not self_begun: + set_info("begun", true) + +sync func _post_configure_game(): + # Begin all players (including self) + # TODO: What do? Maybe, unpause game? + pass + diff --git a/scripts/player.gd b/scripts/player.gd index 8d18ca2..5377fb3 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -52,10 +52,10 @@ func _ready(): get_node("TPCamera/Camera/Ray").add_exception(self) tp_camera.set_enabled(true) tp_camera.cam_view_sensitivity = 0.05 - spawn() if "is_ai" in player_info and player_info.is_ai and not ai_instanced: add_child(preload("res://scenes/ai.tscn").instance()) ai_instanced = true + spawn() else: get_node("PlayerName").set_text(player_info.username) # Remove HUD @@ -178,10 +178,17 @@ func event_to_obj(event): return d func begin(): +<<<<<<< HEAD var master_player = util.get_master_player() +======= + _set_color() + +func _set_color(): + master_player = util.get_master_player() +>>>>>>> 5c5b76f628d2681bb582b3abcc9acef009e2c534 # Set color to blue (teammate) or red (enemy) var color - if master_player and master_player.player_info.is_right_team == player_info.is_right_team: + if master_player.player_info.is_right_team == player_info.is_right_team: color = friend_color else: color = enemy_color @@ -306,7 +313,6 @@ sync func switch_hero(hero): get_node("/root/Level/Players").call_deferred("add_child", new_hero) # We must wait until after _ready is called, so that we don't end up at spawn new_hero.call_deferred("set_status", get_status()) - new_hero.call_deferred("begin") queue_free() func write_recording(): diff --git a/scripts/tp_camera.gd b/scripts/tp_camera.gd index 9f3d129..2ea1502 100644 --- a/scripts/tp_camera.gd +++ b/scripts/tp_camera.gd @@ -101,7 +101,6 @@ func cam_update(): if cam_ray_result.size() != 0: var a = (cam_ray_result.position-pivot.get_global_transform().origin).normalized(); var b = pivot.get_global_transform().origin.distance_to(cam_ray_result.position); - #pos = cam_ray_result.position; pos = pivot.get_global_transform().origin+a*max(b-0.1, 0); else: pos = cam_pos; diff --git a/scripts/util.gd b/scripts/util.gd index 3f49fa8..4a3b62a 100644 --- a/scripts/util.gd +++ b/scripts/util.gd @@ -1,7 +1,20 @@ extends Node +var version = [0,0,0] # Semantic versioning: [0].[1].[2] + +var args + +onready var Options = preload("res://scripts/args.gd").new().Options + +func _ready(): + args = _get_args() + func get_master_player(): - var path = "/root/Level/Players/%d" % get_tree().get_network_unique_id() + return get_player(get_tree().get_network_unique_id()) + +func get_player(netid): + # We not %d? Because sometimes we need to do get_player(thing.get_name()) + var path = "/root/Level/Players/%s" % str(netid) if has_node(path): return get_node(path) else: @@ -14,3 +27,22 @@ func is_friendly(player): else: return true # Doesn't matter, we're headless +func _get_args(): + var opts = Options.new() + opts.set_banner(('A non-violent MOBA inspired by Overwatch and Zineth')) + opts.add('-singleplayer', false, 'Whether to run singeplayer, starting immediately') + opts.add('-server', false, 'Whether to run as server') + opts.add('-matchmaker', false, 'Whether to be the sole matchmaker') + opts.add('-client', false, 'Immediately connect as client') + opts.add('-silent', false, 'If the server is not playing, merely serving') + opts.add('-ip', '127.0.0.1', 'The ip to connect to (client only!)') + opts.add('-port', 54673, 'The port to run a server on or connect to') + opts.add('-hero', 'r', 'Your choice of hero (index)') + opts.add('-level', 'r', 'Your choice of level (index) - server only!') + opts.add('-start-game', false, 'Join as a client and immediately start the game') + opts.add('-ai', true, 'Run this client as AI') + opts.add('-no-record', true, "Don't record this play for AI later") + opts.add('-h', false, "Print help") + opts.parse() + return opts + diff --git a/util/matchmaker.sh b/util/matchmaker.sh new file mode 100644 index 0000000..1fb5897 --- /dev/null +++ b/util/matchmaker.sh @@ -0,0 +1 @@ +godot-server -matchmaker diff --git a/util/server.sh b/util/server.sh index 9959782..3f24370 100644 --- a/util/server.sh +++ b/util/server.sh @@ -1,2 +1,2 @@ -godot-server -level=2 -silent -server -start-game +godot-server -level=2 -silent -server "$@" diff --git a/util/start-multiple.sh b/util/start-multiple.sh index 1213138..8e4d3d7 100644 --- a/util/start-multiple.sh +++ b/util/start-multiple.sh @@ -10,5 +10,5 @@ fi util/open-multiple.sh $count "$@" sleep 1 -godot -start-game "$@" & +godot -client -start-game "$@" &