Skip to content

Commit

Permalink
- Add pull timer support (WIP)
Browse files Browse the repository at this point in the history
- Ignore properties that change frequently via timer calculations
  • Loading branch information
pjmagee committed Oct 26, 2024
1 parent 70c089c commit da11f8e
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 73 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ Note on the fields:
- `Reminder` - Reminder time (i.e 15 seconds before the timer)
- `AudioFile` - Audio file path for effect sound if not using Text to speech
- `Label` - Name of the timer
- `Offset` - Some timers can be offset from the minute mark. This is used to adjust the timer for these cases.

Offset example:

Pulling the small camp at `0:15` and `0:45` is a common strategy in Dota 2. To set up a timer for this, you would set the `First` to `02:00`, the `Interval` to `00:30`, and the `Reminder` to `00:15`. The `Offset` would be `-00:15` to adjust the timer to `0:15` and `0:45`.
<!-- markdownlint-disable MD033 -->
<details>
<summary>appsettings.json</summary>
Expand All @@ -101,6 +105,18 @@ Note on the fields:
"IsTts": false,
"IsSoundEnabled": true
},
{
"Label": "Pull",
"First": "02:00",
"Interval": "00:30",
"Offset": "-00:15",
"Reminder": "00:15",
"AudioFile": "audio/Stack.mp3",
"IsManualReset": false,
"IsEnabled": true,
"IsTts": false,
"IsSoundEnabled": true
},
{
"Label": "Wisdom",
"First": "07:00",
Expand Down
25 changes: 25 additions & 0 deletions src/Dota2Helper.Desktop/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@
"IsSoundEnabled": true,
"IsTts": true
},
{
"Speech": "Pull",
"Label": "Pull 1",
"First": "02:00",
"Interval": "01:00",
"Offset": "-00:15",
"Reminder": "00:15",
"AudioFile": "audio/Stack.mp3",
"IsManualReset": false,
"IsEnabled": false,
"IsSoundEnabled": true,
"IsTts": true
},
{
"Speech": "Pull",
"Label": "Pull 2",
"First": "02:00",
"Interval": "00:45",
"Reminder": "00:15",
"AudioFile": "audio/Stack.mp3",
"IsManualReset": false,
"IsEnabled": false,
"IsSoundEnabled": true,
"IsTts": true
},
{
"Speech": "Wisdom",
"Label": "Wisdom",
Expand Down
1 change: 0 additions & 1 deletion src/Dota2Helper/App.axaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:framework="clr-namespace:Dota2Helper.Core.Framework"
x:Class="Dota2Helper.App"
RequestedThemeVariant="Dark">
<Application.Styles>
Expand Down
7 changes: 3 additions & 4 deletions src/Dota2Helper/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Avalonia.Controls;
using Dota2Helper.Core.Audio;
using Dota2Helper.Core.BackgroundServices;
using Dota2Helper.Core.Configuration;
Expand All @@ -17,8 +18,6 @@
using Dota2Helper.Core.Listeners;
using Dota2Helper.Core.Timers;
using Microsoft.Extensions.Logging;
using GameStateService = Dota2Helper.Core.Gsi.GameStateService;
using ViewLocator = Dota2Helper.Core.Framework.ViewLocator;
using Hosting = Microsoft.Extensions.Hosting;

namespace Dota2Helper;
Expand Down Expand Up @@ -74,8 +73,8 @@ static IHost CreateHost()

builder.Services.AddSingleton<FakeDotaListener>();
builder.Services.AddSingleton<DotaListener>();
builder.Services.AddSingleton<GsiConfigService>();
builder.Services.AddSingleton<IListenerStrategy, DynamicListenerStrategy>();
builder.Services.AddSingleton<GameStateService>();
builder.Services.AddSingleton<IDotaListener>(serviceProvider => serviceProvider.GetRequiredService<DotaListener>());
builder.Services.AddSingleton<IDotaListener>(serviceProvider => serviceProvider.GetRequiredService<FakeDotaListener>());

Expand Down Expand Up @@ -113,7 +112,7 @@ static IHost CreateHost()
builder.Services.AddView<TimersViewModel, TimersView>();
builder.Services.AddView<SettingsViewModel, SettingsView>();

builder.Services.AddHostedService<Core.BackgroundServices.GameStateService>();
builder.Services.AddHostedService<GameStateService>();
builder.Services.AddHostedService<AudioPlayerService>();
builder.Services.AddHostedService<ListenerUpdateService>();

Expand Down
23 changes: 16 additions & 7 deletions src/Dota2Helper/Core/Audio/AudioPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ namespace Dota2Helper.Core.Audio;
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
public class AudioPlayer : IDisposable
{
private readonly static LibVLC LibVlc = new();
private readonly MediaPlayer _player = new(LibVlc);
private readonly Queue<AudioQueueItem> _queue = new();
private readonly SpeechSynthesizer _synthesizer = new();
readonly static LibVLC LibVlc = new();
readonly MediaPlayer _player = new(LibVlc);
readonly Queue<AudioQueueItem> _queue = new();
readonly SpeechSynthesizer _synthesizer = new();

public void QueueReminder(DotaTimer timer) => _queue.Enqueue(AudioQueueItem.FromTimer(timer));
public void QueueReminder(DotaTimer timer)
{
// Prevent duplicate reminders
foreach (var item in _queue)
{
if (item.Label == timer.Label) return;
}

_queue.Enqueue(AudioQueueItem.FromTimer(timer));
}

public int Volume
{
Expand All @@ -33,15 +42,15 @@ public void PlayReminder()
{
if (!_queue.TryDequeue(out var item)) return;

if (item.AudioQueueItemType == AudioQueueItemType.Audio)
if (item.AudioQueueItemType == AudioQueueItemType.Effect)
{
using (var reminderAudio = new Media(LibVlc, item.Value))
{
_player.Play(reminderAudio);
}
}

if (item.AudioQueueItemType == AudioQueueItemType.Tts)
if (item.AudioQueueItemType == AudioQueueItemType.TextToSpeech)
{
_synthesizer.SpeakAsync(item.Value);
}
Expand Down
9 changes: 6 additions & 3 deletions src/Dota2Helper/Core/Audio/AudioQueueItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ namespace Dota2Helper.Core.Audio;
public class AudioQueueItem
{
public required string Value { get; init; }

public required string Label { get; init; }

public AudioQueueItemType AudioQueueItemType { get; init; }


public static AudioQueueItem FromTimer(DotaTimer timer) => new()
{
Value = timer.IsTts ? timer.Speech : timer.AudioFile,
AudioQueueItemType = timer.IsTts ? AudioQueueItemType.Tts : AudioQueueItemType.Audio
AudioQueueItemType = timer.IsTts ? AudioQueueItemType.TextToSpeech : AudioQueueItemType.Effect,
Label = timer.Label
};
}

public enum AudioQueueItemType
{
Tts,
Audio
TextToSpeech,
Effect
}
29 changes: 24 additions & 5 deletions src/Dota2Helper/Core/Configuration/TimeSpanConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,41 @@

namespace Dota2Helper.Core.Configuration;

public class TimeSpanConverter : JsonConverter<TimeSpan>
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var stringValue = reader.GetString();

if (TimeSpan.TryParseExact(stringValue, @"mm\:ss", CultureInfo.InvariantCulture, TimeSpanStyles.None, out var timeSpan))

if (stringValue == null)
{
return TimeSpan.Zero;
}

bool isNegative = stringValue.StartsWith('-');

if (TimeSpan.TryParseExact(stringValue.TrimStart('-'), @"mm\:ss", CultureInfo.InvariantCulture, TimeSpanStyles.None, out var timeSpan))
{
if (isNegative)
{
timeSpan = timeSpan.Negate();
}

return timeSpan;
}

throw new JsonException("Invalid TimeSpan format.");
}

public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("mm\\:ss"));
if (value < TimeSpan.Zero)
{
writer.WriteStringValue("-" + value.ToString("mm\\:ss"));
}
else
{
writer.WriteStringValue(value.ToString("mm\\:ss"));
}
}
}
3 changes: 3 additions & 0 deletions src/Dota2Helper/Core/Configuration/TimerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class TimerOptions
[JsonConverter(typeof(TimeSpanConverter))]
public required TimeSpan Reminder { get; set; }

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

