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; public static readonly Dictionary Players = new(); 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 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, int 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, int 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[peer.Id] = new ServerPlayer { peer = peer, state = new PlayerState { pid = peer.Id, camera = 0, doorStates = [false, false, false] }, username = packet.username }); SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer); if (Players.Count == 1){ P1 = newPlayer; } else{ P2 = newPlayer; } GameLogic.Init(); // TODO: implement a way to start the game once both players join } 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(peer.Id); } } public static void OnCommandReceived(PlayerCommandPacket packet, NetPeer peer) { CommandProcessor.Evaluate(packet.commands, peer.Id); // 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; } }