Ejemplo n.º 1
0
 public void CheckForSkip()
 {
     if (HasSkipped || Server.ValidPlayers.Count == 0)
     {
         return;
     }
     if (SkipVotes.Count >= NeededVotesToSkipLevel)
     {
         HasSkipped = true;
         autoServer.AdvanceLevel();
         foreach (var player in Server.ValidPlayers)
         {
             if (player.Car != null && !player.Car.Finished)
             {
                 player.Car.BroadcastDNF();
             }
         }
         if (Server.ModeStartTime + BadVoterDetectionCutoff > DistanceServerMain.NetworkTime)
         {
             // punishment end times are used to determine name/guid/ip pairs and delete all at once when the punishment time is over
             // we use uniqueOffset to force these times to be unique in all cases
             // this is *not* the proper way to do this but it works for this case and is simple.
             double uniqueOffset = 0.0;
             foreach (var voterInfo in LastMapVoters)
             {
                 SetBadVoterUntil(voterInfo, DistanceServerMain.UnixTime + BadVoterPunishmentTime + uniqueOffset);
                 uniqueOffset += 0.01;
             }
         }
         Server.SayChat(DistanceChat.Server("VoteCommands:SkipSuccess", $"Votes to skip the level have passed {(int)(SkipThreshold*100)}%. Skipping the level in 10 seconds."));
     }
 }
Ejemplo n.º 2
0
        internal void RespondLink()
        {
            var writer = new JsonFx.Json.JsonWriter();

            if (!IsPrivateMode)
            {
                Context.Response.StatusCode        = 401;
                Context.Response.StatusDescription = "Unauthorized";
                Response = writer.Write(new
                {
                    ErrorCode = 401,
                });
                return;
            }
            var    reader = new JsonFx.Json.JsonReader();
            var    data   = (Dictionary <string, object>)reader.Read(Body);
            string guid   = (string)data["Guid"];

            Plugin.Links[SessionId] = guid;
            Response = writer.Write(new
            {
                Success = true,
            });

            if (data.ContainsKey("SendIpWarning") && (bool)data["SendIpWarning"])
            {
                var player = Plugin.Manager.Server.GetDistancePlayer(guid);
                if (player != null)
                {
                    Plugin.Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("HttpServer:Link", "Your game session has been automatically linked with a web session. You can now vote and chat from the website.\nIf this wasn't you, type [00FFFF]/unlink[-]"));
                }
            }
        }
Ejemplo n.º 3
0
 public void Update()
 {
     if (ServerStage == Stage.Started && UnityEngine.Network.time >= LevelEndTime - 60.0)
     {
         ServerStage = Stage.Timeout;
         var timeLeft = LevelEndTime - UnityEngine.Network.time;
         setCountdownTime(Server.ModeTime + timeLeft);
         Server.SayChat(DistanceChat.Server("AutoServer:Warning:Timeout", TimeoutMessageGetter(GenerateLevelTimeoutText(LevelTimeout - 60.0))));
     }
     else if (ServerStage == Stage.Started && IdleTimeout > 0)
     {
         foreach (var player in Server.ValidPlayers)
         {
             if (player.Car != null && player.Car.CarDirectives != null && !player.Car.Finished)
             {
                 var lastMoveData = player.Car.GetExternalData <LastMoveTimeData>();
                 if (!player.Car.CarDirectives.IsZero())
                 {
                     lastMoveData.LastMoveTime = DistanceServerMain.UnixTime;
                 }
                 else if (DistanceServerMain.UnixTime - lastMoveData.LastMoveTime >= IdleTimeout)
                 {
                     lastMoveData.LastMoveTime = DistanceServerMain.UnixTime;
                     player.Car.BroadcastDNF();
                     Server.SayChat(DistanceChat.Server("AutoServer:IdleSpectate", $"{player.Name} has been set to spectate mode for being idle"));
                 }
             }
         }
     }
     else if (ServerStage == Stage.Timeout && UnityEngine.Network.time >= LevelEndTime)
     {
         NextLevel();
     }
     else if (ServerStage == Stage.AllFinished && UnityEngine.Network.time - allFinishedAt >= 10)
     {
         NextLevel();
     }
     if (ReportToMasterServer)
     {
         if (!masterServerDeregistered)
         {
             if (DistanceServerMain.UnixTime - lastMasterServerRegistration > MasterServerReRegisterFrequency)
             {
                 Log.Debug("Deregistering from master server...");
                 masterServerDeregistered    = true;
                 Server.ReportToMasterServer = false;
             }
         }
         else if (DistanceServerMain.UnixTime - lastMasterServerRegistration > MasterServerReRegisterFrequency + 10.0)
         {
             Log.Debug("Re-reregistering to master server...");
             Server.ReportToMasterServer  = true;
             masterServerDeregistered     = false;
             lastMasterServerRegistration = DistanceServerMain.UnixTime;
         }
     }
 }
Ejemplo n.º 4
0
        public System.Collections.IEnumerator GiveTagBubbleSoon(DistancePlayer fromPlayer, DistancePlayer player)
        {
            yield return(new UnityEngine.WaitForSeconds(1));

            if (IsPlayerInMode(player) && TaggedPlayer == fromPlayer)
            {
                fromPlayer.GetExternalData <ReverseTagData>().SecondsTagged = 0.0;
                TagPlayer(player);
            }
            fromPlayer.SayLocalChat(DistanceChat.Server("ReverseTag:SinglePlayerTransition", "Transitioning out of single-player: your timer has been reset and your tag bubble taken!"));
        }
Ejemplo n.º 5
0
 public override void Start()
 {
     Log.Info("Vanilla Functionality Plugin started!");
     Server.OnPlayerValidatedEvent.Connect(player =>
     {
         Server.AddChat(DistanceChat.Server("Vanilla:PlayerJoined", $"[FFE999]{player.Name} has joined the server!"));
     });
     Server.OnPlayerDisconnectedEvent.Connect(player =>
     {
         Server.AddChat(DistanceChat.Server("Vanilla:PlayerLeft", $"[FFE999]{player.Name} has left the game[-]"));
         // TODO: implement timeout message
     });
 }
