commit 3f235f6e04f1420546f1c76589d731e1dcd733b2 Author: Perry Date: Fri Dec 19 17:54:50 2025 +0100 Funkční komunikace mezi jedním clientem a serverem diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..46ae70e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "MonoGameLibrary"] + path = MonoGameLibrary + url = ./MonoGameLibrary diff --git a/.idea/.idea.FNAF_Clone/.idea/.gitignore b/.idea/.idea.FNAF_Clone/.idea/.gitignore new file mode 100644 index 0000000..e70267d --- /dev/null +++ b/.idea/.idea.FNAF_Clone/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/.idea.FNAF_Clone.iml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.FNAF_Clone/.idea/encodings.xml b/.idea/.idea.FNAF_Clone/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.FNAF_Clone/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.FNAF_Clone/.idea/indexLayout.xml b/.idea/.idea.FNAF_Clone/.idea/indexLayout.xml new file mode 100644 index 0000000..e52aba0 --- /dev/null +++ b/.idea/.idea.FNAF_Clone/.idea/indexLayout.xml @@ -0,0 +1,10 @@ + + + + + + FNAF_Server + + + + \ No newline at end of file diff --git a/.idea/.idea.FNAF_Clone/.idea/vcs.xml b/.idea/.idea.FNAF_Clone/.idea/vcs.xml new file mode 100644 index 0000000..6d0398c --- /dev/null +++ b/.idea/.idea.FNAF_Clone/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/FNAF_Clone.sln b/FNAF_Clone.sln new file mode 100644 index 0000000..3652f00 --- /dev/null +++ b/FNAF_Clone.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNAF_Clone", "FNAF_Clone\FNAF_Clone.csproj", "{069FFFDB-7D61-4AC5-9195-D3664F0B05A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNAF_Server", "FNAF_Server\FNAF_Server.csproj", "{5E19BD54-CFE0-453A-9CDB-BDB67A4ECB20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketLib", "PacketLib\PacketLib.csproj", "{EDC8B98F-D2F7-4BDB-8E3B-E3120A743023}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {069FFFDB-7D61-4AC5-9195-D3664F0B05A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {069FFFDB-7D61-4AC5-9195-D3664F0B05A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {069FFFDB-7D61-4AC5-9195-D3664F0B05A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {069FFFDB-7D61-4AC5-9195-D3664F0B05A0}.Release|Any CPU.Build.0 = Release|Any CPU + {5E19BD54-CFE0-453A-9CDB-BDB67A4ECB20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E19BD54-CFE0-453A-9CDB-BDB67A4ECB20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E19BD54-CFE0-453A-9CDB-BDB67A4ECB20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E19BD54-CFE0-453A-9CDB-BDB67A4ECB20}.Release|Any CPU.Build.0 = Release|Any CPU + {EDC8B98F-D2F7-4BDB-8E3B-E3120A743023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDC8B98F-D2F7-4BDB-8E3B-E3120A743023}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDC8B98F-D2F7-4BDB-8E3B-E3120A743023}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDC8B98F-D2F7-4BDB-8E3B-E3120A743023}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/FNAF_Clone.sln.DotSettings.user b/FNAF_Clone.sln.DotSettings.user new file mode 100644 index 0000000..eba5de7 --- /dev/null +++ b/FNAF_Clone.sln.DotSettings.user @@ -0,0 +1,4 @@ + + True + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/FNAF_Clone/.config/dotnet-tools.json b/FNAF_Clone/.config/dotnet-tools.json new file mode 100644 index 0000000..fbedee1 --- /dev/null +++ b/FNAF_Clone/.config/dotnet-tools.json @@ -0,0 +1,36 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-mgcb": { + "version": "3.8.4", + "commands": [ + "mgcb" + ] + }, + "dotnet-mgcb-editor": { + "version": "3.8.4", + "commands": [ + "mgcb-editor" + ] + }, + "dotnet-mgcb-editor-linux": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-linux" + ] + }, + "dotnet-mgcb-editor-windows": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-windows" + ] + }, + "dotnet-mgcb-editor-mac": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-mac" + ] + } + } +} \ No newline at end of file diff --git a/FNAF_Clone/.vscode/launch.json b/FNAF_Clone/.vscode/launch.json new file mode 100644 index 0000000..ae71f1a --- /dev/null +++ b/FNAF_Clone/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "C#: FNAF_Clone Debug", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/FNAF_Clone.csproj" + } + ], + } \ No newline at end of file diff --git a/FNAF_Clone/CameraSystem.cs b/FNAF_Clone/CameraSystem.cs new file mode 100644 index 0000000..c480601 --- /dev/null +++ b/FNAF_Clone/CameraSystem.cs @@ -0,0 +1,18 @@ +namespace FNAF_Clone; + +public class CameraSystem { + public bool Enabled { get; private set; } + public int CurrentCamera { get; private set; } + + public void FlipUp() { + Enabled = true; + } + + public void FlipDown() { + Enabled = false; + } + + public void SetCamera(int camera) { + + } +} \ No newline at end of file diff --git a/FNAF_Clone/Client.cs b/FNAF_Clone/Client.cs new file mode 100644 index 0000000..84e1796 --- /dev/null +++ b/FNAF_Clone/Client.cs @@ -0,0 +1,103 @@ +using System; +using System.Net; +using System.Net.Sockets; +using LiteNetLib; +using LiteNetLib.Utils; +using PacketLib; + +namespace FNAF_Clone; + +public class Client { + private static EventBasedNetListener listener = new(); + private static NetManager client; + private static NetPeer server; + + private static NetDataWriter writer; + private static NetPacketProcessor processor; + public static ClientPlayer Player { get; } = new(); + + + public static void Connect(string endPoint, int port) { + writer = new NetDataWriter(); + processor = new NetPacketProcessor(); + + NestedTypeManager.AutoRegister(processor); + + processor.SubscribeReusable(OnJoinAccept); + processor.SubscribeReusable(OnPlayerUpdate); + + + client = new NetManager(listener){ + AutoRecycle = true + }; + + client.Start(); + Console.WriteLine($"Connecting to server @ {endPoint}:{port}"); + + listener.NetworkReceiveEvent += (peer, reader, channel, method) => { + OnNetworkReceive(peer, reader, method); + }; + + listener.PeerConnectedEvent += peer => { + Console.WriteLine("Connected to Server"); + server = peer; + SendPacket(new JoinPacket {username = "Player1"}, DeliveryMethod.ReliableOrdered); + }; + + client.Connect(endPoint, port, ""); // TODO: figure out how keys work + } + + public static void SendPacket(T packet, DeliveryMethod deliveryMethod) where T : class, new() { + if (server != null) { + writer.Reset(); + processor.Write(writer, packet); + server.Send(writer, deliveryMethod); + } + } + public static void SendCommands(PlayerCommand[] pCommands) { + SendPacket(new PlayerCommandPacket{commands = pCommands}, DeliveryMethod.ReliableOrdered); + } + + public static void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { + processor.ReadAllPackets(reader, peer); + } + + public static void OnJoinAccept(JoinAcceptPacket packet) { + Console.WriteLine($"Accepted by server, pid: {packet.state.pid}"); + Player.state = packet.state; + } + + public static void Update() { + client.PollEvents(); + } + + public static void OnPlayerUpdate(UpdatePlayerPacket packet) { + //Player.state = Player.state.pid == 0 ? packet.stateP1 : packet.stateP2; + + foreach (var e in packet.events){ + switch (e.ID){ + case 0: // join + Console.WriteLine("E: Player joined"); + break; + case 1: // leave + Console.WriteLine("E: Player left"); + break; + case 2: // switch cam + if (Player.state.pid == e.Args[0]){ + Player.state.camera = e.Args[1]; + } + Console.WriteLine($"E: player {e.Args[0]} switched to camera {e.Args[1]}"); + break; + case 3: // toggle cam + Player.state.monitorUp = e.Args[1] == 1; + Console.WriteLine($"E: Player {e.Args[0]} toggled monitor {(e.Args[1] == 0 ? "off" : "on")}"); + break; + case -1: // movement + throw new NotImplementedException(); + + } + } + } + + +} \ No newline at end of file diff --git a/FNAF_Clone/ClientPlayer.cs b/FNAF_Clone/ClientPlayer.cs new file mode 100644 index 0000000..4f6efb4 --- /dev/null +++ b/FNAF_Clone/ClientPlayer.cs @@ -0,0 +1,8 @@ +using PacketLib; + +namespace FNAF_Clone; + +public class ClientPlayer { + public PlayerState state; + public string username; +} \ No newline at end of file diff --git a/FNAF_Clone/CommandManager.cs b/FNAF_Clone/CommandManager.cs new file mode 100644 index 0000000..d2321f7 --- /dev/null +++ b/FNAF_Clone/CommandManager.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Input; +using MonoGameLibrary.Input; +using PacketLib; + +namespace FNAF_Clone; + +public class CommandManager { + private static (string label, Keys key, Action action)[] keybinds = [ + ("Toggle Camera", Keys.S, ToggleCamera) + ]; + + private static InputListenerHook toggleCamHook = new(true); + + public static void InitInputListeners() { + Array.ForEach(keybinds, tuple => InputManager.AddListener(tuple.label, tuple.key, _ => tuple.action(), InputTiming.PRESS, toggleCamHook)); + } + + private static void ToggleCamera() { + Client.SendCommands([PlayerCommand.TOGGLE_MONITOR()]); + } +} \ No newline at end of file diff --git a/FNAF_Clone/Content/Content.mgcb b/FNAF_Clone/Content/Content.mgcb new file mode 100644 index 0000000..ddc4c36 --- /dev/null +++ b/FNAF_Clone/Content/Content.mgcb @@ -0,0 +1,15 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + diff --git a/FNAF_Clone/Content/font.spritefont b/FNAF_Clone/Content/font.spritefont new file mode 100755 index 0000000..bd33ecf --- /dev/null +++ b/FNAF_Clone/Content/font.spritefont @@ -0,0 +1,60 @@ + + + + + + + Arial + + + 12 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/FNAF_Clone/Content/whitrabt.ttf b/FNAF_Clone/Content/whitrabt.ttf new file mode 100644 index 0000000..38e1d76 Binary files /dev/null and b/FNAF_Clone/Content/whitrabt.ttf differ diff --git a/FNAF_Clone/FNAF_Clone.csproj b/FNAF_Clone/FNAF_Clone.csproj new file mode 100644 index 0000000..7c07fc9 --- /dev/null +++ b/FNAF_Clone/FNAF_Clone.csproj @@ -0,0 +1,50 @@ + + + WinExe + net9.0 + Major + false + false + + + app.manifest + Icon.ico + + + + + + + + + Icon.ico + + + Icon.bmp + + + + + + + + + + + + + + lib\MonoGameLibrary.dll + + + + + + + + + + + + + \ No newline at end of file diff --git a/FNAF_Clone/GameMain.cs b/FNAF_Clone/GameMain.cs new file mode 100644 index 0000000..95f2e8f --- /dev/null +++ b/FNAF_Clone/GameMain.cs @@ -0,0 +1,47 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using MonoGameLibrary; +using MonoGameLibrary.Input; + +namespace FNAF_Clone; + +public class GameMain() : Core("fnafkooo", 1920, 1080, false) { + private GraphicsDeviceManager _graphics; + private SpriteBatch _spriteBatch; + + protected override void Initialize() { + Client.Connect("127.0.0.1", 9012); + CommandManager.InitInputListeners(); + + base.Initialize(); + } + + protected override void LoadContent() { + _spriteBatch = new SpriteBatch(GraphicsDevice); + + + // TODO: use this.Content to load your game content here + } + + protected override void Update(GameTime gameTime) { + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || + Keyboard.GetState().IsKeyDown(Keys.Escape)) + Exit(); + + // TODO: Add your update logic here + + InputManager.NextInputCycle(); + + Client.Update(); + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) { + GraphicsDevice.Clear(Color.CornflowerBlue); + + // TODO: Add your drawing code here + + base.Draw(gameTime); + } +} \ No newline at end of file diff --git a/FNAF_Clone/Icon.bmp b/FNAF_Clone/Icon.bmp new file mode 100644 index 0000000..2b48165 Binary files /dev/null and b/FNAF_Clone/Icon.bmp differ diff --git a/FNAF_Clone/Icon.ico b/FNAF_Clone/Icon.ico new file mode 100644 index 0000000..7d9dec1 Binary files /dev/null and b/FNAF_Clone/Icon.ico differ diff --git a/FNAF_Clone/Input/InputListenerHook.cs b/FNAF_Clone/Input/InputListenerHook.cs new file mode 100644 index 0000000..552e75a --- /dev/null +++ b/FNAF_Clone/Input/InputListenerHook.cs @@ -0,0 +1,6 @@ +namespace FNAF_Clone.Input; + +public class InputListenerHook(bool enabled, bool oneTimeOnly = false) { + public bool Enabled { get; set; } = enabled; + public bool RemoveOnNextTrigger { get; set; } = oneTimeOnly; +} \ No newline at end of file diff --git a/FNAF_Clone/Input/InputManager.cs b/FNAF_Clone/Input/InputManager.cs new file mode 100644 index 0000000..95bd117 --- /dev/null +++ b/FNAF_Clone/Input/InputManager.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Input; + +namespace FNAF_Clone.Input; + + +public static class InputManager { + private static Dictionary> keyPressListeners = new(); + private static Dictionary> keyHoldListeners = new(); // The bool is for enabling/disabling that method + private static Dictionary> keyReleaseListeners = new(); + + private static KeyboardState oldState = Keyboard.GetState(); + private static KeyboardState newState; + + // public static ConcurrentQueue inputEventQueue = new(); + + public delegate void KeypressHandler(KeyboardState state); + + + + // public static void StartListening() { + // processInputThread.Start(); + // Console.WriteLine("Started listening for input"); + // } + + public static void NextInputCycle() { + Queue inputEventQueue = new(); + + // Read + + newState = Keyboard.GetState(); + Keys[] pressed = newState.GetPressedKeys(); + Keys[] pressedLastFrame = oldState.GetPressedKeys(); + + Array.ForEach(pressed, key => { + if (oldState.IsKeyUp(key)){ + if (keyPressListeners.TryGetValue(key, out var pressHandlers)){ + pressHandlers.ForEach(t => { + if (t.Item2.Enabled){ + inputEventQueue.Enqueue(t.Item1); + } + if (t.Item2.RemoveOnNextTrigger){ + pressHandlers.Remove(t); + } + }); + } + } + + if (keyHoldListeners.TryGetValue(key, out var holdHandlers)){ + holdHandlers.ForEach(t => { + if (t.Item2.Enabled){ + inputEventQueue.Enqueue(t.Item1); + } + if (t.Item2.RemoveOnNextTrigger){ + holdHandlers.Remove(t); + } + }); + } + }); + + Array.ForEach(pressedLastFrame, key => { + if (!keyReleaseListeners.ContainsKey(key)){ + return; + } + + if (newState.IsKeyUp(key)){ + if (keyReleaseListeners.TryGetValue(key, out var releaseHandlers)) + releaseHandlers.ForEach(t => { + if (t.Item2.Enabled){ + inputEventQueue.Enqueue(t.Item1); + } + if (t.Item2.RemoveOnNextTrigger){ + releaseHandlers.Remove(t); + } + }); + } + }); + oldState = newState; + + // Execute + + foreach (KeypressHandler handler in inputEventQueue){ + handler(newState); + } +} + + // private static Thread processInputThread = new(() => { + // + // }); + + public static void AddListener(Keys key, KeypressHandler action, InputTiming timing, InputListenerHook hook) { + Dictionary> workingDict; + + switch (timing){ + case InputTiming.PRESS: + workingDict = keyPressListeners; + break; + case InputTiming.HOLD: + workingDict = keyHoldListeners; + break; + case InputTiming.RELEASE: + workingDict = keyReleaseListeners; + break; + default: + throw new ArgumentOutOfRangeException(nameof(timing), timing, null); + } + + if (!workingDict.ContainsKey(key)){ + workingDict.Add(key, new()); + } + + workingDict[key].Add((action, hook)); + } +} \ No newline at end of file diff --git a/FNAF_Clone/Input/InputTiming.cs b/FNAF_Clone/Input/InputTiming.cs new file mode 100644 index 0000000..413a161 --- /dev/null +++ b/FNAF_Clone/Input/InputTiming.cs @@ -0,0 +1,7 @@ +namespace FNAF_Clone.Input; + +public enum InputTiming { + PRESS, + HOLD, + RELEASE +} \ No newline at end of file diff --git a/FNAF_Clone/Program.cs b/FNAF_Clone/Program.cs new file mode 100644 index 0000000..99c5548 --- /dev/null +++ b/FNAF_Clone/Program.cs @@ -0,0 +1,2 @@ +using var game = new FNAF_Clone.GameMain(); +game.Run(); \ No newline at end of file diff --git a/FNAF_Clone/app.manifest b/FNAF_Clone/app.manifest new file mode 100644 index 0000000..2d72dbb --- /dev/null +++ b/FNAF_Clone/app.manifest @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + diff --git a/FNAF_Clone/lib/MonoGameLibrary.dll b/FNAF_Clone/lib/MonoGameLibrary.dll new file mode 120000 index 0000000..1929591 --- /dev/null +++ b/FNAF_Clone/lib/MonoGameLibrary.dll @@ -0,0 +1 @@ +../../MonoGameLibrary/MonoGameLibrary/bin/Debug/net8.0/MonoGameLibrary.dll \ No newline at end of file diff --git a/FNAF_Server/FNAF_Server.csproj b/FNAF_Server/FNAF_Server.csproj new file mode 100644 index 0000000..d13b3e5 --- /dev/null +++ b/FNAF_Server/FNAF_Server.csproj @@ -0,0 +1,18 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + diff --git a/FNAF_Server/GameLogic.cs b/FNAF_Server/GameLogic.cs new file mode 100644 index 0000000..e8320a8 --- /dev/null +++ b/FNAF_Server/GameLogic.cs @@ -0,0 +1,12 @@ +using PacketLib; + +namespace FNAF_Server; + +public class GameLogic { + + public static void Update() { + + } + + +} \ No newline at end of file diff --git a/FNAF_Server/Program.cs b/FNAF_Server/Program.cs new file mode 100644 index 0000000..1a0d1d6 --- /dev/null +++ b/FNAF_Server/Program.cs @@ -0,0 +1,9 @@ +using System.Security.Cryptography.X509Certificates; + +namespace FNAF_Server; + +public class Program { + public static void Main(string[] args) { + Server.Start(9012); + } +} \ No newline at end of file diff --git a/FNAF_Server/Server.cs b/FNAF_Server/Server.cs new file mode 100644 index 0000000..acccff3 --- /dev/null +++ b/FNAF_Server/Server.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Threading; +using LiteNetLib; +using LiteNetLib.Utils; +using PacketLib; + +namespace FNAF_Server; + +public class Server { + public static ServerPlayer P1; + public static ServerPlayer P2; + + private static EventBasedNetListener listener; + private static NetManager server; + + private const int TargetTickRate = 30; + private const double TargetDeltaTime = 1.0 / TargetTickRate; + private const long TargetTickTimeMs = 1000 / TargetTickRate; + + private static NetDataWriter writer; + private static NetPacketProcessor processor; + private static Dictionary players = new(); + + private static bool isRunning; + + public static void Start(int port) { + writer = new NetDataWriter(); + processor = new NetPacketProcessor(); + + NestedTypeManager.AutoRegister(processor); + + processor.SubscribeReusable(OnJoinReceived); + processor.SubscribeReusable(OnCommandReceived); + + listener = new EventBasedNetListener(); // ← Initialize the listener! + + + server = new NetManager(listener){ + AutoRecycle = true + }; + + Console.WriteLine($"Starting server on {port}"); + + listener.ConnectionRequestEvent += request => { + Console.WriteLine($"Connection Request from {request.RemoteEndPoint}"); + if (players.Count >= 2){ + Console.WriteLine($"{request.RemoteEndPoint} denied, server full"); + return; + } + request.Accept(); + }; + + listener.NetworkReceiveEvent += (peer, reader, channel, method) => { + OnNetworkReceive(peer, reader, method); + }; + + listener.PeerDisconnectedEvent += (peer, info) => { + OnPeerDisconnected(peer, info); + }; + + server.Start(port); + + Run(); + } + + public static void SendPacket(T packet, NetPeer peer, DeliveryMethod deliveryMethod = DeliveryMethod.ReliableOrdered) where T : class, new() { + if (peer != null) { + writer.Reset(); + processor.Write(writer, packet); + peer.Send(writer, deliveryMethod); + } + } + public static void SendPacket(T packet, uint pid, DeliveryMethod deliveryMethod = DeliveryMethod.ReliableOrdered) where T : class, new() { + SendPacket(packet, players[pid].peer, deliveryMethod); + } + public static void SendPacketToAll(T packet, DeliveryMethod deliveryMethod = DeliveryMethod.ReliableOrdered) where T : class, new() { + foreach (var player in players.Values){ + SendPacket(packet, player.peer, deliveryMethod); + } + } + + public static void SendUpdate(GameEvent[] gevents, uint pid) { + // SendPacket(new UpdatePlayerPacket{eventQueue = new EventQueue{Events = events}}, pid); + SendPacket(new UpdatePlayerPacket{events = gevents}, pid); + + } + + public static void SendUpdateToAll(GameEvent[] gevents) { + // SendPacketToAll(new UpdatePlayerPacket{eventQueue = new EventQueue{Events = events}}); + SendPacketToAll(new UpdatePlayerPacket{events = gevents}); + + } + + + public static void OnJoinReceived(JoinPacket packet, NetPeer peer) { + Console.WriteLine($"Received join from {packet.username} (pid: {(uint)peer.Id})"); + + ServerPlayer newPlayer = (players[(uint)peer.Id] = new ServerPlayer { + peer = peer, + state = new PlayerState { + pid = (uint)peer.Id, + camera = 0 + }, + username = packet.username + }); + + SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer); + + if (players.Count == 1){ + P1 = newPlayer; + } + else{ + P2 = newPlayer; + } + } + + public static void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { + processor.ReadAllPackets(reader, peer); + } + + public static void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { + if (peer.Tag != null) { + players.Remove((uint)peer.Id); + } + } + + public static void OnCommandReceived(PlayerCommandPacket packet, NetPeer peer) { + PlayerCommand[] commands = packet.commands; + + foreach (var playerCommand in commands){ + switch (playerCommand.ID){ + case 0: + Console.WriteLine($"C: Player {peer.Id} switched to camera {playerCommand.Args[0]}"); + SendUpdateToAll([GameEvent.SWITCH_CAM(peer.Id, playerCommand.Args[0])]); + break; + case 1: + bool newState = !players[(uint)peer.Id].state.monitorUp; + players[(uint)peer.Id].state.monitorUp = newState; + Console.WriteLine($"C: Player {peer.Id} toggled camera {(newState ? "on" : "off")}"); + SendUpdateToAll([GameEvent.TOGGLE_MONITOR(peer.Id, newState)]); + break; + } + } + } + + public static void Update() { + server.PollEvents(); + // Console.WriteLine("update"); + GameLogic.Update(); // TODO: add a parameter for player input + } + + public static void Run(CancellationToken cancellationToken = default) + { + isRunning = true; + var stopwatch = Stopwatch.StartNew(); + long previousTicks = 0; + + while (isRunning && !cancellationToken.IsCancellationRequested) + { + long currentTicks = stopwatch.ElapsedMilliseconds; + long elapsed = currentTicks - previousTicks; + + if (elapsed >= TargetTickTimeMs) + { + Update(); + + previousTicks = currentTicks; + if (elapsed > TargetTickTimeMs * 2) + { + Console.WriteLine($"Warning: Tick took {elapsed}ms (target: {TargetTickTimeMs}ms)"); + } + } + else + { + + int sleepTime = (int)(TargetTickTimeMs - elapsed - 2); + if (sleepTime > 0) + { + Thread.Sleep(sleepTime); + } + + while (stopwatch.ElapsedMilliseconds - previousTicks < TargetTickTimeMs){ + Thread.SpinWait(1); + } + } + } + } + + public static void Stop() + { + isRunning = false; + } +} + + diff --git a/FNAF_Server/ServerPlayer.cs b/FNAF_Server/ServerPlayer.cs new file mode 100644 index 0000000..2afafdd --- /dev/null +++ b/FNAF_Server/ServerPlayer.cs @@ -0,0 +1,10 @@ +using LiteNetLib; +using PacketLib; + +namespace FNAF_Server; + +public class ServerPlayer { + public NetPeer peer; + public PlayerState state; + public string username; +} diff --git a/MonoGameLibrary b/MonoGameLibrary new file mode 160000 index 0000000..a2b524e --- /dev/null +++ b/MonoGameLibrary @@ -0,0 +1 @@ +Subproject commit a2b524ee04772d483750047c997172cc62090a9a diff --git a/PacketLib/EventQueue.cs b/PacketLib/EventQueue.cs new file mode 100644 index 0000000..dea8a72 --- /dev/null +++ b/PacketLib/EventQueue.cs @@ -0,0 +1,17 @@ +using LiteNetLib.Utils; + +namespace PacketLib; + +public struct EventQueue : INetSerializable { + public GameEvent[] Events{ get; set; } + + public void Serialize(NetDataWriter writer) { + writer.PutArray(Events); + } + + public void Deserialize(NetDataReader reader) { + Events = reader.GetArray(); + } + + // public GameEvent this[int index] => Events[index]; +} \ No newline at end of file diff --git a/PacketLib/GameEvent.cs b/PacketLib/GameEvent.cs new file mode 100644 index 0000000..615894d --- /dev/null +++ b/PacketLib/GameEvent.cs @@ -0,0 +1,29 @@ +using System.Diagnostics.CodeAnalysis; +using LiteNetLib.Utils; + +namespace PacketLib; + +#nullable disable +public struct GameEvent : INetSerializable{ + public static GameEvent PLAYER_JOIN(int pid) => new(){ID = 0, Args = [pid] }; + public static GameEvent PLAYER_LEAVE(int pid) => new(){ID = 1, Args = [pid] }; + public static GameEvent SWITCH_CAM(int pid, int camId) => new(){ID = 2, Args = [pid, camId] }; + public static GameEvent TOGGLE_MONITOR(int pid, bool state) => new(){ID = 3, Args = [pid, state ? 1 : 0]}; + + public static GameEvent ENEMY_MOVEMENT(int enemyId, int camId) => new(){ID = -1, Args = [enemyId, camId]}; + + public int ID{ get; set; } + public bool Hideable => ID < 0; + public int[] Args{ get; private set; } + + public void Serialize(NetDataWriter writer) { + writer.Put(ID); + writer.PutArray(Args); + } + + public void Deserialize(NetDataReader reader) { + ID = reader.GetInt(); + Args = reader.GetIntArray(); + } + +} \ No newline at end of file diff --git a/PacketLib/JoinAcceptPacket.cs b/PacketLib/JoinAcceptPacket.cs new file mode 100644 index 0000000..178249a --- /dev/null +++ b/PacketLib/JoinAcceptPacket.cs @@ -0,0 +1,5 @@ +namespace PacketLib; + +public class JoinAcceptPacket { + public PlayerState state { get; set; } +} \ No newline at end of file diff --git a/PacketLib/JoinPacket.cs b/PacketLib/JoinPacket.cs new file mode 100644 index 0000000..c009883 --- /dev/null +++ b/PacketLib/JoinPacket.cs @@ -0,0 +1,5 @@ +namespace PacketLib; + +public class JoinPacket { + public string username { get; set; } +} \ No newline at end of file diff --git a/PacketLib/NestedTypeManager.cs b/PacketLib/NestedTypeManager.cs new file mode 100644 index 0000000..c479d8c --- /dev/null +++ b/PacketLib/NestedTypeManager.cs @@ -0,0 +1,12 @@ +using LiteNetLib.Utils; + +namespace PacketLib; + +public static class NestedTypeManager { + public static void AutoRegister(NetPacketProcessor processor) { + processor.RegisterNestedType(); + processor.RegisterNestedType(); + processor.RegisterNestedType(); + processor.RegisterNestedType(); + } +} \ No newline at end of file diff --git a/PacketLib/NetDataWriterExtensions.cs b/PacketLib/NetDataWriterExtensions.cs new file mode 100644 index 0000000..4f74393 --- /dev/null +++ b/PacketLib/NetDataWriterExtensions.cs @@ -0,0 +1,12 @@ +using System.Xml; +using LiteNetLib.Utils; + +namespace PacketLib; + +public static class NetDataWriterExtensions { + public static GameEvent GetGameEvent(this NetDataReader reader) { + GameEvent gevent = new(); + gevent.Deserialize(reader); + return gevent; + } +} \ No newline at end of file diff --git a/PacketLib/PacketLib.csproj b/PacketLib/PacketLib.csproj new file mode 100644 index 0000000..06e6adc --- /dev/null +++ b/PacketLib/PacketLib.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/PacketLib/PlayerCommand.cs b/PacketLib/PlayerCommand.cs new file mode 100644 index 0000000..a800f4f --- /dev/null +++ b/PacketLib/PlayerCommand.cs @@ -0,0 +1,23 @@ +using LiteNetLib.Utils; + +namespace PacketLib; + +public struct PlayerCommand : INetSerializable { + public static PlayerCommand SWITCH_CAM(int camId) => new(){ID = 0, Args = [camId] }; + public static PlayerCommand TOGGLE_MONITOR() => new(){ID = 1, Args = []}; + + public int ID{ get; set; } + public bool Hideable => ID < 0; + public int[] Args{ get; private set; } + + public void Serialize(NetDataWriter writer) { + writer.Put(ID); + writer.PutArray(Args); + } + + public void Deserialize(NetDataReader reader) { + ID = reader.GetInt(); + Args = reader.GetIntArray(); + } + +} \ No newline at end of file diff --git a/PacketLib/PlayerCommandPacket.cs b/PacketLib/PlayerCommandPacket.cs new file mode 100644 index 0000000..8e13f3d --- /dev/null +++ b/PacketLib/PlayerCommandPacket.cs @@ -0,0 +1,5 @@ +namespace PacketLib; + +public class PlayerCommandPacket { + public PlayerCommand[] commands { get; set; } +} \ No newline at end of file diff --git a/PacketLib/PlayerState.cs b/PacketLib/PlayerState.cs new file mode 100644 index 0000000..f8485d8 --- /dev/null +++ b/PacketLib/PlayerState.cs @@ -0,0 +1,29 @@ +using LiteNetLib; +using LiteNetLib.Utils; + +namespace PacketLib; + +public struct PlayerState : INetSerializable { + public uint pid; + public int camera; + public bool monitorUp; + + public void Serialize(NetDataWriter writer) { + writer.Put(pid); + writer.Put(camera); + writer.Put(monitorUp); + } + + public void Deserialize(NetDataReader reader) { + pid = reader.GetUInt(); + camera = reader.GetInt(); + monitorUp = reader.GetBool(); + } +} + + + + + + + diff --git a/PacketLib/UpdatePlayerPacket.cs b/PacketLib/UpdatePlayerPacket.cs new file mode 100644 index 0000000..1e0d663 --- /dev/null +++ b/PacketLib/UpdatePlayerPacket.cs @@ -0,0 +1,11 @@ +namespace PacketLib; + +public class UpdatePlayerPacket { + // public PlayerState stateP1{ get; set; } + // public PlayerState stateP2{ get; set; } + + // TODO: implement anti-desync measures by comparing server and client states + + public GameEvent[] events { get; set; } + // public EventQueue eventQueue { get; set; } +} \ No newline at end of file