コード例 #1
0
        private static void FillApplicableRatings(SpringBattle battle, SpringBattleContext result)
        {
            battle.ApplicableRatings = 0;
            if (battle.HasBots)
            {
                return;
            }
            if (battle.IsMission)
            {
                return;
            }
            if (battle.SpringBattlePlayers?.Where(x => !x.IsSpectator).Select(x => x.AllyNumber).Distinct().Count() < 2)
            {
                return;
            }
            if (battle.ResourceByMapResourceID?.MapIsSpecial == true)
            {
                return;
            }

            //only count balanced custom matches for elo
            if (battle.Mode == AutohostMode.None && battle.SpringBattlePlayers?.Where(x => !x.IsSpectator).GroupBy(x => x.AllyNumber).Select(x => x.Count()).Distinct().Count() > 1)
            {
                return;
            }
            if (battle.Duration < GlobalConst.MinDurationForElo)
            {
                return;
            }
            battle.ApplicableRatings |= (RatingCategoryFlags)result.LobbyStartContext.ApplicableRating;
            //battle.ApplicableRatings |= RatingCategoryFlags.Casual;
        }
コード例 #2
0
        private static void ProcessElos(SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, ZkDataContext db, SpringBattle sb)
        {
            bool noElo = result.OutputExtras.Any(x => x?.StartsWith("noElo", true, System.Globalization.CultureInfo.CurrentCulture) == true);

            sb.CalculateAllElo(noElo);

            if (!noElo)
            {
                RatingSystems.ProcessResult(sb);
            }

            foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator))
            {
                u.Account.CheckLevelUp();
            }

            db.SaveChanges();

            try
            {
                foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account))
                {
                    server.PublishAccountUpdate(a);
                    server.PublishUserProfileUpdate(a);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("error updating extension data: {0}", ex);
            }
        }
コード例 #3
0
        protected virtual async Task OnDedicatedExited(SpringBattleContext springBattleContext)
        {
            StopVote();
            IsInGame     = false;
            RunningSince = null;

            var debriefingMessage = BattleResultHandler.SubmitSpringBattleResult(springBattleContext, server);
            await server.Broadcast(Users.Keys, debriefingMessage);

            await server.Broadcast(server.ConnectedUsers.Keys, new BattleUpdate()
            {
                Header = GetHeader()
            });

            foreach (var s in toNotify)
            {
                await
                server.GhostSay(new Say()
                {
                    User       = GlobalConst.NightwatchName,
                    Text       = $"** {FounderName} 's {Title} just ended, join me! **",
                    Target     = s,
                    IsEmote    = true,
                    Place      = SayPlace.User,
                    Ring       = true,
                    AllowRelay = false
                });
            }

            toNotify.Clear();
        }
コード例 #4
0
 void spring_SpringExited(object sender, SpringBattleContext springBattleContext)
 {
     if (speechSynthesizer != null)
     {
         speechSynthesizer.SpeakAsyncCancelAll();
     }
 }
コード例 #5
0
        protected override async Task OnDedicatedExited(SpringBattleContext springBattleContext)
        {
            try
            {
                StopVote();
                RunningSince = null;
                IsInGame     = false;
                isZombie     = true;

                var playerNames = spring.Context.ActualPlayers.Where(x => x.Name != null).Select(x => x.Name).ToList();

                bool result = BattleResultHandler.SubmitSpringBattleResult(springBattleContext, server, debriefingMessage =>
                {
                    debriefingMessage.ChatChannel = "debriefing_" + debriefingMessage.ServerBattleID;
                    // join people to channel
                    Task.WhenAll(
                        playerNames.Select(x => server.ConnectedUsers.Get(x))
                        .Where(x => x != null)
                        .Select(x => x.Process(new JoinChannel()
                    {
                        ChannelName = debriefingMessage.ChatChannel
                    })));
                    server.Broadcast(playerNames, debriefingMessage);
                });

                await server.RemoveBattle(this);
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error processing battle exited: {0}", ex);
            }
        }