Ejemplo n.º 6
0
 public void Update()
 {
     if (Plugin.ServerStage == BasicAutoServer.Stage.Started && !TimeoutStarted && UnityEngine.Network.time >= LevelEndTime - 60.0)
     {
         TimeoutStarted = true;
         var timeLeft = LevelEndTime - UnityEngine.Network.time;
         Plugin.SetCountdownTime(Plugin.Server.ModeTime + timeLeft);
         Plugin.Server.SayChat(DistanceChat.Server("AutoServer:Warning:Timeout", Plugin.TimeoutMessageGetter(Plugin.GenerateLevelTimeoutText(Plugin.LevelTimeout - 60.0))));
     }
     if (Plugin.ServerStage == BasicAutoServer.Stage.Started && UnityEngine.Network.time >= LevelEndTime)
     {
         Plugin.FinishAllPlayersAndAdvanceLevel();
     }
 }
Ejemplo n.º 7
0
 System.Collections.IEnumerator DoLoadWorkshopLevels()
 {
     // Start players on campaign maps while the workshop maps load
     StartAutoServer();
     NextLevel();
     Server.SayChat(DistanceChat.Server("AutoServer:GeneratingPlaylist", "Generating playlist from Steam Workshop..."));
     Server.OnLevelStartInitiatedEvent.Connect(() =>
     {
         if (currentLevelIndex == Playlist.Count - 1)
         {
             // Update the playlist on the last level
             DistanceServerMainStarter.Instance.StartCoroutine(UpdatePlaylist());
         }
     });
     // Load maps, but don't wait on them to finish -- it might error!
     DistanceServerMainStarter.Instance.StartCoroutine(UpdatePlaylist());
     yield break;
 }
Ejemplo n.º 8
0
        void StartAutoServer()
        {
            Server.CurrentLevel = Playlist[0];
            StartLevel();

            Server.OnUpdateEvent.Connect(Update);
            Server.OnPlayerDisconnectedEvent.Connect(player =>
            {
                if (Server.ValidPlayers.Count == 0 && AdvanceWhenAllPlayersFinish) // && Server.HasModeStarted)
                {
                    Server.SayChat(DistanceChat.Server("AutoServer:Advancing:Empty", "All players have left. Advancing level."));
                    AdvanceLevel();
                }
                else
                {
                    AttemptToAdvanceLevel();
                }
            });
        }
Ejemplo n.º 9
0
 public void CheckForExtend()
 {
     if (Server.ValidPlayers.Count == 0 || autoServer.SprintMode == null)
     {
         return;
     }
     if (ExtendVotes.Count >= NeededVotesToExtendLevel)
     {
         var success = autoServer.SprintMode.ExtendTimeout(ExtendTime);
         if (success)
         {
             Server.SayChat(DistanceChat.Server("VoteCommands:ExtendSuccess", $"Votes to extend the level have passed {(int)(ExtendThreshold * 100)}%. Extending the level by {GetExtendTimeText()}"));
         }
         else
         {
             DelayedExtensions++;
         }
         ExtendVotes.Clear();
     }
 }
Ejemplo n.º 10
0
        void AttemptToAdvanceLevel()
        {
            if (Server.StartingLevel || !Server.HasModeStarted)
            {
                Log.Debug($"Mode not started, not advancing normally.");
                return;
            }
            if (Server.ValidPlayers.Count == 0)
            {
                Log.Debug($"No players, not advancing normally.");
                return;
            }
            var canAdvance = AdvanceWhenAllPlayersFinish;

            foreach (var player in Server.ValidPlayers)
            {
                if ((player.Car != null && !player.Car.Finished) || DistanceServerMain.UnixTime - player.RestartTime < 30)
                {
                    canAdvance = false;
                    break;
                }
            }
            if (canAdvance)
            {
                Log.Debug($"Advancing because all players with cars have finished.");
                Server.SayChat(DistanceChat.Server("AutoServer:Advancing:Finished", "All players finished. Advancing to the next level in 10 seconds."));
                AdvanceLevel();
            }
            else if (AdvanceWhenStartingPlayersFinish)
            {
                if (ServerStage == Stage.Started && GetUnfinishedStartingPlayersCount() == 0)
                {
                    LevelEndTime = DistanceServerMain.NetworkTime + 60.0;
                    ServerStage  = Stage.Timeout;
                    setCountdownTime(Server.ModeTime + 60.0);
                    Server.SayChat(DistanceChat.Server("AutoServer:Warning:InitialFinished", StartingPlayersFinishedMessageGetter()));
                }
            }
        }
Ejemplo n.º 11
0
        public void OnCheckIfLevelCanAdvance(Cancellable canceller)
        {
            if (canceller.IsCancelled)
            {
                return;
            }

            var canAdvance = Plugin.AdvanceWhenAllPlayersFinish;

            foreach (var player in Plugin.Server.ValidPlayers)
            {
                if ((player.Car != null && !player.Car.Finished) || DistanceServerMain.UnixTime - player.RestartTime < 30)
                {
                    canAdvance = false;
                    break;
                }
            }

            if (canAdvance)
            {
                Log.Debug($"Advancing because all players with cars have finished.");
                Plugin.Server.SayChat(DistanceChat.Server("AutoServer:Advancing:Finished", "All players finished. Advancing to the next level in 10 seconds."));
                return;
            }

            canceller.Cancel();

            if (Plugin.AdvanceWhenStartingPlayersFinish)
            {
                if (!TimeoutStarted && Plugin.GetUnfinishedStartingPlayersCount() == 0)
                {
                    LevelEndTime   = DistanceServerMain.NetworkTime + 60.0;
                    TimeoutStarted = true;
                    Plugin.SetCountdownTime(Plugin.Server.ModeTime + 60.0);
                    Plugin.Server.SayChat(DistanceChat.Server("AutoServer:Warning:InitialFinished", Plugin.StartingPlayersFinishedMessageGetter()));
                }
            }
        }
