Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion QuakeSounds.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="SwiftlyS2.Plugin.Audio.API" Version="2.0.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="SwiftlyS2.CS2" Version="1.1.0" ExcludeAssets="runtime" PrivateAssets="all" />
</ItemGroup>

Expand Down
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@

## Overview

**SwiftlyS2-QuakeSounds** is a SwiftlyS2 plugin that plays Quake-style announcer audio for kill streaks, multi-kills, first blood, and special weapon events. It uses the shared Audio interface to play the sounds.
**SwiftlyS2-QuakeSounds** is a SwiftlyS2 plugin that plays Quake-style announcer audio for kill streaks, multi-kills, first blood, and special weapon events.

It supports two playback modes:

- **Audio plugin mode**: Uses the Swiftly Audio plugin (MP3/WAV playback).
- **Workshop Addons**: Relies on addon sound events (.vsndevts).

## Support

Expand All @@ -42,6 +47,11 @@ Need help or have questions? Join our Discord server:
<code>📦</code>
<strong>&nbsp;Download Latest Plugin Version</strong> &rarr;
<a href="https://github.com/a2Labs-cc/SW2-QuakeSounds/releases/latest" target="_blank" rel="noopener noreferrer">Click Here</a>
</li>
<li>
<code>⚙️</code>
<strong>&nbsp;Download Latest Addons Manager</strong> &rarr;
<a href="https://github.com/SwiftlyS2-Plugins/AddonsManager/releases/latest" target="_blank" rel="noopener noreferrer">Click Here</a>
</li>
<li>
<code>⚙️</code>
Expand All @@ -55,7 +65,7 @@ Need help or have questions? Join our Discord server:
<ul>
<li>
<code>⚠️</code>
<strong>&nbsp;Requires Swiftly Audio Plugin in order to work</strong> &rarr;
<strong>&nbsp;Swiftly Audio Plugin is optional</strong> &rarr;
<a href="https://github.com/SwiftlyS2-Plugins/Audio/releases/latest" target="_blank" rel="noopener noreferrer">Click Here</a>
</li>
</ul>
Expand Down Expand Up @@ -91,9 +101,13 @@ The plugin uses SwiftlyS2's JSON config system.

On first run the config is created automatically. The resolved path is logged on startup.

If you are using **Workshop Addons / sound events mode** (no Audio plugin), the default `Sounds` entries that reference `.mp3` files are only examples. You must replace them with the **actual sound event names** that exist in your `.vsndevts` and make sure `SoundEventFile` points to that `.vsndevts` so it gets precached.

### Key Configuration Options

- `Enabled`: Master on/off switch (default: true)
- `UseAudioPlugin`: Use the Swiftly Audio plugin if available; otherwise fall back to addon sounds mode (default: true)
- `SoundEventFile`: (Addon sounds mode) `.vsndevts` file to precache (default: `your_sound_events/quakesounds.vsndevts`)
- `PlayToAll`: Play sounds to all enabled players instead of only the killer (default: false)
- `Volume`: Base volume (0-1) used when no per-player override exists (default: 1.0)
- `CountSelfKills` / `CountTeamKills`: Whether to include suicides/team-kills in streaks (default: false / false)
Expand All @@ -109,6 +123,17 @@ On first run the config is created automatically. The resolved path is logged on
- `!volume <0-10>`: Set your personal QuakeSounds volume (falls back to `Volume` when unset).
- `!quake`: Toggle QuakeSounds on or off for yourself.

### CVars

- `qs_enabled <0|1>`: Enable/disable QuakeSounds globally at runtime.

Examples:

```text
qs_enabled 0
qs_enabled 1
```

## Building

