Skip to content

Commit

Permalink
Merge pull request #553 from TheTrackerCouncil/queue-item-messages
Browse files Browse the repository at this point in the history
Queue item tracking messages & sass around talking a long time
  • Loading branch information
MattEqualsCoder authored Jul 15, 2024
2 parents 6fbe4fc + e3587c7 commit edcb05c
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,11 @@ public class ResponseConfig : IMergeable<ResponseConfig>, IConfigFile<ResponseCo
/// </summary> = new()
public SchrodingersString? BeatGame { get; init; }

/// <summary>
/// Gets the phrases for sass for when tracker has been talking for over a minute
/// </summary>
public SchrodingersString? LongSpeechResponse { get; init; }

/// <summary>
/// Gets a dictionary that contains the phrases to respond with when no
/// voice commands have been issued after a certain period of time, as
Expand Down
16 changes: 15 additions & 1 deletion src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace TrackerCouncil.Smz3.Tracking.Services;
using System;

namespace TrackerCouncil.Smz3.Tracking.Services;

/// <summary>
/// Defines a mechanism to communicate with the player.
Expand Down Expand Up @@ -61,4 +63,16 @@ public void SpeedUp() { }
/// communication rate.
/// </summary>
public void SlowDown() { }

/// <summary>
/// If the TTS is currently speaking
/// </summary>
public bool IsSpeaking { get; }

/// <summary>
/// Event for when the communicator has finished speaking
/// </summary>
public event EventHandler<SpeakCompletedEventArgs> SpeakCompleted;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace TrackerCouncil.Smz3.Tracking.Services;

/// <summary>
/// Event for when the communicator has finished speaking
/// </summary>
/// <param name="speechDuration">How long the speech was going on for</param>
public class SpeakCompletedEventArgs(TimeSpan speechDuration) : EventArgs
{
/// <summary>
/// How long the speech was going on for
/// </summary>
public TimeSpan SpeechDuration => speechDuration;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Speech.Synthesis;
using Microsoft.Extensions.Logging;
using TrackerCouncil.Smz3.Data.Options;

namespace TrackerCouncil.Smz3.Tracking.Services;
Expand All @@ -12,12 +13,13 @@ public class TextToSpeechCommunicator : ICommunicator, IDisposable
{
private readonly SpeechSynthesizer _tts = null!;
private bool _canSpeak;
private DateTime _startSpeakingTime;

/// <summary>
/// Initializes a new instance of the <see
/// cref="TextToSpeechCommunicator"/> class.
/// </summary>
public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor)
public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, ILogger<ICommunicator> logger)
{
if (!OperatingSystem.IsWindows())
{
Expand All @@ -26,6 +28,22 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor)

_tts = new SpeechSynthesizer();
_tts.SelectVoiceByHints(VoiceGender.Female);

_tts.SpeakStarted += (sender, args) =>
{
if (IsSpeaking) return;
_startSpeakingTime = DateTime.Now;
IsSpeaking = true;
};

_tts.SpeakCompleted += (sender, args) =>
{
if (!OperatingSystem.IsWindows() || !_canSpeak || _tts.State != SynthesizerState.Ready) return;
IsSpeaking = false;
var duration = DateTime.Now - _startSpeakingTime;
SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration));
};

_canSpeak = trackerOptionsAccessor.Options?.VoiceFrequency != Shared.Enums.TrackerVoiceFrequency.Disabled;
}

Expand Down Expand Up @@ -123,6 +141,10 @@ public void SlowDown()
_tts.Rate -= 2;
}

public bool IsSpeaking { get; private set; }

public event EventHandler<SpeakCompletedEventArgs>? SpeakCompleted;