Ejemplo n.º 12
0
        public System.Collections.IEnumerator SearchForLevels(DistancePlayer searcher, string searchText, bool isVote, bool isAgainst)
        {
            var    autoServer = Manager.GetPlugin <BasicAutoServer.BasicAutoServer>();
            var    byMatch    = Regex.Match(searchText, @"by (.*)$");
            string onlyBy     = null;

            if (byMatch.Success)
            {
                searchText = Regex.Replace(searchText, @"\s*by (.*)$", "");
                onlyBy     = byMatch.Groups[1].ToString();
            }
            var searches = new List <WorkshopSearch.DistanceSearchRetriever>();

            if (RequiredTags.Count == 0)
            {
                searches.Add(new WorkshopSearch.DistanceSearchRetriever(new WorkshopSearch.DistanceSearchParameters()
                {
                    Search = new WorkshopSearch.WorkshopSearchParameters()
                    {
                        AppId        = WorkshopSearch.Workshop.DistanceAppId,
                        SearchText   = searchText,
                        SearchType   = WorkshopSearch.WorkshopSearchParameters.SearchTypeType.GameFiles,
                        Sort         = WorkshopSearch.WorkshopSearchParameters.SortType.Relevance,
                        Days         = -1,
                        NumPerPage   = 30,
                        Page         = 1,
                        RequiredTags = new string[] { },
                    },
                    GameMode            = autoServer.GameMode,
                    MaxSearch           = 5 * 30,
                    MaxResults          = 3,
                    DistanceLevelFilter = (levels) =>
                    {
                        if (onlyBy != null)
                        {
                            levels.RemoveAll(level =>
                            {
                                return(!level.WorkshopItemResult.AuthorName.ToLower().Contains(onlyBy.ToLower()));
                            });
                        }
                        return(autoServer.FilterWorkshopLevels(levels));
                    }
                }));
            }
            else
            {
                foreach (var tag in RequiredTags)
                {
                    searches.Add(new WorkshopSearch.DistanceSearchRetriever(new WorkshopSearch.DistanceSearchParameters()
                    {
                        Search = new WorkshopSearch.WorkshopSearchParameters()
                        {
                            AppId        = WorkshopSearch.Workshop.DistanceAppId,
                            SearchText   = searchText,
                            SearchType   = WorkshopSearch.WorkshopSearchParameters.SearchTypeType.GameFiles,
                            Sort         = WorkshopSearch.WorkshopSearchParameters.SortType.Relevance,
                            Days         = -1,
                            NumPerPage   = 30,
                            Page         = 1,
                            RequiredTags = new string[] { tag },
                        },
                        GameMode            = autoServer.GameMode,
                        MaxSearch           = 5 * 30,
                        MaxResults          = 3,
                        DistanceLevelFilter = (levels) =>
                        {
                            if (onlyBy != null)
                            {
                                levels.RemoveAll(level =>
                                {
                                    return(!level.WorkshopItemResult.AuthorName.ToLower().Contains(onlyBy.ToLower()));
                                });
                            }
                            return(autoServer.FilterWorkshopLevels(levels));
                        }
                    }));
                }
            }

            var items = new List <WorkshopSearch.DistanceSearchResultItem>();

            foreach (var search in searches)
            {
                yield return(search.TaskCoroutine);

                if (search.HasError)
                {
                    Server.SayLocalChat(searcher.UnityPlayer, DistanceChat.Server("VoteCommands:Feedback:SearchError", $"Error when searching for \"{searchText}\""));
                    Log.Error($"Error when searching for \"{searchText}\": {search.Error}");
                    yield break;
                }
                items.AddRange(search.Results);
            }

            if (items.Count == 0)
            {
                Server.SayLocalChat(searcher.UnityPlayer, DistanceChat.Server("VoteCommands:Feedback:SearchResult", $"No levels found for \"{searchText}\"" + (onlyBy != null ? $" by \"{onlyBy}\"" : "")));
                yield break;
            }
            if (!isVote)
            {
                var result = $"Levels for \"{searchText}\":";
                for (int i = 0; i < Math.Min(3, items.Count); i++)
                {
                    var item = items[i].WorkshopItemResult;
                    result += $"\n[00FF00]{item.ItemName}[-] by {item.AuthorName}" + (item.Rating == -1 ? "" : $" {item.Rating}/5");
                }
                Server.SayLocalChat(searcher.UnityPlayer, DistanceChat.Server("VoteCommands:Feedback:SearchResult", result));
                yield break;
            }
            else
            {
                var result = items[0].DistanceLevelResult;
                if (!isAgainst)
                {
                    var data = new FilterLevelRealtimeEventData(result);
                    OnFilterLevelRealtime.Fire(data);
                    if (data.HardBlocklist)
                    {
                        Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:VoteHardBlocked", $"The level [00FF00]{result.Name}[-] by {items[0].WorkshopItemResult.AuthorName} is blocked because {data.Reason}."));
                        yield break;
                    }
                    PlayerVotes[searcher.UnityPlayerGuid]     = result;
                    PlayerVoteTimes[searcher.UnityPlayerGuid] = DistanceServerMain.UnixTime;
                    Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:VoteSuccess", $"Set {searcher.Name}'s vote to [00FF00]{result.Name}[-] by {items[0].WorkshopItemResult.AuthorName}"));
                    if (data.SoftBlocklist)
                    {
                        int count = PlayerVotes.Sum(pair =>
                        {
                            if (pair.Value.RelativeLevelPath != result.RelativeLevelPath || Server.GetDistancePlayer(pair.Key) == null)
                            {
                                return(0);
                            }
                            return(1);
                        });
                        int sub = 0;
                        if (AgainstVotes.ContainsKey(result.RelativeLevelPath))
                        {
                            sub = AgainstVotes[result.RelativeLevelPath].Sum(playerGuid => Server.GetDistancePlayer(playerGuid) != null ? 1 : 0);
                        }
                        count = count - sub;
                        int needed = NeededVotesToExtendLevel - count;
                        if (needed > 0)
                        {
                            Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:VoteSoftBlocked", $"The level [00FF00]{result.Name}[-] is soft-blocked and needs {needed} more votes to be played because {data.Reason}."));
                        }
                        else
                        {
                            Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:VoteSuccessSoftBlock", $"The level [00FF00]{result.Name}[-] is soft-blocked but has met its required vote count and can now be played."));
                        }
                    }
                }
                else
                {
                    var key = result.RelativeLevelPath;
                    if (!AgainstVotes.ContainsKey(key))
                    {
                        AgainstVotes[key] = new HashSet <string>();
                    }
                    if (AgainstVotes[key].Contains(searcher.UnityPlayerGuid))
                    {
                        AgainstVotes[key].Remove(searcher.UnityPlayerGuid);
                        if (AgainstVotes[key].Count == 0)
                        {
                            AgainstVotes.Remove(key);
                        }
                        Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:AgainstVoteRemoved", $"Cleared {searcher.Name}'s vote against [00FF00]{result.Name}[-] by {items[0].WorkshopItemResult.AuthorName}"));
                    }
                    else
                    {
                        AgainstVotes[key].Add(searcher.UnityPlayerGuid);
                        Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:AgainstVoteAdded", $"Set {searcher.Name}'s vote against [00FF00]{result.Name}[-] by {items[0].WorkshopItemResult.AuthorName}"));
                    }
                }
                yield break;
            }
        }