コード例 #6
0
 private static void FillApplicableRatings(SpringBattle battle, SpringBattleContext result)
 {
     battle.ApplicableRatings = 0;
     if (battle.HasBots)
     {
         return;
     }
     if (battle.IsMission)
     {
         return;
     }
     if (battle.SpringBattlePlayers?.Select(x => x.AllyNumber).Distinct().Count() < 2)
     {
         return;
     }
     if (battle.ResourceByMapResourceID?.MapIsSpecial == true)
     {
         return;
     }
     if (battle.Duration < GlobalConst.MinDurationForElo)
     {
         return;
     }
     battle.ApplicableRatings |= (RatingCategoryFlags)result.LobbyStartContext.ApplicableRating;
     //battle.ApplicableRatings |= RatingCategoryFlags.Casual;
 }
コード例 #7
0
        private static void ProcessXP(SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, ZkDataContext db, SpringBattle sb)
        {
            sb.DispenseXP();

            foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator))
            {
                u.Account.CheckLevelUp();
            }


            db.SaveChanges();

            if (sb.ApplicableRatings == 0)
            {
                try
                {
                    foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account))
                    {
                        server.PublishAccountUpdate(a);
                        server.PublishUserProfileUpdate(a);
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError("error updating extension data: {0}", ex);
                }
            }
        }
コード例 #8
0
        private static void ProcessPlanetWars(SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, SpringBattle sb, ZkDataContext db, StringBuilder text)
        {
            if (result.LobbyStartContext.Mode != AutohostMode.Planetwars || sb.PlayerCount < 2 || sb.Duration < GlobalConst.MinDurationForPlanetwars)
            {
                return;
            }

            List <int> winnerTeams = sb.SpringBattlePlayers.Where(x => x.IsInVictoryTeam && !x.IsSpectator).Select(x => x.AllyNumber).Distinct().ToList();
            int?       winNum      = null;

            if (winnerTeams.Count == 1)
            {
                winNum = winnerTeams[0];
                if (winNum > 1)
                {
                    winNum = null;
                }
            }

            PlanetWarsTurnHandler.EndTurn(result.LobbyStartContext.Map,
                                          result.OutputExtras,
                                          db,
                                          winNum,
                                          sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account).ToList(),
                                          text,
                                          sb,
                                          sb.SpringBattlePlayers.Where(x => !x.IsSpectator && x.AllyNumber == 0).Select(x => x.Account).ToList(),
                                          server.PlanetWarsEventCreator, server);

            server.PlanetWarsMatchMaker.RemoveFromRunningBattles(result.LobbyStartContext.BattleID);
        }
コード例 #9
0
 //Processes new SpringBattle and determines applicable ratings
 public static void ProcessResult(SpringBattle battle, SpringBattleContext result, PendingDebriefing partialDebriefing)
 {
     if (!Initialized)
     {
         return;
     }
     FillApplicableRatings(battle, result);
     ProcessBattle(battle, debriefing: partialDebriefing);
 }
コード例 #10
0
 //Processes new SpringBattle and determines applicable ratings
 public static void ProcessResult(SpringBattle battle, SpringBattleContext result)
 {
     if (!Initialized)
     {
         return;
     }
     FillApplicableRatings(battle, result);
     ProcessBattle(battle);
 }
コード例 #11
0
        protected virtual async Task OnDedicatedExited(SpringBattleContext springBattleContext)
        {
            StopVote();
            IsInGame     = false;
            RunningSince = null;
            EndedSince   = DateTime.UtcNow;

            var debriefingMessage = BattleResultHandler.SubmitSpringBattleResult(springBattleContext, server);

            Debriefings.Add(debriefingMessage);

            await server.Broadcast(Users.Keys, debriefingMessage);

            await server.Broadcast(server.ConnectedUsers.Keys, new BattleUpdate()
            {
                Header = GetHeader()
            });

            foreach (var s in toNotify)
            {
                await
                server.GhostSay(new Say()
                {
                    User       = GlobalConst.NightwatchName,
                    Text       = $"** {FounderName} 's {Title} just ended, join me! **",
                    Target     = s,
                    IsEmote    = true,
                    Place      = SayPlace.User,
                    Ring       = true,
                    AllowRelay = false
                });
            }

            toNotify.Clear();

            var playingEligibleUsers = server.MatchMaker.GetEligibleQuickJoinPlayers(Users.Values.Where(x => !x.LobbyUser.IsAway && !x.IsSpectator).Select(x => server.ConnectedUsers[x.Name]).ToList());

            if (playingEligibleUsers.Count() >= InviteMMPlayers)   //Make sure there are enough eligible users for a battle to be likely to happen

            //put all users into MM queue to suggest battles
            {
                var teamsQueues    = server.MatchMaker.PossibleQueues.Where(x => x.Mode == AutohostMode.Teams).ToList();
                var availableUsers = Users.Values.Where(x => !x.LobbyUser.IsAway).Select(x => server.ConnectedUsers[x.Name]).ToList();
                await server.MatchMaker.MassJoin(availableUsers, teamsQueues);
            }


            if (IsAutohost)
            {
                await RunCommandDirectly <CmdMap>(null);

                discussionTimer.Interval = (DiscussionTime + 1) * 1000;
                discussionTimer.Start();
            }
            await CheckCloseBattle();
        }
