private void testDriverNames()
        {
            List <OpponentData> driversToTest  = new List <OpponentData>();
            List <String>       rawDriverNames = new List <string>();
            DirectoryInfo       soundDirectory = new DirectoryInfo(AudioPlayer.soundFilesPath);

            FileInfo[] filesInSoundDirectory = soundDirectory.GetFiles();
            foreach (FileInfo fileInSoundDirectory in filesInSoundDirectory)
            {
                if (fileInSoundDirectory.Name == "names_test.txt")
                {
                    String[] lines = File.ReadAllLines(Path.Combine(AudioPlayer.soundFilesPath, fileInSoundDirectory.Name));
                    foreach (String line in lines)
                    {
                        if (line.Trim().Length > 0)
                        {
                            driversToTest.Add(makeTempDriver(line.Trim(), rawDriverNames));
                        }
                    }
                    break;
                }
            }
            if (rawDriverNames.Count > 0)
            {
                Console.WriteLine("Playing test sounds for drivers " + String.Join(", ", rawDriverNames));
                List <String> usableDriverNames = DriverNameHelper.getUsableDriverNames(rawDriverNames);
                int           index             = 0;
                foreach (OpponentData driverToTest in driversToTest)
                {
                    if (SoundCache.availableDriverNames.Contains(DriverNameHelper.getUsableDriverName(driverToTest.DriverRawName)))
                    {
                        audioPlayer.playMessage(new QueuedMessage("gap_in_front" + index, 0,
                                                                  messageFragments: MessageContents(Timings.folderTheGapTo, driverToTest, Timings.folderAheadIsIncreasing,
                                                                                                    TimeSpan.FromSeconds((float)Utilities.random.NextDouble() * 10)),
                                                                  alternateMessageFragments: MessageContents(Timings.folderGapInFrontIncreasing, TimeSpan.FromSeconds((float)Utilities.random.NextDouble() * 10)),
                                                                  abstractEvent: this));
                        audioPlayer.wakeMonitorThreadForRegularMessages(DateTime.UtcNow);

                        audioPlayer.playMessage(new QueuedMessage("leader_pitting" + index, 0,
                                                                  messageFragments: MessageContents(Opponents.folderTheLeader, driverToTest, Opponents.folderIsPitting),
                                                                  abstractEvent: this));
                        audioPlayer.wakeMonitorThreadForRegularMessages(DateTime.UtcNow);

                        audioPlayer.playMessage(new QueuedMessage("new_fastest_lap" + index, 0,
                                                                  messageFragments: MessageContents(Opponents.folderNewFastestLapFor, driverToTest, TimeSpan.FromSeconds(Utilities.random.NextDouble() * 100)),
                                                                  abstractEvent: this));
                        audioPlayer.wakeMonitorThreadForRegularMessages(DateTime.UtcNow);

                        index++;
                    }
                    else
                    {
                        Console.WriteLine("No sound file for driver " + driverToTest.DriverRawName + " - unable to play test sounds");
                    }
                }
            }
        }
        override protected void triggerInternal(GameStateData previousGameState, GameStateData currentGameState)
        {
            var cgs = currentGameState;
            var pgs = previousGameState;

            if (cgs.PitData.InPitlane || /*|| cgs.SessionData.SessionRunningTime < 10 */
                GameStateData.onManualFormationLap ||  // We may want manual formation to phase of FrozenOrder.
                pgs == null)
            {
                return; // don't process if we're in the pits or just started a session
            }
            var cfod  = cgs.FrozenOrderData;
            var pfod  = pgs.FrozenOrderData;
            var cfodp = cgs.FrozenOrderData.Phase;

            if (cfodp == FrozenOrderPhase.None)
            {
                return;  // Nothing to do.
            }
            var useAmericanTerms = GlobalBehaviourSettings.useAmericanTerms || GlobalBehaviourSettings.useOvalLogic;
            var useOvalLogic     = GlobalBehaviourSettings.useOvalLogic;

            if (pfod.Phase == FrozenOrderPhase.None)
            {
                Console.WriteLine("Frozen Order: New Phase detected: " + cfod.Phase);
                int delay = Utilities.random.Next(0, 3);
                if (cfod.Phase == FrozenOrderPhase.Rolling)
                {
                    audioPlayer.playMessage(new QueuedMessage(folderRollingStartReminder, delay + 4, secondsDelay: delay, abstractEvent: this, priority: 10));
                }
                else if (cfod.Phase == FrozenOrderPhase.FormationStanding)
                {
                    audioPlayer.playMessage(new QueuedMessage(folderStandingStartReminder, delay + 4, secondsDelay: delay, abstractEvent: this, priority: 10));
                }

                // Clear previous state.
                this.clearState();
            }

            // Because FO Action is distance dependent, it tends to fluctuate.
            // We need to detect when it stabilizes (values stay identical for ACTION_STABLE_THRESHOLD times).
            if (cfod.Action == pfod.Action &&
                cfod.DriverToFollowRaw == pfod.DriverToFollowRaw &&
                cfod.AssignedColumn == pfod.AssignedColumn)
            {
                ++this.numUpdatesActionSame;
            }
            else
            {
                this.newFrozenOrderAction = cfod.Action;
                this.newDriverToFollow    = cfod.DriverToFollowRaw;
                this.newFrozenOrderColumn = cfod.AssignedColumn;

                this.numUpdatesActionSame = 0;
            }

            var isActionUpdateStable = this.numUpdatesActionSame >= FrozenOrderMonitor.ACTION_STABLE_THRESHOLD;

            // Detect if we should be following SC, as SC has no driver name.
            var shouldFollowSafetyCar = false;
            var driverToFollow        = "";

            if (cfodp == FrozenOrderPhase.Rolling || cfodp == FrozenOrderPhase.FullCourseYellow)
            {
                shouldFollowSafetyCar = (cfod.AssignedColumn == FrozenOrderColumn.None && cfod.AssignedPosition == 1) || // Single file order.
                                        (cfod.AssignedColumn != FrozenOrderColumn.None && cfod.AssignedPosition <= 2); // Double file (grid) order.

                driverToFollow = shouldFollowSafetyCar ? (useAmericanTerms ? folderThePaceCar : folderTheSafetyCar) : cfod.DriverToFollowRaw;
            }

            if (cfodp == FrozenOrderPhase.Rolling)
            {
                var prevDriverToFollow    = this.currDriverToFollow;
                var prevFrozenOrderAction = this.currFrozenOrderAction;

                if (isActionUpdateStable &&
                    (this.currFrozenOrderAction != this.newFrozenOrderAction ||
                     this.currDriverToFollow != this.newDriverToFollow ||
                     this.currFrozenOrderColumn != this.newFrozenOrderColumn))
                {
                    this.currFrozenOrderAction = this.newFrozenOrderAction;
                    this.currDriverToFollow    = this.newDriverToFollow;
                    this.currFrozenOrderColumn = this.newFrozenOrderColumn;

                    // canReadDriverToFollow will be true if we're behind the safety car or we can read the driver's name:
                    var canReadDriverToFollow = shouldFollowSafetyCar || AudioPlayer.canReadName(driverToFollow);

                    var usableDriverNameToFollow = shouldFollowSafetyCar ? driverToFollow : DriverNameHelper.getUsableDriverName(driverToFollow);

                    var validationData = new Dictionary <string, object>();
                    validationData.Add(FrozenOrderMonitor.validateMessageTypeKey, FrozenOrderMonitor.validateMessageTypeAction);
                    validationData.Add(FrozenOrderMonitor.validationActionKey, cfod.Action);
                    validationData.Add(FrozenOrderMonitor.validationAssignedPositionKey, cfod.AssignedPosition);
                    validationData.Add(FrozenOrderMonitor.validationDriverToFollowKey, cfod.DriverToFollowRaw);

                    if (this.newFrozenOrderAction == FrozenOrderAction.Follow &&
                        prevDriverToFollow != this.currDriverToFollow)     // Don't announce Follow messages for the driver that we caught up to or allowed to pass.
                    {
                        if (canReadDriverToFollow)
                        {
                            // Follow messages are only meaningful if there's name to announce.
                            int delay = Utilities.random.Next(3, 6);
                            if (cfod.AssignedColumn == FrozenOrderColumn.None ||
                                Utilities.random.Next(1, 11) > 8)     // Randomly, announce message without coulmn info.
                            {
                                audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/follow_driver" : "frozen_order/follow_safety_car", delay + 6,
                                                                          secondsDelay: delay, messageFragments: MessageContents(folderFollow, usableDriverNameToFollow), abstractEvent: this,
                                                                          validationData: validationData, priority: 10));
                            }
                            else
                            {
                                string columnName;
                                if (useOvalLogic)
                                {
                                    columnName = cfod.AssignedColumn == FrozenOrderColumn.Left ? folderInTheInsideColumn : folderInTheOutsideColumn;
                                }
                                else
                                {
                                    columnName = cfod.AssignedColumn == FrozenOrderColumn.Left ? folderInTheLeftColumn : folderInTheRightColumn;
                                }
                                audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/follow_driver_in_col" : "frozen_order/follow_safecy_car_in_col", delay + 6,
                                                                          secondsDelay: delay, messageFragments: MessageContents(folderFollow, usableDriverNameToFollow, columnName), abstractEvent: this,
                                                                          validationData: validationData, priority: 10));
                            }
                        }
                    }
                    else if (this.newFrozenOrderAction == FrozenOrderAction.AllowToPass)
                    {
                        // Follow messages are only meaningful if there's name to announce.
                        int delay = Utilities.random.Next(1, 4);
                        if (canReadDriverToFollow && Utilities.random.Next(1, 11) > 2)  // Randomly, announce message without name.
                        {
                            audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/allow_driver_to_pass" : "frozen_order/allow_safety_car_to_pass", delay + 6,
                                                                      secondsDelay: delay, messageFragments: MessageContents(folderAllow, usableDriverNameToFollow, folderToPass), abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage(folderYoureAheadOfAGuyYouShouldBeFollowing, delay + 6, secondsDelay: delay, abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                    }
                    else if (this.newFrozenOrderAction == FrozenOrderAction.CatchUp)
                    {
                        int delay = Utilities.random.Next(1, 4);
                        if (canReadDriverToFollow && Utilities.random.Next(1, 11) > 2)  // Randomly, announce message without name.
                        {
                            audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/catch_up_to_driver" : "frozen_order/catch_up_to_safety_car", delay + 6,
                                                                      secondsDelay: delay, messageFragments: MessageContents(folderCatchUpTo, usableDriverNameToFollow), abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage(folderYouNeedToCatchUpToTheGuyAhead, delay + 6, secondsDelay: delay, abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                    }
                    else if (this.newFrozenOrderAction == FrozenOrderAction.StayInPole &&
                             prevFrozenOrderAction != FrozenOrderAction.MoveToPole) // No point in nagging user to stay in pole if we previously told them to move there.
                    {
                        int delay = Utilities.random.Next(0, 3);
                        if (cfod.AssignedColumn == FrozenOrderColumn.None ||
                            Utilities.random.Next(1, 11) > 8)     // Randomly, announce message without coulmn info.
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/stay_in_pole", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderStayInPole), abstractEvent: this, validationData: validationData, priority: 10));
                        }
                        else
                        {
                            string folderToPlay = null;
                            if (useOvalLogic)
                            {
                                folderToPlay = cfod.AssignedColumn == FrozenOrderColumn.Left ? folderStayInPoleInInsideColumn : folderStayInPoleInOutsideColumn;
                            }
                            else
                            {
                                folderToPlay = cfod.AssignedColumn == FrozenOrderColumn.Left ? folderStayInPoleInLeftColumn : folderStayInPoleInRightColumn;
                            }

                            audioPlayer.playMessage(new QueuedMessage("frozen_order/stay_in_pole_in_column", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderToPlay), abstractEvent: this, validationData: validationData, priority: 10));
                        }
                    }
                    else if (this.newFrozenOrderAction == FrozenOrderAction.MoveToPole)
                    {
                        int delay = Utilities.random.Next(2, 5);
                        if (cfod.AssignedColumn == FrozenOrderColumn.None)
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/move_to_pole", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderMoveToPole), abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/move_to_pole_row", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderMoveToPoleRow), abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                    }
                }
            }
            else if (cfodp == FrozenOrderPhase.FullCourseYellow)
            {
                var prevDriverToFollow    = this.currDriverToFollow;
                var prevFrozenOrderColumn = this.currFrozenOrderColumn;

                var announceSCRLastFCYLapLane = useAmericanTerms &&
                                                currentGameState.StockCarRulesData.stockCarRulesEnabled &&
                                                (currentGameState.FlagData.fcyPhase == FullCourseYellowPhase.LAST_LAP_NEXT || currentGameState.FlagData.fcyPhase == FullCourseYellowPhase.LAST_LAP_CURRENT);

                if (isActionUpdateStable &&
                    (this.currFrozenOrderAction != this.newFrozenOrderAction ||
                     this.currDriverToFollow != this.newDriverToFollow ||
                     this.currFrozenOrderColumn != this.newFrozenOrderColumn ||
                     announceSCRLastFCYLapLane && !this.scrLastFCYLapLaneAnnounced))
                {
                    this.currFrozenOrderAction = this.newFrozenOrderAction;
                    this.currDriverToFollow    = this.newDriverToFollow;
                    this.currFrozenOrderColumn = this.newFrozenOrderColumn;

                    this.scrLastFCYLapLaneAnnounced = announceSCRLastFCYLapLane;

                    // canReadDriverToFollow will be true if we're behind the safety car or we can read the driver's name:
                    var canReadDriverToFollow = shouldFollowSafetyCar || AudioPlayer.canReadName(driverToFollow);

                    var usableDriverNameToFollow = shouldFollowSafetyCar ? driverToFollow : DriverNameHelper.getUsableDriverName(driverToFollow);

                    var validationData = new Dictionary <string, object>();
                    validationData.Add(FrozenOrderMonitor.validateMessageTypeKey, FrozenOrderMonitor.validateMessageTypeAction);
                    validationData.Add(FrozenOrderMonitor.validationActionKey, cfod.Action);
                    validationData.Add(FrozenOrderMonitor.validationAssignedPositionKey, cfod.AssignedPosition);
                    validationData.Add(FrozenOrderMonitor.validationDriverToFollowKey, cfod.DriverToFollowRaw);

                    if (this.newFrozenOrderAction == FrozenOrderAction.Follow &&
                        ((prevDriverToFollow != this.currDriverToFollow) ||  // Don't announce Follow messages for the driver that we caught up to or allowed to pass.
                         (prevFrozenOrderColumn != this.currFrozenOrderColumn && announceSCRLastFCYLapLane)))        // But announce for SCR last FCY lap.
                    {
                        int delay = Utilities.random.Next(0, 3);
                        if (canReadDriverToFollow)
                        {
                            if (announceSCRLastFCYLapLane && cfod.AssignedColumn == FrozenOrderColumn.Left)
                            {
                                audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/follow_driver_in_left" : "frozen_order/follow_safety_car_in_left",
                                                                          delay + 6, secondsDelay: delay, messageFragments: MessageContents(folderFollow, usableDriverNameToFollow,
                                                                                                                                            useOvalLogic ? folderInTheInsideColumn : folderInTheLeftColumn), abstractEvent: this, validationData: validationData, priority: 10));
                            }
                            else if (announceSCRLastFCYLapLane && cfod.AssignedColumn == FrozenOrderColumn.Right)
                            {
                                audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/follow_driver_in_right" : "frozen_order/follow_safety_car_in_right",
                                                                          delay + 6, secondsDelay: delay,
                                                                          messageFragments: MessageContents(folderFollow, usableDriverNameToFollow, useOvalLogic ? folderInTheOutsideColumn : folderInTheRightColumn),
                                                                          abstractEvent: this, validationData: validationData, priority: 10));
                            }
                            else
                            {
                                audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/follow_driver" : "frozen_order/follow_safety_car", delay + 6,
                                                                          secondsDelay: delay, messageFragments: MessageContents(folderFollow, usableDriverNameToFollow), abstractEvent: this, validationData: validationData, priority: 10));
                            }
                        }
                        else if (announceSCRLastFCYLapLane && cfod.AssignedColumn == FrozenOrderColumn.Left)
                        {
                            audioPlayer.playMessage(new QueuedMessage(useOvalLogic ? folderLineUpInInsideColumn : folderLineUpInLeftColumn, delay + 6, secondsDelay: delay,
                                                                      abstractEvent: this, validationData: validationData, priority: 10));
                        }
                        else if (announceSCRLastFCYLapLane && cfod.AssignedColumn == FrozenOrderColumn.Right)
                        {
                            audioPlayer.playMessage(new QueuedMessage(useOvalLogic ? folderLineUpInOutsideColumn : folderLineUpInRightColumn, delay + 6, secondsDelay: delay,
                                                                      abstractEvent: this, validationData: validationData, priority: 10));
                        }
                    }
                    else if (this.newFrozenOrderAction == FrozenOrderAction.AllowToPass)
                    {
                        int delay = Utilities.random.Next(1, 4);
                        if (canReadDriverToFollow && Utilities.random.Next(1, 11) > 2)  // Randomly, announce message without name.
                        {
                            audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/allow_driver_to_pass" : "frozen_order/allow_safety_car_to_pass",
                                                                      delay + 6, secondsDelay: delay, messageFragments: MessageContents(folderAllow, usableDriverNameToFollow, folderToPass),
                                                                      abstractEvent: this, validationData: validationData, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage(folderYoureAheadOfAGuyYouShouldBeFollowing, delay + 6, secondsDelay: delay, abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                    }
                    else if (this.newFrozenOrderAction == FrozenOrderAction.CatchUp)
                    {
                        int delay = Utilities.random.Next(1, 4);
                        if (canReadDriverToFollow && Utilities.random.Next(1, 11) > 2)  // Randomly, announce message without name.
                        {
                            audioPlayer.playMessage(new QueuedMessage(!shouldFollowSafetyCar ? "frozen_order/catch_up_to_driver" : "frozen_order/catch_up_to_safety_car",
                                                                      delay + 6, secondsDelay: delay, messageFragments: MessageContents(folderCatchUpTo, usableDriverNameToFollow),
                                                                      abstractEvent: this, validationData: validationData, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage(folderYouNeedToCatchUpToTheGuyAhead, delay + 6, secondsDelay: delay, abstractEvent: this,
                                                                      validationData: validationData, priority: 10));
                        }
                    }
                }
            }
            else if (cfodp == FrozenOrderPhase.FormationStanding)
            {
                string columnName = null;
                if (cfod.AssignedColumn != FrozenOrderColumn.None)
                {
                    if (useOvalLogic)
                    {
                        columnName = cfod.AssignedColumn == FrozenOrderColumn.Left ? folderInTheInsideColumn : folderInTheOutsideColumn;
                    }
                    else
                    {
                        columnName = cfod.AssignedColumn == FrozenOrderColumn.Left ? folderInTheLeftColumn : folderInTheRightColumn;
                    }
                }
                if (!this.formationStandingStartAnnounced && cgs.SessionData.SessionRunningTime > 10)
                {
                    this.formationStandingStartAnnounced = true;
                    var isStartingFromPole = cfod.AssignedPosition == 1;
                    int delay = Utilities.random.Next(0, 3);
                    if (isStartingFromPole)
                    {
                        if (columnName == null)
                        {
                            audioPlayer.playMessage(new QueuedMessage(folderWereStartingFromPole, 6, abstractEvent: this));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/youre_starting_from_pole_in_column", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderWereStartingFromPole, columnName), abstractEvent: this, priority: 10));
                        }
                    }
                    else
                    {
                        if (columnName == null)
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/youre_starting_from_pos", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderWeStartingFromPosition, cfod.AssignedPosition), abstractEvent: this, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/youre_starting_from_pos_row_in_column", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(folderWeStartingFromPosition, cfod.AssignedPosition, folderRow, cfod.AssignedGridPosition, columnName),
                                                                      abstractEvent: this, priority: 10));
                        }
                    }
                }

                if (!this.formationStandingPreStartReminderAnnounced &&
                    cgs.SessionData.SectorNumber == 3 &&
                    cgs.PositionAndMotionData.DistanceRoundTrack > (cgs.SessionData.TrackDefinition.trackLength - FrozenOrderMonitor.DIST_TO_START_TO_ANNOUNCE_POS_REMINDER))
                {
                    this.formationStandingPreStartReminderAnnounced = true;
                    var isStartingFromPole = cfod.AssignedPosition == 1;
                    int delay = Utilities.random.Next(0, 3);
                    if (isStartingFromPole)
                    {
                        if (columnName == null)
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/get_ready_starting_from_pole", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(LapCounter.folderGetReady, folderWereStartingFromPole), abstractEvent: this, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/get_ready_starting_from_pole_in_column", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(LapCounter.folderGetReady, folderWereStartingFromPole, columnName), abstractEvent: this, priority: 10));
                        }
                    }
                    else
                    {
                        if (columnName == null)
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/get_ready_youre_starting_from_pos", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(LapCounter.folderGetReady, folderWeStartingFromPosition, cfod.AssignedPosition), abstractEvent: this, priority: 10));
                        }
                        else
                        {
                            audioPlayer.playMessage(new QueuedMessage("frozen_order/get_ready_youre_starting_from_pos_row_in_column", delay + 6, secondsDelay: delay,
                                                                      messageFragments: MessageContents(LapCounter.folderGetReady, folderWeStartingFromPosition, cfod.AssignedPosition, folderRow, cfod.AssignedGridPosition, columnName),
                                                                      abstractEvent: this, priority: 10));
                        }
                    }
                }
            }

            // Announce SC speed.
            if (pfod.SafetyCarSpeed == -1.0f && cfod.SafetyCarSpeed != -1.0f)
            {
                // TODO: may also need to announce basic "SC in" message.
                var kmPerHour        = cfod.SafetyCarSpeed * 3.6f;
                var messageFragments = new List <MessageFragment>();
                if (useAmericanTerms)
                {
                    messageFragments.Add(MessageFragment.Text(FrozenOrderMonitor.folderPaceCarSpeedIs));
                    var milesPerHour = kmPerHour * 0.621371f;
                    messageFragments.Add(MessageFragment.Integer((int)Math.Round(milesPerHour), false));
                    messageFragments.Add(MessageFragment.Text(FrozenOrderMonitor.folderMilesPerHour));
                }
                else
                {
                    messageFragments.Add(MessageFragment.Text(FrozenOrderMonitor.folderSafetyCarSpeedIs));
                    messageFragments.Add(MessageFragment.Integer((int)Math.Round(kmPerHour), false));
                    messageFragments.Add(MessageFragment.Text(FrozenOrderMonitor.folderKilometresPerHour));
                }
                int delay = Utilities.random.Next(10, 16);
                audioPlayer.playMessage(new QueuedMessage("frozen_order/pace_car_speed", delay + 6, secondsDelay: delay, messageFragments: messageFragments, abstractEvent: this, priority: 10));
            }

            // Announce SC left.
            if (pfod.SafetyCarSpeed != -1.0f && cfod.SafetyCarSpeed == -1.0f)
            {
                if (useAmericanTerms)
                {
                    audioPlayer.playMessage(new QueuedMessage(folderPaceCarJustLeft, 10, abstractEvent: this));
                }
                else
                {
                    audioPlayer.playMessage(new QueuedMessage(folderSafetyCarJustLeft, 10, abstractEvent: this));
                }
            }

            // For fast rolling, do nothing for now.
        }
