1
0
Fork 0

Override DeathScreen / GameOver text, add support for per-track strings

Custom message is shown only if the player dies to a Jester.
This commit is contained in:
ivan tkachenko 2025-09-22 02:58:58 +03:00
parent 0fadf50bf4
commit e67c72951e
4 changed files with 142 additions and 1 deletions

View File

@ -2,6 +2,7 @@
## MuzikaGromche 1337.420.9004
- Override Death Screen / Game Over text in certain cases.
## MuzikaGromche 1337.420.9003 - Lights Out Edition

View File

@ -0,0 +1,72 @@
using System.Collections;
using HarmonyLib;
using TMPro;
using UnityEngine;
namespace MuzikaGromche
{
static class DeathScreenGameOverTextManager
{
private const string GameOverTextVanilla = "[LIFE SUPPORT: OFFLINE]";
public const string GameOverTextModdedDefault = "[ MUZIKA: GROMCHE ]";
public static void Clear()
{
SetTextImpl(GameOverTextVanilla);
}
public static void SetText(string? text)
{
SetTextImpl(text ?? GameOverTextModdedDefault);
}
public static IEnumerator SetTextAndClear(string? text)
{
SetText(text);
// Game Over animation duration is about 4.25 seconds
yield return new WaitForSeconds(5f);
Clear();
}
private static void SetTextImpl(string text)
{
GameObject textGameObject = GameObject.Find("Systems/UI/Canvas/DeathScreen/GameOverText");
if (textGameObject == null)
{
return;
}
TextMeshProUGUI tmp = textGameObject.GetComponent<TextMeshProUGUI>();
if (tmp == null)
{
return;
}
var transform = textGameObject.GetComponent<RectTransform>();
if (transform == null)
{
return;
}
// Default transform width is 645.8 which only fit the default message and no extra characters
Vector2 size = transform.sizeDelta;
if (Mathf.Approximately(size.x, 645.8f))
{
size.x += 100f;
transform.sizeDelta = size;
}
tmp.text = text;
}
}
[HarmonyPatch(typeof(RoundManager))]
static class DeathScreenGameOverTextResetPatch
{
[HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))]
[HarmonyPatch(nameof(RoundManager.OnDestroy))]
[HarmonyPrefix]
static void OnDestroy(RoundManager __instance)
{
var _ = __instance;
DeathScreenGameOverTextManager.Clear();
}
}
}

View File

@ -52,6 +52,12 @@
<Reference Include="Unity.Netcode.Runtime" Publicize="true" Private="false">
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.TextMeshPro" Publicize="true" Private="False">
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.TextMeshPro.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI" Publicize="true" Private="False">
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">

View File