コード例 #12
0
 private async void DedicatedServerExited(object sender, SpringBattleContext springBattleContext)
 {
     try
     {
         await OnDedicatedExited(springBattleContext);
     }
     catch (Exception ex)
     {
         Trace.TraceError("Error processing dedi server exited: {0}", ex);
     }
 }
コード例 #13
0
        private static void FillApplicableRatings(SpringBattle battle, SpringBattleContext result)
        {
            battle.ApplicableRatings = 0;
            if (battle.HasBots)
            {
                return;
            }
            if (battle.IsMission)
            {
                return;
            }
            if (battle.SpringBattlePlayers?.Where(x => !x.IsSpectator).Select(x => x.AllyNumber).Distinct().Count() < 2)
            {
                return;
            }
            if (battle.ResourceByMapResourceID?.MapIsSpecial == true)
            {
                return;
            }

            //only count balanced custom matches for elo
            if (battle.Mode == AutohostMode.None && battle.SpringBattlePlayers?.Where(x => !x.IsSpectator).GroupBy(x => x.AllyNumber).Select(x => x.Count()).Distinct().Count() > 1)
            {
                return;
            }
            if (battle.Duration < GlobalConst.MinDurationForElo)
            {
                return;
            }

            //don't mark battles for ratings if they can't be rated
            ICollection <int> winners = battle.SpringBattlePlayers.Where(p => p.IsInVictoryTeam && !p.IsSpectator).Select(p => RatingSystems.GetRatingId(p.AccountID)).Distinct().ToList();
            ICollection <int> losers  = battle.SpringBattlePlayers.Where(p => !p.IsInVictoryTeam && !p.IsSpectator).Select(p => RatingSystems.GetRatingId(p.AccountID)).Distinct().ToList();

            if (winners.Count == 0 || losers.Count == 0 || winners.Intersect(losers).Count() != 0)
            {
                return;
            }

            battle.ApplicableRatings |= (RatingCategoryFlags)result.LobbyStartContext.ApplicableRating;
            //Optionally add other flags here, like a casual or overall rating
        }
コード例 #14
0
 private void spring_BattleStarted(object sender, SpringBattleContext e)
 {
     try
     {
         StopVote();
         if (IsMatchMakerBattle && e.PlayersUnreadyOnStart.Count > 0 && e.IsTimeoutForceStarted)
         {
             string message = string.Format("Players {0} did not choose a start position. Game will be aborted.", e.PlayersUnreadyOnStart.StringJoin());
             spring.SayGame(message);
             Trace.TraceInformation(string.Format("Matchmaker Game {0} aborted because {1}", BattleID, message));
             RunCommandDirectly <CmdExit>(null);
             server.UserLogSay($"Battle aborted because {e.PlayersUnreadyOnStart.Count} players didn't join their MM game: {e.PlayersUnreadyOnStart.StringJoin()}.");
             e.PlayersUnreadyOnStart.ForEach(x => server.MatchMaker.BanPlayer(x));
         }
     }
     catch (Exception ex)
     {
         Trace.TraceError("Error processing spring_BattleStarted started: {0}", ex);
     }
 }
