| | [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)
+
<details>
<summary>Click to show older versions</summary>
| 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)
<details>
<summary>Click to show the Changelog</summary>
+**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 <gamemode>` (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!
# 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:</b>
+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\
-----------------------
# Gamemodes
+Gamemodes can be switched when creating a lobby or inside the lobby by using a command in the chat:
+`/gm <gamemode>`. 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.\
using TheOtherRoles.Players;
using TheOtherRoles.Utilities;
using TheOtherRoles.CustomGameModes;
+using TheOtherRoles.Patches;
namespace TheOtherRoles
{
}
},
() => { 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();
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;
},
() => {
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
}
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");
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;
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));
},
() => { 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(),
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<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel") || x.name.Contains("Cam"));
- if (mapId == 0 || mapId == 3) e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("SurvConsole"));
- else if (mapId == 4) e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("task_cams"));
+ var e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().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<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("SurvConsole"));
+ else if (Helpers.isAirship()) e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().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);
}
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;
},
() => {
CachedPlayer.LocalPlayer.PlayerControl.moveable = true;
},
false,
- GameOptionsManager.Instance.currentNormalGameOptions.MapId == 1 ? "DOORLOG" : "SECURITY"
+ Helpers.isMira() ? "DOORLOG" : "SECURITY"
);
// Security Guard cam button charges
() => { 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;
// 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);
() => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && !PlayerControl.LocalPlayer.Data.IsDead; },
() => {
propSpriteRenderer.sprite = PropHunt.currentTarget?.GetComponent<SpriteRenderer>()?.sprite;
+ if (propSpriteRenderer.sprite == null) propSpriteRenderer.sprite = PropHunt.currentTarget?.transform.GetComponentInChildren<SpriteRenderer>()?.sprite;
if (propSpriteRenderer.sprite != null)
propSpriteHolder.transform.localScale *= 1 / propSpriteRenderer.bounds.size.magnitude;
return PropHunt.currentTarget != null && PropHunt.currentTarget?.GetComponent<SpriteRenderer>()?.sprite != null;
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() {
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) {
__instance.gameObject.AddComponent<SpriteRenderer>();
__instance.GetComponent<CircleCollider2D>().radius = 0.00001f;
- }
+ }
// Runs periodically, resets animation data for players
public static CustomOption dynamicMapEnableMira;
public static CustomOption dynamicMapEnablePolus;
public static CustomOption dynamicMapEnableAirShip;
+ public static CustomOption dynamicMapEnableFungle;
public static CustomOption dynamicMapEnableSubmerged;
public static CustomOption dynamicMapSeparateSettings;
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);
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);
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);
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);
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Reflection;
using UnityEngine;
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 {
PerformKill,
SuppressKill,
BlankKill,
+ DelayVampireKill
}
public enum CustomGamemodes {
}
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);
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)
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<SpriteRenderer>())
}
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
}
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<FungleShipStatus>().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) {
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<float>((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;
}
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 {
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
{
[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);
public static ConfigEntry<bool> ShowLighterDarker { get; set; }
public static ConfigEntry<bool> EnableSoundEffects { get; set; }
public static ConfigEntry<bool> EnableHorseMode { get; set; }
+ public static ConfigEntry<bool> ShowVentsOnMap { get; set; }
public static ConfigEntry<string> Ip { get; set; }
public static ConfigEntry<ushort> Port { get; set; }
public static ConfigEntry<string> ShowPopUpVersion { get; set; }
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);
CustomOptionHolder.Load();
CustomColors.Load();
+ CustomHatManager.LoadHats();
if (BepInExUpdater.UpdateRequired)
{
AddComponent<BepInExUpdater>();
return;
}
+ AddComponent<ModUpdater>();
+
EventUtility.Load();
SubmergedCompatibility.Initialize();
- AddComponent<ModUpdateBehaviour>();
- 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
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
showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value;
enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value;
enableHorseMode = TheOtherRolesPlugin.EnableHorseMode.Value;
+ ShowVentsOnMap = TheOtherRolesPlugin.ShowVentsOnMap.Value;
+
//Patches.ShouldAlwaysHorseAround.isHorseMode = TheOtherRolesPlugin.EnableHorseMode.Value;
}
}
using System.Linq;
using TheOtherRoles.Players;
using TheOtherRoles.Utilities;
+using Hazel;
namespace TheOtherRoles.Modules {
[HarmonyPatch]
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;
}
}
/** 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);
+++ /dev/null
-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<string, HatExtension> CustomHatRegistry = new Dictionary<string, HatExtension>();
- public static Dictionary<string, HatViewData> CustomHatViewDatas = new Dictionary<string, HatViewData>();
- 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<CustomHat> createCustomHatDetails(string[] hats, bool fromDisk = false) {
- Dictionary<string, CustomHat> fronts = new Dictionary<string, CustomHat>();
- Dictionary<string, string> backs = new Dictionary<string, string>();
- Dictionary<string, string> flips = new Dictionary<string, string>();
- Dictionary<string, string> backflips = new Dictionary<string, string>();
- Dictionary<string, string> climbs = new Dictionary<string, string>();
-
- 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<string> options = new HashSet<string>();
- 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<CustomHat> customhats = new List<CustomHat>();
-
- 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<HatManager>.Instance.PlayerMaterial;
- hatShader = tmpShader;
- }
- var viewdata = ScriptableObject.CreateInstance<HatViewData>();
- HatData hat = ScriptableObject.CreateInstance<HatData>();
-
- 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<HatData> 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<TutorialManager>.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<CustomHat> 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<TutorialManager>.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<CustomHat> 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<HatManager>.Instance.DefaultShader;
- if (__instance.BackLayer) {
- __instance.BackLayer.sharedMaterial = DestroyableSingleton<HatManager>.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<SpriteAnimNodeSync>();
- 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<SpriteAnimNodeSync>();
- 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<System.Tuple<HatData, HatExtension>> 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<TMPro.TMP_Text>(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<float>((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<ColorChip>(__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<HatManager>.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<TMPro.TMP_Text>(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<float>((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<ColorChip>();
-
- HatData[] unlockedHats = FastDestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
- Dictionary<string, List<System.Tuple<HatData, HatExtension>>> packages = new Dictionary<string, List<System.Tuple<HatData, HatExtension>>>();
-
- foreach (HatData hatBehaviour in unlockedHats) {
- HatExtension ext = hatBehaviour.getHatExtension();
-
- if (ext != null) {
- if (!packages.ContainsKey(ext.package))
- packages[ext.package] = new List<System.Tuple<HatData, HatExtension>>();
- packages[ext.package].Add(new System.Tuple<HatData, HatExtension>(hatBehaviour, ext));
- } else {
- if (!packages.ContainsKey(innerslothPackageName))
- packages[innerslothPackageName] = new List<System.Tuple<HatData, HatExtension>>();
- packages[innerslothPackageName].Add(new System.Tuple<HatData, HatExtension>(hatBehaviour, null));
- }
- }
-
- packages.Remove("Horse Hats"); // Cannot be selected!
-
- float YOffset = __instance.YStart;
- textTemplate = GameObject.Find("HatsGroup").transform.FindChild("Text").GetComponent<TMPro.TMP_Text>();
-
- 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<System.Tuple<HatData, HatExtension>> 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<CustomHatOnline> hatdetails = new List<CustomHatOnline>();
- 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<HttpStatusCode> 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<CustomHatOnline> hatdatas = new List<CustomHatOnline>();
-
- 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<string> markedfordownload = new List<string>();
-
- 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<string> horseHatProductIds = null;
- private static void addHorseHats(ref List<CustomHatOnline> hatdatas) {
-
- horseHatProductIds = new();
- Assembly assembly = Assembly.GetExecutingAssembly();
- string[] resourceNames = assembly.GetManifestResourceNames();
- List<string> hatFiles = new();
- Dictionary<string, List<string>> hatFilesSorted = new Dictionary<string, List<string>>();
- 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<string>());
- 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;
- }
- }
-}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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<string, string> 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<CustomHat> UnregisteredHats = new();
+ internal static readonly Dictionary<string, HatViewData> ViewDataCache = new();
+ internal static readonly Dictionary<string, HatExtension> ExtensionCache = new();
+
+ private static readonly HatsLoader Loader;
+ private static Material cachedShader;
+
+ internal static HatExtension TestExtension { get; private set; }
+
+ static CustomHatManager()
+ {
+ Loader = TheOtherRolesPlugin.Instance.AddComponent<HatsLoader>();
+ }
+
+ 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<HatManager>.Instance.PlayerMaterial;
+
+ var viewData = ViewDataCache[ch.Name] = ScriptableObject.CreateInstance<HatViewData>();
+ var hat = ScriptableObject.CreateInstance<HatData>();
+
+ 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<CustomHat> CreateHatDetailsFromFileNames(string[] fileNames, bool fromDisk = false)
+ {
+ var fronts = new Dictionary<string, CustomHat>();
+ var backs = new Dictionary<string, string>();
+ var flips = new Dictionary<string, string>();
+ var backFlips = new Dictionary<string, string>();
+ var climbs = new Dictionary<string, string>();
+
+ 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<string>(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<CustomHat>();
+
+ 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<CustomHat> 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<string> GenerateDownloadList(List<CustomHat> hats)
+ {
+ var algorithm = MD5.Create();
+ var toDownload = new List<string>();
+
+ foreach (var hat in hats)
+ {
+ var files = new List<Tuple<string, string>>
+ {
+ 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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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<SkinsConfigFile>(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
--- /dev/null
+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
--- /dev/null
+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<HatData> 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
--- /dev/null
+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<HatManager>.Instance.DefaultShader;
+ if (__instance.BackLayer)
+ {
+ __instance.BackLayer.sharedMaterial = DestroyableSingleton<HatManager>.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<SpriteAnimNodeSync>();
+ 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<SpriteAnimNodeSync>();
+ 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<TutorialManager>.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
--- /dev/null
+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<ColorChip>();
+ var unlockedHats = DestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
+ var packages = new Dictionary<string, List<Tuple<HatData, HatExtension>>>();
+
+ foreach (var hatBehaviour in unlockedHats)
+ {
+ var ext = hatBehaviour.GetHatExtension();
+ if (ext != null)
+ {
+ if (!packages.ContainsKey(ext.Package))
+ {
+ packages[ext.Package] = new List<Tuple<HatData, HatExtension>>();
+ }
+ packages[ext.Package].Add(new Tuple<HatData, HatExtension>(hatBehaviour, ext));
+ }
+ else
+ {
+ if (!packages.ContainsKey(CustomHatManager.InnerslothPackageName))
+ {
+ packages[CustomHatManager.InnerslothPackageName] = new List<Tuple<HatData, HatExtension>>();
+ }
+ packages[CustomHatManager.InnerslothPackageName].Add(new Tuple<HatData, HatExtension>(hatBehaviour, null));
+ }
+ }
+
+ var yOffset = __instance.YStart;
+ textTemplate = GameObject.Find("HatsGroup").transform.FindChild("Text").GetComponent<TextMeshPro>();
+
+ 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<Tuple<HatData, HatExtension>> 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<float>(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<HatManager>.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<float>(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
--- /dev/null
+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
--- /dev/null
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace TheOtherRoles.Modules.CustomHats;
+
+public class SkinsConfigFile
+{
+ [JsonPropertyName("hats")] public List<CustomHat> Hats { get; set; }
+}
\ No newline at end of file
public class AmongUsClientOnPlayerJoinedPatch {
public static void Postfix() {
if (PlayerControl.LocalPlayer != null && AmongUsClient.Instance.AmHost) {
+ GameManager.Instance.LogicOptions.SyncOptions();
CustomOption.ShareOptionSelections();
}
}
}
}
+ [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<Il2CppSystem.Object>) })]
+ [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
{
-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<GithubRelease> 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<Scene, LoadSceneMode>)(OnSceneLoaded));
+ }
- public void Awake()
- {
- if (Instance) Destroy(this);
- Instance = this;
- SceneManager.add_sceneLoaded((System.Action<Scene, LoadSceneMode>) (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<List<GithubRelease>>(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);
PassiveButton passiveButton = button.GetComponent<PassiveButton>();
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<TMPro.TMP_Text>();
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<float>)(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 = $"<size=150%>A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available</size>\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}";
+ var announcement = $"<size=150%>A new THE OTHER ROLES update to {latestRelease.Tag} is available</size>\n{latestRelease.Description}";
var mgr = FindObjectOfType<MainMenuManager>(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($"<size=150%>A MANUAL UPDATE IS REQUIRED</size>"));
- }));
- }
- } 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<MainMenuManager>(true);
var popUpTemplate = UnityEngine.Object.FindObjectOfType<AnnouncementPopUp>(true);
if (popUpTemplate == null) {
}
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 = "",
}
})));
}
+ }
- [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<UpdateData> 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<bool> 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<GithubAsset> 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; }
+ }
}
FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(10f, new Action<float>((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) {
}
Color color;
- if (AnonymousFootprints || Camouflager.camouflageTimer > 0)
+ if (AnonymousFootprints || Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive())
{
color = Palette.PlayerColors[6];
}
using System.Linq;
using TheOtherRoles.Players;
using TheOtherRoles.Utilities;
+using Reactor.Utilities.Extensions;
namespace TheOtherRoles.Objects {
private GameObject gameObject;
public Vent vent;
private SpriteRenderer boxRenderer;
+ private SpriteRenderer ventRenderer;
public JackInTheBox(Vector2 p) {
gameObject = new GameObject("JackInTheBox"){layer = 11};
gameObject.transform.position = position;
boxRenderer = gameObject.AddComponent<SpriteRenderer>();
boxRenderer.sprite = getBoxAnimationSprite(0);
+ boxRenderer.color = boxRenderer.color.SetAlpha(0.5f);
// Create the vent
var referenceVent = UnityEngine.Object.FindObjectOfType<Vent>();
vent.Offset = new Vector3(0f, 0.25f, 0f);
vent.GetComponent<PowerTools.SpriteAnim>()?.Stop();
vent.Id = MapUtilities.CachedShipStatus.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id
- var ventRenderer = vent.GetComponent<SpriteRenderer>();
+ ventRenderer = vent.GetComponent<SpriteRenderer>();
+ if (Helpers.isFungle()) {
+ ventRenderer = vent.transform.GetChild(3).GetComponent<SpriteRenderer>();
+ var animator = vent.transform.GetChild(3).GetComponent<PowerTools.SpriteAnim>();
+ animator?.Stop();
+ }
+ //ventRenderer.Destroy();
ventRenderer.sprite = null; // Use the box.boxRenderer instead
vent.myRend = ventRenderer;
var allVentsList = MapUtilities.CachedShipStatus.AllVents.ToList();
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);
}
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;
}
int colorId = playerControl.Data.DefaultOutfit.ColorId;
- if (Camouflager.camouflageTimer > 0) {
+ if (Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive()) {
playerNameDisplay = "A camouflaged player";
colorId = 6;
}
+++ /dev/null
-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;
- }
- }
-}
[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;
public static void OptionsMenuBehaviour_StartPostfix(OptionsMenuBehaviour __instance)
{
if (!__instance.CensorChatButton) return;
-
+
if (!popUp)
{
CreateCustom(__instance);
}
-
if (!buttonPrefab)
{
buttonPrefab = Object.Instantiate(__instance.CensorChatButton);
moreOptionsButton.OnClick = new ButtonClickedEvent();
moreOptionsButton.OnClick.AddListener((Action) (() =>
{
+ bool closeUnderlying = false;
if (!popUp) return;
if (__instance.transform.parent && __instance.transform.parent == FastDestroyableSingleton<HudManager>.Instance.transform)
{
popUp.transform.SetParent(FastDestroyableSingleton<HudManager>.Instance.transform);
popUp.transform.localPosition = new Vector3(0, 0, -800f);
+ closeUnderlying = true;
}
else
{
CheckSetTitle();
RefreshOpen();
+ if (closeUnderlying)
+ __instance.Close();
}));
}
-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
Design by <color=#FCCE03FF>Bavari</color>";
public static string contributorsCredentials =
-$@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color></size>";
+$@"<size=60%> <color=#FCCE03FF>Special thanks to Smeggy</color></size>";
[HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))]
internal static class PingTrackerPatch
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);
if ((witchDiesWithExiledLover || exiledIsWitch) && Witch.witchVoteSavesTargets) Witch.futureSpelled = new List<PlayerControl>();
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) {
TORMapOptions.camerasToAdd = new List<SurvCamera>();
foreach (Vent vent in TORMapOptions.ventsToSeal) {
- PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
- animator?.Stop();
+ PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
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<SpriteRenderer>();
+ animator = vent.transform.GetChild(3).GetComponent<PowerTools.SpriteAnim>();
+ }
+ 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<Vent>();
// 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);
public static void Prefix(GameStartManager __instance) {
if (!GameData.Instance ) return; // No instance
+ __instance.MinPlayers = 1;
update = GameData.Instance.PlayerCount != __instance.LastPlayerCount;
}
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();
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!
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;
popUp = Object.Instantiate(popUpTemplate);
popUp.gameObject.SetActive(true);
- string creditsString = @$"<align=""center""><b>Github Contributors:</b>
+ string creditsString = @$"<align=""center""><b>Team:</b>
+Mallöris K3ndo Bavari Gendelo
+
+<b>Former Team Members:</b>
+Eisbison (GOAT) Thunderstorm584 EndOfFile
+
+<b>Additional Devs:</b>
+EnoPM twix NesTT
+
+<b>Github Contributors:</b>
Alex2911 amsyarasyiq MaximeGillot
Psynomit probablyadnf JustASysAdmin
-[https://discord.gg/77RkMJHWsM]Discord[] Moderators:
-Streamblox Draco Cordraconis
+<b>[https://discord.gg/77RkMJHWsM]Discord[] Moderators:</b>
+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 += $@"<size=60%> Other Credits & Resources:
+ creditsString += $@"<size=60%> <b>Other Credits & Resources:</b>
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
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;
}
})));
namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(MapBehaviour))]
- class MapBehaviourPatch {
- public static Dictionary<PlayerControl, SpriteRenderer> herePoints = new Dictionary<PlayerControl, SpriteRenderer>();
+ static class MapBehaviourPatch {
+ public static Dictionary<PlayerControl, SpriteRenderer> herePoints = new();
+
+ public static Sprite Vent = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Vent.png", 150f);
+
+ public static List<List<Vent>> VentNetworks = new();
+
+ public static Dictionary<string, GameObject> 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;
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<MapTaskOverlay>.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<SpriteRenderer>().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<SpriteRenderer>().sprite = JackInTheBox.getBoxAnimationSprite(0);
+ MapIcon.transform.localScale = new Vector3(0.5f, 0.5f, 1);
+ MapIcon.GetComponent<SpriteRenderer>().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<SpriteRenderer>().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<Vent> 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());
+ }
+ }
}
}
[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<SpriteRenderer>(__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<SpriteRenderer>(__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<PlayerVoteArea>();
+ if (component != null)
+ {
+ spriteRenderer.material.SetInt(PlayerMaterial.MaskLayer, component.MaskLayer);
+ }
+
+ __instance.StartCoroutine(Effects.Bloop(index * 0.3f, transform));
parent.GetComponent<VoteSpreader>().AddVote(spriteRenderer);
- voterPlayer.Object.SetColor(cId);
return false;
}
- }
+ }
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateResults))]
class MeetingHudPopulateVotesPatch {
- static bool Prefix(MeetingHud __instance, Il2CppStructArray<MeetingHud.VoterState> states) {
+ private static bool Prefix(MeetingHud __instance, Il2CppStructArray<MeetingHud.VoterState> states) {
// Swapper swap
PlayerVoteArea swapped1 = null;
// Resett Bait list
Bait.active = new Dictionary<DeadPlayer, float>();
// 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
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];
using TheOtherRoles.CustomGameModes;
using static UnityEngine.GraphicsBuffer;
using AmongUs.GameOptions;
+using Assets.CoreScripts;
using Sentry.Internal.Extensions;
using Reactor.Utilities.Extensions;
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
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;
}
}
}
+ 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) {
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;
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) {
}
}
+ // 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() {
// Update Player Info
updatePlayerInfo();
+ //Update pet visibility
+ setPetVisibility();
+
// Time Master
bendTimeUpdate();
// Morphling
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;
}
if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
{
- FastDestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
+ FastDestroyableSingleton<UnityTelemetry>.Instance.SendWho();
}
}
}
[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) {
}
if (isImpostor) return shipStatus.MaxLightRadius * GameOptionsManager.Instance.currentNormalGameOptions.ImpostorLightMod;
-
- SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast<SwitchSystem>();
- float lerpValue = switchSystem.Value / 255f;
+ float lerpValue = 1.0f;
+ try {
+ SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast<SwitchSystem>();
+ lerpValue = switchSystem.Value / 255f;
+ } catch { }
return Mathf.Lerp(shipStatus.MinLightRadius, shipStatus.MaxLightRadius, lerpValue) * GameOptionsManager.Instance.currentNormalGameOptions.CrewLightMod;
}
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) {
GameOptionsManager.Instance.currentNormalGameOptions.NumLongTasks = Mathf.RoundToInt(CustomOptionHolder.hideNSeekLongTasks.getFloat());
}
+ MapBehaviourPatch.VentNetworks.Clear();
return true;
}
--- /dev/null
+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<float>((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<float>((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;
+ }
+ }
+}
}
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;
}
}
+ [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() {
}
}
-
- 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<MeshRenderer> viewPorts = new();
Transform viewablesTransform = null;
if (SkeldCamsMinigame != null) {
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<GameObject>();
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<SpriteRenderer>();
overlayRenderer.sprite = overlaySprite;
overlayObject.SetActive(false);
}
}
-
- 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();
}
}
- private static void resetNightVision() {
+ public static void resetNightVision() {
foreach (var go in nightVisionOverlays) {
go.Destroy();
}
using TheOtherRoles.CustomGameModes;
using AmongUs.Data;
using AmongUs.GameOptions;
-using UnityEngine.UIElements;
+using Assets.CoreScripts;
namespace TheOtherRoles
{
GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 0;
SurveillanceMinigamePatch.nightVisionOverlays = null;
EventUtility.clearAndReload();
+ MapBehaviourPatch.clearAndReload();
}
public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) {
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) {
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;
}
SecurityGuard.remainingScrews -= SecurityGuard.ventPrice;
if (CachedPlayer.LocalPlayer.PlayerControl == SecurityGuard.securityGuard) {
PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
- 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<SpriteRenderer>();
+ animator = vent.transform.GetChild(3).GetComponent<PowerTools.SpriteAnim>();
+ }
+ 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;
}
}
}
+ 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<HudManager>.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;
if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton<HudManager>.Instance)
FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(guesser, msg);
if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
- FastDestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
+ FastDestroyableSingleton<UnityTelemetry>.Instance.SendWho();
}
}
PlayerControl player = Helpers.playerById(playerId);
var prop = PropHunt.FindPropByNameAndPos(propName, posX);
if (prop == null) return;
- player.GetComponent<SpriteRenderer>().sprite = prop.GetComponent<SpriteRenderer>().sprite;
+ try {
+ player.GetComponent<SpriteRenderer>().sprite = prop.GetComponent<SpriteRenderer>().sprite;
+ } catch {
+ player.GetComponent<SpriteRenderer>().sprite = prop.transform.GetComponentInChildren<SpriteRenderer>().sprite;
+ }
player.transform.localScale = prop.transform.lossyScale;
player.Visible = false;
PropHunt.currentObject[player.PlayerId] = new Tuple<string, float>(propName, posX);
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
}
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; }
public static Sprite getAdminSprite() {
byte mapId = GameOptionsManager.Instance.currentNormalGameOptions.MapId;
UseButtonSettings button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.PolusAdminButton]; // Polus
- if (mapId == 0 || mapId == 3) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks
- else if (mapId == 1) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ
- else if (mapId == 4) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship
+ if (Helpers.isSkeld() || mapId == 3) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks
+ else if (Helpers.isMira()) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ
+ else if (Helpers.isAirship()) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship
+ else if (Helpers.isFungle()) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Hacker can Access the Admin panel on Fungle
adminSprite = button.Image;
return adminSprite;
}
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;
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 { }
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
- <Version>4.4.2</Version>
+ <Version>4.5.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<PlatformTarget>x86</PlatformTarget>
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
- <PackageReference Include="AmongUs.GameLibs.Steam" Version="2023.7.11" />
- <PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.671" />
+ <PackageReference Include="AmongUs.GameLibs.Steam" Version="2023.11.28" />
+ <PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.671" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" />
<PackageReference Include="Reactor" Version="2.2.0" />
</ItemGroup>
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<EventTypes> eventQueue = null;
- public static bool eventInvert = false;
+ public enum EventTypes {
+ Communication,
+ Animation,
+ Invert,
+ KnockKnock
+ }
- public static void clearAndReload() {
- eventQueue = new List<EventTypes>();
- 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<EventTypes> 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<EventTypes>();
+ 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<float>((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<float>((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<CachedPlayer> 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<CachedPlayer> 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<CachedPlayer> 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<HudManager>.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Animation], new Action<float>((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<CachedPlayer> 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<HudManager>.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Animation], new Action<float>((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<HudManager>.Instance.roomTracker;
- string lastRoom = "";
- try { lastRoom = FastDestroyableSingleton<TranslationController>.Instance.GetString(tracker.LastRoom.RoomId); } catch { }
- msg += lastRoom != ""? lastRoom : (TheOtherRoles.rnd.Next(2) > 0 ? "sus" : "safe");
-
- FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(relevantPlayers[rnd.Next(relevantPlayers.Count)], $"{msg}");
- break;
- case EventTypes.Invert:
- eventInvert = true;
- FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Invert], new Action<float>((p) => {
- if (p == 1)
- eventInvert = false;
- })));
- break;
- case EventTypes.KnockKnock:
- SoundEffectsManager.play("knockKnock");
- knocked = true;
- break;
+ }
+ RoomTracker tracker = FastDestroyableSingleton<HudManager>.Instance.roomTracker;
+ string lastRoom = "";
+ try { lastRoom = FastDestroyableSingleton<TranslationController>.Instance.GetString(tracker.LastRoom.RoomId); } catch { }
+ msg += lastRoom != ""? lastRoom : (TheOtherRoles.rnd.Next(2) > 0 ? "sus" : "safe");
+
+ FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(relevantPlayers[rnd.Next(relevantPlayers.Count)], $"{msg}");
+ break;
+ case EventTypes.Invert:
+ eventInvert = true;
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Invert], new Action<float>((p) => {
+ if (p == 1)
+ eventInvert = false;
+ })));
+ break;
+ case EventTypes.KnockKnock:
+ SoundEffectsManager.play("knockKnock");
+ knocked = true;
+ break;
- }
}
}
-}
+}
\ No newline at end of file
"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",