@ -260,6 +260,7 @@ namespace MuzikaGromche
FadeOutDuration = 4,
FlickerLightsTimeSeries = [-5, 31],
Lyrics = [],
GameOverText = "[MUZIKA GROMCHE: K-POP]",
},
new SelectableAudioTrack
{
@ -278,6 +279,7 @@ namespace MuzikaGromche
FadeOutDuration = 4,
FlickerLightsTimeSeries = [-5],
Lyrics = [],
GameOverText = "[COULD'VE BEEN: IMMORTAL]",
},
new SelectableAudioTrack
{
@ -296,6 +298,7 @@ namespace MuzikaGromche
FadeOutDuration = 4,
FlickerLightsTimeSeries = [-5.5f, 31, 63.9f],
Lyrics = [],
GameOverText = "[ HEY, YOUNG BLOOD ]",
},
new SelectableAudioTrack
{
@ -443,6 +446,7 @@ namespace MuzikaGromche
(96, $"\t\t\tresolving ur private IP\n/{PwnLyricsVariants[^1]}"),
(98, $"\t\t\tresolving ur private IP\nP_WNED"),
],
GameOverText = "[HACK3D BY: RUSSI4NS]",
},
new SelectableAudioTrack
{
@ -467,6 +471,7 @@ namespace MuzikaGromche
FadeOutDuration = 6,
FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45],
Lyrics = [],
GameOverText = "[DIDN'T PUMP IT: LOUDER]",
},
new SelectableAudioTrack
{
@ -680,6 +685,7 @@ namespace MuzikaGromche
harmony.PatchAll(typeof(DiscoBallTilePatch));
harmony.PatchAll(typeof(DiscoBallDespawnPatch));
harmony.PatchAll(typeof(SpawnRatePatch));
harmony.PatchAll(typeof(DeathScreenGameOverTextResetPatch));
NetcodePatcher();
Compatibility.Register(this);
}
@ -882,6 +888,8 @@ namespace MuzikaGromche
public string[] LyricsLines { get; }
public Palette Palette { get; }
public string? GameOverText { get => null; }
}
// A proxy audio track with default implementation for every IAudioTrack method that simply forwards requests to the inner IAudioTrack.
@ -907,6 +915,7 @@ namespace MuzikaGromche
float[] IAudioTrack.LyricsTimeSeries => Track.LyricsTimeSeries;
string[] IAudioTrack.LyricsLines => Track.LyricsLines;
Palette IAudioTrack.Palette => Track.Palette;
string? IAudioTrack.GameOverText => Track.GameOverText;
}
// Core audio track implementation with some defaults and config overrides.
@ -982,6 +991,8 @@ namespace MuzikaGromche
}
public Palette Palette { get; set; } = Palette.DEFAULT;
public string? GameOverText { get; init; } = null;
}
// Standalone, top-level, selectable audio track
@ -1881,6 +1892,7 @@ namespace MuzikaGromche
SetupEntriesToSkipWinding(configFile);
SetupEntriesForPaletteOverride(configFile);
SetupEntriesForTimingsOverride(configFile);
SetupEntriesForGameOverText(configFile);
#endif
var chanceRange = new AcceptableValueRange<int>(0, 100);
@ -2182,6 +2194,33 @@ namespace MuzikaGromche
}
}
}
private void SetupEntriesForGameOverText(ConfigFile configFile)
{
const string section = "Game Over";
var gameOverTextConfigEntry = configFile.Bind(section, "Game Over Text", DeathScreenGameOverTextManager.GameOverTextModdedDefault,
new ConfigDescription("Custom Game Over text to show."));
LethalConfigManager.AddConfigItem(new GenericButtonConfigItem(section, "Game Over Animation",
"Run Death Screen / Game Over animation 3 times.", "Trigger", () =>
{
HUDManager.Instance.StartCoroutine(AnimateGameOverText(gameOverTextConfigEntry.Value));
}));
LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(gameOverTextConfigEntry, requiresRestart: false));
}
static IEnumerator AnimateGameOverText(string text)
{
yield return new WaitForSeconds(1f);
for (int i = 0; i < 3; i++)
{
DeathScreenGameOverTextManager.SetText(text);
HUDManager.Instance.gameOverAnimator.SetTrigger("gameOver");
yield return new WaitForSeconds(5f);
HUDManager.Instance.gameOverAnimator.SetTrigger("revive");
yield return new WaitForSeconds(1f);
}
DeathScreenGameOverTextManager.Clear();
}
#endif
private T Default<T>(T options) where T : BaseOptions
@ -2335,6 +2374,16 @@ namespace MuzikaGromche
LoopAudioSource.PlayScheduled(loopStartDspTime);
Debug.Log($"{nameof(MuzikaGromche)} Play Intro: dspTime={AudioSettings.dspTime:N4}, intro.time={IntroAudioSource.time:N4}/{IntroAudioSource.clip.length:N4}, scheduled Loop={loopStartDspTime}");
}
public void OverrideDeathScreenGameOverText()
{
if (CurrentTrack == null)
{
// Playing as a client with a host who doesn't have the mod
return;
}
StartCoroutine(DeathScreenGameOverTextManager.SetTextAndClear(CurrentTrack.GameOverText));
}
}
[HarmonyPatch(typeof(JesterAI))]
@ -2484,6 +2533,18 @@ namespace MuzikaGromche
}
}
}
[HarmonyPatch(nameof(JesterAI.killPlayerAnimation))]
[HarmonyPrefix]
static void JesterKillPlayerAnimationPrefix(JesterAI __instance, int playerId)
{
// Note on cast to int: base game already downcasts ulong to int anyway
if (playerId == (int)GameNetworkManager.Instance.localPlayerController.playerClientId)
{
var behaviour = __instance.GetComponent<MuzikaGromcheJesterNetworkBehaviour>();
behaviour.OverrideDeathScreenGameOverText();
}
}
}
[HarmonyPatch(typeof(EnemyAI))]
@ -2499,8 +2560,9 @@ namespace MuzikaGromche
{
PoweredLightsBehaviour.Instance.ResetLightColor();
DiscoBallManager.Disable();
DeathScreenGameOverTextManager.Clear();
// Just in case if players have spawned multiple Jesters,
// Don't reset Plugin.CurrentTrack to null,
// Don't reset Config.CurrentTrack to null,
// so that the latest chosen track remains set.
}
}