/// <summary>
/// The audio file for effect sound
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Dota2Helper/Core/Framework/ViewLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Dota2Helper.Core.Framework;

public class ViewLocator : IDataTemplate
{
private readonly Dictionary<Type, Func<Control>> _dic;
readonly Dictionary<Type, Func<Control>> _dic;

public ViewLocator(IEnumerable<ViewLocationDescriptor> descriptors)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Dota2Helper.Core.Gsi;

public partial class GameStateService(ILogger<GameStateService> logger)
public partial class GsiConfigService(ILogger<GsiConfigService> logger)
{
const string ConfigFile = "gamestate_integration_d2helper.cfg";

Expand Down
16 changes: 12 additions & 4 deletions src/Dota2Helper/Core/Listeners/DotaListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Dota2Helper.Core.Configuration;
using Dota2Helper.Core.Gsi;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Dota2Helper.Core.Listeners;

public class DotaListener(ILogger<DotaListener> logger, IOptions<Settings> settings, GameStateService gameStateService) : IDotaListener
public class DotaListener(ILogger<DotaListener> logger, IOptions<Settings> settings, GsiConfigService gsiConfig) : IDotaListener
{
HttpListener? _listener;

Expand All @@ -33,9 +34,16 @@ public class DotaListener(ILogger<DotaListener> logger, IOptions<Settings> setti

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

try
Expand Down
Loading

0 comments on commit da11f8e

Please sign in to comment.