Phase 4: BepInEx plugin complete
- Added ModConfig.cs with full configuration system - Updated Plugin.cs to use config and proper initialization - Enhanced VoiceManagerPatch with dynamic type finding and config usage - Enhanced AudioDuckingPatch with fade time and snapshot patching - Enhanced EnemyAudioPatch with enemy attack and CFVoice hooks - Added Build.ps1 for automated building and deployment - Added comprehensive README.md with installation and configuration
This commit is contained in:
89
src/HomuraHimeAudioMod/Build.ps1
Normal file
89
src/HomuraHimeAudioMod/Build.ps1
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
# Build script for HomuraHimeAudioMod
|
||||||
|
# Requires: .NET SDK 4.8 or .NET Framework 4.8
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$Configuration = "Release",
|
||||||
|
[string]$OutputDir = $null
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$ProjectDir = Split-Path -Parent $PSScriptRoot
|
||||||
|
$ProjectFile = Join-Path $ProjectDir "HomuraHimeAudioMod.csproj"
|
||||||
|
$DefaultOutput = Join-Path $ProjectDir "bin\$Configuration\net48"
|
||||||
|
|
||||||
|
if (-not (Test-Path $ProjectFile)) {
|
||||||
|
Write-Host "[ERROR] Project file not found: $ProjectFile" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "HomuraHime Audio Mod - Build Script" -ForegroundColor Cyan
|
||||||
|
Write-Host "===================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "Configuration: $Configuration" -ForegroundColor Gray
|
||||||
|
Write-Host "Project: $ProjectFile" -ForegroundColor Gray
|
||||||
|
|
||||||
|
# Check for .NET SDK
|
||||||
|
$dotnet = Get-Command dotnet -ErrorAction SilentlyContinue
|
||||||
|
if (-not $dotnet) {
|
||||||
|
Write-Host "[ERROR] .NET SDK not found. Please install .NET Framework 4.8 SDK or .NET SDK." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean previous build
|
||||||
|
Write-Host "`n[STEP 1] Cleaning previous build..." -ForegroundColor Yellow
|
||||||
|
dotnet clean $ProjectFile -c $Configuration 2>&1 | Out-Null
|
||||||
|
|
||||||
|
# Build
|
||||||
|
Write-Host "[STEP 2] Building..." -ForegroundColor Yellow
|
||||||
|
$buildOutput = dotnet build $ProjectFile -c $Configuration --nologo 2>&1
|
||||||
|
$buildOutput | ForEach-Object { Write-Host $_ -ForegroundColor Gray }
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "`n[ERROR] Build failed!" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find output
|
||||||
|
$outputDll = if ($OutputDir) {
|
||||||
|
Join-Path $OutputDir "HomuraHimeAudioMod.dll"
|
||||||
|
} else {
|
||||||
|
Get-ChildItem -Path $DefaultOutput -Filter "HomuraHimeAudioMod.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { $_.FullName }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($outputDll -and (Test-Path $outputDll)) {
|
||||||
|
$fileInfo = Get-Item $outputDll
|
||||||
|
Write-Host "`n[OK] Build successful!" -ForegroundColor Green
|
||||||
|
Write-Host "Output: $outputDll" -ForegroundColor Cyan
|
||||||
|
Write-Host "Size: $([math]::Round($fileInfo.Length / 1024, 2)) KB" -ForegroundColor Gray
|
||||||
|
Write-Host "Built: $($fileInfo.LastWriteTime)" -ForegroundColor Gray
|
||||||
|
} else {
|
||||||
|
Write-Host "[WARN] Could not locate output DLL automatically." -ForegroundColor Yellow
|
||||||
|
Write-Host "Please check: $DefaultOutput" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy to BepInEx plugins folder
|
||||||
|
$gamePath = "C:\apps\steam\steamapps\common\Homura Hime"
|
||||||
|
$pluginsPath = Join-Path $gamePath "BepInEx\plugins"
|
||||||
|
|
||||||
|
if (Test-Path $pluginsPath) {
|
||||||
|
$targetPath = Join-Path $pluginsPath "HomuraHimeAudioMod.dll"
|
||||||
|
|
||||||
|
if ($outputDll -and (Test-Path $outputDll)) {
|
||||||
|
Write-Host "`n[STEP 3] Copying to BepInEx plugins..." -ForegroundColor Yellow
|
||||||
|
Copy-Item -Path $outputDll -Destination $targetPath -Force
|
||||||
|
Write-Host "[OK] Copied to: $targetPath" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "`n[WARN] BepInEx not installed yet." -ForegroundColor Yellow
|
||||||
|
Write-Host "Run the game once to generate BepInEx structure, then copy manually." -ForegroundColor Gray
|
||||||
|
Write-Host "Target: $pluginsPath" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n===================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "Build complete!" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||||
|
Write-Host " 1. Launch HomuraHime via Steam" -ForegroundColor Cyan
|
||||||
|
Write-Host " 2. Check BepInEx\LogOutput.log for mod initialization" -ForegroundColor Cyan
|
||||||
|
Write-Host " 3. Adjust settings in BepInEx\config\com.homurahime.audiomod.cfg" -ForegroundColor Cyan
|
||||||
@@ -6,28 +6,40 @@
|
|||||||
<RootNamespace>HomuraHimeAudioMod</RootNamespace>
|
<RootNamespace>HomuraHimeAudioMod</RootNamespace>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<Authors>Ed</Authors>
|
<Authors>Ed</Authors>
|
||||||
|
<Company>HomuraHime Modding</Company>
|
||||||
|
<Product>HomuraHime Audio Mod</Product>
|
||||||
<Description>Audio improvements mod for HomuraHime - fixes glitches and improves enemy indicators</Description>
|
<Description>Audio improvements mod for HomuraHime - fixes glitches and improves enemy indicators</Description>
|
||||||
<GameAssemblyPath>C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\Managed</GameAssemblyPath>
|
<Copyright>Copyright 2026</Copyright>
|
||||||
|
<GamePath>C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\Managed</GamePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="Exists('$(GamePath)')">
|
||||||
|
<DefineConstants>GAME_PATH_EXISTS</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="0Harmony" Version="2.2.2" />
|
<PackageReference Include="0Harmony" Version="2.2.2" />
|
||||||
<PackageReference Include="BepInEx" Version="5.4.21" />
|
<PackageReference Include="BepInEx" Version="5.4.21" />
|
||||||
|
<PackageReference Include="UnityEngine.Modules" Version="2022.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup Condition="Exists('$(GamePath)')">
|
||||||
<Reference Include="FMODUnity">
|
<Reference Include="FMODUnity">
|
||||||
<HintPath>$(GameAssemblyPath)\FMODUnity.dll</HintPath>
|
<HintPath>$(GamePath)\FMODUnity.dll</HintPath>
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="UnityEngine.AudioModule">
|
<Reference Include="UnityEngine.AudioModule">
|
||||||
<HintPath>$(GameAssemblyPath)\UnityEngine.AudioModule.dll</HintPath>
|
<HintPath>$(GamePath)\UnityEngine.AudioModule.dll</HintPath>
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Assembly-CSharp">
|
<Reference Include="Assembly-CSharp">
|
||||||
<HintPath>$(GameAssemblyPath)\Assembly-CSharp.dll</HintPath>
|
<HintPath>$(GamePath)\Assembly-CSharp.dll</HintPath>
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="README.md" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
92
src/HomuraHimeAudioMod/ModConfig.cs
Normal file
92
src/HomuraHimeAudioMod/ModConfig.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using BepInEx;
|
||||||
|
using BepInEx.Configuration;
|
||||||
|
|
||||||
|
namespace HomuraHimeAudioMod
|
||||||
|
{
|
||||||
|
public static class ModConfig
|
||||||
|
{
|
||||||
|
public static ConfigEntry<int> MaxVoiceCount { get; set; }
|
||||||
|
public static ConfigEntry<float> DuckFadeTime { get; set; }
|
||||||
|
public static ConfigEntry<float> DuckVolume { get; set; }
|
||||||
|
public static ConfigEntry<float> EnemyIndicatorBoost { get; set; }
|
||||||
|
public static ConfigEntry<float> AlertSoundBoost { get; set; }
|
||||||
|
public static ConfigEntry<float> AttackSoundBoost { get; set; }
|
||||||
|
public static ConfigEntry<bool> EnableDebugLogging { get; set; }
|
||||||
|
public static ConfigEntry<bool> EnableVoiceLimitFix { get; set; }
|
||||||
|
public static ConfigEntry<bool> EnableDuckingFix { get; set; }
|
||||||
|
public static ConfigEntry<bool> EnableEnemyAudioBoost { get; set; }
|
||||||
|
|
||||||
|
public static void Initialize(ConfigFile config)
|
||||||
|
{
|
||||||
|
EnableDebugLogging = config.Bind(
|
||||||
|
"General",
|
||||||
|
"EnableDebugLogging",
|
||||||
|
false,
|
||||||
|
"Enable detailed debug logging for audio events"
|
||||||
|
);
|
||||||
|
|
||||||
|
EnableVoiceLimitFix = config.Bind(
|
||||||
|
"VoiceManager",
|
||||||
|
"EnableVoiceLimitFix",
|
||||||
|
true,
|
||||||
|
"Enable voice limit improvements to prevent audio cutoff"
|
||||||
|
);
|
||||||
|
|
||||||
|
MaxVoiceCount = config.Bind(
|
||||||
|
"VoiceManager",
|
||||||
|
"MaxVoiceCount",
|
||||||
|
128,
|
||||||
|
"Maximum number of concurrent voices (default: 64-128)"
|
||||||
|
);
|
||||||
|
|
||||||
|
EnableDuckingFix = config.Bind(
|
||||||
|
"Ducking",
|
||||||
|
"EnableDuckingFix",
|
||||||
|
true,
|
||||||
|
"Enable audio ducking improvements"
|
||||||
|
);
|
||||||
|
|
||||||
|
DuckFadeTime = config.Bind(
|
||||||
|
"Ducking",
|
||||||
|
"DuckFadeTime",
|
||||||
|
0.05f,
|
||||||
|
"Fade time for audio ducking (seconds)"
|
||||||
|
);
|
||||||
|
|
||||||
|
DuckVolume = config.Bind(
|
||||||
|
"Ducking",
|
||||||
|
"DuckVolume",
|
||||||
|
0.3f,
|
||||||
|
"Volume level during ducking (0.0-1.0)"
|
||||||
|
);
|
||||||
|
|
||||||
|
EnableEnemyAudioBoost = config.Bind(
|
||||||
|
"EnemyAudio",
|
||||||
|
"EnableEnemyAudioBoost",
|
||||||
|
true,
|
||||||
|
"Enable enemy indicator audio boost"
|
||||||
|
);
|
||||||
|
|
||||||
|
EnemyIndicatorBoost = config.Bind(
|
||||||
|
"EnemyAudio",
|
||||||
|
"EnemyIndicatorBoost",
|
||||||
|
1.2f,
|
||||||
|
"Volume multiplier for enemy indicator sounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
AlertSoundBoost = config.Bind(
|
||||||
|
"EnemyAudio",
|
||||||
|
"AlertSoundBoost",
|
||||||
|
1.3f,
|
||||||
|
"Volume multiplier for enemy alert sounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
AttackSoundBoost = config.Bind(
|
||||||
|
"EnemyAudio",
|
||||||
|
"AttackSoundBoost",
|
||||||
|
1.15f,
|
||||||
|
"Volume multiplier for enemy attack sounds"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,28 +7,24 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
{
|
{
|
||||||
public class AudioDuckingPatch
|
public class AudioDuckingPatch
|
||||||
{
|
{
|
||||||
public const float COMBAT_DUCK_AMOUNT = 0.25f;
|
|
||||||
public const float COMBAT_DUCK_FADE_IN = 0.03f;
|
|
||||||
public const float COMBAT_DUCK_FADE_OUT = 0.15f;
|
|
||||||
public const float VOICE_DUCK_AMOUNT = 0.35f;
|
|
||||||
public const float SFX_DUCK_AMOUNT = 0.4f;
|
|
||||||
|
|
||||||
public static void Apply(ref Harmony harmony)
|
public static void Apply(ref Harmony harmony)
|
||||||
{
|
{
|
||||||
|
if (!ModConfig.EnableDuckingFix.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogInfo("AudioDucking patches disabled in config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Plugin.Log.LogInfo("Applying AudioDucking patches...");
|
Plugin.Log.LogInfo("Applying AudioDucking patches...");
|
||||||
|
|
||||||
var originalType = AccessTools.TypeByName("Andy.SnapshotManager");
|
var snapshotManagerType = FindSnapshotManagerType();
|
||||||
if (originalType != null)
|
if (snapshotManagerType != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo($"Found SnapshotManager type: {originalType.FullName}");
|
Plugin.Log.LogInfo($"Found SnapshotManager type: {snapshotManagerType.FullName}");
|
||||||
PatchSnapshotApply(harmony, originalType);
|
PatchSnapshotApply(harmony, snapshotManagerType);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Plugin.Log.LogWarning("SnapshotManager type not found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var fadeManagerType = AccessTools.TypeByName("Andy.DEAudioFadeManager");
|
var fadeManagerType = FindFadeManagerType();
|
||||||
if (fadeManagerType != null)
|
if (fadeManagerType != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo($"Found DEAudioFadeManager type: {fadeManagerType.FullName}");
|
Plugin.Log.LogInfo($"Found DEAudioFadeManager type: {fadeManagerType.FullName}");
|
||||||
@@ -38,6 +34,60 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
Plugin.Log.LogInfo("AudioDucking patches applied");
|
Plugin.Log.LogInfo("AudioDucking patches applied");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Type FindSnapshotManagerType()
|
||||||
|
{
|
||||||
|
string[] searchNames = new[]
|
||||||
|
{
|
||||||
|
"Andy.SnapshotManager",
|
||||||
|
"SnapshotManager"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var name in searchNames)
|
||||||
|
{
|
||||||
|
var type = AccessTools.TypeByName(name);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.Name == "SnapshotManager")
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type FindFadeManagerType()
|
||||||
|
{
|
||||||
|
string[] searchNames = new[]
|
||||||
|
{
|
||||||
|
"Andy.DEAudioFadeManager",
|
||||||
|
"DEAudioFadeManager"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var name in searchNames)
|
||||||
|
{
|
||||||
|
var type = AccessTools.TypeByName(name);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.Name == "DEAudioFadeManager")
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static void PatchSnapshotApply(Harmony harmony, Type targetType)
|
private static void PatchSnapshotApply(Harmony harmony, Type targetType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -91,14 +141,23 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
|
|
||||||
private static bool ApplySnapshotPrefix(ref string snapshotName, ref float fadeTime)
|
private static bool ApplySnapshotPrefix(ref string snapshotName, ref float fadeTime)
|
||||||
{
|
{
|
||||||
if (fadeTime < COMBAT_DUCK_FADE_IN)
|
float configuredFadeTime = ModConfig.DuckFadeTime.Value;
|
||||||
|
|
||||||
|
if (fadeTime < configuredFadeTime)
|
||||||
{
|
{
|
||||||
fadeTime = COMBAT_DUCK_FADE_IN;
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"Adjusting fade time from {fadeTime} to {configuredFadeTime}");
|
||||||
|
}
|
||||||
|
fadeTime = configuredFadeTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshotName != null && snapshotName.Contains("Battle"))
|
if (ModConfig.EnableDebugLogging.Value && !string.IsNullOrEmpty(snapshotName))
|
||||||
{
|
{
|
||||||
Plugin.Log.LogDebug($"Battle snapshot detected: {snapshotName}, fadeTime: {fadeTime}");
|
if (snapshotName.Contains("Battle") || snapshotName.Contains("Combat"))
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"Battle/Combat snapshot: {snapshotName}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -106,18 +165,28 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
|
|
||||||
private static bool FadeInPrefix(ref float fadeTime)
|
private static bool FadeInPrefix(ref float fadeTime)
|
||||||
{
|
{
|
||||||
if (fadeTime < 0.01f)
|
const float minFadeIn = 0.01f;
|
||||||
|
if (fadeTime < minFadeIn)
|
||||||
{
|
{
|
||||||
fadeTime = 0.01f;
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"FadeIn adjusted: {fadeTime} -> {minFadeIn}");
|
||||||
|
}
|
||||||
|
fadeTime = minFadeIn;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool FadeOutPrefix(ref float fadeTime)
|
private static bool FadeOutPrefix(ref float fadeTime)
|
||||||
{
|
{
|
||||||
if (fadeTime < 0.05f)
|
const float minFadeOut = 0.05f;
|
||||||
|
if (fadeTime < minFadeOut)
|
||||||
{
|
{
|
||||||
fadeTime = 0.05f;
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"FadeOut adjusted: {fadeTime} -> {minFadeOut}");
|
||||||
|
}
|
||||||
|
fadeTime = minFadeOut;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace HomuraHimeAudioMod.Patches
|
namespace HomuraHimeAudioMod.Patches
|
||||||
{
|
{
|
||||||
public class EnemyAudioPatch
|
public class EnemyAudioPatch
|
||||||
{
|
{
|
||||||
public const float ENEMY_INDICATOR_VOLUME_BOOST = 1.2f;
|
|
||||||
public const float ALERT_SOUND_VOLUME_BOOST = 1.3f;
|
|
||||||
public const float ATTACK_SOUND_VOLUME_BOOST = 1.15f;
|
|
||||||
|
|
||||||
public static void Apply(ref Harmony harmony)
|
public static void Apply(ref Harmony harmony)
|
||||||
{
|
{
|
||||||
|
if (!ModConfig.EnableEnemyAudioBoost.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogInfo("EnemyAudio patches disabled in config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Plugin.Log.LogInfo("Applying EnemyAudio patches...");
|
Plugin.Log.LogInfo("Applying EnemyAudio patches...");
|
||||||
|
|
||||||
PatchEnemyAttackBase(harmony);
|
PatchEnemyAttackBase(harmony);
|
||||||
@@ -25,12 +26,12 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var enemyAttackBaseType = AccessTools.TypeByName("EnemyAttackBase");
|
var enemyAttackBaseType = FindEnemyAttackBaseType();
|
||||||
if (enemyAttackBaseType != null)
|
if (enemyAttackBaseType != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo($"Found EnemyAttackBase type: {enemyAttackBaseType.FullName}");
|
Plugin.Log.LogInfo($"Found EnemyAttackBase type: {enemyAttackBaseType.FullName}");
|
||||||
|
|
||||||
var attackMethods = new[] { "Attack", "OnAttack", "DoAttack", "AlertAction" };
|
string[] attackMethods = new[] { "Attack", "OnAttack", "DoAttack", "AlertAction", "OnEnter", "StartAttack" };
|
||||||
foreach (var methodName in attackMethods)
|
foreach (var methodName in attackMethods)
|
||||||
{
|
{
|
||||||
var method = AccessTools.Method(enemyAttackBaseType, methodName);
|
var method = AccessTools.Method(enemyAttackBaseType, methodName);
|
||||||
@@ -44,10 +45,6 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Plugin.Log.LogWarning("EnemyAttackBase type not found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -59,7 +56,7 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cfVoiceType = AccessTools.TypeByName("CFVoiceEventUtility");
|
var cfVoiceType = FindCFVoiceEventUtilityType();
|
||||||
if (cfVoiceType != null)
|
if (cfVoiceType != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo($"Found CFVoiceEventUtility type: {cfVoiceType.FullName}");
|
Plugin.Log.LogInfo($"Found CFVoiceEventUtility type: {cfVoiceType.FullName}");
|
||||||
@@ -74,10 +71,6 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
Plugin.Log.LogInfo("Patched PlayCFVoice");
|
Plugin.Log.LogInfo("Patched PlayCFVoice");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Plugin.Log.LogWarning("CFVoiceEventUtility type not found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -89,7 +82,7 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var handlerType = AccessTools.TypeByName("EnemyDamageVoiceSFXHandler");
|
var handlerType = FindEnemyDamageVoiceSFXHandlerType();
|
||||||
if (handlerType != null)
|
if (handlerType != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo($"Found EnemyDamageVoiceSFXHandler type: {handlerType.FullName}");
|
Plugin.Log.LogInfo($"Found EnemyDamageVoiceSFXHandler type: {handlerType.FullName}");
|
||||||
@@ -97,7 +90,7 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
var feedbackVoiceField = AccessTools.Field(handlerType, "FeedbackVoice");
|
var feedbackVoiceField = AccessTools.Field(handlerType, "FeedbackVoice");
|
||||||
if (feedbackVoiceField != null)
|
if (feedbackVoiceField != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo("Found FeedbackVoice field - enemy damage audio hookable");
|
Plugin.Log.LogInfo("EnemyDamageVoiceSFXHandler.FeedbackVoice field found - injectable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,32 +100,141 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Type FindEnemyAttackBaseType()
|
||||||
|
{
|
||||||
|
string[] searchNames = new[]
|
||||||
|
{
|
||||||
|
"EnemyAttackBase",
|
||||||
|
"Character.EnemyAttackBase",
|
||||||
|
"EnemyAttack"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var name in searchNames)
|
||||||
|
{
|
||||||
|
var type = AccessTools.TypeByName(name);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.Name == "EnemyAttackBase")
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type FindCFVoiceEventUtilityType()
|
||||||
|
{
|
||||||
|
string[] searchNames = new[]
|
||||||
|
{
|
||||||
|
"CFVoiceEventUtility"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var name in searchNames)
|
||||||
|
{
|
||||||
|
var type = AccessTools.TypeByName(name);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.Name == "CFVoiceEventUtility")
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type FindEnemyDamageVoiceSFXHandlerType()
|
||||||
|
{
|
||||||
|
string[] searchNames = new[]
|
||||||
|
{
|
||||||
|
"EnemyDamageVoiceSFXHandler",
|
||||||
|
"Andy.EnemyDamageVoiceSFXHandler"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var name in searchNames)
|
||||||
|
{
|
||||||
|
var type = AccessTools.TypeByName(name);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.Name == "EnemyDamageVoiceSFXHandler")
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool EnemyAttackPrefix(string eventKey)
|
private static bool EnemyAttackPrefix(string eventKey)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(eventKey))
|
if (!ModConfig.EnableEnemyAudioBoost.Value)
|
||||||
{
|
return true;
|
||||||
Plugin.Log.LogDebug($"Enemy attack event: {eventKey}");
|
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(eventKey))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
if (eventKey.Contains("Alert") || eventKey.Contains("Warning"))
|
if (eventKey.Contains("Alert") || eventKey.Contains("Warning"))
|
||||||
{
|
{
|
||||||
Plugin.Log.LogDebug($"Enemy alert sound detected: {eventKey}");
|
Plugin.Log.LogDebug($"Enemy alert: {eventKey}");
|
||||||
}
|
}
|
||||||
else if (eventKey.Contains("Attack"))
|
else if (eventKey.Contains("Attack"))
|
||||||
{
|
{
|
||||||
Plugin.Log.LogDebug($"Enemy attack sound: {eventKey}");
|
Plugin.Log.LogDebug($"Enemy attack: {eventKey}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PlayCFVoicePrefix(ref string eventKey)
|
private static bool PlayCFVoicePrefix(ref string eventKey)
|
||||||
{
|
{
|
||||||
|
if (!ModConfig.EnableEnemyAudioBoost.Value)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(eventKey))
|
if (string.IsNullOrEmpty(eventKey))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (eventKey.Contains("Enemy"))
|
if (eventKey.Contains("Enemy"))
|
||||||
{
|
{
|
||||||
Plugin.Log.LogDebug($"Enemy voice event triggered: {eventKey}");
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"Enemy voice event: {eventKey}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventKey.Contains("Alert"))
|
||||||
|
{
|
||||||
|
float boost = ModConfig.AlertSoundBoost.Value;
|
||||||
|
if (boost != 1.0f)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"Alert boost: {boost}x");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (eventKey.Contains("Attack"))
|
||||||
|
{
|
||||||
|
float boost = ModConfig.AttackSoundBoost.Value;
|
||||||
|
if (boost != 1.0f)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"Attack boost: {boost}x");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7,42 +7,69 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
{
|
{
|
||||||
public class VoiceManagerPatch
|
public class VoiceManagerPatch
|
||||||
{
|
{
|
||||||
public const int MAX_VOICE_COUNT = 128;
|
|
||||||
public const float DEFAULT_DUCK_FADE_TIME = 0.05f;
|
|
||||||
public const float DEFAULT_DUCK_VOLUME = 0.3f;
|
|
||||||
|
|
||||||
public static void Apply(ref Harmony harmony)
|
public static void Apply(ref Harmony harmony)
|
||||||
{
|
{
|
||||||
|
if (!ModConfig.EnableVoiceLimitFix.Value && !ModConfig.EnableDuckingFix.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogInfo("VoiceManager patches disabled in config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Plugin.Log.LogInfo("Applying VoiceManager patches...");
|
Plugin.Log.LogInfo("Applying VoiceManager patches...");
|
||||||
|
|
||||||
var originalType = AccessTools.TypeByName("Andy.UtageFmodVoiceManager");
|
var originalType = FindFmodVoiceManagerType();
|
||||||
if (originalType != null)
|
if (originalType != null)
|
||||||
{
|
{
|
||||||
Plugin.Log.LogInfo($"Found UtageFmodVoiceManager type: {originalType.FullName}");
|
Plugin.Log.LogInfo($"Found voice manager type: {originalType.FullName}");
|
||||||
|
|
||||||
PatchUpdateDucking(harmony, originalType);
|
if (ModConfig.EnableDuckingFix.Value)
|
||||||
PatchVoiceCount(harmony, originalType);
|
{
|
||||||
PatchDuckVolume(harmony, originalType);
|
PatchUpdateDucking(harmony, originalType);
|
||||||
|
PatchDuckVolume(harmony, originalType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ModConfig.EnableVoiceLimitFix.Value)
|
||||||
|
{
|
||||||
|
PatchVoiceCount(harmony, originalType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Plugin.Log.LogWarning("UtageFmodVoiceManager type not found by name, searching...");
|
Plugin.Log.LogWarning("FmodVoiceManager type not found - patches may not apply correctly");
|
||||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
}
|
||||||
|
|
||||||
|
Plugin.Log.LogInfo("VoiceManager patches applied");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type FindFmodVoiceManagerType()
|
||||||
|
{
|
||||||
|
string[] searchNames = new[]
|
||||||
|
{
|
||||||
|
"Andy.UtageFmodVoiceManager",
|
||||||
|
"UtageFmodVoiceManager",
|
||||||
|
"FmodVoiceManager",
|
||||||
|
"VoiceManager"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var name in searchNames)
|
||||||
|
{
|
||||||
|
var type = AccessTools.TypeByName(name);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
{
|
{
|
||||||
foreach (var type in assembly.GetTypes())
|
if (type.Name.Contains("FmodVoiceManager") || type.Name.Contains("UtageFmodVoice"))
|
||||||
{
|
{
|
||||||
if (type.Name.Contains("FmodVoiceManager"))
|
return type;
|
||||||
{
|
|
||||||
Plugin.Log.LogInfo($"Found alternative: {type.FullName}");
|
|
||||||
PatchUpdateDucking(harmony, type);
|
|
||||||
PatchVoiceCount(harmony, type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugin.Log.LogInfo("VoiceManager patches applied");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PatchUpdateDucking(Harmony harmony, Type targetType)
|
private static void PatchUpdateDucking(Harmony harmony, Type targetType)
|
||||||
@@ -58,10 +85,6 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
);
|
);
|
||||||
Plugin.Log.LogInfo("Patched UpdateDucking");
|
Plugin.Log.LogInfo("Patched UpdateDucking");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Plugin.Log.LogWarning("UpdateDucking method not found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -86,10 +109,6 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
Plugin.Log.LogInfo("Patched CountVoice getter");
|
Plugin.Log.LogInfo("Patched CountVoice getter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Plugin.Log.LogWarning("CountVoice property not found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -121,37 +140,47 @@ namespace HomuraHimeAudioMod.Patches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateDuckingPostfix(IEnumerator self, UtageFmodVoiceManager __instance)
|
private static void UpdateDuckingPostfix(IEnumerator self, object __instance)
|
||||||
{
|
{
|
||||||
if (__instance != null)
|
if (__instance == null) return;
|
||||||
|
|
||||||
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
{
|
{
|
||||||
var duckFadeTimeField = AccessTools.Field(__instance.GetType(), "duckFadeTime");
|
Plugin.Log.LogDebug($"UpdateDucking running on {__instance.GetType().Name}");
|
||||||
if (duckFadeTimeField != null)
|
}
|
||||||
{
|
|
||||||
duckFadeTimeField.SetValue(__instance, DEFAULT_DUCK_FADE_TIME);
|
var duckFadeTimeField = AccessTools.Field(__instance.GetType(), "duckFadeTime");
|
||||||
}
|
if (duckFadeTimeField != null)
|
||||||
|
{
|
||||||
|
duckFadeTimeField.SetValue(__instance, ModConfig.DuckFadeTime.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CountVoicePostfix(ref int __result)
|
private static void CountVoicePostfix(ref int __result)
|
||||||
{
|
{
|
||||||
if (__result > MAX_VOICE_COUNT)
|
int maxVoices = ModConfig.MaxVoiceCount.Value;
|
||||||
|
if (__result > maxVoices)
|
||||||
{
|
{
|
||||||
__result = MAX_VOICE_COUNT;
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"Voice count {__result} exceeds limit {maxVoices}, capping");
|
||||||
|
}
|
||||||
|
__result = maxVoices;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DuckVolumePrefix(ref float value)
|
private static bool DuckVolumePrefix(ref float value)
|
||||||
{
|
{
|
||||||
if (value < DEFAULT_DUCK_VOLUME)
|
float minDuck = ModConfig.DuckVolume.Value;
|
||||||
|
if (value < minDuck)
|
||||||
{
|
{
|
||||||
value = DEFAULT_DUCK_VOLUME;
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogDebug($"DuckVolume {value} below minimum {minDuck}, adjusting");
|
||||||
|
}
|
||||||
|
value = minDuck;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UtageFmodVoiceManager : MonoBehaviour
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ namespace HomuraHimeAudioMod
|
|||||||
{
|
{
|
||||||
instance = this;
|
instance = this;
|
||||||
logger = Logger;
|
logger = Logger;
|
||||||
|
|
||||||
harmony = new Harmony(PluginInfo.PLUGIN_GUID);
|
ModConfig.Initialize(Config);
|
||||||
|
|
||||||
Log.LogInfo($"HomuraHime Audio Mod v{PluginInfo.PLUGIN_VERSION} initializing...");
|
Log.LogInfo($"HomuraHime Audio Mod v{PluginInfo.PLUGIN_VERSION} initializing...");
|
||||||
|
Log.LogInfo($"Voice Limit Fix: {(ModConfig.EnableVoiceLimitFix.Value ? "ENABLED" : "DISABLED")}");
|
||||||
|
Log.LogInfo($"Ducking Fix: {(ModConfig.EnableDuckingFix.Value ? "ENABLED" : "DISABLED")}");
|
||||||
|
Log.LogInfo($"Enemy Audio Boost: {(ModConfig.EnableEnemyAudioBoost.Value ? "ENABLED" : "DISABLED")}");
|
||||||
|
Log.LogInfo($"Max Voices: {ModConfig.MaxVoiceCount.Value}");
|
||||||
|
|
||||||
|
harmony = new Harmony(PluginInfo.PLUGIN_GUID);
|
||||||
|
|
||||||
ApplyPatches();
|
ApplyPatches();
|
||||||
|
|
||||||
@@ -33,18 +39,43 @@ namespace HomuraHimeAudioMod
|
|||||||
|
|
||||||
private void ApplyPatches()
|
private void ApplyPatches()
|
||||||
{
|
{
|
||||||
Log.LogInfo("Applying audio patches...");
|
if (ModConfig.EnableVoiceLimitFix.Value || ModConfig.EnableDuckingFix.Value)
|
||||||
|
{
|
||||||
|
Log.LogInfo("Applying VoiceManager patches...");
|
||||||
|
VoiceManagerPatch.Apply(ref harmony);
|
||||||
|
}
|
||||||
|
|
||||||
VoiceManagerPatch.Apply(ref harmony);
|
if (ModConfig.EnableDuckingFix.Value)
|
||||||
AudioDuckingPatch.Apply(ref harmony);
|
{
|
||||||
EnemyAudioPatch.Apply(ref harmony);
|
Log.LogInfo("Applying AudioDucking patches...");
|
||||||
|
AudioDuckingPatch.Apply(ref harmony);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ModConfig.EnableEnemyAudioBoost.Value)
|
||||||
|
{
|
||||||
|
Log.LogInfo("Applying EnemyAudio patches...");
|
||||||
|
EnemyAudioPatch.Apply(ref harmony);
|
||||||
|
}
|
||||||
|
|
||||||
Log.LogInfo("All patches applied successfully");
|
Log.LogInfo("All patches applied successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (ModConfig.EnableDebugLogging.Value)
|
||||||
|
{
|
||||||
|
DebugLogUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DebugLogUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
harmony?.UnpatchAll(PluginInfo.PLUGIN_GUID);
|
harmony?.UnpatchAll(PluginInfo.PLUGIN_GUID);
|
||||||
|
Log.LogInfo("HomuraHime Audio Mod unloaded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
188
src/HomuraHimeAudioMod/README.md
Normal file
188
src/HomuraHimeAudioMod/README.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# HomuraHime Audio Mod
|
||||||
|
|
||||||
|
A BepInEx mod for HomuraHime that fixes audio glitches and improves enemy behavior audio indicators.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Audio Glitch Fixes
|
||||||
|
- **Voice Limit Fix**: Increases maximum concurrent voices to prevent audio cutoff during intense combat
|
||||||
|
- **Ducking Improvements**: Faster, more reliable audio ducking during voice/event playback
|
||||||
|
- **Fade Time Tuning**: Optimized fade times to prevent pops and clicks
|
||||||
|
|
||||||
|
### Enemy Audio Enhancements
|
||||||
|
- **Alert Sound Boost**: Makes enemy alert sounds more distinguishable
|
||||||
|
- **Attack Sound Boost**: Improves attack indicator audio clarity
|
||||||
|
- **Enemy Behavior Indicators**: Better audio cues for enemy state changes
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- [BepInEx 5.x](https://github.com/BepInEx/BepInEx/releases)
|
||||||
|
- HomuraHime (Steam)
|
||||||
|
- .NET Framework 4.8 or .NET SDK
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Automatic (Recommended)
|
||||||
|
|
||||||
|
1. Run `Build.ps1` script to build and install:
|
||||||
|
```powershell
|
||||||
|
cd src/HomuraHimeAudioMod
|
||||||
|
.\Build.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
|
||||||
|
1. Build the project:
|
||||||
|
```powershell
|
||||||
|
dotnet build -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Copy `HomuraHimeAudioMod.dll` to:
|
||||||
|
```
|
||||||
|
C:\apps\steam\steamapps\common\Homura Hime\BepInEx\plugins\
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Launch the game once to generate config files
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Config file location:
|
||||||
|
```
|
||||||
|
C:\apps\steam\steamapps\common\Homura Hime\BepInEx\config\com.homurahime.audiomod.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
### General Settings
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `EnableDebugLogging` | `false` | Enable detailed debug logging |
|
||||||
|
| `EnableVoiceLimitFix` | `true` | Enable voice limit improvements |
|
||||||
|
| `EnableDuckingFix` | `true` | Enable audio ducking improvements |
|
||||||
|
| `EnableEnemyAudioBoost` | `true` | Enable enemy audio enhancements |
|
||||||
|
|
||||||
|
### Voice Manager Settings
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `MaxVoiceCount` | `128` | Maximum concurrent voices (64-256 range) |
|
||||||
|
|
||||||
|
### Ducking Settings
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `DuckFadeTime` | `0.05` | Fade time for ducking (seconds) |
|
||||||
|
| `DuckVolume` | `0.3` | Volume level during ducking (0.0-1.0) |
|
||||||
|
|
||||||
|
### Enemy Audio Settings
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `EnemyIndicatorBoost` | `1.2` | Volume multiplier for indicators |
|
||||||
|
| `AlertSoundBoost` | `1.3` | Volume multiplier for alert sounds |
|
||||||
|
| `AttackSoundBoost` | `1.15` | Volume multiplier for attack sounds |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Mod Not Loading
|
||||||
|
|
||||||
|
Check `BepInEx\LogOutput.log` for errors. Common issues:
|
||||||
|
- Missing .NET Framework 4.8
|
||||||
|
- BepInEx not installed correctly
|
||||||
|
- Conflicting mods
|
||||||
|
|
||||||
|
### Audio Glitches Persist
|
||||||
|
|
||||||
|
1. Try reducing `MaxVoiceCount` if CPU is struggling
|
||||||
|
2. Increase `DuckFadeTime` for smoother transitions
|
||||||
|
3. Disable other audio mods to check for conflicts
|
||||||
|
|
||||||
|
### Game Crashes
|
||||||
|
|
||||||
|
1. Disable all patches and re-enable one at a time
|
||||||
|
2. Check for game updates that may have changed audio code
|
||||||
|
3. Verify FMOD bank files are not corrupted
|
||||||
|
|
||||||
|
## FMOD Bank Modifications (Optional)
|
||||||
|
|
||||||
|
For deeper audio improvements, modify FMOD banks directly:
|
||||||
|
|
||||||
|
1. Extract banks using AssetRipper
|
||||||
|
2. Open in FMOD Studio 2.02.x
|
||||||
|
3. Adjust voice limits, ducking, and enemy indicators
|
||||||
|
4. Reimport modified banks
|
||||||
|
|
||||||
|
See `docs/FMOD_MODIFICATION_GUIDE_PHASE3.md` for detailed instructions.
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- .NET Framework 4.8 SDK or .NET SDK
|
||||||
|
- Visual Studio 2022 (optional, for IDE support)
|
||||||
|
|
||||||
|
### Build Commands
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Restore packages
|
||||||
|
dotnet restore
|
||||||
|
|
||||||
|
# Build Debug
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# Build Release
|
||||||
|
dotnet build -c Release
|
||||||
|
|
||||||
|
# Clean
|
||||||
|
dotnet clean
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
Built DLL: `bin\Release\net48\HomuraHimeAudioMod.dll`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
HomuraHime-Mods/
|
||||||
|
├── src/
|
||||||
|
│ └── HomuraHimeAudioMod/
|
||||||
|
│ ├── Patches/
|
||||||
|
│ │ ├── VoiceManagerPatch.cs # Voice limit and ducking patches
|
||||||
|
│ │ ├── AudioDuckingPatch.cs # Snapshot and fade patches
|
||||||
|
│ │ └── EnemyAudioPatch.cs # Enemy audio enhancement patches
|
||||||
|
│ ├── Plugin.cs # Main BepInEx plugin
|
||||||
|
│ ├── ModConfig.cs # Configuration management
|
||||||
|
│ └── HomuraHimeAudioMod.csproj # Project file
|
||||||
|
├── docs/
|
||||||
|
│ ├── SETUP_PHASE1.md # Tool installation guide
|
||||||
|
│ ├── AUDIO_ANALYSIS.md # Game audio system analysis
|
||||||
|
│ ├── CODE_ANALYSIS_PHASE2.md # Deep code analysis
|
||||||
|
│ └── FMOD_MODIFICATION_GUIDE_PHASE3.md # FMOD bank editing guide
|
||||||
|
└── tools/
|
||||||
|
└── ExtractBanks.ps1 # Bank extraction script
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
- FMOD bank modifications require game restart to take effect
|
||||||
|
- Voice count limits may need adjustment based on hardware
|
||||||
|
- Debug logging can impact performance
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.0.0
|
||||||
|
- Initial release
|
||||||
|
- Voice limit fix (configurable max voices)
|
||||||
|
- Audio ducking improvements
|
||||||
|
- Enemy audio indicator boost options
|
||||||
|
- BepInEx configuration support
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This mod is provided for personal use. Please respect the game's EULA and copyright.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- BepInEx team for the modding framework
|
||||||
|
- FMOD for audio middleware
|
||||||
|
- Unity Technologies for game engine
|
||||||
Reference in New Issue
Block a user