diff --git a/Art/manifest.json b/Art/manifest.json index 81cbfe3..06f81b6 100644 --- a/Art/manifest.json +++ b/Art/manifest.json @@ -1,7 +1,10 @@ { "name": "Coroner", - "version_number": "1.0.0", + "version_number": "1.1.0", "website_url": "https://github.com/EliteMasterEric/Coroner", "description": "Rework the Performance Report with new info, including cause of death.", - "dependencies": [] + "dependencies": [ + "BepInEx-BepInExPack-5.4.2100", + "2018-LC_API-2.1.1" + ] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9977698..506e6bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 1.1.0 +## Additions +- Added a custom cause of death for the dropship. +- Added an optional dependency on LC_API and used it to improve accuracy of cause of death reports over multiplayer. +## Bug Fixes +- Fixed a softlock/crash related to checking if the player has a Jetpack. +- Fixed the cause of death not being evaluated properly when being crushed by a ladder. +- Fixed the cause of death not being evaluated properly when drowning in Quicksand. +- Fixed a bug where notes would not start with "Notes:". +- Fixed an issue where BepInEx was not listed as a mandatory dependency. +- Fixed an issue with enemy-related custom deaths not working + ## 1.0.0 Initial release. - Added cause of death to the results screen. diff --git a/Coroner/AdvancedCauseOfDeath.cs b/Coroner/AdvancedCauseOfDeath.cs index 5cfbb40..3854837 100644 --- a/Coroner/AdvancedCauseOfDeath.cs +++ b/Coroner/AdvancedCauseOfDeath.cs @@ -1,30 +1,32 @@ using System; using System.Collections.Generic; using GameNetcodeStuff; -using Steamworks; namespace Coroner { class AdvancedDeathTracker { + public const int PLAYER_CAUSE_OF_DEATH_DROPSHIP = 300; + private static readonly Dictionary PlayerCauseOfDeath = new Dictionary(); public static void ClearDeathTracker() { PlayerCauseOfDeath.Clear(); } - public static void SetCauseOfDeath(int playerIndex, AdvancedCauseOfDeath causeOfDeath) { + public static void SetCauseOfDeath(int playerIndex, AdvancedCauseOfDeath causeOfDeath, bool broadcast = true) { PlayerCauseOfDeath[playerIndex] = causeOfDeath; + if (broadcast) DeathBroadcaster.BroadcastCauseOfDeath(playerIndex, causeOfDeath); } - public static void SetCauseOfDeath(int playerIndex, CauseOfDeath causeOfDeath) { - SetCauseOfDeath(playerIndex, ConvertCauseOfDeath(causeOfDeath)); + public static void SetCauseOfDeath(int playerIndex, CauseOfDeath causeOfDeath, bool broadcast = true) { + SetCauseOfDeath(playerIndex, ConvertCauseOfDeath(causeOfDeath), broadcast); } - public static void SetCauseOfDeath(PlayerControllerB playerController, CauseOfDeath causeOfDeath) { - SetCauseOfDeath((int) playerController.playerClientId, ConvertCauseOfDeath(causeOfDeath)); + public static void SetCauseOfDeath(PlayerControllerB playerController, CauseOfDeath causeOfDeath, bool broadcast = true) { + SetCauseOfDeath((int) playerController.playerClientId, ConvertCauseOfDeath(causeOfDeath), broadcast); } - public static void SetCauseOfDeath(PlayerControllerB playerController, AdvancedCauseOfDeath causeOfDeath) { - SetCauseOfDeath((int) playerController.playerClientId, causeOfDeath); + public static void SetCauseOfDeath(PlayerControllerB playerController, AdvancedCauseOfDeath causeOfDeath, bool broadcast = true) { + SetCauseOfDeath((int) playerController.playerClientId, causeOfDeath, broadcast); } public static AdvancedCauseOfDeath GetCauseOfDeath(int playerIndex) { @@ -35,16 +37,17 @@ public static AdvancedCauseOfDeath GetCauseOfDeath(int playerIndex) { public static AdvancedCauseOfDeath GetCauseOfDeath(PlayerControllerB playerController) { if (!PlayerCauseOfDeath.ContainsKey((int) playerController.playerClientId)) { + Plugin.Instance.PluginLogger.LogInfo($"Player {playerController.playerClientId} has no custom cause of death stored! Using fallback..."); return GuessCauseOfDeath(playerController); + } else { + Plugin.Instance.PluginLogger.LogInfo($"Player {playerController.playerClientId} has custom cause of death stored! {PlayerCauseOfDeath[(int) playerController.playerClientId]}"); + return PlayerCauseOfDeath[(int) playerController.playerClientId]; } - return PlayerCauseOfDeath[(int) playerController.playerClientId]; } public static AdvancedCauseOfDeath GuessCauseOfDeath(PlayerControllerB playerController) { if (playerController.isPlayerDead) { - if (playerController.causeOfDeath == CauseOfDeath.Suffocation && playerController.isSinking) { - return AdvancedCauseOfDeath.Quicksand; - } else if (IsHoldingJetpack(playerController)) { + if (IsHoldingJetpack(playerController)) { if (playerController.causeOfDeath == CauseOfDeath.Gravity) { return AdvancedCauseOfDeath.Jetpack_Gravity; } else if (playerController.causeOfDeath == CauseOfDeath.Blast) { @@ -59,10 +62,13 @@ public static AdvancedCauseOfDeath GuessCauseOfDeath(PlayerControllerB playerCon } public static bool IsHoldingJetpack(PlayerControllerB playerController) { - var heldObject = playerController.currentlyHeldObjectServer.gameObject.GetComponent(); - if (heldObject == null) { - return false; - } + var heldObjectServer = playerController.currentlyHeldObjectServer; + if (heldObjectServer == null) return false; + var heldObjectGameObject = heldObjectServer.gameObject; + if (heldObjectGameObject == null) return false; + var heldObject = heldObjectGameObject.GetComponent(); + if (heldObject == null) return false; + if (heldObject is JetpackItem) { return true; } else { @@ -112,7 +118,7 @@ public static string StringifyCauseOfDeath(AdvancedCauseOfDeath causeOfDeath) { case AdvancedCauseOfDeath.Gravity: return "Fell to their death."; case AdvancedCauseOfDeath.Blast: - return "Exploded."; + return "Went out with a bang."; case AdvancedCauseOfDeath.Strangulation: return "Strangled to death."; case AdvancedCauseOfDeath.Suffocation: @@ -130,8 +136,6 @@ public static string StringifyCauseOfDeath(AdvancedCauseOfDeath causeOfDeath) { case AdvancedCauseOfDeath.Electrocution: return "Electrocuted to death."; - case AdvancedCauseOfDeath.Enemy_DepositItemsDesk: - return "Received a demotion."; case AdvancedCauseOfDeath.Enemy_Bracken: return "Had their neck snapped by a Bracken."; case AdvancedCauseOfDeath.Enemy_EyelessDog: @@ -148,18 +152,36 @@ public static string StringifyCauseOfDeath(AdvancedCauseOfDeath causeOfDeath) { return "Was eaten by a Baboon Hawk."; case AdvancedCauseOfDeath.Enemy_Jester: return "Was the butt of a joke."; + case AdvancedCauseOfDeath.Enemy_SnareFlea: + return "Was suffocated a Snare Flea."; + case AdvancedCauseOfDeath.Enemy_Hygrodere: + return "Was absorbed by a Hygrodere."; + case AdvancedCauseOfDeath.Enemy_HoarderBug: + return "Was hoarded by a Hoarder Bug."; + case AdvancedCauseOfDeath.Enemy_SporeLizard: + return "Was puffed by a Spore Lizard."; + case AdvancedCauseOfDeath.Enemy_SandSpider: + return "Ensnared in the Sand Spider's web."; - case AdvancedCauseOfDeath.Quicksand: - return "Got stuck in quicksand."; case AdvancedCauseOfDeath.Jetpack_Gravity: return "Flew too close to the sun."; case AdvancedCauseOfDeath.Jetpack_Blast: - return "Went up in a fiery blaze."; + return "Turned into a firework."; + case AdvancedCauseOfDeath.Player_Murder: + return "Was the victim of a murder."; + case AdvancedCauseOfDeath.Player_Quicksand: + return "Got stuck in quicksand."; + case AdvancedCauseOfDeath.Player_DepositItemsDesk: + return "Received a demotion."; + case AdvancedCauseOfDeath.Player_Dropship: + return "Couldn't wait for their items."; + case AdvancedCauseOfDeath.Player_StunGrenade: + return "Was the victim of a murder."; case AdvancedCauseOfDeath.Unknown: - return "Died somehow."; + return "Most sincerely dead."; default: - return "Died somehow."; + return "Most sincerely dead."; } } @@ -185,19 +207,28 @@ enum AdvancedCauseOfDeath { Electrocution, // Custom causes (enemies) - Enemy_DepositItemsDesk, + Enemy_BaboonHawk, // Also known as BaboonBird Enemy_Bracken, // Also known as Flowerman + Enemy_CircuitBees, // Also known as RedLocustBees + Enemy_EarthLeviathan, // Also known as SandWorm Enemy_EyelessDog, // Also known as MouthDog Enemy_ForestGiant, - Enemy_CircuitBees, // Also known as RedLocustBees Enemy_GhostGirl, // Also known as DressGirl - Enemy_EarthLeviathan, // Also known as SandWorm - Enemy_BaboonHawk, // Also known as BaboonBird + Enemy_Jester, + Enemy_SnareFlea, // Also known as Centipede + Enemy_SporeLizard, // Also known as Puffer TODO: Implement this. + Enemy_Hygrodere, // Also known as Blob TODO: Implement this. + Enemy_SandSpider, // TODO: Implement this. + Enemy_Thumper, // Also known as Crawler TODO: Implement this. + Enemy_HoarderBug, // TODO: Implement this. // Custom causes (other) - Quicksand, - Enemy_Jester, Jetpack_Gravity, Jetpack_Blast, + Player_Quicksand, + Player_Murder, + Player_DepositItemsDesk, + Player_Dropship, + Player_StunGrenade, // TODO: Implement this. } } \ No newline at end of file diff --git a/Coroner/Coroner.csproj b/Coroner/Coroner.csproj index cf97c18..68bdeed 100644 --- a/Coroner/Coroner.csproj +++ b/Coroner/Coroner.csproj @@ -73,6 +73,11 @@ false ..\include\UnityEngine.UI.dll + + + false + ..\include\LC_API.dll + @@ -87,6 +92,12 @@ + + + + + + diff --git a/Coroner/DeathBroadcaster.cs b/Coroner/DeathBroadcaster.cs new file mode 100644 index 0000000..a6fb1ce --- /dev/null +++ b/Coroner/DeathBroadcaster.cs @@ -0,0 +1,44 @@ +namespace Coroner { + class DeathBroadcaster { + const string SIGNATURE_DEATH = PluginInfo.PLUGIN_GUID + ".death"; + + public static void Initialize() { + Plugin.Instance.PluginLogger.LogInfo("Initializing DeathBroadcaster..."); + if (Plugin.Instance.IsLC_APIPresent) { + Plugin.Instance.PluginLogger.LogInfo("LC_API is present! Registering signature..."); + LC_API.ServerAPI.Networking.GetString += OnBroadcastString; + } else { + Plugin.Instance.PluginLogger.LogInfo("LC_API is not present! Skipping registration..."); + } + } + + static void OnBroadcastString(string data, string signature) { + if (signature == SIGNATURE_DEATH) { + Plugin.Instance.PluginLogger.LogInfo("Broadcast has been received from LC_API!"); + string[] split = data.Split('|'); + int playerId = int.Parse(split[0]); + int causeOfDeathInt = int.Parse(split[1]); + AdvancedCauseOfDeath causeOfDeath = (AdvancedCauseOfDeath) causeOfDeathInt; + Plugin.Instance.PluginLogger.LogInfo("Player " + playerId + " died of " + AdvancedDeathTracker.StringifyCauseOfDeath(causeOfDeath)); + AdvancedDeathTracker.SetCauseOfDeath(playerId, causeOfDeath, false); + } + } + + public static void BroadcastCauseOfDeath(int playerId, AdvancedCauseOfDeath causeOfDeath) { + AttemptBroadcast(BuildData(playerId, causeOfDeath), SIGNATURE_DEATH); + } + + static string BuildData(int playerId, AdvancedCauseOfDeath causeOfDeath) { + return playerId + "|" + ((int) causeOfDeath); + } + + static void AttemptBroadcast(string data, string signature) { + if (Plugin.Instance.IsLC_APIPresent) { + Plugin.Instance.PluginLogger.LogInfo("LC_API is present! Broadcasting..."); + LC_API.ServerAPI.Networking.Broadcast(data, signature); + } else { + Plugin.Instance.PluginLogger.LogInfo("LC_API is not present! Skipping broadcast..."); + } + } + } +} \ No newline at end of file diff --git a/Coroner/Patch/CauseOfDeathPatch.cs b/Coroner/Patch/CauseOfDeathPatch.cs index e88de5e..d969781 100644 --- a/Coroner/Patch/CauseOfDeathPatch.cs +++ b/Coroner/Patch/CauseOfDeathPatch.cs @@ -8,84 +8,71 @@ namespace Coroner.Patch { * and storing it in the AdvancedDeathTracker, because `causeOfDeath` is not precise enough. */ - [HarmonyPatch(typeof(DepositItemsDesk))] - [HarmonyPatch("AnimationGrabPlayer")] - class DepositItemsDeskAnimationGrabPlayerPatch { - public static void Postfix(int playerID) { - Plugin.Instance.PluginLogger.LogInfo("Accessing state after tentacle devouring..."); + [HarmonyPatch(typeof(PlayerControllerB))] + [HarmonyPatch("KillPlayer")] + class PlayerControllerBKillPlayerPatch { + public static void Prefix(PlayerControllerB __instance, ref CauseOfDeath causeOfDeath) { + // NOTE: Only called on the client of the player who died. - PlayerControllerB playerDying = StartOfRound.Instance.allPlayerScripts[playerID]; - if (playerDying.isPlayerDead) { - Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); - AdvancedDeathTracker.SetCauseOfDeath(playerDying, AdvancedCauseOfDeath.Enemy_DepositItemsDesk); - } else { - Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); + if ((int) causeOfDeath == AdvancedDeathTracker.PLAYER_CAUSE_OF_DEATH_DROPSHIP) { + Plugin.Instance.PluginLogger.LogInfo("Player died from item dropship! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(__instance, AdvancedCauseOfDeath.Player_Dropship); + // Now to fix the jank by adding a normal value! + causeOfDeath = CauseOfDeath.Crushing; return; } + + if (__instance.isSinking && causeOfDeath == CauseOfDeath.Suffocation) { + Plugin.Instance.PluginLogger.LogInfo("Player died of suffociation while sinking in quicksand! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(__instance, AdvancedCauseOfDeath.Player_Quicksand); + } else { + Plugin.Instance.PluginLogger.LogInfo("Player is dying! No cause of death..."); + } } } - [HarmonyPatch(typeof(FlowermanAI))] - [HarmonyPatch("killAnimation")] - class FlowermanAIKillAnimationPatch { - public static void Postfix(FlowermanAI __instance) { - Plugin.Instance.PluginLogger.LogInfo("Accessing state after Bracken snapping neck..."); - - if (__instance.inSpecialAnimationWithPlayer == null) { - Plugin.Instance.PluginLogger.LogWarning("Could not access player after snapping neck!"); - return; - } + [HarmonyPatch(typeof(DepositItemsDesk))] + [HarmonyPatch("AnimationGrabPlayer")] + class DepositItemsDeskAnimationGrabPlayerPatch { + public static void Postfix(int playerID) { + Plugin.Instance.PluginLogger.LogInfo("Accessing state after tentacle devouring..."); - if (__instance.inSpecialAnimationWithPlayer.isPlayerDead) { - Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); - AdvancedDeathTracker.SetCauseOfDeath(__instance.inSpecialAnimationWithPlayer, AdvancedCauseOfDeath.Enemy_Bracken); - } else { - Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); - return; - } + PlayerControllerB playerDying = StartOfRound.Instance.allPlayerScripts[playerID]; + Plugin.Instance.PluginLogger.LogInfo("Player is dying! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(playerDying, AdvancedCauseOfDeath.Player_DepositItemsDesk); } } - [HarmonyPatch(typeof(MouthDogAI))] - [HarmonyPatch("KillPlayer")] - class MouthDogAIKillPlayerPatch { - public static void Postfix(int playerId) { - Plugin.Instance.PluginLogger.LogInfo("Accessing state after dog devouring..."); + [HarmonyPatch(typeof(JesterAI))] + [HarmonyPatch("killPlayerAnimation")] + class JesterAIKillPlayerAnimationPatch { + public static void Postfix(JesterAI __instance, int playerId) { + Plugin.Instance.PluginLogger.LogInfo("Accessing state after Jester mauling..."); - PlayerControllerB playerDying = StartOfRound.Instance.allPlayerScripts[playerId]; - if (playerDying == null) { + PlayerControllerB playerControllerB = StartOfRound.Instance.allPlayerScripts[playerId]; + if (playerControllerB == null) { Plugin.Instance.PluginLogger.LogWarning("Could not access player after death!"); return; } - if (playerDying.isPlayerDead) { - Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); - AdvancedDeathTracker.SetCauseOfDeath(playerDying, AdvancedCauseOfDeath.Enemy_EyelessDog); - } else { - Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); - return; - } + Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(playerControllerB, AdvancedCauseOfDeath.Enemy_Jester); } } - [HarmonyPatch(typeof(ForestGiantAI))] - [HarmonyPatch("EatPlayerAnimation")] - class ForestGiantAIEatPlayerAnimationPatch { - public static void Postfix(PlayerControllerB playerBeingEaten) { - Plugin.Instance.PluginLogger.LogInfo("Accessing state after Forest Giant devouring..."); + [HarmonyPatch(typeof(SandWormAI))] + [HarmonyPatch("EatPlayer")] + class SandWormAIEatPlayerPatch { + public static void Postfix(PlayerControllerB playerScript) { + Plugin.Instance.PluginLogger.LogInfo("Accessing state after Sand Worm devouring..."); - if (playerBeingEaten == null) { + if (playerScript == null) { Plugin.Instance.PluginLogger.LogWarning("Could not access player after death!"); return; } - - if (playerBeingEaten.isPlayerDead) { - Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); - AdvancedDeathTracker.SetCauseOfDeath(playerBeingEaten, AdvancedCauseOfDeath.Enemy_ForestGiant); - } else { - Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); - return; - } + + Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(playerScript, AdvancedCauseOfDeath.Enemy_EarthLeviathan); } } @@ -93,7 +80,7 @@ public static void Postfix(PlayerControllerB playerBeingEaten) { [HarmonyPatch("BeeKillPlayerOnLocalClient")] class RedLocustBeesBeeKillPlayerOnLocalClientPatch { public static void Postfix(int playerId) { - Plugin.Instance.PluginLogger.LogInfo("Accessing state after Red Locust devouring..."); + Plugin.Instance.PluginLogger.LogInfo("Accessing state after Circuit Bee electrocution..."); PlayerControllerB playerDying = StartOfRound.Instance.allPlayerScripts[playerId]; if (playerDying == null) { @@ -115,34 +102,86 @@ public static void Postfix(int playerId) { [HarmonyPatch("OnCollideWithPlayer")] class DressGirlAIOnCollideWithPlayerPatch { public static void Postfix(DressGirlAI __instance, Collider other) { + Plugin.Instance.PluginLogger.LogInfo("Processing Ghost Girl player collision..."); + if (__instance.hauntingPlayer == null) { - Plugin.Instance.PluginLogger.LogWarning("Could not access player after death!"); + Plugin.Instance.PluginLogger.LogWarning("Could not access player after collision!"); return; } if (__instance.hauntingPlayer.isPlayerDead) { Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); AdvancedDeathTracker.SetCauseOfDeath(__instance.hauntingPlayer, AdvancedCauseOfDeath.Enemy_GhostGirl); - } else { - Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); + } + } + } + + [HarmonyPatch(typeof(FlowermanAI))] + [HarmonyPatch("killAnimation")] + class FlowermanAIKillAnimationPatch { + public static void Postfix(FlowermanAI __instance) { + Plugin.Instance.PluginLogger.LogInfo("Accessing state after Bracken snapping neck..."); + + if (__instance.inSpecialAnimationWithPlayer == null) { + Plugin.Instance.PluginLogger.LogWarning("Could not access player after snapping neck!"); return; } + + Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(__instance.inSpecialAnimationWithPlayer, AdvancedCauseOfDeath.Enemy_Bracken); } } - [HarmonyPatch(typeof(SandWormAI))] - [HarmonyPatch("EatPlayer")] - class SandWormAIEatPlayerPatch { - public static void Postfix(PlayerControllerB playerScript) { - if (playerScript == null) { + [HarmonyPatch(typeof(ForestGiantAI))] + [HarmonyPatch("EatPlayerAnimation")] + class ForestGiantAIEatPlayerAnimationPatch { + public static void Postfix(PlayerControllerB playerBeingEaten) { + Plugin.Instance.PluginLogger.LogInfo("Accessing state after Forest Giant devouring..."); + + if (playerBeingEaten == null) { Plugin.Instance.PluginLogger.LogWarning("Could not access player after death!"); return; } - if (playerScript.isPlayerDead) { + + Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(playerBeingEaten, AdvancedCauseOfDeath.Enemy_ForestGiant); + } + } + + [HarmonyPatch(typeof(MouthDogAI))] + [HarmonyPatch("KillPlayer")] + class MouthDogAIKillPlayerPatch { + public static void Postfix(int playerId) { + Plugin.Instance.PluginLogger.LogInfo("Accessing state after dog devouring..."); + + PlayerControllerB playerDying = StartOfRound.Instance.allPlayerScripts[playerId]; + if (playerDying == null) { + Plugin.Instance.PluginLogger.LogWarning("Could not access player after death!"); + return; + } + + Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); + AdvancedDeathTracker.SetCauseOfDeath(playerDying, AdvancedCauseOfDeath.Enemy_EyelessDog); + } + } + + [HarmonyPatch(typeof(CentipedeAI))] + [HarmonyPatch("DamagePlayerOnIntervals")] + class CentipedeAIDamagePlayerOnIntervalsPatch { + public static void Postfix(CentipedeAI __instance) { + Plugin.Instance.PluginLogger.LogInfo("Handling Snare Flea damage..."); + if (__instance.clingingToPlayer == null) { + Plugin.Instance.PluginLogger.LogWarning("Could not access player being clung to!"); + return; + } + + if (__instance.clingingToPlayer.isPlayerDead && __instance.clingingToPlayer.causeOfDeath == CauseOfDeath.Suffocation) { Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); - AdvancedDeathTracker.SetCauseOfDeath(playerScript, AdvancedCauseOfDeath.Enemy_EarthLeviathan); - } else { - Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); + AdvancedDeathTracker.SetCauseOfDeath(__instance.clingingToPlayer, AdvancedCauseOfDeath.Enemy_SnareFlea); + } else if (__instance.clingingToPlayer.isPlayerDead) { + Plugin.Instance.PluginLogger.LogWarning("Player died while attacked by Snare Flea! Skipping..."); return; + } else { + // Player still alive. } } } @@ -151,6 +190,8 @@ public static void Postfix(PlayerControllerB playerScript) { [HarmonyPatch("OnCollideWithPlayer")] class BaboonBirdAIOnCollideWithPlayerPatch { public static void Postfix(BaboonBirdAI __instance, Collider other) { + Plugin.Instance.PluginLogger.LogInfo("Handling Baboon Hawk damage..."); + var doingKillAnimation = Traverse.Create(__instance).Field("doingKillAnimation").GetValue(); PlayerControllerB playerControllerB = __instance.MeetsStandardPlayerCollisionConditions(other, __instance.inSpecialAnimation || doingKillAnimation); if (playerControllerB == null) { @@ -168,23 +209,90 @@ public static void Postfix(BaboonBirdAI __instance, Collider other) { } } - [HarmonyPatch(typeof(JesterAI))] - [HarmonyPatch("killPlayerAnimation")] - class JesterAIKillPlayerAnimationPatch { - public static void Postfix(JesterAI __instance, int playerId) { - PlayerControllerB playerControllerB = StartOfRound.Instance.allPlayerScripts[playerId]; - if (playerControllerB == null) { - Plugin.Instance.PluginLogger.LogWarning("Could not access player after death!"); + [HarmonyPatch(typeof(PlayerControllerB))] + [HarmonyPatch("DamagePlayerFromOtherClientClientRpc")] + class PlayerControllerBDamagePlayerFromOtherClientClientRpcPatch { + public static void Postfix(PlayerControllerB __instance, int playerWhoHit) { + Plugin.Instance.PluginLogger.LogInfo("Handling friendly fire damage..."); + if (__instance == null) { + Plugin.Instance.PluginLogger.LogWarning("Could not access victim after death!"); return; } + // PlayerControllerB playerControllerWhoHit = StartOfRound.Instance.allPlayerScripts[playerWhoHit]; - if (playerControllerB.isPlayerDead) { + if (__instance.isPlayerDead) { Plugin.Instance.PluginLogger.LogInfo("Player is now dead! Setting special cause of death..."); - AdvancedDeathTracker.SetCauseOfDeath(playerControllerB, AdvancedCauseOfDeath.Enemy_Jester); + AdvancedDeathTracker.SetCauseOfDeath(__instance, AdvancedCauseOfDeath.Player_Murder); } else { Plugin.Instance.PluginLogger.LogWarning("Player is somehow still alive! Skipping..."); return; } } } + + [HarmonyPatch(typeof(ExtensionLadderItem))] + [HarmonyPatch("StartLadderAnimation")] + class ExtensionLadderItemStartLadderAnimationPatch { + public static void Postfix(ExtensionLadderItem __instance) { + Plugin.Instance.PluginLogger.LogInfo("Extension ladder started animation! Modifying kill trigger..."); + + GameObject extensionLadderGameObject = __instance.gameObject; + if (extensionLadderGameObject == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch GameObject from ExtensionLadderItem."); + } + Transform killTriggerTransform = extensionLadderGameObject.transform.Find("AnimContainer/MeshContainer/LadderMeshContainer/BaseLadder/LadderSecondPart/KillTrigger"); + + if (killTriggerTransform == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch KillTrigger Transform from ExtensionLadderItem."); + } + + GameObject killTriggerGameObject = killTriggerTransform.gameObject; + + if (killTriggerGameObject == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch KillTrigger GameObject from ExtensionLadderItem."); + } + + KillLocalPlayer killLocalPlayer = killTriggerGameObject.GetComponent(); + + if (killLocalPlayer == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch KillLocalPlayer from KillTrigger GameObject."); + } + + // Correct the cause of death. + killLocalPlayer.causeOfDeath = CauseOfDeath.Crushing; + } + } + + [HarmonyPatch(typeof(ItemDropship))] + [HarmonyPatch("Start")] + class ItemDropshipStartPatch { + public static void Postfix(ItemDropship __instance) { + Plugin.Instance.PluginLogger.LogInfo("Item dropship spawned! Modifying kill trigger..."); + + GameObject itemDropshipGameObject = __instance.gameObject; + if (itemDropshipGameObject == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch GameObject from ItemDropship."); + } + Transform killTriggerTransform = itemDropshipGameObject.transform.Find("ItemShip/KillTrigger"); + + if (killTriggerTransform == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch KillTrigger Transform from ItemDropship."); + } + + GameObject killTriggerGameObject = killTriggerTransform.gameObject; + + if (killTriggerGameObject == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch KillTrigger GameObject from ItemDropship."); + } + + KillLocalPlayer killLocalPlayer = killTriggerGameObject.GetComponent(); + + if (killLocalPlayer == null) { + Plugin.Instance.PluginLogger.LogError("Could not fetch KillLocalPlayer from KillTrigger GameObject."); + } + + // Modify the cause of death in a janky way. + killLocalPlayer.causeOfDeath = (CauseOfDeath) AdvancedDeathTracker.PLAYER_CAUSE_OF_DEATH_DROPSHIP; + } + } } \ No newline at end of file diff --git a/Coroner/Patch/HUDManagerPatch.cs b/Coroner/Patch/HUDManagerPatch.cs index 0d06f75..e518577 100644 --- a/Coroner/Patch/HUDManagerPatch.cs +++ b/Coroner/Patch/HUDManagerPatch.cs @@ -18,20 +18,35 @@ class HUDManagerFillEndGameStatsPatch { "* The bravest employee.\n", "* Did a sick flip.\n", "* Stubbed their toe.\n", - "* The most likely to succeed.\n", - "* The least likely to succeed.\n", - "* The most likely to die.\n", - "* The least likely to die.\n", + "* The most likely to die next time.\n", + "* The least likely to die next time.\n", + "* Dislikes smoke.\n", "* A team player.\n", "* A real go-getter.\n", + "* Ate the most snacks.\n", "* Passed GO and collected $200.\n", - "* Getting freaky on a Friday night.\n", + "* Got freaky on a Friday night.\n", "* I think this one's a serial killer.\n", + "* Perfectly unremarkable.\n", + "* Hasn't called their mother in a while.\n", + "* Has IP address 127.0.0.1.\n", + "* Secretly a lizard.\n" }; static readonly Random RANDOM = new Random(); public static void Postfix(HUDManager __instance) { + try { + OverridePerformanceReport(__instance); + } catch (Exception e) { + Plugin.Instance.PluginLogger.LogError("Coroner threw an exception while Caught an exception overriding performance report: "); + // Display the error and the stack trace. + Plugin.Instance.PluginLogger.LogError(e.ToString()); + Plugin.Instance.PluginLogger.LogError(e.StackTrace.ToString()); + } + } + + static void OverridePerformanceReport(HUDManager __instance) { Plugin.Instance.PluginLogger.LogInfo("Applying Coroner patches to player notes..."); for (int playerIndex = 0; playerIndex < __instance.statsUIElements.playerNotesText.Length; playerIndex++) { @@ -42,27 +57,30 @@ public static void Postfix(HUDManager __instance) { } TextMeshProUGUI textMesh = __instance.statsUIElements.playerNotesText[playerIndex]; - if (__instance.playersManager.allPlayerScripts[playerIndex].isPlayerDead) { + if (playerController.isPlayerDead) { if (Plugin.Instance.PluginConfig.ShouldDisplayCauseOfDeath()) { var causeOfDeath = AdvancedDeathTracker.GetCauseOfDeath(playerController); var causeOfDeathStr = AdvancedDeathTracker.StringifyCauseOfDeath(causeOfDeath); if (Plugin.Instance.PluginConfig.ShouldDeathReplaceNotes()) { Plugin.Instance.PluginLogger.LogInfo("Player " + playerIndex + " is dead! Replacing notes with Cause of Death..."); - textMesh.text = "* " + causeOfDeathStr + "\n"; + // Reset the notes. + textMesh.text = "Notes: \n"; } else { Plugin.Instance.PluginLogger.LogInfo("Player " + playerIndex + " is dead! Appending notes with Cause of Death..."); - textMesh.text += "* " + causeOfDeathStr + "\n"; } + textMesh.text += "* " + causeOfDeathStr + "\n"; } else { Plugin.Instance.PluginLogger.LogInfo("Player " + playerIndex + " is dead, but Config says leave it be..."); } } else { Plugin.Instance.PluginLogger.LogInfo("Player " + playerIndex + " is not dead!"); - if (textMesh.text == "") { + if (textMesh.text == "Notes: \n") { if (Plugin.Instance.PluginConfig.ShouldDisplayFunnyNotes()) { Plugin.Instance.PluginLogger.LogInfo("Player " + playerIndex + " has no notes! Injecting something funny..."); - textMesh.text = ChooseFunnyNote(); + // Reset the notes. + textMesh.text = "Notes: \n"; + textMesh.text += ChooseFunnyNote(); } else { Plugin.Instance.PluginLogger.LogInfo("Player " + playerIndex + " has no notes, but Config says leave it be..."); } diff --git a/Coroner/Plugin.cs b/Coroner/Plugin.cs index b5f447f..bb9d38b 100644 --- a/Coroner/Plugin.cs +++ b/Coroner/Plugin.cs @@ -2,6 +2,7 @@ using HarmonyLib; using BepInEx.Logging; +using BepInEx.Bootstrap; namespace Coroner { @@ -14,6 +15,7 @@ public static class PluginInfo } [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + [BepInDependency("LC_API", BepInDependency.DependencyFlags.SoftDependency)] public class Plugin : BaseUnityPlugin { public static Plugin Instance { get; private set; } @@ -21,11 +23,14 @@ public class Plugin : BaseUnityPlugin public ManualLogSource PluginLogger; public PluginConfig PluginConfig; + + public bool IsLC_APIPresent = false; private void Awake() { Instance = this; + PluginLogger = Logger; // Apply Harmony patches (if any exist) @@ -36,6 +41,33 @@ private void Awake() PluginLogger.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} ({PluginInfo.PLUGIN_GUID}) is loaded!"); LoadConfig(); + QueryLC_API(); + DeathBroadcaster.Initialize(); + } + + private void QueryLC_API() + { + PluginLogger.LogInfo("Checking for LC_API..."); + if (Chainloader.PluginInfos.ContainsKey("LC_API")) + { + BepInEx.PluginInfo pluginInfo; + Chainloader.PluginInfos.TryGetValue("LC_API", out pluginInfo); + + if (pluginInfo == null) + { + PluginLogger.LogError("Detected LC_API, but could not get plugin info!"); + IsLC_APIPresent = false; + return; + } + + PluginLogger.LogInfo("LCAPI is present! " + pluginInfo.Metadata.GUID + ":" + pluginInfo.Metadata.Version); + IsLC_APIPresent = true; + } + else + { + PluginLogger.LogInfo("LCAPI is not present."); + IsLC_APIPresent = false; + } } private void LoadConfig() diff --git a/README.md b/README.md index 3214421..3d98823 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,19 @@ A plugin which overhauls the end-of-mission Performance Report with new information, including cause of death for any deceased players. There are some fun easter eggs thrown in too. +**NOTE:** [LC_API](https://thunderstore.io/c/lethal-company/p/2018/LC_API/) is an optional but highly recommended dependency! + ## Demonstration ![](https://raw.githubusercontent.com/EliteMasterEric/Coroner/master/Art/StungByBees.png) ## Notice -This should support MoreCompany's overhauled results screen! If you have any issues please report them on Discord. +- If [LC_API](https://thunderstore.io/c/lethal-company/p/2018/LC_API/) is installed, death reports will be more precise between players. +- This should support MoreCompany's overhauled results screen but it's currently not thoroughly tested. ## Upcoming -- More detailed information on cause of death. +- More detailed information on specific causes of death. - Additional bug fixes. ## Credits - EliteMasterEric: Programming +- FoguDragon: Playtesting diff --git a/Releases/build.bat b/Releases/build.bat index 579048a..a87d70b 100644 --- a/Releases/build.bat +++ b/Releases/build.bat @@ -4,6 +4,8 @@ REM Copy ../Art/manifest.json to the current directory copy /y ..\Art\manifest.json . REM Copy ../README.md to the current directory copy /y ..\README.md . +REM Copy ../CHANGELOG.md to the current directory +copy /y ..\CHANGELOG.md . REM Copy all files from ../Coroner/build/bin/Debug to the current directory xcopy /s /y /q ..\Coroner\build\bin\Debug\* .