コード例 #15
0
        private static SpringBattle SaveSpringBattle(SpringBattleContext result, ZkDataContext db)
        {
            var sb = new SpringBattle
            {
                HostAccountID     = Account.AccountByName(db, result.LobbyStartContext.FounderName)?.AccountID,
                Mode              = result.LobbyStartContext.Mode,
                Duration          = result.Duration,
                EngineGameID      = result.EngineBattleID,
                MapResourceID     = db.Resources.Single(x => x.InternalName == result.LobbyStartContext.Map).ResourceID,
                ModResourceID     = db.Resources.Single(x => x.InternalName == result.LobbyStartContext.Mod).ResourceID,
                HasBots           = result.LobbyStartContext.Bots.Any(),
                IsMission         = result.LobbyStartContext.IsMission,
                PlayerCount       = result.ActualPlayers.Count(x => !x.IsSpectator),
                StartTime         = result.StartTime,
                Title             = result.LobbyStartContext.Title,
                ReplayFileName    = Path.GetFileName(result.ReplayName),
                EngineVersion     = result.LobbyStartContext.EngineVersion,
                IsMatchMaker      = result.LobbyStartContext.IsMatchMakerGame,
                ApplicableRatings = 0,
            };

            db.SpringBattles.InsertOnSubmit(sb);

            // store players
            foreach (BattlePlayerResult p in result.ActualPlayers)
            {
                var account = Account.AccountByName(db, p.Name);
                if (account != null)
                {
                    sb.SpringBattlePlayers.Add(new SpringBattlePlayer
                    {
                        Account         = account,
                        AccountID       = account.AccountID,
                        AllyNumber      = p.AllyNumber,
                        IsInVictoryTeam = p.IsVictoryTeam,
                        IsSpectator     = p.IsSpectator,
                        LoseTime        = p.LoseTime
                    });
                }
            }

            // store bots
            var victoryAllyID = result.ActualPlayers.Where(x => x.IsVictoryTeam).Select(x => (int?)x.AllyNumber).FirstOrDefault() ?? -1;

            if (victoryAllyID == -1)
            {
                victoryAllyID = (result.ActualPlayers.Min(x => (int?)x.AllyNumber) ?? -1) + 1;                    // no player won, its likely to be next lowes team (stupid hack needed)
            }
            foreach (var bot in result.LobbyStartContext.Bots)
            {
                sb.SpringBattleBots.Add(new SpringBattleBot()
                {
                    AllyNumber      = bot.AllyID,
                    BotAI           = bot.BotAI,
                    BotName         = bot.BotName,
                    IsInVictoryTeam = bot.AllyID == victoryAllyID
                });
            }

            db.SaveChanges();

            return(db.SpringBattles.FirstOrDefault(x => x.SpringBattleID == sb.SpringBattleID)); // reselect from db to get proper lazy proxies
        }