/// <inheritdoc/>
public void Dispose()
{
Expand Down
77 changes: 58 additions & 19 deletions src/TrackerCouncil.Smz3.Tracking/Tracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public sealed class Tracker : TrackerBase, IDisposable
private readonly HashSet<SchrodingersString> _saidLines = new();
private IEnumerable<ItemType>? _previousMissingItems;
private List<Location> _lastMarkedLocations = new();
private List<Item> _pendingSpeechItems = [];

/// <summary>
/// Initializes a new instance of the <see cref="Tracker"/> class.
Expand Down Expand Up @@ -160,6 +161,8 @@ public Tracker(IWorldAccessor worldAccessor,
World = _worldAccessor.World;
Options = _trackerOptions.Options;

_communicator.SpeakCompleted += CommunicatorOnSpeakCompleted;

Mood = configs.CurrentMood ?? "";
}

Expand Down Expand Up @@ -852,6 +855,12 @@ public override bool TrackItem(Item item, string? trackedAs = null, float? confi
|| !item.Metadata.IsDungeonItem()
|| World.Config.ZeldaKeysanity);

if (stateResponse && _communicator.IsSpeaking)
{
_pendingSpeechItems.Add(item);
stateResponse = false;
}

// Actually track the item if it's for the local player's world
if (item.World == World)
{
Expand Down Expand Up @@ -1123,25 +1132,7 @@ public override void TrackItems(List<Item> items, bool autoTracked, bool giftedI
item.Track();
}

if (items.Count == 2)
{
Say(x => x.TrackedTwoItems, args: [items[0].Metadata.Name, items[1].Metadata.Name]);
}
else if (items.Count == 3)
{
Say(x => x.TrackedThreeItems, args: [items[0].Metadata.Name, items[1].Metadata.Name, items[2].Metadata.Name]);
}
else
{
var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(2).ToList();
if (itemsToSay.Count() < 2)
{
var numToTake = 2 - itemsToSay.Count();
itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(numToTake));
}

Say(x => x.TrackedManyItems, args: [itemsToSay[0].Metadata.Name, itemsToSay[1].Metadata.Name, items.Count - 2]);
}
AnnounceTrackedItems(items);

OnItemTracked(new ItemTrackedEventArgs(null, null, null, true));
IsDirty = true;
Expand Down Expand Up @@ -2590,4 +2581,52 @@ private void GiveLocationHint(IEnumerable<Location> accessibleBefore)
}
}

private void AnnounceTrackedItems(List<Item> items)
{
if (items.Count == 1)
{
var item = items[0];
if (item.TryGetTrackingResponse(out var response))
{
Say(text: response.Format(item.Counter));
}
else
{
Say(text: Responses.TrackedItem?.Format(item.Name, item.Metadata.NameWithArticle));
}
}
else if (items.Count == 2)
{
Say(x => x.TrackedTwoItems, args: [items[0].Metadata.NameWithArticle, items[1].Metadata.NameWithArticle]);
}
else if (items.Count == 3)
{
Say(x => x.TrackedThreeItems, args: [items[0].Metadata.NameWithArticle, items[1].Metadata.NameWithArticle, items[2].Metadata.NameWithArticle]);
}
else
{
var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(2).ToList();
if (itemsToSay.Count() < 2)
{
var numToTake = 2 - itemsToSay.Count();
itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(numToTake));
}

Say(x => x.TrackedManyItems, args: [itemsToSay[0].Metadata.NameWithArticle, itemsToSay[1].Metadata.NameWithArticle, items.Count - 2]);
}
}

private void CommunicatorOnSpeakCompleted(object? sender, SpeakCompletedEventArgs e)
{
if (_pendingSpeechItems.Count > 0)
{
AnnounceTrackedItems(_pendingSpeechItems);
_pendingSpeechItems.Clear();
}

if (e.SpeechDuration.TotalSeconds > 60)
{
Say(x => x.LongSpeechResponse);
}
}
}
4 changes: 3 additions & 1 deletion src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
<ItemGroup>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia_Gif" Version="1.0.0" />
<PackageReference Include="MattEqualsCoder.AvaloniaControls" Version="0.9.17" />
<PackageReference Include="MattEqualsCoder.AvaloniaControls" Version="1.3.1" />
<PackageReference Include="MattEqualsCoder.DynamicForms.Avalonia" Version="0.0.3-rc.5" />
<PackageReference Include="MattEqualsCoder.MSURandomizer.Avalonia" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/TrackerCouncil.Smz3.UI/ViewModels/ViewModelBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using AvaloniaControls;
using AvaloniaControls.Extensions;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;

Expand Down

0 comments on commit edcb05c

Please sign in to comment.