diff --git a/.idea/.idea.CodingLand/.idea/vcs.xml b/.idea/.idea.CodingLand/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.CodingLand/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Scenes/Player.tscn b/Scenes/Player.tscn
index 55a06ed..649872a 100644
--- a/Scenes/Player.tscn
+++ b/Scenes/Player.tscn
@@ -6,8 +6,9 @@
[sub_resource type="CircleShape2D" id="CircleShape2D_68yf8"]
radius = 26.0768
-[node name="Player" type="CharacterBody2D"]
+[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("PlayerDashCountdown")]
script = ExtResource("1_0btyt")
+PlayerDashCountdown = NodePath("DashCountdown")
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(2.08165e-12, 2.08165e-12)
@@ -16,3 +17,6 @@ texture = ExtResource("1_cqpqa")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_68yf8")
+
+[node name="DashCountdown" type="Timer" parent="."]
+one_shot = true
diff --git a/Scenes/Root.tscn b/Scenes/Root.tscn
index 9fbb810..3b911f1 100644
--- a/Scenes/Root.tscn
+++ b/Scenes/Root.tscn
@@ -1,14 +1,26 @@
-[gd_scene load_steps=2 format=3 uid="uid://bjhmjrldq4lkt"]
+[gd_scene load_steps=4 format=3 uid="uid://bjhmjrldq4lkt"]
+[ext_resource type="Script" path="res://Scripts/Multiplayer.cs" id="1_fym13"]
[ext_resource type="PackedScene" uid="uid://b3gx0bl43lku3" path="res://Scenes/Player.tscn" id="1_vby0g"]
+[ext_resource type="PackedScene" uid="uid://bvll23f5ibd4v" path="res://Scenes/UI/MultiplayerUI.tscn" id="2_7o53i"]
[node name="Node" type="Node"]
-[node name="Camera2D" type="Camera2D" parent="."]
+[node name="MultiplayerNode" type="Node" parent="." node_paths=PackedStringArray("Playground")]
+script = ExtResource("1_fym13")
+Playground = NodePath("../Playground")
+
+[node name="MultiplayerUi" parent="." node_paths=PackedStringArray("MultiplayerController") instance=ExtResource("2_7o53i")]
+MultiplayerController = NodePath("../MultiplayerNode")
+
+[node name="Playground" type="Node2D" parent="."]
+
+[node name="Camera2D" type="Camera2D" parent="Playground"]
+enabled = false
process_callback = 0
position_smoothing_enabled = true
rotation_smoothing_enabled = true
-[node name="Player" parent="." node_paths=PackedStringArray("PlayerCamera") instance=ExtResource("1_vby0g")]
+[node name="Player" parent="Playground" node_paths=PackedStringArray("PlayerCamera") instance=ExtResource("1_vby0g")]
position = Vector2(0, 2)
PlayerCamera = NodePath("../Camera2D")
diff --git a/Scenes/UI/MultiplayerUI.tscn b/Scenes/UI/MultiplayerUI.tscn
new file mode 100644
index 0000000..39a5d45
--- /dev/null
+++ b/Scenes/UI/MultiplayerUI.tscn
@@ -0,0 +1,49 @@
+[gd_scene load_steps=2 format=3 uid="uid://bvll23f5ibd4v"]
+
+[ext_resource type="Script" path="res://Scripts/UI/MultiplayerUi.cs" id="1_fm6j5"]
+
+[node name="MultiplayerUi" type="Control" node_paths=PackedStringArray("ServerPortInput", "ServerAddrInput", "StartAsServerButton", "StartAsClientButton")]
+process_mode = 3
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_fm6j5")
+ServerPortInput = NodePath("CenterContainer/VBoxContainer/PortEdit")
+ServerAddrInput = NodePath("CenterContainer/VBoxContainer/AddrEdit")
+StartAsServerButton = NodePath("CenterContainer/VBoxContainer/HBoxContainer/HostButton")
+StartAsClientButton = NodePath("CenterContainer/VBoxContainer/HBoxContainer/ConnectButton")
+
+[node name="CenterContainer" type="CenterContainer" parent="."]
+layout_mode = 0
+offset_right = 1152.0
+offset_bottom = 648.0
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="CenterContainer/VBoxContainer"]
+layout_mode = 2
+text = "Mutiplayer"
+horizontal_alignment = 1
+
+[node name="PortEdit" type="LineEdit" parent="CenterContainer/VBoxContainer"]
+layout_mode = 2
+placeholder_text = "Server port"
+
+[node name="AddrEdit" type="LineEdit" parent="CenterContainer/VBoxContainer"]
+layout_mode = 2
+placeholder_text = "Server address"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="HostButton" type="Button" parent="CenterContainer/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Host a Game"
+
+[node name="ConnectButton" type="Button" parent="CenterContainer/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Connect a Game"
diff --git a/Scripts/Multiplayer.cs b/Scripts/Multiplayer.cs
new file mode 100644
index 0000000..ee6a593
--- /dev/null
+++ b/Scripts/Multiplayer.cs
@@ -0,0 +1,71 @@
+using System;
+using Godot;
+
+namespace CodingLand.Scripts;
+
+public partial class Multiplayer : Node
+{
+ [Export] public Node2D Playground;
+
+ private void GameFreeze()
+ {
+ Playground.Hide();
+ GetTree().Paused = true;
+ }
+
+ private void GameUnfreeze()
+ {
+ Playground.Show();
+ Playground.GetNode("Camera2D").Enabled = true;
+ GetTree().Paused = false;
+ }
+
+ public override void _Ready()
+ {
+ GameFreeze();
+
+ if (DisplayServer.GetName() == "headless")
+ {
+ GD.Print("Starting server in headless mode...");
+ StartAsServer(4343);
+ }
+ }
+
+ public bool StartAsServer(int port)
+ {
+ var peer = new ENetMultiplayerPeer();
+ peer.CreateServer(port);
+ if (peer.GetConnectionStatus() == MultiplayerPeer.ConnectionStatus.Disconnected)
+ {
+ GD.PrintErr("Failed to start multiplayer server...");
+ OS.Alert("Failed to start multiplayer server...");
+ return false;
+ }
+
+ Multiplayer.MultiplayerPeer = peer;
+ GameUnfreeze();
+
+ return true;
+ }
+
+ public bool StartAsClient(string addr, int port)
+ {
+ if (string.IsNullOrEmpty(addr)) return false;
+
+ var peer = new ENetMultiplayerPeer();
+ peer.CreateClient(addr, port);
+ if (peer.GetConnectionStatus() == MultiplayerPeer.ConnectionStatus.Disconnected)
+ {
+ var info = $"Unable to connect multiplayer server {addr}:{port}";
+ GD.PrintErr(info);
+ OS.Alert(info);
+ return false;
+ }
+
+ Multiplayer.MultiplayerPeer = peer;
+ GameUnfreeze();
+
+ return true;
+ }
+
+}
diff --git a/Scripts/Player.cs b/Scripts/Player.cs
index 4d7b803..805db43 100644
--- a/Scripts/Player.cs
+++ b/Scripts/Player.cs
@@ -1,3 +1,4 @@
+using System;
using Godot;
namespace CodingLand.Scripts;
@@ -12,6 +13,9 @@ public partial class Player : CharacterBody2D
[Export] public float CameraSmoothness = 0.05f;
[Export] public Camera2D PlayerCamera;
+
+ [Export] public Timer PlayerDashCountdown;
+ [Export] public float PlayerDashAcceleration = 2f;
public override void _PhysicsProcess(double delta)
{
@@ -39,6 +43,12 @@ public partial class Player : CharacterBody2D
{
Velocity = Velocity.MoveToward(Vector2.Zero, Deceleration * (float)delta);
}
+
+ if (Input.IsActionJustPressed("move_dash") && PlayerDashCountdown.IsStopped())
+ {
+ Velocity *= PlayerDashAcceleration;
+ PlayerDashCountdown.Start();
+ }
Position += Velocity * (float)delta;
MoveAndSlide();
diff --git a/Scripts/UI/MultiplayerUi.cs b/Scripts/UI/MultiplayerUi.cs
new file mode 100644
index 0000000..f4d7003
--- /dev/null
+++ b/Scripts/UI/MultiplayerUi.cs
@@ -0,0 +1,74 @@
+using Godot;
+
+namespace CodingLand.Scripts.UI;
+
+public partial class MultiplayerUi : Control
+{
+ [Export] public int DefaultServerPort = 4343;
+ [Export] public string DefaultServerAddr = "127.0.0.1";
+
+ [Export] public Multiplayer MultiplayerController;
+
+ [Export] public LineEdit ServerPortInput;
+ [Export] public LineEdit ServerAddrInput;
+ [Export] public Button StartAsServerButton;
+ [Export] public Button StartAsClientButton;
+
+ private void ApplySize()
+ {
+ var screenSize = GetViewportRect().Size;
+
+ Position = new Vector2(0, 0);
+ Size = screenSize;
+ GetNode("CenterContainer").Size = screenSize;
+ }
+
+ private bool DoValidation()
+ {
+ var addr = ServerAddrInput.Text;
+ var port = ServerPortInput.Text;
+
+ if (string.IsNullOrEmpty(addr)) return false;
+ if (string.IsNullOrEmpty(port) || !int.TryParse(port, out _)) return false;
+
+ return true;
+ }
+
+ public override void _Ready()
+ {
+ ApplySize();
+
+ ServerPortInput.Text = DefaultServerPort.ToString();
+ ServerAddrInput.Text = DefaultServerAddr;
+
+ StartAsServerButton.Pressed += () =>
+ {
+ if (!DoValidation())
+ {
+ OS.Alert("Invalid multiplayer configuration.");
+ return;
+ }
+
+ var port = ServerPortInput.Text;
+ var result = MultiplayerController.StartAsServer(int.Parse(port));
+
+ if (result)
+ Hide();
+ };
+ StartAsServerButton.Pressed += () =>
+ {
+ if (!DoValidation())
+ {
+ OS.Alert("Invalid multiplayer configuration.");
+ return;
+ }
+
+ var addr = ServerAddrInput.Text;
+ var port = ServerPortInput.Text;
+ var result = MultiplayerController.StartAsClient(addr, int.Parse(port));
+
+ if (result)
+ Hide();
+ };
+ }
+}
diff --git a/project.godot b/project.godot
index 4fb437d..9980d8d 100644
--- a/project.godot
+++ b/project.godot
@@ -41,6 +41,11 @@ move_right={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
]
}
+move_dash={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"echo":false,"script":null)
+]
+}
[rendering]