/// <inheritdoc /> protected override Task UpdateQueuedMessage(QueuedMessage queuedMessage, DateTime?abandoned, CancellationToken cancellationToken = default(CancellationToken)) { CheckDisposed(); return(_commandExecutor.Execute( () => base.UpdateQueuedMessage(queuedMessage, abandoned, cancellationToken), cancellationToken)); }
public void playMessageImmediately(QueuedMessage queuedMessage) { if (disableImmediateMessages) { return; } if (queuedMessage.canBePlayed) { lock (immediateClips) { if (immediateClips.Contains(queuedMessage.messageName)) { Console.WriteLine("Clip for event " + queuedMessage.messageName + " is already queued, ignoring"); return; } else { lastImmediateMessageName = queuedMessage.messageName; lastImmediateMessageTime = DateTime.Now; this.useShortBeepWhenOpeningChannel = false; this.holdChannelOpen = false; immediateClips.Add(queuedMessage.messageName, queuedMessage); } } } }
/// <summary> /// Updates a queued message in the database i.e. in response to an acknowledgement failure /// </summary> /// <param name="queuedMessage">The queued message to delete</param> /// <param name="abandoned">The date/time the message was abandoned (if applicable)</param> /// <param name="cancellationToken">(Optional) A cancellation token through which the /// caller can request cancellation of the update operation</param> /// <returns>Returns a task that completes when the update operation completes</returns> protected virtual async Task UpdateQueuedMessage(QueuedMessage queuedMessage, DateTime?abandoned, CancellationToken cancellationToken = default(CancellationToken)) { var connection = ConnectionProvider.GetConnection(); try { var message = queuedMessage.Message; var headers = message.Headers; var commandBuilder = CommandBuilders.NewUpdateQueuedMessageCommandBuilder(); commandBuilder.MessageId = headers.MessageId; commandBuilder.QueueName = QueueName; commandBuilder.Abandoned = abandoned; commandBuilder.Attempts = queuedMessage.Attempts; using (var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled)) { using (var command = commandBuilder.BuildDbCommand(connection)) { await command.ExecuteNonQueryAsync(cancellationToken); } scope.Complete(); } } finally { ConnectionProvider.ReleaseConnection(connection); } }
/// <inheritdoc /> protected override Task DeleteQueuedMessage(QueuedMessage queuedMessage, CancellationToken cancellationToken = default(CancellationToken)) { CheckDisposed(); return(_commandExecutor.Execute( () => base.DeleteQueuedMessage(queuedMessage, cancellationToken), cancellationToken)); }
public void playMessage(QueuedMessage queuedMessage, PearlsOfWisdom.PearlType pearlType, double pearlMessageProbability) { if (queuedMessage.canBePlayed) { lock (queuedClips) { if (queuedClips.Contains(queuedMessage.messageName)) { Console.WriteLine("Clip for event " + queuedMessage.messageName + " is already queued, ignoring"); return; } else { PearlsOfWisdom.PearlMessagePosition pearlPosition = PearlsOfWisdom.PearlMessagePosition.NONE; if (pearlType != PearlsOfWisdom.PearlType.NONE && checkPearlOfWisdomValid(pearlType)) { pearlPosition = pearlsOfWisdom.getMessagePosition(pearlMessageProbability); } if (pearlPosition == PearlsOfWisdom.PearlMessagePosition.BEFORE) { QueuedMessage pearlQueuedMessage = new QueuedMessage(queuedMessage.abstractEvent); pearlQueuedMessage.dueTime = queuedMessage.dueTime; queuedClips.Add(PearlsOfWisdom.getMessageFolder(pearlType), pearlQueuedMessage); } queuedClips.Add(queuedMessage.messageName, queuedMessage); if (pearlPosition == PearlsOfWisdom.PearlMessagePosition.AFTER) { QueuedMessage pearlQueuedMessage = new QueuedMessage(queuedMessage.abstractEvent); pearlQueuedMessage.dueTime = queuedMessage.dueTime; queuedClips.Add(PearlsOfWisdom.getMessageFolder(pearlType), pearlQueuedMessage); } } } } }
/// <inheritdoc /> protected override async Task <IEnumerable <QueuedMessage> > GetPendingMessages(CancellationToken cancellationToken = default(CancellationToken)) { var queuedMessages = new List <QueuedMessage>(); var connection = ConnectionProvider.GetConnection(); try { var commandBuilder = CommandBuilders.NewSelectPendingMessagesCommandBuilder(); commandBuilder.QueueName = QueueName; using (var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled)) { using (var command = commandBuilder.BuildDbCommand(connection)) { using (var reader = await command.ExecuteReaderAsync(cancellationToken)) { while (await reader.ReadAsync(cancellationToken)) { try { var record = commandBuilder.BuildQueuedMessageRecord(reader); var messageContent = record.Content; var headers = DeserializeHeaders(record.Headers); var message = new Message(headers, messageContent); if (message.IsEncrypted() && MessageEncryptionService != null) { message = await MessageEncryptionService.Decrypt(message); } #pragma warning disable 612 var principal = await ResolvePrincipal(headers, record.SenderPrincipal); #pragma warning restore 612 message = message.WithoutSecurityToken(); var attempts = record.Attempts; var queuedMessage = new QueuedMessage(message, principal, attempts); queuedMessages.Add(queuedMessage); } catch (Exception ex) { DiagnosticService.Emit(new SQLEventBuilder(this, SQLEventType.SQLMessageRecordFormatError) { Detail = "Error reading previously queued message record; skipping", Exception = ex }.Build()); } } } } scope.Complete(); } } finally { ConnectionProvider.ReleaseConnection(connection); } // SQL calls are not async to avoid the need for TransactionAsyncFlowOption // and dependency on .NET 4.5.1 and later return(queuedMessages.AsEnumerable()); }
private void RaiseMessageQueuedEvent(QueuedMessage queuedMessage) { if (this.MessageQueued != null) { this.MessageQueued(queuedMessage); } }
private void MoveToDeadLetterDirectory(QueuedMessage queuedMessage) { string pattern = ""; try { var message = queuedMessage.Message; var headers = message.Headers; pattern = headers.MessageId + "*.pmsg"; var matchingFiles = _directory.EnumerateFiles(pattern); foreach (var matchingFile in matchingFiles) { MoveToDeadLetterDirectory(matchingFile); } } catch (Exception ex) { DiagnosticService.Emit( new FilesystemEventBuilder(this, DiagnosticEventType.DeadLetterError) { Detail = "Error moving message file(s) to dead letter directory", Queue = QueueName, Path = Path.Combine(_directory.FullName, pattern), Exception = ex }.Build()); } }
public Task Enqueue(Message message, IPrincipal senderPrincipal) { CheckDisposed(); var queuedMessage = new QueuedMessage(message, senderPrincipal); return _queuedMessages.SendAsync(queuedMessage); // TODO: handle accepted == false }
/// <inheritdoc /> protected override async Task <IEnumerable <QueuedMessage> > GetPendingMessages(CancellationToken cancellationToken = new CancellationToken()) { var pendingMessages = new List <QueuedMessage>(); var files = _directory.EnumerateFiles("*.pmsg"); foreach (var file in files) { try { var messageFile = new MessageFile(file); var message = await messageFile.ReadMessage(cancellationToken); if (message.IsEncrypted() && _messageEncryptionService != null) { message = await _messageEncryptionService.Decrypt(message); } var principal = await _securityTokenService.NullSafeValidate(message.Headers.SecurityToken); var queuedMessage = new QueuedMessage(message, principal); pendingMessages.Add(queuedMessage); } catch (Exception ex) { DiagnosticService.Emit(new FilesystemEventBuilder(this, FilesystemEventType.MessageFileFormatError) { Detail = "Error reading previously queued message file; skipping", Path = file.FullName, Exception = ex }.Build()); } } return(pendingMessages); }
/// <summary> /// Transport a <see cref="QueuedMessage"/>. /// </summary> /// <param name="message">The <see cref="IMessage"/> to transport.</param> /// <returns>An awaitable object representing the send operation.</returns> public async Task SendMessageAsync(QueuedMessage message) { const string action = "message"; try { var fullActionPath = new Uri(message.Envelope.Recipient.PeerAddress, Path.Combine(ActionBase, action)); var serialisedMessage = this.Serialiser.Serialise(message.Envelope); var result = await this.ExecutePostRequest(fullActionPath, serialisedMessage); if (result.IsSuccessStatusCode && this.MessageSent != null) { this.MessageSent(message, serialisedMessage); } else { throw new HttpRequestException(result.Content.ReadAsStringAsync().Result); } } catch (Exception exception) { if (this.MessageFailedToSend != null) { this.MessageFailedToSend(exception, message); } } }
private void readStatus() { if (CrewChief.gameDefinition.gameEnum != GameEnum.IRACING) { List <QueuedMessage> damageMessages = new List <QueuedMessage>(); switch (lastReportedPunctureCorner) { case CornerData.Corners.FRONT_LEFT: damageMessages.Add(new QueuedMessage(folderLeftFrontPuncture, 0)); break; case CornerData.Corners.FRONT_RIGHT: damageMessages.Add(new QueuedMessage(folderRightFrontPuncture, 0)); break; case CornerData.Corners.REAR_LEFT: damageMessages.Add(new QueuedMessage(folderLeftRearPuncture, 0)); break; case CornerData.Corners.REAR_RIGHT: damageMessages.Add(new QueuedMessage(folderRightRearPuncture, 0)); break; } QueuedMessage aero = getDamageMessage(Component.AERO, false); if (aero != null) { damageMessages.Add(aero); } QueuedMessage tranny = getDamageMessage(Component.TRANNY, false); if (tranny != null) { damageMessages.Add(tranny); } QueuedMessage engine = getDamageMessage(Component.ENGINE, false); if (engine != null) { damageMessages.Add(engine); } QueuedMessage sus = getDamageMessage(Component.SUSPENSION, false); if (sus != null) { damageMessages.Add(sus); } QueuedMessage brakes = getDamageMessage(Component.BRAKES, false); if (brakes != null) { damageMessages.Add(brakes); } if (damageMessages.Count == 0) { // no damage damageMessages.Add(new QueuedMessage(folderNoDamageOnAnyComponent, 0)); } foreach (QueuedMessage message in damageMessages) { audioPlayer.playMessageImmediately(message); } } }
/// <inheritdoc cref="Producer.ProduceCoreAsync" /> protected override Task <IOffset?> ProduceCoreAsync(IOutboundEnvelope envelope) { var queuedMessage = new QueuedMessage(envelope); _queue.Add(queuedMessage); return(queuedMessage.TaskCompletionSource.Task); }
/// <summary> /// Update: Unity callback invoked on every frame. Used here to process /// messages on our message queue (on the main thread). /// </summary> protected void Update() { while (messageQueue.Count > 0) { QueuedMessage qm = messageQueue.Dequeue(); HandleMessage(qm.client, qm.message); } }
public void NewMessage(byte[] message, IPEndPoint endpoint) { QueuedMessage queuedMessage = new QueuedMessage(); queuedMessage.messageData = message; queuedMessage.source = endpoint; mMessageQueue.Enqueue(queuedMessage); }
internal override Task <T> ExecuteAsync <T>(Message message, ResultProcessor <T> processor, ServerEndPoint server = null) { if (message == null) { return(CompletedTask <T> .Default(asyncState)); } multiplexer.CheckMessage(message); multiplexer.Trace("Wrapping " + message.Command, "Transaction"); // prepare the inner command as a task Task <T> task; if (message.IsFireAndForget) { task = CompletedTask <T> .Default(null); // F+F explicitly does not get async-state } else { var tcs = TaskSource.Create <T>(asyncState); var source = ResultBox <T> .Get(tcs); message.SetSource(source, processor); task = tcs.Task; } // prepare an outer-command that decorates that, but expects QUEUED var queued = new QueuedMessage(message); var wasQueued = ResultBox <bool> .Get(null); queued.SetSource(wasQueued, QueuedProcessor.Default); // store it, and return the task of the *outer* command // (there is no task for the inner command) lock (SyncLock) { (_pending ?? (_pending = new List <QueuedMessage>())).Add(queued); switch (message.Command) { case RedisCommand.UNKNOWN: case RedisCommand.EVAL: case RedisCommand.EVALSHA: // people can do very naughty things in an EVAL // including change the DB; change it back to what we // think it should be! var sel = PhysicalConnection.GetSelectDatabaseCommand(message.Db); queued = new QueuedMessage(sel); wasQueued = ResultBox <bool> .Get(null); queued.SetSource(wasQueued, QueuedProcessor.Default); _pending.Add(queued); break; } } return(task); }
/// <summary> /// Deletes a queued message from the database /// </summary> /// <param name="queuedMessage">The queued message to delete</param> /// <param name="cancellationToken">(Optional) A cancellation token through which the /// caller can request cancellation of the delete operation</param> /// <returns>Returns a task that completes when the delete operation completes</returns> protected virtual async Task DeleteQueuedMessage(QueuedMessage queuedMessage, CancellationToken cancellationToken = default(CancellationToken)) { var message = queuedMessage.Message; var messageHeaders = message.Headers; var messageId = messageHeaders.MessageId; var fb = Builders <QueuedMessageDocument> .Filter; var filter = fb.Eq(qm => qm.Queue, (string)QueueName) & fb.Eq(qm => qm.MessageId, (string)messageId); await _queuedMessages.DeleteOneAsync(filter, cancellationToken); }
public void SetResetPassword(User user) { user.ResetPasswordExpiry = CurrentRequestData.Now.AddDays(1); user.ResetPasswordGuid = Guid.NewGuid(); _userManagementService.SaveUser(user); QueuedMessage queuedMessage = _messageParser.GetMessage(user); _messageParser.QueueMessage(queuedMessage); }
/// <summary> /// Create a path to sent queue for the given <paramref name="message"/>. /// </summary> /// <param name="message">The instance of <see cref="QueuedMessage"/>.</param> /// <returns>A path to sent queue for the given message.</returns> public static string SentLocation(this QueuedMessage message) { var messageLocation = string.Format( CultureInfo.InvariantCulture, "/{0}/sent/{1}", message.Envelope.Recipient.EscapePeerAddress(), message.QueuedAt.ToFileTimeUtc()); return(messageLocation); }
public void OnCompleted() { var completed = new QueuedMessage <StreamMessage <TKey, TPayload> > { Kind = MessageKind.Completed }; this.serializer.Serialize(this.destination, completed); this.destination.Flush(); this.done.Set(); }
/// <summary> /// Mark a message as sent. /// </summary> /// <param name="message">The <see cref="T:ServiceBus.Queueing.QueuedMessage"/> that was sent.</param> /// <param name="messageContent">The raw message content.</param> public void Dequeue(QueuedMessage message, string messageContent) { message.HasSent = true; this._ftpClient.CreatePeerDirectoryIfNotExist(message.Envelope.Recipient); this._ftpClient.DeleteMessage(new Uri(message.MessageLocation(), UriKind.Relative)); this._ftpClient.PutMessage(new Uri(message.SentLocation(), UriKind.Relative), messageContent); }
internal void LogMessageFailedToSend(Exception ex, QueuedMessage failedMessage) { this._serviceBus.Log.InfoFormat( CultureInfo.CurrentCulture, "Message of type [{0}] could not be sent to [{1}] at [{2}]. Reason [{3}]", failedMessage.Envelope.Message.MessageType, failedMessage.Envelope.Recipient.PeerAddress, DateTime.Now, this.GetInnerMostException(ex).Message); }
public async Task <string> SendAsync(FakeSocket receiver, string message) { QueuedMessage = new QueuedMessage(); QueuedMessage.Target = receiver; QueuedMessage.Message = message; QueuedMessage.Received = false; await _fakeSocketProvider.Send(this); return("OK"); }
public void Queue(QueuedMessage queuedMessage, List <AttachmentData> attachments = null, bool trySendImmediately = true) { if (queuedMessage != null) { _emailSender.AddToQueue(queuedMessage, attachments); if (trySendImmediately) { _emailSender.SendMailMessage(queuedMessage); } } }
async Task SendMessage(Message message) { var queued = new QueuedMessage(message); while (!cancelCts.IsCancellationRequested) { var old = Interlocked.CompareExchange(ref currentMessage, queued, null); if (old == null) { break; } await old.Task.Task; } cancelCts.Token.ThrowIfCancellationRequested(); var doc = message.Write(this); var sb = new StringBuilder(); var settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; using (var writer = XmlWriter.Create(sb, settings)) { doc.WriteTo(writer); } var bytes = new UTF8Encoding().GetBytes(sb.ToString()); var header = BitConverter.GetBytes(bytes.Length); if (bytes.Length == 0) { throw new ServerErrorException(); } await stream.WriteAsync(header, 0, 4).ConfigureAwait(false); await stream.FlushAsync(); await stream.WriteAsync(bytes, 0, bytes.Length); await stream.FlushAsync(); var old2 = Interlocked.CompareExchange(ref currentMessage, null, queued); if (old2 != queued) { throw new ServerErrorException(); } queued.Task.SetResult(true); }
public void Queue(QueuedMessage queuedMessage, bool trySendImmediately = true) { if (queuedMessage != null) { if (trySendImmediately) { _emailSender.SendMailMessage(queuedMessage); } _emailSender.AddToQueue(queuedMessage); } }
public override void respond(String voiceMessage) { if (SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.CAR_STATUS) || SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.DAMAGE_REPORT) || SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.STATUS)) { readStatus(); } else { QueuedMessage damageMessage = null; if (SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.HOWS_MY_AERO)) { damageMessage = getDamageMessage(Component.AERO, true); } if (SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.HOWS_MY_TRANSMISSION)) { damageMessage = getDamageMessage(Component.TRANNY, true); } if (SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.HOWS_MY_ENGINE)) { damageMessage = getDamageMessage(Component.ENGINE, true); } if (SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.HOWS_MY_SUSPENSION)) { damageMessage = getDamageMessage(Component.SUSPENSION, true); } if (SpeechRecogniser.ResultContains(voiceMessage, SpeechRecogniser.HOWS_MY_BRAKES)) { damageMessage = getDamageMessage(Component.BRAKES, true); } if (CrewChief.gameDefinition.gameEnum != GameEnum.IRACING && damageMessage != null) { // play this immediately or play "stand by", and queue it to be played in a few seconds if (delayResponses && Utilities.random.Next(10) >= 2 && SoundCache.availableSounds.Contains(AudioPlayer.folderStandBy)) { audioPlayer.playMessageImmediately(new QueuedMessage(AudioPlayer.folderStandBy, 0)); int secondsDelay = Math.Max(5, Utilities.random.Next(11)); audioPlayer.pauseQueue(secondsDelay); damageMessage.dueTime = (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond) + (1000 * secondsDelay); audioPlayer.playDelayedImmediateMessage(damageMessage); } else { audioPlayer.playMessageImmediately(damageMessage); } } else { audioPlayer.playMessageImmediately(new QueuedMessage(AudioPlayer.folderNoData, 0)); } } }
private void EnqueueSend(ILowProtocol sender, byte[] data) { QueuedMessage message = new QueuedMessage(sender, data); if (m_outgoingMessages.Count == 0) { Monitor.PulseAll(m_outgoingMessages); } m_outgoingMessagesFrequency.Increment(); m_outgoingMessages.Enqueue(message); }
/// <summary> /// Safely dequeues an item from the queue if there are any /// </summary> /// <param name="message"></param> /// <returns></returns> private bool TryRead(out QueuedMessage message) { if (client.sendQueue.Count > 0) { lock (client.sendSyncRoot) { return(client.sendQueue.TryDequeue(out message)); } } message = default(QueuedMessage); return(false); }
public void ResetPasswordService_SetResetPassword_ShouldSaveAQueuedMessage() { var user = new User(); var queuedMessage = new QueuedMessage(); A.CallTo(() => _messageParser.GetMessage(user, null, null, null, null, null, null)) .Returns(queuedMessage); _resetPasswordService.SetResetPassword(user); A.CallTo(() => _messageParser.QueueMessage(queuedMessage, null, true)).MustHaveHappened(); }
public void QueueMessage(PacketId eventName, byte[] data, bool needsAck = false) { var qm = new QueuedMessage { id = eventName, data = data, needsAck = needsAck }; lock (m_OutboundQueue) { m_OutboundQueue.Add(qm); } }
/// <summary> /// Dispatches a notification message produced by a subscription. /// </summary> private void DispatchMessage(Subscription subscription, NotificationMessage notification) { lock (m_messages) { for (LinkedListNode<QueuedRequest> node = m_requests.First; node != null; node = node.Next) { // wake up timed out requests and force them to return a fault. if (node.Value.Timeout < DateTime.UtcNow) { node.Value.Signal.Set(); m_requests.Remove(node); continue; } // check for a request that matches the session. if (Object.ReferenceEquals(subscription.Session, node.Value.Session)) { // pass the message to the request and wake up the request thread. node.Value.SubscriptionId = subscription.Id; node.Value.Message = notification; node.Value.Signal.Set(); m_requests.Remove(node); return; } } // messages only go on the publish queue if no threads are waiting. QueuedMessage message = new QueuedMessage(); message.Session = subscription.Session; message.Subscription = subscription; message.Message = notification; m_messages.AddLast(message); // ensure queue does not grow too large. while (m_messages.Count > 50) { m_messages.RemoveFirst(); } } }
public override void respond(String voiceMessage) { Boolean gotData = false; if (currentGameState != null) { if (voiceMessage.StartsWith(SpeechRecogniser.WHAT_TYRE_IS) || voiceMessage.StartsWith(SpeechRecogniser.WHAT_TYRES_IS)) { Object opponentKey = getOpponentKey(voiceMessage, " on"); if (opponentKey != null) { OpponentData opponentData = currentGameState.OpponentData[opponentKey]; if (opponentData.CurrentTyres == TyreType.Option) { gotData = true; audioPlayer.playClipImmediately(new QueuedMessage(MandatoryPitStops.folderMandatoryPitStopsOptionTyres, 0, null), false); audioPlayer.closeChannel(); } else if (opponentData.CurrentTyres == TyreType.Prime) { gotData = true; audioPlayer.playClipImmediately(new QueuedMessage(MandatoryPitStops.folderMandatoryPitStopsPrimeTyres, 0, null), false); audioPlayer.closeChannel(); } } } if (voiceMessage.StartsWith(SpeechRecogniser.WHATS) && (voiceMessage.EndsWith(SpeechRecogniser.LAST_LAP) || voiceMessage.EndsWith(SpeechRecogniser.BEST_LAP))) { if (voiceMessage.EndsWith(SpeechRecogniser.LAST_LAP)) { float lastLap = getOpponentLastLap(getOpponentKey(voiceMessage, "'s ")); if (lastLap != -1) { gotData = true; audioPlayer.playClipImmediately(new QueuedMessage("opponentLastLap", MessageContents( TimeSpanWrapper.FromSeconds(lastLap, true)), 0, null), false); audioPlayer.closeChannel(); } } else { float bestLap = getOpponentBestLap(getOpponentKey(voiceMessage, "'s ")); if (bestLap != -1) { gotData = true; audioPlayer.playClipImmediately(new QueuedMessage("opponentBestLap", MessageContents( TimeSpanWrapper.FromSeconds(bestLap, true)), 0, null), false); audioPlayer.closeChannel(); } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHERE_IS)) { foreach (KeyValuePair<Object, OpponentData> entry in currentGameState.OpponentData) { String usableDriverName = DriverNameHelper.getUsableNameForRawName(entry.Value.DriverRawName); if (voiceMessage.Contains(usableDriverName)) { Console.WriteLine("Got opponent name, raw name = " + entry.Value.DriverRawName + ", using " + usableDriverName); if (entry.Value.IsActive) { int position = entry.Value.Position; OpponentData.OpponentDelta opponentDelta = entry.Value.getTimeDifferenceToPlayer(currentGameState.SessionData); audioPlayer.playClipImmediately(new QueuedMessage("opponentPosition", MessageContents(Position.folderStub, QueuedMessage.folderNameNumbersStub + position), 0, null), false); if (opponentDelta != null && (opponentDelta.lapDifference != 0 || Math.Abs(opponentDelta.time) > 0.05)) { if (opponentDelta.lapDifference == 1) { audioPlayer.playClipImmediately(new QueuedMessage(Position.folderOneLapBehind, 0, null), false); } else if (opponentDelta.lapDifference > 1) { audioPlayer.playClipImmediately(new QueuedMessage("opponentTimeDelta", MessageContents(QueuedMessage.folderNameNumbersStub + opponentDelta.lapDifference, Position.folderLapsBehind), 0, null), false); } else if (opponentDelta.lapDifference == -1) { audioPlayer.playClipImmediately(new QueuedMessage(Position.folderOneLapAhead, 0, null), false); } else if (opponentDelta.lapDifference < -1) { audioPlayer.playClipImmediately(new QueuedMessage("opponentTimeDelta", MessageContents(QueuedMessage.folderNameNumbersStub + opponentDelta.lapDifference, Position.folderLapsAhead), 0, null), false); } else { TimeSpanWrapper delta = TimeSpanWrapper.FromSeconds(Math.Abs(opponentDelta.time), true); String aheadOrBehind = Position.folderAhead; if (opponentDelta.time < 0) { aheadOrBehind = Position.folderBehind; } audioPlayer.playClipImmediately(new QueuedMessage("opponentTimeDelta", MessageContents(delta, aheadOrBehind), 0, null), false); } } audioPlayer.closeChannel(); gotData = true; } else { Console.WriteLine("Driver "+ entry.Value.DriverRawName + " is no longer active in this session"); } break; } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHOS_BEHIND_ON_TRACK)) { Object opponentKey = currentGameState.getOpponentKeyBehindOnTrack(); if (opponentKey != null) { OpponentData opponent = currentGameState.OpponentData[opponentKey]; QueuedMessage queuedMessage = new QueuedMessage("opponentNameAndPosition", MessageContents(currentGameState.OpponentData[opponentKey], Position.folderStub, QueuedMessage.folderNameNumbersStub + opponent.Position), MessageContents(Position.folderStub, QueuedMessage.folderNameNumbersStub + opponent.Position, folderCantPronounceName), 0, null); if (queuedMessage.canBePlayed) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); gotData = true; } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHOS_IN_FRONT_ON_TRACK) || voiceMessage.StartsWith(SpeechRecogniser.WHOS_AHEAD_ON_TRACK)) { Object opponentKey = currentGameState.getOpponentKeyInFrontOnTrack(); if (opponentKey != null) { OpponentData opponent = currentGameState.OpponentData[opponentKey]; QueuedMessage queuedMessage = new QueuedMessage("opponentName", MessageContents(currentGameState.OpponentData[opponentKey], Position.folderStub, QueuedMessage.folderNameNumbersStub + opponent.Position), MessageContents(Position.folderStub, QueuedMessage.folderNameNumbersStub + opponent.Position, folderCantPronounceName), 0, null); if (queuedMessage.canBePlayed) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); gotData = true; } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHOS_BEHIND_IN_THE_RACE) || voiceMessage.StartsWith(SpeechRecogniser.WHOS_BEHIND)) { if (currentGameState.isLast()) { audioPlayer.playClipImmediately(new QueuedMessage(Position.folderLast, 0, null), false); audioPlayer.closeChannel(); gotData = true; } else { OpponentData opponent = currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position + 1, false); if (opponent != null) { QueuedMessage queuedMessage = new QueuedMessage("opponentName", MessageContents(opponent), MessageContents(folderCantPronounceName), 0, null); if (queuedMessage.canBePlayed) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); gotData = true; } } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHOS_IN_FRONT_IN_THE_RACE) || voiceMessage.StartsWith(SpeechRecogniser.WHOS_AHEAD_IN_THE_RACE) || voiceMessage.StartsWith(SpeechRecogniser.WHOS_IN_FRONT) || voiceMessage.StartsWith(SpeechRecogniser.WHOS_AHEAD)) { if (currentGameState.SessionData.Position == 1) { audioPlayer.playClipImmediately(new QueuedMessage(Position.folderLeading, 0, null), false); audioPlayer.closeChannel(); gotData = true; } else { OpponentData opponent = currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position - 1, false); if (opponent != null) { QueuedMessage queuedMessage = new QueuedMessage("opponentName", MessageContents(opponent), MessageContents(folderCantPronounceName), 0, null); if (queuedMessage.canBePlayed) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); gotData = true; } } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHOS_LEADING) && currentGameState.SessionData.Position > 1) { OpponentData opponent = currentGameState.getOpponentAtPosition(1, false); if (opponent != null) { QueuedMessage queuedMessage = new QueuedMessage("opponentName", MessageContents(opponent), MessageContents(folderCantPronounceName), 0, null); if (queuedMessage.canBePlayed) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); gotData = true; } } } else if (voiceMessage.StartsWith(SpeechRecogniser.WHOS_IN)) { Object opponentKey = getOpponentKey(voiceMessage, ""); if (opponentKey != null) { if (opponentKey == positionIsPlayerKey) { audioPlayer.playClipImmediately(new QueuedMessage(folderWeAre, 0, null), false); audioPlayer.closeChannel(); gotData = true; } else if (currentGameState.OpponentData.ContainsKey(opponentKey)) { OpponentData opponent = currentGameState.OpponentData[opponentKey]; QueuedMessage queuedMessage = new QueuedMessage("opponentName", MessageContents(opponent), MessageContents(folderCantPronounceName), 0, null); if (queuedMessage.canBePlayed) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); gotData = true; } } } } } if (!gotData) { audioPlayer.playClipImmediately(new QueuedMessage(AudioPlayer.folderNoData, 0, null), false); audioPlayer.closeChannel(); } }
protected override void triggerInternal(Shared lastState, Shared currentState) { float currentSpeed = currentState.CarSpeed; float previousSpeed = lastState.CarSpeed; if ((CommonData.isRaceStarted || (enableInQualAndPractice && CommonData.isSessionRunning)) && currentState.Player.GameSimulationTime > timeAfterRaceStartToActivate && currentState.ControlType == (int)Constant.Control.Player && currentSpeed > minSpeedForSpotterToOperate) { float deltaFront = Math.Abs(currentState.TimeDeltaFront); float deltaBehind = Math.Abs(currentState.TimeDeltaBehind); // if we think there's already a car along side, add a little to the car length so we're // sure it's gone before calling clear float carLengthToUse = carLength; if (channelOpen) { carLengthToUse += gapNeededForClear; } // initialise to some large value and put the real value in here only if the // time gap suggests we're overlapping float closingSpeedInFront = 9999; float closingSpeedBehind = 9999; Boolean carAlongSideInFront = carLengthToUse / currentSpeed > deltaFront; Boolean carAlongSideInFrontPrevious = carLengthToUse / previousSpeed > Math.Abs(lastState.TimeDeltaFront); Boolean carAlongSideBehind = carLengthToUse / currentSpeed > deltaBehind; Boolean carAlongSideBehindPrevious = carLengthToUse / previousSpeed > Math.Abs(lastState.TimeDeltaBehind); // only say a car is overlapping if it's been overlapping for 2 game state updates // and the closing speed isn't too high if (carAlongSideInFront) { // check the closing speed before warning closingSpeedInFront = getClosingSpeed(lastState, currentState, true); } if (carAlongSideBehind) { // check the closing speed before warning closingSpeedBehind = getClosingSpeed(lastState, currentState, false); } DateTime now = DateTime.Now; if (channelOpen && !carAlongSideInFront && (!require2ClearsForClear || !carAlongSideInFrontPrevious) && !carAlongSideBehind && (!require2ClearsForClear || !carAlongSideBehindPrevious)) { newlyOverlapping = true; Console.WriteLine("think we're clear, deltaFront = " + deltaFront + " time gap = " + carLengthToUse / currentSpeed); Console.WriteLine("deltaBehind = " + deltaBehind + " time gap = " + carLengthToUse / currentSpeed); Console.WriteLine("race time = " + currentState.Player.GameSimulationTime); if (newlyClear) { Console.WriteLine("Waiting " + clearMessageDelay); newlyClear = false; timeWhenWeThinkWeAreClear = now; } else if (now > timeWhenWeThinkWeAreClear.Add(clearMessageDelay)) { channelOpen = false; audioPlayer.removeImmediateClip(folderHoldYourLine); audioPlayer.removeImmediateClip(folderStillThere); QueuedMessage clearMessage = new QueuedMessage(0, this); clearMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearMessageExpiresAfter; audioPlayer.playClipImmediately(folderClear, clearMessage); audioPlayer.closeChannel(); } } else if ((carAlongSideInFront && (!require2OverlapsForHold || carAlongSideInFrontPrevious) && Math.Abs(closingSpeedInFront) < maxClosingSpeed) || (carAlongSideBehind && (!require2OverlapsForHold || carAlongSideBehindPrevious) && Math.Abs(closingSpeedBehind) < maxClosingSpeed)) { Boolean frontOverlapIsReducing = carAlongSideInFront && closingSpeedInFront > 0; Boolean rearOverlapIsReducing = carAlongSideBehind && closingSpeedBehind > 0; if (channelOpen && now > timeOfLastHoldMessage.Add(repeatHoldFrequency)) { // channel's already open, still there Console.WriteLine("Still there..."); timeOfLastHoldMessage = now; audioPlayer.removeImmediateClip(folderHoldYourLine); audioPlayer.removeImmediateClip(folderClear); QueuedMessage stillThereMessage = new QueuedMessage(0, this); stillThereMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.playClipImmediately(folderStillThere, stillThereMessage); } else if (!channelOpen && (rearOverlapIsReducing || (frontOverlapIsReducing && !spotterOnlyWhenBeingPassed))) { newlyClear = true; if (newlyOverlapping) { timeWhenWeThinkWeAreOverlapping = now; newlyOverlapping = false; } else if (now > timeWhenWeThinkWeAreOverlapping.Add(overlapMessageDelay)) { Console.WriteLine("race time = " + currentState.Player.GameSimulationTime); if (carAlongSideInFront) { Console.WriteLine("new overlap in front, deltaFront = " + deltaFront + " time gap = " + carLengthToUse / currentSpeed + " closing speed = " + closingSpeedInFront); } if (carAlongSideBehind) { Console.WriteLine("new overlap behind, deltaBehind = " + deltaBehind + " time gap = " + carLengthToUse / currentSpeed + " closing speed = " + closingSpeedBehind); } timeOfLastHoldMessage = now; channelOpen = true; audioPlayer.removeImmediateClip(folderClear); audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.openChannel(); QueuedMessage holdMessage = new QueuedMessage(0, this); holdMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.playClipImmediately(folderHoldYourLine, holdMessage); } } } } else if (channelOpen) { channelOpen = false; audioPlayer.closeChannel(); } }
private void playEstimatedTypeLifeMinutes(int minutesRemainingOnTheseTyres, Boolean immediate) { if (immediate) { if (minutesRemainingOnTheseTyres > 59 || minutesRemainingOnTheseTyres > (timeInSession - timeElapsed) / 60) { audioPlayer.playClipImmediately(new QueuedMessage(folderGoodWear, 0, null), false); audioPlayer.closeChannel(); return; } else if (minutesRemainingOnTheseTyres < 1) { audioPlayer.playClipImmediately(new QueuedMessage(folderKnackeredAllRound, 0, null), false); audioPlayer.closeChannel(); return; } } if (minutesRemainingOnTheseTyres < 59 && minutesRemainingOnTheseTyres > 1 && minutesRemainingOnTheseTyres <= (timeInSession - timeElapsed) / 60) { QueuedMessage queuedMessage = new QueuedMessage("minutes_on_current_tyres", MessageContents(folderMinutesOnCurrentTyresIntro, QueuedMessage.folderNameNumbersStub + minutesRemainingOnTheseTyres, folderMinutesOnCurrentTyresOutro), 0, null); if (immediate) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); } else { audioPlayer.queueClip(queuedMessage); } } else if (immediate) { audioPlayer.playClipImmediately(new QueuedMessage(AudioPlayer.folderNoData, 0, null), false); audioPlayer.closeChannel(); } }
private void playEstimatedTypeLifeLaps(int lapsRemainingOnTheseTyres, Boolean immediate) { if (immediate) { if (lapsRemainingOnTheseTyres > 59 || lapsRemainingOnTheseTyres > lapsInSession - completedLaps) { audioPlayer.playClipImmediately(new QueuedMessage(folderGoodWear, 0, null), false); audioPlayer.closeChannel(); return; } else if (lapsRemainingOnTheseTyres < 1) { audioPlayer.playClipImmediately(new QueuedMessage(folderKnackeredAllRound, 0, null), false); audioPlayer.closeChannel(); return; } } if (lapsRemainingOnTheseTyres < 59 && lapsRemainingOnTheseTyres > 1 && lapsRemainingOnTheseTyres <= lapsInSession - completedLaps) { QueuedMessage queuedMessage = new QueuedMessage("laps_on_current_tyres", MessageContents(folderLapsOnCurrentTyresIntro, QueuedMessage.folderNameNumbersStub + lapsRemainingOnTheseTyres, folderLapsOnCurrentTyresOutro), 0, null); if (immediate) { audioPlayer.playClipImmediately(queuedMessage, false); audioPlayer.closeChannel(); } else { audioPlayer.queueClip(queuedMessage); } } else if (immediate) { audioPlayer.playClipImmediately(new QueuedMessage(AudioPlayer.folderNoData, 0, null), false); audioPlayer.closeChannel(); } }
protected override void triggerInternal(GameStateData previousGameState, GameStateData currentGameState) { sessionType = currentGameState.SessionData.SessionType; this.currentGameState = currentGameState; if (currentGameState.SessionData.IsNewLap) { if (currentGameState.SessionData.LapTimePrevious > 0) { if (currentGameState.OpponentData.Count > 0) { if (currentGameState.SessionData.OpponentsLapTimeSessionBestPlayerClass > 0) { deltaPlayerLastToSessionBestInClass = TimeSpan.FromSeconds( currentGameState.SessionData.LapTimePrevious - currentGameState.SessionData.OpponentsLapTimeSessionBestPlayerClass); } if (currentGameState.SessionData.OpponentsLapTimeSessionBestOverall > 0) { deltaPlayerLastToSessionBestOverall = TimeSpan.FromSeconds( currentGameState.SessionData.LapTimePrevious - currentGameState.SessionData.OpponentsLapTimeSessionBestOverall); } } else if (currentGameState.SessionData.PlayerLapTimeSessionBest > 0) { deltaPlayerLastToSessionBestOverall = TimeSpan.FromSeconds(currentGameState.SessionData.LapTimePrevious - currentGameState.SessionData.PlayerLapTimeSessionBest); deltaPlayerLastToSessionBestInClass = deltaPlayerLastToSessionBestOverall; } if (currentGameState.SessionData.LapTimePrevious <= currentGameState.SessionData.PlayerLapTimeSessionBest) { deltaPlayerBestToSessionBestInClass = deltaPlayerLastToSessionBestInClass; deltaPlayerBestToSessionBestOverall = deltaPlayerLastToSessionBestOverall; } } else { // the last lap was invalid so the delta is undefined deltaPlayerLastToSessionBestInClass = TimeSpan.MaxValue; deltaPlayerLastToSessionBestOverall = TimeSpan.MaxValue; } } currentPosition = currentGameState.SessionData.Position; // check the current lap is still valid if (lapIsValid && currentGameState.SessionData.CompletedLaps > 0 && !currentGameState.SessionData.IsNewLap && !currentGameState.SessionData.CurrentLapIsValid) { lapIsValid = false; } if (currentGameState.SessionData.IsNewLap) { lastLapTime = currentGameState.SessionData.LapTimePrevious; if (lastLapTime > 0 && lapIsValid) { if (bestLapTime == 0 || lastLapTime < bestLapTime) { bestLapTime = lastLapTime; } } } float[] lapAndSectorsComparisonData = new float[] { -1, -1, -1, -1 }; if (currentGameState.SessionData.IsNewSector) { isHotLapping = currentGameState.SessionData.SessionType == SessionType.HotLap || (currentGameState.OpponentData.Count == 0 || ( currentGameState.OpponentData.Count == 1 && currentGameState.OpponentData.First().Value.DriverRawName == currentGameState.SessionData.DriverRawName)); if (isHotLapping) { lapAndSectorsComparisonData[1] = currentGameState.SessionData.PlayerBestLapSector1Time; lapAndSectorsComparisonData[2] = currentGameState.SessionData.PlayerBestLapSector2Time; lapAndSectorsComparisonData[3] = currentGameState.SessionData.PlayerBestLapSector3Time; } else { if (currentGameState.SessionData.SessionType == SessionType.Race) { lapAndSectorsComparisonData = currentGameState.getTimeAndSectorsForBestOpponentLapInWindow(paceCheckLapsWindowForRace, currentGameState.carClass.carClassEnum); } else if (currentGameState.SessionData.SessionType == SessionType.Qualify || currentGameState.SessionData.SessionType == SessionType.Practice) { lapAndSectorsComparisonData = currentGameState.getTimeAndSectorsForBestOpponentLapInWindow(-1, currentGameState.carClass.carClassEnum); } } } if (!currentGameState.PitData.OnInLap && !currentGameState.PitData.OnOutLap) { Boolean sectorsReportedForLap = false; if (currentGameState.SessionData.IsNewLap && ((currentGameState.SessionData.SessionType == SessionType.HotLap && currentGameState.SessionData.CompletedLaps > 0) || currentGameState.SessionData.CompletedLaps > 1)) { if (lapTimesWindow == null) { lapTimesWindow = new List<float>(lapTimesWindowSize); } lastLapRating = getLastLapRating(currentGameState, lapAndSectorsComparisonData); if (currentGameState.SessionData.PreviousLapWasValid) { lapTimesWindow.Insert(0, currentGameState.SessionData.LapTimePrevious); if (lapIsValid) { Boolean playedLapTime = false; if (isHotLapping) { // always play the laptime in hotlap mode audioPlayer.queueClip(new QueuedMessage("laptime", MessageContents(folderLapTimeIntro, TimeSpan.FromSeconds(currentGameState.SessionData.LapTimePrevious)), 0, this)); playedLapTime = true; } else if (((currentGameState.SessionData.SessionType == SessionType.Qualify || currentGameState.SessionData.SessionType == SessionType.Practice) && frequencyOfPlayerQualAndPracLapTimeReports > random.NextDouble() * 10) || (currentGameState.SessionData.SessionType == SessionType.Race && frequencyOfPlayerRaceLapTimeReports > random.NextDouble() * 10)) { // usually play it in practice / qual mode, occasionally play it in race mode QueuedMessage gapFillerLapTime = new QueuedMessage("laptime", MessageContents(folderLapTimeIntro, TimeSpan.FromSeconds(currentGameState.SessionData.LapTimePrevious)), 0, this); if (currentGameState.SessionData.SessionType == SessionType.Race) { gapFillerLapTime.maxPermittedQueueLengthForMessage = maxQueueLengthForRaceLapTimeReports; } audioPlayer.queueClip(gapFillerLapTime); playedLapTime = true; } if (currentGameState.SessionData.SessionType == SessionType.Qualify || currentGameState.SessionData.SessionType == SessionType.Practice || currentGameState.SessionData.SessionType == SessionType.HotLap) { if (currentGameState.SessionData.SessionType == SessionType.HotLap || currentGameState.OpponentData.Count == 0) { if (lastLapRating == LastLapRating.BEST_IN_CLASS || deltaPlayerLastToSessionBestOverall <= TimeSpan.Zero) { audioPlayer.queueClip(new QueuedMessage(folderPersonalBest, 0, this)); } else if (deltaPlayerLastToSessionBestOverall < TimeSpan.FromMilliseconds(50)) { audioPlayer.queueClip(new QueuedMessage(folderLessThanATenthOffThePace, 0, this)); } else if (deltaPlayerLastToSessionBestOverall < TimeSpan.MaxValue) { audioPlayer.queueClip(new QueuedMessage("lapTimeNotRaceGap", MessageContents(folderGapIntro, deltaPlayerLastToSessionBestOverall, folderGapOutroOffPace), 0, this)); } if (practiceAndQualSectorReportsLapEnd && frequencyOfPracticeAndQualSectorDeltaReports > random.NextDouble() * 10) { List<MessageFragment> sectorMessageFragments = getSectorDeltaMessages(SectorReportOption.COMBINED, currentGameState.SessionData.LastSector1Time, lapAndSectorsComparisonData[1], currentGameState.SessionData.LastSector2Time, lapAndSectorsComparisonData[2], currentGameState.SessionData.LastSector3Time, lapAndSectorsComparisonData[3], true); if (sectorMessageFragments.Count > 0) { audioPlayer.queueClip(new QueuedMessage("sectorsHotLap", sectorMessageFragments, 0, this)); sectorsReportedForLap = true; } } } // need to be careful with the rating here as it's based on the known opponent laps, and we may have joined the session part way through else if (currentGameState.SessionData.Position == 1) { Boolean newGapToSecond = false; if (previousGameState != null && previousGameState.SessionData.Position > 1) { newGapToSecond = true; if (currentGameState.SessionData.SessionType == SessionType.Qualify) { audioPlayer.queueClip(new QueuedMessage(Position.folderPole, 0, this)); } else if (currentGameState.SessionData.SessionType == SessionType.Practice) { audioPlayer.queueClip(new QueuedMessage(Position.folderStub + 1, 0, this)); } } else if (deltaPlayerLastToSessionBestOverall < lastGapToSecondWhenLeadingPracOrQual) { newGapToSecond = true; lastGapToSecondWhenLeadingPracOrQual = deltaPlayerLastToSessionBestOverall; } if (newGapToSecond) { lastGapToSecondWhenLeadingPracOrQual = deltaPlayerLastToSessionBestOverall; TimeSpan gapBehind = deltaPlayerLastToSessionBestOverall.Negate(); // only play qual / prac deltas for Raceroom as the PCars data is inaccurate for sessions joined part way through if ((!disablePCarspracAndQualPoleDeltaReports || CrewChief.gameDefinition.gameEnum == GameDefinition.raceRoom.gameEnum) && ((gapBehind.Seconds > 0 || gapBehind.Milliseconds > 50) && gapBehind.Seconds < 60)) { // delay this a bit... audioPlayer.queueClip(new QueuedMessage("lapTimeNotRaceGap", MessageContents(folderGapIntro, gapBehind, folderQuickerThanSecondPlace), random.Next(0, 20), this)); } } } else { if (lastLapRating == LastLapRating.PERSONAL_BEST_STILL_SLOW || lastLapRating == LastLapRating.PERSONAL_BEST_CLOSE_TO_CLASS_LEADER || lastLapRating == LastLapRating.PERSONAL_BEST_CLOSE_TO_OVERALL_LEADER) { audioPlayer.queueClip(new QueuedMessage(folderPersonalBest, 0, this)); } // don't read this message if the rounded time gap is 0.0 seconds or it's more than 59 seconds // only play qual / prac deltas for Raceroom as the PCars data is inaccurate for sessions joined part way through if ((!disablePCarspracAndQualPoleDeltaReports || CrewChief.gameDefinition.gameEnum == GameDefinition.raceRoom.gameEnum) && (deltaPlayerLastToSessionBestInClass.Seconds > 0 || deltaPlayerLastToSessionBestInClass.Milliseconds > 50) && deltaPlayerLastToSessionBestInClass.Seconds < 60) { // delay this a bit... audioPlayer.queueClip(new QueuedMessage("lapTimeNotRaceGap", MessageContents(folderGapIntro, deltaPlayerLastToSessionBestInClass, folderGapOutroOffPace), random.Next(0, 20), this)); } if (practiceAndQualSectorReportsLapEnd && frequencyOfPracticeAndQualSectorDeltaReports > random.NextDouble() * 10) { List<MessageFragment> sectorMessageFragments = getSectorDeltaMessages(SectorReportOption.COMBINED, currentGameState.SessionData.LastSector1Time, lapAndSectorsComparisonData[1], currentGameState.SessionData.LastSector2Time, lapAndSectorsComparisonData[2], currentGameState.SessionData.LastSector3Time, lapAndSectorsComparisonData[3], true); if (sectorMessageFragments.Count > 0) { audioPlayer.queueClip(new QueuedMessage("sectorsQual", sectorMessageFragments, 0, this)); sectorsReportedForLap = true; } } } } else if (currentGameState.SessionData.SessionType == SessionType.Race) { Boolean playedLapMessage = false; if (frequencyOfPlayerRaceLapTimeReports > random.NextDouble() * 10) { float pearlLikelihood = 0.8f; switch (lastLapRating) { case LastLapRating.BEST_OVERALL: playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderBestLapInRace, 0, this), PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.BEST_IN_CLASS: playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderBestLapInRaceForClass, 0, this), PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.SETTING_CURRENT_PACE: playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderSettingCurrentRacePace, 0, this), PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.CLOSE_TO_CURRENT_PACE: // don't keep playing this one if (random.NextDouble() < 0.5) { playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderMatchingCurrentRacePace, 0, this), PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); } break; case LastLapRating.PERSONAL_BEST_CLOSE_TO_OVERALL_LEADER: case LastLapRating.PERSONAL_BEST_CLOSE_TO_CLASS_LEADER: playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderGoodLap, 0, this), PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.PERSONAL_BEST_STILL_SLOW: playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderPersonalBest, 0, this), PearlsOfWisdom.PearlType.NEUTRAL, pearlLikelihood); break; case LastLapRating.CLOSE_TO_OVERALL_LEADER: case LastLapRating.CLOSE_TO_CLASS_LEADER: // this is an OK lap but not a PB. We only want to say "decent lap" occasionally here if (random.NextDouble() < 0.2) { playedLapMessage = true; audioPlayer.queueClip(new QueuedMessage(folderGoodLap, 0, this), PearlsOfWisdom.PearlType.NEUTRAL, pearlLikelihood); } break; default: break; } } if (raceSectorReportsAtLapEnd && frequencyOfRaceSectorDeltaReports > random.NextDouble() * 10) { double r = random.NextDouble(); SectorReportOption reportOption = SectorReportOption.COMBINED; if (playedLapTime && playedLapMessage) { // if we've already played a laptime and lap rating, use the short sector message. reportOption = SectorReportOption.WORST_ONLY; } else if (r > 0.6 || ((playedLapTime || playedLapMessage) && r > 0.3)) { // if we've played one of these, usually use the abbrieviated version. If we've played neither, sometimes use the abbrieviated version reportOption = SectorReportOption.BEST_AND_WORST; } List<MessageFragment> sectorMessageFragments = getSectorDeltaMessages(reportOption, currentGameState.SessionData.LastSector1Time, lapAndSectorsComparisonData[1], currentGameState.SessionData.LastSector2Time, lapAndSectorsComparisonData[2], currentGameState.SessionData.LastSector3Time, lapAndSectorsComparisonData[3], false); if (sectorMessageFragments.Count > 0) { QueuedMessage message = new QueuedMessage("sectorsRace", sectorMessageFragments, 0, this); message.maxPermittedQueueLengthForMessage = maxQueueLengthForRaceSectorDeltaReports; audioPlayer.queueClip(message); sectorsReportedForLap = true; } } // play the consistency message if we've not played the good lap message, or sometimes // play them both Boolean playConsistencyMessage = !playedLapMessage || random.NextDouble() < 0.25; if (playConsistencyMessage && currentGameState.SessionData.CompletedLaps >= lastConsistencyUpdate + lapTimesWindowSize && lapTimesWindow.Count >= lapTimesWindowSize) { ConsistencyResult consistency = checkAgainstPreviousLaps(); if (consistency == ConsistencyResult.CONSISTENT) { lastConsistencyUpdate = currentGameState.SessionData.CompletedLaps; audioPlayer.queueClip(new QueuedMessage(folderConsistentTimes, random.Next(0, 20), this)); } else if (consistency == ConsistencyResult.IMPROVING) { lastConsistencyUpdate = currentGameState.SessionData.CompletedLaps; audioPlayer.queueClip(new QueuedMessage(folderImprovingTimes, random.Next(0, 20), this)); } else if (consistency == ConsistencyResult.WORSENING) { // don't play the worsening message if the lap rating is good if (lastLapRating == LastLapRating.BEST_IN_CLASS || lastLapRating == LastLapRating.BEST_OVERALL || lastLapRating == LastLapRating.SETTING_CURRENT_PACE || lastLapRating == LastLapRating.CLOSE_TO_CURRENT_PACE) { Console.WriteLine("Skipping 'worsening' laptimes message - inconsistent with lap rating"); } else { lastConsistencyUpdate = currentGameState.SessionData.CompletedLaps; audioPlayer.queueClip(new QueuedMessage(folderWorseningTimes, random.Next(0, 20), this)); } } } } } } lapIsValid = true; } // report sector delta at the completion of a sector? if (!sectorsReportedForLap && currentGameState.SessionData.IsNewSector && ((currentGameState.SessionData.SessionType == SessionType.Race && raceSectorReportsAtEachSector) || (currentGameState.SessionData.SessionType != SessionType.Race && practiceAndQualSectorReportsAtEachSector))) { double r = random.NextDouble() * 10; Boolean canPlayForRace = frequencyOfRaceSectorDeltaReports > r; Boolean canPlayForPracAndQual = frequencyOfPracticeAndQualSectorDeltaReports > r; if ((currentGameState.SessionData.SessionType == SessionType.Race && canPlayForRace) || ((currentGameState.SessionData.SessionType == SessionType.Practice || currentGameState.SessionData.SessionType == SessionType.Qualify || currentGameState.SessionData.SessionType == SessionType.HotLap) && canPlayForPracAndQual)) { float playerSector = -1; float comparisonSector = -1; SectorSet sectorEnum = SectorSet.NONE; switch (currentGameState.SessionData.SectorNumber) { case 1: playerSector = currentGameState.SessionData.LastSector3Time; comparisonSector = lapAndSectorsComparisonData[3]; sectorEnum = SectorSet.THREE; break; case 2: playerSector = currentGameState.SessionData.LastSector1Time; comparisonSector = lapAndSectorsComparisonData[1]; sectorEnum = SectorSet.ONE; break; case 3: playerSector = currentGameState.SessionData.LastSector2Time; comparisonSector = lapAndSectorsComparisonData[2]; sectorEnum = SectorSet.TWO; break; } if (playerSector > 0 && comparisonSector > 0) { String folder = getFolderForSectorCombination(getEnumForSectorDelta(playerSector - comparisonSector, currentGameState.SessionData.SessionType != SessionType.Race), sectorEnum); if (folder != null) { audioPlayer.queueClip(new QueuedMessage(folder, random.Next(2, 4), this)); } } } } } }
// ReSharper disable once UnusedMethodReturnValue.Local private async Task ProcessQueuedMessage(QueuedMessage queuedMessage, CancellationToken cancellationToken) { var attemptsRemaining = _maxAttempts; while (attemptsRemaining > 0) { attemptsRemaining--; var message = queuedMessage.Message; var context = new InMemoryQueuedMessageContext(message, queuedMessage.SenderPrincipal); cancellationToken.ThrowIfCancellationRequested(); await _concurrentMessageProcessingSlot.WaitAsync(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); try { await _listener.MessageReceived(message, context, cancellationToken); if (_autoAcknowledge && !context.Acknowledged) { await context.Acknowledge(); } } catch (Exception ex) { Log.WarnFormat("Unhandled exception handling queued message {0}", ex, queuedMessage.Message.Headers.MessageId); } finally { _concurrentMessageProcessingSlot.Release(); } if (context.Acknowledged) { // TODO: Implement journaling break; } if (attemptsRemaining > 0) { await Task.Delay(_retryDelay, cancellationToken); } } }
protected override void triggerInternal(GameStateData previousGameState, GameStateData currentGameState) { isLeading = currentGameState.SessionData.Position == 1; isLast = currentGameState.isLast(); isRace = currentGameState.SessionData.SessionType == SessionType.Race; currentGapInFront = currentGameState.SessionData.TimeDeltaFront; currentGapBehind = currentGameState.SessionData.TimeDeltaBehind; if (currentGameState.SessionData.IsNewLap) { playedGapBehindForThisLap = false; } if (gapsInFront == null || gapsBehind == null) { clearState(); } if (!currentGameState.SessionData.IsRacingSameCarInFront) { gapsInFront.Clear(); } if (!currentGameState.SessionData.IsRacingSameCarBehind) { gapsBehind.Clear(); } if (!currentGameState.PitData.InPitlane && enableGapMessages) { if (isRace && !CrewChief.readOpponentDeltasForEveryLap && currentGameState.SessionData.IsNewSector) { sectorsSinceLastGapAheadReport++; sectorsSinceLastGapBehindReport++; sectorsSinceLastCloseCarAheadReport++; sectorsSinceLastCloseCarBehindReport++; GapStatus gapInFrontStatus = GapStatus.NONE; GapStatus gapBehindStatus = GapStatus.NONE; if (currentGameState.SessionData.Position != 1) { gapsInFront.Insert(0, currentGameState.SessionData.TimeDeltaFront); gapInFrontStatus = getGapStatus(gapsInFront, gapInFrontAtLastReport); } if (!isLast) { gapsBehind.Insert(0, currentGameState.SessionData.TimeDeltaBehind); gapBehindStatus = getGapStatus(gapsBehind, gapBehindAtLastReport); } // Play which ever is the smaller gap, but we're not interested if the gap is < 0.5 or > 20 seconds or hasn't changed: Boolean playGapInFront = gapInFrontStatus != GapStatus.NONE && (gapBehindStatus == GapStatus.NONE || (gapsInFront.Count() > 0 && gapsBehind.Count() > 0 && gapsInFront[0] < gapsBehind[0])); Boolean playGapBehind = !playGapInFront && gapBehindStatus != GapStatus.NONE; if (playGapInFront) { if (gapInFrontStatus == GapStatus.CLOSE) { if (sectorsSinceLastCloseCarAheadReport >= sectorsUntilNextCloseCarAheadReport) { sectorsSinceLastCloseCarAheadReport = 0; sectorsUntilNextCloseCarAheadReport = adjustForMidLapPreference(currentGameState.SessionData.SectorNumber, rand.Next(closeAheadMinSectorWait, closeAheadMaxSectorWait)); audioPlayer.queueClip(new QueuedMessage(folderBeingHeldUp, 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); gapInFrontAtLastReport = gapsInFront[0]; } } else if (gapInFrontStatus != GapStatus.NONE && sectorsSinceLastGapAheadReport >= sectorsUntilNextGapAheadReport) { sectorsSinceLastGapAheadReport = 0; sectorsUntilNextGapAheadReport = adjustForMidLapPreference(currentGameState.SessionData.SectorNumber, rand.Next(gapAheadMinSectorWait, gapAheadMaxSectorWait)); TimeSpanWrapper gapInFront = TimeSpanWrapper.FromMilliseconds(gapsInFront[0] * 1000, true); Boolean readGap = gapInFront.timeSpan.Seconds > 0 || gapInFront.timeSpan.Milliseconds > 50; if (readGap) { if (gapInFrontStatus == GapStatus.INCREASING) { audioPlayer.queueClip(new QueuedMessage("Timings/gap_in_front", MessageContents(folderTheGapTo, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position - 1, false), folderAheadIsIncreasing, gapInFront), MessageContents(folderGapInFrontIncreasing, gapInFront), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); } else if (gapInFrontStatus == GapStatus.DECREASING) { audioPlayer.queueClip(new QueuedMessage("Timings/gap_in_front", MessageContents(folderYoureReeling, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position - 1, false), folderInTheGapIsNow, gapInFront), MessageContents(folderGapInFrontDecreasing, gapInFront), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); } else if (gapInFrontStatus == GapStatus.OTHER) { audioPlayer.queueClip(new QueuedMessage("Timings/gap_in_front", MessageContents(folderTheGapTo, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position - 1, false), folderAheadIsNow, gapInFront), MessageContents(folderGapInFrontIsNow, gapInFront), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); } } gapInFrontAtLastReport = gapsInFront[0]; } } else if (playGapBehind) { if (gapBehindStatus == GapStatus.CLOSE) { if (sectorsSinceLastCloseCarBehindReport >= sectorsUntilNextCloseCarBehindReport) { sectorsSinceLastCloseCarBehindReport = 0; sectorsUntilNextCloseCarBehindReport = adjustForMidLapPreference(currentGameState.SessionData.SectorNumber, rand.Next(closeBehindMinSectorWait, closeBehindMaxSectorWait)); audioPlayer.queueClip(new QueuedMessage(folderBeingPressured, 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); gapBehindAtLastReport = gapsBehind[0]; } } else if (gapBehindStatus != GapStatus.NONE && sectorsSinceLastGapBehindReport >= sectorsUntilNextGapBehindReport) { sectorsSinceLastGapBehindReport = 0; sectorsUntilNextGapBehindReport = adjustForMidLapPreference(currentGameState.SessionData.SectorNumber, rand.Next(gapBehindMinSectorWait, gapBehindMaxSectorWait)); TimeSpanWrapper gapBehind = TimeSpanWrapper.FromMilliseconds(gapsBehind[0] * 1000, true); Boolean readGap = gapBehind.timeSpan.Seconds > 0 || gapBehind.timeSpan.Milliseconds > 50; if (readGap) { if (gapBehindStatus == GapStatus.INCREASING) { audioPlayer.queueClip(new QueuedMessage("Timings/gap_behind", MessageContents(folderTheGapTo, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position + 1, false), folderBehindIsIncreasing, gapBehind), MessageContents(folderGapBehindIncreasing, gapBehind), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); } else if (gapBehindStatus == GapStatus.DECREASING) { audioPlayer.queueClip(new QueuedMessage("Timings/gap_behind", MessageContents(currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position + 1, false), folderIsReelingYouIn, gapBehind), MessageContents(folderGapBehindDecreasing, gapBehind), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); } else if (gapBehindStatus == GapStatus.OTHER) { audioPlayer.queueClip(new QueuedMessage("Timings/gap_behind", MessageContents(folderTheGapTo, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position + 1, false), folderBehindIsNow, gapBehind), MessageContents(folderGapBehindIsNow, gapBehind), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } })); } } gapBehindAtLastReport = gapsBehind[0]; } } } if (isRace && CrewChief.readOpponentDeltasForEveryLap && currentGameState.SessionData.CompletedLaps > 0) { if (currentGameState.SessionData.Position > 1 && currentGameState.SessionData.IsNewLap) { if (currentGapInFront > 0.05) { TimeSpanWrapper gap = TimeSpanWrapper.FromSeconds(currentGapInFront, true); QueuedMessage message = new QueuedMessage("Timings/gap_ahead", MessageContents(folderTheGapTo, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position - 1, false), folderAheadIsNow, gap), MessageContents(folderGapInFrontIsNow, gap), 0, this, new Dictionary<string,object>{ {"position", currentGameState.SessionData.Position} }); message.playEvenWhenSilenced = true; audioPlayer.queueClip(message); } } if (!currentGameState.isLast()) { if (!playedGapBehindForThisLap && currentGapBehind > 0.05 && currentGameState.SessionData.LapTimeCurrent > 0 && currentGameState.SessionData.LapTimeCurrent >= currentGapBehind && currentGameState.SessionData.LapTimeCurrent <= currentGapBehind + CrewChief._timeInterval.TotalSeconds) { playedGapBehindForThisLap = true; TimeSpanWrapper gap = TimeSpanWrapper.FromSeconds(currentGapBehind, true); QueuedMessage message = new QueuedMessage("Timings/gap_behind", MessageContents(folderTheGapTo, currentGameState.getOpponentAtPosition(currentGameState.SessionData.Position + 1, false), folderBehindIsNow, gap), MessageContents(folderGapBehindIsNow, gap), 0, this, new Dictionary<string, object> { { "position", currentGameState.SessionData.Position } }); message.playEvenWhenSilenced = true; audioPlayer.queueClip(message); } } } } }
protected override void triggerInternal(Data.Shared lastState, Data.Shared currentState) { if (currentState.LapTimeBest > 0) { sessionBestLapTimeDeltaToLeader = TimeSpan.FromSeconds(currentState.LapTimeBest - getLapTimeBestForClassLeader(currentState)); } else { sessionBestLapTimeDeltaToLeader = TimeSpan.MaxValue; } if (currentState.LapTimePrevious > 0) { currentLapTimeDeltaToLeadersBest = TimeSpan.FromSeconds(currentState.LapTimePrevious - getLapTimeBestForClassLeader(currentState)); } else { // the last lap was invalid so the delta is undefined currentLapTimeDeltaToLeadersBest = TimeSpan.MaxValue; } currentPosition = currentState.Position; // in race sessions (race only) the LapTimePrevious isn't set to -1 if that lap was invalid, so // we need to record that it's invalid while we're actually on the lap if (CommonData.isSessionRunning && lapIsValid && currentState.CompletedLaps > 0 && !CommonData.isNewLap && currentState.LapTimeCurrent == -1) { lapIsValid = false; } if (CommonData.isSessionRunning && CommonData.isNewLap) { lastLapTime = currentState.LapTimePrevious; } if (CommonData.isSessionRunning && CommonData.isNewLap && !CommonData.isInLap && !CommonData.isOutLap && ((CommonData.isHotLapping && currentState.CompletedLaps > 0) || currentState.CompletedLaps > 1)) { if (lapTimesWindow == null) { lapTimesWindow = new List<float>(lapTimesWindowSize); } // this might be NO_DATA lastLapRating = getLastLapRating(currentState); if (currentState.LapTimePrevious > 0) { lapTimesWindow.Insert(0, currentState.LapTimePrevious); if (lapIsValid) { // queue the actual laptime as a 'gap filler' - this is only played if the // queue would otherwise be empty if (enableLapTimeMessages && readLapTimes && !CommonData.isHotLapping) { QueuedMessage gapFillerLapTime = new QueuedMessage(folderLapTimeIntro, null, TimeSpan.FromSeconds(currentState.LapTimePrevious), 0, this); gapFillerLapTime.gapFiller = true; audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "laptime", gapFillerLapTime); } if (enableLapTimeMessages && currentState.SessionType == (int)Constant.Session.Qualify || currentState.SessionType == (int)Constant.Session.Practice) { if (CommonData.isHotLapping) { // special case for hot lapping - read best lap message and the laptime audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "laptime", new QueuedMessage(folderLapTimeIntro, null, TimeSpan.FromSeconds(currentState.LapTimePrevious), 0, this)); if (lastLapRating == LastLapRating.BEST_IN_CLASS || currentLapTimeDeltaToLeadersBest <= TimeSpan.Zero) { audioPlayer.queueClip(folderPersonalBest, 0, this); } else if (currentLapTimeDeltaToLeadersBest < TimeSpan.FromMilliseconds(50)) { audioPlayer.queueClip(folderLessThanATenthOffThePace, 0, this); } else if (currentLapTimeDeltaToLeadersBest < TimeSpan.MaxValue) { audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "_lapTimeNotRaceGap", new QueuedMessage(folderGapIntro, folderGapOutroOffPace, currentLapTimeDeltaToLeadersBest, 0, this)); } } else if (lastLapRating == LastLapRating.BEST_IN_CLASS) { audioPlayer.queueClip(folderFastestInClass, 0, this); if (sessionBestLapTimeDeltaToLeader < TimeSpan.Zero) { TimeSpan gapBehind = sessionBestLapTimeDeltaToLeader.Negate(); if ((gapBehind.Seconds > 0 || gapBehind.Milliseconds > 50) && gapBehind.Seconds < 60) { // delay this a bit... audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "_lapTimeNotRaceGap", new QueuedMessage(folderGapIntro, folderQuickerThanSecondPlace, gapBehind, random.Next(0, 20), this)); } } } else if (lastLapRating == LastLapRating.BEST_OVERALL) { if (currentState.SessionType == (int)Constant.Session.Qualify) { audioPlayer.queueClip(Position.folderPole, 0, this); } else if (currentState.SessionType == (int)Constant.Session.Practice) { audioPlayer.queueClip(Position.folderStub + currentState.Position, 0, this); } if (sessionBestLapTimeDeltaToLeader < TimeSpan.Zero) { TimeSpan gapBehind = sessionBestLapTimeDeltaToLeader.Negate(); if ((gapBehind.Seconds > 0 || gapBehind.Milliseconds > 50) && gapBehind.Seconds < 60) { // delay this a bit... audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "_lapTimeNotRaceGap", new QueuedMessage(folderGapIntro, folderQuickerThanSecondPlace, gapBehind, random.Next(0, 20), this)); } } } else { if (lastLapRating == LastLapRating.PERSONAL_BEST_STILL_SLOW || lastLapRating == LastLapRating.PERSONAL_BEST_CLOSE_TO_CLASS_LEADER || lastLapRating == LastLapRating.PERSONAL_BEST_CLOSE_TO_OVERALL_LEADER) { audioPlayer.queueClip(folderPersonalBest, 0, this); } if (getLapTimeBestForClassLeader(currentState) > 0) { // don't read this message if the rounded time gap is 0.0 seconds or it's more than 59 seconds if ((sessionBestLapTimeDeltaToLeader.Seconds > 0 || sessionBestLapTimeDeltaToLeader.Milliseconds > 50) && sessionBestLapTimeDeltaToLeader.Seconds < 60) { // delay this a bit... audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "_lapTimeNotRaceGap", new QueuedMessage(folderGapIntro, folderGapOutroOffPace, sessionBestLapTimeDeltaToLeader, random.Next(0, 20), this)); } } } } else if (enableLapTimeMessages && CommonData.isRaceRunning) { Boolean playedLapMessage = false; float pearlLikelihood = 0.8f; switch (lastLapRating) { case LastLapRating.BEST_OVERALL: playedLapMessage = true; audioPlayer.queueClip(folderBestLapInRace, 0, this, PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.BEST_IN_CLASS: playedLapMessage = true; audioPlayer.queueClip(folderBestLapInRaceForClass, 0, this, PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.PERSONAL_BEST_CLOSE_TO_OVERALL_LEADER: case LastLapRating.PERSONAL_BEST_CLOSE_TO_CLASS_LEADER: playedLapMessage = true; audioPlayer.queueClip(folderGoodLap, 0, this, PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); break; case LastLapRating.PERSONAL_BEST_STILL_SLOW: playedLapMessage = true; audioPlayer.queueClip(folderPersonalBest, 0, this, PearlsOfWisdom.PearlType.NEUTRAL, pearlLikelihood); break; case LastLapRating.CLOSE_TO_OVERALL_LEADER: case LastLapRating.CLOSE_TO_CLASS_LEADER: // this is an OK lap but not a PB. We only want to say "decent lap" occasionally here if (random.NextDouble() > 0.8) { playedLapMessage = true; audioPlayer.queueClip(folderGoodLap, 0, this, PearlsOfWisdom.PearlType.NEUTRAL, pearlLikelihood); } break; default: break; } // play the consistency message if we've not played the good lap message, or sometimes // play them both Boolean playConsistencyMessage = !playedLapMessage || random.NextDouble() < 0.25; if (playConsistencyMessage && currentState.CompletedLaps >= lastConsistencyUpdate + lapTimesWindowSize && lapTimesWindow.Count >= lapTimesWindowSize) { ConsistencyResult consistency = checkAgainstPreviousLaps(); if (consistency == ConsistencyResult.CONSISTENT) { lastConsistencyUpdate = currentState.CompletedLaps; audioPlayer.queueClip(folderConsistentTimes, 0, this); } else if (consistency == ConsistencyResult.IMPROVING) { lastConsistencyUpdate = currentState.CompletedLaps; audioPlayer.queueClip(folderImprovingTimes, 0, this); } if (consistency == ConsistencyResult.WORSENING) { lastConsistencyUpdate = currentState.CompletedLaps; audioPlayer.queueClip(folderWorseningTimes, 0, this); } } } } } lapIsValid = true; } }
protected override void triggerInternal(Data.Shared lastState, Data.Shared currentState) { // in race sessions (race only) the previousLapTime isn't set to -1 if that lap was invalid, so // we need to record that it's invalid while we're actually on the lap if (CommonData.isSessionRunning && lapIsValid && currentState.CompletedLaps > 0 && !CommonData.isNewLap && currentState.LapTimeCurrent == -1) { lapIsValid = false; } if (CommonData.isSessionRunning && CommonData.isNewLap && currentState.CompletedLaps > 1 && currentState.LapTimePrevious > 0) { if (lapTimesWindow == null) { lapTimesWindow = new List<float>(lapTimesWindowSize); } lapTimesWindow.Insert(0, currentState.LapTimePrevious); Boolean playedLapMessage = false; if (lapIsValid) { // queue the actual laptime as a 'gap filler' - this is only played if the // queue would otherwise be empty if (readLapTimes) { QueuedMessage gapFillerLapTime = new QueuedMessage(folderLapTimeIntro, null, TimeSpan.FromSeconds(currentState.LapTimePrevious), 0, this); gapFillerLapTime.gapFiller = true; audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "laptime", gapFillerLapTime); } LastLapRating lastLapRating = getLastLapRating(currentState); float currentLapTimeDeltaToLeader = getLapTimeDeltaToClassLeader(currentState); if (lapDeltaToClassLeaderAtLastLap != -1 && (currentState.SessionType == (int)Constant.Session.Qualify || currentState.SessionType == (int)Constant.Session.Practice)) { if (readLapTimes) { audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "_lapTimeNotRaceTime", new QueuedMessage(folderLapTimeIntro, null, TimeSpan.FromSeconds(currentState.LapTimePrevious), 0, this)); } // time delta to leader has changed to report it if (lapDeltaToClassLeaderAtLastLap != currentLapTimeDeltaToLeader) { if (lastLapRating == LastLapRating.BEST_IN_CLASS) { audioPlayer.queueClip(folderFastestInClass, 0, this); } else if (lastLapRating == LastLapRating.BEST_OVERALL) { if (currentState.SessionType == (int)Constant.Session.Qualify) { audioPlayer.queueClip(Position.folderPole, 0, this); } } else if (getLapTimeBestForClassLeader(currentState) > 0) { // don't read this message if the rounded time gap is 0.0 seconds TimeSpan gap = TimeSpan.FromSeconds(currentState.LapTimeBest - getLapTimeBestForClassLeader(currentState)); if (gap.Seconds > 0 || gap.Milliseconds > 50) { // delay this a bit... audioPlayer.queueClip(QueuedMessage.compoundMessageIdentifier + "_lapTimeNotRaceGap", new QueuedMessage(folderGapIntro, folderGapOutroOffPace, gap, new Random().Next(0, 20), this)); } } } else if (lastLapRating == LastLapRating.PERSONAL_BEST_STILL_SLOW || lastLapRating == LastLapRating.PERSONAL_BEST_CLOSE_TO_CLASS_LEADER || lastLapRating == LastLapRating.PERSONAL_BEST_CLOSE_TO_OVERALL_LEADER) { audioPlayer.queueClip(folderPersonalBest, 0, this); } } else { float pearlLikelihood = 0f; if (CommonData.isRaceStarted) { pearlLikelihood = 0.8f; } switch (lastLapRating) { case LastLapRating.BEST_OVERALL: audioPlayer.queueClip(folderBestLapInRace, 0, this, PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); playedLapMessage = true; break; case LastLapRating.BEST_IN_CLASS: audioPlayer.queueClip(folderBestLapInRaceForClass, 0, this, PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); playedLapMessage = true; break; case LastLapRating.PERSONAL_BEST_CLOSE_TO_OVERALL_LEADER: case LastLapRating.PERSONAL_BEST_CLOSE_TO_CLASS_LEADER: audioPlayer.queueClip(folderGoodLap, 0, this, PearlsOfWisdom.PearlType.GOOD, pearlLikelihood); playedLapMessage = true; break; case LastLapRating.PERSONAL_BEST_STILL_SLOW: audioPlayer.queueClip(folderPersonalBest, 0, this, PearlsOfWisdom.PearlType.NEUTRAL, pearlLikelihood); playedLapMessage = true; break; case LastLapRating.CLOSE_TO_OVERALL_LEADER: case LastLapRating.CLOSE_TO_CLASS_LEADER: audioPlayer.queueClip(folderGoodLap, 0, this, PearlsOfWisdom.PearlType.NEUTRAL, pearlLikelihood); playedLapMessage = true; break; default: break; } } lapDeltaToClassLeaderAtLastLap = currentLapTimeDeltaToLeader; } if (currentState.SessionType == (int)Constant.Session.Race && !playedLapMessage && currentState.CompletedLaps >= lastConsistencyUpdate + lapTimesWindowSize && lapTimesWindow.Count >= lapTimesWindowSize) { ConsistencyResult consistency = checkAgainstPreviousLaps(); if (consistency == ConsistencyResult.CONSISTENT) { lastConsistencyUpdate = currentState.CompletedLaps; audioPlayer.queueClip(folderConsistentTimes, 0, this); } else if (consistency == ConsistencyResult.IMPROVING) { lastConsistencyUpdate = currentState.CompletedLaps; audioPlayer.queueClip(folderImprovingTimes, 0, this); } if (consistency == ConsistencyResult.WORSENING) { lastConsistencyUpdate = currentState.CompletedLaps; audioPlayer.queueClip(folderWorseningTimes, 0, this); } } lapIsValid = true; } }
private void playNextMessage(int carsOnLeftCount, int carsOnRightCount, DateTime now) { if (nextMessageType != NextMessageType.none && now > nextMessageDue) { if (messageIsValid(nextMessageType, carsOnLeftCount, carsOnRightCount)) { switch (nextMessageType) { case NextMessageType.threeWide: audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderClearAllRound); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearRight); QueuedMessage inTheMiddleMessage = new QueuedMessage(folderInTheMiddle, 0, null); inTheMiddleMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + inTheMiddleMessageExpiresAfter; audioPlayer.playClipImmediately(inTheMiddleMessage, true, true); nextMessageType = NextMessageType.stillThere; nextMessageDue = now.Add(repeatHoldFrequency); reportedOverlapLeft = true; reportedOverlapRight = true; wasInMiddle = true; break; case NextMessageType.carLeft: audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderClearAllRound); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearRight); QueuedMessage carLeftMessage = new QueuedMessage(folderCarLeft, 0, null); carLeftMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.playClipImmediately(carLeftMessage, true, true); nextMessageType = NextMessageType.stillThere; nextMessageDue = now.Add(repeatHoldFrequency); reportedOverlapLeft = true; break; case NextMessageType.carRight: audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderClearAllRound); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearRight); QueuedMessage carRightMessage = new QueuedMessage(folderCarRight, 0, null); carRightMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.playClipImmediately(carRightMessage, true, true); nextMessageType = NextMessageType.stillThere; nextMessageDue = now.Add(repeatHoldFrequency); reportedOverlapRight = true; break; case NextMessageType.clearAllRound: if (reportedOverlapLeft || reportedOverlapRight) { QueuedMessage clearAllRoundMessage = new QueuedMessage(folderClearAllRound, 0, null); clearAllRoundMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearAllRoundMessageExpiresAfter; audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearRight); audioPlayer.playClipImmediately(clearAllRoundMessage, false); nextMessageType = NextMessageType.none; } audioPlayer.closeChannel(); reportedOverlapLeft = false; reportedOverlapRight = false; wasInMiddle = false; break; case NextMessageType.clearLeft: if (reportedOverlapLeft) { if (carsOnRightCount == 0 && wasInMiddle) { QueuedMessage clearAllRoundMessage = new QueuedMessage(folderClearAllRound, 0, null); clearAllRoundMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearMessageExpiresAfter; audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderClearRight); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.playClipImmediately(clearAllRoundMessage, false); nextMessageType = NextMessageType.none; audioPlayer.closeChannel(); reportedOverlapRight = false; wasInMiddle = false; } else { QueuedMessage clearLeftMessage = new QueuedMessage(folderClearLeft, 0, null); clearLeftMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearMessageExpiresAfter; audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderClearRight); audioPlayer.removeImmediateClip(folderClearAllRound); audioPlayer.playClipImmediately(clearLeftMessage, false); if (wasInMiddle) { nextMessageType = NextMessageType.carRight; nextMessageDue = now.Add(repeatHoldFrequency); } else { nextMessageType = NextMessageType.none; } } } if (carsOnRightCount == 0) { audioPlayer.closeChannel(); } reportedOverlapLeft = false; break; case NextMessageType.clearRight: if (reportedOverlapRight) { if (carsOnLeftCount == 0 && wasInMiddle) { QueuedMessage clearAllRoundMessage = new QueuedMessage(folderClearAllRound, 0, null); clearAllRoundMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearMessageExpiresAfter; audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearRight); audioPlayer.playClipImmediately(clearAllRoundMessage, false); nextMessageType = NextMessageType.none; audioPlayer.closeChannel(); reportedOverlapLeft = false; wasInMiddle = false; } else { QueuedMessage clearRightMessage = new QueuedMessage(folderClearRight, 0, null); clearRightMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearMessageExpiresAfter; audioPlayer.removeImmediateClip(folderCarLeft); audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.removeImmediateClip(folderCarRight); audioPlayer.removeImmediateClip(folderInTheMiddle); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearAllRound); audioPlayer.playClipImmediately(clearRightMessage, false); if (wasInMiddle) { nextMessageType = NextMessageType.carLeft; nextMessageDue = now.Add(repeatHoldFrequency); } else { nextMessageType = NextMessageType.none; } } } if (carsOnLeftCount == 0) { audioPlayer.closeChannel(); } reportedOverlapRight = false; break; case NextMessageType.stillThere: if (reportedOverlapLeft || reportedOverlapRight) { QueuedMessage holdYourLineMessage = new QueuedMessage(folderStillThere, 0, null); holdYourLineMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.removeImmediateClip(folderClearRight); audioPlayer.removeImmediateClip(folderClearLeft); audioPlayer.removeImmediateClip(folderClearAllRound); audioPlayer.playClipImmediately(holdYourLineMessage, true, false); nextMessageType = NextMessageType.stillThere; nextMessageDue = now.Add(repeatHoldFrequency); } break; case NextMessageType.none: break; } } else { audioPlayer.closeChannel(); reportedOverlapLeft = false; reportedOverlapRight = false; } } }
protected override void triggerInternal(Shared lastState, Shared currentState) { DateTime now = DateTime.Now; float currentSpeed = currentState.CarSpeed; float previousSpeed = lastState.CarSpeed; if (enabled && CommonData.isRaceRunning && currentState.Player.GameSimulationTime > timeAfterRaceStartToActivate && currentState.ControlType == (int)Constant.Control.Player && currentSpeed > minSpeedForSpotterToOperate) { channelLeftOpenTimerStarted = false; timeWhenChannelShouldBeClosed = now.Add(timeToWaitBeforeClosingChannelLeftOpen); float currentDeltaFront = currentState.TimeDeltaFront; float currentDeltaBehind = currentState.TimeDeltaBehind; float previousDeltaFront = lastState.TimeDeltaFront; float previousDeltaBehind = lastState.TimeDeltaBehind; if (checkDelta(currentDeltaFront, currentSpeed) && checkDelta(currentDeltaBehind, currentSpeed) && checkDelta(previousDeltaFront, previousSpeed) && checkDelta(previousDeltaBehind, previousSpeed)) { lastSpotterDataIsUsable = true; // if we think there's already a car along side, add a little to the car length so we're // sure it's gone before calling clear float carLengthToUse = carLength; if (isCurrentlyOverlapping) { carLengthToUse += gapNeededForClear; } // the time deltas might be small negative numbers while the cars are along side. They might also be large // negative numbers if the data are noisy - either way, use the absolute value to check for overlap Boolean carAlongSideInFront = carLengthToUse / currentSpeed > Math.Abs(currentDeltaFront); Boolean carAlongSideBehind = carLengthToUse / currentSpeed > Math.Abs(currentDeltaBehind); if (!carAlongSideInFront && !carAlongSideBehind) { // we're clear here, so when we next detect we're overlapping we know this must be // a new overlap. This may be valid or due to noise. newlyOverlapping = true; if (isCurrentlyOverlapping) { if (newlyClear) { // start the timer... newlyClear = false; timeWhenWeCanSayClear = now.Add(clearMessageDelay); } // only play "clear" if we've been clear for the specified time if (now > timeWhenWeCanSayClear) { // don't play this message if the channel's closed isCurrentlyOverlapping = false; if (audioPlayer.isChannelOpen()) { Console.WriteLine("Clear - delta front = " + currentDeltaFront + " delta behind = " + currentDeltaBehind); QueuedMessage clearMessage = new QueuedMessage(0, this); clearMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + clearMessageExpiresAfter; audioPlayer.removeImmediateClip(folderStillThere); audioPlayer.playClipImmediately(folderClear, clearMessage); audioPlayer.closeChannel(); } else { Console.WriteLine("Not playing clear message - channel is already closed"); } } } } else { // we're overlapping here, so when we next detect we're 'clear' we know this must be // a new clear. This may be valid or due to noise newlyClear = true; if (newlyOverlapping) { timeWhenWeCanSayHold = now.Add(overlapMessageDelay); newlyOverlapping = false; } if (now > timeWhenWeCanSayHold) { if (isCurrentlyOverlapping) { // play "still there" if we've not played one for a while if (now > timeOfNextHoldMessage) { // channel's already open, still there Console.WriteLine("Still there - delta front = " + currentDeltaFront + " delta behind = " + currentDeltaBehind); timeOfNextHoldMessage = now.Add(repeatHoldFrequency); QueuedMessage stillThereMessage = new QueuedMessage(0, this); stillThereMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.playClipImmediately(folderStillThere, stillThereMessage); } } else { // only say a car is overlapping if the closing speed isn't too high float timeElapsed = (float)currentState.Player.GameSimulationTime - (float)lastState.Player.GameSimulationTime; float closingSpeedInFront = (previousDeltaFront - currentDeltaFront) * currentSpeed / timeElapsed; float closingSpeedBehind = (previousDeltaBehind - currentDeltaBehind) * currentSpeed / timeElapsed; if ((carAlongSideInFront && Math.Abs(closingSpeedInFront) < maxClosingSpeed) || (carAlongSideBehind && Math.Abs(closingSpeedBehind) < maxClosingSpeed)) { Boolean frontOverlapIsReducing = carAlongSideInFront && closingSpeedInFront > 0; Boolean rearOverlapIsReducing = carAlongSideBehind && closingSpeedBehind > 0; if (rearOverlapIsReducing || (frontOverlapIsReducing && !spotterOnlyWhenBeingPassed)) { Console.WriteLine("New overlap"); Console.WriteLine("delta front = " + currentDeltaFront + " closing speed front = " + closingSpeedInFront); Console.WriteLine("delta behind = " + currentDeltaBehind + " closing speed behind = " + closingSpeedBehind); timeOfNextHoldMessage = now.Add(repeatHoldFrequency); isCurrentlyOverlapping = true; audioPlayer.holdOpenChannel(true); QueuedMessage holdMessage = new QueuedMessage(0, this); holdMessage.expiryTime = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + holdMessageExpiresAfter; audioPlayer.playClipImmediately(folderHoldYourLine, holdMessage); } } } } } } else if (isCurrentlyOverlapping) { if (lastSpotterDataIsUsable) { // this is the first chunk of unusable data lastSpotterDataIsUsable = false; timeWhenWeveHadEnoughUnusableData = now.Add(maxTimeToKeepChannelOpenWhileReceivingUnusableData); } if (now > timeWhenWeveHadEnoughUnusableData) { Console.WriteLine("Had " + maxTimeToKeepChannelOpenWhileReceivingUnusableData.Seconds + " seconds of unusable spotter data, closing channel"); isCurrentlyOverlapping = false; audioPlayer.closeChannel(); } } } else if (isCurrentlyOverlapping) { if (!channelLeftOpenTimerStarted) { timeWhenChannelShouldBeClosed = now.Add(timeToWaitBeforeClosingChannelLeftOpen); channelLeftOpenTimerStarted = true; } if (now > timeWhenChannelShouldBeClosed) { Console.WriteLine("Closing channel left open in spotter"); timeWhenChannelShouldBeClosed = DateTime.MaxValue; isCurrentlyOverlapping = false; audioPlayer.closeChannel(); channelLeftOpenTimerStarted = false; isCurrentlyOverlapping = false; } } }