Ejemplo n.º 13
0
        public override void Start()
        {
            Log.Info("Basic Auto Server started!");
            Playlist.AddRange(OfficialPlaylist);
            Filters = new List <FilterWorkshopSearchDelegate>();
            ReadSettings();

            UnityEngine.MasterServer.dedicatedServer = ReportToMasterServerAsDedicatedServer;

            Server.MasterServerGameModeOverride = MasterServerGameModeOverride;
            Server.ServerName = ServerName;
            Server.MaxPlayers = MaxPlayers;
            Server.Port       = Port;
            Server.UseNat     = UseNat;
            if (ReportToMasterServerInitialDelay > 0)
            {
                Server.ReportToMasterServer = false;
                DistanceServerMainStarter.Instance.StartCoroutine(SetReportToMasterServerAfterDelay());
            }
            else
            {
                Server.ReportToMasterServer = ReportToMasterServer;
            }
            lastMasterServerRegistration = DistanceServerMain.UnixTime;
            if (PrivateServerPassword != null)
            {
                UnityEngine.Network.incomingPassword = PrivateServerPassword;
            }

            Server.OnPlayerConnectedEvent.Connect(player =>
            {
                player.Countdown = CountdownTime;
                player.OnCarAddedEvent.Connect(car =>
                {
                    car.AddExternalData(new LastMoveTimeData(DistanceServerMain.UnixTime));
                    OnCarAddedEvent.Fire(car);
                });
            });

            if (WelcomeMessage != null)
            {
                Server.OnPlayerValidatedEvent.Connect(player =>
                {
                    var message = WelcomeMessage;
                    if (message.Contains("$linkcode") && LinkCodeGetter != null)
                    {
                        message = message.Replace("$linkcode", LinkCodeGetter(player));
                    }
                    message = message.Replace("$player", player.Name);
                    if (!Server.HasModeStarted || player.Car != null)
                    {
                        Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("AutoServer:Welcome", message));
                    }
                    else
                    {
                        IEventConnection connection = null;
                        connection = player.OnCarAddedEvent.Connect(car =>
                        {
                            Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("AutoServer:Welcome", message));
                            connection.Disconnect();
                        });
                    }
                });
            }

            Server.OnLevelStartedEvent.Connect(() =>
            {
                CountdownTime = -1.0;
            });

            Server.OnModeStartedEvent.Connect(() =>
            {
                StartingPlayerGuids.Clear();
                foreach (var player in Server.ValidPlayers)
                {
                    if (player.Car != null)
                    {
                        player.Car.GetExternalData <LastMoveTimeData>().LastMoveTime = DistanceServerMain.UnixTime;
                        SetStartingPlayer(player.UnityPlayerGuid, true);
                    }
                }
                if (ServerStage == Stage.Starting)
                {
                    ServerStage = Stage.Started;
                }
            });

            OnAdvancingToNextLevel.Connect(() =>
            {
                if (TipMessages.Count > 0)
                {
                    var tip    = TipMessages[currentTip];
                    currentTip = (currentTip + 1) % TipMessages.Count;
                    Server.SayChat(DistanceChat.Server("AutoServer:Tip", tip));
                }
            });

            DistanceServerMain.GetEvent <Events.Instanced.Finished>().Connect((instance, data) =>
            {
                Log.WriteLine($"{((DistanceCar)instance).Player.Name} finished");
                AttemptToAdvanceLevel();
            });

            if (LoadWorkshopLevels)
            {
                DistanceServerMainStarter.Instance.StartCoroutine(DoLoadWorkshopLevels());
            }
            else
            {
                StartAutoServer();
            }
        }
Ejemplo n.º 14
0
        System.Collections.IEnumerator UpdatePlaylist()
        {
            HashSet <string> foundLevels = new HashSet <string>();
            List <DistanceSearchRetriever> levelRetrievers = new List <DistanceSearchRetriever>();
            var filePath = new System.IO.FileInfo(Manager.ServerDirectory.FullName + "/BasicAutoServer.json");

            if (filePath.Exists)
            {
                try
                {
                    var txt        = System.IO.File.ReadAllText(filePath.FullName);
                    var reader     = new JsonFx.Json.JsonReader();
                    var dictionary = (Dictionary <string, object>)reader.Read(txt);
                    if (dictionary.ContainsKey("Workshop"))
                    {
                        Log.Info("Using workshop level info stored in BasicAutoServer.json");
                        var levelSettings = (object[])dictionary["Workshop"];
                        foreach (var settingsObject in levelSettings)
                        {
                            var settings = (Dictionary <string, object>)settingsObject;
                            var search   = (string)settings["Search"];
                            var sort     = (WorkshopSearchParameters.SortType)Enum.Parse(typeof(WorkshopSearchParameters.SortType), (string)settings["Sort"]);
                            var days     = (int)settings["Days"];
                            var tagsBase = (object[])settings["Tags"];
                            var tagsList = new List <string>()
                            {
                            };
                            foreach (object tagBase in tagsBase)
                            {
                                tagsList.Add((string)tagBase);
                            }
                            var count        = (int)settings["Count"];
                            var searchParams = WorkshopSearchParameters.GameFiles(
                                searchText: search,
                                appId: Workshop.DistanceAppId,
                                sort: sort,
                                days: days,
                                requiredTags: tagsList.ToArray(),
                                numPerPage: 30
                                );
                            var retriever = new DistanceSearchRetriever(new DistanceSearchParameters()
                            {
                                Search = searchParams,
                                DistanceLevelFilter = (levels) =>
                                {
                                    levels.RemoveAll(level =>
                                    {
                                        return(foundLevels.Contains(level.DistanceLevelResult.RelativeLevelPath));
                                    });
                                    foreach (var level in levels)
                                    {
                                        foundLevels.Add(level.DistanceLevelResult.RelativeLevelPath);
                                    }
                                    var res = FilterWorkshopLevels(levels);
                                    return(res);
                                },
                                MaxSearch = count,
                                GameMode  = GameMode
                            }, false);
                            levelRetrievers.Add(retriever);
                        }
                    }
                }
                catch (Exception e)
                {
                    Log.Error($"Error retrieving workshop level settings:\n{e}");
                }
            }
            if (levelRetrievers == null || levelRetrievers.Count == 0)
            {
                Log.Error("No workshop levels defined. Using default: official levels");
                Playlist = OfficialPlaylist;
                yield break;
            }

            var levelListIndex = 0;

            foreach (var retriever in levelRetrievers)
            {
                retriever.StartCoroutine();
                yield return(retriever.TaskCoroutine);

                if (retriever.HasError)
                {
                    Log.Error($"Error retrieving levels: {retriever.Error}");
                }
                foreach (var levelInfo in retriever.Results)
                {
                    levelInfo.DistanceLevelResult.AddExternalData(new ListIndexExternalData(levelListIndex));
                }
                levelListIndex++;
            }

            Log.Info($"Level Retrievers: {levelRetrievers.Count}");

            var levelResults = levelRetrievers.ConvertAll(retriever => retriever.Results.ConvertAll(result => result.DistanceLevelResult));
            var results      = Combine(levelResults.ToArray());

            var listStr = $"Levels ({results.Count}):";

            foreach (var level in results)
            {
                listStr += $"\n{level.GetExternalData<ListIndexExternalData>().listIndex} {level.Name}";
            }
            Log.Info(listStr);

            if (results.Count == 0)
            {
                Log.Error("Workshop search returned nothing, using default: official levels");
                Playlist = OfficialPlaylist;
                hasLoadedWorkshopLevels = false;
                yield break;
            }

            Playlist          = results;
            currentLevelIndex = -1;

            if (!hasLoadedWorkshopLevels)
            {
                hasLoadedWorkshopLevels = true;
                Server.SayChat(DistanceChat.Server("BasicAutoServer:LoadedLevels", "Workshop playlist generated. Skipping to workshop playlist..."));
                FinishAllPlayersAndAdvanceLevel();
            }
        }
