public async Task Games(string type = null, [Remainder] string restOfLine = null) { var channel = Context.Channel as IMessageChannel; /*if (string.IsNullOrWhiteSpace(type) || !_service.IsValidGameType(type)) * { * var embed = new EmbedBuilder().WithOkColor() * .WithTitle("Games List") * .WithDescription($"<:game_icon_battlezone98redux:342134901975547916> `{Prefix}games bz98` | `{Prefix}games bz98r` | `{Prefix}games bzr`\n" + * $"<:game_icon_battlezone2:342134902587785219> `{Prefix}games bz2`"); * await channel.EmbedAsync(embed).ConfigureAwait(false); * return; * }*/ if (type == null || !_service.IsValidGameType(type)) { var embed = new EmbedBuilder().WithErrorColor() .WithTitle(GetText("gameslist")) .WithDescription(GetText("unknowngame", Prefix)); await channel.EmbedAsync(embed).ConfigureAwait(false); return; } using (channel.EnterTypingState()) { DataGameList list = await _service.GetGames(type); EmbedBuilder embed = new EmbedBuilder() .WithColor(new Color(255, 255, 255)) .WithTitle($"{list.GameTitle} {GetText("gamelist")}") .WithDescription($"{list.Header.Description}\n`{list.Games?.Length ?? 0} Game(s)`"); if (!string.IsNullOrWhiteSpace(list.Header.Image)) { embed.WithThumbnailUrl(list.Header.Image); } if (!string.IsNullOrWhiteSpace(list.Header.Credit)) { embed.WithFooter(efb => efb.WithText(list.Header.Credit)); } foreach (DataGameListServerStatus status in list.Header.ServerStatus) { string StatusText = string.Empty; switch (status.Status) { case EDataGameListServerStatus.Online: StatusText += "✅ Online"; break; case EDataGameListServerStatus.Offline: StatusText += "❌ Offline"; break; case EDataGameListServerStatus.NoGames: StatusText += "⚠ No Games"; break; case EDataGameListServerStatus.Unknown: StatusText += "❓ Unknown"; break; } if (status.Updated.HasValue) { StatusText += $"\nUpdated {GamesListService.TimeAgoUtc(status.Updated.Value)}"; } if (string.IsNullOrWhiteSpace(StatusText)) { StatusText = "-"; } embed.AddField(efb => efb.WithName(status.Name).WithValue(StatusText).WithIsInline(true)); } await channel.EmbedAsync(embed).ConfigureAwait(false); //LastGameList = list.Games; if (!string.IsNullOrWhiteSpace(restOfLine) && restOfLine.Trim().ToLowerInvariant() == "all") { for (int x = 0; x < (list.Games?.Length ?? 0); x++) { await channel.EmbedAsync(GetGameEmbed(list.Games, x)).ConfigureAwait(false); } return; //return Task.CompletedTask; } int page = 0; int.TryParse(restOfLine?.Trim(), out page); page--; if (page < 0) { page = 0; } if (page >= (list.Games?.Length ?? 0)) { page = list.Games?.Length ?? 0; } // this appears to pre-generate the pages because I don't have to cache this if ((list.Games?.Length ?? 0) > 0) { await Context.SendPaginatedConfirmAsync(page, /*async*/ (curPage) => { return(GetGameEmbed(list.Games, curPage)); }, list.Games?.Length ?? 0, 1, addPaginatedFooter : false); } } //Format.Sanitize }
public async Task <DataGameList> GetGamesNew() { using (var http = new HttpClient()) { var res = await http.GetStringAsync(queryUrl).ConfigureAwait(false); var gamelist = JsonConvert.DeserializeObject <BZCCRaknetData>(res); //gamelist.SetBz2Service(this); //return gamelist; DataGameList data = new DataGameList() { GameTitle = this.Title, Header = new DataGameListHeader() { Description = "List of games currently on Battlezone CC matchmaking servers", Image = "http://discord.battlezone.report/resources/logos/bzcc.png", Credit = "Brought to you by Nielk1's Raknet Bot" } }; { bool isOnIonDriver = gamelist.GET.Any(game => game.IsOnIonDriver()); DataGameListServerStatus IonDriverStatus = new DataGameListServerStatus() { Name = "IonDriver", Updated = null }; //if (isIonDriver) //{ // IonDriverStatus.Status = EDataGameListServerStatus.Online; //} //else if (isIonDriver) //{ // IonDriverStatus.Status = EDataGameListServerStatus.NoMarker; //} //else //{ // IonDriverStatus.Status = EDataGameListServerStatus.Unknown; //} IonDriverStatus.Status = EDataGameListServerStatus.NotSet; bool isOnRebellion = gamelist.GET.Any(game => game.IsOnRebellion()); bool haveRebellionStatus = gamelist.proxyStatus.ContainsKey("Rebellion"); bool isRebellionUp = haveRebellionStatus && gamelist.proxyStatus["Rebellion"].success == true; string statusRebellion = haveRebellionStatus ? gamelist.proxyStatus["Rebellion"].status : null; DateTime?dateRebellion = haveRebellionStatus ? gamelist.proxyStatus["Rebellion"].updated : null; DataGameListServerStatus RebellionStatus = new DataGameListServerStatus() { Name = "Rebellion (Primary)", Updated = dateRebellion }; if (isRebellionUp) { RebellionStatus.Status = EDataGameListServerStatus.Online; } else if (!isRebellionUp) { RebellionStatus.Status = EDataGameListServerStatus.Offline; } else { RebellionStatus.Status = EDataGameListServerStatus.Unknown; } data.Header.ServerStatus = new DataGameListServerStatus[] { IonDriverStatus, RebellionStatus }; } data.Games = (await Task.WhenAll( gamelist.GET .Select(async raw => { DataGameListGame game = new DataGameListGame(); game.Name = raw.Name; game.Image = "http://discord.battlezone.report/resources/logos/nomap.png"; game.CurPlayers = raw.CurPlayers; game.MaxPlayers = raw.MaxPlayers; if (raw.Locked) { game.Status = EDataGameListServerGameStatus.Locked; } else if (raw.Passworded) { game.Status = EDataGameListServerGameStatus.Passworded; } else if (!raw.MaxPlayers.HasValue) { game.Status = EDataGameListServerGameStatus.Unknown; } else { game.Status = EDataGameListServerGameStatus.Open; } game.MapFilename = raw.MapFile; game.Footer = raw.MapFile + @".bzn"; if (!string.IsNullOrWhiteSpace(raw.MOTD)) { game.TopInfo.Add(Format.Sanitize(raw.MOTD)); } foreach (string mod in raw.Mods) { ulong workshopIdNum = 0; if (!string.IsNullOrWhiteSpace(mod) && mod != "0") { if (ulong.TryParse(mod, out workshopIdNum) && workshopIdNum > 0) { Task <string> modNameTask = Task.Run(async() => { string modNameRet = await _steam.GetSteamWorkshopName(mod); return(modNameRet); }); var modName = modNameTask?.Result ?? mod; if (!string.IsNullOrWhiteSpace(modName)) { game.TopInfo.Add($"Mod: [{Format.Sanitize(modName)}](http://steamcommunity.com/sharedfiles/filedetails/?id={mod})"); } else { game.TopInfo.Add($"Mod: [{Format.Sanitize(mod)}](http://steamcommunity.com/sharedfiles/filedetails/?id={mod}"); } } else { game.TopInfo.Add("Mod: " + mod); } } break; // hack to only do the first entry until I properly redo this } { int k = 1; int d = 1; int s = 1; bool scoreNeedsSign = false; raw.pl.ForEach(dr => { k = Math.Max(k, (dr.Kills.HasValue ? dr.Kills.Value : 0).ToString().Length); d = Math.Max(d, (dr.Deaths.HasValue ? dr.Deaths.Value : 0).ToString().Length); s = Math.Max(s, Math.Abs(dr.Score.HasValue ? dr.Score.Value : 0).ToString().Length); scoreNeedsSign = scoreNeedsSign || (dr.Score < 0); }); await Task.WhenAll(raw.pl.Select(async dr => { int Killv = dr.Kills.HasValue ? dr.Kills.Value : 0; int Deathsv = dr.Deaths.HasValue ? dr.Deaths.Value : 0; int Scorev = dr.Score.HasValue ? dr.Score.Value : 0; string scoresign = "0"; if (Scorev > 0) { scoresign = "+"; } if (Scorev < 0) { scoresign = "-"; } if (!scoreNeedsSign) { scoresign = string.Empty; } UserData userData = await GetUserData(dr.PlayerID); game.Players.Add(new DataGameListPlayer() { Index = dr.Team, Name = dr.Name, PlayerClass = $"{Killv.ToString().PadLeft(k, '0')}/{Deathsv.ToString().PadLeft(d, '0')}/{scoresign}{Math.Abs(Scorev).ToString().PadLeft(s, '0')}", Url = userData?.ProfileUrl }); })); game.PlayersHeader = "[T] (K/D/S) Players"; } { string name = await GetBZCCGameProperty("name", raw.MapFile); if (string.IsNullOrWhiteSpace(name)) { game.Properties.Add(new Tuple <string, string>("Map", $"[{raw.MapFile}]")); } else { game.Properties.Add(new Tuple <string, string>("Map", $"{name}")); } if (!string.IsNullOrWhiteSpace(raw.v)) { game.Properties.Add(new Tuple <string, string>("Version", $"{raw.v}")); } /*if (!string.IsNullOrWhiteSpace(raw.d)) * { * game.Properties.Add(new Tuple<string, string>("Mod", $"[{raw.d}]")); * }*/ if (!string.IsNullOrWhiteSpace(raw.t)) { switch (raw.t) { case "0": game.Properties.Add(new Tuple <string, string>("NAT", $"NONE")); /// Works with anyone break; case "1": game.Properties.Add(new Tuple <string, string>("NAT", $"FULL CONE")); /// Accepts any datagrams to a port that has been previously used. Will accept the first datagram from the remote peer. break; case "2": game.Properties.Add(new Tuple <string, string>("NAT", $"ADDRESS RESTRICTED")); /// Accepts datagrams to a port as long as the datagram source IP address is a system we have already sent to. Will accept the first datagram if both systems send simultaneously. Otherwise, will accept the first datagram after we have sent one datagram. break; case "3": game.Properties.Add(new Tuple <string, string>("NAT", $"PORT RESTRICTED")); /// Same as address-restricted cone NAT, but we had to send to both the correct remote IP address and correct remote port. The same source address and port to a different destination uses the same mapping. break; case "4": game.Properties.Add(new Tuple <string, string>("NAT", $"SYMMETRIC")); /// A different port is chosen for every remote destination. The same source address and port to a different destination uses a different mapping. Since the port will be different, the first external punchthrough attempt will fail. For this to work it requires port-prediction (MAX_PREDICTIVE_PORT_RANGE>1) and that the router chooses ports sequentially. break; case "5": game.Properties.Add(new Tuple <string, string>("NAT", $"UNKNOWN")); /// Hasn't been determined. NATTypeDetectionClient does not use this, but other plugins might break; case "6": game.Properties.Add(new Tuple <string, string>("NAT", $"DETECTION IN PROGRESS")); /// In progress. NATTypeDetectionClient does not use this, but other plugins might break; case "7": game.Properties.Add(new Tuple <string, string>("NAT", $"SUPPORTS UPNP")); /// Didn't bother figuring it out, as we support UPNP, so it is equivalent to NAT_TYPE_NONE. NATTypeDetectionClient does not use this, but other plugins might break; default: game.Properties.Add(new Tuple <string, string>("NAT", $"[" + raw.t + "]")); break; } } switch (raw.proxySource) { case "Rebellion": game.Properties.Add(new Tuple <string, string>("List", $"Rebellion")); break; default: game.Properties.Add(new Tuple <string, string>("List", $"IonDriver")); break; } { switch (raw.GameType) { case 0: game.Properties.Add(new Tuple <string, string>("Type", $"All")); break; case 1: switch (raw.GameSubType) { case 0: game.Properties.Add(new Tuple <string, string>("Type", $"DM")); break; case 1: game.Properties.Add(new Tuple <string, string>("Type", $"KOTH")); break; case 2: game.Properties.Add(new Tuple <string, string>("Type", $"CTF")); break; case 3: game.Properties.Add(new Tuple <string, string>("Type", $"Loot")); break; case 4: game.Properties.Add(new Tuple <string, string>("Type", $"DM [RESERVED]")); break; case 5: game.Properties.Add(new Tuple <string, string>("Type", $"Race")); break; case 6: game.Properties.Add(new Tuple <string, string>("Type", $"Race (Vehicle Only)")); break; case 7: game.Properties.Add(new Tuple <string, string>("Type", $"DM (Vehicle Only)")); break; default: game.Properties.Add(new Tuple <string, string>("Type", $"DM [UNKNOWN {raw.GameSubType}]")); break; } break; case 2: switch (raw.GameSubType) { case 12: game.Properties.Add(new Tuple <string, string>("Type", $"STRAT")); break; case 13: case 41: game.Properties.Add(new Tuple <string, string>("Type", $"MPI")); break; default: game.Properties.Add(new Tuple <string, string>("Type", $"STRAT [UNKNOWN {raw.GameSubType}]")); break; } break; case 3: game.Properties.Add(new Tuple <string, string>("Type", $"MPI [Invalid]")); break; } if (raw.GameTimeMinutes.HasValue) { switch (raw.ServerInfoMode) { case 1: if (raw.GameTimeMinutes.Value == 255) { game.Properties.Add(new Tuple <string, string>("Time", $"Not playing or in shell")); } else { game.Properties.Add(new Tuple <string, string>("Time", $"Not playing or in shell for {raw.GameTimeMinutes.Value} minutes")); } break; case 3: game.Properties.Add(new Tuple <string, string>("Time", $"Playing for {raw.GameTimeMinutes.Value} minutes")); break; } } if (raw.TPS.HasValue && raw.TPS > 0) { game.Properties.Add(new Tuple <string, string>("TPS", $"{raw.TPS}")); } if (raw.MaxPing.HasValue && raw.MaxPing > 0) { game.Properties.Add(new Tuple <string, string>("MaxPing", $"{raw.MaxPing}")); } if (raw.TimeLimit.HasValue && raw.TimeLimit > 0) { game.Properties.Add(new Tuple <string, string>("TimeLim", $"{raw.TimeLimit}")); } if (raw.KillLimit.HasValue && raw.KillLimit > 0) { game.Properties.Add(new Tuple <string, string>("KillLim", $"{raw.KillLimit}")); } } } return(game); }))).ToArray(); return(data); } }
public async Task <DataGameList> GetGamesNew() { Tuple <string, DateTime> gameData = await TryReadText(filePath, TimeSpan.FromSeconds(5)); if (gameData == null || string.IsNullOrWhiteSpace(gameData.Item1)) { return(null); } var LobbyData = JsonConvert.DeserializeObject <Dictionary <string, Lobby> >(gameData.Item1); //return new BZ98ServerData() //{ // Games = LobbyData.Where(dr => !dr.Value.isChat // && (!dr.Value.isPrivate || (dr.Value.isPrivate && dr.Value.IsPassworded == true)) // && ((!string.IsNullOrWhiteSpace(dr.Value.clientVersion)) && ("0123456789".Contains(dr.Value.clientVersion[0]))) // not mobile which starts with MB or something // && ((!string.IsNullOrWhiteSpace(dr.Value.clientVersion)) && (dr.Value.clientVersion != "0.0.0")) // not test game // ).Select(dr => dr.Value).ToList(), // Modified = gameData.Item2 //}; DataGameList data = new DataGameList() { GameTitle = this.Title, Header = new DataGameListHeader() { Description = "List of games currently on BZ98 Redux matchmaking server", Image = "http://discord.battlezone.report/resources/logos/bz98r.png", Credit = $"Fetched by Nielk1's BZ98Bridge Bot" } }; data.Header.ServerStatus = new DataGameListServerStatus[] { new DataGameListServerStatus() { Name = "Rebellion", Status = EDataGameListServerStatus.NotSet, Updated = gameData.Item2 } }; data.Games = (await Task.WhenAll( LobbyData .Where(dr => !dr.Value.isChat && (!dr.Value.isPrivate || (dr.Value.isPrivate && dr.Value.IsPassworded == true)) //&& ((!string.IsNullOrWhiteSpace(dr.Value.clientVersion)) && ("0123456789".Contains(dr.Value.clientVersion[0]))) // not mobile which starts with MB or something && ((!string.IsNullOrWhiteSpace(dr.Value.clientVersion)) && (dr.Value.clientVersion != "0.0.0")) // not test game ).Select(dr => dr.Value) .Select(async raw => { DataGameListGame game = new DataGameListGame(); game.Name = raw.Name; game.Image = await GetShellMap(raw.MapFile) ?? "http://discord.battlezone.report/resources/logos/nomap.png"; game.CurPlayers = raw.userCount; game.MaxPlayers = raw.PlayerLimit; if (raw.isLocked) { game.Status = EDataGameListServerGameStatus.Locked; } else if (raw.IsPassworded == true) { game.Status = EDataGameListServerGameStatus.Passworded; } else { game.Status = EDataGameListServerGameStatus.Open; } game.MapFilename = raw.MapFile; game.Footer = raw.MapFile + @".bzn"; if (!string.IsNullOrWhiteSpace(raw.clientVersion)) { game.Footer += $" <{raw.clientVersion}>"; } { await Task.WhenAll(raw.users .Select(async dr => { string team = dr.Value.metadata.ContainsKey("team") ? dr.Value.metadata["team"] : string.Empty; string vehicle = dr.Value.metadata.ContainsKey("vehicle") ? dr.Value.metadata["vehicle"] : string.Empty; int teamInt = 0; UserData userData = await GetUserData(dr.Value.id, dr.Value.authType); game.Players.Add(new DataGameListPlayer() { Index = int.TryParse(team, out teamInt) ? (int?)teamInt : null, Name = dr.Value.name, PlayerClass = vehicle, Url = userData?.ProfileUrl }); })); game.PlayersHeader = "Players"; } { ulong workshopIdNum = 0; if (!string.IsNullOrWhiteSpace(raw.WorkshopID) && raw.WorkshopID != "0") { if (ulong.TryParse(raw.WorkshopID, out workshopIdNum) && workshopIdNum > 0) { Task <string> modNameTask = Task.Run(async() => { string modNameRet = await _steam.GetSteamWorkshopName(raw.WorkshopID); return(modNameRet); }); var modName = modNameTask?.Result ?? raw.WorkshopID; if (!string.IsNullOrWhiteSpace(modName)) { game.TopInfo.Add($"Mod: [{Format.Sanitize(modName)}](http://steamcommunity.com/sharedfiles/filedetails/?id={raw.WorkshopID})"); } else { game.TopInfo.Add($"Mod: [{Format.Sanitize(raw.WorkshopID)}](http://steamcommunity.com/sharedfiles/filedetails/?id={raw.WorkshopID}"); } } else { game.TopInfo.Add("Mod: " + raw.WorkshopID); } } } game.Properties.Add(new Tuple <string, string>("Map", "[" + raw.MapFile + "]")); game.Properties.Add(new Tuple <string, string>("State", raw.IsEnded ? "Ended" : raw.IsLaunched ? "Launched" : "In Shell")); if (raw.TimeLimit.HasValue && raw.TimeLimit.Value > 0) { game.Properties.Add(new Tuple <string, string>("TimeLimit", raw.TimeLimit.Value.ToString())); } if (raw.KillLimit.HasValue && raw.KillLimit.Value > 0) { game.Properties.Add(new Tuple <string, string>("KillLimit", raw.KillLimit.Value.ToString())); } if (raw.Lives.HasValue && raw.Lives.Value > 0) { game.Properties.Add(new Tuple <string, string>("Lives", raw.Lives.Value.ToString())); } if (raw.SyncJoin.HasValue) { game.Properties.Add(new Tuple <string, string>("SyncJoin", raw.SyncJoin.Value ? "On" : "Off")); } if (raw.SatelliteEnabled.HasValue) { game.Properties.Add(new Tuple <string, string>("Satellite", raw.SatelliteEnabled.Value ? "On" : "Off")); } if (raw.BarracksEnabled.HasValue) { game.Properties.Add(new Tuple <string, string>("Barracks", raw.BarracksEnabled.Value ? "On" : "Off")); } if (raw.SniperEnabled.HasValue) { game.Properties.Add(new Tuple <string, string>("Sniper", raw.SniperEnabled.Value ? "On" : "Off")); } if (raw.SplinterEnabled.HasValue) { game.Properties.Add(new Tuple <string, string>("Splinter", raw.SplinterEnabled.Value ? "On" : "Off")); } return(game); }) )).ToArray(); return(data); }
public async Task <DataGameList> GetGamesNew() { using (var http = new HttpClient()) { var res = await http.GetStringAsync(queryUrl).ConfigureAwait(false); var gamelist = JsonConvert.DeserializeObject <RaknetData>(res); //gamelist.SetBz2Service(this); //return gamelist; DataGameList data = new DataGameList() { GameTitle = this.Title, Header = new DataGameListHeader() { Description = "List of games currently on Battlezone II matchmaking servers", Image = "http://discord.battlezone.report/resources/logos/bz2.png", Credit = "Brought to you by Nielk1's Raknet Bot" } }; { bool isIonDriver = gamelist.GET.Any(game => game.IsIonDriverMarker()); bool isOnIonDriver = gamelist.GET.Any(game => game.IsOnIonDriver()); DataGameListServerStatus IonDriverStatus = new DataGameListServerStatus() { Name = "IonDriver", Updated = null }; if (isIonDriver || isOnIonDriver) { IonDriverStatus.Status = EDataGameListServerStatus.Online; } else { IonDriverStatus.Status = EDataGameListServerStatus.NoGames; } bool isMatesFamily = gamelist.GET.Any(game => game.IsMatesFamilyMarker()); bool isOnMatesFamily = gamelist.GET.Any(game => game.IsOnMatesFamily()); bool haveMatesFamilyStatus = gamelist.proxyStatus.ContainsKey("masterserver.matesfamily.org"); bool isMatesFamilyUp = haveMatesFamilyStatus && gamelist.proxyStatus["masterserver.matesfamily.org"].success == true; string statusMatesFamily = haveMatesFamilyStatus ? gamelist.proxyStatus["masterserver.matesfamily.org"].status : null; DateTime?dateMatesFamily = haveMatesFamilyStatus ? gamelist.proxyStatus["masterserver.matesfamily.org"].updated : null; DataGameListServerStatus MatesFamilyStatus = new DataGameListServerStatus() { Name = "MatesFamily (Primary)", Updated = dateMatesFamily }; if (isOnMatesFamily || isMatesFamily) { MatesFamilyStatus.Status = EDataGameListServerStatus.Online; } else if (isMatesFamilyUp) { MatesFamilyStatus.Status = EDataGameListServerStatus.NoGames; } else if (!isMatesFamilyUp) { MatesFamilyStatus.Status = EDataGameListServerStatus.Offline; } else { MatesFamilyStatus.Status = EDataGameListServerStatus.Unknown; } data.Header.ServerStatus = new DataGameListServerStatus[] { IonDriverStatus, MatesFamilyStatus }; } data.Games = (await Task.WhenAll( gamelist.GET .Where(dr => !dr.IsMarker()) .Select(async raw => { DataGameListGame game = new DataGameListGame(); game.Name = raw.Name; game.Image = await GetBZ2GameProperty("shell", raw.MapFile) ?? "http://discord.battlezone.report/resources/logos/nomap.png"; game.CurPlayers = raw.CurPlayers; game.MaxPlayers = raw.MaxPlayers; if (raw.Locked) { game.Status = EDataGameListServerGameStatus.Locked; } else if (raw.Passworded) { game.Status = EDataGameListServerGameStatus.Passworded; } else if (!raw.CurPlayers.HasValue || !raw.MaxPlayers.HasValue) { game.Status = EDataGameListServerGameStatus.Unknown; } else { game.Status = EDataGameListServerGameStatus.Open; } game.MapFilename = raw.MapFile; game.Footer = raw.MapFile + @".bzn"; if (raw.pong != null && raw.pong.CompressedData != null) { if (raw.pong.CompressedData.MOTD.Length > 0) { game.TopInfo.Add(Format.Sanitize(raw.pong.CompressedData.MOTD)); } if (raw.pong.CompressedData.MapURL.Length > 0) { game.TopInfo.Add(Format.Sanitize(raw.pong.CompressedData.MapURL)); } } { string name = await GetBZ2GameProperty("name", raw.MapFile); string version = await GetBZ2GameProperty("version", raw.v); string mod = await GetBZ2GameProperty("mod", raw.d); if (string.IsNullOrWhiteSpace(name)) { game.Properties.Add(new Tuple <string, string>("Map", $"[{raw.MapFile}]")); } else { game.Properties.Add(new Tuple <string, string>("Map", $"{name}")); } if (string.IsNullOrWhiteSpace(version)) { game.Properties.Add(new Tuple <string, string>("Version", $"[{raw.v}]")); } else { game.Properties.Add(new Tuple <string, string>("Version", $"{version}")); } if (string.IsNullOrWhiteSpace(mod)) { game.Properties.Add(new Tuple <string, string>("Mod", $"[{raw.d}]")); } else { game.Properties.Add(new Tuple <string, string>("Mod", $"{mod}")); } switch (raw.t) { case "0": game.Properties.Add(new Tuple <string, string>("NAT", $"NONE")); /// Works with anyone break; case "1": game.Properties.Add(new Tuple <string, string>("NAT", $"FULL CONE")); /// Accepts any datagrams to a port that has been previously used. Will accept the first datagram from the remote peer. break; case "2": game.Properties.Add(new Tuple <string, string>("NAT", $"ADDRESS RESTRICTED")); /// Accepts datagrams to a port as long as the datagram source IP address is a system we have already sent to. Will accept the first datagram if both systems send simultaneously. Otherwise, will accept the first datagram after we have sent one datagram. break; case "3": game.Properties.Add(new Tuple <string, string>("NAT", $"PORT RESTRICTED")); /// Same as address-restricted cone NAT, but we had to send to both the correct remote IP address and correct remote port. The same source address and port to a different destination uses the same mapping. break; case "4": game.Properties.Add(new Tuple <string, string>("NAT", $"SYMMETRIC")); /// A different port is chosen for every remote destination. The same source address and port to a different destination uses a different mapping. Since the port will be different, the first external punchthrough attempt will fail. For this to work it requires port-prediction (MAX_PREDICTIVE_PORT_RANGE>1) and that the router chooses ports sequentially. break; case "5": game.Properties.Add(new Tuple <string, string>("NAT", $"UNKNOWN")); /// Hasn't been determined. NATTypeDetectionClient does not use this, but other plugins might break; case "6": game.Properties.Add(new Tuple <string, string>("NAT", $"DETECTION IN PROGRESS")); /// In progress. NATTypeDetectionClient does not use this, but other plugins might break; case "7": game.Properties.Add(new Tuple <string, string>("NAT", $"SUPPORTS UPNP")); /// Didn't bother figuring it out, as we support UPNP, so it is equivalent to NAT_TYPE_NONE. NATTypeDetectionClient does not use this, but other plugins might break; default: game.Properties.Add(new Tuple <string, string>("NAT", $"[" + raw.t + "]")); break; } switch (raw.proxySource) { case "masterserver.matesfamily.org": game.Properties.Add(new Tuple <string, string>("List", $"MatesFamily")); break; case "gamelist.kebbz.com": game.Properties.Add(new Tuple <string, string>("List", $"KebbzNet")); break; default: game.Properties.Add(new Tuple <string, string>("List", $"IonDriver")); break; } if (raw.pong != null) { if (raw.pong.CompressedData != null) { int k = 1; int d = 1; int s = 1; bool scoreNeedsSign = false; raw.pong.CompressedData.Players.ForEach(dr => { k = Math.Max(k, dr.Kills.ToString().Length); d = Math.Max(d, dr.Deaths.ToString().Length); s = Math.Max(s, Math.Abs(dr.Score).ToString().Length); scoreNeedsSign = scoreNeedsSign || (dr.Score < 0); }); raw.pong.CompressedData.Players.ForEach(dr => { string scoresign = "0"; if (dr.Score > 0) { scoresign = "+"; } if (dr.Score < 0) { scoresign = "-"; } if (!scoreNeedsSign) { scoresign = string.Empty; } game.Players.Add(new DataGameListPlayer() { Index = dr.Team, Name = dr.UserName, PlayerClass = $"{dr.Kills.ToString().PadLeft(k, '0')}/{dr.Deaths.ToString().PadLeft(d, '0')}/{scoresign}{Math.Abs(dr.Score).ToString().PadLeft(s, '0')}", Url = null }); }); game.PlayersHeader = "[T] (K/D/S) Players"; } switch (raw.pong.GameType) { case 0: game.Properties.Add(new Tuple <string, string>("Type", $"All")); break; case 1: switch (raw.pong.GameSubType) { case 0: game.Properties.Add(new Tuple <string, string>("Type", $"DM")); break; case 1: game.Properties.Add(new Tuple <string, string>("Type", $"KOTH")); break; case 2: game.Properties.Add(new Tuple <string, string>("Type", $"CTF")); break; case 3: game.Properties.Add(new Tuple <string, string>("Type", $"Loot")); break; case 4: game.Properties.Add(new Tuple <string, string>("Type", $"DM [RESERVED]")); break; case 5: game.Properties.Add(new Tuple <string, string>("Type", $"Race")); break; case 6: game.Properties.Add(new Tuple <string, string>("Type", $"Race (Vehicle Only)")); break; case 7: game.Properties.Add(new Tuple <string, string>("Type", $"DM (Vehicle Only)")); break; default: game.Properties.Add(new Tuple <string, string>("Type", $"DM [UNKNOWN]")); break; } break; case 2: if (raw.pong.TeamsOn && raw.pong.OnlyOneTeam) { game.Properties.Add(new Tuple <string, string>("Type", $"MPI")); } else { game.Properties.Add(new Tuple <string, string>("Type", $"Strat")); } break; case 3: game.Properties.Add(new Tuple <string, string>("Type", $"MPI [Invalid]")); break; } switch (raw.pong.ServerInfoMode) { case 1: game.Properties.Add(new Tuple <string, string>("Time", $"Not playing or in shell for {raw.pong.GameTimeMinutes} minutes")); break; case 3: game.Properties.Add(new Tuple <string, string>("Time", $"Playing for {raw.pong.GameTimeMinutes} minutes")); break; } game.Properties.Add(new Tuple <string, string>("TPS", $"{raw.pong.TPS}")); if (raw.pong.MaxPing > 0) { game.Properties.Add(new Tuple <string, string>("MaxPing", $"{raw.pong.MaxPing}")); } if (raw.pong.TimeLimit > 0) { game.Properties.Add(new Tuple <string, string>("TimeLim", $"{raw.pong.TimeLimit}")); } if (raw.pong.KillLimit > 0) { game.Properties.Add(new Tuple <string, string>("KillLim", $"{raw.pong.KillLimit}")); } } } return(game); }))).ToArray(); return(data); } }