using System.Collections.Concurrent; using System.Net; using System.Numerics; using System.Text; 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"); DiscordBot bot = new DiscordBot(); await bot.Run(); server.ClientJoined += (c, _) => { if (Settings.Instance.BanList.Enabled && (Settings.Instance.BanList.Players.Contains(c.Id) || Settings.Instance.BanList.IpAddresses.Contains( ((IPEndPoint) c.Socket!.RemoteEndPoint!).Address.ToString()))) throw new Exception($"Banned player attempted join: {c.Name}"); 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.ClientsConnected) { 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) => 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 new save, preventing moon sync until Cascade"); break; case "WaterfallWorldHomeStage": bool wasSpeedrun = (bool) c.Metadata["speedrun"]!; c.Metadata["speedrun"] = false; if (wasSpeedrun) Task.Run(async () => { c.Logger.Info("Entered Cascade with moon sync disabled, enabling moon sync"); await Task.Delay(15000); await ClientSyncShineBag(c); }); break; } if (Settings.Instance.Scenario.MergeEnabled) { server.BroadcastReplace(gamePacket, c, (from, to, gp) => { gp.ScenarioNum = (byte?) to.Metadata["scenario"] ?? 200; to.Send(gp, from); }); return false; } 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 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("rejoin", args => { bool moreThanOne = false; StringBuilder builder = new StringBuilder(); Client[] clients = (args.Length == 1 && args[0] == "*" ? server.Clients.Where(c => c.Connected && args.Any(x => c.Name.StartsWith(x) || (Guid.TryParse(x, out Guid result) && result == c.Id))) : server.Clients.Where(c => c.Connected)).ToArray(); foreach (Client user in clients) { if (moreThanOne) builder.Append(", "); builder.Append(user.Name); user.Dispose(); moreThanOne = true; } return clients.Length > 0 ? $"Caused {builder} to rejoin" : "Usage: rejoin "; }); CommandHandler.RegisterCommand("crash", args => { bool moreThanOne = false; StringBuilder builder = new StringBuilder(); Client[] clients = (args.Length == 1 && args[0] == "*" ? server.Clients.Where(c => c.Connected && args.Any(x => c.Name.StartsWith(x) || (Guid.TryParse(x, out Guid result) && result == c.Id))) : server.Clients.Where(c => c.Connected)).ToArray(); foreach (Client user in clients) { if (moreThanOne) builder.Append(", "); moreThanOne = true; builder.Append(user.Name); Task.Run(async () => { await user.Send(new ChangeStagePacket { Id = "$among$us/SubArea", Stage = "$agogusStage", Scenario = 21, SubScenarioType = 69 // invalid id }); user.Dispose(); }); } return clients.Length > 0 ? $"Crashed {builder}" : "Usage: crash "; }); CommandHandler.RegisterCommand("ban", args => { bool moreThanOne = false; StringBuilder builder = new StringBuilder(); Client[] clients = (args.Length == 1 && args[0] == "*" ? server.Clients.Where(c => c.Connected && args.Any(x => c.Name.StartsWith(x) || (Guid.TryParse(x, out Guid result) && result == c.Id))) : server.Clients.Where(c => c.Connected)).ToArray(); foreach (Client user in clients) { if (moreThanOne) builder.Append(", "); moreThanOne = true; builder.Append(user.Name); Task.Run(async () => { await user.Send(new ChangeStagePacket { Id = "$agogus/banned4lyfe", Stage = "$ejected", Scenario = 69, SubScenarioType = 21 // invalid id }); IPEndPoint? endpoint = (IPEndPoint?) user.Socket?.RemoteEndPoint; Settings.Instance.BanList.Players.Add(user.Id); if (endpoint != null) Settings.Instance.BanList.IpAddresses.Add(endpoint.ToString()); user.Dispose(); }); } if (clients.Length > 0) { Settings.SaveSettings(); return $"Banned {builder}."; } return "Usage: ban "; }); 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! ```cap -> Cap Kingdom\ncascade -> Cascade Kingdom\nsand -> Sand Kingdom\nlake -> Lake Kingdom\nwooded -> Wooded Kingdom\ncloud -> Cloud Kingdom\nlost -> Lost Kingdom\nmetro -> Metro Kingdom\nsea -> Sea Kingdom\nsnow -> Snow Kingdom\nlunch -> Luncheon Kingdom\nruined -> Ruined Kingdom\nbowser -> Bowser's Kingdom\nmoon -> Moon Kingdom\nmush -> Mushroom Kingdom\ndark -> Dark Side\ndarker -> Darker Side```"; } if (!sbyte.TryParse(args[2], out sbyte scenario) || scenario < -1) return $"Invalid scenario number {args[2]} (range: [-1 to 127])"; Client[] players = args[3] == "*" ? server.Clients.Where(c => c.Connected).ToArray() : server.Clients.Where(c => c.Connected && args[3..].Any(x => c.Name.StartsWith(x) || (Guid.TryParse(x, out Guid result) && result == c.Id))) .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! ```cap -> Cap Kingdom\ncascade -> Cascade Kingdom\nsand -> Sand Kingdom\nlake -> Lake Kingdom\nwooded -> Wooded Kingdom\ncloud -> Cloud Kingdom\nlost -> Lost Kingdom\nmetro -> Metro Kingdom\nsea -> Sea Kingdom\nsnow -> Snow Kingdom\nlunch -> Luncheon Kingdom\nruined -> Ruined Kingdom\nbowser -> Bowser's Kingdom\nmoon -> Moon Kingdom\nmush -> Mushroom Kingdom\ndark -> Dark Side\ndarker -> Darker Side```"; } 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