Ejemplo n.º 15
0
        void ProcessChatMessage(DistanceChat data)
        {
            Log.DebugLine("VC PC", 0);
            if (data.SenderGuid == "server")
            {
                Log.DebugLine("VC PC", 1);
                return;
            }
            Log.DebugLine("VC PC", 2);
            var player = Server.GetDistancePlayer(data.SenderGuid);

            if (player == null)
            {
                Log.DebugLine("VC PC", 3);
                return;
            }
            Log.DebugLine("VC PC", 4);

            var isMuted = false;

            if (TempMuted.ContainsKey(data.SenderGuid))
            {
                var mutedUntil = TempMuted[data.SenderGuid];
                if (mutedUntil > DistanceServerMain.UnixTime)
                {
                    isMuted = true;
                }
                else
                {
                    TempMuted.Remove(data.SenderGuid);
                }
            }

            var playerMatch = Regex.Match(data.Message, @"^\[[0-9A-F]{6}\](.+)\[FFFFFF\]: (.*)$");
            var message     = playerMatch.Groups[2].ToString();

            Match match;

            match = Regex.Match(message, @"^/help$");
            if (match.Success)
            {
                Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Help:Simple", "[00FFFF]/vote /skip /extend /restart /not /clear[-]"));
            }

            match = Regex.Match(message, @"^/skip$");
            if (match.Success && SkipThreshold < 100 && SkipThreshold != -1)
            {
                if (isMuted)
                {
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Muted", "You are not allowed to vote while muted"));
                }
                else if (!SkipVotes.Contains(player.UnityPlayerGuid))
                {
                    SkipVotes.Add(player.UnityPlayerGuid);
                    Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:SkipVoteAdded", $"Added your vote to skip the level {SkipVotes.Count}/{NeededVotesToSkipLevel}"));
                    CheckForSkip();
                }
                else
                {
                    SkipVotes.Remove(player.UnityPlayerGuid);
                    Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:SkipVoteRemoved", $"Removed your vote to skip the level {SkipVotes.Count}/{NeededVotesToSkipLevel}"));
                }
                return;
            }

            match = Regex.Match(message, @"^/extend$");
            if (match.Success && SkipThreshold < 100 && SkipThreshold != -1)
            {
                if (autoServer.SprintMode == null)
                {
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:BadGameMode", $"You can only extend in Sprint."));
                    return;
                }

                if (isMuted)
                {
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Muted", "You are not allowed to vote while muted"));
                }
                else if (!ExtendVotes.Contains(player.UnityPlayerGuid))
                {
                    ExtendVotes.Add(player.UnityPlayerGuid);
                    Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:ExtendVoteRemoved", $"Added your vote to extend the level {ExtendVotes.Count}/{NeededVotesToExtendLevel}"));
                    CheckForExtend();
                }
                else
                {
                    ExtendVotes.Remove(player.UnityPlayerGuid);
                    Server.SayChat(DistanceChat.Server("VoteCommands:Feedback:ExtendVoteRemoved", $"Removed your vote to extend the level {ExtendVotes.Count}/{NeededVotesToExtendLevel}"));
                }
                return;
            }

            match = Regex.Match(message, @"^/restart$");
            if (match.Success)
            {
                if (autoServer.SprintMode == null)
                {
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:BadGameMode", $"You can only restart in Sprint."));
                    return;
                }

                var restartOkay = false;

                if (autoServer.ServerStage == BasicAutoServer.BasicAutoServer.Stage.Started)
                {
                    if (!autoServer.SprintMode.TimeoutStarted || autoServer.SprintMode.LevelEndTime - DistanceServerMain.NetworkTime > 30)
                    {
                        restartOkay = true;
                    }
                }

                if (player.State != DistancePlayer.PlayerState.StartedMode || player.Car == null)
                {
                    restartOkay = false;
                }

                if (Server.IsInLobby)
                {
                    restartOkay = false;
                }

                if (!restartOkay)
                {
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:NoRestart", $"You cannot restart right now"));
                    return;
                }
                Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Restart", $"Restarting the level, just for you..."));
                DistanceServerMainStarter.Instance.StartCoroutine(RestartPlayerAfter(player, 2));
                return;
            }

            var    isVote          = true;
            var    isAgainst       = false;
            string levelSearchName = null;

            match = Regex.Match(message, @"^/vote (.*)$");
            if (!match.Success)
            {
                match     = Regex.Match(message, @"^/not (.*)$");
                isAgainst = true;
            }
            if (!match.Success)
            {
                match  = Regex.Match(message, @"^/search (.*)$");
                isVote = false;
            }
            if (match.Success)
            {
                levelSearchName = match.Groups[1].ToString();
            }
            else
            {
                if (Regex.Match(message, @"^/vote$").Success || Regex.Match(message, @"^/search$").Success || Regex.Match(message, @"^/not$").Success)
                {
                    if (PlayerVotes.ContainsKey(player.UnityPlayerGuid))
                    {
                        Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Help:CurrentVote", $"Your current vote is for [00FF00]{PlayerVotes[player.UnityPlayerGuid].Name}[-]"));
                    }
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Help:Detailed", "[00FFFF]/search name[-] to search\n[00FFFF]/clear[-] to clear your vote\n[00FFFF]/vote name[-] to vote for a level\n[00FFFF]/not name[-] to vote against a level\n[00FFFF]/skip[-] to vote to skip the level"));
                }
                else if (Regex.Match(message, @"^/clear$").Success)
                {
                    string levelName = "";
                    if (PlayerVotes.ContainsKey(player.UnityPlayerGuid))
                    {
                        levelName = $" for [00FF00]{PlayerVotes[player.UnityPlayerGuid].Name}[-]";
                    }
                    PlayerVotes.Remove(player.UnityPlayerGuid);
                    PlayerVoteTimes.Remove(player.UnityPlayerGuid);
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Feedback:LevelVoteAdded", $"Removed your vote" + levelName));
                }
                return;
            }

            if (isMuted && isVote)
            {
                Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("VoteCommands:Muted", "You are not allowed to vote while muted"));
                return;
            }

            DistanceServerMainStarter.Instance.StartCoroutine(SearchForLevels(player, levelSearchName, isVote, isAgainst));
        }
