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(); 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"] = (byte?)0; c.Metadata["2d"] = false; c.Metadata["speedrun"] = false; foreach (Client client in server.Clients.Where(client => client.Metadata.ContainsKey("lastGamePacket")).ToArray()) { try { c.Send((GamePacket) client.Metadata["lastGamePacket"]!, client).Wait(); } catch { // lol who gives a fuck } } }; async Task ClientSyncShineBag(Client client) { try { if ((bool?) client.Metadata["speedrun"] ?? false) return; 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; switch (gamePacket.Stage) { case "CapWorldHomeStage" when gamePacket.ScenarioNum == 0: c.Metadata["speedrun"] = true; ((ConcurrentBag) (c.Metadata["shineSync"] ??= new ConcurrentBag())).Clear(); shineBag.Clear(); c.Logger.Info("Entered Cap on scenario 0, enabling speedrun flag"); break; case "WaterfallWorldHomeStage": bool wasSpeedrun = (bool) c.Metadata["speedrun"]!; c.Metadata["speedrun"] = false; if (wasSpeedrun) Task.Run(async () => { c.Logger.Info("Entered Cascade with speedrun mode on"); await Task.Delay(15000); await ClientSyncShineBag(c); }); break; } server.BroadcastReplace(gamePacket, c, (from, to, gp) => { gp.ScenarioNum = (byte?) to.Metadata["scenario"] ?? 200; from.Logger.Warn($"to {to.Logger.Name}, {to.Metadata["scenario"]}-{gp.ScenarioNum}"); to.Send(gp, from); }); return false; } 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 moon {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 (Constants.MapNames.TryGetValue(stage.ToLower(), out string? mapName)) { stage = mapName; } if(!stage.Contains("Stage") && !stage.Contains("Zone")) { return "Invalid Stage Name!"; } if (!sbyte.TryParse(args[2], out sbyte scenario)) return $"Invalid scenario number {args[2]} (range: [-128 to 127])"; 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("sendall", args => { const string optionUsage = "Usage: sendall "; if (args.Length < 1) return optionUsage; string stage = args[0]; if (Constants.MapNames.TryGetValue(stage.ToLower(), out string? mapName)) { stage = mapName; } if(!stage.Contains("Stage") && !stage.Contains("Zone")) { return "Invalid Stage Name!"; } Client[] players = server.Clients.Where(c => c.Connected).ToArray(); Parallel.ForEachAsync(players, async (c, _) => { await c.Send(new ChangeStagePacket { Stage = stage, Id = "", Scenario = -1, SubScenarioType = 0 }); }).Wait(); return $"Sent players to {stage}:{-1}"; }); CommandHandler.RegisterCommand("scenario", args => { const string optionUsage = "Valid options: merge [true/false]"; 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; } case "merge" when args.Length == 1: { return $"Scenario merging is {Settings.Instance.Scenario.MergeEnabled}"; } default: return optionUsage; } }); CommandHandler.RegisterCommand("tag", args => { const string optionUsage = "Valid options:\n\ttime \n\tseeking \n\tstart