From eed70795dd760e371e603d3a00e160ac103f7f6b Mon Sep 17 00:00:00 2001 From: copygirl Date: Wed, 28 Apr 2021 08:13:22 +0200 Subject: [PATCH] Redo networking, use integrated server "Singleplayer" now connects to an integrated server which runs in a completely separate scene tree - Update to Godot 3.3.0 - Reorganize code into multiple subfolders & files - Add Client and Server scenes & scripts, extend Game - Add NetworkSync, syncs state for networked objects - Add NetworkRPC, allows for C#-friendly RPC by adding attribute to instance and static methods - Replace most packets with RPC - Allow de/serialization of Node / synced objects - Remove LocalPlayer for now, add IsLocal to Player - Add BlockPos and Facing structs --- YourFortV.csproj | 2 +- project.godot | 6 +- scene/Block.tscn | 2 +- scene/ClientScene.tscn | 43 ++++ scene/EscapeMenu.tscn | 18 +- scene/GameScene.tscn | 54 +--- scene/LocalPlayer.tscn | 15 -- scene/Player.tscn | 11 +- scene/ServerScene.tscn | 7 + src/Block.cs | 6 - src/BlockPackets.cs | 61 ----- src/CreativeBuilding.cs | 137 +++------- src/EscapeMenuAppearance.cs | 12 +- src/EscapeMenuMultiplayer.cs | 128 ++++++---- src/Game.cs | 52 ---- src/LocalPlayer.cs | 50 ---- src/Network.cs | 234 ----------------- src/Network/DeSerializer.Impl.cs | 257 +++++++++++++++++++ src/Network/DeSerializer.Interfaces.cs | 53 ++++ src/Network/DeSerializerRegistry.cs | 65 +++++ src/Network/IntegratedServer.cs | 27 ++ src/Network/NetworkPackets.cs | 96 +++++++ src/Network/NetworkRPC.cs | 205 +++++++++++++++ src/Network/NetworkSync.cs | 340 +++++++++++++++++++++++++ src/NetworkAPI.cs | 208 --------------- src/NetworkAPIDeSerializers.cs | 198 -------------- src/Objects/Block.cs | 19 ++ src/Objects/Player.cs | 126 +++++++++ src/Player.cs | 126 --------- src/Scenes/Client.cs | 74 ++++++ src/Scenes/Game.cs | 27 ++ src/Scenes/Server.cs | 110 ++++++++ src/{ => Utility}/Extensions.cs | 7 + src/Utility/Math.cs | 99 +++++++ 34 files changed, 1704 insertions(+), 1171 deletions(-) create mode 100644 scene/ClientScene.tscn delete mode 100644 scene/LocalPlayer.tscn create mode 100644 scene/ServerScene.tscn delete mode 100644 src/Block.cs delete mode 100644 src/BlockPackets.cs delete mode 100644 src/Game.cs delete mode 100644 src/LocalPlayer.cs delete mode 100644 src/Network.cs create mode 100644 src/Network/DeSerializer.Impl.cs create mode 100644 src/Network/DeSerializer.Interfaces.cs create mode 100644 src/Network/DeSerializerRegistry.cs create mode 100644 src/Network/IntegratedServer.cs create mode 100644 src/Network/NetworkPackets.cs create mode 100644 src/Network/NetworkRPC.cs create mode 100644 src/Network/NetworkSync.cs delete mode 100644 src/NetworkAPI.cs delete mode 100644 src/NetworkAPIDeSerializers.cs create mode 100644 src/Objects/Block.cs create mode 100644 src/Objects/Player.cs delete mode 100644 src/Player.cs create mode 100644 src/Scenes/Client.cs create mode 100644 src/Scenes/Game.cs create mode 100644 src/Scenes/Server.cs rename src/{ => Utility}/Extensions.cs (53%) create mode 100644 src/Utility/Math.cs diff --git a/YourFortV.csproj b/YourFortV.csproj index cb988d2..db88a8d 100644 --- a/YourFortV.csproj +++ b/YourFortV.csproj @@ -1,4 +1,4 @@ - + net472 diff --git a/project.godot b/project.godot index 68bf0a6..6d3a700 100644 --- a/project.godot +++ b/project.godot @@ -10,13 +10,12 @@ config_version=4 _global_script_classes=[ ] _global_script_class_icons={ - } [application] config/name="YourFortV" -run/main_scene="res://scene/GameScene.tscn" +run/main_scene="res://scene/ClientScene.tscn" boot_splash/image="res://gfx/icon.png" boot_splash/use_filter=false config/icon="res://gfx/icon.png" @@ -90,6 +89,7 @@ interact_break={ [rendering] -quality/2d/use_pixel_snap=true +2d/snapping/use_gpu_pixel_snap=true quality/filters/use_nearest_mipmap_filter=true quality/dynamic_fonts/use_oversampling=false +quality/2d/use_pixel_snap=true diff --git a/scene/Block.tscn b/scene/Block.tscn index b3af89b..0be6d5c 100644 --- a/scene/Block.tscn +++ b/scene/Block.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=2] [ext_resource path="res://gfx/block.png" type="Texture" id=1] -[ext_resource path="res://src/Block.cs" type="Script" id=2] +[ext_resource path="res://src/Objects/Block.cs" type="Script" id=2] [sub_resource type="RectangleShape2D" id=1] extents = Vector2( 8, 8 ) diff --git a/scene/ClientScene.tscn b/scene/ClientScene.tscn new file mode 100644 index 0000000..cdd2d76 --- /dev/null +++ b/scene/ClientScene.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=9 format=2] + +[ext_resource path="res://scene/GameScene.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Cursor.cs" type="Script" id=2] +[ext_resource path="res://gfx/cursor.png" type="Texture" id=3] +[ext_resource path="res://gfx/background.png" type="Texture" id=4] +[ext_resource path="res://src/Background.cs" type="Script" id=5] +[ext_resource path="res://src/Viewport.cs" type="Script" id=6] +[ext_resource path="res://scene/EscapeMenu.tscn" type="PackedScene" id=7] +[ext_resource path="res://src/Scenes/Client.cs" type="Script" id=8] + +[node name="Client" instance=ExtResource( 1 )] +script = ExtResource( 8 ) +CursorPath = NodePath("CursorLayer/Cursor") + +[node name="Viewport" type="Node" parent="." index="0"] +script = ExtResource( 6 ) + +[node name="Background" type="TextureRect" parent="." index="1"] +modulate = Color( 0.278431, 0.286275, 0.301961, 1 ) +margin_right = 1280.0 +margin_bottom = 720.0 +texture = ExtResource( 4 ) +stretch_mode = 2 +script = ExtResource( 5 ) +__meta__ = { +"_edit_lock_": true, +"_edit_use_anchors_": false +} + +[node name="HUD" type="CanvasLayer" parent="." index="3"] + +[node name="EscapeMenu" parent="HUD" index="0" instance=ExtResource( 7 )] +visible = false + +[node name="CursorLayer" type="CanvasLayer" parent="." index="4"] +layer = 2 +follow_viewport_enable = true + +[node name="Cursor" type="Sprite" parent="CursorLayer" index="0"] +z_index = 1000 +texture = ExtResource( 3 ) +script = ExtResource( 2 ) diff --git a/scene/EscapeMenu.tscn b/scene/EscapeMenu.tscn index c53dede..52c0a92 100644 --- a/scene/EscapeMenu.tscn +++ b/scene/EscapeMenu.tscn @@ -12,6 +12,7 @@ anchor_bottom = 1.0 theme = ExtResource( 1 ) script = ExtResource( 2 ) __meta__ = { +"_edit_lock_": true, "_edit_use_anchors_": false } ReturnPath = NodePath("CenterContainer/PanelContainer/VBoxContainer/Return") @@ -20,11 +21,15 @@ ReturnPath = NodePath("CenterContainer/PanelContainer/VBoxContainer/Return") anchor_right = 1.0 anchor_bottom = 1.0 color = Color( 0, 0, 0, 0.501961 ) +__meta__ = { +"_edit_lock_": true +} [node name="CenterContainer" type="CenterContainer" parent="."] anchor_right = 1.0 anchor_bottom = 1.0 __meta__ = { +"_edit_lock_": true, "_edit_use_anchors_": false } @@ -134,9 +139,9 @@ step = 0.0 scrollable = false [node name="Label" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance/VBoxContainer"] -margin_top = 42.0 +margin_top = 43.0 margin_right = 182.0 -margin_bottom = 54.0 +margin_bottom = 55.0 rect_min_size = Vector2( 0, 12 ) custom_colors/font_color = Color( 0.6, 0.6, 0.6, 1 ) text = "(Close Menu to apply changes.)" @@ -153,7 +158,7 @@ margin_right = -4.0 margin_bottom = -4.0 script = ExtResource( 4 ) StatusPath = NodePath("ContainerStatus/Status") -ServerStartStopPath = NodePath("ContainerServer/ServerStartStop") +ServerOpenClosePath = NodePath("ContainerServer/ServerOpenClose") ServerPortPath = NodePath("ContainerServer/ServerPort") ClientDisConnectPath = NodePath("ContainerClient/ClientDisConnect") ClientAddressPath = NodePath("ContainerClient/ClientAddress") @@ -213,12 +218,12 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="ServerStartStop" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer"] +[node name="ServerOpenClose" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer"] margin_left = 94.0 margin_right = 221.0 margin_bottom = 19.0 size_flags_horizontal = 3 -text = "Start Server" +text = "Open Server" __meta__ = { "_edit_use_anchors_": false } @@ -327,11 +332,12 @@ scroll_active = false __meta__ = { "_edit_use_anchors_": false } + [connection signal="visibility_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" method="_on_Appearance_visibility_changed"] [connection signal="text_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance/VBoxContainer/ContainerName/DisplayName" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" method="_on_DisplayName_text_changed"] [connection signal="value_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance/VBoxContainer/ContainerColor/Hue" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" method="_on_Hue_value_changed"] [connection signal="text_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer/ServerPort" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ServerPort_text_changed"] -[connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer/ServerStartStop" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ServerStartStop_pressed"] +[connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer/ServerOpenClose" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ServerOpenClose_pressed"] [connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerClient/ClientDisConnect" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ClientDisConnect_pressed"] [connection signal="toggled" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerHideAddress/HideAddress" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_HideAddress_toggled"] [connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/Quit" to="." method="_on_Quit_pressed"] diff --git a/scene/GameScene.tscn b/scene/GameScene.tscn index 4b7866a..9d340eb 100644 --- a/scene/GameScene.tscn +++ b/scene/GameScene.tscn @@ -1,64 +1,16 @@ -[gd_scene load_steps=12 format=2] +[gd_scene load_steps=2 format=2] -[ext_resource path="res://scene/EscapeMenu.tscn" type="PackedScene" id=1] -[ext_resource path="res://src/Cursor.cs" type="Script" id=2] -[ext_resource path="res://src/Game.cs" type="Script" id=3] -[ext_resource path="res://gfx/cursor.png" type="Texture" id=4] -[ext_resource path="res://scene/LocalPlayer.tscn" type="PackedScene" id=5] -[ext_resource path="res://scene/Block.tscn" type="PackedScene" id=6] -[ext_resource path="res://src/Viewport.cs" type="Script" id=7] -[ext_resource path="res://src/Network.cs" type="Script" id=8] -[ext_resource path="res://scene/Player.tscn" type="PackedScene" id=9] -[ext_resource path="res://gfx/background.png" type="Texture" id=10] -[ext_resource path="res://src/Background.cs" type="Script" id=11] +[ext_resource path="res://src/Scenes/Game.cs" type="Script" id=3] [node name="Game" type="Node2D"] pause_mode = 2 script = ExtResource( 3 ) -CursorPath = NodePath("CursorLayer/Cursor") +PlayerContainerPath = NodePath("World/Players") BlockContainerPath = NodePath("World/Blocks") -BlockScene = ExtResource( 6 ) - -[node name="Viewport" type="Node" parent="."] -script = ExtResource( 7 ) - -[node name="Network" type="Node" parent="."] -script = ExtResource( 8 ) -PlayerContainerPath = NodePath("../World/Players") -OtherPlayerScene = ExtResource( 9 ) - -[node name="Background" type="TextureRect" parent="."] -modulate = Color( 0.278431, 0.286275, 0.301961, 1 ) -margin_left = 1.0 -margin_top = -1.0 -margin_right = 1281.0 -margin_bottom = 719.0 -texture = ExtResource( 10 ) -stretch_mode = 2 -script = ExtResource( 11 ) -__meta__ = { -"_edit_use_anchors_": false -} [node name="World" type="Node" parent="."] [node name="Players" type="Node" parent="World"] pause_mode = 1 -[node name="LocalPlayer" parent="World/Players" instance=ExtResource( 5 )] - [node name="Blocks" type="Node" parent="World"] - -[node name="HUD" type="CanvasLayer" parent="."] - -[node name="EscapeMenu" parent="HUD" instance=ExtResource( 1 )] -visible = false - -[node name="CursorLayer" type="CanvasLayer" parent="."] -layer = 2 -follow_viewport_enable = true - -[node name="Cursor" type="Sprite" parent="CursorLayer"] -z_index = 1000 -texture = ExtResource( 4 ) -script = ExtResource( 2 ) diff --git a/scene/LocalPlayer.tscn b/scene/LocalPlayer.tscn deleted file mode 100644 index ddcd1d0..0000000 --- a/scene/LocalPlayer.tscn +++ /dev/null @@ -1,15 +0,0 @@ -[gd_scene load_steps=4 format=2] - -[ext_resource path="res://scene/Player.tscn" type="PackedScene" id=1] -[ext_resource path="res://src/CreativeBuilding.cs" type="Script" id=2] -[ext_resource path="res://src/LocalPlayer.cs" type="Script" id=3] - -[node name="LocalPlayer" instance=ExtResource( 1 )] -script = ExtResource( 3 ) - -[node name="Camera" type="Camera2D" parent="." index="3"] -pause_mode = 2 -current = true - -[node name="CreativeBuilding" type="Node2D" parent="." index="4"] -script = ExtResource( 2 ) diff --git a/scene/Player.tscn b/scene/Player.tscn index af2b46f..692e401 100644 --- a/scene/Player.tscn +++ b/scene/Player.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=5 format=2] +[gd_scene load_steps=6 format=2] [ext_resource path="res://ui_theme.tres" type="Theme" id=1] [ext_resource path="res://gfx/player.png" type="Texture" id=2] -[ext_resource path="res://src/Player.cs" type="Script" id=3] +[ext_resource path="res://src/Objects/Player.cs" type="Script" id=3] +[ext_resource path="res://src/CreativeBuilding.cs" type="Script" id=4] [sub_resource type="CircleShape2D" id=1] radius = 8.0 @@ -36,3 +37,9 @@ __meta__ = { [node name="Sprite" type="Sprite" parent="."] z_index = -5 texture = ExtResource( 2 ) + +[node name="Camera" type="Camera2D" parent="."] +pause_mode = 2 + +[node name="CreativeBuilding" type="Node2D" parent="."] +script = ExtResource( 4 ) diff --git a/scene/ServerScene.tscn b/scene/ServerScene.tscn new file mode 100644 index 0000000..95958dc --- /dev/null +++ b/scene/ServerScene.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://scene/GameScene.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Scenes/Server.cs" type="Script" id=2] + +[node name="Server" instance=ExtResource( 1 )] +script = ExtResource( 2 ) diff --git a/src/Block.cs b/src/Block.cs deleted file mode 100644 index 55518c9..0000000 --- a/src/Block.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Godot; - -public class Block : StaticBody2D -{ - // Empty, but useful to find out whether an object is a "block". -} diff --git a/src/BlockPackets.cs b/src/BlockPackets.cs deleted file mode 100644 index d3df67b..0000000 --- a/src/BlockPackets.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Godot; - -public static class BlockPackets -{ - public static void Register() - { - Network.API.RegisterS2CPacket(OnSpawnBlockPacket); - Network.API.RegisterS2CPacket(OnSpawnBlocksPacket); - Network.API.RegisterS2CPacket(OnDestroyBlockPacket); - } - - private static void OnSpawnBlockPacket(SpawnBlockPacket packet) - { - // Delete any block previously at this position. - Game.Instance.GetBlockAt(packet.Position)?.QueueFree(); - - var block = Game.Instance.BlockScene.Init(); - block.Position = packet.Position; - block.Modulate = packet.Color; - Game.Instance.BlockContainer.AddChild(block); - } - - private static void OnSpawnBlocksPacket(SpawnBlocksPacket packet) - { - Game.Instance.ClearBlocks(); - foreach (var blockInfo in packet.Blocks) { - var block = Game.Instance.BlockScene.Init(); - block.Position = blockInfo.Position; - block.Modulate = blockInfo.Color; - Game.Instance.BlockContainer.AddChild(block); - } - } - - private static void OnDestroyBlockPacket(DestroyBlockPacket packet) - => Game.Instance.GetBlockAt(packet.Position)?.QueueFree(); -} - -public class SpawnBlockPacket -{ - public Vector2 Position { get; } - public Color Color { get; } - public SpawnBlockPacket(Block block) - { Position = block.Position; Color = block.Modulate; } -} - -public class SpawnBlocksPacket -{ - public List Blocks { get; } - public SpawnBlocksPacket() - => Blocks = Game.Instance.BlockContainer.GetChildren().OfType() - .Select(block => new SpawnBlockPacket(block)).ToList(); -} - -public class DestroyBlockPacket -{ - public Vector2 Position { get; } - public DestroyBlockPacket(Block block) - { Position = block.Position; } -} diff --git a/src/CreativeBuilding.cs b/src/CreativeBuilding.cs index 1cfee84..6b0fd99 100644 --- a/src/CreativeBuilding.cs +++ b/src/CreativeBuilding.cs @@ -11,32 +11,27 @@ public class CreativeBuilding : Node2D Breaking, } - private static readonly Vector2[] _neighborPositions = new Vector2[]{ - Vector2.Left*16, Vector2.Right*16, Vector2.Up*16, Vector2.Down*16 }; - [Export] public int MaxLength { get; set; } = 6; private Texture _blockTex; - private Vector2 _startPos; - private Vector2 _direction; + private BlockPos _startPos; + private Facing _direction; private int _length; private bool _canBuild; private BuildMode? _currentMode = null; - private IEnumerable BlockPositions => - Enumerable.Range(0, _length + 1).Select(i => _startPos + _direction * (i * 16)); - public override void _Ready() { _blockTex = GD.Load("res://gfx/block.png"); } - public override void _Process(float delta) + public override void _PhysicsProcess(float delta) { + if (!(this.GetGame() is Client client)) return; Update(); - if (EscapeMenu.Instance.Visible || !Game.Cursor.Visible) + if (EscapeMenu.Instance.Visible || !client.Cursor.Visible) { _currentMode = null; return; } switch (_currentMode) { @@ -49,127 +44,79 @@ public class CreativeBuilding : Node2D case BuildMode.Placing: if (Input.IsActionJustPressed("interact_break")) _currentMode = null; else if (!Input.IsActionPressed("interact_place")) { - if (_canBuild) - foreach (var pos in BlockPositions) - PlaceBlock(pos); + if (_canBuild) this.GetClient()?.RPC(PlaceLine, _startPos, _direction, _length); _currentMode = null; } break; case BuildMode.Breaking: if (Input.IsActionJustPressed("interact_place")) _currentMode = null; else if (!Input.IsActionPressed("interact_break")) { - foreach (var pos in BlockPositions) { - var block = Game.Instance.GetBlockAt(pos); - if (block != null) BreakBlock(block); - } + this.GetClient()?.RPC(BreakLine, _startPos, _direction, _length); _currentMode = null; } break; } if (_currentMode != null) { - var rad90 = Mathf.Deg2Rad(90.0F); - var angle = Mathf.Round(_startPos.AngleToPoint(Game.Cursor.Position) / rad90) * rad90; - _direction = new Vector2(-Mathf.Cos(angle), -Mathf.Sin(angle)); - _length = Math.Min(MaxLength, Mathf.RoundToInt(_startPos.DistanceTo(Game.Cursor.Position) / 16)); + var start = _startPos.ToVector(); + var angle = client.Cursor.Position.AngleToPoint(start); // angle_to_point appears reversed. + _direction = Facings.FromAngle(angle); + _length = Math.Min(MaxLength, Mathf.RoundToInt(start.DistanceTo(client.Cursor.Position) / 16)); } else { - _startPos = (Game.Cursor.Position / 16).Round() * 16; + _startPos = BlockPos.FromVector(client.Cursor.Position); _length = 0; } - bool IsBlockAt(Vector2 pos) => Game.Instance.GetBlockAt(pos) != null; - _canBuild = !IsBlockAt(_startPos) && _neighborPositions.Any(pos => IsBlockAt(_startPos + pos)); - } - - private Block PlaceBlock(Vector2 position) - { - if (Game.Instance.GetBlockAt(position) != null) return null; - // FIXME: Test if there is a player in the way. - - var block = Game.Instance.BlockScene.Init(); - block.Position = position; - block.Modulate = Game.LocalPlayer.Color.Blend(Color.FromHsv(0.0F, 0.0F, GD.Randf(), 0.2F)); - Game.Instance.BlockContainer.AddChild(block); - - if (Network.IsMultiplayerReady) { - if (Network.IsServer) Network.API.SendToEveryone(new SpawnBlockPacket(block)); - else Network.API.SendToServer(new PlaceBlockPacket(position)); - } - - return block; - } - - private void BreakBlock(Block block) - { - // FIXME: Use a different (safer) way to check if a block is one of the default ones. - if (block.Modulate.s < 0.5F) return; - - if (Network.IsMultiplayerReady) { - if (Network.IsServer) Network.API.SendToEveryone(new DestroyBlockPacket(block)); - else Network.API.SendToServer(new BreakBlockPacket(block)); - } - - block.QueueFree(); + bool IsBlockAt(BlockPos pos) => client.GetBlockAt(pos) != null; + _canBuild = !IsBlockAt(_startPos) && Facings.All.Any(pos => IsBlockAt(_startPos + pos.ToBlockPos())); } public override void _Draw() { - if (!Game.Cursor.Visible) return; + if (!(this.GetGame() is Client client) || !client.Cursor.Visible || EscapeMenu.Instance.Visible) return; var green = Color.FromHsv(1.0F / 3, 1.0F, 1.0F, 0.4F); var red = Color.FromHsv(0.0F, 1.0F, 1.0F, 0.4F); var black = new Color(0.0F, 0.0F, 0.0F, 0.65F); - foreach (var pos in BlockPositions) { - var hasBlock = Game.Instance.GetBlockAt(pos) != null; - var color = (_currentMode != BuildMode.Breaking) + foreach (var pos in GetBlockPositions(_startPos, _direction, _length)) { + var hasBlock = client.GetBlockAt(pos) != null; + var color = (_currentMode != BuildMode.Breaking) ? ((_canBuild && !hasBlock) ? green : red) : (hasBlock ? black : red); - DrawTexture(_blockTex, ToLocal(pos - _blockTex.GetSize() / 2), color); + DrawTexture(_blockTex, ToLocal(pos.ToVector() - _blockTex.GetSize() / 2), color); } } + private static IEnumerable GetBlockPositions(BlockPos start, Facing direction, int length) + => Enumerable.Range(0, length + 1).Select(i => start + direction.ToBlockPos() * i); - public static void RegisterPackets() - { - Network.API.RegisterC2SPacket(OnPlaceBlockPacket); - Network.API.RegisterC2SPacket(OnBreakBlockPacket); - } - - private class PlaceBlockPacket - { - public Vector2 Position { get; } - public PlaceBlockPacket(Vector2 position) => Position = position; - } - private static void OnPlaceBlockPacket(Player player, PlaceBlockPacket packet) + [RPC(PacketDirection.ClientToServer)] + private static void PlaceLine(Server server, NetworkID networkID, BlockPos start, Facing direction, int length) { - if (Game.Instance.GetBlockAt(packet.Position) != null) return; - var block = Game.Instance.BlockScene.Init(); - block.Position = packet.Position; - block.Modulate = player.Color.Blend(Color.FromHsv(0.0F, 0.0F, GD.Randf(), 0.2F)); - Game.Instance.BlockContainer.AddChild(block); - - Network.API.SendToEveryone(new SpawnBlockPacket(block)); + var player = server.GetPlayer(networkID); + // TODO: Test if starting block is valid. + foreach (var pos in GetBlockPositions(start, direction, length)) { + if (server.GetBlockAt(pos) != null) continue; + // FIXME: Test if there is a player in the way. + + server.Spawn(block => { + block.Position = pos; + block.Color = player.Color.Blend(Color.FromHsv(0.0F, 0.0F, GD.Randf(), 0.2F)); + }); + } } - private class BreakBlockPacket - { - public Vector2 Position { get; } - public BreakBlockPacket(Block block) => Position = block.Position; - } - private static void OnBreakBlockPacket(Player player, BreakBlockPacket packet) + [RPC(PacketDirection.ClientToServer)] + private static void BreakLine(Server server, NetworkID networkID, BlockPos start, Facing direction, int length) { - var block = Game.Instance.GetBlockAt(packet.Position); - if (block == null) return; - - if (block.Modulate.s < 0.5F) { - // TODO: Respawn the block the client thought it destroyed? - return; + // var player = server.GetPlayer(networkID); + // TODO: Do additional verification on the packet. + foreach (var pos in GetBlockPositions(start, direction, length)) { + var block = server.GetBlockAt(pos); + if (block?.Unbreakable != false) continue; + block.Destroy(); } - // TODO: Further verify whether player can break a block at this position. - - Network.API.SendToEveryoneExcept(player, new DestroyBlockPacket(block)); - block.QueueFree(); } } diff --git a/src/EscapeMenuAppearance.cs b/src/EscapeMenuAppearance.cs index d78127e..fa27025 100644 --- a/src/EscapeMenuAppearance.cs +++ b/src/EscapeMenuAppearance.cs @@ -18,8 +18,7 @@ public class EscapeMenuAppearance : CenterContainer ColorSlider = GetNode(ColorSliderPath); ColorSlider.Value = GD.Randf(); - var color = Color.FromHsv((float)ColorSlider.Value, 1.0F, 1.0F); - Game.LocalPlayer.Color = ColorPreview.Modulate = color; + ColorPreview.Modulate = Color.FromHsv((float)ColorSlider.Value, 1.0F, 1.0F); } @@ -45,9 +44,10 @@ public class EscapeMenuAppearance : CenterContainer private void _on_Appearance_visibility_changed() { - if (!IsVisibleInTree()) - Player.ChangeAppearance(Game.LocalPlayer, - DisplayName.Text, ColorPreview.Modulate, - Network.IsClient); + if (IsVisibleInTree()) return; + var client = this.GetClient(); + // TODO: Find a better way to know if we're connected? + if (client.CustomMultiplayer.NetworkPeer?.GetConnectionStatus() == NetworkedMultiplayerPeer.ConnectionStatus.Connected) + client.RPC(Player.ChangeAppearance, DisplayName.Text, ColorPreview.Modulate); } } diff --git a/src/EscapeMenuMultiplayer.cs b/src/EscapeMenuMultiplayer.cs index 3bdf735..0caf271 100644 --- a/src/EscapeMenuMultiplayer.cs +++ b/src/EscapeMenuMultiplayer.cs @@ -3,65 +3,81 @@ using Godot; public class EscapeMenuMultiplayer : Container { + private const ushort DEFAULT_PORT = 42005; + [Export] public NodePath StatusPath { get; set; } - [Export] public NodePath ServerStartStopPath { get; set; } + [Export] public NodePath ServerOpenClosePath { get; set; } [Export] public NodePath ServerPortPath { get; set; } [Export] public NodePath ClientDisConnectPath { get; set; } [Export] public NodePath ClientAddressPath { get; set; } public Label Status { get; private set; } - public Button ServerStartStop { get; private set; } + public Button ServerOpenClose { get; private set; } public LineEdit ServerPort { get; private set; } public Button ClientDisConnect { get; private set; } public LineEdit ClientAddress { get; private set; } + public IntegratedServer Server { get; private set; } + public override void _Ready() { Status = GetNode