using System.Collections.Concurrent; using System.Numerics; using Server; using Shared; using Shared.Packet.Packets; using Timer = System.Timers.Timer; Server.Server server = new Server.Server(); HashSet shineBag = new HashSet(); // int shineTx = 0; // used for logging CancellationTokenSource cts = new CancellationTokenSource(); Task listenTask = server.Listen(cts.Token); Logger consoleLogger = new Logger("Console"); server.ClientJoined += (c, _) => { c.Metadata["shineSync"] = new ConcurrentBag(); c.Metadata["loadedSave"] = false; c.Metadata["scenario"] = 0; c.Metadata["2d"] = false; foreach (Client client in server.Clients.Where(client => client.Metadata.ContainsKey("lastGamePacket")).ToArray()) { try { Task.WaitAll(c.Send((GamePacket) client.Metadata["lastGamePacket"]!, client)); } catch { // lol who gives a fuck } } c.PacketTransformer += (_, packet) => { if (Settings.Instance.Scenario.MergeEnabled && packet is GamePacket gamePacket) { gamePacket.ScenarioNum = (byte?) c.Metadata["scenario"] ?? 0; return gamePacket; } return packet; }; }; async Task ClientSyncShineBag(Client client) { try { ConcurrentBag clientBag = (ConcurrentBag) (client.Metadata["shineSync"] ??= new ConcurrentBag()); foreach (int shine in shineBag.Except(clientBag).ToArray()) { clientBag.Add(shine); await client.Send(new ShinePacket { ShineId = shine }); } } catch { // errors that can happen when sending will crash the server :) } } async void SyncShineBag() { try { await Parallel.ForEachAsync(server.Clients.ToArray(), async (client, _) => await ClientSyncShineBag(client)); } catch { // errors that can happen shines change will crash the server :) } } Timer timer = new Timer(120000); timer.AutoReset = true; timer.Enabled = true; timer.Elapsed += (_, _) => { SyncShineBag(); }; timer.Start(); float MarioSize(bool is2d) { return is2d ? 180 : 160; } server.PacketHandler = (c, p) => { switch (p) { case GamePacket gamePacket: { c.Logger.Info($"Got game packet {gamePacket.Stage}->{gamePacket.ScenarioNum}"); c.Metadata["scenario"] = gamePacket.ScenarioNum; c.Metadata["2d"] = gamePacket.Is2d; c.Metadata["lastGamePacket"] = gamePacket; break; } case TagPacket tagPacket: { if ((tagPacket.UpdateType & TagPacket.TagUpdate.State) != 0) c.Metadata["seeking"] = tagPacket.IsIt; if ((tagPacket.UpdateType & TagPacket.TagUpdate.Time) != 0) c.Metadata["time"] = new Time(tagPacket.Minutes, tagPacket.Seconds, DateTime.Now); break; } case CostumePacket: ClientSyncShineBag(c); c.Metadata["loadedSave"] = true; break; case ShinePacket shinePacket: { if (c.Metadata["loadedSave"] is false) break; ConcurrentBag playerBag = (ConcurrentBag) c.Metadata["shineSync"]; shineBag.Add(shinePacket.ShineId); if (playerBag.Contains(shinePacket.ShineId)) break; c.Logger.Info($"Got shine {shinePacket.ShineId}"); playerBag.Add(shinePacket.ShineId); SyncShineBag(); break; } case PlayerPacket playerPacket when Settings.Instance.Flip.Enabled && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Others && Settings.Instance.Flip.Players.Contains(c.Id): { playerPacket.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]); playerPacket.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)); server.Broadcast(playerPacket, c); return false; } case PlayerPacket playerPacket when Settings.Instance.Flip.Enabled && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Self && !Settings.Instance.Flip.Players.Contains(c.Id): { server.BroadcastReplace(playerPacket, c, (from, to, sp) => { if (Settings.Instance.Flip.Players.Contains(to.Id)) { sp.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]); sp.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)); } to.Send(sp, from); }); return false; } } return true; }; CommandHandler.RegisterCommand("send", args => { const string optionUsage = "Usage: send "; if (args.Length < 4) return optionUsage; string stage = args[0]; string id = args[1]; if (!byte.TryParse(args[2], out byte scenario)) return $"Invalid scenario number {args[2]} (range: [0-255])"; Client[] players = args[3] == "*" ? server.Clients.Where(c => c.Connected).ToArray() : server.Clients.Where(c => c.Connected && args[3..].Contains(c.Name)).ToArray(); Parallel.ForEachAsync(players, async (c,_) => { await c.Send(new ChangeStagePacket { Stage = stage, Id = id, Scenario = scenario, SubScenarioType = 0 }); }).Wait(); return $"Sent players to {stage}:{scenario}"; }); CommandHandler.RegisterCommand("scenario", args => { const string optionUsage = "Valid options: merge "; if (args.Length < 1) return optionUsage; switch (args[0]) { case "merge" when args.Length == 2: { if (bool.TryParse(args[1], out bool result)) { Settings.Instance.Scenario.MergeEnabled = result; Settings.SaveSettings(); return result ? "Enabled scenario merge" : "Disabled scenario merge"; } return optionUsage; } default: return optionUsage; } }); CommandHandler.RegisterCommand("tag", args => { const string optionUsage = "Valid options:\n\ttime \n\tseeking \n\tstart