コード例 #16
0
        //stores replay, springbattle, processes rating and xp, returns whether battle ended normally
        public static bool SubmitSpringBattleResult(SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, Action <BattleDebriefing> consumer)
        {
            var ret = new BattleDebriefing();

            try
            {
                bool isValidGame = true;
                if (!result.GameEndedOk)
                {
                    ret.Message = "Game didn't end properly";
                    isValidGame = false;
                }
                if (result.IsCheating)
                {
                    ret.Message = "Cheats were enabled during this game";
                    isValidGame = false;
                }

                var db   = new ZkDataContext();
                var text = new StringBuilder();

                var sb = SaveSpringBattle(result, db);

                if (isValidGame)
                {
                    StoreAwards(result.OutputExtras, sb, db);
                }
                StoreLogs(result.OutputExtras, sb, db);

                if (result.LobbyStartContext.Mode == AutohostMode.Planetwars)
                {
                    ProcessPlanetWars(result, server, sb, db, text);
                }


                Dictionary <int, int> orgLevels = sb.SpringBattlePlayers.Select(x => x.Account).ToDictionary(x => x.AccountID, x => x.Level);

                //fill in applicable ratings
                bool noElo = !isValidGame || result.LobbyStartContext.ModOptions.Any(x => x.Key.ToLower() == "noelo" && x.Value != "0" && x.Value != "false");
                if (!noElo)
                {
                    RatingSystems.FillApplicableRatings(sb, result);
                }

                if (isValidGame)
                {
                    ProcessXP(result, server, db, sb);
                }


                ret.Url            = string.Format("{1}/Battles/Detail/{0}", sb.SpringBattleID, GlobalConst.BaseSiteUrl);
                ret.ServerBattleID = sb.SpringBattleID;

                server.GhostSay(
                    new Say()
                {
                    Text =
                        string.Format("BATTLE DETAILS AND REPLAY ----> {0} <-----", ret.Url),
                    IsEmote = true,
                    Place   = SayPlace.Battle,
                    User    = GlobalConst.NightwatchName
                },
                    result.LobbyStartContext.BattleID);


                foreach (var p in sb.SpringBattlePlayers.Where(x => !x.IsSpectator))
                {
                    ret.DebriefingUsers[p.Account.Name] = new BattleDebriefing.DebriefingUser()
                    {
                        AccountID       = p.AccountID,
                        LoseTime        = p.LoseTime ?? -1,
                        AllyNumber      = p.AllyNumber,
                        IsInVictoryTeam = p.IsInVictoryTeam,
                        EloChange       = 0,
                        IsRankdown      = false,
                        IsRankup        = false,
                        NewElo          = -1,
                        NextRankElo     = -1,
                        PrevRankElo     = -1,
                        NewRank         = p.Account.Rank,
                        XpChange        = p.XpChange ?? 0,
                        NewXp           = p.Account.Xp,
                        NextLevelXp     = Account.GetXpForLevel(p.Account.Level + 1),
                        PrevLevelXp     = Account.GetXpForLevel(p.Account.Level),
                        IsLevelUp       = orgLevels[p.AccountID] < p.Account.Level,
                        Awards          = sb.AccountBattleAwards.Where(x => x.AccountID == p.AccountID).Select(x => new BattleDebriefing.DebriefingAward()
                        {
                            Value       = x.Value,
                            Key         = x.AwardKey,
                            Description = x.AwardDescription
                        }).ToList()
                    };
                }

                //send to rating

                if (!noElo)
                {
                    RatingSystems.ProcessResult(sb, result, new PendingDebriefing()
                    {
                        debriefingConsumer = consumer,
                        partialDebriefing  = ret,
                        battle             = sb,
                    });
                }

                Trace.TraceInformation("Battle ended: Server exited for B" + sb.SpringBattleID);
                db.SaveChanges();

                return(true);
            }
            catch (Exception ex)
            {
                var data = JsonConvert.SerializeObject(result);
                Trace.TraceError($"{ex}\nData:\n{data}");
                ret.Message = "Error processing game result: " + ex.Message;
                return(false);
            }
        }
