diff --git a/README.md b/README.md index a14e50b9b..279bfd0a1 100644 --- a/README.md +++ b/README.md @@ -37,22 +37,24 @@ Even more roles are coming soon. :) | | [Trapper](#trapper) | | | | | [Nice Guesser](#guesser) | | | -The [Role Assignment](#role-assignment) sections explains how the roles are being distributed among the players. +The [Role Assignment](#role-assignment) section explains how the roles are being distributed among the players. # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2023.11.28s| v4.5.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.0/TheOtherRoles.zip) | 2023.07.12s| v4.4.2| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.2/TheOtherRoles.zip) -| 2023.07.12s| v4.4.1| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.1/TheOtherRoles.zip) -| 2023.07.12s| v4.4.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.0/TheOtherRoles.zip) -| 2023.07.12s| v4.3.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.4/TheOtherRoles.zip) -| 2023.07.12s| v4.3.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.3/TheOtherRoles.zip) -| 2023.03.28s| v4.3.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.2/TheOtherRoles.zip) +
Click to show older versions | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2023.07.12s| v4.4.1| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.1/TheOtherRoles.zip) +| 2023.07.12s| v4.4.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.0/TheOtherRoles.zip) +| 2023.07.12s| v4.3.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.4/TheOtherRoles.zip) +| 2023.07.12s| v4.3.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.3/TheOtherRoles.zip) +| 2023.03.28s| v4.3.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.2/TheOtherRoles.zip) | 2023.03.28s| v4.3.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.1/TheOtherRoles.zip) | 2023.02.28s| v4.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.0/TheOtherRoles.zip) | 2022.12.14s| v4.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.2.1/TheOtherRoles.zip) @@ -128,6 +130,18 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 4.5.0** +- Updated to Among Us version 2023.11.28 (with the new map, Fungle) +- Added a new feature: Show Vents on Map (toggle in your local options) +- Added a new kill distance: Very Short (thanks twix) +- Added a feature to change the gamemode in the settings: `/gm ` (host only) +- Fixed a bug where Pet visibility was sometimes wron +- Fixed the HatLoader - testing hats should work again (thanks EnoPM) +- Changed the HatLoader - loading the hats does not require a restart of the mod anymore +- Changed the ModUpdater (thanks EnoPM) +- Changed the Vampire: Kills will be delayed a bit longer if the target is using a ladder, platform or zipline +- Note: Compatibility with Submerged and LevelImposter is not tested yet. + **Version 4.4.2** - Added 10 new colors and adapted some existing ones (thanks to Avlona & Listoric for sorting our colors!) - Added a message of the day feature in the main menu. Look out for interesting news and lame jokes! @@ -791,6 +805,26 @@ docker run -d -p 22023:22023/udp --env IMPOSTOR_AntiCheatEnabled=false --env IMP # Credits & Resources +Team: +Mallöris K3ndo Bavari Gendelo + +Former Team Members: +Eisbison (GOAT) Thunderstorm584 EndOfFile + +Additional Devs: +EnoPM twix NesTT + +Github Contributors: +Alex2911 amsyarasyiq MaximeGillot +Psynomit probablyadnf JustASysAdmin + +[Discord](https://discord.gg/77RkMJHWsM]Discord) Moderators: +Draco Cordraconis Streamblox (formerly) +Thanks to all our discord helpers! + +Thanks to miniduikboot & GD for hosting modded servers (and so much more) + + [OxygenFilter](https://github.com/NuclearPowered/Reactor.OxygenFilter) - For all the versions between v2.3.0 and v2.6.1, we were using the OxygenFilter for automatic deobfuscation\ [Reactor](https://github.com/NuclearPowered/Reactor) - The framework used for all versions before v2.0.0\ [BepInEx](https://github.com/BepInEx) - Used to hook to game functions\ @@ -2030,6 +2064,9 @@ can only use them, if the previous player did not use them before) ----------------------- # Gamemodes +Gamemodes can be switched when creating a lobby or inside the lobby by using a command in the chat: +`/gm `. Use the following gamemodes: `guess` or `gm`,`prophunt` or `ph`, `hidenseek` or `hns`. If `/gm` is used without argument or the argument can't be parsed, the lobby will switch to classic mode. + ## Guesser Modifier The **Guesser-Gamemode** is an extension to the Classic-Gamemode and gives you a multitude of new options for Guessers.\ diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index f733166bd..c9e3b8544 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -9,6 +9,7 @@ using TheOtherRoles.Players; using TheOtherRoles.Utilities; using TheOtherRoles.CustomGameModes; +using TheOtherRoles.Patches; namespace TheOtherRoles { @@ -549,7 +550,7 @@ public static void createButtonsPostfix(HudManager __instance) { } }, () => { return Morphling.morphling != null && Morphling.morphling == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; }, - () => { return (Morphling.currentTarget || Morphling.sampledTarget) && CachedPlayer.LocalPlayer.PlayerControl.CanMove; }, + () => { return (Morphling.currentTarget || Morphling.sampledTarget) && CachedPlayer.LocalPlayer.PlayerControl.CanMove && !Helpers.MushroomSabotageActive(); }, () => { morphlingButton.Timer = morphlingButton.MaxTimer; morphlingButton.Sprite = Morphling.getSampleSprite(); @@ -695,11 +696,11 @@ public static void createButtonsPostfix(HudManager __instance) { Hacker.chargesVitals--; }, - () => { return Hacker.hacker != null && Hacker.hacker == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead && GameOptionsManager.Instance.currentNormalGameOptions.MapId != 0 && GameOptionsManager.Instance.currentNormalGameOptions.MapId != 3; }, + () => { return Hacker.hacker != null && Hacker.hacker == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead && GameOptionsManager.Instance.currentGameOptions.MapId != 0 && GameOptionsManager.Instance.currentNormalGameOptions.MapId != 3; }, () => { if (hackerVitalsChargesText != null) hackerVitalsChargesText.text = $"{Hacker.chargesVitals} / {Hacker.toolsNumber}"; - hackerVitalsButton.actionButton.graphic.sprite = GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? Hacker.getLogSprite() : Hacker.getVitalsSprite(); - hackerVitalsButton.actionButton.OverrideText(GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? "DOORLOG" : "VITALS"); + hackerVitalsButton.actionButton.graphic.sprite = Helpers.isMira() ? Hacker.getLogSprite() : Hacker.getVitalsSprite(); + hackerVitalsButton.actionButton.OverrideText(Helpers.isMira() ? "DOORLOG" : "VITALS"); return Hacker.chargesVitals > 0; }, () => { @@ -713,16 +714,16 @@ public static void createButtonsPostfix(HudManager __instance) { KeyCode.Q, true, 0f, - () => { + () => { hackerVitalsButton.Timer = hackerVitalsButton.MaxTimer; - if(!hackerAdminTableButton.isEffectActive) CachedPlayer.LocalPlayer.PlayerControl.moveable = true; + if (!hackerAdminTableButton.isEffectActive) CachedPlayer.LocalPlayer.PlayerControl.moveable = true; if (Minigame.Instance) { - if (GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1) Hacker.doorLog.ForceClose(); + if (Helpers.isMira()) Hacker.doorLog.ForceClose(); else Hacker.vitals.ForceClose(); } }, false, - GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? "DOORLOG" : "VITALS" + Helpers.isMira() ? "DOORLOG" : "VITALS" ); // Hacker Vitals Charges @@ -809,12 +810,14 @@ public static void createButtonsPostfix(HudManager __instance) { } if (p == 1f) { // Perform kill if possible and reset bitten (regardless whether the kill was successful or not) - Helpers.checkMurderAttemptAndKill(Vampire.vampire, Vampire.bitten, showAnimation: false); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); - writer.Write(byte.MaxValue); - writer.Write(byte.MaxValue); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); + var res = Helpers.checkMurderAttemptAndKill(Vampire.vampire, Vampire.bitten, showAnimation: false); + if (res == MurderAttemptResult.PerformKill) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); + writer.Write(byte.MaxValue); + writer.Write(byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); + } } }))); SoundEffectsManager.play("vampireBite"); @@ -1114,7 +1117,8 @@ public static void createButtonsPostfix(HudManager __instance) { RPCProcedure.lightsOut(); SoundEffectsManager.play("lighterLight"); }, - () => { return Trickster.trickster != null && Trickster.trickster == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; }, + () => { return Trickster.trickster != null && Trickster.trickster == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead + && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; }, () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; }, () => { lightsOutButton.Timer = lightsOutButton.MaxTimer; @@ -1241,7 +1245,7 @@ public static void createButtonsPostfix(HudManager __instance) { RPCProcedure.sealVent(SecurityGuard.ventTarget.Id); SecurityGuard.ventTarget = null; - } else if (GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1 && !SubmergedCompatibility.IsSubmerged) { // Place camera if there's no vent and it's not MiraHQ or Submerged + } else if (!Helpers.isMira() && !Helpers.isFungle() && !SubmergedCompatibility.IsSubmerged) { // Place camera if there's no vent and it's not MiraHQ or Submerged var pos = CachedPlayer.LocalPlayer.transform.position; byte[] buff = new byte[sizeof(float) * 2]; Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float)); @@ -1257,12 +1261,12 @@ public static void createButtonsPostfix(HudManager __instance) { }, () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews >= Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); }, () => { - securityGuardButton.actionButton.graphic.sprite = (SecurityGuard.ventTarget == null && GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1 && !SubmergedCompatibility.IsSubmerged) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); + securityGuardButton.actionButton.graphic.sprite = (SecurityGuard.ventTarget == null && !Helpers.isMira() && !Helpers.isFungle() && !SubmergedCompatibility.IsSubmerged) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}"; if (SecurityGuard.ventTarget != null) return SecurityGuard.remainingScrews >= SecurityGuard.ventPrice && CachedPlayer.LocalPlayer.PlayerControl.CanMove; - return GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1 && !SubmergedCompatibility.IsSubmerged && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && CachedPlayer.LocalPlayer.PlayerControl.CanMove; + return !Helpers.isMira() && !Helpers.isFungle() && !SubmergedCompatibility.IsSubmerged && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && CachedPlayer.LocalPlayer.PlayerControl.CanMove; }, () => { securityGuardButton.Timer = securityGuardButton.MaxTimer; }, SecurityGuard.getPlaceCameraButtonSprite(), @@ -1280,12 +1284,12 @@ public static void createButtonsPostfix(HudManager __instance) { securityGuardCamButton = new CustomButton( () => { - if (GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1) { + if (!Helpers.isMira()) { if (SecurityGuard.minigame == null) { byte mapId = GameOptionsManager.Instance.currentNormalGameOptions.MapId; - var e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel") || x.name.Contains("Cam")); - if (mapId == 0 || mapId == 3) e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("SurvConsole")); - else if (mapId == 4) e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("task_cams")); + var e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel") || x.name.Contains("Cam") || x.name.Contains("BinocularsSecurityConsole")); + if (Helpers.isSkeld() || mapId == 3) e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("SurvConsole")); + else if (Helpers.isAirship()) e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("task_cams")); if (e == null || Camera.main == null) return; SecurityGuard.minigame = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false); } @@ -1307,12 +1311,14 @@ public static void createButtonsPostfix(HudManager __instance) { if (SecurityGuard.cantMove) CachedPlayer.LocalPlayer.PlayerControl.moveable = false; CachedPlayer.LocalPlayer.NetTransform.Halt(); // Stop current movement }, - () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice) - && !SubmergedCompatibility.IsSubmerged; }, + () => { + return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice) + && !SubmergedCompatibility.IsSubmerged; + }, () => { if (securityGuardChargesText != null) securityGuardChargesText.text = $"{SecurityGuard.charges} / {SecurityGuard.maxCharges}"; - securityGuardCamButton.actionButton.graphic.sprite = GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? SecurityGuard.getLogSprite() : SecurityGuard.getCamSprite(); - securityGuardCamButton.actionButton.OverrideText(GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? "DOORLOG" : "SECURITY"); + securityGuardCamButton.actionButton.graphic.sprite = Helpers.isMira() ? SecurityGuard.getLogSprite() : SecurityGuard.getCamSprite(); + securityGuardCamButton.actionButton.OverrideText(Helpers.isMira() ? "DOORLOG" : "SECURITY"); return CachedPlayer.LocalPlayer.PlayerControl.CanMove && SecurityGuard.charges > 0; }, () => { @@ -1334,7 +1340,7 @@ public static void createButtonsPostfix(HudManager __instance) { CachedPlayer.LocalPlayer.PlayerControl.moveable = true; }, false, - GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? "DOORLOG" : "SECURITY" + Helpers.isMira() ? "DOORLOG" : "SECURITY" ); // Security Guard cam button charges @@ -1683,7 +1689,7 @@ public static void createButtonsPostfix(HudManager __instance) { () => { return Ninja.ninja != null && Ninja.ninja == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; }, () => { // CouldUse ninjaButton.Sprite = Ninja.ninjaMarked != null ? Ninja.getKillButtonSprite() : Ninja.getMarkButtonSprite(); - return (Ninja.currentTarget != null || Ninja.ninjaMarked != null) && CachedPlayer.LocalPlayer.PlayerControl.CanMove; + return (Ninja.currentTarget != null || Ninja.ninjaMarked != null && !TransportationToolPatches.isUsingTransportation(Ninja.ninjaMarked)) && CachedPlayer.LocalPlayer.PlayerControl.CanMove; }, () => { // on meeting ends ninjaButton.Timer = ninjaButton.MaxTimer; @@ -2047,7 +2053,6 @@ public static void createButtonsPostfix(HudManager __instance) { // Prop stuff var player = PlayerControl.LocalPlayer; GameObject disguiseTarget = PropHunt.currentTarget; - // PropHunt.FindClosestDisguiseObject(player.gameObject, 1, true); if (disguiseTarget != null) { player.transform.localScale = disguiseTarget.transform.lossyScale; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetProp, Hazel.SendOption.Reliable, -1); @@ -2063,6 +2068,7 @@ public static void createButtonsPostfix(HudManager __instance) { () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && !PlayerControl.LocalPlayer.Data.IsDead; }, () => { propSpriteRenderer.sprite = PropHunt.currentTarget?.GetComponent()?.sprite; + if (propSpriteRenderer.sprite == null) propSpriteRenderer.sprite = PropHunt.currentTarget?.transform.GetComponentInChildren()?.sprite; if (propSpriteRenderer.sprite != null) propSpriteHolder.transform.localScale *= 1 / propSpriteRenderer.bounds.size.magnitude; return PropHunt.currentTarget != null && PropHunt.currentTarget?.GetComponent()?.sprite != null; diff --git a/TheOtherRoles/CustomGameModes/PropHunt.cs b/TheOtherRoles/CustomGameModes/PropHunt.cs index bdbd39d47..caa7754a7 100644 --- a/TheOtherRoles/CustomGameModes/PropHunt.cs +++ b/TheOtherRoles/CustomGameModes/PropHunt.cs @@ -122,6 +122,21 @@ public static Sprite getIntroSprite(int index) { return Helpers.loadSpriteFromResources($"TheOtherRoles.Resources.IntroAnimation.intro_{index + 1000}.png", 150f, cache: false); } + public static void updateWhitelistedObjects() { + TheOtherRolesPlugin.Logger.LogMessage($"updating whitelisted objects!"); + string allNames = Helpers.readTextFromResources("TheOtherRoles.Resources.Txt.props.txt"); + TheOtherRolesPlugin.Logger.LogMessage($"raed from res"); + bool debug = false; + if (debug) { + allNames = Helpers.readTextFromFile(System.IO.Directory.GetCurrentDirectory() + "\\props.txt"); + } + TheOtherRolesPlugin.Logger.LogMessage($"after debug"); + whitelistedObjects = allNames.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).ToList(); + TheOtherRolesPlugin.Logger.LogMessage($"after split"); + + TheOtherRolesPlugin.Logger.LogMessage($"Last element: {whitelistedObjects.Last()}"); + } + public static void propTargetAndTimerDisplayUpdate() { @@ -344,9 +359,8 @@ public static GameObject FindClosestDisguiseObject(GameObject origin, float radi try { Collider2D bestCollider = null; float bestDist = 9999; - if (whitelistedObjects == null || whitelistedObjects.Count == 0) { - string allNames = Helpers.readTextFromResources("TheOtherRoles.Resources.Txt.Props.txt"); - whitelistedObjects = allNames.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).ToList(); + if (whitelistedObjects == null || whitelistedObjects.Count == 0 || verbose) { + updateWhitelistedObjects(); } foreach (Collider2D collider in Physics2D.OverlapCircleAll(origin.transform.position, radius)) { if (verbose) { @@ -444,7 +458,7 @@ public static void PlayerControlFixedUpdatePatch(PlayerControl __instance) { __instance.gameObject.AddComponent(); __instance.GetComponent().radius = 0.00001f; - } + } // Runs periodically, resets animation data for players diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 2d78be4a9..876f718c1 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -315,6 +315,7 @@ public class CustomOptionHolder { public static CustomOption dynamicMapEnableMira; public static CustomOption dynamicMapEnablePolus; public static CustomOption dynamicMapEnableAirShip; + public static CustomOption dynamicMapEnableFungle; public static CustomOption dynamicMapEnableSubmerged; public static CustomOption dynamicMapSeparateSettings; @@ -631,7 +632,7 @@ public static void Load() { securityGuardCamPrice = CustomOption.Create(283, Types.Crewmate, "Number Of Screws Per Cam", 2f, 1f, 15f, 1f, securityGuardSpawnRate); securityGuardVentPrice = CustomOption.Create(284, Types.Crewmate, "Number Of Screws Per Vent", 1f, 1f, 15f, 1f, securityGuardSpawnRate); securityGuardCamDuration = CustomOption.Create(285, Types.Crewmate, "Security Guard Duration", 10f, 2.5f, 60f, 2.5f, securityGuardSpawnRate); - securityGuardCamMaxCharges = CustomOption.Create(286, Types.Crewmate, "Gadged Max Charges", 5f, 1f, 30f, 1f, securityGuardSpawnRate); + securityGuardCamMaxCharges = CustomOption.Create(286, Types.Crewmate, "Gadget Max Charges", 5f, 1f, 30f, 1f, securityGuardSpawnRate); securityGuardCamRechargeTasksNumber = CustomOption.Create(287, Types.Crewmate, "Number Of Tasks Needed For Recharging", 3f, 1f, 10f, 1f, securityGuardSpawnRate); securityGuardNoMove = CustomOption.Create(288, Types.Crewmate, "Cant Move During Cam Duration", true, securityGuardSpawnRate); @@ -718,7 +719,7 @@ public static void Load() { guesserGamemodeCantGuessSnitchIfTaksDone = CustomOption.Create(2010, Types.Guesser, "Guesser Can't Guess Snitch When Tasks Completed", true, null); // Hide N Seek Gamemode (3000 - 3999) - hideNSeekMap = CustomOption.Create(3020, Types.HideNSeekMain, cs(Color.yellow, "Map"), new string[] { "The Skeld", "Mira", "Polus", "Airship", "Submerged", "LI Map"}, null, true, onChange: () => { int map = hideNSeekMap.selection; if (map >= 3) map++; GameOptionsManager.Instance.currentNormalGameOptions.MapId = (byte)map; }); + hideNSeekMap = CustomOption.Create(3020, Types.HideNSeekMain, cs(Color.yellow, "Map"), new string[] { "The Skeld", "Mira", "Polus", "Airship", "Fungle", "Submerged", "LI Map"}, null, true, onChange: () => { int map = hideNSeekMap.selection; if (map >= 3) map++; GameOptionsManager.Instance.currentNormalGameOptions.MapId = (byte)map; }); hideNSeekHunterCount = CustomOption.Create(3000, Types.HideNSeekMain, cs(Color.yellow, "Number Of Hunters"), 1f, 1f, 3f, 1f); hideNSeekKillCooldown = CustomOption.Create(3021, Types.HideNSeekMain, cs(Color.yellow, "Kill Cooldown"), 10f, 2.5f, 60f, 2.5f); hideNSeekHunterVision = CustomOption.Create(3001, Types.HideNSeekMain, cs(Color.yellow, "Hunter Vision"), 0.5f, 0.25f, 2f, 0.25f); @@ -749,7 +750,7 @@ public static void Load() { huntedShieldNumber = CustomOption.Create(3026, Types.HideNSeekRoles, cs(Color.gray, "Hunted Shield Number"), 3f, 1f, 15f, 1f); // Prop Hunt General Options - propHuntMap = CustomOption.Create(4020, Types.PropHunt, cs(Color.yellow, "Map"), new string[] { "The Skeld", "Mira", "Polus", "Airship", "Submerged", "LI Map"}, null, true, onChange: ()=> { int map = propHuntMap.selection; if (map >= 3) map++; GameOptionsManager.Instance.currentNormalGameOptions.MapId = (byte)map;}); + propHuntMap = CustomOption.Create(4020, Types.PropHunt, cs(Color.yellow, "Map"), new string[] { "The Skeld", "Mira", "Polus", "Airship", "Fungle", "Submerged", "LI Map"}, null, true, onChange: ()=> { int map = propHuntMap.selection; if (map >= 3) map++; GameOptionsManager.Instance.currentNormalGameOptions.MapId = (byte)map;}); propHuntTimer = CustomOption.Create(4021, Types.PropHunt, cs(Color.yellow, "Timer In Min"), 5f, 1f, 30f, 0.5f); propHuntUnstuckCooldown = CustomOption.Create(4011, Types.PropHunt, cs(Color.yellow, "Unstuck Cooldown"), 30f, 2.5f, 60f, 2.5f); propHuntUnstuckDuration = CustomOption.Create(4012, Types.PropHunt, cs(Color.yellow, "Unstuck Duration"), 2f, 1f, 60f, 1f); @@ -795,6 +796,7 @@ public static void Load() { dynamicMapEnableMira = CustomOption.Create(502, Types.General, "Mira", rates, dynamicMap, false); dynamicMapEnablePolus = CustomOption.Create(503, Types.General, "Polus", rates, dynamicMap, false); dynamicMapEnableAirShip = CustomOption.Create(504, Types.General, "Airship", rates, dynamicMap, false); + dynamicMapEnableFungle = CustomOption.Create(506, Types.General, "Fungle", rates, dynamicMap, false); dynamicMapEnableSubmerged = CustomOption.Create(505, Types.General, "Submerged", rates, dynamicMap, false); dynamicMapSeparateSettings = CustomOption.Create(509, Types.General, "Use Random Map Setting Presets", false, dynamicMap, false); diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index f333bf3db..d9dc79cb9 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Reflection; using UnityEngine; @@ -11,11 +12,10 @@ using TheOtherRoles.Players; using TheOtherRoles.Utilities; using System.Threading.Tasks; -using System.Net; using TheOtherRoles.CustomGameModes; using Reactor.Utilities.Extensions; using AmongUs.GameOptions; -using Innersloth.Assets; +using TheOtherRoles.Patches; namespace TheOtherRoles { @@ -23,6 +23,7 @@ public enum MurderAttemptResult { PerformKill, SuppressKill, BlankKill, + DelayVampireKill } public enum CustomGamemodes { @@ -85,7 +86,7 @@ public static Texture2D loadTextureFromDisk(string path) { } public static AudioClip loadAudioClipFromResources(string path, string clipName = "UNNAMED_TOR_AUDIO_CLIP") { - // must be "raw (headerless) 2-channel signed 32 bit pcm (le)" (can e.g. use Audacity® to export) + // must be "raw (headerless) 2-channel signed 32 bit pcm (le)" (can e.g. use Audacity® to export) try { Assembly assembly = Assembly.GetExecutingAssembly(); Stream stream = assembly.GetManifestResourceStream(path); @@ -121,6 +122,12 @@ public static string readTextFromResources(string path) { return textStreamReader.ReadToEnd(); } + public static string readTextFromFile(string path) { + Stream stream = File.OpenRead(path); + StreamReader textStreamReader = new StreamReader(stream); + return textStreamReader.ReadToEnd(); + } + public static PlayerControl playerById(byte id) { foreach (PlayerControl player in CachedPlayer.AllPlayers) @@ -238,6 +245,38 @@ public static void clearAllTasks(this PlayerControl player) { player.Data.Tasks.Clear(); } + public static void MurderPlayer(this PlayerControl player, PlayerControl target) + { + player.MurderPlayer(target, MurderResultFlags.Succeeded); + } + + public static void RpcRepairSystem(this ShipStatus shipStatus, SystemTypes systemType, byte amount) + { + shipStatus.RpcUpdateSystem(systemType, amount); + } + + public static bool isMira() { + return GameOptionsManager.Instance.CurrentGameOptions.MapId == 1; + } + + public static bool isAirship() { + return GameOptionsManager.Instance.CurrentGameOptions.MapId == 4; + } + public static bool isSkeld() { + return GameOptionsManager.Instance.CurrentGameOptions.MapId == 0; + } + public static bool isPolus() { + return GameOptionsManager.Instance.CurrentGameOptions.MapId == 2; + } + + public static bool isFungle() { + return GameOptionsManager.Instance.CurrentGameOptions.MapId == 5; + } + + public static bool MushroomSabotageActive() { + return CachedPlayer.LocalPlayer.PlayerControl.myTasks.ToArray().Any((x) => x.TaskType == TaskTypes.MushroomMixupSabotage); + } + public static void setSemiTransparent(this PoolablePlayer player, bool value, float alpha=0.25f) { alpha = value ? alpha : 1f; foreach (SpriteRenderer r in player.gameObject.GetComponentsInChildren()) @@ -281,7 +320,7 @@ public static KeyValuePair MaxPair(this Dictionary self, o } public static bool hidePlayerName(PlayerControl source, PlayerControl target) { - if (Camouflager.camouflageTimer > 0f) return true; // No names are visible + if (Camouflager.camouflageTimer > 0f || Helpers.MushroomSabotageActive()) return true; // No names are visible if (Patches.SurveillanceMinigamePatch.nightVisionIsActive) return true; else if (Ninja.isInvisble && Ninja.ninja == target) return true; else if (!TORMapOptions.hidePlayerNames) return false; // All names are visible @@ -295,7 +334,13 @@ public static bool hidePlayerName(PlayerControl source, PlayerControl target) { } public static void setDefaultLook(this PlayerControl target, bool enforceNightVisionUpdate = true) { - target.setLook(target.Data.PlayerName, target.Data.DefaultOutfit.ColorId, target.Data.DefaultOutfit.HatId, target.Data.DefaultOutfit.VisorId, target.Data.DefaultOutfit.SkinId, target.Data.DefaultOutfit.PetId, enforceNightVisionUpdate); + if (Helpers.MushroomSabotageActive()) { + var instance = ShipStatus.Instance.CastFast().specialSabotage; + MushroomMixupSabotageSystem.CondensedOutfit condensedOutfit = instance.currentMixups[target.PlayerId]; + GameData.PlayerOutfit playerOutfit = instance.ConvertToPlayerOutfit(condensedOutfit); + target.MixUpOutfit(playerOutfit); + } else + target.setLook(target.Data.PlayerName, target.Data.DefaultOutfit.ColorId, target.Data.DefaultOutfit.HatId, target.Data.DefaultOutfit.VisorId, target.Data.DefaultOutfit.SkinId, target.Data.DefaultOutfit.PetId, enforceNightVisionUpdate); } public static void setLook(this PlayerControl target, String playerName, int colorId, string hatId, string visorId, string skinId, string petId, bool enforceNightVisionUpdate = true) { @@ -447,22 +492,40 @@ public static MurderAttemptResult checkMuderAttempt(PlayerControl killer, Player return MurderAttemptResult.SuppressKill; } - + if (TransportationToolPatches.isUsingTransportation(target) && !blockRewind && killer == Vampire.vampire) { + return MurderAttemptResult.DelayVampireKill; + } else if (TransportationToolPatches.isUsingTransportation(target)) + return MurderAttemptResult.SuppressKill; return MurderAttemptResult.PerformKill; } + public static void MurderPlayer(PlayerControl killer, PlayerControl target, bool showAnimation) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); + writer.Write(killer.PlayerId); + writer.Write(target.PlayerId); + writer.Write(showAnimation ? Byte.MaxValue : 0); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.uncheckedMurderPlayer(killer.PlayerId, target.PlayerId, showAnimation ? Byte.MaxValue : (byte)0); + } + public static MurderAttemptResult checkMurderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false, bool showAnimation = true, bool ignoreBlank = false, bool ignoreIfKillerIsDead = false) { // The local player checks for the validity of the kill and performs it afterwards (different to vanilla, where the host performs all the checks) // The kill attempt will be shared using a custom RPC, hence combining modded and unmodded versions is impossible MurderAttemptResult murder = checkMuderAttempt(killer, target, isMeetingStart, ignoreBlank, ignoreIfKillerIsDead); if (murder == MurderAttemptResult.PerformKill) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); - writer.Write(killer.PlayerId); - writer.Write(target.PlayerId); - writer.Write(showAnimation ? Byte.MaxValue : 0); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.uncheckedMurderPlayer(killer.PlayerId, target.PlayerId, showAnimation ? Byte.MaxValue : (byte)0); + MurderPlayer(killer, target, showAnimation); + } else if (murder == MurderAttemptResult.DelayVampireKill) { + HudManager.Instance.StartCoroutine(Effects.Lerp(10f, new Action((p) => { + if (!TransportationToolPatches.isUsingTransportation(target) && Vampire.bitten != null) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); + writer.Write(byte.MaxValue); + writer.Write(byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); + MurderPlayer(killer, target, showAnimation); + } + }))); } return murder; } @@ -530,10 +593,24 @@ public static void toggleZoom(bool reset=false) { ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); // This will move button positions to the correct position. } + private static long GetBuiltInTicks() + { + var assembly = Assembly.GetExecutingAssembly(); + var builtin = assembly.GetType("Builtin"); + if (builtin == null) return 0; + var field = builtin.GetField("CompileTime"); + if (field == null) return 0; + var value = field.GetValue(null); + if (value == null) return 0; + return (long)value; + } + public static async Task checkBeta() { if (TheOtherRolesPlugin.betaDays > 0) { TheOtherRolesPlugin.Logger.LogMessage($"Beta check"); - var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc); // This may show as an error, but it is not, compilation will work! + var ticks = GetBuiltInTicks(); + var compileTime = new DateTime(ticks, DateTimeKind.Utc); // This may show as an error, but it is not, compilation will work! + TheOtherRolesPlugin.Logger.LogMessage($"Compiled at {compileTime.ToString(CultureInfo.InvariantCulture)}"); DateTime? now; // Get time from the internet, so no-one can cheat it (so easily). try { diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index 11a1b2f9e..b6e7178db 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -16,11 +16,12 @@ using TheOtherRoles.Modules; using TheOtherRoles.Players; using TheOtherRoles.Utilities; -using Reactor; using Il2CppSystem.Security.Cryptography; using Il2CppSystem.Text; using Reactor.Networking.Attributes; using AmongUs.Data; +using TheOtherRoles.Modules.CustomHats; +using static TheOtherRoles.Modules.ModUpdater; namespace TheOtherRoles { @@ -28,10 +29,11 @@ namespace TheOtherRoles [BepInDependency(SubmergedCompatibility.SUBMERGED_GUID, BepInDependency.DependencyFlags.SoftDependency)] [BepInProcess("Among Us.exe")] [ReactorModFlags(Reactor.Networking.ModFlags.RequireOnAllClients)] + public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "4.4.2"; + public const string VersionString = "4.5.0"; public static uint betaDays = 0; // amount of days for the build to be usable (0 for infinite!) public static Version Version = Version.Parse(VersionString); @@ -51,6 +53,7 @@ public class TheOtherRolesPlugin : BasePlugin public static ConfigEntry ShowLighterDarker { get; set; } public static ConfigEntry EnableSoundEffects { get; set; } public static ConfigEntry EnableHorseMode { get; set; } + public static ConfigEntry ShowVentsOnMap { get; set; } public static ConfigEntry Ip { get; set; } public static ConfigEntry Port { get; set; } public static ConfigEntry ShowPopUpVersion { get; set; } @@ -104,6 +107,7 @@ public override void Load() { EnableSoundEffects = Config.Bind("Custom", "Enable Sound Effects", true); EnableHorseMode = Config.Bind("Custom", "Enable Horse Mode", false); ShowPopUpVersion = Config.Bind("Custom", "Show PopUp", "0"); + ShowVentsOnMap = Config.Bind("Custom", "Show vent positions on minimap", false); Ip = Config.Bind("Custom", "Custom Server IP", "127.0.0.1"); Port = Config.Bind("Custom", "Custom Server Port", (ushort)22023); @@ -116,23 +120,22 @@ public override void Load() { CustomOptionHolder.Load(); CustomColors.Load(); + CustomHatManager.LoadHats(); if (BepInExUpdater.UpdateRequired) { AddComponent(); return; } + AddComponent(); + EventUtility.Load(); SubmergedCompatibility.Initialize(); - AddComponent(); - Modules.MainMenuPatch.addSceneChangeCallbacks(); + MainMenuPatch.addSceneChangeCallbacks(); _ = RoleInfo.loadReadme(); + AddToKillDistanceSetting.addKillDistance(); TheOtherRolesPlugin.Logger.LogInfo("Loading TOR completed!"); } - public static Sprite GetModStamp() { - if (ModStamp) return ModStamp; - return ModStamp = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ModStamp.png", 150f); - } } // Deactivate bans, since I always leave my local testing game and ban myself diff --git a/TheOtherRoles/MapOptions.cs b/TheOtherRoles/MapOptions.cs index 79994947c..83cce2d65 100644 --- a/TheOtherRoles/MapOptions.cs +++ b/TheOtherRoles/MapOptions.cs @@ -18,6 +18,7 @@ static class TORMapOptions { public static bool enableSoundEffects = true; public static bool enableHorseMode = false; public static bool shieldFirstKill = false; + public static bool ShowVentsOnMap = true; public static CustomGamemodes gameMode = CustomGamemodes.Classic; // Updating values @@ -52,6 +53,8 @@ public static void reloadPluginOptions() { showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value; enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value; enableHorseMode = TheOtherRolesPlugin.EnableHorseMode.Value; + ShowVentsOnMap = TheOtherRolesPlugin.ShowVentsOnMap.Value; + //Patches.ShouldAlwaysHorseAround.isHorseMode = TheOtherRolesPlugin.EnableHorseMode.Value; } } diff --git a/TheOtherRoles/Modules/ChatCommands.cs b/TheOtherRoles/Modules/ChatCommands.cs index d33f863cf..4b0c2b6c2 100644 --- a/TheOtherRoles/Modules/ChatCommands.cs +++ b/TheOtherRoles/Modules/ChatCommands.cs @@ -3,6 +3,7 @@ using System.Linq; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using Hazel; namespace TheOtherRoles.Modules { [HarmonyPatch] @@ -35,6 +36,28 @@ static bool Prefix(ChatController __instance) { handled = true; } } + } else if (text.ToLower().StartsWith("/gm")) { + string gm = text.Substring(4).ToLower(); + CustomGamemodes gameMode = CustomGamemodes.Classic; + if (gm.StartsWith("prop") || gm.StartsWith("ph")) { + gameMode = CustomGamemodes.PropHunt; + } else if (gm.StartsWith("guess") || gm.StartsWith("gm")) { + gameMode = CustomGamemodes.Guesser; + } else if (gm.StartsWith("hide") || gm.StartsWith("hn")) { + gameMode = CustomGamemodes.HideNSeek; + } + // else its classic! + + if (AmongUsClient.Instance.AmHost) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGamemode, Hazel.SendOption.Reliable, -1); + writer.Write((byte)TORMapOptions.gameMode); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shareGamemode((byte)gameMode); + RPCProcedure.shareGamemode((byte)TORMapOptions.gameMode); + } else { + __instance.AddChat(CachedPlayer.LocalPlayer.PlayerControl, "Nice try, but you have to be the host to use this feature"); + } + handled = true; } } diff --git a/TheOtherRoles/Modules/CustomColors.cs b/TheOtherRoles/Modules/CustomColors.cs index 9653b01e6..495af2413 100644 --- a/TheOtherRoles/Modules/CustomColors.cs +++ b/TheOtherRoles/Modules/CustomColors.cs @@ -143,14 +143,10 @@ public static void Load() { /** Add Colors **/ int id = 50000; - foreach (var c in Palette.PlayerColors) { - TheOtherRolesPlugin.Logger.LogMessage(c); - } foreach (CustomColor cc in colors) { longlist.Add((StringNames)id); CustomColors.ColorStrings[id++] = cc.longname; colorlist.Add(cc.color); - TheOtherRolesPlugin.Logger.LogMessage(cc.color); shadowlist.Add(cc.shadow); if (cc.isLighterColor) lighterColors.Add(colorlist.Count - 1); diff --git a/TheOtherRoles/Modules/CustomHats.cs b/TheOtherRoles/Modules/CustomHats.cs deleted file mode 100644 index a7ff4de99..000000000 --- a/TheOtherRoles/Modules/CustomHats.cs +++ /dev/null @@ -1,830 +0,0 @@ -using HarmonyLib; -using UnityEngine; -using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using System.Security.Cryptography; -using Newtonsoft.Json.Linq; -using TheOtherRoles.Players; -using TheOtherRoles.Utilities; -using AmongUs.Data; -using AmongUs.Data.Legacy; -using System.Reflection; -using static Rewired.Controller; -using static TheOtherRoles.Modules.CustomHatLoader; -using Innersloth.Assets; -using UnityEngine.AddressableAssets; -using PowerTools; -using System.Drawing; - -namespace TheOtherRoles.Modules { - [HarmonyPatch] - public class CustomHats { - private static bool LOADED = false; - private static bool RUNNING = false; - public static Material hatShader; - - public static Dictionary CustomHatRegistry = new Dictionary(); - public static Dictionary CustomHatViewDatas = new Dictionary(); - public static HatExtension TestExt = null; - - public class HatExtension { - public string author { get; set;} - public string package { get; set;} - public string condition { get; set;} - public Sprite FlipImage { get; set;} - public Sprite BackFlipImage { get; set;} - } - - public class CustomHat { - public string author { get; set;} - public string package { get; set;} - public string condition { get; set;} - public string name { get; set;} - public string resource { get; set;} - public string flipresource { get; set;} - public string backflipresource { get; set;} - public string backresource { get; set;} - public string climbresource { get; set;} - public bool bounce { get; set;} - public bool adaptive { get; set;} - public bool behind { get; set;} - } - - private static List createCustomHatDetails(string[] hats, bool fromDisk = false) { - Dictionary fronts = new Dictionary(); - Dictionary backs = new Dictionary(); - Dictionary flips = new Dictionary(); - Dictionary backflips = new Dictionary(); - Dictionary climbs = new Dictionary(); - - for (int i = 0; i < hats.Length; i++) { - string s = fromDisk ? hats[i].Substring(hats[i].LastIndexOf("\\") + 1).Split('.')[0] : hats[i].Split('.')[3]; - string[] p = s.Split('_'); - - HashSet options = new HashSet(); - for (int j = 1; j < p.Length; j++) - options.Add(p[j]); - - if (options.Contains("back") && options.Contains("flip")) - backflips.Add(p[0], hats[i]); - else if (options.Contains("climb")) - climbs.Add(p[0], hats[i]); - else if (options.Contains("back")) - backs.Add(p[0], hats[i]); - else if (options.Contains("flip")) - flips.Add(p[0], hats[i]); - else { - CustomHat custom = new CustomHat { resource = hats[i] }; - custom.name = p[0].Replace('-', ' '); - custom.bounce = options.Contains("bounce"); - custom.adaptive = options.Contains("adaptive"); - custom.behind = options.Contains("behind"); - - fronts.Add(p[0], custom); - } - } - - List customhats = new List(); - - foreach (string k in fronts.Keys) { - CustomHat hat = fronts[k]; - string br, cr, fr, bfr; - backs.TryGetValue(k, out br); - climbs.TryGetValue(k, out cr); - flips.TryGetValue(k, out fr); - backflips.TryGetValue(k, out bfr); - if (br != null) - hat.backresource = br; - if (cr != null) - hat.climbresource = cr; - if (fr != null) - hat.flipresource = fr; - if (bfr != null) - hat.backflipresource = bfr; - if (hat.backresource != null) - hat.behind = true; - - customhats.Add(hat); - } - - return customhats; - } - - private static Sprite CreateHatSprite(string path, bool fromDisk = false) { - Texture2D texture = fromDisk ? Helpers.loadTextureFromDisk(path) : Helpers.loadTextureFromResources(path); - if (texture == null) - return null; - Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.53f, 0.575f), texture.width * 0.375f); - if (sprite == null) - return null; - texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; - sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; - - return sprite; - - } - - private static HatData CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) { - if (hatShader == null) { - Material tmpShader = FastDestroyableSingleton.Instance.PlayerMaterial; - hatShader = tmpShader; - } - var viewdata = ScriptableObject.CreateInstance(); - HatData hat = ScriptableObject.CreateInstance(); - - viewdata.MainImage = CreateHatSprite(ch.resource, fromDisk); - viewdata.FloorImage = viewdata.MainImage; - if (ch.backresource != null) { - viewdata.BackImage = CreateHatSprite(ch.backresource, fromDisk); - ch.behind = true; // Required to view backresource - } - if (ch.climbresource != null) { - viewdata.ClimbImage = CreateHatSprite(ch.climbresource, fromDisk); - viewdata.LeftClimbImage = viewdata.ClimbImage; - } - hat.name = ch.name; - hat.displayOrder = 99; - hat.ProductId = "hat_" + ch.name.Replace(' ', '_'); - hat.InFront = !ch.behind; - hat.NoBounce = !ch.bounce; - hat.ChipOffset = new Vector2(0f, 0.2f); - hat.Free = true; - - if (ch.adaptive && hatShader != null) - viewdata.AltShader = hatShader; - - HatExtension extend = new HatExtension(); - extend.author = ch.author != null ? ch.author : "Unknown"; - extend.package = ch.package != null ? ch.package : "Misc."; - extend.condition = ch.condition != null ? ch.condition : "none"; - - if (ch.flipresource != null) - extend.FlipImage = CreateHatSprite(ch.flipresource, fromDisk); - if (ch.backflipresource != null) - extend.BackFlipImage = CreateHatSprite(ch.backflipresource, fromDisk); - - if (testOnly) { - TestExt = extend; - TestExt.condition = hat.name; - } else { - CustomHatRegistry.Add(hat.name, extend); - } - CustomHatViewDatas.TryAdd(hat.name, viewdata); - var assetRef = new AssetReference(viewdata.Pointer); - - hat.ViewDataRef = assetRef; - hat.CreateAddressableAsset(); - return hat; - } - - private static HatData CreateHatBehaviour(CustomHatLoader.CustomHatOnline chd, bool fromDisk = true) { - if (fromDisk) { - string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; - chd.resource = filePath + chd.resource; - if (chd.backresource != null) - chd.backresource = filePath + chd.backresource; - if (chd.climbresource != null) - chd.climbresource = filePath + chd.climbresource; - if (chd.flipresource != null) - chd.flipresource = filePath + chd.flipresource; - if (chd.backflipresource != null) - chd.backflipresource = filePath + chd.backflipresource; - } - - return CreateHatBehaviour(chd, fromDisk, false); - } - - [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))] - private static class HatManagerPatch { - private static List allHatsList; - static void Prefix(HatManager __instance) { - if (RUNNING || LOADED) return; - RUNNING = true; // prevent simultanious execution - allHatsList = __instance.allHats.ToList(); - - try { - while (CustomHatLoader.hatdetails.Count > 0) { - bool fromDisk = !(CustomHatLoader.hatdetails[0].name.Contains("HorseHat_") && (EventUtility.canBeEnabled || EventUtility.isEnabled)); - allHatsList.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0], fromDisk)); - CustomHatLoader.hatdetails.RemoveAt(0); - } - __instance.allHats = allHatsList.ToArray(); - LOADED = true; // this will only be set to true if loading is succesful. - } catch (System.Exception e) { - if (!LOADED) - TheOtherRolesPlugin.Logger.LogMessage("Unable to add Custom Hats\n" + e); - } - } - static void Postfix(HatManager __instance) { - RUNNING = false; - } - } - - [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleAnimation))] - private static class PlayerPhysicsHandleAnimationPatch { - private static void Postfix(PlayerPhysics __instance) { - if (!CustomHatViewDatas.ContainsKey(__instance.myPlayer.cosmetics.hat.Hat.name)) return; - HatViewData viewData = CustomHatViewDatas[__instance.myPlayer.cosmetics.hat.Hat.name]; - AnimationClip currentAnimation = __instance.Animations.Animator.GetCurrentAnimation(); - if (currentAnimation == __instance.Animations.group.ClimbUpAnim || currentAnimation == __instance.Animations.group.ClimbDownAnim) return; - HatParent hp = __instance.myPlayer.cosmetics.hat; - if (hp == null || hp.Hat == null) return; - HatExtension extend = hp.Hat.getHatExtension(); - if (extend == null) return; - if (extend.FlipImage != null) { - if (__instance.FlipX) { - hp.FrontLayer.sprite = extend.FlipImage; - } else { - hp.FrontLayer.sprite = viewData.MainImage; - } - } - if (extend.BackFlipImage != null) { - if (__instance.FlipX) { - hp.BackLayer.sprite = extend.BackFlipImage; - } else { - hp.BackLayer.sprite = viewData.BackImage; - } - } - } - } - - [HarmonyPatch] - private static class FreeplayHatTestingPatches - { - [HarmonyPriority(Priority.High)] - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), typeof(int))] - private static class HatParentSetHatPatchColor { - static void Prefix(HatParent __instance) { - if (DestroyableSingleton.InstanceExists) { - try { - string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test"; - if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath); - DirectoryInfo d = new DirectoryInfo(filePath); - string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Text files - List hats = createCustomHatDetails(filePaths, true); - if (hats.Count > 0) { - __instance.Hat = CreateHatBehaviour(hats[0], true, true); - } - } catch (System.Exception e) { - System.Console.WriteLine("Unable to create test hat\n" + e); - } - } - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), typeof(HatData), typeof(int))] - private static class HatParentSetHatPatchExtra { - static bool Prefix(HatParent __instance, HatData hat, int color) - { - if (!DestroyableSingleton.InstanceExists) return true; - - try - { - __instance.Hat = hat; - __instance.hatDataAsset = __instance.Hat.CreateAddressableAsset(); - - string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test"; - if (!Directory.Exists(filePath)) return true; - DirectoryInfo d = new DirectoryInfo(filePath); - string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Test files - List hats = createCustomHatDetails(filePaths, true); - if (hats.Count > 0) - { - __instance.Hat = CreateHatBehaviour(hats[0], true, true); - __instance.Hat.CreateAddressableAsset(); - } - } - catch (System.Exception e) - { - System.Console.WriteLine("Unable to create test hat\n" + e); - return true; - } - - __instance.PopulateFromHatViewData(); - __instance.SetMaterialColor(color); - return false; - } - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), typeof(int))] - public class SetHatPatch { - public static bool Prefix(HatParent __instance, int color) { - if (!CustomHatViewDatas.ContainsKey(__instance.Hat.name)) return true; - __instance.hatDataAsset = null; - __instance.PopulateFromHatViewData(); - __instance.SetMaterialColor(color); - return false; - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.UpdateMaterial))] - public class UpdateMaterialPatch { - public static bool Prefix(HatParent __instance) { - HatViewData asset; - try { - HatViewData vanillaAsset = __instance.hatDataAsset.GetAsset(); - return true; - } catch { - try { - asset = CustomHatViewDatas[__instance.Hat.name]; - } catch { - return false; - } - } - if (asset.AltShader) { - __instance.FrontLayer.sharedMaterial = asset.AltShader; - if (__instance.BackLayer) { - __instance.BackLayer.sharedMaterial = asset.AltShader; - } - } else { - __instance.FrontLayer.sharedMaterial = DestroyableSingleton.Instance.DefaultShader; - if (__instance.BackLayer) { - __instance.BackLayer.sharedMaterial = DestroyableSingleton.Instance.DefaultShader; - } - } - int colorId = __instance.matProperties.ColorId; - PlayerMaterial.SetColors(colorId, __instance.FrontLayer); - if (__instance.BackLayer) { - PlayerMaterial.SetColors(colorId, __instance.BackLayer); - } - __instance.FrontLayer.material.SetInt(PlayerMaterial.MaskLayer, __instance.matProperties.MaskLayer); - if (__instance.BackLayer) { - __instance.BackLayer.material.SetInt(PlayerMaterial.MaskLayer, __instance.matProperties.MaskLayer); - } - PlayerMaterial.MaskType maskType = __instance.matProperties.MaskType; - if (maskType == PlayerMaterial.MaskType.ScrollingUI) { - if (__instance.FrontLayer) { - __instance.FrontLayer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - } - if (__instance.BackLayer) { - __instance.BackLayer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - return false; - } - } else if (maskType == PlayerMaterial.MaskType.Exile) { - if (__instance.FrontLayer) { - __instance.FrontLayer.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; - } - if (__instance.BackLayer) { - __instance.BackLayer.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; - return false; - } - } else { - if (__instance.FrontLayer) { - __instance.FrontLayer.maskInteraction = SpriteMaskInteraction.None; - } - if (__instance.BackLayer) { - __instance.BackLayer.maskInteraction = SpriteMaskInteraction.None; - } - } - return false; - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.LateUpdate))] - public static class HatParentLateUpdatePatch { - public static bool Prefix(HatParent __instance) { - if (__instance.Parent) { - HatViewData hatViewData; - try { - hatViewData = __instance.hatDataAsset.GetAsset(); - return true; - } catch { - try { - hatViewData = CustomHatViewDatas[__instance.Hat.name]; - } catch { - return false; - } - } - if (__instance.Hat && hatViewData != null) { - if (__instance.FrontLayer.sprite != hatViewData.ClimbImage && __instance.FrontLayer.sprite != hatViewData.FloorImage) { - if ((__instance.Hat.InFront || hatViewData.BackImage) && hatViewData.LeftMainImage) { - __instance.FrontLayer.sprite = (__instance.Parent.flipX ? hatViewData.LeftMainImage : hatViewData.MainImage); - } - if (hatViewData.BackImage && hatViewData.LeftBackImage) { - __instance.BackLayer.sprite = (__instance.Parent.flipX ? hatViewData.LeftBackImage : hatViewData.BackImage); - return false; - } - if (!hatViewData.BackImage && !__instance.Hat.InFront && hatViewData.LeftMainImage) { - __instance.BackLayer.sprite = (__instance.Parent.flipX ? hatViewData.LeftMainImage : hatViewData.MainImage); - return false; - } - } else if (__instance.FrontLayer.sprite == hatViewData.ClimbImage || __instance.FrontLayer.sprite == hatViewData.LeftClimbImage) { - SpriteAnimNodeSync spriteAnimNodeSync = __instance.SpriteSyncNode ?? __instance.GetComponent(); - if (spriteAnimNodeSync) { - spriteAnimNodeSync.NodeId = 0; - } - } - } - } - return false; - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetFloorAnim))] - public class HatParentSetFloorAnimPatch { - public static bool Prefix(HatParent __instance) { - try { - HatViewData vanillaAsset = __instance.hatDataAsset.GetAsset(); - return true; - } catch { } - HatViewData hatViewData = CustomHatViewDatas[__instance.Hat.name]; - __instance.BackLayer.enabled = false; - __instance.FrontLayer.enabled = true; - __instance.FrontLayer.sprite = hatViewData.FloorImage; - return false; - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetIdleAnim))] - public class HatParentSetIdleAnimPatch { - public static bool Prefix(HatParent __instance, int colorId) { - if (!__instance.Hat) return false; - if (!CustomHatViewDatas.ContainsKey(__instance.Hat.name)) - return true; - HatViewData hatViewData = CustomHatViewDatas[__instance.Hat.name]; - __instance.hatDataAsset = null; - __instance.PopulateFromHatViewData(); - __instance.SetMaterialColor(colorId); - return false; - } - } - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetClimbAnim))] - public class HatParentSetClimbAnimPatch { - public static bool Prefix(HatParent __instance) { - try { - HatViewData vanillaAsset = __instance.hatDataAsset.GetAsset(); - return true; - } catch { } - - HatViewData hatViewData = CustomHatViewDatas[__instance.Hat.name]; - if (!__instance.options.ShowForClimb) { - return false; - } - __instance.BackLayer.enabled = false; - __instance.FrontLayer.enabled = true; - __instance.FrontLayer.sprite = hatViewData.ClimbImage; - return false; - } - } - - - [HarmonyPatch(typeof(HatParent), nameof(HatParent.PopulateFromHatViewData))] - public class PopulateFromHatViewDataPatch { - public static bool Prefix(HatParent __instance) { - try { - HatViewData vanillaAsset = __instance.hatDataAsset.GetAsset(); - return true; - } catch { - if (__instance.Hat && !CustomHatViewDatas.ContainsKey(__instance.Hat.name)) - return true; - } - - - HatViewData asset = CustomHatViewDatas[__instance.Hat.name]; - - if (!asset) { - return true; - } - __instance.UpdateMaterial(); - - SpriteAnimNodeSync spriteAnimNodeSync = __instance.SpriteSyncNode ?? __instance.GetComponent(); - if (spriteAnimNodeSync) { - spriteAnimNodeSync.NodeId = (__instance.Hat.NoBounce ? 1 : 0); - } - if (__instance.Hat.InFront) { - __instance.BackLayer.enabled = false; - __instance.FrontLayer.enabled = true; - __instance.FrontLayer.sprite = asset.MainImage; - } else if (asset.BackImage) { - __instance.BackLayer.enabled = true; - __instance.FrontLayer.enabled = true; - __instance.BackLayer.sprite = asset.BackImage; - __instance.FrontLayer.sprite = asset.MainImage; - } else { - __instance.BackLayer.enabled = true; - __instance.FrontLayer.enabled = false; - __instance.FrontLayer.sprite = null; - __instance.BackLayer.sprite = asset.MainImage; - } - if (__instance.options.Initialized && __instance.HideHat()) { - __instance.FrontLayer.enabled = false; - __instance.BackLayer.enabled = false; - } - return false; - } - } - - [HarmonyPatch(typeof(CosmeticsCache), nameof(CosmeticsCache.GetHat))] - public class CosmeticsCacheGetHatPatch { - public static bool Prefix(string id, ref HatViewData __result) { - TheOtherRolesPlugin.Logger.LogMessage($"trying to load hat {id} from cosmetics cache"); - if (CustomHatViewDatas.ContainsKey(id)) { - __result = CustomHatViewDatas[id]; - return false; - } - return true; - } - } - - [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.OnEnable))] - public class HatsTabOnEnablePatch { - public static string innerslothPackageName = "Innersloth Hats"; - private static TMPro.TMP_Text textTemplate; - - public static float createHatPackage(List> hats, string packageName, float YStart, HatsTab __instance) { - bool isDefaultPackage = innerslothPackageName == packageName; - if (!isDefaultPackage) - hats = hats.OrderBy(x => x.Item1.name).ToList(); - float offset = YStart; - - if (textTemplate != null) { - TMPro.TMP_Text title = UnityEngine.Object.Instantiate(textTemplate, __instance.scroller.Inner); - title.transform.localPosition = new Vector3(2.25f, YStart, -1f); - title.transform.localScale = Vector3.one * 1.5f; - title.fontSize *= 0.5f; - title.enableAutoSizing = false; - __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { title.SetText(packageName); }))); - offset -= 0.8f * __instance.YOffset; - } - for (int i = 0; i < hats.Count; i++) { - HatData hat = hats[i].Item1; - HatExtension ext = hats[i].Item2; - - float xpos = __instance.XRange.Lerp((i % __instance.NumPerRow) / (__instance.NumPerRow - 1f)); - float ypos = offset - (i / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset; - ColorChip colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); - if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) { - colorChip.Button.OnMouseOver.AddListener((System.Action)(() => __instance.SelectHat(hat))); - colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(FastDestroyableSingleton.Instance.GetHatById(DataManager.Player.Customization.Hat)))); - colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.ClickEquip())); - } else { - colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.SelectHat(hat))); - } - colorChip.Button.ClickMask = __instance.scroller.Hitbox; - Transform background = colorChip.transform.FindChild("Background"); - Transform foreground = colorChip.transform.FindChild("ForeGround"); - - if (ext != null) { - if (background != null) { - background.localPosition = Vector3.down * 0.243f; - background.localScale = new Vector3(background.localScale.x, 0.8f, background.localScale.y); - } - if (foreground != null) { - foreground.localPosition = Vector3.down * 0.243f; - } - - if (textTemplate != null) { - TMPro.TMP_Text description = UnityEngine.Object.Instantiate(textTemplate, colorChip.transform); - description.transform.localPosition = new Vector3(0f, -0.65f, -1f); - description.alignment = TMPro.TextAlignmentOptions.Center; - description.transform.localScale = Vector3.one * 0.65f; - __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { description.SetText($"{hat.name}\nby {ext.author}"); }))); - } - } - - colorChip.transform.localPosition = new Vector3(xpos, ypos, -1f); - colorChip.Inner.SetHat(hat, __instance.HasLocalPlayer() ? CachedPlayer.LocalPlayer.Data.DefaultOutfit.ColorId : ((int)DataManager.Player.Customization.Color)); - colorChip.Inner.transform.localPosition = hat.ChipOffset; - colorChip.Tag = hat; - colorChip.SelectionHighlight.gameObject.SetActive(false); - __instance.ColorChips.Add(colorChip); - } - return offset - ((hats.Count - 1) / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset - 1.75f; - } - - public static void Postfix(HatsTab __instance) { - for (int i = 0; i < __instance.scroller.Inner.childCount; i++) - UnityEngine.Object.Destroy(__instance.scroller.Inner.GetChild(i).gameObject); - __instance.ColorChips = new Il2CppSystem.Collections.Generic.List(); - - HatData[] unlockedHats = FastDestroyableSingleton.Instance.GetUnlockedHats(); - Dictionary>> packages = new Dictionary>>(); - - foreach (HatData hatBehaviour in unlockedHats) { - HatExtension ext = hatBehaviour.getHatExtension(); - - if (ext != null) { - if (!packages.ContainsKey(ext.package)) - packages[ext.package] = new List>(); - packages[ext.package].Add(new System.Tuple(hatBehaviour, ext)); - } else { - if (!packages.ContainsKey(innerslothPackageName)) - packages[innerslothPackageName] = new List>(); - packages[innerslothPackageName].Add(new System.Tuple(hatBehaviour, null)); - } - } - - packages.Remove("Horse Hats"); // Cannot be selected! - - float YOffset = __instance.YStart; - textTemplate = GameObject.Find("HatsGroup").transform.FindChild("Text").GetComponent(); - - var orderedKeys = packages.Keys.OrderBy((string x) => { - if (x == innerslothPackageName) return 1000; - if (x == "Developer Hats") return 0; - return 500; - }); - foreach (string key in orderedKeys) { - List> value = packages[key]; - YOffset = createHatPackage(value, key, YOffset, __instance); - } - - __instance.scroller.ContentYBounds.max = -(YOffset + 4.1f); - } - } - - } - - public class CustomHatLoader { - public static bool running = false; - private const string REPO = "https://raw.githubusercontent.com/Eisbison/TheOtherHats/master"; - - public static List hatdetails = new List(); - private static Task hatFetchTask = null; - public static void LaunchHatFetcher() { - if (running) - return; - running = true; - hatFetchTask = LaunchHatFetcherAsync(); - } - - private static async Task LaunchHatFetcherAsync() { - try { - HttpStatusCode status = await FetchHats(); - if (status != HttpStatusCode.OK) - System.Console.WriteLine("Custom Hats could not be loaded\n"); - } catch (System.Exception e) { - System.Console.WriteLine("Unable to fetch hats\n" + e.Message); - } - running = false; - } - - private static string sanitizeResourcePath(string res) { - if (res == null || !res.EndsWith(".png")) - return null; - - res = res.Replace("\\", "") - .Replace("/", "") - .Replace("*", "") - .Replace("..", ""); - return res; - } - - public static async Task FetchHats() { - HttpClient http = new HttpClient(); - http.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue{ NoCache = true }; - var response = await http.GetAsync(new System.Uri($"{REPO}/CustomHats.json"), HttpCompletionOption.ResponseContentRead); - try { - if (response.StatusCode != HttpStatusCode.OK) return response.StatusCode; - if (response.Content == null) { - System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); - return HttpStatusCode.ExpectationFailed; - } - string json = await response.Content.ReadAsStringAsync(); - JToken jobj = JObject.Parse(json)["hats"]; - if (!jobj.HasValues) return HttpStatusCode.ExpectationFailed; - - List hatdatas = new List(); - - for (JToken current = jobj.First; current != null; current = current.Next) { - if (current.HasValues) { - CustomHatOnline info = new CustomHatOnline(); - - info.name = current["name"]?.ToString(); - info.resource = sanitizeResourcePath(current["resource"]?.ToString()); - if (info.resource == null || info.name == null) // required - continue; - info.reshasha = current["reshasha"]?.ToString(); - info.backresource = sanitizeResourcePath(current["backresource"]?.ToString()); - info.reshashb = current["reshashb"]?.ToString(); - info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString()); - info.reshashc = current["reshashc"]?.ToString(); - info.flipresource = sanitizeResourcePath(current["flipresource"]?.ToString()); - info.reshashf = current["reshashf"]?.ToString(); - info.backflipresource = sanitizeResourcePath(current["backflipresource"]?.ToString()); - info.reshashbf = current["reshashbf"]?.ToString(); - - info.author = current["author"]?.ToString(); - info.package = current["package"]?.ToString(); - info.condition = current["condition"]?.ToString(); - info.bounce = current["bounce"] != null; - info.adaptive = current["adaptive"] != null; - info.behind = current["behind"] != null; - hatdatas.Add(info); - } - } - - List markedfordownload = new List(); - - string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; - if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath); - MD5 md5 = MD5.Create(); - foreach (CustomHatOnline data in hatdatas) { - if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5)) - markedfordownload.Add(data.resource); - if (data.backresource != null && doesResourceRequireDownload(filePath + data.backresource, data.reshashb, md5)) - markedfordownload.Add(data.backresource); - if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5)) - markedfordownload.Add(data.climbresource); - if (data.flipresource != null && doesResourceRequireDownload(filePath + data.flipresource, data.reshashf, md5)) - markedfordownload.Add(data.flipresource); - if (data.backflipresource != null && doesResourceRequireDownload(filePath + data.backflipresource, data.reshashbf, md5)) - markedfordownload.Add(data.backflipresource); - } - - foreach(var file in markedfordownload) { - - var hatFileResponse = await http.GetAsync($"{REPO}/hats/{file}", HttpCompletionOption.ResponseContentRead); - if (hatFileResponse.StatusCode != HttpStatusCode.OK) continue; - using (var responseStream = await hatFileResponse.Content.ReadAsStreamAsync()) { - using (var fileStream = File.Create($"{filePath}\\{file}")) { - responseStream.CopyTo(fileStream); - } - } - } - - if (EventUtility.canBeEnabled || EventUtility.isEnabled) addHorseHats(ref hatdatas); - - - hatdetails = hatdatas; - } catch (System.Exception ex) { - TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); - System.Console.WriteLine(ex); - } - return HttpStatusCode.OK; - } - - private static bool doesResourceRequireDownload(string respath, string reshash, MD5 md5) { - if (reshash == null || !File.Exists(respath)) - return true; - - using (var stream = File.OpenRead(respath)) { - var hash = System.BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); - return !reshash.Equals(hash); - } - } - - - public static List horseHatProductIds = null; - private static void addHorseHats(ref List hatdatas) { - - horseHatProductIds = new(); - Assembly assembly = Assembly.GetExecutingAssembly(); - string[] resourceNames = assembly.GetManifestResourceNames(); - List hatFiles = new(); - Dictionary> hatFilesSorted = new Dictionary>(); - foreach (string resourceName in resourceNames) { - if (resourceName.Contains("TheOtherRoles.Resources.HorseHats.") && resourceName.Contains(".png")) { - hatFiles.Add(resourceName); - } - } - - foreach (string s in hatFiles) { - string value = s.Substring(0, s.LastIndexOf("HorseSpecialHat") + 17); - if (value.Contains(".")) value.Remove(value.LastIndexOf(".")); - if (!hatFilesSorted.ContainsKey(value)) hatFilesSorted.Add(value, new List()); - hatFilesSorted[value].Add(s); - } - - foreach (var item in hatFilesSorted) { - CustomHatOnline info = new CustomHatOnline(); - info.name = "HorseHat_" + item.Key; - info.resource = item.Value.FirstOrDefault(x => !x.Contains("back")); - info.backresource = item.Value.FirstOrDefault(x => x.Contains("back")); - info.adaptive = info.resource != null && info.resource.Contains("adaptive"); - info.flipresource = item.Value.FirstOrDefault(x => x.Contains("flip")); - info.climbresource = item.Value.FirstOrDefault(x => x.Contains("climb")); - info.package = "Horse Hats"; - if (info.resource == null || info.name == null) // required - continue; - horseHatProductIds.Add("hat_" + info.name.Replace(" ", "_")); - hatdatas.Add(info); - } - } - - public class CustomHatOnline : CustomHats.CustomHat { - public string reshasha { get; set;} - public string reshashb { get; set;} - public string reshashc { get; set;} - public string reshashf { get; set;} - public string reshashbf { get; set;} - } - } - public static class CustomHatExtensions { - public static CustomHats.HatExtension getHatExtension(this HatData hat) { - CustomHats.HatExtension ret = null; - if (CustomHats.TestExt != null && CustomHats.TestExt.condition.Equals(hat.name)) { - return CustomHats.TestExt; - } - CustomHats.CustomHatRegistry.TryGetValue(hat.name, out ret); - return ret; - } - } -} diff --git a/TheOtherRoles/Modules/CustomHats/CustomHat.cs b/TheOtherRoles/Modules/CustomHats/CustomHat.cs new file mode 100644 index 000000000..210c8b9ce --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/CustomHat.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace TheOtherRoles.Modules.CustomHats; + +public class CustomHat : CustomHatHashes +{ + [JsonPropertyName("author")] public string Author { get; set; } + + [JsonPropertyName("bounce")] public bool Bounce { get; set; } + + [JsonPropertyName("climbresource")] public string ClimbResource { get; set; } + + [JsonPropertyName("condition")] public string Condition { get; set; } + + [JsonPropertyName("name")] public string Name { get; set; } + + [JsonPropertyName("package")] public string Package { get; set; } + + [JsonPropertyName("resource")] public string Resource { get; set; } + + [JsonPropertyName("adaptive")] public bool Adaptive { get; set; } + + [JsonPropertyName("behind")] public bool Behind { get; set; } + + [JsonPropertyName("backresource")] public string BackResource { get; set; } + + [JsonPropertyName("backflipresource")] public string BackFlipResource { get; set; } + + [JsonPropertyName("flipresource")] public string FlipResource { get; set; } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/CustomHatHashes.cs b/TheOtherRoles/Modules/CustomHats/CustomHatHashes.cs new file mode 100644 index 000000000..ead2350bf --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/CustomHatHashes.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace TheOtherRoles.Modules.CustomHats; + +public class CustomHatHashes +{ + [JsonPropertyName("reshasha")] public string ResHashA { get; set; } + + [JsonPropertyName("reshashb")] public string ResHashB { get; set; } + + [JsonPropertyName("reshashbf")] public string ResHashBf { get; set; } + + [JsonPropertyName("reshashc")] public string ResHashC { get; set; } + + [JsonPropertyName("reshashf")] public string ResHashF { get; set; } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs b/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs new file mode 100644 index 000000000..4eb518577 --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using UnityEngine; +using UnityEngine.AddressableAssets; + +namespace TheOtherRoles.Modules.CustomHats; + +public static class CustomHatManager +{ + public const string ResourcesDirectory = "TheOtherHats"; + public const string InnerslothPackageName = "Innersloth Hats"; + public const string DeveloperPackageName = "Developer Hats"; + + internal static readonly Tuple Repository = new("TheOtherRolesAU", "TheOtherHats"); + internal static string RepositoryUrl + { + get + { + var (owner, repository) = Repository; + return $"https://raw.githubusercontent.com/{owner}/{repository}/master"; + } + } + + internal static readonly string ManifestFileName = "CustomHats.json"; + + internal static string CustomSkinsDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath)!, ResourcesDirectory); + internal static string HatsDirectory => CustomSkinsDirectory; + + internal static readonly List UnregisteredHats = new(); + internal static readonly Dictionary ViewDataCache = new(); + internal static readonly Dictionary ExtensionCache = new(); + + private static readonly HatsLoader Loader; + private static Material cachedShader; + + internal static HatExtension TestExtension { get; private set; } + + static CustomHatManager() + { + Loader = TheOtherRolesPlugin.Instance.AddComponent(); + } + + internal static void LoadHats() + { + Loader.FetchHats(); + } + + internal static bool TryGetCached(this HatParent hatParent, out HatViewData asset) + { + if (hatParent && hatParent.Hat) return hatParent.Hat.TryGetCached(out asset); + asset = null; + return false; + } + + internal static bool TryGetCached(this HatData hat, out HatViewData asset) + { + return ViewDataCache.TryGetValue(hat.name, out asset); + } + + internal static bool IsCached(this HatData hat) + { + return ViewDataCache.ContainsKey(hat.name); + } + + internal static bool IsCached(this HatParent hatParent) + { + return hatParent.Hat.IsCached(); + } + + internal static HatData CreateHatBehaviour(CustomHat ch, bool testOnly = false) + { + if (cachedShader == null) cachedShader = DestroyableSingleton.Instance.PlayerMaterial; + + var viewData = ViewDataCache[ch.Name] = ScriptableObject.CreateInstance(); + var hat = ScriptableObject.CreateInstance(); + + viewData.MainImage = CreateHatSprite(ch.Resource); + if (viewData.MainImage == null) { + throw new FileNotFoundException("File not downloaded yet"); + } + viewData.FloorImage = viewData.MainImage; + if (ch.BackResource != null) + { + viewData.BackImage = CreateHatSprite(ch.BackResource); + ch.Behind = true; + } + + if (ch.ClimbResource != null) + { + viewData.ClimbImage = CreateHatSprite(ch.ClimbResource); + viewData.LeftClimbImage = viewData.ClimbImage; + } + + hat.name = ch.Name; + hat.displayOrder = 99; + hat.ProductId = "hat_" + ch.Name.Replace(' ', '_'); + hat.InFront = !ch.Behind; + hat.NoBounce = !ch.Bounce; + hat.ChipOffset = new Vector2(0f, 0.2f); + hat.Free = true; + + if (ch.Adaptive && cachedShader != null) + { + viewData.AltShader = cachedShader; + } + + var extend = new HatExtension + { + Author = ch.Author ?? "Unknown", + Package = ch.Package ?? "Misc.", + Condition = ch.Condition ?? "none" + }; + + if (ch.FlipResource != null) + { + extend.FlipImage = CreateHatSprite(ch.FlipResource); + } + + if (ch.BackFlipResource != null) + { + extend.BackFlipImage = CreateHatSprite(ch.BackFlipResource); + } + + if (testOnly) + { + TestExtension = extend; + TestExtension.Condition = hat.name; + } + else + { + ExtensionCache[hat.name] = extend; + } + + hat.ViewDataRef = new AssetReference(ViewDataCache[hat.name].Pointer); + hat.CreateAddressableAsset(); + return hat; + } + + private static Sprite CreateHatSprite(string path) + { + var texture = Helpers.loadTextureFromDisk(Path.Combine(HatsDirectory, path)); + if (texture == null) return null; + var sprite = Sprite.Create(texture, + new Rect(0, 0, texture.width, texture.height), + new Vector2(0.53f, 0.575f), + texture.width * 0.375f); + if (sprite == null) return null; + texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; + sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; + + return sprite; + } + + public static List CreateHatDetailsFromFileNames(string[] fileNames, bool fromDisk = false) + { + var fronts = new Dictionary(); + var backs = new Dictionary(); + var flips = new Dictionary(); + var backFlips = new Dictionary(); + var climbs = new Dictionary(); + + foreach (var fileName in fileNames) + { + var index = fileName.LastIndexOf("\\", StringComparison.InvariantCulture) + 1; + var s = fromDisk ? fileName[index..].Split('.')[0] : fileName.Split('.')[3]; + var p = s.Split('_'); + var options = new HashSet(p); + if (options.Contains("back") && options.Contains("flip")) + { + backFlips[p[0]] = fileName; + } + else if (options.Contains("climb")) + { + climbs[p[0]] = fileName; + } + else if (options.Contains("back")) + { + backs[p[0]] = fileName; + } + else if (options.Contains("flip")) + { + flips[p[0]] = fileName; + } + else + { + fronts[p[0]] = new CustomHat + { + Resource = fileName, + Name = p[0].Replace('-', ' '), + Bounce = options.Contains("bounce"), + Adaptive = options.Contains("adaptive"), + Behind = options.Contains("behind"), + }; + } + } + + var hats = new List(); + + foreach (var frontKvP in fronts) + { + var k = frontKvP.Key; + var hat = frontKvP.Value; + backs.TryGetValue(k, out var backResource); + climbs.TryGetValue(k, out var climbResource); + flips.TryGetValue(k, out var flipResource); + backFlips.TryGetValue(k, out var backFlipResource); + if (backResource != null) hat.BackResource = backResource; + if (climbResource != null) hat.ClimbResource = climbResource; + if (flipResource != null) hat.FlipResource = flipResource; + if (backFlipResource != null) hat.BackFlipResource = backFlipResource; + if (hat.BackResource != null) hat.Behind = true; + hats.Add(hat); + } + + return hats; + } + + internal static List SanitizeHats(SkinsConfigFile response) + { + foreach (var hat in response.Hats) + { + hat.Resource = SanitizeFileName(hat.Resource); + hat.BackResource = SanitizeFileName(hat.BackResource); + hat.ClimbResource = SanitizeFileName(hat.ClimbResource); + hat.FlipResource = SanitizeFileName(hat.FlipResource); + hat.BackFlipResource = SanitizeFileName(hat.BackFlipResource); + } + + return response.Hats; + } + + private static string SanitizeFileName(string path) + { + if (path == null || !path.EndsWith(".png")) return null; + return path.Replace("\\", "") + .Replace("/", "") + .Replace("*", "") + .Replace("..", ""); + } + + private static bool ResourceRequireDownload(string resFile, string resHash, HashAlgorithm algorithm) + { + var filePath = Path.Combine(HatsDirectory, resFile); + if (resHash == null || !File.Exists(filePath)) + { + return true; + } + using var stream = File.OpenRead(filePath); + var hash = BitConverter.ToString(algorithm.ComputeHash(stream)) + .Replace("-", string.Empty) + .ToLowerInvariant(); + return !resHash.Equals(hash); + } + + internal static List GenerateDownloadList(List hats) + { + var algorithm = MD5.Create(); + var toDownload = new List(); + + foreach (var hat in hats) + { + var files = new List> + { + new(hat.Resource, hat.ResHashA), + new(hat.BackResource, hat.ResHashB), + new(hat.ClimbResource, hat.ResHashC), + new(hat.FlipResource, hat.ResHashF), + new(hat.BackFlipResource, hat.ResHashBf) + }; + foreach (var (fileName, fileHash) in files) + { + if (fileName != null && ResourceRequireDownload(fileName, fileHash, algorithm)) + { + toDownload.Add(fileName); + } + } + } + + return toDownload; + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/Extensions/HatDataExtensions.cs b/TheOtherRoles/Modules/CustomHats/Extensions/HatDataExtensions.cs new file mode 100644 index 000000000..d7a11200e --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/Extensions/HatDataExtensions.cs @@ -0,0 +1,14 @@ +namespace TheOtherRoles.Modules.CustomHats.Extensions; + +internal static class HatDataExtensions +{ + public static HatExtension GetHatExtension(this HatData hat) + { + if (CustomHatManager.TestExtension != null && CustomHatManager.TestExtension.Condition.Equals(hat.name)) + { + return CustomHatManager.TestExtension; + } + + return CustomHatManager.ExtensionCache.TryGetValue(hat.name, out var extension) ? extension : null; + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/HatExtension.cs b/TheOtherRoles/Modules/CustomHats/HatExtension.cs new file mode 100644 index 000000000..a50f5b74b --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/HatExtension.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace TheOtherRoles.Modules.CustomHats; + +public class HatExtension +{ + public string Author { get; set; } + public string Package { get; set; } + public string Condition { get; set; } + public Sprite FlipImage { get; set; } + public Sprite BackFlipImage { get; set; } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/HatsLoader.cs b/TheOtherRoles/Modules/CustomHats/HatsLoader.cs new file mode 100644 index 000000000..d8b0584f4 --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/HatsLoader.cs @@ -0,0 +1,103 @@ +using System.Collections; +using System.IO; +using System.Text.Json; +using BepInEx.Unity.IL2CPP.Utils; +using UnityEngine; +using UnityEngine.Networking; +using static TheOtherRoles.Modules.CustomHats.CustomHatManager; + +namespace TheOtherRoles.Modules.CustomHats; + +public class HatsLoader : MonoBehaviour +{ + private bool isRunning; + + public void FetchHats() + { + if (isRunning) return; + this.StartCoroutine(CoFetchHats()); + } + + [HideFromIl2Cpp] + private IEnumerator CoFetchHats() + { + isRunning = true; + var www = new UnityWebRequest(); + www.SetMethod(UnityWebRequest.UnityWebRequestMethod.Get); + TheOtherRolesPlugin.Logger.LogMessage($"Download manifest at: {RepositoryUrl}/{ManifestFileName}"); + www.SetUrl($"{RepositoryUrl}/{ManifestFileName}"); + www.downloadHandler = new DownloadHandlerBuffer(); + var operation = www.SendWebRequest(); + + while (!operation.isDone) + { + yield return new WaitForEndOfFrame(); + } + + if (www.isNetworkError || www.isHttpError) + { + TheOtherRolesPlugin.Logger.LogError(www.error); + yield break; + } + + var response = JsonSerializer.Deserialize(www.downloadHandler.text, new JsonSerializerOptions + { + AllowTrailingCommas = true + }); + www.downloadHandler.Dispose(); + www.Dispose(); + + if (!Directory.Exists(HatsDirectory)) Directory.CreateDirectory(HatsDirectory); + + UnregisteredHats.AddRange(SanitizeHats(response)); + var toDownload = GenerateDownloadList(UnregisteredHats); + + TheOtherRolesPlugin.Logger.LogMessage($"I'll download {toDownload.Count} hat files"); + + foreach (var fileName in toDownload) + { + yield return CoDownloadHatAsset(fileName); + } + + isRunning = false; + } + + private static IEnumerator CoDownloadHatAsset(string fileName) + { + var www = new UnityWebRequest(); + www.SetMethod(UnityWebRequest.UnityWebRequestMethod.Get); + fileName = fileName.Replace(" ", "%20"); + TheOtherRolesPlugin.Logger.LogMessage($"downloading hat: {fileName}"); + www.SetUrl($"{RepositoryUrl}/hats/{fileName}"); + www.downloadHandler = new DownloadHandlerBuffer(); + var operation = www.SendWebRequest(); + + while (!operation.isDone) + { + yield return new WaitForEndOfFrame(); + } + + if (www.isNetworkError || www.isHttpError) + { + TheOtherRolesPlugin.Logger.LogError(www.error); + yield break; + } + + var filePath = Path.Combine(HatsDirectory, fileName); + filePath = filePath.Replace("%20", " "); + var persistTask = File.WriteAllBytesAsync(filePath, www.downloadHandler.data); + while (!persistTask.IsCompleted) + { + if (persistTask.Exception != null) + { + TheOtherRolesPlugin.Logger.LogError(persistTask.Exception.Message); + break; + } + + yield return new WaitForEndOfFrame(); + } + + www.downloadHandler.Dispose(); + www.Dispose(); + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/Patches/CosmeticsCachePatches.cs b/TheOtherRoles/Modules/CustomHats/Patches/CosmeticsCachePatches.cs new file mode 100644 index 000000000..31b843f49 --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/Patches/CosmeticsCachePatches.cs @@ -0,0 +1,16 @@ +using HarmonyLib; +using TheOtherRoles; + +namespace TheOtherRoles.Modules.CustomHats.Patches; + +[HarmonyPatch(typeof(CosmeticsCache))] +internal static class CosmeticsCachePatches +{ + [HarmonyPatch(nameof(CosmeticsCache.GetHat))] + [HarmonyPrefix] + private static bool GetHatPrefix(string id, ref HatViewData __result) + { + TheOtherRolesPlugin.Logger.LogMessage($"trying to load hat {id} from cosmetics cache"); + return !CustomHatManager.ViewDataCache.TryGetValue(id, out __result); + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/Patches/HatManagerPatches.cs b/TheOtherRoles/Modules/CustomHats/Patches/HatManagerPatches.cs new file mode 100644 index 000000000..cdfc2ab52 --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/Patches/HatManagerPatches.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.Extensions; +using HarmonyLib; +using TheOtherRoles; + +namespace TheOtherRoles.Modules.CustomHats.Patches; + +[HarmonyPatch(typeof(HatManager))] +internal static class HatManagerPatches +{ + private static bool isRunning; + private static bool isLoaded; + private static List allHats; + + [HarmonyPatch(nameof(HatManager.GetHatById))] + [HarmonyPrefix] + private static void GetHatByIdPrefix(HatManager __instance) + { + if (isRunning || isLoaded) return; + isRunning = true; + // Maybe we can use lock keyword to ensure simultaneous list manipulations ? + allHats = __instance.allHats.ToList(); + var cache = CustomHatManager.UnregisteredHats.Clone(); + foreach (var hat in cache) + { + try + { + allHats.Add(CustomHatManager.CreateHatBehaviour(hat)); + CustomHatManager.UnregisteredHats.Remove(hat); + } + catch + { + // This means the file has not been downloaded yet, do nothing... + } + } + if (CustomHatManager.UnregisteredHats.Count == 0) + isLoaded = true; + cache.Clear(); + + __instance.allHats = allHats.ToArray(); + } + + [HarmonyPatch(nameof(HatManager.GetHatById))] + [HarmonyPostfix] + private static void GetHatByIdPostfix() + { + isRunning = false; + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs b/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs new file mode 100644 index 000000000..6112de08f --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs @@ -0,0 +1,269 @@ +using System; +using System.IO; +using System.Linq; +using HarmonyLib; +using PowerTools; +using TheOtherRoles; +using UnityEngine; + +namespace TheOtherRoles.Modules.CustomHats.Patches; + +[HarmonyPatch(typeof(HatParent))] +internal static class HatParentPatches +{ + [HarmonyPatch(nameof(HatParent.SetHat), typeof(int))] + [HarmonyPriority(Priority.High)] + [HarmonyPrefix] + private static void SetHatPrefix(HatParent __instance) + { + SetCustomHat(__instance); + } + + [HarmonyPatch(nameof(HatParent.SetHat), typeof(HatData), typeof(int))] + [HarmonyPrefix] + private static bool SetHatPrefix(HatParent __instance, HatData hat, int color) + { + if (SetCustomHat(__instance)) return true; + __instance.PopulateFromHatViewData(); + __instance.SetMaterialColor(color); + return false; + } + + [HarmonyPatch(nameof(HatParent.SetHat), typeof(int))] + [HarmonyPrefix] + private static bool SetHatPrefix(HatParent __instance, int color) + { + if (!__instance.IsCached()) return true; + __instance.hatDataAsset = null; + __instance.PopulateFromHatViewData(); + __instance.SetMaterialColor(color); + return false; + } + + [HarmonyPatch(nameof(HatParent.UpdateMaterial))] + [HarmonyPrefix] + private static bool UpdateMaterialPrefix(HatParent __instance) + { + if (!__instance.TryGetCached(out var asset)) return true; + if (asset && asset.AltShader) + { + __instance.FrontLayer.sharedMaterial = asset.AltShader; + if (__instance.BackLayer) + { + __instance.BackLayer.sharedMaterial = asset.AltShader; + } + } + else + { + __instance.FrontLayer.sharedMaterial = DestroyableSingleton.Instance.DefaultShader; + if (__instance.BackLayer) + { + __instance.BackLayer.sharedMaterial = DestroyableSingleton.Instance.DefaultShader; + } + } + + var colorId = __instance.matProperties.ColorId; + PlayerMaterial.SetColors(colorId, __instance.FrontLayer); + if (__instance.BackLayer) + { + PlayerMaterial.SetColors(colorId, __instance.BackLayer); + } + + __instance.FrontLayer.material.SetInt(PlayerMaterial.MaskLayer, __instance.matProperties.MaskLayer); + if (__instance.BackLayer) + { + __instance.BackLayer.material.SetInt(PlayerMaterial.MaskLayer, __instance.matProperties.MaskLayer); + } + + var maskType = __instance.matProperties.MaskType; + switch (maskType) + { + case PlayerMaterial.MaskType.ScrollingUI: + if (__instance.FrontLayer) + { + __instance.FrontLayer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + } + + if (__instance.BackLayer) + { + __instance.BackLayer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + } + + break; + case PlayerMaterial.MaskType.Exile: + if (__instance.FrontLayer) + { + __instance.FrontLayer.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; + } + + if (__instance.BackLayer) + { + __instance.BackLayer.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; + } + + break; + default: + if (__instance.FrontLayer) + { + __instance.FrontLayer.maskInteraction = SpriteMaskInteraction.None; + } + + if (__instance.BackLayer) + { + __instance.BackLayer.maskInteraction = SpriteMaskInteraction.None; + } + + break; + } + + if (__instance.matProperties.MaskLayer > 0) return false; + PlayerMaterial.SetMaskLayerBasedOnLocalPlayer(__instance.FrontLayer, __instance.matProperties.IsLocalPlayer); + if (!__instance.BackLayer) return false; + PlayerMaterial.SetMaskLayerBasedOnLocalPlayer(__instance.BackLayer, __instance.matProperties.IsLocalPlayer); + + return false; + } + + [HarmonyPatch(nameof(HatParent.LateUpdate))] + [HarmonyPrefix] + private static bool LateUpdatePrefix(HatParent __instance) + { + if (!__instance.Parent || !__instance.Hat) return false; + if (!__instance.TryGetCached(out var hatViewData)) return true; + if (__instance.FrontLayer.sprite != hatViewData.ClimbImage && + __instance.FrontLayer.sprite != hatViewData.FloorImage) + { + if ((__instance.Hat.InFront || hatViewData.BackImage) && hatViewData.LeftMainImage) + { + __instance.FrontLayer.sprite = + __instance.Parent.flipX ? hatViewData.LeftMainImage : hatViewData.MainImage; + } + + if (hatViewData.BackImage && hatViewData.LeftBackImage) + { + __instance.BackLayer.sprite = + __instance.Parent.flipX ? hatViewData.LeftBackImage : hatViewData.BackImage; + return false; + } + + if (!hatViewData.BackImage && !__instance.Hat.InFront && hatViewData.LeftMainImage) + { + __instance.BackLayer.sprite = + __instance.Parent.flipX ? hatViewData.LeftMainImage : hatViewData.MainImage; + return false; + } + } + else if (__instance.FrontLayer.sprite == hatViewData.ClimbImage || + __instance.FrontLayer.sprite == hatViewData.LeftClimbImage) + { + var spriteAnimNodeSync = __instance.SpriteSyncNode != null + ? __instance.SpriteSyncNode + : __instance.GetComponent(); + if (spriteAnimNodeSync) + { + spriteAnimNodeSync.NodeId = 0; + } + } + + return false; + } + + [HarmonyPatch(nameof(HatParent.SetFloorAnim))] + [HarmonyPrefix] + private static bool SetFloorAnimPrefix(HatParent __instance) + { + if (!__instance.TryGetCached(out var hatViewData)) return true; + __instance.BackLayer.enabled = false; + __instance.FrontLayer.enabled = true; + __instance.FrontLayer.sprite = hatViewData.FloorImage; + return false; + } + + [HarmonyPatch(nameof(HatParent.SetIdleAnim))] + [HarmonyPrefix] + private static bool SetIdleAnimPrefix(HatParent __instance, int colorId) + { + if (!__instance.Hat) return false; + if (!__instance.IsCached()) return true; + __instance.hatDataAsset = null; + __instance.PopulateFromHatViewData(); + __instance.SetMaterialColor(colorId); + return false; + } + + [HarmonyPatch(nameof(HatParent.SetClimbAnim))] + [HarmonyPrefix] + private static bool SetClimbAnimPrefix(HatParent __instance) + { + if (!__instance.TryGetCached(out var hatViewData)) return true; + if (!__instance.options.ShowForClimb) return false; + __instance.BackLayer.enabled = false; + __instance.FrontLayer.enabled = true; + __instance.FrontLayer.sprite = hatViewData.ClimbImage; + return false; + } + + [HarmonyPatch(nameof(HatParent.PopulateFromHatViewData))] + [HarmonyPrefix] + private static bool PopulateFromHatViewDataPrefix(HatParent __instance) + { + if (!__instance.TryGetCached(out var asset)) return true; + __instance.UpdateMaterial(); + + var spriteAnimNodeSync = __instance.SpriteSyncNode + ? __instance.SpriteSyncNode + : __instance.GetComponent(); + if (spriteAnimNodeSync) + { + spriteAnimNodeSync.NodeId = __instance.Hat.NoBounce ? 1 : 0; + } + + if (__instance.Hat.InFront) + { + __instance.BackLayer.enabled = false; + __instance.FrontLayer.enabled = true; + __instance.FrontLayer.sprite = asset.MainImage; + } + else if (asset.BackImage) + { + __instance.BackLayer.enabled = true; + __instance.FrontLayer.enabled = true; + __instance.BackLayer.sprite = asset.BackImage; + __instance.FrontLayer.sprite = asset.MainImage; + } + else + { + __instance.BackLayer.enabled = true; + __instance.FrontLayer.enabled = false; + __instance.FrontLayer.sprite = null; + __instance.BackLayer.sprite = asset.MainImage; + } + + if (!__instance.options.Initialized || !__instance.HideHat()) return false; + __instance.FrontLayer.enabled = false; + __instance.BackLayer.enabled = false; + return false; + } + + private static bool SetCustomHat(HatParent hatParent) + { + var dirPath = Path.Combine(CustomHatManager.HatsDirectory, "Test"); + if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath); + if (!DestroyableSingleton.InstanceExists) return true; + var d = new DirectoryInfo(dirPath); + var filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); + var hats = CustomHatManager.CreateHatDetailsFromFileNames(filePaths, true); + if (hats.Count <= 0) return false; + try + { + hatParent.Hat = CustomHatManager.CreateHatBehaviour(hats[0], true); + } + catch (Exception err) + { + TheOtherRolesPlugin.Logger.LogWarning($"Unable to create test hat \n{err}"); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/Patches/HatsTabPatches.cs b/TheOtherRoles/Modules/CustomHats/Patches/HatsTabPatches.cs new file mode 100644 index 000000000..5a258e4c8 --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/Patches/HatsTabPatches.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AmongUs.Data; +using TheOtherRoles.Modules.CustomHats.Extensions; +using HarmonyLib; +using TMPro; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace TheOtherRoles.Modules.CustomHats.Patches; + +[HarmonyPatch(typeof(HatsTab))] +internal static class HatsTabPatches +{ + private static TextMeshPro textTemplate; + + [HarmonyPatch(nameof(HatsTab.OnEnable))] + [HarmonyPostfix] + private static void OnEnablePostfix(HatsTab __instance) + { + for (var i = 0; i < __instance.scroller.Inner.childCount; i++) + { + Object.Destroy(__instance.scroller.Inner.GetChild(i).gameObject); + } + + __instance.ColorChips = new Il2CppSystem.Collections.Generic.List(); + var unlockedHats = DestroyableSingleton.Instance.GetUnlockedHats(); + var packages = new Dictionary>>(); + + foreach (var hatBehaviour in unlockedHats) + { + var ext = hatBehaviour.GetHatExtension(); + if (ext != null) + { + if (!packages.ContainsKey(ext.Package)) + { + packages[ext.Package] = new List>(); + } + packages[ext.Package].Add(new Tuple(hatBehaviour, ext)); + } + else + { + if (!packages.ContainsKey(CustomHatManager.InnerslothPackageName)) + { + packages[CustomHatManager.InnerslothPackageName] = new List>(); + } + packages[CustomHatManager.InnerslothPackageName].Add(new Tuple(hatBehaviour, null)); + } + } + + var yOffset = __instance.YStart; + textTemplate = GameObject.Find("HatsGroup").transform.FindChild("Text").GetComponent(); + + var orderedKeys = packages.Keys.OrderBy(x => + x switch + { + CustomHatManager.InnerslothPackageName => 1000, + CustomHatManager.DeveloperPackageName => 0, + _ => 500 + }); + foreach (var key in orderedKeys) + { + var value = packages[key]; + yOffset = CreateHatPackage(value, key, yOffset, __instance); + } + + __instance.scroller.ContentYBounds.max = -(yOffset + 4.1f); + } + + private static float CreateHatPackage(List> hats, string packageName, float yStart, + HatsTab hatsTab) + { + var isDefaultPackage = CustomHatManager.InnerslothPackageName == packageName; + if (!isDefaultPackage) + { + hats = hats.OrderBy(x => x.Item1.name).ToList(); + } + + var offset = yStart; + if (textTemplate != null) + { + var title = Object.Instantiate(textTemplate, hatsTab.scroller.Inner); + title.transform.localPosition = new Vector3(2.25f, yStart, -1f); + title.transform.localScale = Vector3.one * 1.5f; + title.fontSize *= 0.5f; + title.enableAutoSizing = false; + hatsTab.StartCoroutine(Effects.Lerp(0.1f, new Action(p => { title.SetText(packageName); }))); + offset -= 0.8f * hatsTab.YOffset; + } + + for (var i = 0; i < hats.Count; i++) + { + var (hat, ext) = hats[i]; + var xPos = hatsTab.XRange.Lerp(i % hatsTab.NumPerRow / (hatsTab.NumPerRow - 1f)); + var yPos = offset - i / hatsTab.NumPerRow * (isDefaultPackage ? 1f : 1.5f) * hatsTab.YOffset; + var colorChip = Object.Instantiate(hatsTab.ColorTabPrefab, hatsTab.scroller.Inner); + if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) + { + colorChip.Button.OnMouseOver.AddListener((Action)(() => hatsTab.SelectHat(hat))); + colorChip.Button.OnMouseOut.AddListener((Action)(() => hatsTab.SelectHat(DestroyableSingleton.Instance.GetHatById(DataManager.Player.Customization.Hat)))); + colorChip.Button.OnClick.AddListener((Action)hatsTab.ClickEquip); + } + else + { + colorChip.Button.OnClick.AddListener((Action)(() => hatsTab.SelectHat(hat))); + } + colorChip.Button.ClickMask = hatsTab.scroller.Hitbox; + colorChip.Inner.SetMaskType(PlayerMaterial.MaskType.ScrollingUI); + hatsTab.UpdateMaterials(colorChip.Inner.FrontLayer, hat); + var background = colorChip.transform.FindChild("Background"); + var foreground = colorChip.transform.FindChild("ForeGround"); + + if (ext != null) + { + if (background != null) { + background.localPosition = Vector3.down * 0.243f; + background.localScale = new Vector3(background.localScale.x, 0.8f, background.localScale.y); + } + if (foreground != null) { + foreground.localPosition = Vector3.down * 0.243f; + } + + if (textTemplate != null) { + var description = Object.Instantiate(textTemplate, colorChip.transform); + description.transform.localPosition = new Vector3(0f, -0.65f, -1f); + description.alignment = TextAlignmentOptions.Center; + description.transform.localScale = Vector3.one * 0.65f; + hatsTab.StartCoroutine(Effects.Lerp(0.1f, new Action(p => { description.SetText($"{hat.name}\nby {ext.Author}"); }))); + } + } + + colorChip.transform.localPosition = new Vector3(xPos, yPos, -1f); + colorChip.Inner.SetHat(hat, hatsTab.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color); + colorChip.Inner.transform.localPosition = hat.ChipOffset; + colorChip.Tag = hat; + colorChip.SelectionHighlight.gameObject.SetActive(false); + hatsTab.ColorChips.Add(colorChip); + } + + return offset - (hats.Count - 1) / hatsTab.NumPerRow * (isDefaultPackage ? 1f : 1.5f) * hatsTab.YOffset - + 1.75f; + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/Patches/PlayerPhysicsPatches.cs b/TheOtherRoles/Modules/CustomHats/Patches/PlayerPhysicsPatches.cs new file mode 100644 index 000000000..e454b7083 --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/Patches/PlayerPhysicsPatches.cs @@ -0,0 +1,45 @@ +using TheOtherRoles.Modules.CustomHats.Extensions; +using HarmonyLib; + +namespace TheOtherRoles.Modules.CustomHats.Patches; + +[HarmonyPatch(typeof(PlayerPhysics))] +internal static class PlayerPhysicsPatches +{ + [HarmonyPatch(nameof(PlayerPhysics.HandleAnimation))] + [HarmonyPostfix] + private static void HandleAnimationPostfix(PlayerPhysics __instance) + { + var currentAnimation = __instance.Animations.Animator.GetCurrentAnimation(); + if (currentAnimation == __instance.Animations.group.ClimbUpAnim) return; + if (currentAnimation == __instance.Animations.group.ClimbDownAnim) return; + var hatParent = __instance.myPlayer.cosmetics.hat; + if (hatParent == null || hatParent == null) return; + if (!hatParent.TryGetCached(out var viewData)) return; + var extend = hatParent.Hat.GetHatExtension(); + if (extend == null) return; + if (extend.FlipImage != null) + { + if (__instance.FlipX) + { + hatParent.FrontLayer.sprite = extend.FlipImage; + } + else + { + hatParent.FrontLayer.sprite = viewData.MainImage; + } + } + + if (extend.BackFlipImage != null) + { + if (__instance.FlipX) + { + hatParent.BackLayer.sprite = extend.BackFlipImage; + } + else + { + hatParent.BackLayer.sprite = viewData.BackImage; + } + } + } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/SkinsConfigFile.cs b/TheOtherRoles/Modules/CustomHats/SkinsConfigFile.cs new file mode 100644 index 000000000..a840cca0d --- /dev/null +++ b/TheOtherRoles/Modules/CustomHats/SkinsConfigFile.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace TheOtherRoles.Modules.CustomHats; + +public class SkinsConfigFile +{ + [JsonPropertyName("hats")] public List Hats { get; set; } +} \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index d751e994b..b072bf5d6 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -893,6 +893,7 @@ public static void Postfix() public class AmongUsClientOnPlayerJoinedPatch { public static void Postfix() { if (PlayerControl.LocalPlayer != null && AmongUsClient.Instance.AmHost) { + GameManager.Instance.LogicOptions.SyncOptions(); CustomOption.ShareOptionSelections(); } } @@ -1129,6 +1130,89 @@ private static void Postfix(ref string __result) } } + [HarmonyPatch] + public class AddToKillDistanceSetting + { + [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.AreInvalid))] + [HarmonyPrefix] + + public static bool Prefix(GameOptionsData __instance, ref int maxExpectedPlayers) + { + //making the killdistances bound check higher since extra short is added + return __instance.MaxPlayers > maxExpectedPlayers || __instance.NumImpostors < 1 + || __instance.NumImpostors > 3 || __instance.KillDistance < 0 + || __instance.KillDistance >= GameOptionsData.KillDistances.Count + || __instance.PlayerSpeedMod <= 0f || __instance.PlayerSpeedMod > 3f; + } + + [HarmonyPatch(typeof(NormalGameOptionsV07), nameof(NormalGameOptionsV07.AreInvalid))] + [HarmonyPrefix] + + public static bool Prefix(NormalGameOptionsV07 __instance, ref int maxExpectedPlayers) + { + return __instance.MaxPlayers > maxExpectedPlayers || __instance.NumImpostors < 1 + || __instance.NumImpostors > 3 || __instance.KillDistance < 0 + || __instance.KillDistance >= GameOptionsData.KillDistances.Count + || __instance.PlayerSpeedMod <= 0f || __instance.PlayerSpeedMod > 3f; + } + + [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))] + [HarmonyPrefix] + + public static void Prefix(StringOption __instance) + { + //prevents indexoutofrange exception breaking the setting if long happens to be selected + //when host opens the laptop + if (__instance.Title == StringNames.GameKillDistance && __instance.Value == 3) { + __instance.Value = 1; + GameOptionsManager.Instance.currentNormalGameOptions.KillDistance = 1; + GameManager.Instance.LogicOptions.SyncOptions(); + } + } + + [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))] + [HarmonyPostfix] + + public static void Postfix(StringOption __instance) + { + if (__instance.Title == StringNames.GameKillDistance && __instance.Values.Count == 3) { + __instance.Values = new( + new StringNames[] { (StringNames)49999, StringNames.SettingShort, StringNames.SettingMedium, StringNames.SettingLong }); + } + } + + [HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.AppendItem), + new Type[] { typeof(Il2CppSystem.Text.StringBuilder), typeof(StringNames), typeof(string) })] + [HarmonyPrefix] + + public static void Prefix(ref StringNames stringName, ref string value) + { + if (stringName == StringNames.GameKillDistance) { + var index = GameOptionsManager.Instance.currentNormalGameOptions.KillDistance; + value = GameOptionsData.KillDistanceStrings[index]; + } + } + + [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), + new[] { typeof(StringNames), typeof(Il2CppReferenceArray) })] + [HarmonyPriority(Priority.Last)] + + public static bool Prefix(ref string __result, ref StringNames id) + { + if ((int)id == 49999) { + __result = "Very Short"; + return false; + } + return true; + } + + public static void addKillDistance() + { + GameOptionsData.KillDistances = new(new float[] { 0.5f, 1f, 1.8f, 2.5f }); + GameOptionsData.KillDistanceStrings = new(new string[] { "Very Short", "Short", "Medium", "Long" }); + } + } + [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] public static class GameOptionsNextPagePatch { diff --git a/TheOtherRoles/Modules/ModUpdater.cs b/TheOtherRoles/Modules/ModUpdater.cs index 44dda533f..65b15100f 100644 --- a/TheOtherRoles/Modules/ModUpdater.cs +++ b/TheOtherRoles/Modules/ModUpdater.cs @@ -1,91 +1,164 @@ -using System; +using System; using System.Collections; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Threading.Tasks; -using AmongUs.Data; -using Assets.InnerNet; +using System.Text.Json; +using System.Text.Json.Serialization; using BepInEx; -using BepInEx.Bootstrap; -using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP.Utils; -using Mono.Cecil; -using Newtonsoft.Json.Linq; -using TMPro; -using Twitch; using UnityEngine; -using UnityEngine.SceneManagement; +using UnityEngine.Networking; using UnityEngine.UI; -using Action = System.Action; -using IntPtr = System.IntPtr; -using Version = SemanticVersioning.Version; - -namespace TheOtherRoles.Modules -{ - public class ModUpdateBehaviour : MonoBehaviour - { - public static readonly bool CheckForSubmergedUpdates = true; - public static bool showPopUp = true; - public static bool updateInProgress = false; - - public static ModUpdateBehaviour Instance { get; private set; } - public ModUpdateBehaviour(IntPtr ptr) : base(ptr) { } - public class UpdateData - { - public string Content; - public string Tag; - public string TimeString; - public JObject Request; - public Version Version => Version.Parse(Tag); - - public UpdateData(JObject data) - { - Tag = data["tag_name"]?.ToString().TrimStart('v'); - Content = data["body"]?.ToString(); - TimeString = DateTime.FromBinary(((Il2CppSystem.DateTime)data["published_at"]).ToBinaryRaw()).ToString(); - Request = data; - } +using UnityEngine.SceneManagement; +using AmongUs.Data; +using Assets.InnerNet; +using Twitch; +using static StarGen; - public bool IsNewer(Version version) - { - if (!Version.TryParse(Tag, out var myVersion)) return false; - return myVersion.BaseVersion() > version.BaseVersion(); +namespace TheOtherRoles.Modules { + public class ModUpdater : MonoBehaviour { + public const string RepositoryOwner = "TheOtherRolesAU"; + public const string RepositoryName = "TheOtherRoles"; + public static ModUpdater Instance { get; private set; } + + public ModUpdater(IntPtr ptr) : base(ptr) { } + + private bool _busy; + private bool showPopUp = true; + public List Releases; + + public void Awake() { + if (Instance) Destroy(Instance); + Instance = this; + foreach (var file in Directory.GetFiles(Paths.PluginPath, "*.old")) { + File.Delete(file); } } - public UpdateData TORUpdate; - public UpdateData SubmergedUpdate; - - [HideFromIl2Cpp] - public UpdateData RequiredUpdateData => TORUpdate ?? SubmergedUpdate; + private void Start() { + if (_busy) return; + this.StartCoroutine(CoCheckForUpdate()); + SceneManager.add_sceneLoaded((System.Action)(OnSceneLoaded)); + } - public void Awake() - { - if (Instance) Destroy(this); - Instance = this; - SceneManager.add_sceneLoaded((System.Action) (OnSceneLoaded)); - this.StartCoroutine(CoCheckUpdates()); + [HideFromIl2Cpp] + public void StartDownloadRelease(GithubRelease release) { + if (_busy) return; + this.StartCoroutine(CoDownloadRelease(release)); + } + + [HideFromIl2Cpp] + private IEnumerator CoCheckForUpdate() { + _busy = true; + var www = new UnityWebRequest(); + www.SetMethod(UnityWebRequest.UnityWebRequestMethod.Get); + www.SetUrl($"https://api.github.com/repos/{RepositoryOwner}/{RepositoryName}/releases"); + www.downloadHandler = new DownloadHandlerBuffer(); + var operation = www.SendWebRequest(); + + while (!operation.isDone) { + yield return new WaitForEndOfFrame(); + } + + if (www.isNetworkError || www.isHttpError) { + yield break; + } + + Releases = JsonSerializer.Deserialize>(www.downloadHandler.text); + www.downloadHandler.Dispose(); + www.Dispose(); + Releases.Sort(SortReleases); + _busy = false; + } + + [HideFromIl2Cpp] + private IEnumerator CoDownloadRelease(GithubRelease release) { + _busy = true; + + var popup = Instantiate(TwitchManager.Instance.TwitchPopup); + popup.TextAreaTMP.fontSize *= 0.7f; + popup.TextAreaTMP.enableAutoSizing = false; + + popup.Show(); + + var button = popup.transform.GetChild(2).gameObject; + button.SetActive(false); + popup.TextAreaTMP.text = $"Updating TOR\nPlease wait..."; + + var asset = release.Assets.Find(FilterPluginAsset); + var www = new UnityWebRequest(); + www.SetMethod(UnityWebRequest.UnityWebRequestMethod.Get); + www.SetUrl(asset.DownloadUrl); + www.downloadHandler = new DownloadHandlerBuffer(); + var operation = www.SendWebRequest(); + + while (!operation.isDone) { + int stars = Mathf.CeilToInt(www.downloadProgress * 10); + string progress = $"Updating TOR\nPlease wait...\nDownloading...\n{new String((char)0x25A0, stars) + new String((char)0x25A1, 10 - stars)}"; + popup.TextAreaTMP.text = progress; + yield return new WaitForEndOfFrame(); + } - foreach (var file in Directory.GetFiles(Paths.PluginPath, "*.old")) - { - File.Delete(file); + if (www.isNetworkError || www.isHttpError) { + popup.TextAreaTMP.text = "Update wasn't successful\nTry again later,\nor update manually."; + yield break; } + popup.TextAreaTMP.text = $"Updating TOR\nPlease wait...\n\nDownload complete\ncopying file..."; + + var filePath = Path.Combine(Paths.PluginPath, asset.Name); + + if (File.Exists(filePath + ".old")) File.Delete(filePath + "old"); + if (File.Exists(filePath)) File.Move(filePath, filePath + ".old"); + + var persistTask = File.WriteAllBytesAsync(filePath, www.downloadHandler.data); + var hasError = false; + while (!persistTask.IsCompleted) { + if (persistTask.Exception != null) { + hasError = true; + break; + } + + yield return new WaitForEndOfFrame(); + } + + www.downloadHandler.Dispose(); + www.Dispose(); + + if (!hasError) { + popup.TextAreaTMP.text = $"TheOtherRoles\nupdated successfully\nPlease restart the game."; + } + button.SetActive(true); + _busy = false; + } + + [HideFromIl2Cpp] + private static bool FilterLatestRelease(GithubRelease release) { + return release.IsNewer(TheOtherRolesPlugin.Version) && release.Assets.Any(FilterPluginAsset); + } + + [HideFromIl2Cpp] + private static bool FilterPluginAsset(GithubAsset asset) { + return asset.Name == "TheOtherRoles.dll"; } - private void OnSceneLoaded(Scene scene, LoadSceneMode mode) - { - if (updateInProgress || scene.name != "MainMenu") return; - if (RequiredUpdateData is null) { - showPopUp = false; + [HideFromIl2Cpp] + private static int SortReleases(GithubRelease a, GithubRelease b) { + if (a.IsNewer(b.Version)) return -1; + if (b.IsNewer(a.Version)) return 1; + return 0; + } + + private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { + if (_busy || scene.name != "MainMenu") return; + var latestRelease = Releases.FirstOrDefault(); + if (latestRelease == null || latestRelease.Version <= TheOtherRolesPlugin.Version) return; - } var template = GameObject.Find("ExitGameButton"); if (!template) return; - + var button = Instantiate(template, null); var buttonTransform = button.transform; //buttonTransform.localPosition = new Vector3(-2f, -2f); @@ -93,74 +166,26 @@ private void OnSceneLoaded(Scene scene, LoadSceneMode mode) PassiveButton passiveButton = button.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.OnClick.AddListener((Action) (() => + passiveButton.OnClick.AddListener((Action)(() => { - this.StartCoroutine(CoUpdate()); + StartDownloadRelease(latestRelease); button.SetActive(false); })); var text = button.transform.GetComponentInChildren(); string t = "Update TOR"; - if (TORUpdate is null && SubmergedUpdate is not null) t = SubmergedCompatibility.Loaded ? $"Update\nSubmerged" : $"Download\nSubmerged"; - StartCoroutine(Effects.Lerp(0.1f, (System.Action)(p => text.SetText(t)))); passiveButton.OnMouseOut.AddListener((Action)(() => text.color = Color.red)); passiveButton.OnMouseOver.AddListener((Action)(() => text.color = Color.white)); - - var isSubmerged = TORUpdate == null; - var announcement = $"A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}"; + var announcement = $"A new THE OTHER ROLES update to {latestRelease.Tag} is available\n{latestRelease.Description}"; var mgr = FindObjectOfType(true); - - if (!isSubmerged) { - try { - string updateVersion = TORUpdate.Content[^5..]; - if (Version.Parse(TheOtherRolesPlugin.VersionString).BaseVersion() < Version.Parse(updateVersion).BaseVersion()) { - passiveButton.OnClick.RemoveAllListeners(); - passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.OnClick.AddListener((Action)(() => { - mgr.StartCoroutine(CoShowAnnouncement($"A MANUAL UPDATE IS REQUIRED")); - })); - } - } catch { - TheOtherRolesPlugin.Logger.LogError("parsing version for auto updater failed :("); - } - - } - - if (isSubmerged && !SubmergedCompatibility.Loaded) showPopUp = false; - if (showPopUp) mgr.StartCoroutine(CoShowAnnouncement(announcement, shortTitle: isSubmerged ? "Submerged Update" : "TOR Update", date: isSubmerged ? SubmergedUpdate.TimeString : TORUpdate.TimeString)); + if (showPopUp) mgr.StartCoroutine(CoShowAnnouncement(announcement, shortTitle: "TOR Update", date : latestRelease.PublishedAt)) ; showPopUp = false; - } - - [HideFromIl2Cpp] - public IEnumerator CoUpdate() - { - updateInProgress = true; - var isSubmerged = TORUpdate is null; - var updateName = (isSubmerged ? "Submerged" : "The Other Roles"); - - var popup = Instantiate(TwitchManager.Instance.TwitchPopup); - popup.TextAreaTMP.fontSize *= 0.7f; - popup.TextAreaTMP.enableAutoSizing = false; - - popup.Show(); - var button = popup.transform.GetChild(2).gameObject; - button.SetActive(false); - popup.TextAreaTMP.text = $"Updating {updateName}\nPlease wait..."; - - var download = Task.Run(DownloadUpdate); - while (!download.IsCompleted) yield return null; - - button.SetActive(true); - popup.TextAreaTMP.text = download.Result ? $"{updateName}\nupdated successfully\nPlease restart the game." : "Update wasn't successful\nTry again later,\nor update manually."; } - - private static int announcementNumber = 501; [HideFromIl2Cpp] - public IEnumerator CoShowAnnouncement(string announcement, bool show=true, string shortTitle="TOR Update", string title="", string date="") - { + public IEnumerator CoShowAnnouncement(string announcement, bool show = true, string shortTitle = "TOR Update", string title = "", string date = "") { var mgr = FindObjectOfType(true); var popUpTemplate = UnityEngine.Object.FindObjectOfType(true); if (popUpTemplate == null) { @@ -169,12 +194,12 @@ public IEnumerator CoShowAnnouncement(string announcement, bool show=true, strin } var popUp = UnityEngine.Object.Instantiate(popUpTemplate); - popUp.gameObject.SetActive(true); + popUp.gameObject.SetActive(true); Assets.InnerNet.Announcement creditsAnnouncement = new() { Id = "torAnnouncement", Language = 0, - Number = announcementNumber++, + Number = 6969, Title = title == "" ? "The Other Roles Announcement" : title, ShortTitle = shortTitle, SubTitle = "", @@ -195,112 +220,57 @@ public IEnumerator CoShowAnnouncement(string announcement, bool show=true, strin } }))); } + } - [HideFromIl2Cpp] - public static IEnumerator CoCheckUpdates() - { - // Since running the update check task causes a crash for some users, allow the user to disable the updater by creating a file called noupdater.txt - // in their Among Us folder (root) - if (System.IO.File.Exists(System.IO.Directory.GetCurrentDirectory() + "\\noupdater.txt")) yield break; - var torUpdateCheck = Task.Run(() => Instance.GetGithubUpdate("Eisbison", "TheOtherRoles")); - while (!torUpdateCheck.IsCompleted) yield return null; - if (torUpdateCheck.Result != null && torUpdateCheck.Result.IsNewer(Version.Parse(TheOtherRolesPlugin.VersionString))) - { - Instance.TORUpdate = torUpdateCheck.Result; - } - if (CheckForSubmergedUpdates) - { - var submergedUpdateCheck = Task.Run(() => Instance.GetGithubUpdate("SubmergedAmongUs", "Submerged")); - while (!submergedUpdateCheck.IsCompleted) yield return null; - if (submergedUpdateCheck.Result != null && (!SubmergedCompatibility.Loaded || submergedUpdateCheck.Result.IsNewer(SubmergedCompatibility.Version))) - { - Instance.SubmergedUpdate = submergedUpdateCheck.Result; - if (Instance.SubmergedUpdate.Tag.Equals("2022.10.26") || IL2CPPChainloader.Instance.Plugins.TryGetValue("com.DigiWorm.LevelImposter", out PluginInfo _)) Instance.SubmergedUpdate = null; - } - } - Instance.OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single); - } + public class GithubRelease { + [JsonPropertyName("id")] + public int Id { get; set; } - [HideFromIl2Cpp] - public async Task GetGithubUpdate(string owner, string repo) - { - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); - try { - var req = await client.GetAsync($"https://api.github.com/repos/{owner}/{repo}/releases/latest", HttpCompletionOption.ResponseContentRead); - if (!req.IsSuccessStatusCode) return null; - - var dataString = await req.Content.ReadAsStringAsync(); - JObject data = JObject.Parse(dataString); - return new UpdateData(data); - } catch (HttpRequestException) { - return null; - } - } + [JsonPropertyName("tag_name")] + public string Tag { get; set; } - private bool TryUpdateSubmergedInternally() - { - if (SubmergedUpdate == null) return false; - try - { - if (!SubmergedCompatibility.LoadedExternally) return false; - var thisAsm = Assembly.GetCallingAssembly(); - var resourceName = thisAsm.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith("Submerged.dll")); - if (resourceName == default) return false; - - using var submergedStream = thisAsm.GetManifestResourceStream(resourceName)!; - var asmDef = AssemblyDefinition.ReadAssembly(submergedStream, TypeLoader.ReaderParameters); - var pluginType = asmDef.MainModule.Types.FirstOrDefault(t => t.IsSubtypeOf(typeof(BasePlugin))); - var info = IL2CPPChainloader.ToPluginInfo(pluginType, ""); - if (SubmergedUpdate.IsNewer(info.Metadata.Version)) return false; - File.Delete(SubmergedCompatibility.Assembly.Location); + [JsonPropertyName("name")] + public string Name { get; set; } - } - catch (Exception e) - { - TheOtherRolesPlugin.Logger.LogError(e); - return false; - } - return true; - } - - - [HideFromIl2Cpp] - public async Task DownloadUpdate() - { - var isSubmerged = TORUpdate is null; - if (isSubmerged && TryUpdateSubmergedInternally()) return true; - var data = isSubmerged ? SubmergedUpdate : TORUpdate; - - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); - - JToken assets = data.Request["assets"]; - string downloadURI = ""; - for (JToken current = assets.First; current != null; current = current.Next) - { - string browser_download_url = current["browser_download_url"]?.ToString(); - if (browser_download_url != null && current["content_type"] != null) { - if (current["content_type"].ToString().Equals("application/x-msdownload") && - browser_download_url.EndsWith(".dll")) { - downloadURI = browser_download_url; - break; - } - } - } + [JsonPropertyName("draft")] + public bool Draft { get; set; } - if (downloadURI.Length == 0) return false; + [JsonPropertyName("prerelease")] + public bool Prerelease { get; set; } - var res = await client.GetAsync(downloadURI, HttpCompletionOption.ResponseContentRead); - string filePath = Path.Combine(Paths.PluginPath, isSubmerged ? "Submerged.dll" : "TheOtherRoles.dll"); - if (File.Exists(filePath + ".old")) File.Delete(filePath + ".old"); - if (File.Exists(filePath)) File.Move(filePath, filePath + ".old"); + [JsonPropertyName("created_at")] + public string CreatedAt { get; set; } + + [JsonPropertyName("published_at")] + public string PublishedAt { get; set; } + + [JsonPropertyName("body")] + public string Description { get; set; } - await using var responseStream = await res.Content.ReadAsStreamAsync(); - await using var fileStream = File.Create(filePath); - await responseStream.CopyToAsync(fileStream); + [JsonPropertyName("assets")] + public List Assets { get; set; } - return true; + public Version Version => Version.Parse(Tag.Replace("v", string.Empty)); + + public bool IsNewer(Version version) { + return Version > version; } } + + public class GithubAsset { + [JsonPropertyName("url")] + public string Url { get; set; } + + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("size")] + public int Size { get; set; } + + [JsonPropertyName("browser_download_url")] + public string DownloadUrl { get; set; } + } } diff --git a/TheOtherRoles/Objects/Bloodytrail.cs b/TheOtherRoles/Objects/Bloodytrail.cs index 6b9ec645e..b3af38f36 100644 --- a/TheOtherRoles/Objects/Bloodytrail.cs +++ b/TheOtherRoles/Objects/Bloodytrail.cs @@ -46,7 +46,7 @@ public Bloodytrail(PlayerControl player, PlayerControl bloodyPlayer) { FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(10f, new Action((p) => { Color c = color; - if (Camouflager.camouflageTimer > 0) c = Palette.PlayerColors[6]; + if (Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive()) c = Palette.PlayerColors[6]; if (spriteRenderer) spriteRenderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01(1 - p)); if (p == 1f && blood != null) { diff --git a/TheOtherRoles/Objects/Footprint.cs b/TheOtherRoles/Objects/Footprint.cs index c3aa892c3..8d8138e61 100644 --- a/TheOtherRoles/Objects/Footprint.cs +++ b/TheOtherRoles/Objects/Footprint.cs @@ -86,7 +86,7 @@ private void Update() } Color color; - if (AnonymousFootprints || Camouflager.camouflageTimer > 0) + if (AnonymousFootprints || Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive()) { color = Palette.PlayerColors[6]; } diff --git a/TheOtherRoles/Objects/JackInTheBox.cs b/TheOtherRoles/Objects/JackInTheBox.cs index 152513d1e..e44ba2836 100644 --- a/TheOtherRoles/Objects/JackInTheBox.cs +++ b/TheOtherRoles/Objects/JackInTheBox.cs @@ -4,6 +4,7 @@ using System.Linq; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using Reactor.Utilities.Extensions; namespace TheOtherRoles.Objects { @@ -36,6 +37,7 @@ public static void startAnimation(int ventId) { private GameObject gameObject; public Vent vent; private SpriteRenderer boxRenderer; + private SpriteRenderer ventRenderer; public JackInTheBox(Vector2 p) { gameObject = new GameObject("JackInTheBox"){layer = 11}; @@ -46,6 +48,7 @@ public JackInTheBox(Vector2 p) { gameObject.transform.position = position; boxRenderer = gameObject.AddComponent(); boxRenderer.sprite = getBoxAnimationSprite(0); + boxRenderer.color = boxRenderer.color.SetAlpha(0.5f); // Create the vent var referenceVent = UnityEngine.Object.FindObjectOfType(); @@ -60,7 +63,13 @@ public JackInTheBox(Vector2 p) { vent.Offset = new Vector3(0f, 0.25f, 0f); vent.GetComponent()?.Stop(); vent.Id = MapUtilities.CachedShipStatus.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id - var ventRenderer = vent.GetComponent(); + ventRenderer = vent.GetComponent(); + if (Helpers.isFungle()) { + ventRenderer = vent.transform.GetChild(3).GetComponent(); + var animator = vent.transform.GetChild(3).GetComponent(); + animator?.Stop(); + } + //ventRenderer.Destroy(); ventRenderer.sprite = null; // Use the box.boxRenderer instead vent.myRend = ventRenderer; var allVentsList = MapUtilities.CachedShipStatus.AllVents.ToList(); @@ -69,9 +78,9 @@ public JackInTheBox(Vector2 p) { vent.gameObject.SetActive(false); vent.name = "JackInTheBoxVent_" + vent.Id; - // Only render the box for the Trickster - var playerIsTrickster = CachedPlayer.LocalPlayer.PlayerControl == Trickster.trickster; - gameObject.SetActive(playerIsTrickster); + // Only render the box for the Trickster and for Ghosts + var showBoxToLocalPlayer = CachedPlayer.LocalPlayer.PlayerControl == Trickster.trickster || PlayerControl.LocalPlayer.Data.IsDead; + gameObject.SetActive(showBoxToLocalPlayer); AllJackInTheBoxes.Add(this); } @@ -79,14 +88,16 @@ public JackInTheBox(Vector2 p) { public static void UpdateStates() { if (boxesConvertedToVents == true) return; foreach (var box in AllJackInTheBoxes) { - var playerIsTrickster = CachedPlayer.LocalPlayer.PlayerControl == Trickster.trickster; - box.gameObject.SetActive(playerIsTrickster); + var showBoxToLocalPlayer = CachedPlayer.LocalPlayer.PlayerControl == Trickster.trickster || PlayerControl.LocalPlayer.Data.IsDead; + box.gameObject.SetActive(showBoxToLocalPlayer); } } public void convertToVent() { gameObject.SetActive(true); vent.gameObject.SetActive(true); + boxRenderer.color = boxRenderer.color.SetAlpha(1f); + ventRenderer.sprite = null; return; } diff --git a/TheOtherRoles/Objects/Portal.cs b/TheOtherRoles/Objects/Portal.cs index d68e9af4e..a512a322a 100644 --- a/TheOtherRoles/Objects/Portal.cs +++ b/TheOtherRoles/Objects/Portal.cs @@ -52,7 +52,7 @@ public static void startTeleport(byte playerId, byte exit) { int colorId = playerControl.Data.DefaultOutfit.ColorId; - if (Camouflager.camouflageTimer > 0) { + if (Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive()) { playerNameDisplay = "A camouflaged player"; colorId = 6; } diff --git a/TheOtherRoles/Patches/AirShipSetAntiTpPosition.cs b/TheOtherRoles/Patches/AirShipSetAntiTpPosition.cs deleted file mode 100644 index fcfd22d1c..000000000 --- a/TheOtherRoles/Patches/AirShipSetAntiTpPosition.cs +++ /dev/null @@ -1,21 +0,0 @@ -using HarmonyLib; -using System; - -namespace TheOtherRoles.Patches { - [HarmonyPatch] - public static class AirShipSetAntiTpPosition { - - // Save the position of the player prior to starting the climb / gap platform - [HarmonyPrefix] - [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.ClimbLadder))] - public static void prefix() { - AntiTeleport.position = Players.CachedPlayer.LocalPlayer.transform.position; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MovingPlatformBehaviour), nameof(MovingPlatformBehaviour.UsePlatform))] - public static void prefix2() { - AntiTeleport.position = Players.CachedPlayer.LocalPlayer.transform.position; - } - } -} diff --git a/TheOtherRoles/Patches/ClientOptionsPatch.cs b/TheOtherRoles/Patches/ClientOptionsPatch.cs index 132929b2d..f212c5cf8 100644 --- a/TheOtherRoles/Patches/ClientOptionsPatch.cs +++ b/TheOtherRoles/Patches/ClientOptionsPatch.cs @@ -13,14 +13,15 @@ namespace TheOtherRoles.Patches [HarmonyPatch] public static class ClientOptionsPatch { - private static SelectionBehaviour[] AllOptions = { - new SelectionBehaviour("Ghosts See Tasks & Other Info", () => TORMapOptions.ghostsSeeInformation = TheOtherRolesPlugin.GhostsSeeInformation.Value = !TheOtherRolesPlugin.GhostsSeeInformation.Value, TheOtherRolesPlugin.GhostsSeeInformation.Value), - new SelectionBehaviour("Ghosts Can See Votes", () => TORMapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value, TheOtherRolesPlugin.GhostsSeeVotes.Value), - new SelectionBehaviour("Ghosts Can See Roles", () => TORMapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value, TheOtherRolesPlugin.GhostsSeeRoles.Value), - new SelectionBehaviour("Ghosts Can Additionally See Modifier", () => TORMapOptions.ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value = !TheOtherRolesPlugin.GhostsSeeModifier.Value, TheOtherRolesPlugin.GhostsSeeModifier.Value), - new SelectionBehaviour("Show Role Summary", () => TORMapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value, TheOtherRolesPlugin.ShowRoleSummary.Value), - new SelectionBehaviour("Show Lighter / Darker", () => TORMapOptions.showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value = !TheOtherRolesPlugin.ShowLighterDarker.Value, TheOtherRolesPlugin.ShowLighterDarker.Value), - new SelectionBehaviour("Enable Sound Effects", () => TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value, TheOtherRolesPlugin.EnableSoundEffects.Value), + private static readonly SelectionBehaviour[] AllOptions = { + new("Ghosts See Tasks & Other Info", () => TORMapOptions.ghostsSeeInformation = TheOtherRolesPlugin.GhostsSeeInformation.Value = !TheOtherRolesPlugin.GhostsSeeInformation.Value, TheOtherRolesPlugin.GhostsSeeInformation.Value), + new("Ghosts Can See Votes", () => TORMapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value, TheOtherRolesPlugin.GhostsSeeVotes.Value), + new("Ghosts Can See Roles", () => TORMapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value, TheOtherRolesPlugin.GhostsSeeRoles.Value), + new("Ghosts Can Additionally See Modifier", () => TORMapOptions.ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value = !TheOtherRolesPlugin.GhostsSeeModifier.Value, TheOtherRolesPlugin.GhostsSeeModifier.Value), + new("Show Role Summary", () => TORMapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value, TheOtherRolesPlugin.ShowRoleSummary.Value), + new("Show Lighter / Darker", () => TORMapOptions.showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value = !TheOtherRolesPlugin.ShowLighterDarker.Value, TheOtherRolesPlugin.ShowLighterDarker.Value), + new("Enable Sound Effects", () => TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value, TheOtherRolesPlugin.EnableSoundEffects.Value), + new("Show Vents On Map", () => TORMapOptions.ShowVentsOnMap = TheOtherRolesPlugin.ShowVentsOnMap.Value = !TheOtherRolesPlugin.ShowVentsOnMap.Value, TheOtherRolesPlugin.ShowVentsOnMap.Value), }; private static GameObject popUp; @@ -49,12 +50,11 @@ public static void MainMenuManager_StartPostfix(MainMenuManager __instance) public static void OptionsMenuBehaviour_StartPostfix(OptionsMenuBehaviour __instance) { if (!__instance.CensorChatButton) return; - + if (!popUp) { CreateCustom(__instance); } - if (!buttonPrefab) { buttonPrefab = Object.Instantiate(__instance.CensorChatButton); @@ -108,12 +108,14 @@ private static void InitializeMoreButton(OptionsMenuBehaviour __instance) moreOptionsButton.OnClick = new ButtonClickedEvent(); moreOptionsButton.OnClick.AddListener((Action) (() => { + bool closeUnderlying = false; if (!popUp) return; if (__instance.transform.parent && __instance.transform.parent == FastDestroyableSingleton.Instance.transform) { popUp.transform.SetParent(FastDestroyableSingleton.Instance.transform); popUp.transform.localPosition = new Vector3(0, 0, -800f); + closeUnderlying = true; } else { @@ -123,6 +125,8 @@ private static void InitializeMoreButton(OptionsMenuBehaviour __instance) CheckSetTitle(); RefreshOpen(); + if (closeUnderlying) + __instance.Close(); })); } diff --git a/TheOtherRoles/Patches/ConstantsPatch.cs b/TheOtherRoles/Patches/ConstantsPatch.cs index b310363c6..c9a5fc3fb 100644 --- a/TheOtherRoles/Patches/ConstantsPatch.cs +++ b/TheOtherRoles/Patches/ConstantsPatch.cs @@ -1,11 +1,11 @@ -using HarmonyLib; +using HarmonyLib; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(Constants), nameof(Constants.GetBroadcastVersion))] public static class ConstantsPatch { public static void Postfix(ref int __result) { if (AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame) - __result = Constants.GetVersion(2222, 0, 0, 0); + __result += 25; } } -} +} \ No newline at end of file diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index 28b13aeb9..9f8bcccc4 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -25,7 +25,7 @@ public static class CredentialsPatch { Design by Bavari"; public static string contributorsCredentials = -$@" Special thanks to K3ndo & Smeggy"; +$@" Special thanks to Smeggy"; [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] internal static class PingTrackerPatch diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index 5a88ba2db..8a1c2fd57 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -262,13 +262,13 @@ public static void Postfix(EndGameManager __instance) { float num7 = Mathf.Lerp(1f, 0.65f, num4) * 0.9f; Vector3 vector = new Vector3(num7, num7, 1f); poolablePlayer.transform.localScale = vector; - poolablePlayer.UpdateFromPlayerOutfit((GameData.PlayerOutfit) winningPlayerData2, PlayerMaterial.MaskType.ComplexUI, winningPlayerData2.IsDead, true); if (winningPlayerData2.IsDead) { - poolablePlayer.cosmetics.currentBodySprite.BodySprite.sprite = poolablePlayer.cosmetics.currentBodySprite.GhostSprite; + poolablePlayer.SetBodyAsGhost(); poolablePlayer.SetDeadFlipX(i % 2 == 0); } else { poolablePlayer.SetFlipX(i % 2 == 0); } + poolablePlayer.UpdateFromPlayerOutfit(winningPlayerData2, PlayerMaterial.MaskType.None, winningPlayerData2.IsDead, true); poolablePlayer.cosmetics.nameText.color = Color.white; poolablePlayer.cosmetics.nameText.transform.localScale = new Vector3(1f / vector.x, 1f / vector.y, 1f / vector.z); diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index e2ad90a33..63a4ef52a 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -62,8 +62,7 @@ public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref Ga if ((witchDiesWithExiledLover || exiledIsWitch) && Witch.witchVoteSavesTargets) Witch.futureSpelled = new List(); foreach (PlayerControl target in Witch.futureSpelled) { - if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill) - { + if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill){ if (exiled != null && Lawyer.lawyer != null && (target == Lawyer.lawyer || target == Lovers.otherLover(Lawyer.lawyer)) && Lawyer.target != null && Lawyer.isProsecutor && Lawyer.target.PlayerId == exiled.PlayerId) continue; if (target == Lawyer.target && Lawyer.lawyer != null) { @@ -101,13 +100,20 @@ public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref Ga TORMapOptions.camerasToAdd = new List(); foreach (Vent vent in TORMapOptions.ventsToSeal) { - PowerTools.SpriteAnim animator = vent.GetComponent(); - animator?.Stop(); + PowerTools.SpriteAnim animator = vent.GetComponent(); vent.EnterVentAnim = vent.ExitVentAnim = null; - vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + Sprite newSprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + SpriteRenderer rend = vent.myRend; + if (Helpers.isFungle()) { + newSprite = SecurityGuard.getFungleVentSealedSprite(); + rend = vent.transform.GetChild(3).GetComponent(); + animator = vent.transform.GetChild(3).GetComponent(); + } + animator?.Stop(); + rend.sprite = newSprite; if (SubmergedCompatibility.IsSubmerged && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite(); if (SubmergedCompatibility.IsSubmerged && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite(); - vent.myRend.color = Color.white; + rend.color = Color.white; vent.name = "SealedVent_" + vent.name; } TORMapOptions.ventsToSeal = new List(); @@ -136,6 +142,13 @@ public static void Postfix(AirshipExileController __instance) { // Workaround to add a "postfix" to the destroying of the exile controller (i.e. cutscene) and SpwanInMinigame of submerged [HarmonyPatch(typeof(UnityEngine.Object), nameof(UnityEngine.Object.Destroy), new Type[] { typeof(GameObject) })] public static void Prefix(GameObject obj) { + // Nightvision: + if (obj != null && obj.name != null && obj.name.Contains("FungleSecurity")) { + SurveillanceMinigamePatch.resetNightVision(); + return; + } + + // submerged if (!SubmergedCompatibility.IsSubmerged) return; if (obj.name.Contains("ExileCutscene")) { WrapUpPostfix(ExileControllerBeginPatch.lastExiled); diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index a49d03a8c..c6b257626 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -50,6 +50,7 @@ public class GameStartManagerUpdatePatch { public static void Prefix(GameStartManager __instance) { if (!GameData.Instance ) return; // No instance + __instance.MinPlayers = 1; update = GameData.Instance.PlayerCount != __instance.LastPlayerCount; } @@ -187,7 +188,7 @@ public static bool Prefix(GameStartManager __instance) { break; } } - if ((continueStart && TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt) && GameOptionsManager.Instance.CurrentGameOptions.MapId != 6) { + if (continueStart && (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt) && GameOptionsManager.Instance.CurrentGameOptions.MapId != 6) { byte mapId = 0; if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) mapId = (byte)CustomOptionHolder.hideNSeekMap.getSelection(); else if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) mapId = (byte)CustomOptionHolder.propHuntMap.getSelection(); @@ -210,6 +211,7 @@ public static bool Prefix(GameStartManager __instance) { probabilities.Add(CustomOptionHolder.dynamicMapEnableMira.getSelection() / 10f); probabilities.Add(CustomOptionHolder.dynamicMapEnablePolus.getSelection() / 10f); probabilities.Add(CustomOptionHolder.dynamicMapEnableAirShip.getSelection() / 10f); + probabilities.Add(CustomOptionHolder.dynamicMapEnableFungle.getSelection() / 10f); probabilities.Add(CustomOptionHolder.dynamicMapEnableSubmerged.getSelection() / 10f); // if any map is at 100%, remove all maps that are not! diff --git a/TheOtherRoles/Patches/MainMenuPatch.cs b/TheOtherRoles/Patches/MainMenuPatch.cs index e1f8951eb..8dbdc85bc 100644 --- a/TheOtherRoles/Patches/MainMenuPatch.cs +++ b/TheOtherRoles/Patches/MainMenuPatch.cs @@ -21,7 +21,6 @@ public class MainMenuPatch { private static AnnouncementPopUp popUp; private static void Prefix(MainMenuManager __instance) { - CustomHatLoader.LaunchHatFetcher(); var template = GameObject.Find("ExitGameButton"); var template2 = GameObject.Find("CreditsButton"); if (template == null || template2 == null) return; @@ -78,18 +77,27 @@ private static void Prefix(MainMenuManager __instance) { popUp = Object.Instantiate(popUpTemplate); popUp.gameObject.SetActive(true); - string creditsString = @$"Github Contributors: + string creditsString = @$"Team: +Mallöris K3ndo Bavari Gendelo + +Former Team Members: +Eisbison (GOAT) Thunderstorm584 EndOfFile + +Additional Devs: +EnoPM twix NesTT + +Github Contributors: Alex2911 amsyarasyiq MaximeGillot Psynomit probablyadnf JustASysAdmin -[https://discord.gg/77RkMJHWsM]Discord[] Moderators: -Streamblox Draco Cordraconis +[https://discord.gg/77RkMJHWsM]Discord[] Moderators: +Draco Cordraconis Streamblox (formerly) Thanks to all our discord helpers! -Thanks to miniduikboot & GD for hosting modded servers +Thanks to miniduikboot & GD for hosting modded servers (and so much more) "; - creditsString += $@" Other Credits & Resources: + creditsString += $@" Other Credits & Resources: OxygenFilter - For the versions v2.3.0 to v2.6.1, we were using the OxygenFilter for automatic deobfuscation Reactor - The framework used for all versions before v2.0.0, and again since 4.2.0 BepInEx - Used to hook game functions @@ -130,7 +138,7 @@ Thanks to all our discord helpers! DataManager.Player.Announcements.SetAnnouncements(new Announcement[] { creditsAnnouncement }); popUp.CreateAnnouncementList(); popUp.UpdateAnnouncementText(creditsAnnouncement.Number); - popUp.visibleAnnouncements[0].PassiveButton.OnClick.RemoveAllListeners(); + popUp.visibleAnnouncements._items[0].PassiveButton.OnClick.RemoveAllListeners(); DataManager.Player.Announcements.allAnnouncements = backup; } }))); diff --git a/TheOtherRoles/Patches/MapBehaviourPatch.cs b/TheOtherRoles/Patches/MapBehaviourPatch.cs index 4825fd475..8456f7d09 100644 --- a/TheOtherRoles/Patches/MapBehaviourPatch.cs +++ b/TheOtherRoles/Patches/MapBehaviourPatch.cs @@ -12,18 +12,36 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(MapBehaviour))] - class MapBehaviourPatch { - public static Dictionary herePoints = new Dictionary(); + static class MapBehaviourPatch { + public static Dictionary herePoints = new(); + + public static Sprite Vent = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Vent.png", 150f); + + public static List> VentNetworks = new(); + + public static Dictionary mapIcons = new(); + + public static void clearAndReload() { + foreach (var mapIcon in mapIcons.Values) { + mapIcon.Destroy(); + } + mapIcons = new(); + VentNetworks = new(); + herePoints = new(); + + } + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.FixedUpdate))] static void Postfix(MapBehaviour __instance) { + __instance.HerePoint.transform.SetLocalZ(-2.1f); if (Trapper.trapper != null && CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) { foreach (PlayerControl player in Trapper.playersOnMap) { if (herePoints.ContainsKey(player)) continue; Vector3 v = Trap.trapPlayerIdMap[player.PlayerId].trap.transform.position; v /= MapUtilities.CachedShipStatus.MapScale; v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x); - v.z = -1f; + v.z = -2.1f; var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true); herePoint.transform.localPosition = v; herePoint.enabled = true; @@ -38,41 +56,141 @@ static void Postfix(MapBehaviour __instance) { herePoints.Remove(s.Key); } } else if (Snitch.snitch != null && CachedPlayer.LocalPlayer.PlayerId == Snitch.snitch.PlayerId && !Snitch.snitch.Data.IsDead && Snitch.mode != Snitch.Mode.Chat) { - var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data); - int numberOfTasks = playerTotal - playerCompleted; + var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data); + int numberOfTasks = playerTotal - playerCompleted; - if (numberOfTasks == 0) { + if (numberOfTasks == 0) { if (MeetingHud.Instance == null) { - foreach (PlayerControl player in CachedPlayer.AllPlayers) { - if (Snitch.targets == Snitch.Targets.EvilPlayers && !Helpers.isEvil(player)) continue; - else if (Snitch.targets == Snitch.Targets.Killers && !Helpers.isKiller(player)) continue; + foreach (PlayerControl player in CachedPlayer.AllPlayers) { + if (Snitch.targets == Snitch.Targets.EvilPlayers && !Helpers.isEvil(player)) continue; + else if (Snitch.targets == Snitch.Targets.Killers && !Helpers.isKiller(player)) continue; if (player.Data.IsDead) continue; - Vector3 v = player.transform.position; - v /= MapUtilities.CachedShipStatus.MapScale; - v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x); - v.z = -1f; + Vector3 v = player.transform.position; + v /= MapUtilities.CachedShipStatus.MapScale; + v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x); + v.z = -2.1f; if (herePoints.ContainsKey(player)) { herePoints[player].transform.localPosition = v; continue; } - var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true); - herePoint.transform.localPosition = v; - herePoint.enabled = true; - int colorId = player.CurrentOutfit.ColorId; - player.CurrentOutfit.ColorId = 6; - player.SetPlayerMaterialColors(herePoint); - player.CurrentOutfit.ColorId = colorId; - herePoints.Add(player, herePoint); - } - } else { - foreach (var s in herePoints) { - UnityEngine.Object.Destroy(s.Value); - herePoints.Remove(s.Key); - } - } + var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true); + herePoint.transform.localPosition = v; + herePoint.enabled = true; + int colorId = player.CurrentOutfit.ColorId; + player.CurrentOutfit.ColorId = 6; + player.SetPlayerMaterialColors(herePoint); + player.CurrentOutfit.ColorId = colorId; + herePoints.Add(player, herePoint); + } + } else { + foreach (var s in herePoints) { + UnityEngine.Object.Destroy(s.Value); + herePoints.Remove(s.Key); + } + } + } + } + + foreach (var vent in MapUtilities.CachedShipStatus.AllVents) { + if ((vent.name.StartsWith("JackInThe") && !(PlayerControl.LocalPlayer == Trickster.trickster || PlayerControl.LocalPlayer.Data.IsDead))) continue; //for trickster vents + + if (!TheOtherRolesPlugin.ShowVentsOnMap.Value) { + if (mapIcons.Count > 0) { + mapIcons.Values.Do((x) => x.Destroy()); + mapIcons.Clear(); + } + break; + } + + var Instance = DestroyableSingleton.Instance; + var task = PlayerControl.LocalPlayer.myTasks.ToArray().FirstOrDefault(x => x.TaskType == TaskTypes.VentCleaning); + + var location = vent.transform.position / MapUtilities.CachedShipStatus.MapScale; + location.z = -2f; //show above sabotage buttons + + GameObject MapIcon; + if (!mapIcons.ContainsKey($"vent {vent.Id} icon")) { + MapIcon = GameObject.Instantiate(__instance.HerePoint.gameObject, __instance.HerePoint.transform.parent); + mapIcons.Add($"vent {vent.Id} icon", MapIcon); + } + else { + MapIcon = mapIcons[$"vent {vent.Id} icon"]; + } + + MapIcon.GetComponent().sprite = Vent; + + MapIcon.name = $"vent {vent.Id} icon"; + MapIcon.transform.localPosition = location; + + if (task?.IsComplete == false && task.FindConsoles()[0].ConsoleId == vent.Id) { + MapIcon.transform.localScale *= 0.6f; + } + if (vent.name.StartsWith("JackInThe")) { + MapIcon.GetComponent().sprite = JackInTheBox.getBoxAnimationSprite(0); + MapIcon.transform.localScale = new Vector3(0.5f, 0.5f, 1); + MapIcon.GetComponent().color = vent.isActiveAndEnabled ? Color.yellow : Color.yellow.SetAlpha(0.5f); } + + if (AllVentsRegistered(Instance)) { + var array = VentNetworks.ToArray(); + foreach (var connectedgroup in VentNetworks) { + var index = Array.IndexOf(array, connectedgroup); + if (connectedgroup[0].name.StartsWith("JackInThe")) + continue; + connectedgroup.Do(x => GetIcon(x).GetComponent().color = Palette.PlayerColors[index]); + } + continue; + } + + HandleMiraOrSub(); + + var network = GetNetworkFor(vent); + if (network == null) { + VentNetworks.Add(new(vent.NearbyVents.Where(x => x != null)) { vent }); + } + else { + if (!network.Any(x => x == vent)) network.Add(vent); + } } HudManagerUpdate.CloseSettings(); } + + public static List GetNetworkFor(Vent vent) + { + return VentNetworks.FirstOrDefault(x => x.Any(y => y == vent || y == vent.Left || y == vent.Center || y == vent.Right)); + } + + public static bool AllVentsRegistered(MapTaskOverlay __instance) + { + foreach (var vent in MapUtilities.CachedShipStatus.AllVents) + { + if (!vent.isActiveAndEnabled) continue; + var network = GetNetworkFor(vent); + if (network == null || !network.Any(x => x == vent)) return false; + if (!mapIcons.ContainsKey($"vent {vent.Id} icon")) return false; + } + return true; + } + + public static GameObject GetIcon(Vent vent) + { + var icon = mapIcons[$"vent {vent.Id} icon"]; + return icon; + } + + public static void HandleMiraOrSub() + { + if (VentNetworks.Count != 0) return; + + if (Helpers.isMira()) { + var vents = MapUtilities.CachedShipStatus.AllVents.Where(x => !x.name.Contains("JackInTheBoxVent_")); + VentNetworks.Add(vents.ToList()); + return; + } + if (MapUtilities.CachedShipStatus.Type == SubmergedCompatibility.SUBMERGED_MAP_TYPE) { + var vents = MapUtilities.CachedShipStatus.AllVents.Where(x => x.Id is 12 or 13 or 15 or 16); + VentNetworks.Add(vents.ToList()); + } + } } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 717a08c84..30cc11da8 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -137,25 +137,39 @@ static bool Prefix(MeetingHud __instance) { [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.BloopAVoteIcon))] class MeetingHudBloopAVoteIconPatch { - public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]GameData.PlayerInfo voterPlayer, [HarmonyArgument(1)]int index, [HarmonyArgument(2)]Transform parent) { - SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); - int cId = voterPlayer.DefaultOutfit.ColorId; - if (!(!GameOptionsManager.Instance.currentNormalGameOptions.AnonymousVotes || (CachedPlayer.LocalPlayer.Data.IsDead && TORMapOptions.ghostsSeeVotes) || Mayor.mayor != null && CachedPlayer.LocalPlayer.PlayerControl == Mayor.mayor && Mayor.canSeeVoteColors && TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data).Item1 >= Mayor.tasksNeededToSeeVoteColors)) - voterPlayer.Object.SetColor(6); - voterPlayer.Object.SetPlayerMaterialColors(spriteRenderer); - spriteRenderer.transform.SetParent(parent); - spriteRenderer.transform.localScale = Vector3.zero; - __instance.StartCoroutine(Effects.Bloop((float)index * 0.3f, spriteRenderer.transform, 1f, 0.5f)); + public static bool Prefix(MeetingHud __instance, GameData.PlayerInfo voterPlayer, int index, Transform parent) { + var spriteRenderer = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); + var showVoteColors = !GameManager.Instance.LogicOptions.GetAnonymousVotes() || + (CachedPlayer.LocalPlayer.Data.IsDead && TORMapOptions.ghostsSeeVotes) || + (Mayor.mayor != null && Mayor.mayor == CachedPlayer.LocalPlayer.PlayerControl && Mayor.canSeeVoteColors && TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data).Item1 >= Mayor.tasksNeededToSeeVoteColors); + if (showVoteColors) + { + PlayerMaterial.SetColors(voterPlayer.DefaultOutfit.ColorId, spriteRenderer); + } + else + { + PlayerMaterial.SetColors(Palette.DisabledGrey, spriteRenderer); + } + + var transform = spriteRenderer.transform; + transform.SetParent(parent); + transform.localScale = Vector3.zero; + var component = parent.GetComponent(); + if (component != null) + { + spriteRenderer.material.SetInt(PlayerMaterial.MaskLayer, component.MaskLayer); + } + + __instance.StartCoroutine(Effects.Bloop(index * 0.3f, transform)); parent.GetComponent().AddVote(spriteRenderer); - voterPlayer.Object.SetColor(cId); return false; } - } + } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateResults))] class MeetingHudPopulateVotesPatch { - static bool Prefix(MeetingHud __instance, Il2CppStructArray states) { + private static bool Prefix(MeetingHud __instance, Il2CppStructArray states) { // Swapper swap PlayerVoteArea swapped1 = null; @@ -662,10 +676,11 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]GameData // Resett Bait list Bait.active = new Dictionary(); // Save AntiTeleport position, if the player is able to move (i.e. not on a ladder or a gap thingy) - if (CachedPlayer.LocalPlayer.PlayerPhysics.enabled && CachedPlayer.LocalPlayer.PlayerControl.moveable || CachedPlayer.LocalPlayer.PlayerControl.inVent + if (CachedPlayer.LocalPlayer.PlayerPhysics.enabled && (CachedPlayer.LocalPlayer.PlayerControl.moveable || CachedPlayer.LocalPlayer.PlayerControl.inVent || HudManagerStartPatch.hackerVitalsButton.isEffectActive || HudManagerStartPatch.hackerAdminTableButton.isEffectActive || HudManagerStartPatch.securityGuardCamButton.isEffectActive - || Portal.isTeleporting && Portal.teleportedPlayers.Last().playerId == CachedPlayer.LocalPlayer.PlayerId) { - AntiTeleport.position = CachedPlayer.LocalPlayer.transform.position; + || Portal.isTeleporting && Portal.teleportedPlayers.Last().playerId == CachedPlayer.LocalPlayer.PlayerId)) { + if (!CachedPlayer.LocalPlayer.PlayerControl.inMovingPlat) + AntiTeleport.position = CachedPlayer.LocalPlayer.transform.position; } // Medium meeting start time @@ -726,7 +741,7 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]GameData if (x == 1f) { foreach (PlayerControl p in CachedPlayer.AllPlayers) { if (Snitch.targets == Snitch.Targets.Killers && !Helpers.isKiller(p)) continue; - else if (Snitch.targets == Snitch.Targets.EvilPlayers && !Helpers.isEvil(p)) continue; + if (Snitch.targets == Snitch.Targets.EvilPlayers && !Helpers.isEvil(p)) continue; if (!Snitch.playerRoomMap.ContainsKey(p.PlayerId)) continue; if (p.Data.IsDead) continue; var room = Snitch.playerRoomMap[p.PlayerId]; diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index b251d7cab..456732f37 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -12,6 +12,7 @@ using TheOtherRoles.CustomGameModes; using static UnityEngine.GraphicsBuffer; using AmongUs.GameOptions; +using Assets.CoreScripts; using Sentry.Internal.Extensions; using Reactor.Utilities.Extensions; @@ -68,7 +69,7 @@ static void setBasePlayerOutlines() { bool isMorphedMorphling = target == Morphling.morphling && Morphling.morphTarget != null && Morphling.morphTimer > 0f; bool hasVisibleShield = false; Color color = Medic.shieldedColor; - if (Camouflager.camouflageTimer <= 0f && Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) { + if (Camouflager.camouflageTimer <= 0f && !Helpers.MushroomSabotageActive() && Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) { hasVisibleShield = Medic.showShielded == 0 || Helpers.shouldShowGhostInfo() // Everyone or Ghost info || (Medic.showShielded == 1 && (CachedPlayer.LocalPlayer.PlayerControl == Medic.shielded || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic)) // Shielded + Medic || (Medic.showShielded == 2 && CachedPlayer.LocalPlayer.PlayerControl == Medic.medic); // Medic only @@ -76,7 +77,7 @@ static void setBasePlayerOutlines() { hasVisibleShield = hasVisibleShield && (Medic.meetingAfterShielding || !Medic.showShieldAfterMeeting || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic || Helpers.shouldShowGhostInfo()); } - if (Camouflager.camouflageTimer <= 0f && TORMapOptions.firstKillPlayer != null && TORMapOptions.shieldFirstKill && ((target == TORMapOptions.firstKillPlayer && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == TORMapOptions.firstKillPlayer))) { + if (Camouflager.camouflageTimer <= 0f && !Helpers.MushroomSabotageActive() && TORMapOptions.firstKillPlayer != null && TORMapOptions.shieldFirstKill && ((target == TORMapOptions.firstKillPlayer && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == TORMapOptions.firstKillPlayer))) { hasVisibleShield = true; color = Color.blue; } @@ -91,6 +92,15 @@ static void setBasePlayerOutlines() { } } + static void setPetVisibility() { + bool localalive = !CachedPlayer.LocalPlayer.Data.IsDead; + foreach (var player in CachedPlayer.AllPlayers) + { + bool playeralive = !player.Data.IsDead; + player.PlayerControl.cosmetics.SetPetVisible((localalive && playeralive) || !localalive); + } + } + public static void bendTimeUpdate() { if (TimeMaster.isRewinding) { if (localPlayerPositions.Count > 0) { @@ -451,7 +461,7 @@ public static void playerSizeUpdate(PlayerControl p) { collider.offset = Mini.defaultColliderOffset * Vector2.down; // Set adapted player size to Mini and Morphling - if (Mini.mini == null || Camouflager.camouflageTimer > 0f || Mini.mini == Morphling.morphling && Morphling.morphTimer > 0) return; + if (Mini.mini == null || Camouflager.camouflageTimer > 0f || Helpers.MushroomSabotageActive() || Mini.mini == Morphling.morphling && Morphling.morphTimer > 0) return; float growingProgress = Mini.growingProgress(); float scale = growingProgress * 0.35f + 0.35f; @@ -739,12 +749,17 @@ public static void mediumSetTarget() { Medium.target = target; } + static bool mushroomSaboWasActive = false; static void morphlingAndCamouflagerUpdate() { + bool mushRoomSaboIsActive = Helpers.MushroomSabotageActive(); + if (!mushroomSaboWasActive) mushroomSaboWasActive = mushRoomSaboIsActive; + float oldCamouflageTimer = Camouflager.camouflageTimer; float oldMorphTimer = Morphling.morphTimer; Camouflager.camouflageTimer = Mathf.Max(0f, Camouflager.camouflageTimer - Time.fixedDeltaTime); Morphling.morphTimer = Mathf.Max(0f, Morphling.morphTimer - Time.fixedDeltaTime); + if (mushRoomSaboIsActive) return; // Camouflage reset and set Morphling look if necessary if (oldCamouflageTimer > 0f && Camouflager.camouflageTimer <= 0f) { @@ -755,9 +770,22 @@ static void morphlingAndCamouflagerUpdate() { } } + // If the MushRoomSabotage ends while Morph is still active set the Morphlings look to the target's look + if (mushroomSaboWasActive) { + if (Morphling.morphTimer > 0f && Morphling.morphling != null && Morphling.morphTarget != null) { + PlayerControl target = Morphling.morphTarget; + Morphling.morphling.setLook(target.Data.PlayerName, target.Data.DefaultOutfit.ColorId, target.Data.DefaultOutfit.HatId, target.Data.DefaultOutfit.VisorId, target.Data.DefaultOutfit.SkinId, target.Data.DefaultOutfit.PetId); + } + if (Camouflager.camouflageTimer > 0) { + foreach (PlayerControl player in CachedPlayer.AllPlayers) + player.setLook("", 6, "", "", "", ""); + } + } + // Morphling reset (only if camouflage is inactive) if (Camouflager.camouflageTimer <= 0f && oldMorphTimer > 0f && Morphling.morphTimer <= 0f && Morphling.morphling != null) Morphling.resetMorph(); + mushroomSaboWasActive = false; } public static void lawyerUpdate() { @@ -980,6 +1008,9 @@ public static void Postfix(PlayerControl __instance) { // Update Player Info updatePlayerInfo(); + //Update pet visibility + setPetVisibility(); + // Time Master bendTimeUpdate(); // Morphling @@ -1076,7 +1107,7 @@ public static void Postfix(PlayerControl __instance) { class PlayerPhysicsWalkPlayerToPatch { private static Vector2 offset = Vector2.zero; public static void Prefix(PlayerPhysics __instance) { - bool correctOffset = Camouflager.camouflageTimer <= 0f && (__instance.myPlayer == Mini.mini || (Morphling.morphling != null && __instance.myPlayer == Morphling.morphling && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f)); + bool correctOffset = Camouflager.camouflageTimer <= 0f && !Helpers.MushroomSabotageActive() && (__instance.myPlayer == Mini.mini || (Morphling.morphling != null && __instance.myPlayer == Morphling.morphling && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f)); correctOffset = correctOffset && !(Mini.mini == Morphling.morphling && Morphling.morphTimer > 0f); if (correctOffset) { float currentScaling = (Mini.growingProgress() + 1) * 0.5f; @@ -1138,7 +1169,7 @@ static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]GameData.Playe } if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0) { - FastDestroyableSingleton.Instance.SendWho(); + FastDestroyableSingleton.Instance.SendWho(); } } } diff --git a/TheOtherRoles/Patches/ShipStatusPatch.cs b/TheOtherRoles/Patches/ShipStatusPatch.cs index 9aada9385..18d48a222 100644 --- a/TheOtherRoles/Patches/ShipStatusPatch.cs +++ b/TheOtherRoles/Patches/ShipStatusPatch.cs @@ -15,7 +15,7 @@ public class ShipStatusPatch [HarmonyPrefix] [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CalculateLightRadius))] public static bool Prefix(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo player) { - if (!__instance.Systems.ContainsKey(SystemTypes.Electrical) || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return true; + if ((!__instance.Systems.ContainsKey(SystemTypes.Electrical) && !Helpers.isFungle()) || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return true; // If Game Mode is PropHunt: if (PropHunt.isPropHuntGM) { @@ -86,9 +86,11 @@ public static float GetNeutralLightRadius(ShipStatus shipStatus, bool isImpostor } if (isImpostor) return shipStatus.MaxLightRadius * GameOptionsManager.Instance.currentNormalGameOptions.ImpostorLightMod; - - SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast(); - float lerpValue = switchSystem.Value / 255f; + float lerpValue = 1.0f; + try { + SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast(); + lerpValue = switchSystem.Value / 255f; + } catch { } return Mathf.Lerp(shipStatus.MinLightRadius, shipStatus.MaxLightRadius, lerpValue) * GameOptionsManager.Instance.currentNormalGameOptions.CrewLightMod; } @@ -117,7 +119,7 @@ public static bool Prefix(ShipStatus __instance) if (TORMapOptions.gameMode != CustomGamemodes.HideNSeek) { var commonTaskCount = __instance.CommonTasks.Count; - var normalTaskCount = __instance.NormalTasks.Count; + var normalTaskCount = __instance.ShortTasks.Count; var longTaskCount = __instance.LongTasks.Count; if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) { @@ -134,6 +136,7 @@ public static bool Prefix(ShipStatus __instance) GameOptionsManager.Instance.currentNormalGameOptions.NumLongTasks = Mathf.RoundToInt(CustomOptionHolder.hideNSeekLongTasks.getFloat()); } + MapBehaviourPatch.VentNetworks.Clear(); return true; } diff --git a/TheOtherRoles/Patches/TransportationToolPatches.cs b/TheOtherRoles/Patches/TransportationToolPatches.cs new file mode 100644 index 000000000..1924aeeb6 --- /dev/null +++ b/TheOtherRoles/Patches/TransportationToolPatches.cs @@ -0,0 +1,77 @@ +using HarmonyLib; +using Il2CppSystem.Collections.Generic; +using System; +using UnityEngine.Windows.Speech; +using TheOtherRoles; +using static UnityEngine.GraphicsBuffer; + +namespace TheOtherRoles.Patches { + [HarmonyPatch] + public static class TransportationToolPatches { + /* + * Moving Plattform / Zipline / Ladders move the player out of bounds, thus we want to disable functions of the mod if the player is currently using one of these. + * Save the players anti tp position before using it. + * + * Zipline can also break camo, fix that one too. + */ + + public static bool isUsingTransportation(PlayerControl pc) { + return pc.inMovingPlat || pc.onLadder; + } + + // Zipline: + [HarmonyPrefix] + [HarmonyPatch(typeof(ZiplineBehaviour), nameof(ZiplineBehaviour.Use), new Type[] {typeof(PlayerControl), typeof(bool)})] + public static void prefix3(ZiplineBehaviour __instance, PlayerControl player, bool fromTop) { + AntiTeleport.position = Players.CachedPlayer.LocalPlayer.transform.position; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(ZiplineBehaviour), nameof(ZiplineBehaviour.Use), new Type[] { typeof(PlayerControl), typeof(bool) })] + public static void postfix(ZiplineBehaviour __instance, PlayerControl player, bool fromTop) { + // Fix camo: + __instance.StartCoroutine(Effects.Lerp(fromTop ? __instance.downTravelTime : __instance.upTravelTime, new System.Action((p) => { + HandZiplinePoolable hand; + __instance.playerIdHands.TryGetValue(player.PlayerId, out hand); + if (hand != null) { + if (Camouflager.camouflageTimer <= 0 && !Helpers.MushroomSabotageActive()) { + if (player == Morphling.morphling && Morphling.morphTimer > 0) { + hand.SetPlayerColor(Morphling.morphTarget.CurrentOutfit, PlayerMaterial.MaskType.None); + // Also set hat color, cause the line destroys it... + player.RawSetHat(Morphling.morphTarget.Data.DefaultOutfit.HatId, Morphling.morphTarget.Data.DefaultOutfit.ColorId); + } else { + hand.SetPlayerColor(player.CurrentOutfit, PlayerMaterial.MaskType.None); + } + } else { + PlayerMaterial.SetColors(6, hand.handRenderer); + } + } + }))); + } + + // Save the position of the player prior to starting the climb / gap platform + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.ClimbLadder))] + public static void prefix() { + AntiTeleport.position = Players.CachedPlayer.LocalPlayer.transform.position; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.ClimbLadder))] + public static void postfix2(PlayerPhysics __instance, Ladder source, byte climbLadderSid) { + // Fix camo: + var player = __instance.myPlayer; + __instance.StartCoroutine(Effects.Lerp(5.0f, new System.Action((p) => { + if (Camouflager.camouflageTimer <= 0 && !Helpers.MushroomSabotageActive() && player == Morphling.morphling && Morphling.morphTimer > 0.1f) { + player.RawSetHat(Morphling.morphTarget.Data.DefaultOutfit.HatId, Morphling.morphTarget.Data.DefaultOutfit.ColorId); + } + }))); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(MovingPlatformBehaviour), nameof(MovingPlatformBehaviour.UsePlatform))] + public static void prefix2() { + AntiTeleport.position = Players.CachedPlayer.LocalPlayer.transform.position; + } + } +} diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 8ef4a720d..1b2bfa1dd 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -253,7 +253,7 @@ static void timerUpdate() { } public static void miniUpdate() { - if (Mini.mini == null || Camouflager.camouflageTimer > 0f || Mini.mini == Morphling.morphling && Morphling.morphTimer > 0f || Mini.mini == Ninja.ninja && Ninja.isInvisble || SurveillanceMinigamePatch.nightVisionIsActive) return; + if (Mini.mini == null || Camouflager.camouflageTimer > 0f || Helpers.MushroomSabotageActive() || Mini.mini == Morphling.morphling && Morphling.morphTimer > 0f || Mini.mini == Ninja.ninja && Ninja.isInvisble || SurveillanceMinigamePatch.nightVisionIsActive) return; float growingProgress = Mini.growingProgress(); float scale = growingProgress * 0.35f + 0.35f; diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index 2835ec9a2..24b09e80a 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -580,6 +580,14 @@ public static void Postfix(PlanetSurveillanceMinigame __instance) { } } + [HarmonyPatch(typeof(FungleSurveillanceMinigame), nameof(FungleSurveillanceMinigame.Update))] + class FungleSurveillanceMinigameUpdatePatch { + public static void Postfix(FungleSurveillanceMinigame __instance) { + nightVisionUpdate(FungleCamMinigame: __instance); + } + } + + [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.OnDestroy))] class SurveillanceMinigameDestroyPatch { public static void Prefix() { @@ -594,10 +602,9 @@ public static void Prefix() { } } - - private static void nightVisionUpdate(SurveillanceMinigame SkeldCamsMinigame = null, PlanetSurveillanceMinigame SwitchCamsMinigame = null) { + private static void nightVisionUpdate(SurveillanceMinigame SkeldCamsMinigame = null, PlanetSurveillanceMinigame SwitchCamsMinigame = null, FungleSurveillanceMinigame FungleCamMinigame = null) { + GameObject closeButton = null; if (nightVisionOverlays == null) { - GameObject closeButton = null; List viewPorts = new(); Transform viewablesTransform = null; if (SkeldCamsMinigame != null) { @@ -608,15 +615,31 @@ private static void nightVisionUpdate(SurveillanceMinigame SkeldCamsMinigame = n closeButton = SwitchCamsMinigame.Viewables.transform.Find("CloseButton").gameObject; viewPorts.Add(SwitchCamsMinigame.ViewPort); viewablesTransform = SwitchCamsMinigame.Viewables.transform; + } else if (FungleCamMinigame != null) { + closeButton = FungleCamMinigame.transform.Find("CloseButton").gameObject; + viewPorts.Add(FungleCamMinigame.viewport); + viewablesTransform = FungleCamMinigame.viewport.transform; } else return; nightVisionOverlays = new List(); foreach (var renderer in viewPorts) { - GameObject overlayObject = GameObject.Instantiate(closeButton, viewablesTransform); - overlayObject.transform.position = new Vector3(renderer.transform.position.x, renderer.transform.position.y, overlayObject.transform.position.z); - overlayObject.transform.localScale = (SkeldCamsMinigame != null) ? new Vector3(0.91f, 0.612f, 1f) : new Vector3(2.124f, 1.356f, 1f); - overlayObject.layer = closeButton.layer; + GameObject overlayObject; + float zPosition; + if (FungleCamMinigame != null) { + overlayObject = GameObject.Instantiate(closeButton, renderer.transform); + overlayObject.layer = renderer.gameObject.layer; + zPosition = - 0.5f; + overlayObject.transform.localPosition = new Vector3(0, 0, zPosition); + } else { + overlayObject = GameObject.Instantiate(closeButton, viewablesTransform); + zPosition = overlayObject.transform.position.z; + overlayObject.layer = closeButton.layer; + overlayObject.transform.position = new Vector3(renderer.transform.position.x, renderer.transform.position.y, zPosition); + } + Vector3 localScale = (SkeldCamsMinigame != null) ? new Vector3(0.91f, 0.612f, 1f) : new Vector3(2.124f, 1.356f, 1f); + localScale = (FungleCamMinigame != null) ? new Vector3(10f, 10f, 1f) : localScale; + overlayObject.transform.localScale = localScale; var overlayRenderer = overlayObject.GetComponent(); overlayRenderer.sprite = overlaySprite; overlayObject.SetActive(false); @@ -625,8 +648,7 @@ private static void nightVisionUpdate(SurveillanceMinigame SkeldCamsMinigame = n } } - - isLightsOut = CachedPlayer.LocalPlayer.PlayerControl.myTasks.ToArray().Any(x => x.name.Contains("FixLightsTask")); + isLightsOut = CachedPlayer.LocalPlayer.PlayerControl.myTasks.ToArray().Any(x => x.name.Contains("FixLightsTask")) || Trickster.lightsOutTimer > 0; bool ignoreNightVision = CustomOptionHolder.camsNoNightVisionIfImpVision.getBool() && Helpers.hasImpVision(GameData.Instance.GetPlayerById(CachedPlayer.LocalPlayer.PlayerId)) || CachedPlayer.LocalPlayer.Data.IsDead; bool nightVisionEnabled = CustomOptionHolder.camsNightVision.getBool(); @@ -652,7 +674,7 @@ private static void nightVisionUpdate(SurveillanceMinigame SkeldCamsMinigame = n } } - private static void resetNightVision() { + public static void resetNightVision() { foreach (var go in nightVisionOverlays) { go.Destroy(); } diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 0e64211fb..4edcbdf1a 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -15,7 +15,7 @@ using TheOtherRoles.CustomGameModes; using AmongUs.Data; using AmongUs.GameOptions; -using UnityEngine.UIElements; +using Assets.CoreScripts; namespace TheOtherRoles { @@ -184,6 +184,7 @@ public static void resetVariables() { GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 0; SurveillanceMinigamePatch.nightVisionOverlays = null; EventUtility.clearAndReload(); + MapBehaviourPatch.clearAndReload(); } public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) { @@ -601,8 +602,10 @@ public static void camouflagerCamouflage() { if (Camouflager.camouflager == null) return; Camouflager.camouflageTimer = Camouflager.duration; + if (Helpers.MushroomSabotageActive()) return; // Dont overwrite the fungle "camo" foreach (PlayerControl player in CachedPlayer.AllPlayers) player.setLook("", 6, "", "", "", ""); + } public static void vampireSetBitten(byte targetId, byte performReset) { @@ -808,7 +811,7 @@ public static void setInvisible(byte playerId, byte flag) target.cosmetics.colorBlindText.gameObject.SetActive(DataManager.Settings.Accessibility.ColorBlindMode); target.cosmetics.colorBlindText.color = target.cosmetics.colorBlindText.color.SetAlpha(1f); - if (Camouflager.camouflageTimer <= 0) target.setDefaultLook(); + if (Camouflager.camouflageTimer <= 0 && !Helpers.MushroomSabotageActive()) target.setDefaultLook(); Ninja.isInvisble = false; return; } @@ -893,12 +896,20 @@ public static void sealVent(int ventId) { SecurityGuard.remainingScrews -= SecurityGuard.ventPrice; if (CachedPlayer.LocalPlayer.PlayerControl == SecurityGuard.securityGuard) { PowerTools.SpriteAnim animator = vent.GetComponent(); - animator?.Stop(); + vent.EnterVentAnim = vent.ExitVentAnim = null; - vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + Sprite newSprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + SpriteRenderer rend = vent.myRend; + if (Helpers.isFungle()) { + newSprite = SecurityGuard.getFungleVentSealedSprite(); + rend = vent.transform.GetChild(3).GetComponent(); + animator = vent.transform.GetChild(3).GetComponent(); + } + animator?.Stop(); + rend.sprite = newSprite; if (SubmergedCompatibility.IsSubmerged && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite(); if (SubmergedCompatibility.IsSubmerged && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite(); - vent.myRend.color = new Color(1f, 1f, 1f, 0.5f); + rend.color = new Color(1f, 1f, 1f, 0.5f); vent.name = "FutureSealedVent_" + vent.name; } @@ -948,6 +959,15 @@ public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedT } } + if (Lawyer.lawyer != null && !Lawyer.isProsecutor && Lawyer.lawyer.PlayerId == killerId && Lawyer.target != null && Lawyer.target.PlayerId == dyingTargetId) { + // Lawyer guessed client. + if (CachedPlayer.LocalPlayer.PlayerControl == Lawyer.lawyer) { + FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(Lawyer.lawyer.Data, Lawyer.lawyer.Data); + if (MeetingHudPatch.guesserUI != null) MeetingHudPatch.guesserUIExitButton.OnClick.Invoke(); + } + Lawyer.lawyer.Exiled(); + } + dyingTarget.Exiled(); GameHistory.overrideDeathReasonAndKiller(dyingTarget, DeadPlayer.CustomDeathReason.Guess, guesser); byte partnerId = dyingLoverPartner != null ? dyingLoverPartner.PlayerId : dyingTargetId; @@ -1004,7 +1024,7 @@ public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedT if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance) FastDestroyableSingleton.Instance.Chat.AddChat(guesser, msg); if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0) - FastDestroyableSingleton.Instance.SendWho(); + FastDestroyableSingleton.Instance.SendWho(); } } @@ -1149,7 +1169,11 @@ public static void propHuntSetProp(byte playerId, string propName, float posX) { PlayerControl player = Helpers.playerById(playerId); var prop = PropHunt.FindPropByNameAndPos(propName, posX); if (prop == null) return; - player.GetComponent().sprite = prop.GetComponent().sprite; + try { + player.GetComponent().sprite = prop.GetComponent().sprite; + } catch { + player.GetComponent().sprite = prop.transform.GetComponentInChildren().sprite; + } player.transform.localScale = prop.transform.lossyScale; player.Visible = false; PropHunt.currentObject[player.PlayerId] = new Tuple(propName, posX); diff --git a/TheOtherRoles/Resources/FungleVentSealed.png b/TheOtherRoles/Resources/FungleVentSealed.png new file mode 100644 index 000000000..16b157c53 Binary files /dev/null and b/TheOtherRoles/Resources/FungleVentSealed.png differ diff --git a/TheOtherRoles/Resources/Txt/Props.txt b/TheOtherRoles/Resources/Txt/Props.txt index 906cae8c1..923468106 100644 --- a/TheOtherRoles/Resources/Txt/Props.txt +++ b/TheOtherRoles/Resources/Txt/Props.txt @@ -89,4 +89,8 @@ NormalMeetingVent console-mr-callmeeting deco-mr-table console-mr-fishfeed -NormalAdminVent \ No newline at end of file +NormalAdminVent + +Debris +MetalPlate_ +Mushroom ( \ No newline at end of file diff --git a/TheOtherRoles/Resources/Vent.png b/TheOtherRoles/Resources/Vent.png new file mode 100644 index 000000000..29fa8f421 Binary files /dev/null and b/TheOtherRoles/Resources/Vent.png differ diff --git a/TheOtherRoles/SubmergedCompatibility.cs b/TheOtherRoles/SubmergedCompatibility.cs index 21f4a3000..f3311b66f 100644 --- a/TheOtherRoles/SubmergedCompatibility.cs +++ b/TheOtherRoles/SubmergedCompatibility.cs @@ -20,7 +20,7 @@ public static class Classes } public const string SUBMERGED_GUID = "Submerged"; - public const ShipStatus.MapType SUBMERGED_MAP_TYPE = (ShipStatus.MapType) 5; + public const ShipStatus.MapType SUBMERGED_MAP_TYPE = (ShipStatus.MapType) 6; public static SemanticVersioning.Version Version { get; private set; } public static bool Loaded { get; private set; } diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 053206af6..6143779be 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -695,9 +695,10 @@ public static Sprite getLogSprite() { public static Sprite getAdminSprite() { byte mapId = GameOptionsManager.Instance.currentNormalGameOptions.MapId; UseButtonSettings button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.PolusAdminButton]; // Polus - if (mapId == 0 || mapId == 3) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks - else if (mapId == 1) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ - else if (mapId == 4) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship + if (Helpers.isSkeld() || mapId == 3) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks + else if (Helpers.isMira()) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ + else if (Helpers.isAirship()) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship + else if (Helpers.isFungle()) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Hacker can Access the Admin panel on Fungle adminSprite = button.Image; return adminSprite; } @@ -1140,6 +1141,14 @@ public static Sprite getStaticVentSealedSprite() { return staticVentSealedSprite; } + private static Sprite fungleVentSealedSprite; + public static Sprite getFungleVentSealedSprite() { + if (fungleVentSealedSprite) return fungleVentSealedSprite; + fungleVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.FungleVentSealed.png", 160f); + return fungleVentSealedSprite; + } + + private static Sprite submergedCentralUpperVentSealedSprite; public static Sprite getSubmergedCentralUpperSealedSprite() { if (submergedCentralUpperVentSealedSprite) return submergedCentralUpperVentSealedSprite; @@ -1941,8 +1950,10 @@ public static void update() { chameleonPlayer.SetHatAndVisorAlpha(visibility); chameleonPlayer.cosmetics.skin.layer.color = chameleonPlayer.cosmetics.skin.layer.color.SetAlpha(visibility); chameleonPlayer.cosmetics.nameText.color = chameleonPlayer.cosmetics.nameText.color.SetAlpha(visibility); - chameleonPlayer.cosmetics.currentPet.rend.color = chameleonPlayer.cosmetics.currentPet.rend.color.SetAlpha(petVisibility); - chameleonPlayer.cosmetics.currentPet.shadowRend.color = chameleonPlayer.cosmetics.currentPet.shadowRend.color.SetAlpha(petVisibility); + foreach (var rend in chameleonPlayer.cosmetics.currentPet.renderers) + rend.color = rend.color.SetAlpha(petVisibility); + foreach (var shadowRend in chameleonPlayer.cosmetics.currentPet.shadows) + shadowRend.color = shadowRend.color.SetAlpha(petVisibility); } catch { } } diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index 5faf18aa3..c2ced5320 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,13 +1,14 @@  net6.0 - 4.4.2 + 4.5.0 TheOtherRoles Eisbison latest true true x86 + true @@ -15,8 +16,8 @@ - - + + diff --git a/TheOtherRoles/Utilities/EventUtility.cs b/TheOtherRoles/Utilities/EventUtility.cs index b04f2cb35..caacedcd6 100644 --- a/TheOtherRoles/Utilities/EventUtility.cs +++ b/TheOtherRoles/Utilities/EventUtility.cs @@ -10,149 +10,150 @@ using InnerNet; using TheOtherRoles.Modules; -namespace TheOtherRoles.Utilities { - public static class EventUtility { - - public enum EventTypes { - Communication, - Animation, - Invert, - KnockKnock - } +namespace TheOtherRoles.Utilities; - public static readonly float[] eventFrequencies = {15f, 60f, 60f, 300f}; - public static readonly float[] eventDurations = {0f, 1f, 5f, 0f}; - public static double[] eventProbabilities; - private static bool knocked = false; - public static bool disableHorses = false; - - public static void Load() { - if (!isEnabled) return; - eventProbabilities = new double[6]; - foreach (EventTypes curEvent in Enum.GetValues(typeof(EventTypes))) { - float desired_trials = 60 * eventFrequencies[(int)curEvent]; - eventProbabilities[(int)curEvent] = desired_trials != 0 ? 1f / desired_trials : 1f; - } - - } +public static class EventUtility { - private static List eventQueue = null; - public static bool eventInvert = false; + public enum EventTypes { + Communication, + Animation, + Invert, + KnockKnock + } - public static void clearAndReload() { - eventQueue = new List(); - eventInvert = false; - if (canBeEnabled && CustomOptionHolder.enableCodenameDisableHorses != null) - disableHorses = CustomOptionHolder.enableCodenameDisableHorses.getBool(); + public static readonly float[] eventFrequencies = {15f, 60f, 60f, 300f}; + public static readonly float[] eventDurations = {0f, 1f, 5f, 0f}; + public static double[] eventProbabilities; + private static bool knocked = false; + public static bool disableHorses = false; + + public static void Load() { + if (!isEnabled) return; + eventProbabilities = new double[6]; + foreach (EventTypes curEvent in Enum.GetValues(typeof(EventTypes))) { + float desired_trials = 60 * eventFrequencies[(int)curEvent]; + eventProbabilities[(int)curEvent] = desired_trials != 0 ? 1f / desired_trials : 1f; } + + } - public static void Update() { - if (!isEnabled || eventQueue == null || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return; - foreach (EventTypes curEvent in eventQueue.ToArray()) { - if (TheOtherRoles.rnd.NextSingle() < eventProbabilities[(int)curEvent]) { - eventQueue.Remove(curEvent); - StartEvent(curEvent); - } - } + private static List eventQueue = null; + public static bool eventInvert = false; - AddToQueue(EventTypes.Animation); - AddToQueue(EventTypes.Invert); - if (!knocked) { - AddToQueue(EventTypes.KnockKnock); + public static void clearAndReload() { + eventQueue = new List(); + eventInvert = false; + if (canBeEnabled && CustomOptionHolder.enableCodenameDisableHorses != null) + disableHorses = CustomOptionHolder.enableCodenameDisableHorses.getBool(); + } + + public static void Update() { + if (!isEnabled || eventQueue == null || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return; + foreach (EventTypes curEvent in eventQueue.ToArray()) { + if (TheOtherRoles.rnd.NextSingle() < eventProbabilities[(int)curEvent]) { + eventQueue.Remove(curEvent); + StartEvent(curEvent); } } - private static DateTime enabled = DateTime.FromBinary(-8585213068854775808); - public static bool isEventDate => DateTime.Today.Date == enabled; + AddToQueue(EventTypes.Animation); + AddToQueue(EventTypes.Invert); + if (!knocked) { + AddToQueue(EventTypes.KnockKnock); + } + } - public static bool canBeEnabled => DateTime.Today.Date > enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT - public static bool isEnabled => isEventDate || canBeEnabled && CustomOptionHolder.enableCodenameHorsemode != null && CustomOptionHolder.enableCodenameHorsemode.getBool(); + private static DateTime enabled = DateTime.FromBinary(-8585213068854775808); + public static bool isEventDate => DateTime.Today.Date == enabled; - public static void AddToQueue(EventTypes newEvent) { - if (!isEnabled || eventQueue == null || eventQueue.Contains(newEvent)) return; - eventQueue.Add(newEvent); - } + public static bool canBeEnabled => DateTime.Today.Date > enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT + public static bool isEnabled => isEventDate || canBeEnabled && CustomOptionHolder.enableCodenameHorsemode != null && CustomOptionHolder.enableCodenameHorsemode.getBool(); + public static void AddToQueue(EventTypes newEvent) { + if (!isEnabled || eventQueue == null || eventQueue.Contains(newEvent)) return; + eventQueue.Add(newEvent); + } - private static string defaultHat = "default"; - public static void meetingEndsUpdate() { - if (!isEnabled) return; - PlayerControl.LocalPlayer.RpcSetHat(CustomHatLoader.horseHatProductIds[rnd.Next(CustomHatLoader.horseHatProductIds.Count)]); - } + private static string defaultHat = "default"; + public static void meetingEndsUpdate() { + if (!isEnabled) return; + // TODO - Implement Horse hats + // PlayerControl.LocalPlayer.RpcSetHat(CustomHatLoader.horseHatProductIds[rnd.Next(CustomHatLoader.horseHatProductIds.Count)]); + } - public static void meetingStartsUpdate() { - if (!isEnabled) return; - if (rnd.NextDouble() <= 0.3f) { AddToQueue(EventTypes.Communication); } - PlayerControl.LocalPlayer.RpcSetHat(defaultHat); - HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action((p) => { - if (MeetingHud.Instance && MeetingHud.Instance.playerStates != null) { - foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) { - GameData.PlayerInfo pInfo = GameData.Instance.AllPlayers.ToArray().First(x => x.PlayerId == pva.TargetPlayerId); - pva.SetCosmetics(pInfo); // Needed cause cosmetics are set async'd. - } + + public static void meetingStartsUpdate() { + if (!isEnabled) return; + if (rnd.NextDouble() <= 0.3f) { AddToQueue(EventTypes.Communication); } + PlayerControl.LocalPlayer.RpcSetHat(defaultHat); + HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action((p) => { + if (MeetingHud.Instance && MeetingHud.Instance.playerStates != null) { + foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) { + GameData.PlayerInfo pInfo = GameData.Instance.AllPlayers.ToArray().First(x => x.PlayerId == pva.TargetPlayerId); + pva.SetCosmetics(pInfo); // Needed cause cosmetics are set async'd. } - }))); - } + } + }))); + } - public static void gameStartsUpdate() { - if (!isEnabled) return; - defaultHat = PlayerControl.LocalPlayer.Data.DefaultOutfit.HatId; - meetingEndsUpdate(); - List relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList(); - foreach (CachedPlayer pc in relevantPlayers) - pc.PlayerControl.MyPhysics.SetBodyType(disableHorses ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse); + public static void gameStartsUpdate() { + if (!isEnabled) return; + defaultHat = PlayerControl.LocalPlayer.Data.DefaultOutfit.HatId; + meetingEndsUpdate(); + List relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList(); + foreach (CachedPlayer pc in relevantPlayers) + pc.PlayerControl.MyPhysics.SetBodyType(disableHorses ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse); - } + } - public static void gameEndsUpdate() { - if (!isEnabled) return; - if (defaultHat != "default") - PlayerControl.LocalPlayer.RpcSetHat(defaultHat); - } + public static void gameEndsUpdate() { + if (!isEnabled) return; + if (defaultHat != "default") + PlayerControl.LocalPlayer.RpcSetHat(defaultHat); + } - public static void StartEvent(EventTypes eventToStart) { - List relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList(); - switch (eventToStart) { - case EventTypes.Animation: - CachedPlayer animationPlayer = relevantPlayers[rnd.Next(relevantPlayers.Count)]; - animationPlayer.PlayerPhysics.SetBodyType(rnd.Next(2) > 0 ? (disableHorses ? PlayerBodyTypes.Horse : PlayerBodyTypes.Normal) : PlayerBodyTypes.Seeker); - FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Animation], new Action((p) => { - if (p==1) - animationPlayer.PlayerControl.MyPhysics.SetBodyType(disableHorses ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse); - }))); - break; - case EventTypes.Communication: - int index = TheOtherRoles.rnd.Next(relevantPlayers.Count); - CachedPlayer firstPlayer = relevantPlayers[index]; - relevantPlayers.RemoveAt(index); - string msg = firstPlayer.Data.PlayerName + " "; - foreach (CachedPlayer pc in relevantPlayers.ToArray()) { - if (TheOtherRoles.rnd.NextSingle() < 1f / relevantPlayers.Count) { - relevantPlayers.Remove(pc); - msg += pc.Data.PlayerName + " "; - } + public static void StartEvent(EventTypes eventToStart) { + List relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList(); + switch (eventToStart) { + case EventTypes.Animation: + CachedPlayer animationPlayer = relevantPlayers[rnd.Next(relevantPlayers.Count)]; + animationPlayer.PlayerPhysics.SetBodyType(rnd.Next(2) > 0 ? (disableHorses ? PlayerBodyTypes.Horse : PlayerBodyTypes.Normal) : PlayerBodyTypes.Seeker); + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Animation], new Action((p) => { + if (p==1) + animationPlayer.PlayerControl.MyPhysics.SetBodyType(disableHorses ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse); + }))); + break; + case EventTypes.Communication: + int index = TheOtherRoles.rnd.Next(relevantPlayers.Count); + CachedPlayer firstPlayer = relevantPlayers[index]; + relevantPlayers.RemoveAt(index); + string msg = firstPlayer.Data.PlayerName + " "; + foreach (CachedPlayer pc in relevantPlayers.ToArray()) { + if (TheOtherRoles.rnd.NextSingle() < 1f / relevantPlayers.Count) { + relevantPlayers.Remove(pc); + msg += pc.Data.PlayerName + " "; } - RoomTracker tracker = FastDestroyableSingleton.Instance.roomTracker; - string lastRoom = ""; - try { lastRoom = FastDestroyableSingleton.Instance.GetString(tracker.LastRoom.RoomId); } catch { } - msg += lastRoom != ""? lastRoom : (TheOtherRoles.rnd.Next(2) > 0 ? "sus" : "safe"); - - FastDestroyableSingleton.Instance.Chat.AddChat(relevantPlayers[rnd.Next(relevantPlayers.Count)], $"{msg}"); - break; - case EventTypes.Invert: - eventInvert = true; - FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Invert], new Action((p) => { - if (p == 1) - eventInvert = false; - }))); - break; - case EventTypes.KnockKnock: - SoundEffectsManager.play("knockKnock"); - knocked = true; - break; + } + RoomTracker tracker = FastDestroyableSingleton.Instance.roomTracker; + string lastRoom = ""; + try { lastRoom = FastDestroyableSingleton.Instance.GetString(tracker.LastRoom.RoomId); } catch { } + msg += lastRoom != ""? lastRoom : (TheOtherRoles.rnd.Next(2) > 0 ? "sus" : "safe"); + + FastDestroyableSingleton.Instance.Chat.AddChat(relevantPlayers[rnd.Next(relevantPlayers.Count)], $"{msg}"); + break; + case EventTypes.Invert: + eventInvert = true; + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Invert], new Action((p) => { + if (p == 1) + eventInvert = false; + }))); + break; + case EventTypes.KnockKnock: + SoundEffectsManager.play("knockKnock"); + knocked = true; + break; - } } } -} +} \ No newline at end of file diff --git a/TheOtherRoles/packages.lock.json b/TheOtherRoles/packages.lock.json index ae1b9649e..3a2a5c34f 100644 --- a/TheOtherRoles/packages.lock.json +++ b/TheOtherRoles/packages.lock.json @@ -4,9 +4,9 @@ "net6.0": { "AmongUs.GameLibs.Steam": { "type": "Direct", - "requested": "[2023.7.11, )", - "resolved": "2023.7.11", - "contentHash": "e5zbU+4AYnp1zAQDizO5GRIh1kemkgCa8rFVAcr9bxeupkBu5dwwdHxArWv/pUybIJTZfOPjkOnicZ2qKWLX7g==" + "requested": "[2023.11.28, )", + "resolved": "2023.11.28", + "contentHash": "EsKrZS4tjTRrY7mtZls3tMy54zije4vleEA9s0UGUr4SF3qm2axR6wMncyIJCk4ACXlN5Bxpsd3/upDe9Gnx6A==" }, "BepInEx.IL2CPP.MSBuild": { "type": "Direct",