Exemple #3
0
        private Tuple <string, Boolean> getOpponentKey(String voiceMessage, String expectedNumberSuffix)
        {
            string  opponentKey         = null;
            Boolean gotByPositionNumber = false;

            if (voiceMessage.Contains(SpeechRecogniser.THE_LEADER))
            {
                if (currentGameState.SessionData.Position > 1)
                {
                    opponentKey = currentGameState.getOpponentKeyAtPosition(1, false);
                }
                else if (currentGameState.SessionData.Position == 1)
                {
                    opponentKey = positionIsPlayerKey;
                }
            }
            if ((voiceMessage.Contains(SpeechRecogniser.THE_CAR_AHEAD) || voiceMessage.Contains(SpeechRecogniser.THE_GUY_AHEAD) ||
                 voiceMessage.Contains(SpeechRecogniser.THE_GUY_IN_FRONT) || voiceMessage.Contains(SpeechRecogniser.THE_CAR_IN_FRONT)) && currentGameState.SessionData.Position > 1)
            {
                opponentKey = currentGameState.getOpponentKeyInFront(false);
            }
            else if ((voiceMessage.Contains(SpeechRecogniser.THE_CAR_BEHIND) || voiceMessage.Contains(SpeechRecogniser.THE_GUY_BEHIND)) &&
                     !currentGameState.isLast())
            {
                opponentKey = currentGameState.getOpponentKeyBehind(false);
            }
            else if (voiceMessage.Contains(SpeechRecogniser.POSITION_LONG) || voiceMessage.Contains(SpeechRecogniser.POSITION_SHORT))
            {
                int position = 0;
                foreach (KeyValuePair <String, int> entry in SpeechRecogniser.numberToNumber)
                {
                    if (expectedNumberSuffix.Length > 0)
                    {
                        if (voiceMessage.Contains(" " + entry.Key + expectedNumberSuffix))
                        {
                            position = entry.Value;
                            break;
                        }
                    }
                    else
                    {
                        if (voiceMessage.EndsWith(" " + entry.Key))
                        {
                            position = entry.Value;
                            break;
                        }
                    }
                }
                if (position != currentGameState.SessionData.Position)
                {
                    opponentKey = currentGameState.getOpponentKeyAtPosition(position, false);
                }
                else
                {
                    opponentKey = positionIsPlayerKey;
                }
                gotByPositionNumber = true;
            }
            else
            {
                foreach (KeyValuePair <string, OpponentData> entry in currentGameState.OpponentData)
                {
                    String usableDriverName = DriverNameHelper.getUsableDriverName(entry.Value.DriverRawName);
                    if (voiceMessage.Contains(usableDriverName))
                    {
                        opponentKey = entry.Key;
                        break;
                    }
                }
            }
            return(new Tuple <string, bool>(opponentKey, gotByPositionNumber));
        }