Ejemplo n.º 16
0
        void OnAdvancingToNextLevel()
        {
            LastMapVoters.Clear();
            var levelLookup = new Dictionary <string, DistanceLevel>();
            var validVotes  = new Dictionary <string, int>();

            foreach (var vote in PlayerVotes)
            {
                var distancePlayer = Server.GetDistancePlayer(vote.Key);
                if (distancePlayer == null)
                {
                    if (!LeftAt.ContainsKey(vote.Key) || DistanceServerMain.UnixTime - LeftAt[vote.Key] > 5 * 60)
                    {
                        PlayerVotes.Remove(vote.Key);
                        PlayerVoteTimes.Remove(vote.Key);
                        LeftAt.Remove(vote.Key);
                    }
                }
                else
                {
                    var isBadVoter = IsBadVoter(distancePlayer, DistanceServerMain.UnixTime);
                    var isLateVote = PlayerVoteTimes[vote.Key] > DistanceServerMain.UnixTime - VoteNotSafetyTime;
                    if (isBadVoter && isLateVote)
                    {
                        Server.SayLocalChat(distancePlayer.UnityPlayer, DistanceChat.Server("VoteCommands:LateVote", "Your vote has been skipped this round because you voted late!\nYour vote will apply on the next round."));
                    }
                    else
                    {
                        int count = 0;
                        validVotes.TryGetValue(vote.Value.RelativeLevelPath, out count);
                        validVotes[vote.Value.RelativeLevelPath] = count + 1;
                        if (!levelLookup.ContainsKey(vote.Value.RelativeLevelPath))
                        {
                            levelLookup[vote.Value.RelativeLevelPath] = vote.Value;
                        }
                    }
                }
            }

            foreach (var pair in RecentMaps.ToArray())
            {
                if (Server.CurrentLevelId > pair.Value)
                {
                    RecentMaps.Remove(pair.Key);
                }
            }

            var votesSum = 0;

            foreach (var vote in validVotes.ToArray())
            {
                var data = new FilterLevelRealtimeEventData(levelLookup[vote.Key]);
                OnFilterLevelRealtime.Fire(data);
                if (data.HardBlocklist || (data.SoftBlocklist && vote.Value < NeededVotesToOverrideSoftBlocklist))
                {
                    validVotes.Remove(vote.Key);
                }
                else
                {
                    var value = vote.Value;
                    if (AgainstVotes.ContainsKey(vote.Key))
                    {
                        var count = 0;
                        foreach (var guid in AgainstVotes[vote.Key])
                        {
                            if (Server.GetDistancePlayer(guid) != null)
                            {
                                count++;
                                value--;
                            }
                        }
                    }
                    if (value <= 0)
                    {
                        validVotes.Remove(vote.Key);
                    }
                    else
                    {
                        validVotes[vote.Key] = value;
                        votesSum            += value;
                    }
                }
            }

            foreach (var pair in LeftAt.ToArray())
            {
                if (DistanceServerMain.UnixTime - pair.Value > 5 * 60)
                {
                    LeftAt.Remove(pair.Key);
                    foreach (var votePair in AgainstVotes.ToArray())
                    {
                        votePair.Value.Remove(pair.Key);
                        if (votePair.Value.Count == 0)
                        {
                            AgainstVotes.Remove(votePair.Key);
                        }
                    }
                }
            }

            if (validVotes.Count == 0)
            {
                return;
            }
            var           choiceInt = new Random().Next(votesSum);
            var           choiceSum = 0;
            DistanceLevel level     = null;

            foreach (var pair in validVotes)
            {
                choiceSum += pair.Value;
                if (choiceInt < choiceSum)
                {
                    level = levelLookup[pair.Key];
                    break;
                }
            }
            if (level == null)
            {
                return;
            }
            autoServer.SetNextLevel(level);
            var    voteCount   = 0;
            string firstPlayer = null;

            foreach (var vote in PlayerVotes.ToArray())
            {
                var distancePlayer = Server.GetDistancePlayer(vote.Key);
                if (vote.Value.RelativeLevelPath == level.RelativeLevelPath && distancePlayer != null)
                {
                    voteCount++;
                    PlayerVotes.Remove(vote.Key);
                    PlayerVoteTimes.Remove(vote.Key);
                    LastMapVoters.Add(new MapVoterInfo(distancePlayer));
                    if (firstPlayer == null)
                    {
                        firstPlayer = vote.Key;
                    }
                }
            }
            var          nextLevelId = Server.CurrentLevelId + 1;
            EventCleaner conns       = new EventCleaner();

            conns.Add(
                Server.OnModeStartedEvent.Connect(() =>
            {
                var chat = DistanceChat.Server("VoteCommands:ChosenLevel", $"Chosen level is [00FF00]{level.Name}[-], voted for by {Server.GetDistancePlayer(firstPlayer).Name}" + (voteCount > 1 ? $" and {voteCount - 1} others" : ""));
                Server.SayChat(chat);
                conns.Clean();
            }),
                Server.OnLevelStartInitiatedEvent.Connect(() =>
            {
                if (Server.CurrentLevelId != nextLevelId)
                {
                    conns.Clean();
                }
            })
                );
        }