```bash
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration/QuakeSoundsConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public sealed class QuakeSoundsConfig
{
public bool Enabled { get; set; } = true;
public bool Debug { get; set; } = false;
public bool UseAudioPlugin { get; set; } = true;
public string SoundEventFile { get; set; } = "your_sound_events/quakesounds.vsndevts";
public bool PlayToAll { get; set; } = false;
public float Volume { get; set; } = 1.0f;
public bool CountSelfKills { get; set; } = false;
Expand Down
12 changes: 11 additions & 1 deletion src/EventHandlers/PlayerDeathHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ private bool ShouldSkipKillByConfig(IPlayer attacker, IPlayer victim)
[GameEventHandler(HookMode.Post)]
public HookResult OnPlayerDeath(EventPlayerDeath @event)
{
if (!IsPluginEnabled())
{
return HookResult.Continue;
}

var victim = @event.Accessor.GetPlayer("userid");
var attacker = @event.Accessor.GetPlayer("attacker");

Expand Down Expand Up @@ -137,8 +142,13 @@ public HookResult OnPlayerDeath(EventPlayerDeath @event)

private bool TryPlay(IPlayer attacker, string soundKey)
{
if (!IsPluginEnabled())
{
return false;
}

// Try to play sound
var played = _audioService?.TryPlay(
var played = _soundService?.TryPlay(
attacker,
soundKey,
_config,
Expand Down
7 changes: 6 additions & 1 deletion src/EventHandlers/RoundStartHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public partial class QuakeSounds
{
private void TryPlayRoundSoundToAll(string soundKey)
{
if (!IsPluginEnabled())
{
return;
}

var anyPlayer = Core.PlayerManager.GetAllPlayers()
.FirstOrDefault(p => p is { IsValid: true } && !p.IsFakeClient);

Expand All @@ -27,7 +32,7 @@ private void TryPlayRoundSoundToAll(string soundKey)
_config.Messages.EnableCenterMessage = false;
_config.Messages.EnableChatMessage = false;

_audioService?.TryPlay(
_soundService?.TryPlay(
anyPlayer,
soundKey,
_config,
Expand Down
102 changes: 81 additions & 21 deletions src/QuakeSounds.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
using AudioApi;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using QuakeSounds.Services;
using SwiftlyS2.Shared;
using SwiftlyS2.Shared.Convars;
using SwiftlyS2.Shared.Misc;
using SwiftlyS2.Shared.Plugins;
using System;
using System.Collections.Generic;

namespace QuakeSounds;

[PluginMetadata(Id = "QuakeSounds", Version = "1.0.2", Name = "QuakeSounds", Author = "aga", Description = "No description.")]
[PluginMetadata(Id = "QuakeSounds", Version = "1.1.0", Name = "QuakeSounds", Author = "aga", Description = "No description.")]
public partial class QuakeSounds : BasePlugin {
private AudioService? _audioService;
private ISoundService? _soundService;
private readonly GameStateService _gameStateService;
private readonly MessageService _messageService;
private QuakeSoundsConfig _config = new();
private IConVar<int>? _qsEnabled;
private IDisposable? _configReloadRegistration;
private readonly List<string> _registeredCommands = new();

Expand All @@ -32,21 +33,54 @@ public override void ConfigureSharedInterface(IInterfaceManager interfaceManager

public override void UseSharedInterface(IInterfaceManager interfaceManager)
{
if (!interfaceManager.HasSharedInterface("audio"))
if (_config.UseAudioPlugin)
{
Core.Logger.LogWarning("[QuakeSounds] Audio shared interface not found. Install/enable the 'Audio' plugin.");
_audioService = null;
return;
}
if (!interfaceManager.HasSharedInterface("audio"))
{
Core.Logger.LogWarning("[QuakeSounds] Audio plugin not found, falling back to addon sounds mode.");
_soundService = new AddonSoundService(Core);
return;
}

var audioApi = interfaceManager.GetSharedInterface<IAudioApi>("audio");
_audioService = new AudioService(Core, audioApi);
try
{
var audioApiType = Type.GetType("AudioApi.IAudioApi, AudioApi");
if (audioApiType == null)
{
Core.Logger.LogWarning("[QuakeSounds] AudioApi assembly not found, falling back to addon sounds mode.");
_soundService = new AddonSoundService(Core);
return;
}

var getSharedInterfaceMethod = typeof(IInterfaceManager).GetMethod("GetSharedInterface");
var genericMethod = getSharedInterfaceMethod?.MakeGenericMethod(audioApiType);
var audioApi = genericMethod?.Invoke(interfaceManager, new object[] { "audio" });

if (audioApi == null)
{
Core.Logger.LogWarning("[QuakeSounds] Failed to get Audio API interface, falling back to addon sounds mode.");
_soundService = new AddonSoundService(Core);
return;
}

_soundService = AudioService.Create(Core, audioApi);
Core.Logger.LogInformation("[QuakeSounds] Using Audio API mode.");
}
catch (Exception ex)
{
Core.Logger.LogError(ex, "[QuakeSounds] Failed to initialize Audio API, falling back to addon sounds mode.");
_soundService = new AddonSoundService(Core);
}
}
else
{
_soundService = new AddonSoundService(Core);
Core.Logger.LogInformation("[QuakeSounds] Using addon sounds mode.");
}
}

public override void Load(bool hotReload)
{
Core.Logger.LogInformation("[QuakeSounds] Load called - hotReload: {HotReload}, Instance: {InstanceHash}", hotReload, GetHashCode());

Core.Configuration
.InitializeJsonWithModel<QuakeSoundsConfig>("config.jsonc", "Main")
.Configure(builder =>
Expand All @@ -56,46 +90,67 @@ public override void Load(bool hotReload)

ReloadConfig();

_qsEnabled = Core.ConVar.CreateOrFind<int>(
"qs_enabled",
"Enable/disable QuakeSounds globally. 1 = enabled, 0 = disabled.",
_config.Enabled ? 1 : 0,
0,
1
);

if (!_config.UseAudioPlugin && !string.IsNullOrEmpty(_config.SoundEventFile))
{
Core.Event.OnPrecacheResource += Event_OnPrecacheResource;
}

_configReloadRegistration?.Dispose();
_configReloadRegistration = ChangeToken.OnChange(
() => Core.Configuration.Manager.GetReloadToken(),
() =>
{
ReloadConfig();
_audioService?.ClearCache();
_soundService?.ClearCache();
}
);

_registeredCommands.Add("volume");
_registeredCommands.Add("quake");
Core.Logger.LogInformation("[QuakeSounds] Load completed - Commands registered: {Count}", _registeredCommands.Count);
Core.Logger.LogInformation("[QuakeSounds] Plugin loaded successfully.");
}

public override void Unload()
{
Core.Logger.LogInformation("[QuakeSounds] Unload called - Instance: {InstanceHash}", GetHashCode());

if (!_config.UseAudioPlugin && !string.IsNullOrEmpty(_config.SoundEventFile))
{
Core.Event.OnPrecacheResource -= Event_OnPrecacheResource;
}

foreach (var commandName in _registeredCommands)
{
try
{
Core.Logger.LogInformation("[QuakeSounds] Unregistering command: {Command}", commandName);

Core.Command.UnregisterCommand(commandName);
}
catch (Exception ex)
{
Core.Logger.LogWarning(ex, "[QuakeSounds] Failed to unregister command: {Command}", commandName);
Core.Logger.LogError(ex, "[QuakeSounds] Failed to unregister command: {Command}", commandName);
}
}
_registeredCommands.Clear();

_configReloadRegistration?.Dispose();
_configReloadRegistration = null;

_audioService?.ClearCache();
_soundService?.ClearCache();
_gameStateService.ResetAll();
Core.Logger.LogInformation("[QuakeSounds] Unload completed");
}

private void Event_OnPrecacheResource(SwiftlyS2.Shared.Events.IOnPrecacheResourceEvent @event)
{
if (!string.IsNullOrEmpty(_config.SoundEventFile))
{
@event.AddItem(_config.SoundEventFile);
}
}

private void ReloadConfig()
Expand All @@ -122,4 +177,9 @@ private void ReloadConfig()

Core.Logger.LogInformation("[QuakeSounds] Config reloaded. Enabled: {Enabled}. KillStreakAnnounces count: {Count}", _config.Enabled, _config.KillStreakAnnounces.Count);
}

private bool IsPluginEnabled()
{
return (_qsEnabled?.Value ?? (_config.Enabled ? 1 : 0)) != 0;
}
}
Loading