Exemple #4
0
        override protected void triggerInternal(GameStateData previousGameState, GameStateData currentGameState)
        {
            this.currentGameState = currentGameState;
            if (nextCarAheadChangeMessage == DateTime.MinValue)
            {
                nextCarAheadChangeMessage = currentGameState.Now.Add(TimeSpan.FromSeconds(30));
            }
            if (nextLeadChangeMessage == DateTime.MinValue)
            {
                nextLeadChangeMessage = currentGameState.Now.Add(TimeSpan.FromSeconds(30));
            }
            if (currentGameState.SessionData.SessionType != SessionType.Race || frequencyOfOpponentRaceLapTimes > 0)
            {
                foreach (KeyValuePair <string, OpponentData> entry in currentGameState.OpponentData)
                {
                    string       opponentKey  = entry.Key;
                    OpponentData opponentData = entry.Value;

                    if (opponentData.IsNewLap && opponentData.LastLapTime > 0 && opponentData.OpponentLapData.Count > 1 &&
                        opponentData.LastLapValid && opponentData.CurrentBestLapTime > 0)
                    {
                        float currentFastestLap;
                        if (currentGameState.SessionData.PlayerLapTimeSessionBest == -1)
                        {
                            currentFastestLap = currentGameState.SessionData.OpponentsLapTimeSessionBestOverall;
                        }
                        else if (currentGameState.SessionData.OpponentsLapTimeSessionBestOverall == -1)
                        {
                            currentFastestLap = currentGameState.SessionData.PlayerLapTimeSessionBest;
                        }
                        else
                        {
                            currentFastestLap = Math.Min(currentGameState.SessionData.PlayerLapTimeSessionBest, currentGameState.SessionData.OpponentsLapTimeSessionBestOverall);
                        }

                        // this opponent has just completed a lap - do we need to report it? if it's fast overall and more than
                        // a tenth quicker then his previous best we do...
                        if (((currentGameState.SessionData.SessionType == SessionType.Race && opponentData.CompletedLaps > 2) ||
                             (currentGameState.SessionData.SessionType != SessionType.Race && opponentData.CompletedLaps > 1)) && opponentData.LastLapTime <= currentFastestLap &&
                            (SoundCache.hasSuitableTTSVoice || SoundCache.availableDriverNames.Contains(DriverNameHelper.getUsableDriverName(opponentData.DriverRawName))))
                        {
                            audioPlayer.playMessage(new QueuedMessage("new_fastest_lap", MessageContents(folderNewFastestLapFor, opponentData,
                                                                                                         TimeSpan.FromSeconds(opponentData.LastLapTime)), 0, this));
                        }
                        else if ((currentGameState.SessionData.SessionType == SessionType.Race &&
                                  (opponentData.LastLapTime <= opponentData.CurrentBestLapTime &&
                                   opponentData.LastLapTime < opponentData.PreviousBestLapTime - minImprovementBeforeReadingOpponentRaceTime &&
                                   opponentData.LastLapTime < currentFastestLap + maxOffPaceBeforeReadingOpponentRaceTime)) ||
                                 ((currentGameState.SessionData.SessionType == SessionType.Practice || currentGameState.SessionData.SessionType == SessionType.Qualify) &&
                                  opponentData.LastLapTime <= opponentData.CurrentBestLapTime))
                        {
                            if (currentGameState.SessionData.UnFilteredPosition > 1 && opponentData.UnFilteredPosition == 1 &&
                                (currentGameState.SessionData.SessionType == SessionType.Race || frequencyOfOpponentPracticeAndQualLapTimes > 0))
                            {
                                // he's leading, and has recorded 3 or more laps, and this one's his fastest
                                Console.WriteLine("Leader fast lap - this lap time = " + opponentData.LastLapTime + " session best = " + currentFastestLap);
                                audioPlayer.playMessage(new QueuedMessage("leader_good_laptime", MessageContents(folderLeaderHasJustDoneA,
                                                                                                                 TimeSpan.FromSeconds(opponentData.LastLapTime)), 0, this));
                            }
                            else if (currentGameState.SessionData.UnFilteredPosition > 1 && opponentData.UnFilteredPosition == currentGameState.SessionData.Position - 1 &&
                                     (currentGameState.SessionData.SessionType == SessionType.Race || random.Next(10) < frequencyOfOpponentPracticeAndQualLapTimes))
                            {
                                // he's ahead of us, and has recorded 3 or more laps, and this one's his fastest
                                Console.WriteLine("Car ahead fast lap - this lap time = " + opponentData.LastLapTime + " session best = " + currentFastestLap);
                                audioPlayer.playMessage(new QueuedMessage("car_ahead_good_laptime", MessageContents(folderTheCarAheadHasJustDoneA,
                                                                                                                    TimeSpan.FromSeconds(opponentData.LastLapTime)), 0, this));
                            }
                            else if (!currentGameState.isLast() && opponentData.UnFilteredPosition == currentGameState.SessionData.Position + 1 &&
                                     (currentGameState.SessionData.SessionType == SessionType.Race || random.Next(10) < frequencyOfOpponentPracticeAndQualLapTimes))
                            {
                                // he's behind us, and has recorded 3 or more laps, and this one's his fastest
                                Console.WriteLine("Car behind fast lap - this lap time = " + opponentData.LastLapTime + " session best = " + currentFastestLap);
                                audioPlayer.playMessage(new QueuedMessage("car_behind_good_laptime", MessageContents(folderTheCarBehindHasJustDoneA,
                                                                                                                     TimeSpan.FromSeconds(opponentData.LastLapTime)), 0, this));
                            }
                        }
                    }
                }
            }

            if (currentGameState.SessionData.SessionType == SessionType.Race)
            {
                if (!currentGameState.SessionData.IsRacingSameCarInFront)
                {
                    if (currentGameState.SessionData.Position > 2 && currentGameState.Now > nextCarAheadChangeMessage && !currentGameState.PitData.InPitlane &&
                        currentGameState.SessionData.CompletedLaps > 0)
                    {
                        OpponentData opponentData = currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position - 1, false);
                        if (opponentData != null && !opponentData.isEnteringPits() && !opponentData.InPits &&
                            (SoundCache.hasSuitableTTSVoice || SoundCache.availableDriverNames.Contains(DriverNameHelper.getUsableDriverName(opponentData.DriverRawName))))
                        {
                            audioPlayer.playMessage(new QueuedMessage("new_car_ahead", MessageContents(folderNextCarIs, opponentData),
                                                                      random.Next(Position.maxSecondsToWaitBeforeReportingPass + 1, Position.maxSecondsToWaitBeforeReportingPass + 3), this,
                                                                      new Dictionary <string, object> {
                                { validationDriverAheadKey, opponentData.DriverRawName }
                            }));
                            nextCarAheadChangeMessage = currentGameState.Now.Add(TimeSpan.FromSeconds(30));
                        }
                    }
                }
                if (currentGameState.SessionData.HasLeadChanged)
                {
                    OpponentData leader = currentGameState.getOpponentAtPosition(1, false);
                    if (leader != null)
                    {
                        String name = leader.DriverRawName;
                        if (currentGameState.SessionData.Position > 1 && previousGameState.SessionData.Position > 1 &&
                            currentGameState.Now > nextLeadChangeMessage &&
                            (SoundCache.hasSuitableTTSVoice || SoundCache.availableDriverNames.Contains(DriverNameHelper.getUsableDriverName(name))))
                        {
                            Console.WriteLine("Lead change, current leader is " + name + " laps completed = " + currentGameState.SessionData.CompletedLaps);
                            audioPlayer.playMessage(new QueuedMessage("new_leader", MessageContents(leader, folderIsNowLeading), 2, this,
                                                                      new Dictionary <string, object> {
                                { validationNewLeaderKey, name }
                            }));
                            nextLeadChangeMessage = currentGameState.Now.Add(TimeSpan.FromSeconds(30));
                        }
                    }
                }

                if (currentGameState.PitData.LeaderIsPitting &&
                    currentGameState.SessionData.SessionPhase != SessionPhase.Countdown && currentGameState.SessionData.SessionPhase != SessionPhase.Formation)
                {
                    audioPlayer.playMessage(new QueuedMessage("leader_is_pitting", MessageContents(folderTheLeader, currentGameState.PitData.OpponentForLeaderPitting,
                                                                                                   folderIsPitting), MessageContents(folderLeaderIsPitting), 0, this));
                }

                if (currentGameState.PitData.CarInFrontIsPitting && currentGameState.SessionData.TimeDeltaFront > 3 &&
                    currentGameState.SessionData.SessionPhase != SessionPhase.Countdown && currentGameState.SessionData.SessionPhase != SessionPhase.Formation)
                {
                    audioPlayer.playMessage(new QueuedMessage("car_in_front_is_pitting", MessageContents(currentGameState.PitData.OpponentForCarAheadPitting,
                                                                                                         folderAheadIsPitting), MessageContents(folderCarAheadIsPitting), 0, this));
                }

                if (currentGameState.PitData.CarBehindIsPitting && currentGameState.SessionData.TimeDeltaBehind > 3 &&
                    currentGameState.SessionData.SessionPhase != SessionPhase.Countdown && currentGameState.SessionData.SessionPhase != SessionPhase.Formation)
                {
                    audioPlayer.playMessage(new QueuedMessage("car_behind_is_pitting", MessageContents(currentGameState.PitData.OpponentForCarBehindPitting,
                                                                                                       folderBehindIsPitting), MessageContents(folderCarBehindIsPitting), 0, this));
                }
            }
        }