Ejemplo n.º 17
0
        public void Update()
        {
            if (Finished)
            {
                return;
            }
            if (Plugin.ServerStage != BasicAutoServer.Stage.Started)
            {
                return;
            }

            var playersInMode = GetModePlayers();

            if (playersInMode.Count <= 1)
            {
                IsInSinglePlayerMode = true;
                MaxModeTime         += UnityEngine.Time.deltaTime;
            }
            else if (IsInSinglePlayerMode)
            {
                IsInSinglePlayerMode = false;
                // When transitioning out of single-player mode, give the bubble to the new player to reset the timer:
                if (TaggedPlayer != null)
                {
                    var nonTagged = playersInMode.Find((player) => TaggedPlayer != player);
                    if (nonTagged != null)
                    {
                        TaggedPlayer.GetExternalData <ReverseTagData>().SecondsTagged = 0.0;
                        DistanceServerMainStarter.Instance.StartCoroutine(GiveTagBubbleSoon(TaggedPlayer, nonTagged));
                    }
                }
            }

            if (TaggedPlayer != null && !IsPlayerInMode(TaggedPlayer))
            {
                TagRandomLastPlacePlayer();
                if (!IsPlayerInMode(TaggedPlayer))
                {
                    TagPlayer(null);
                }
            }

            if (TaggedPlayer != null && !IsInSinglePlayerMode)
            {
                TaggedPlayer.GetExternalData <ReverseTagData>().AddSecondsTagged(UnityEngine.Time.deltaTime, WinTime);
            }

            var leader = GetFirstPlacePlayer();

            if (leader != Leader)
            {
                if (leader != null)
                {
                    var colorHex    = Distance::ColorEx.ColorToHexNGUI(Distance::ColorEx.PlayerRainbowColor(leader.Index));
                    var nameColored = $"[{colorHex}]{leader.Name}[-]";
                    var chat        = DistanceChat.Server("Vanilla:TakenTheLead", $"{nameColored} has taken the lead!");
                    chat.ChatType = DistanceChat.ChatTypeEnum.ServerVanilla;
                    Plugin.Server.SayChat(chat);
                }
                Leader = leader;
            }

            var leaderSecondsTagged = Leader == null ? 0.0 : Leader.GetExternalData <ReverseTagData>().SecondsTagged;

            if (leaderSecondsTagged >= WinTime || Plugin.Server.ModeTime >= MaxModeTime)
            {
                Finished = true;

                if (Leader != null)
                {
                    var networkFinishedData = new Distance::Events.ReverseTag.Finished.Data(Leader.Index, leaderSecondsTagged);
                    DistanceServerMain.GetEvent <Events.ServerToClient.ReverseTagFinished>().Fire(UnityEngine.RPCMode.Others, networkFinishedData);
                }

                Log.Debug($"Advancing level because win condition met: {leaderSecondsTagged} >= {WinTime} || {Plugin.Server.ModeTime} > {MaxModeTime}");
                Plugin.AdvanceLevel();
            }

            if (Finished)
            {
                return;
            }

            AdvanceLevelIfOnlyFinishedPlayers();

            var secondsLeft = Math.Max(0.0, Math.Min(WinTime - leaderSecondsTagged, MaxModeTime - Plugin.Server.ModeTime));

            if (secondsLeft <= 30 && !HasShown30SecondWarning)
            {
                HasShown30SecondWarning = true;
                var colorHex = Distance::ColorEx.ColorToHexNGUI(Distance::Colors.orangeRed);
                var chat     = DistanceChat.Server("Vanilla:TimeWarning", $"[{colorHex}]30 seconds left![-]");
                chat.ChatType = DistanceChat.ChatTypeEnum.ServerVanilla;
                Plugin.Server.SayChat(chat);
            }
            else if (secondsLeft <= 10 && !HasShown10SecondWarning)
            {
                HasShown10SecondWarning = true;
                var colorHex = Distance::ColorEx.ColorToHexNGUI(Distance::Colors.orangeRed);
                var chat     = DistanceChat.Server("Vanilla:TimeWarning", $"[{colorHex}]10 seconds left![-]");
                chat.ChatType = DistanceChat.ChatTypeEnum.ServerVanilla;
                Plugin.Server.SayChat(chat);
            }
        }
Ejemplo n.º 18
0
        void ProcessChatMessage(DistanceChat data)
        {
            if (data.SenderGuid == "server")
            {
                return;
            }
            var player = Server.GetDistancePlayer(data.SenderGuid);

            if (player == null)
            {
                return;
            }

            var isMuted = false;

            if (TempMuted.ContainsKey(data.SenderGuid))
            {
                var mutedUntil = TempMuted[data.SenderGuid];
                if (mutedUntil > DistanceServerMain.UnixTime)
                {
                    isMuted = true;
                }
                else
                {
                    TempMuted.Remove(data.SenderGuid);
                }
            }

            if (!isMuted)
            {
                var playerMatch = Regex.Match(data.Message, @"^\[[0-9A-F]{6}\](.+)\[FFFFFF\]: (.*)$");
                var message     = playerMatch.Groups[2].ToString();
                var safeMessage = Regex.Replace(ChatFilter.CharReplacement.ConvertTextToBase(message), @"[^a-zA-Z0-9 ]", "");

                var isMessageBad = false;
                foreach (var word in FilteredWords)
                {
                    if (Regex.IsMatch(message, word, RegexOptions.IgnoreCase) || Regex.IsMatch(safeMessage, word, RegexOptions.IgnoreCase))
                    {
                        isMessageBad = true;
                        LogMessage($"Player {player.UnityPlayerGuid} ({player.Name}) said a filtered word: {word} FROM {message} ({safeMessage})");
                        break;
                    }
                }
                if (!isMessageBad)
                {
                    return;
                }



                int currentLevel = -1;
                PlayerLevels.TryGetValue(player.UnityPlayerGuid, out currentLevel);
                currentLevel = Math.Min(PunishLevels.Count - 2, currentLevel);
                Log.Debug($"PunishLevels: {PunishLevels.Count}");
                if (PunishLevels.Count == 0)
                {
                    LogMessage($"  Cannot punish because the next punishment level ({currentLevel + 1}) does not exist. (max {PunishLevels.Count - 1})");
                    WriteLogIfNecessary();
                }
                else
                {
                    currentLevel = currentLevel + 1;
                    PlayerLevels[player.UnityPlayerGuid] = currentLevel;
                    var punishment = PunishLevels[currentLevel];
                    TempMuted[player.UnityPlayerGuid] = DistanceServerMain.UnixTime + Math.Abs(punishment);
                    var votePlugin = Manager.GetPlugin <VoteCommands.VoteCommands>();
                    if (votePlugin != null)
                    {
                        votePlugin.Mute(player.UnityPlayerGuid, TempMuted[player.UnityPlayerGuid]);
                    }
                    WritePersistentStateIfNecessary();
                    LogMessage($"  Muting (shadow: {(punishment < 0 ? "yes" : "no")}) for {Math.Abs(punishment)} seconds (level {currentLevel})");
                    WriteLogIfNecessary();
                    if (punishment > 0)
                    {
                        player.SayLocalChat(DistanceChat.Server("ChatFilter:Muted", $"[FF0000]Be nice![-] You are muted for {Math.Abs(punishment)} seconds."));
                    }
                }
            }

            var isShadowMuted = false;
            int level         = 0;

            PlayerLevels.TryGetValue(player.UnityPlayerGuid, out level);
            isShadowMuted = level < PunishLevels.Count && PunishLevels[level] < 0;
            if (isShadowMuted)
            {
                Server.DeleteChatMessage(data);
                foreach (var otherPlayer in Server.ValidPlayers)
                {
                    if (otherPlayer != player)
                    {
                        otherPlayer.DeleteChatMessage(data, true);
                    }
                }
            }
            else
            {
                Server.DeleteChatMessage(data, true);
            }
            data.Blocked = true;
        }