コード例 #17
0
        protected virtual async Task OnDedicatedExited(SpringBattleContext springBattleContext)
        {
            StopVote();
            IsInGame        = false;
            RunningSince    = null;
            BlockPollsUntil = DateTime.UtcNow.AddSeconds(DiscussionSeconds);

            bool result = BattleResultHandler.SubmitSpringBattleResult(springBattleContext, server, (debriefing) =>
            {
                Debriefings.Add(debriefing);
                server.Broadcast(springBattleContext.ActualPlayers.Select(x => x.Name), debriefing);
                Trace.TraceInformation("Battle ended: Sent out debriefings for B" + debriefing.ServerBattleID);
            });

            await server.Broadcast(server.ConnectedUsers.Keys, new BattleUpdate()
            {
                Header = GetHeader()
            });

            foreach (var s in toNotify)
            {
                await
                server.GhostSay(new Say()
                {
                    User       = GlobalConst.NightwatchName,
                    Text       = $"** {FounderName} 's {Title} just ended, join me! **",
                    Target     = s,
                    IsEmote    = true,
                    Place      = SayPlace.User,
                    Ring       = true,
                    AllowRelay = false
                });
            }

            toNotify.Clear();

            var playingEligibleUsers = server.MatchMaker.GetEligibleQuickJoinPlayers(Users.Values.Where(x => !x.LobbyUser.IsAway && !x.IsSpectator && x.Name != null).Select(x => server.ConnectedUsers[x.Name]).ToList());

            if (playingEligibleUsers.Count() >= InviteMMPlayers)
            { //Make sure there are enough eligible users for a battle to be likely to happen
                //put all users into MM queue to suggest battles
                var teamsQueues    = server.MatchMaker.PossibleQueues.Where(x => x.Mode == AutohostMode.Teams).ToList();
                var availableUsers = Users.Values.Where(x => !x.LobbyUser.IsAway && x.Name != null).Select(x => server.ConnectedUsers[x.Name]).ToList();
                await server.MatchMaker.MassJoin(availableUsers, teamsQueues);

                DiscussionSeconds = MatchMaker.TimerSeconds + 2;
            }
            else
            {
                DiscussionSeconds = 5;
            }
            BlockPollsUntil = DateTime.UtcNow.AddSeconds(DiscussionSeconds);


            if (IsAutohost || (!Users.ContainsKey(FounderName) || Users[FounderName].LobbyUser?.IsAway == true) && Mode != AutohostMode.None && Mode != AutohostMode.Planetwars && !IsPassworded)
            {
                if (!result)
                {
                    //Game was aborted/exited/invalid, allow manual commands
                    BlockPollsUntil = DateTime.UtcNow;
                }
                else
                {
                    //Initiate discussion time, then map vote, then start vote
                    discussionTimer.Interval = (DiscussionSeconds - 1) * 1000;
                    discussionTimer.Start();
                }
            }
            await CheckCloseBattle();
        }
コード例 #18
0
        public static BattleDebriefing SubmitSpringBattleResult(SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server)
        {
            var ret = new BattleDebriefing();

            try
            {
                if (!result.GameEndedOk)
                {
                    ret.Message = "Game didn't end properly";
                    return(ret);
                }
                if (result.IsCheating)
                {
                    ret.Message = "Cheats were enabled during this game";
                    return(ret);
                }

                var db   = new ZkDataContext();
                var text = new StringBuilder();

                var sb = SaveSpringBattle(result, db);



                ProcessExtras(result.OutputExtras, sb, db);

                if (result.LobbyStartContext.Mode == AutohostMode.Planetwars)
                {
                    ProcessPlanetWars(result, server, sb, db, text);
                }


                Dictionary <int, int> orgLevels = sb.SpringBattlePlayers.Select(x => x.Account).ToDictionary(x => x.AccountID, x => x.Level);

                ProcessElos(result, server, db, sb);


                ret.Url            = string.Format("{1}/Battles/Detail/{0}", sb.SpringBattleID, GlobalConst.BaseSiteUrl);
                ret.ServerBattleID = sb.SpringBattleID;

                server.GhostSay(
                    new Say()
                {
                    Text =
                        string.Format("BATTLE DETAILS AND REPLAY ----> {0} <-----", ret.Url),
                    IsEmote = true,
                    Place   = SayPlace.Battle,
                    User    = GlobalConst.NightwatchName
                },
                    result.LobbyStartContext.BattleID);


                foreach (var p in sb.SpringBattlePlayers.Where(x => !x.IsSpectator))
                {
                    ret.DebriefingUsers[p.Account.Name] = new BattleDebriefing.DebriefingUser()
                    {
                        LoseTime        = p.LoseTime,
                        AllyNumber      = p.AllyNumber,
                        IsInVictoryTeam = p.IsInVictoryTeam,
                        EloChange       = p.EloChange?.ToString("F2"),
                        XpChange        = p.XpChange,
                        IsLevelUp       = orgLevels[p.AccountID] < p.Account.Level,
                        Awards          = sb.AccountBattleAwards.Where(x => x.AccountID == p.AccountID).Select(x => new BattleDebriefing.DebriefingAward()
                        {
                            Value       = x.Value,
                            Key         = x.AwardKey,
                            Description = x.AwardDescription
                        }).ToList()
                    };
                }

                return(ret);
            }
            catch (Exception ex)
            {
                var data = JsonConvert.SerializeObject(result);
                Trace.TraceError($"{ex}\nData:\n{data}");
                ret.Message = "Error processing game result: " + ex.Message;
                return(ret);
            }
        }