Skip to content

Commit

Permalink
- support expiring timers after a period of game time
Browse files Browse the repository at this point in the history
  • Loading branch information
pjmagee committed Oct 27, 2024
1 parent da11f8e commit 549e54e
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 149 deletions.
1 change: 1 addition & 0 deletions src/Dota2Helper.Desktop/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ private static AppBuilder BuildAvaloniaApp()
.WithInterFont()
.LogToTrace()
.UseReactiveUI();

}
}
28 changes: 19 additions & 9 deletions src/Dota2Helper.Desktop/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"Label": "Stack",
"First": "02:00",
"Interval": "01:00",
"Reminder": "00:15",
"Reminder": "00:20",
"AudioFile": "audio/Stack.mp3",
"ExpireAt": "20:00",
"IsManualReset": false,
"IsEnabled": true,
"IsSoundEnabled": true,
Expand All @@ -16,11 +17,12 @@
{
"Speech": "Pull",
"Label": "Pull 1",
"First": "02:00",
"First": "02:15",
"ExpireAt": "15:00",
"Interval": "01:00",
"Offset": "-00:15",
"Reminder": "00:15",
"AudioFile": "audio/Stack.mp3",
"AudioFile": "",
"IsManualReset": false,
"IsEnabled": false,
"IsSoundEnabled": true,
Expand All @@ -29,10 +31,12 @@
{
"Speech": "Pull",
"Label": "Pull 2",
"First": "02:00",
"Interval": "00:45",
"ExpireAt": "15:00",
"First": "02:45",
"Interval": "01:00",
"Offset": "-00:30",
"Reminder": "00:15",
"AudioFile": "audio/Stack.mp3",
"AudioFile": "",
"IsManualReset": false,
"IsEnabled": false,
"IsSoundEnabled": true,
Expand All @@ -42,6 +46,7 @@
"Speech": "Wisdom",
"Label": "Wisdom",
"First": "07:00",
"ExpireAt": "30:00",
"Interval": "07:00",
"Reminder": "00:45",
"AudioFile": "audio/Wisdom.mp3",
Expand All @@ -56,6 +61,7 @@
"First": "00:00",
"Interval": "03:00",
"Reminder": "00:20",
"ExpireAt": "30:00",
"AudioFile": "audio/Bounty.mp3",
"IsManualReset": false,
"IsEnabled": true,
Expand All @@ -67,6 +73,7 @@
"Label": "Power",
"First": "06:00",
"Interval": "06:00",
"ExpireAt": "35:00",
"Reminder": "00:20",
"AudioFile": "audio/Power.mp3",
"IsManualReset": false,
Expand All @@ -80,6 +87,7 @@
"First": "03:00",
"Interval": "03:00",
"Reminder": "00:15",
"ExpireAt": "25:00",
"AudioFile": "audio/Lotus.mp3",
"IsManualReset": false,
"IsEnabled": true,
Expand All @@ -92,9 +100,10 @@
"First": "20:00",
"Interval": "10:00",
"Reminder": "00:45",
"ExpireAt": "35:00",
"AudioFile": "audio/tormentor.wav",
"IsManualReset": true,
"IsEnabled": true,
"IsEnabled": false,
"IsTts": false,
"IsSoundEnabled": true
},
Expand All @@ -104,9 +113,10 @@
"First": "20:00",
"Interval": "10:00",
"Reminder": "00:45",
"ExpireAt": "35:00",
"AudioFile": "audio/tormentor.wav",
"IsManualReset": true,
"IsEnabled": true,
"IsEnabled": false,
"IsTts": false,
"IsSoundEnabled": true
},
Expand All @@ -119,7 +129,7 @@
"AudioFile": "audio/roshan.wav",
"IsManualReset": true,
"IsEnabled": true,
"IsTts": true,
"IsTts": false,
"IsSoundEnabled": true
},
{
Expand Down
29 changes: 15 additions & 14 deletions src/Dota2Helper/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,8 @@ public override async void OnFrameworkInitializationCompleted()

desktop.ShutdownRequested += (sender, args) =>
{
foreach (var listener in Host.Services.GetServices<IDotaListener>())
{
try
{
listener.Dispose();
}
catch (Exception e)
{
Host.Services.GetRequiredService<ILogger<App>>().LogError(e, "Error disposing listener");
}
}

Host.Services.GetRequiredService<DotaListener>().Dispose();
Host.Services.GetRequiredService<FakeDotaListener>().Dispose();
Host.Dispose();
Host = null;
};
Expand All @@ -75,8 +65,19 @@ static IHost CreateHost()
builder.Services.AddSingleton<DotaListener>();
builder.Services.AddSingleton<GsiConfigService>();
builder.Services.AddSingleton<IListenerStrategy, DynamicListenerStrategy>();
builder.Services.AddSingleton<IDotaListener>(serviceProvider => serviceProvider.GetRequiredService<DotaListener>());
builder.Services.AddSingleton<IDotaListener>(serviceProvider => serviceProvider.GetRequiredService<FakeDotaListener>());

if (Design.IsDesignMode)
{
builder.Services.AddSingleton<ITextToSpeech, StubTextToSpeech>();
}
else if (OperatingSystem.IsWindows())
{
builder.Services.AddSingleton<ITextToSpeech, TextToSpeechWindows>();
}
else if (OperatingSystem.IsMacOS())
{
builder.Services.AddSingleton<ITextToSpeech, TextToSpeechMac>();
}

builder.Logging.ClearProviders();
builder.Logging.AddDebug();
Expand Down
78 changes: 69 additions & 9 deletions src/Dota2Helper/Core/Audio/AudioPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,79 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;
using System.Speech.Synthesis;
using Dota2Helper.Core.Timers;
using LibVLCSharp.Shared;

namespace Dota2Helper.Core.Audio;

[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
public interface ITextToSpeech : IDisposable
{
void Speak(string text);
int Volume { get; set; }
}

public class TextToSpeechMac : ITextToSpeech
{
public void Speak(string text)
{
throw new PlatformNotSupportedException("macOS is not supported.");
}

public int Volume { get; set; }

public void Dispose()
{
}
}

public class StubTextToSpeech : ITextToSpeech
{
public void Speak(string text)
{
}

public int Volume { get; set; }

public void Dispose()
{
}
}

[SupportedOSPlatform("windows")]
public class TextToSpeechWindows : ITextToSpeech
{
readonly SpeechSynthesizer _synthesizer = new();

public void Speak(string text)
{
_synthesizer.SpeakAsync(text);
}

public int Volume
{
set => _synthesizer.Volume = value;
get => _synthesizer.Volume;
}

public void Dispose()
{
_synthesizer.Dispose();
}
}

public class AudioPlayer : IDisposable
{
readonly static LibVLC LibVlc = new();
readonly MediaPlayer _player = new(LibVlc);
readonly Queue<AudioQueueItem> _queue = new();
readonly SpeechSynthesizer _synthesizer = new();
readonly ITextToSpeech _synthesizer;

public AudioPlayer(ITextToSpeech synthesizer)
{
_synthesizer = synthesizer;
}

public void QueueReminder(DotaTimer timer)
{
Expand All @@ -26,18 +86,18 @@ public void QueueReminder(DotaTimer timer)
_queue.Enqueue(AudioQueueItem.FromTimer(timer));
}

public int Volume
public int Volume
{
set
{
_player.Volume = value;
_synthesizer.Volume = value;
_synthesizer.Volume = value;
}
get => _player.Volume;
}

public bool HasReminderQueued => _queue.Count > 0;

public void PlayReminder()
{
if (!_queue.TryDequeue(out var item)) return;
Expand All @@ -47,12 +107,12 @@ public void PlayReminder()
using (var reminderAudio = new Media(LibVlc, item.Value))
{
_player.Play(reminderAudio);
}
}
}

if (item.AudioQueueItemType == AudioQueueItemType.TextToSpeech)
{
_synthesizer.SpeakAsync(item.Value);
_synthesizer.Speak(item.Value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ await Dispatcher.UIThread.InvokeAsync(() => timers.Do(timer =>
logger.LogError(ex, "Error updating timer");
}
}
), DispatcherPriority.Background, stoppingToken);
), DispatcherPriority.Render, stoppingToken);
}
catch (Exception ex)
{
Expand Down
5 changes: 4 additions & 1 deletion src/Dota2Helper/Core/Configuration/TimerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ public class TimerOptions
[JsonRequired]
[JsonConverter(typeof(TimeSpanConverter))]
public required TimeSpan Interval { get; set; }

[JsonConverter(typeof(TimeSpanConverter))]
public TimeSpan? ExpireAt { get; set; }

[JsonRequired]
[JsonConverter(typeof(TimeSpanConverter))]
public required TimeSpan Reminder { get; set; }

[JsonConverter(typeof(TimeSpanConverter))]
public TimeSpan Offset { get; set; } = TimeSpan.Zero;
public TimeSpan? Offset { get; set; }

/// <summary>
/// The audio file for effect sound
Expand Down
3 changes: 2 additions & 1 deletion src/Dota2Helper/Core/Gsi/GsiConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using Avalonia.Controls;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;

namespace Dota2Helper.Core.Gsi;

public partial class GsiConfigService(ILogger<GsiConfigService> logger)
{
const string ConfigFile = "gamestate_integration_d2helper.cfg";
string ConfigFile => Design.IsDesignMode ? "gamestate_integration_design.cfg" : "gamestate_integration_d2helper.cfg";

string? FindGameStateIntegrationPath()
{
Expand Down
13 changes: 3 additions & 10 deletions src/Dota2Helper/Core/Listeners/DotaListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,9 @@ public class DotaListener(ILogger<DotaListener> logger, IOptions<Settings> setti

if (_listener == null)
{
if (!Design.IsDesignMode)
{
_listener = new HttpListener();
_listener.Prefixes.Add(gsiConfig.GetUri().ToString());
_listener.Start();
}
else
{
return null;
}
_listener = new HttpListener();
_listener.Prefixes.Add(gsiConfig.GetUri().ToString());
_listener.Start();
}

try
Expand Down
18 changes: 7 additions & 11 deletions src/Dota2Helper/Core/Listeners/DynamicListenerStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Microsoft.Extensions.Logging;

namespace Dota2Helper.Core.Listeners;

public class DynamicListenerStrategy(ILogger<DynamicListenerStrategy> logger, FakeDotaListener fakeDotaListener, DotaListener dotaListener) : IListenerStrategy
public class DynamicListenerStrategy(
ILogger<DynamicListenerStrategy> logger,
FakeDotaListener fakeDotaListener,
DotaListener dotaListener) : IListenerStrategy
{
IDotaListener? _listener;

Expand All @@ -18,11 +17,6 @@ public class DynamicListenerStrategy(ILogger<DynamicListenerStrategy> logger, Fa

public void UpdateListener()
{
if (Design.IsDesignMode)
{
_listener = fakeDotaListener;
}

if (_dota2 == null)
{
_dota2 = Process.GetProcessesByName("dota2").FirstOrDefault();
Expand All @@ -32,9 +26,11 @@ public void UpdateListener()
logger.LogInformation("Dota2 is running, caching detected process for exit event");

_dota2.EnableRaisingEvents = true;

_dota2.Exited += (sender, args) =>
{
logger.LogInformation("Dota2 exited, switching to FakeDotaListener");

_listener = fakeDotaListener;
_dota2 = null;
};
Expand Down
Loading

0 comments on commit 549e54e

Please sign in to comment.