Ejemplo n.º 19
0
        public override void Start()
        {
            ReadSettings();
            Log.Info($"Starting HTTP Info Server on port {Port}");

            worker = new ThreadWorker <RequestInfo>();
            worker.QueueResponses = false;

            Server.OnUpdateEvent.Connect(() =>
            {
                worker.Respond(info =>
                {
                    info.Request.Respond();
                    return(info.Request);
                });
            });

            var listener = new HttpListener();

            Server.OnDestroyEvent.Connect(() =>
            {
                listener.Abort();
            });
            listener.Prefixes.Add($"http://*:{Port}/");
            if (PortHttps >= 0 && PortHttps != Port)
            {
                listener.Prefixes.Add($"https://*:{PortHttps}/");
            }
            listener.Start();
            listener.BeginGetContext(listenerCallback, listener);

            Log.Debug($"Started HTTP(S) Info Server on port {Port}");

            DistanceServerMain.GetEvent <Events.ClientToAllClients.ChatMessage>().Connect((data, info) =>
            {
                var playerMatch = Regex.Match(data.message_, @"^\[[0-9A-F]{6}\](.*?)\[FFFFFF\]: (.*)$");
                if (!playerMatch.Success)
                {
                    return;
                }
                var playerName = Regex.Replace(playerMatch.Groups[1].ToString(), @"\[.*\]", "").ToLower();
                var player     = Server.ValidPlayers.Find(distPlayer => distPlayer.Name.ToLower() == Regex.Replace(playerMatch.Groups[1].ToString(), @"\[.*\]", "").ToLower());
                if (player == null)
                {
                    return;
                }
                var message = playerMatch.Groups[2].ToString();

                Match match;
                match = Regex.Match(message, @"^/unlink$");
                if (match.Success)
                {
                    var keysToRemove = new List <string>();
                    foreach (var pair in Links)
                    {
                        if (pair.Value == player.UnityPlayerGuid)
                        {
                            keysToRemove.Add(pair.Key);
                        }
                    }
                    foreach (var key in keysToRemove)
                    {
                        Links.Remove(key);
                    }
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("HttpServer:Link", $"Your game session has been unlinked from {keysToRemove.Count} web session{(keysToRemove.Count == 1 ? "s" : "")}"));
                    return;
                }

                match = Regex.Match(message, @"^/link (\w{6})$");
                if (match.Success)
                {
                    var code = match.Groups[1].ToString().ToUpper();
                    if (!CodesReverse.ContainsKey(code))
                    {
                        var add = "";
                        if (HelpTextWebsite != null)
                        {
                            add = $"\nVisit {HelpTextWebsite.Replace("$linkcode", GetOrGenerateCode(player.UnityPlayerGuid))} to view and vote online.";
                        }
                        Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("HttpServer:Link", "Invalid link code!" + add));
                    }
                    Links[CodesReverse[code]] = player.UnityPlayerGuid;
                    CodesReverse.Remove(code);
                    Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("HttpServer:Link", $"Your game session has been linked to a web session!\nType [00FFFF]/unlink[-] to undo this."));
                    return;
                }

                match = Regex.Match(message, @"^/link");
                if (match.Success)
                {
                    if (HelpTextWebsite != null)
                    {
                        Server.SayLocalChat(player.UnityPlayer, DistanceChat.Server("HttpServer:Link", $"Visit {HelpTextWebsite.Replace("$linkcode", GetOrGenerateCode(player.UnityPlayerGuid))} to view and vote online."));
                    }
                    return;
                }
            });

            Manager.Server.OnPlayerValidatedEvent.Connect(player =>
            {
                GetOrGenerateCode(player.UnityPlayerGuid);
            });

            var autoServer = Manager.GetPlugin <BasicAutoServer.BasicAutoServer>();

            if (autoServer != null)
            {
                Log.Debug("Set LinkCodeGetter in BasicAutoServer");
                autoServer.LinkCodeGetter = player =>
                {
                    return(GetOrGenerateCode(player.UnityPlayerGuid));
                };
            }

            Manager.Server.OnPlayerDisconnectedEvent.Connect(player =>
            {
                Expiry[player.UnityPlayerGuid] = DistanceServerMain.UnixTime + 5 * 60;
                string keyToRemove             = null;
                foreach (var pair in CodesForward)
                {
                    if (pair.Value == player.UnityPlayerGuid)
                    {
                        keyToRemove = pair.Key;
                        break;
                    }
                }
                if (keyToRemove != null)
                {
                    CodesForward.Remove(keyToRemove);
                }
            });

            double lastUpdate = 0;

            Manager.Server.OnUpdateEvent.Connect(() =>
            {
                var now = DistanceServerMain.UnixTime;
                if (now - lastUpdate >= 60)
                {
                    lastUpdate       = now;
                    var KeysToRemove = new List <string>();
                    foreach (var pair in Links)
                    {
                        if (IsExpired(pair.Key, now) || IsExpired(pair.Value, now))
                        {
                            KeysToRemove.Add(pair.Key);
                        }
                    }
                    foreach (var key in KeysToRemove)
                    {
                        Links.Remove(key);
                    }
                    KeysToRemove.Clear();
                    foreach (var pair in CodesForward)
                    {
                        if (IsExpired(pair.Value, now))
                        {
                            KeysToRemove.Add(pair.Key);
                        }
                    }
                    foreach (var key in KeysToRemove)
                    {
                        CodesForward.Remove(key);
                    }
                    KeysToRemove.Clear();
                    foreach (var pair in CodesReverse)
                    {
                        if (IsExpired(pair.Value, now))
                        {
                            KeysToRemove.Add(pair.Key);
                        }
                    }
                    foreach (var key in KeysToRemove)
                    {
                        CodesReverse.Remove(key);
                    }
                }
            });

            Log.Debug($"Started handling code linking");
        }