| [Mafioso (Mafia)](#mafia) | [Engineer](#engineer) | [Arsonist](#arsonist) | [Anti Teleport](#anti-teleport) | [Guesser Modifier](#guesser-modifier) |
| [Janitor (Mafia)](#mafia) | [Sheriff](#sheriff) | [Jackal](#jackal) | [Tie Breaker](#tie-breaker) | [Hide 'n' Seek](#hide-n-seek) |
| [Morphling](#morphling) | [Deputy](#deputy) | [Sidekick](#sidekick) | [Bait](#bait) | [Vanilla Hide 'n' Seek](https://www.innersloth.com/new-game-mode-hide-n-seek-is-here-emergency-meeting-35/) |
-| [Camouflager](#camouflager) | [Lighter](#lighter) | [Vulture](#vulture) | [Lovers](#lovers) |
+| [Camouflager](#camouflager) | [Lighter](#lighter) | [Vulture](#vulture) | [Lovers](#lovers) | [PropHunt](#prophunt)|
| [Vampire](#vampire) | [Detective](#detective) | [Lawyer](#lawyer) | [Sunglasses](#sunglasses) |
| [Eraser](#eraser) | [Time Master](#time-master) | [Prosecutor](#prosecutor) | [Mini](#mini) |
| [Trickster](#trickster) | [Medic](#medic) | [Pursuer](#pursuer) | [VIP](#vip) |
# Changelog
<details>
<summary>Click to show the Changelog</summary>
-
+
+**Version 4.4.0**
+- Added a new gamemode, PropHunt. Players can disguise as objects on the map
+- Changed Lighter and Darker colors: L/D are no longer random, but rather assigned in alternating order.
+
**Version 4.3.4**
- Updated BepInEx dependency to 671
- Fixed compatibility to Submerged (not thouroughly tested)
[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies were inspired by the one from the **Crowded Mod Team**\
[Goose-Goose-Duck](https://store.steampowered.com/app/1568590/Goose_Goose_Duck) - Idea for the Vulture role came from **Slushiegoose**\
[TheEpicRoles](https://github.com/LaicosVK/TheEpicRoles) - Idea for the first kill shield (partly) and the tabbed option menu (fully + some code), by **LaicosVK** **DasMonschta** **Nova**\
-[Ninja](#ninja), [Thief](#thief), [Lawyer](#lawyer) / [Pursuer](#pursuer), [Deputy](#deputy), [Portalmaker](#portalmaker), [Guesser Modifier](#guesser-modifier) - Idea: [K3ndo](https://github.com/K3ndoo) ; Developed by [Gendelo](https://github.com/gendelo3) & [Mallöris](https://github.com/Mallaris)
+[Ninja](#ninja), [Thief](#thief), [Lawyer](#lawyer) / [Pursuer](#pursuer), [Deputy](#deputy), [Portalmaker](#portalmaker), [Guesser Modifier](#guesser-modifier) - Idea: [K3ndo](https://github.com/K3ndoo) ; Developed by [Gendelo](https://github.com/gendelo3) & [Mallöris](https://github.com/Mallaris) \
+[dMiner53](https://github.com/ugackMiner53/PropHunt) - Idea and core code for the Prop Hunt game mode
# Settings
The mod adds a few new settings to Among Us (in addition to the role settings):
- **Submission:** If you got a hat design, you can submit it on our [Discord server](https://discord.gg/77RkMJHWsM). We'll look at all the hats and add all the good ones to the game.
# Colors
+Note: Lighter and Darker colors are no longer based on the players color. Instead they are assigned alternatingly, s.t. they are always balanced in a lobby.

# Roles
| Kill Cooldown | - | Hunter Light Vision | - | Hunted Rewind Time | -
| Hunter Vision | - | Hunter Light Punish in Sec | - | Hunted Shield Number | -
| Hunted Vision | - | Hunter Admin Cooldown | -
-| Common Tasks | - | Hunter Admin Duration | -
-| Short Tasks | - | Hunter Admin Punish In Sec | -
-| Long Tasks | - | Hunter Arrow Cooldown | -
-| Timer In Min | - | Hunter Arrow Duration | -
+| | - | Hunter Admin Duration | -
+| | - | Hunter Admin Punish In Sec | -
+| | - | Hunter Arrow Cooldown | -
+| | - | Hunter Arrow Duration | -
| Task Win Is Possible | - | Hunter Arrow Punish In Sec | -
| Finish Tasks Punish In Sec | -
| Enable Sabotages | -
| Time The Hunter Needs To Wait | -
+## PropHunt
+The **Prop-Gamemode** is a standalone Gamemode where Hunters have to find the disguised players ("Props").\
+The Hunters and Props who are still alive are displayed to everyone in the bottom left corner (similar to the Arsonist display).\
+When the game starts, the Hunter's movement is disabled for x-seconds (depends on the settings), and their screen is blacked out.\
+There is a time-limit for each round, if the timer runs out and at least one Prop is still alive, the remaining Props win.\
+If the Hunters kill all players before the timer runs out the Hunters win.\
+When a Prop dies, they will either join the Hunter team or die (setting).
+
+#### Shared Abilities:
+- Unstuck: By pressing this button, you can travel through walls for a short time. This is needed, because props sometimes get stuck in walls, due to their changed colliders. Don't use it to cheat!
+
+#### Hunter Abilities:
+- Reveal: A random players current prop is revealed to all players for x seconds (according to settings). The timer will be reduced by *punish* s.
+- Mobile Admin table (like Hacker) - Normal Admin table shares the cooldown
+- Find: Display the DangerMeter known from Vanilla HideNSeek for x seconds. The dangerlevel is rising the closer the Hunter gets to the neares prop.
+#### Prop Abilities:
+- Invisibility: As a disguised Prop, become invisible for x seconds.
+- Speedboost: Move faster for x seconds.
+
+**NOTE:**
+- The Report button lights up, but cannot be pressed.
+- Hunters can vent
+- The Kill Button and Vent Button are permanently enabled, so that you cannot use them to check if there is a prop nearby
+- The Kill Button can always be pressed, but you can also miss if no Prop is nearby. The cooldown can be configured for hits and misses seperately.
+
+
+### Game Options
+| General | Description | Hunter | Description | Hunted | Description |
+|----------|:-------------:|----------|:-------------:|----------|:-------------:|
+| Map | - | Number of Hunters | - | Props Become Hunters When Found | -
+| Timer in Min | - | Initial Blackout Duration | - | Invisibility Enabled | -
+| Unstuck Cooldown | - | Kill Cooldown After Miss | - | Invisibility Cooldown | -
+| Unstuck Duration | - | Kill Cooldown After Hit | - | Invisibility Duration | -
+| Hunter Vision | - | Reveal Prop Cooldown | - | Speedboost Enabled | -
+| Prop Vision | - | Reveal Prop Duration | - | Speedboost Cooldown | -
+| | - | Reveal Time Punish | deducted from rem. timer | Speedboost Duration | -
+| | - | Hunter Admin Cooldown | - | Speedboost Ratio | Factor with which speed is multiplied
+| | - | Find Cooldown | -
+| | - | Find Duration | -
+
## GCERROR
If the error message "Fatal Error in GC - Collecting from unknown thread" stops you from playing the game, you can now disable the mod-updater, which causes this error.
In order to do this, create a file called `noupdater.txt` in your modded Among Us folder.
private static CustomButton hunterAdminTableButton;
private static CustomButton hunterArrowButton;
private static CustomButton huntedShieldButton;
+ public static CustomButton propDisguiseButton;
+ private static CustomButton propHuntUnstuckButton;
+ public static CustomButton propHuntRevealButton;
+ private static CustomButton propHuntInvisButton;
+ private static CustomButton propHuntSpeedboostButton;
+ public static CustomButton propHuntAdminButton;
+ public static CustomButton propHuntFindButton;
public static Dictionary<byte, List<CustomButton>> deputyHandcuffedButtons = null;
public static PoolablePlayer targetDisplay;
+ public static GameObject propSpriteHolder;
+ public static SpriteRenderer propSpriteRenderer;
public static TMPro.TMP_Text securityGuardButtonScrewsText;
public static TMPro.TMP_Text securityGuardChargesText;
huntedShieldButton.MaxTimer = Hunted.shieldCooldown;
defuseButton.MaxTimer = 0f;
defuseButton.Timer = 0f;
+ propDisguiseButton.MaxTimer = 1f;
+ propHuntUnstuckButton.MaxTimer = PropHunt.unstuckCooldown;
+ propHuntRevealButton.MaxTimer = PropHunt.revealCooldown;
+ propHuntInvisButton.MaxTimer = PropHunt.invisCooldown;
+ propHuntSpeedboostButton.MaxTimer = PropHunt.speedboostCooldown;
+ propHuntAdminButton.MaxTimer = PropHunt.adminCooldown;
+ propHuntFindButton.MaxTimer = PropHunt.findCooldown;
timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
hackerButton.EffectDuration = Hacker.duration;
huntedShieldButton.EffectDuration = Hunted.shieldDuration;
defuseButton.EffectDuration = Bomber.defuseDuration;
bomberButton.EffectDuration = Bomber.destructionTime + Bomber.bombActiveAfter;
+ propHuntUnstuckButton.EffectDuration = PropHunt.unstuckDuration;
+ propHuntRevealButton.EffectDuration = PropHunt.revealDuration;
+ propHuntInvisButton.EffectDuration = PropHunt.invisDuration;
+ propHuntSpeedboostButton.EffectDuration = PropHunt.speedboostDuration;
+ propHuntAdminButton.EffectDuration = PropHunt.adminDuration;
+ propHuntFindButton.EffectDuration = PropHunt.findDuration;
// Already set the timer to the max, as the button is enabled during the game and not available at the start
lightsOutButton.Timer = lightsOutButton.MaxTimer;
zoomOutButton.MaxTimer = 0f;
RPCProcedure.placeGarlic(buff);
SoundEffectsManager.play("garlic");
},
- () => { return !Vampire.localPlacedGarlic && !CachedPlayer.LocalPlayer.Data.IsDead && Vampire.garlicsActive && !HideNSeek.isHideNSeekGM; },
+ () => { return !Vampire.localPlacedGarlic && !CachedPlayer.LocalPlayer.Data.IsDead && Vampire.garlicsActive && !HideNSeek.isHideNSeekGM && !PropHunt.isPropHuntGM; },
() => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && !Vampire.localPlacedGarlic; },
() => { },
Vampire.getGarlicButtonSprite(),
if (GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1) {
if (SecurityGuard.minigame == null) {
byte mapId = GameOptionsManager.Instance.currentNormalGameOptions.MapId;
- UnityEngine.Object.FindObjectsOfType<SystemConsole>().ToList().ForEach(x => TheOtherRolesPlugin.Logger.LogMessage($"{x.name} {x.GetType()}, {x.MinigamePrefab.TaskType}"));
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"));
huntedShieldCountText.transform.localScale = Vector3.one * 0.5f;
huntedShieldCountText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
+ propDisguiseButton = new CustomButton(
+ () => {
+ // 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);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write(disguiseTarget.gameObject.name);
+ writer.Write(disguiseTarget.gameObject.transform.position.x);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.propHuntSetProp(CachedPlayer.LocalPlayer.PlayerId, disguiseTarget.gameObject.name, disguiseTarget.gameObject.transform.position.x);
+ SoundEffectsManager.play("morphlingMorph");
+ propDisguiseButton.Timer = 1f;
+ }
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => {
+ propSpriteRenderer.sprite = PropHunt.currentTarget?.GetComponent<SpriteRenderer>()?.sprite;
+ if (propSpriteRenderer.sprite != null)
+ propSpriteHolder.transform.localScale *= 1 / propSpriteRenderer.bounds.size.magnitude;
+ return PropHunt.currentTarget != null && PropHunt.currentTarget?.GetComponent<SpriteRenderer>()?.sprite != null;
+ },
+ () => {},
+ null,
+ CustomButton.ButtonPositions.lowerRowRight,
+ __instance,
+ KeyCode.F,
+ buttonText: "DISGUISE"
+ );
+ propSpriteHolder = new GameObject("TORPropButtonPropSpritePreview");
+ propSpriteRenderer = propSpriteHolder.AddComponent<SpriteRenderer>();
+ propSpriteHolder.transform.SetParent(propDisguiseButton.actionButtonGameObject.transform, false);
+ propSpriteHolder.transform.localPosition = new Vector3(0, 0, -2f);
+
+ propHuntUnstuckButton = new CustomButton(
+
+ () => {
+ PlayerControl.LocalPlayer.Collider.enabled = false;
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return true; },
+ () => { },
+ PropHunt.getUnstuckButtonSprite(),
+ CustomButton.ButtonPositions.upperRowLeft,
+ __instance,
+ KeyCode.LeftShift,
+ true,
+ 1f,
+ () => { PlayerControl.LocalPlayer.Collider.enabled = true;
+ propHuntUnstuckButton.Timer = propHuntUnstuckButton.MaxTimer;
+ },
+ buttonText: "UNSTUCK"
+ );
+
+ propHuntRevealButton = new CustomButton(
+ () => {
+ // select a random crewplayer to reveal.
+ var candidates = PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.Role.IsImpostor && !x.Data.IsDead && !PropHunt.isCurrentlyRevealed.ContainsKey(x.PlayerId)).ToList();
+ var rng = new System.Random();
+ PlayerControl selectedPlayer = candidates[rng.Next(candidates.Count)];
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetRevealed, Hazel.SendOption.Reliable, -1);
+ writer.Write(selectedPlayer.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.propHuntSetRevealed(selectedPlayer.PlayerId);
+
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.Data.Role.IsImpostor; },
+ () => { return PropHunt.timer - PropHunt.revealPunish > 0; },
+ () => { },
+ PropHunt.getRevealButtonSprite(),
+ CustomButton.ButtonPositions.upperRowFarLeft,
+ __instance,
+ KeyCode.R,
+ true,
+ 5f,
+ () => {
+ propHuntRevealButton.Timer = propHuntRevealButton.MaxTimer;
+ }
+ );
+
+ propHuntInvisButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PropHuntSetInvis, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.propHuntSetInvis(CachedPlayer.LocalPlayer.PlayerId);
+ SoundEffectsManager.play("morphlingMorph");
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.IsDead && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && PropHunt.enableInvis; },
+ () => { return PropHunt.currentObject.ContainsKey(PlayerControl.LocalPlayer.PlayerId); },
+ () => { },
+ PropHunt.getInvisButtonSprite(),
+ CustomButton.ButtonPositions.upperRowFarLeft,
+ __instance,
+ KeyCode.I,
+ true,
+ 5f,
+ () => {
+ SoundEffectsManager.play("morphlingMorph");
+ propHuntInvisButton.Timer = propHuntInvisButton.MaxTimer;
+ },
+ buttonText: "INVIS"
+ );
+
+ propHuntSpeedboostButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PropHuntSetSpeedboost, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.propHuntSetSpeedboost(CachedPlayer.LocalPlayer.PlayerId);
+ SoundEffectsManager.play("timemasterShield");
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.IsDead && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && PropHunt.enableSpeedboost; },
+ () => { return true; },
+ () => { },
+ PropHunt.getSpeedboostButtonSprite(),
+ CustomButton.ButtonPositions.lowerRowCenter,
+ __instance,
+ KeyCode.G,
+ true,
+ 5f,
+ () => {
+ SoundEffectsManager.stop("timemasterShield");
+ propHuntSpeedboostButton.Timer = propHuntSpeedboostButton.MaxTimer;
+ },
+ buttonText: "BOOST"
+ );
+
+ propHuntAdminButton = new CustomButton(
+ () => {
+ if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled) {
+ HudManager __instance = FastDestroyableSingleton<HudManager>.Instance;
+ __instance.InitMap();
+ MapBehaviour.Instance.ShowCountOverlay(allowedToMove: true, showLivePlayerPosition: true, includeDeadBodies: false);
+ }
+
+ CachedPlayer.LocalPlayer.NetTransform.Halt(); // Stop current movement
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.Data.Role.IsImpostor; },
+ () => {
+ propHuntAdminButton.PositionOffset = PlayerControl.LocalPlayer.inVent ? CustomButton.ButtonPositions.lowerRowRight : CustomButton.ButtonPositions.upperRowCenter;
+ return !PlayerControl.LocalPlayer.inVent; },
+ () => {
+ propHuntAdminButton.Timer = hunterAdminTableButton.MaxTimer;
+ propHuntAdminButton.isEffectActive = false;
+ propHuntAdminButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ },
+ Hacker.getAdminSprite(),
+ CustomButton.ButtonPositions.upperRowCenter,
+ __instance,
+ KeyCode.G,
+ true,
+ PropHunt.adminDuration,
+ () => {
+ propHuntAdminButton.Timer = propHuntAdminButton.MaxTimer;
+ if (MapBehaviour.Instance && MapBehaviour.Instance.isActiveAndEnabled) MapBehaviour.Instance.Close();
+ },
+ false,
+ "ADMIN"
+ );
+ propHuntFindButton = new CustomButton(
+ () => {
+ SoundEffectsManager.play("timemasterShield");
+ },
+ () => { return PropHunt.isPropHuntGM && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.Data.Role.IsImpostor; },
+ () => { return true; },
+ () => { },
+ PropHunt.getFindButtonSprite(),
+ CustomButton.ButtonPositions.lowerRowCenter,
+ __instance,
+ KeyCode.F,
+ true,
+ 5f,
+ () => {
+ SoundEffectsManager.stop("timemasterShield");
+ propHuntFindButton.Timer = propHuntFindButton.MaxTimer;
+ propHuntFindButton.isEffectActive = false;
+ },
+ buttonText: "FIND"
+ );
+
// Set the default (or settings from the previous game) timers / durations when spawning the buttons
initialized = true;
setCustomButtonCooldowns();
--- /dev/null
+using AmongUs.Data;
+using AmongUs.GameOptions;
+using HarmonyLib;
+using Hazel;
+using Reactor.Utilities;
+using Reactor.Utilities.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TheOtherRoles.Patches;
+using TheOtherRoles.Players;
+using TheOtherRoles.Utilities;
+using TMPro;
+using UnityEngine;
+
+namespace TheOtherRoles.CustomGameModes {
+ [HarmonyPatch]
+ class PropHunt {
+ public static bool isPropHuntGM = false;
+
+ public static Dictionary<byte, int> remainingShots = new Dictionary<byte, int>();
+ public static float timer = 20f;
+ public static bool timerRunning = false;
+ public static float blackOutTimer = 0f;
+
+ public static int numberOfHunters;
+ public static float initialBlackoutTime;
+ public static float killCooldownHit;
+ public static float killCooldownMiss;
+ public static float hunterVision;
+ public static float propVision;
+ public static float revealCooldown = 5f;
+ public static float revealDuration = 5f;
+ public static float unstuckDuration = 5f;
+ public static float unstuckCooldown = 5f;
+ public static float revealPunish = 0f;
+
+ public static float invisCooldown = 0f;
+ public static float invisDuration = 0f;
+ public static float speedboostCooldown = 0f;
+ public static float speedboostDuration = 0f;
+ public static float speedboostRatio = 0f;
+
+ public static float adminCooldown = 5f;
+ public static float adminDuration = 10f;
+
+ public static float findCooldown = 10f;
+ public static float findDuration = 10f;
+
+ public static bool enableSpeedboost = true;
+ public static bool enableInvis = true;
+
+ public static bool propBecomesHunterWhenFound;
+ private static Sprite disguiseButtonSprite;
+ private static Sprite unstuckButtonSprite;
+ private static Sprite revealButtonSprite;
+ private static Sprite invisButtonSprite;
+ private static Sprite speedboostButtonSprite;
+ private static Sprite findButtonSprite;
+ private static Sprite poolablesBackgroundSprite;
+ public static DateTime startTime = DateTime.UtcNow;
+ public static TMP_Text timerText;
+ public static List<string> whitelistedObjects = new List<string>() { };
+
+ public static Dictionary<byte, Tuple<string, float>> currentObject = new();
+ public static Dictionary<byte, float> isCurrentlyRevealed = new();
+ private static Dictionary<byte, GameObject> revealRenderer = new();
+ public static Dictionary<byte, float> invisPlayers = new();
+
+ public static Dictionary<byte, float> speedboostActive = new();
+
+ public static GameObject currentTarget = null;
+ private static GameObject poolablesBackground;
+
+ public static float dangerMeterActive = 0f;
+
+ private static List<GameObject> duplicatedCollider = new();
+ private static GameObject introObject;
+
+ public static void clearAndReload() {
+ remainingShots.Clear();
+ isPropHuntGM = TORMapOptions.gameMode == CustomGamemodes.PropHunt;
+ numberOfHunters = CustomOptionHolder.propHuntNumberOfHunters.getQuantity();
+ initialBlackoutTime = CustomOptionHolder.hunterInitialBlackoutTime.getFloat();
+ //maxMissesBeforeDeath = CustomOptionHolder.hunterMaxMissesBeforeDeath.getQuantity();
+ propBecomesHunterWhenFound = CustomOptionHolder.propBecomesHunterWhenFound.getBool();
+ killCooldownMiss = CustomOptionHolder.hunterMissCooldown.getFloat();
+ killCooldownHit = CustomOptionHolder.hunterHitCooldown.getFloat();
+ hunterVision = CustomOptionHolder.propHunterVision.getFloat();
+ propVision = CustomOptionHolder.propVision.getFloat();
+ timer = CustomOptionHolder.propHuntTimer.getFloat() * 60;
+ revealDuration = CustomOptionHolder.propHuntRevealDuration.getFloat();
+ revealCooldown = CustomOptionHolder.propHuntRevealCooldown.getFloat();
+ unstuckDuration = CustomOptionHolder.propHuntUnstuckDuration.getFloat();
+ unstuckCooldown = CustomOptionHolder.propHuntUnstuckCooldown.getFloat();
+ revealPunish = CustomOptionHolder.propHuntRevealPunish.getFloat();
+ invisCooldown = CustomOptionHolder.propHuntInvisCooldown.getFloat();
+ invisDuration = CustomOptionHolder.propHuntInvisDuration.getFloat();
+ speedboostCooldown = CustomOptionHolder.propHuntSpeedboostCooldown.getFloat();
+ speedboostDuration = CustomOptionHolder.propHuntSpeedboostDuration.getFloat();
+ speedboostRatio = CustomOptionHolder.propHuntSpeedboostSpeed.getFloat();
+ enableSpeedboost = CustomOptionHolder.propHuntSpeedboostEnabled.getBool();
+ enableInvis = CustomOptionHolder.propHuntInvisEnabled.getBool();
+ adminCooldown = CustomOptionHolder.propHuntAdminCooldown.getFloat();
+ findCooldown = CustomOptionHolder.propHuntFindCooldown.getFloat();
+ findDuration = CustomOptionHolder.propHuntFindDuration.getFloat();
+ timerRunning = false;
+ timerText?.Destroy();
+ timerText = null;
+ currentObject = new();
+ isCurrentlyRevealed = new();
+ poolablesBackground?.Destroy();
+ foreach (var go in revealRenderer.Values) { go.Destroy(); }
+ revealRenderer = new();
+ speedboostActive = new();
+ invisPlayers = new();
+ foreach (var go in duplicatedCollider) { go.Destroy(); }
+ duplicatedCollider = new();
+ }
+
+ public static Sprite getIntroSprite(int index) {
+ return Helpers.loadSpriteFromResources($"TheOtherRoles.Resources.IntroAnimation.intro_{index + 1000}.png", 150f, cache: false);
+ }
+
+ public static void update() {
+
+ if (!isPropHuntGM) {
+ // Make sure the DangerMeter is not displayed in TOR HideNSeek, Classic or Guesser Game mode.
+ if (GameOptionsManager.Instance.currentGameOptions.GameMode != AmongUs.GameOptions.GameModes.HideNSeek) HudManager.Instance.DangerMeter?.gameObject.SetActive(false);
+ return;
+ }
+ if (timerRunning) timer = Math.Clamp(timer -= Time.deltaTime, 0, timer >= 0 ? timer : 0);
+ else if (blackOutTimer > 0f) blackOutTimer -= Time.deltaTime;
+
+ // Local player find prop Target:
+ if (!PlayerControl.LocalPlayer.Data.Role.IsImpostor) currentTarget = FindClosestDisguiseObject(PlayerControl.LocalPlayer.gameObject, 1f);
+
+ if (timerText == null) {
+ RoomTracker roomTracker = FastDestroyableSingleton<HudManager>.Instance?.roomTracker;
+ if (roomTracker != null) {
+ GameObject gameObject = UnityEngine.Object.Instantiate(roomTracker.gameObject);
+
+ gameObject.transform.SetParent(FastDestroyableSingleton<HudManager>.Instance.transform);
+ UnityEngine.Object.DestroyImmediate(gameObject.GetComponent<RoomTracker>());
+ timerText = gameObject.GetComponent<TMP_Text>();
+
+ // Use local position to place it in the player's view instead of the world location
+ gameObject.transform.localPosition = new Vector3(0, -1.8f, gameObject.transform.localPosition.z);
+ if (AmongUs.Data.DataManager.Settings.Gameplay.StreamerMode) gameObject.transform.localPosition = new Vector3(0, 2f, gameObject.transform.localPosition.z);
+ }
+ } else {
+ if (timerRunning || blackOutTimer > 0f) {
+ var relevantTimer = timerRunning ? timer : blackOutTimer;
+ int minutes = (int)relevantTimer / 60;
+ int seconds = (int)relevantTimer % 60;
+ string suffix = $" {minutes:00}:{seconds:00}";
+ timerText.text = Helpers.cs(timerRunning ? Color.blue : Color.red, suffix);
+ timerText.outlineColor = Color.white;
+ timerText.outlineWidth = 0.1f;
+ timerText.color = timerRunning ? Color.blue : Color.red;
+ }
+ }
+ if (HudManagerStartPatch.propDisguiseButton != null && HudManagerStartPatch.propDisguiseButton.Timer > HudManagerStartPatch.propDisguiseButton.MaxTimer) HudManagerStartPatch.propDisguiseButton.Timer = HudManagerStartPatch.propDisguiseButton.MaxTimer;
+ // poolable players update.
+ if (poolablesBackground == null) {
+ poolablesBackground = new GameObject("poolablesBackground");
+ poolablesBackground.AddComponent<SpriteRenderer>();
+ if (poolablesBackgroundSprite == null) poolablesBackgroundSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.poolablesBackground.jpg", 200f);
+ }
+ poolablesBackground.transform.SetParent(HudManager.Instance.transform);
+ poolablesBackground.transform.localPosition = IntroCutsceneOnDestroyPatch.bottomLeft + new Vector3(-1.45f, -0.05f, 0) + Vector3.right * PlayerControl.AllPlayerControls.Count * 0.2f;
+ var backgroundSizeX = PlayerControl.AllPlayerControls.Count * 0.4f + 0.2f;
+ poolablesBackground.GetComponent<SpriteRenderer>().sprite = poolablesBackgroundSprite;
+ poolablesBackground.transform.localScale = new Vector3(poolablesBackground.transform.localScale.x * backgroundSizeX / poolablesBackground.GetComponent<SpriteRenderer>().bounds.size.x, poolablesBackground.transform.localScale.y, poolablesBackground.transform.localScale.z);
+
+ foreach (var pc in PlayerControl.AllPlayerControls) {
+ if (!TORMapOptions.playerIcons.ContainsKey(pc.PlayerId)) continue;
+ var poolablePlayer = TORMapOptions.playerIcons[pc.PlayerId];
+ if (pc.Data.IsDead) {
+ poolablePlayer.setSemiTransparent(true, 0.25f);
+ poolablePlayer.cosmetics.nameText.text = Helpers.cs(Palette.DisabledGrey, pc.Data.PlayerName); ;
+ } else if (pc.Data.Role.IsImpostor) {
+ poolablePlayer.cosmetics.nameText.text = Helpers.cs(Palette.ImpostorRed, pc.Data.PlayerName);
+ poolablePlayer.cosmetics.currentBodySprite.BodySprite.material.SetFloat("_Outline", 2f);
+ poolablePlayer.cosmetics.currentBodySprite.BodySprite.material.SetColor("_OutlineColor", Palette.ImpostorRed);
+ poolablePlayer.cosmetics.nameText.fontSize = 4;
+ } else {
+ // Display Prop
+ poolablePlayer.cosmetics.nameText.text = Helpers.cs(Palette.CrewmateBlue, pc.Data.PlayerName); ;
+ if (isCurrentlyRevealed.ContainsKey(pc.PlayerId)) {
+
+ }
+ }
+ // update currently revealed:
+ if (isCurrentlyRevealed.ContainsKey(pc.PlayerId)) {
+ if (!revealRenderer.ContainsKey(pc.PlayerId)) {
+ var go = new GameObject($"reveal_renderer_{pc.PlayerId}");
+ go.AddComponent<SpriteRenderer>();
+ go.transform.SetParent(poolablePlayer.transform.parent, false);
+ go.SetActive(true);
+ go.transform.localPosition = poolablePlayer.transform.localPosition + new Vector3(0, 0, -50f);
+ poolablePlayer.gameObject.SetActive(false);
+ revealRenderer.Add(pc.PlayerId, go);
+ }
+ float revealTimer = isCurrentlyRevealed[pc.PlayerId] - Time.deltaTime;
+ isCurrentlyRevealed[pc.PlayerId] = revealTimer;
+ if (revealTimer > 0) {
+ // get sprite:
+ if (currentObject.ContainsKey(pc.PlayerId)) {
+ revealRenderer[pc.PlayerId].GetComponent<SpriteRenderer>().sprite = pc.GetComponent<SpriteRenderer>().sprite;
+ revealRenderer[pc.PlayerId].transform.localScale *= 0.5f / revealRenderer[pc.PlayerId].GetComponent<SpriteRenderer>().bounds.size.magnitude;
+ }
+ } else {
+ revealRenderer[pc.PlayerId].Destroy();
+ isCurrentlyRevealed.Remove(pc.PlayerId);
+ revealRenderer.Remove(pc.PlayerId);
+ poolablePlayer.gameObject.SetActive(true);
+ SoundEffectsManager.play("morphlingMorph");
+ }
+ }
+ }
+
+ // speedboost
+ foreach (var key in speedboostActive.Keys) {
+ float speedboostTimer = speedboostActive[key] - Time.deltaTime;
+ speedboostActive[key] = speedboostTimer;
+ if (speedboostTimer < 0)
+ speedboostActive.Remove(key);
+ }
+ // invisupdate
+ foreach (var playerId in invisPlayers.Keys) {
+ var pc = Helpers.playerById(playerId);
+ if (pc == null || pc.Data.IsDead) continue;
+ float timeLeft = invisPlayers[playerId] - Time.deltaTime;
+ invisPlayers[playerId] = timeLeft;
+ if (timeLeft > 0) {
+ pc.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, PlayerControl.LocalPlayer.Data.IsDead || PlayerControl.LocalPlayer.PlayerId == playerId ? 0.1f: 0f);
+
+ }
+ else {
+ pc.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
+ invisPlayers.Remove(playerId);
+ }
+ if (isCurrentlyRevealed.ContainsKey(playerId)) {
+ revealRenderer[playerId].GetComponent<SpriteRenderer>().color = pc.GetComponent<SpriteRenderer>().color;
+ }
+ }
+
+ // Update dangerMeter
+ if (HudManager.Instance.DangerMeter.gameObject.active) {
+ float dist = 55f;
+ float dist2 = 15f;
+ float curr = float.MaxValue;
+ foreach (PlayerControl playerControl in PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead && (PlayerControl.LocalPlayer.Data.Role.IsImpostor ? !x.Data.Role.IsImpostor : x.Data.Role.IsImpostor))) {
+ if (invisPlayers.ContainsKey(playerControl.PlayerId)) continue; // Dont light up for invisible players
+ if (!(playerControl == null)) {
+
+ float sqrMagnitude = (playerControl.transform.position - PlayerControl.LocalPlayer.transform.position).sqrMagnitude;
+ if (sqrMagnitude < dist && curr > sqrMagnitude) {
+ curr = sqrMagnitude;
+ }
+ }
+ }
+ float dangerLevel1 = Mathf.Clamp01((dist - curr) / (dist - dist2));
+ float dangerLevel2 = Mathf.Clamp01((dist2 - curr) / dist2);
+ HudManager.Instance.DangerMeter.SetDangerValue(dangerLevel1, dangerLevel2);
+ }
+ HudManager.Instance.DangerMeter?.gameObject.SetActive(!PlayerControl.LocalPlayer.Data.IsDead && (!PlayerControl.LocalPlayer.Data.Role.IsImpostor || HudManagerStartPatch.propHuntFindButton.isEffectActive));
+ }
+
+ public static void transformLayers() { // A bit of a hacky way to make sure that props as well as propable objects are not visible in the dark, while keeping collisions enabled.
+ PlayerControl.LocalPlayer.clearAllTasks();
+ foreach (Collider2D collider in Physics2D.OverlapCircleAll(PlayerControl.LocalPlayer.transform.position, 500)) {
+ bool whiteListed = false;
+ foreach (var whiteListedWord in whitelistedObjects) if (collider.gameObject.name.Contains(whiteListedWord) && collider.gameObject.GetComponent<SpriteRenderer>() != null) whiteListed = true;
+ if (collider.GetComponent<Console>() != null || whiteListed) {
+ if (whiteListed) {
+ var newgo = GameObject.Instantiate(collider.gameObject, collider.transform.parent);
+ newgo.name = "DONTUSE";
+ duplicatedCollider.Add(newgo);
+ }
+
+ collider.gameObject.layer = PlayerControl.LocalPlayer.gameObject.layer;
+ }
+ }
+ }
+
+ public static Sprite getDisguiseButtonSprite() {
+ if (disguiseButtonSprite) return disguiseButtonSprite;
+ disguiseButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DisguiseButton.png", 115f);
+ return disguiseButtonSprite;
+ }
+
+ public static Sprite getUnstuckButtonSprite() {
+ if (unstuckButtonSprite) return unstuckButtonSprite;
+ unstuckButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.UnStuck.png", 115f);
+ return unstuckButtonSprite;
+ }
+ public static Sprite getRevealButtonSprite() {
+ if (revealButtonSprite) return revealButtonSprite;
+ revealButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Reveal.png", 115f);
+ return revealButtonSprite;
+ }
+
+ public static Sprite getInvisButtonSprite() {
+ if (invisButtonSprite) return invisButtonSprite;
+ invisButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.InvisButton.png", 115f);
+ return invisButtonSprite;
+ }
+
+ public static Sprite getFindButtonSprite() {
+ if (findButtonSprite) return findButtonSprite;
+ findButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.FindButton.png", 115f);
+ return findButtonSprite;
+ }
+ public static Sprite getSpeedboostButtonSprite() {
+ if (speedboostButtonSprite) return speedboostButtonSprite;
+ speedboostButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SpeedboostButton.png", 115f);
+ return speedboostButtonSprite;
+ }
+
+
+ public static GameObject FindClosestDisguiseObject(GameObject origin, float radius, bool verbose=false) {
+ // This throws errors if no console is nearby
+ 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();
+ }
+ foreach (Collider2D collider in Physics2D.OverlapCircleAll(origin.transform.position, radius)) {
+ if (verbose) {
+ TheOtherRolesPlugin.Logger.LogMessage($"Nearby Object: {collider.gameObject.name}");
+ }
+ bool whiteListed = false;
+ foreach (var whiteListedWord in whitelistedObjects) {
+ if (collider.gameObject.name.Contains(whiteListedWord)) whiteListed = true;
+ }
+ if (collider.GetComponent<Console>() != null || whiteListed) {
+ float dist = Vector2.Distance(origin.transform.position, collider.transform.position);
+ if (dist < bestDist) {
+ bestCollider = collider;
+ bestDist = dist;
+ }
+ }
+ }
+ return bestCollider.gameObject;
+ } catch { return null; }
+ }
+
+ public static GameObject FindPropByNameAndPos(string propName, float posX) {
+ var candidates = GameObject.FindObjectsOfType<GameObject>();
+ GameObject prop = null;
+ foreach (var candidate in candidates) {
+ if (candidate.name == propName && candidate.transform.position.x == posX)
+ prop = candidate;
+ }
+ return prop;
+ }
+
+ [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.FixedUpdate))]
+ [HarmonyPostfix]
+ public static void SpeedbostPostfix(PlayerPhysics __instance) {
+ if (!__instance.AmOwner || !speedboostActive.ContainsKey(__instance.myPlayer.PlayerId)) return;
+ if (GameData.Instance && __instance.myPlayer.CanMove)
+ __instance.body.velocity *= speedboostRatio;
+ }
+
+ [HarmonyPatch(typeof(CustomNetworkTransform), nameof(CustomNetworkTransform.FixedUpdate))]
+ [HarmonyPostfix]
+ public static void PostfixNetworkSpeed(CustomNetworkTransform __instance) {
+ if (__instance.AmOwner || !speedboostActive.ContainsKey(__instance.myPlayer.PlayerId)) return;
+ if (GameData.Instance && __instance.myPlayer.CanMove)
+ __instance.body.velocity *= speedboostRatio;
+ }
+
+ [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))]
+ [HarmonyPostfix]
+ public static void IntroCutsceneDestroyPatch(IntroCutscene __instance) {
+ if (!isPropHuntGM || !PlayerControl.LocalPlayer.Data.Role.IsImpostor) return;
+ PlayerControl.LocalPlayer.moveable = false;
+ MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PropHuntStartTimer, Hazel.SendOption.Reliable, -1);
+ writer2.Write(true);
+ AmongUsClient.Instance.FinishRpcImmediately(writer2);
+ RPCProcedure.propHuntStartTimer(true);
+ introObject = new GameObject("introrenderer");
+ introObject.layer = HudManager.Instance.FullScreen.gameObject.layer;
+ introObject.transform.SetParent(HudManager.Instance.FullScreen.transform);
+ introObject.transform.localPosition = new Vector3(0, 0, -1f);
+ SpriteRenderer introRenderer = introObject.AddComponent<SpriteRenderer>();
+ introObject.SetActive(true);
+ introRenderer.enabled = true;
+ int nFrames = 25 * (int)initialBlackoutTime + 10;
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(initialBlackoutTime, new Action<float>((p) => {
+ if (p == 1f) {
+ // start timer
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PropHuntStartTimer, Hazel.SendOption.Reliable, -1);
+ writer.Write(false);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.propHuntStartTimer();
+ PlayerControl.LocalPlayer.moveable = true;
+ HudManager.Instance.FullScreen.enabled = false;
+ introObject.Destroy();
+ } else {
+ HudManager.Instance.FullScreen.enabled = true;
+ HudManager.Instance.FullScreen.gameObject.SetActive(true);
+ HudManager.Instance.FullScreen.color = new Color(0, 0, 0, 1);
+ introRenderer.sprite?.Destroy();
+ introRenderer.sprite = getIntroSprite(510 - nFrames + (int)(p * nFrames));
+ Resources.UnloadUnusedAssets(); // Needed so that the last sprite gets unloaded
+ }
+ })));
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
+ [HarmonyPostfix]
+ public static void PlayerControlFixedUpdatePatch(PlayerControl __instance) {
+ if (!PropHunt.isPropHuntGM) return;
+ if (__instance.Data.Role.IsImpostor) {
+ __instance.GetComponent<CircleCollider2D>().radius = 0.2234f;
+ return;
+ }
+ if (__instance.GetComponent<SpriteRenderer>() != null || __instance.Data.IsDead) return;
+
+ __instance.gameObject.AddComponent<SpriteRenderer>();
+ __instance.GetComponent<CircleCollider2D>().radius = 0.00001f;
+ }
+
+
+ // Runs periodically, resets animation data for players
+ [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleAnimation))]
+ [HarmonyPostfix]
+ public static void PlayerPhysicsAnimationPatch(PlayerPhysics __instance) {
+ if (!AmongUsClient.Instance.IsGameStarted || !PropHunt.isPropHuntGM)
+ return;
+ try {
+ if (__instance.GetComponent<SpriteRenderer>().sprite != null && !__instance.myPlayer.Data.Role.IsImpostor) {
+ __instance.myPlayer.Visible = false;
+ __instance.GetComponent<SpriteRenderer>().flipX = __instance.myPlayer.cosmetics.currentBodySprite.BodySprite.flipX;
+ __instance.myPlayer.cosmetics.currentPet?.Destroy();
+ }
+ if (__instance.myPlayer.Data.IsDead) {
+ __instance.myPlayer.Visible = PlayerControl.LocalPlayer.Data.IsDead;
+ GameObject.Destroy(__instance.GetComponent<SpriteRenderer>());
+ }
+ } catch { }
+ }
+
+ // Make prop impostor on death
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Die))]
+ [HarmonyPostfix]
+ public static void MakePropImpostorPatch(PlayerControl __instance) {
+ if (!PropHunt.isPropHuntGM) return;
+ __instance.transform.localScale = new Vector3(0.7f, 0.7f, 1);
+ if (!__instance.Data.Role.IsImpostor && propBecomesHunterWhenFound) {
+ __instance.Revive();
+ DestroyableSingleton<RoleManager>.Instance.SetRole(__instance, RoleTypes.Impostor);
+ if (__instance == PlayerControl.LocalPlayer) {
+ HudManagerStartPatch.propHuntRevealButton.Timer = revealCooldown;
+ HudManagerStartPatch.propHuntFindButton.Timer = findCooldown;
+ HudManagerStartPatch.propHuntAdminButton.Timer = adminCooldown;
+ }
+ __instance.MyPhysics.SetBodyType(PlayerBodyTypes.Seeker);
+ } else {
+ // Find correct dead body, set sprite to dead console object...
+ Sprite currentPlayerSprite = __instance.GetComponent<SpriteRenderer>().sprite;
+ foreach (var db in UnityEngine.Object.FindObjectsOfType<DeadBody>())
+ if (db.ParentId == __instance.PlayerId && currentPlayerSprite != null) {
+ db.bodyRenderers[0].sprite = currentPlayerSprite;
+ db.bodyRenderers[0].color = new Color(0.5f, 0.5f, 0.5f, 0.5f);
+ }
+ }
+ __instance.GetComponent<SpriteRenderer>().sprite = null;
+ }
+
+ [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
+ [HarmonyPostfix]
+ public static void MapSetPostfix() { // Make sure the map in the settings is in sync with the map from li
+ if (TORMapOptions.gameMode != CustomGamemodes.PropHunt && TORMapOptions.gameMode != CustomGamemodes.HideNSeek || AmongUsClient.Instance.IsGameStarted) return;
+ int map = GameOptionsManager.Instance.currentGameOptions.MapId;
+ if (map > 3) map--;
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek)
+ if (CustomOptionHolder.hideNSeekMap.selection != map)
+ CustomOptionHolder.hideNSeekMap.updateSelection(map);
+ if (TORMapOptions.gameMode == CustomGamemodes.PropHunt)
+ if (CustomOptionHolder.propHuntMap.selection != map)
+ CustomOptionHolder.propHuntMap.updateSelection(map);
+ }
+
+
+ [HarmonyPatch(typeof(MapConsole), nameof(MapConsole.CanUse))]
+ [HarmonyPostfix]
+ public static void AdminCanUsePostfix(MapConsole __instance, GameData.PlayerInfo pc, ref bool canUse, ref bool couldUse, ref float __result) {
+ if (!PropHunt.isPropHuntGM || !PlayerControl.LocalPlayer.Data.Role.IsImpostor) return;
+ if (canUse) {
+ if (HudManagerStartPatch.propHuntAdminButton.Timer > 0) {
+ canUse = couldUse = false;
+ __result = float.MaxValue;
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(MapConsole), nameof(MapConsole.Use))]
+ [HarmonyPrefix]
+ public static bool AdminUsePostfix(MapConsole __instance) {
+ if (!PropHunt.isPropHuntGM || !PlayerControl.LocalPlayer.Data.Role.IsImpostor) return true;
+ HudManagerStartPatch.propHuntAdminButton.onClickEvent();
+ return false;
+ }
+
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
+ [HarmonyPostfix]
+ public static void MurderPlayerPostfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) {
+ if (!PropHunt.isPropHuntGM) return;
+ try {
+ target.NetTransform.RpcSnapTo(__instance.transform.position);
+ } catch { }
+ }
+
+ // Make it so that the kill button doesn't light up when near a player
+ [HarmonyPatch(typeof(VentButton), nameof(VentButton.SetTarget))]
+ [HarmonyPatch(typeof(KillButton), nameof(KillButton.SetTarget))]
+ [HarmonyPostfix]
+ public static void KillButtonHighlightPatch(ActionButton __instance) {
+ if (!PropHunt.isPropHuntGM) return;
+ __instance.SetEnabled();
+ }
+
+
+ // Penalize the impostor if there is no prop killed
+ [HarmonyPatch(typeof(KillButton), nameof(KillButton.CheckClick))]
+ [HarmonyPrefix]
+ public static bool CheckClickPatch(KillButton __instance) {
+ if (!PropHunt.isPropHuntGM) return true;
+ __instance.DoClick();
+ return false;
+ }
+
+ // Penalize the impostor if there is no prop killed
+ [HarmonyPatch(typeof(KillButton), nameof(KillButton.DoClick))]
+ [HarmonyPrefix]
+ public static bool KillButtonClickPatch(KillButton __instance) {
+ if (!PropHunt.isPropHuntGM || __instance.isCoolingDown || PlayerControl.LocalPlayer.Data.IsDead || PlayerControl.LocalPlayer.inVent) return false;
+
+ __instance.SetTarget(PlayerControl.LocalPlayer.Data.Role.GetPlayersInAbilityRangeSorted(RoleBehaviour.GetTempPlayerList(), true).ToArray().FirstOrDefault());
+
+ if (__instance.currentTarget == null) {
+ PlayerControl.LocalPlayer.SetKillTimer(killCooldownMiss);
+ } else { // There is a target, execute kill!
+ MurderAttemptResult res = Helpers.checkMurderAttemptAndKill(CachedPlayer.LocalPlayer.PlayerControl, __instance.currentTarget);
+ __instance.SetTarget(null);
+ PlayerControl.LocalPlayer.SetKillTimer(killCooldownHit);
+ }
+ return false;
+ }
+
+ [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show))]
+ [HarmonyPrefix]
+ public static void MapBehaviourShowPatch(MapBehaviour __instance, ref MapOptions opts) {
+ if (!PropHunt.isPropHuntGM) return;
+ if (opts.Mode == MapOptions.Modes.Sabotage) opts.Mode = MapOptions.Modes.Normal;
+ }
+
+
+ // Disable a lot of stuff
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
+ [HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))]
+ [HarmonyPrefix]
+ public static bool DisableFunctions() {
+ if (!PropHunt.isPropHuntGM) return true;
+ return false;
+ }
+ }
+}
public static CustomOption huntedShieldRewindTime;
public static CustomOption huntedShieldNumber;
+ // Prop Hunt Settings
+ public static CustomOption propHuntMap;
+ public static CustomOption propHuntTimer;
+ public static CustomOption propHuntNumberOfHunters;
+ public static CustomOption hunterInitialBlackoutTime;
+ public static CustomOption hunterMissCooldown;
+ public static CustomOption hunterHitCooldown;
+ public static CustomOption hunterMaxMissesBeforeDeath;
+ public static CustomOption propBecomesHunterWhenFound;
+ public static CustomOption propHunterVision;
+ public static CustomOption propVision;
+ public static CustomOption propHuntRevealCooldown;
+ public static CustomOption propHuntRevealDuration;
+ public static CustomOption propHuntRevealPunish;
+ public static CustomOption propHuntUnstuckCooldown;
+ public static CustomOption propHuntUnstuckDuration;
+ public static CustomOption propHuntInvisCooldown;
+ public static CustomOption propHuntInvisDuration;
+ public static CustomOption propHuntSpeedboostCooldown;
+ public static CustomOption propHuntSpeedboostDuration;
+ public static CustomOption propHuntSpeedboostSpeed;
+ public static CustomOption propHuntSpeedboostEnabled;
+ public static CustomOption propHuntInvisEnabled;
+ public static CustomOption propHuntAdminCooldown;
+ public static CustomOption propHuntFindCooldown;
+ public static CustomOption propHuntFindDuration;
+
internal static Dictionary<byte, byte[]> blockedRolePairings = new Dictionary<byte, byte[]>();
public static string cs(Color c, string s) {
return (byte)(f * 255);
}
+ public static bool isMapSelectionOption(CustomOption option) {
+ return option == CustomOptionHolder.propHuntMap && option == CustomOptionHolder.hideNSeekMap;
+ }
+
public static void Load() {
CustomOption.vanillaSettings = TheOtherRolesPlugin.Instance.Config.Bind("Preset0", "VanillaOptions", "");
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" }, null, true);
+ 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; });
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);
hideNSeekCommonTasks = CustomOption.Create(3023, Types.HideNSeekMain, cs(Color.yellow, "Common Tasks"), 1f, 0f, 4f, 1f);
hideNSeekShortTasks = CustomOption.Create(3024, Types.HideNSeekMain, cs(Color.yellow, "Short Tasks"), 3f, 1f, 23f, 1f);
hideNSeekLongTasks = CustomOption.Create(3025, Types.HideNSeekMain, cs(Color.yellow, "Long Tasks"), 3f, 0f, 15f, 1f);
- hideNSeekTimer = CustomOption.Create(3003, Types.HideNSeekMain, cs(Color.yellow, "Timer In Min"), 5f, 1f, 30f, 1f);
+ hideNSeekTimer = CustomOption.Create(3003, Types.HideNSeekMain, cs(Color.yellow, "Timer In Min"), 5f, 1f, 30f, 0.5f);
hideNSeekTaskWin = CustomOption.Create(3004, Types.HideNSeekMain, cs(Color.yellow, "Task Win Is Possible"), false);
hideNSeekTaskPunish = CustomOption.Create(3017, Types.HideNSeekMain, cs(Color.yellow, "Finish Tasks Punish In Sec"), 10f, 0f, 30f, 1f);
hideNSeekCanSabotage = CustomOption.Create(3019, Types.HideNSeekMain, cs(Color.yellow, "Enable Sabotages"), false);
huntedShieldRewindTime = CustomOption.Create(3018, Types.HideNSeekRoles, cs(Color.gray, "Hunted Rewind Time"), 3f, 1f, 10f, 1f);
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;});
+ 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);
+ propHunterVision = CustomOption.Create(4006, Types.PropHunt, cs(Color.yellow, "Hunter Vision"), 0.5f, 0.25f, 2f, 0.25f);
+ propVision = CustomOption.Create(4007, Types.PropHunt, cs(Color.yellow, "Prop Vision"), 2f, 0.25f, 5f, 0.25f);
+ // Hunter Options
+ propHuntNumberOfHunters = CustomOption.Create(4000, Types.PropHunt, cs(Color.red, "Number Of Hunters"), 1f, 1f, 5f, 1f, null, true);
+ hunterInitialBlackoutTime = CustomOption.Create(4001, Types.PropHunt, cs(Color.red, "Hunter Initial Blackout Duration"), 10f, 5f, 20f, 1f);
+ hunterMissCooldown = CustomOption.Create(4004, Types.PropHunt, cs(Color.red, "Kill Cooldown After Miss"), 10f, 2.5f, 60f, 2.5f);
+ hunterHitCooldown = CustomOption.Create(4005, Types.PropHunt, cs(Color.red, "Kill Cooldown After Hit"), 10f, 2.5f, 60f, 2.5f);
+ propHuntRevealCooldown = CustomOption.Create(4008, Types.PropHunt, cs(Color.red, "Reveal Prop Cooldown"), 30f, 10f, 90f, 2.5f);
+ propHuntRevealDuration = CustomOption.Create(4009, Types.PropHunt, cs(Color.red, "Reveal Prop Duration"), 5f, 1f, 60f, 1f);
+ propHuntRevealPunish = CustomOption.Create(4010, Types.PropHunt, cs(Color.red, "Reveal Time Punish"), 10f, 0f, 1800f, 5f);
+ propHuntAdminCooldown = CustomOption.Create(4022, Types.PropHunt, cs(Color.red, "Hunter Admin Cooldown"), 30f, 2.5f, 1800f, 2.5f);
+ propHuntFindCooldown = CustomOption.Create(4023, Types.PropHunt, cs(Color.red, "Find Cooldown"), 60f, 2.5f, 1800f, 2.5f);
+ propHuntFindDuration = CustomOption.Create(4024, Types.PropHunt, cs(Color.red, "Find Duration"), 5f, 1f, 15f, 1f);
+ // Prop Options
+ propBecomesHunterWhenFound = CustomOption.Create(4003, Types.PropHunt, cs(Palette.CrewmateBlue, "Props Become Hunters When Found"), false, null, true);
+ propHuntInvisEnabled = CustomOption.Create(4013, Types.PropHunt, cs(Palette.CrewmateBlue, "Invisibility Enabled"), true, null, true);
+ propHuntInvisCooldown = CustomOption.Create(4014, Types.PropHunt, cs(Palette.CrewmateBlue, "Invisibility Cooldown"), 120f, 10f, 1800f, 2.5f, propHuntInvisEnabled);
+ propHuntInvisDuration = CustomOption.Create(4015, Types.PropHunt, cs(Palette.CrewmateBlue, "Invisibility Duration"), 5f, 1f, 30f, 1f, propHuntInvisEnabled);
+ propHuntSpeedboostEnabled = CustomOption.Create(4016, Types.PropHunt, cs(Palette.CrewmateBlue, "Speedboost Enabled"), true, null, true);
+ propHuntSpeedboostCooldown = CustomOption.Create(4017, Types.PropHunt, cs(Palette.CrewmateBlue, "Speedboost Cooldown"), 60f, 2.5f, 1800f, 2.5f, propHuntSpeedboostEnabled);
+ propHuntSpeedboostDuration = CustomOption.Create(4018, Types.PropHunt, cs(Palette.CrewmateBlue, "Speedboost Duration"), 5f, 1f, 15f, 1f, propHuntSpeedboostEnabled);
+ propHuntSpeedboostSpeed = CustomOption.Create(4019, Types.PropHunt, cs(Palette.CrewmateBlue, "Speedboost Ratio"), 2f, 1.25f, 5f, 0.25f, propHuntSpeedboostEnabled);
+
+
+
// Other options
maxNumberOfMeetings = CustomOption.Create(3, Types.General, "Number Of Meetings (excluding Mayor meeting)", 10, 0, 15, 1, null, true);
blockSkippingInEmergencyMeetings = CustomOption.Create(4, Types.General, "Block Skipping In Emergency Meetings", false);
public enum CustomGamemodes {
Classic,
Guesser,
- HideNSeek
+ HideNSeek,
+ PropHunt
}
public static class Helpers
{
public static Dictionary<string, Sprite> CachedSprites = new();
- public static Sprite loadSpriteFromResources(string path, float pixelsPerUnit) {
+ public static Sprite loadSpriteFromResources(string path, float pixelsPerUnit, bool cache=true) {
try
{
- if (CachedSprites.TryGetValue(path + pixelsPerUnit, out var sprite)) return sprite;
+ if (cache && CachedSprites.TryGetValue(path + pixelsPerUnit, out var sprite)) return sprite;
Texture2D texture = loadTextureFromResources(path);
sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
- sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor;
+ if (cache) sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor;
+ if (!cache) return sprite;
return CachedSprites[path + pixelsPerUnit] = sprite;
- } catch {
+ } catch (Exception e) {
System.Console.WriteLine("Error loading sprite from path: " + path);
}
return null;
if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(exampleClip, false, 0.8f);
*/
}
+
+ public static string readTextFromResources(string path) {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ Stream stream = assembly.GetManifestResourceStream(path);
+ StreamReader textStreamReader = new StreamReader(stream);
+ return textStreamReader.ReadToEnd();
+ }
+
public static PlayerControl playerById(byte id)
{
foreach (PlayerControl player in CachedPlayer.AllPlayers)
return cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription}");
}
-
- public static bool isLighterColor(int colorId) {
- return CustomColors.lighterColors.Contains(colorId);
+
+ public static bool isD(byte playerId) {
+ return playerId % 2 == 0;
+ }
+
+ public static bool isLighterColor(PlayerControl target) {
+ return isD(target.PlayerId);
}
public static bool isCustomServer() {
player.Data.Tasks.Clear();
}
- public static void setSemiTransparent(this PoolablePlayer player, bool value) {
- float alpha = value ? 0.25f : 1f;
+ 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>())
r.color = new Color(r.color.r, r.color.g, r.color.b, alpha);
player.cosmetics.nameText.color = new Color(player.cosmetics.nameText.color.r, player.cosmetics.nameText.color.g, player.cosmetics.nameText.color.b, alpha);
if (AmongUsClient.Instance.IsGameOver) return MurderAttemptResult.SuppressKill;
if (killer == null || killer.Data == null || (killer.Data.IsDead && !ignoreIfKillerIsDead) || killer.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow non Impostor kills compared to vanilla code
if (target == null || target.Data == null || target.Data.IsDead || target.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow killing players in vents compared to vanilla code
-
- if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return MurderAttemptResult.PerformKill;
+ if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek || PropHunt.isPropHuntGM) return MurderAttemptResult.PerformKill;
// Handle first kill attempt
if (TORMapOptions.shieldFirstKill && TORMapOptions.firstKillPlayer == target) return MurderAttemptResult.SuppressKill;
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);
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "4.3.4";
+ public const string VersionString = "4.4.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);
[HarmonyPatch(typeof(HatParent), nameof(HatParent.LateUpdate))]
public static class HatParentLateUpdatePatch {
public static bool Prefix(HatParent __instance) {
- return false;
if (__instance.Parent) {
HatViewData hatViewData;
try {
using static TheOtherRoles.CustomOption;
using Reactor.Utilities.Extensions;
using AmongUs.GameOptions;
+using BepInEx.Unity.IL2CPP;
+using BepInEx;
+using static ShipStatus;
namespace TheOtherRoles {
public class CustomOption {
Modifier,
Guesser,
HideNSeekMain,
- HideNSeekRoles
+ HideNSeekRoles,
+ PropHunt,
}
public static List<CustomOption> options = new List<CustomOption>();
public CustomOption parent;
public bool isHeader;
public CustomOptionType type;
+ public Action onChange = null;
// Option creation
- public CustomOption(int id, CustomOptionType type, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader) {
+ public CustomOption(int id, CustomOptionType type, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader, Action onChange = null) {
this.id = id;
this.name = parent == null ? name : "- " + name;
this.selections = selections;
this.parent = parent;
this.isHeader = isHeader;
this.type = type;
+ this.onChange = onChange;
selection = 0;
if (id != 0) {
entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", id.ToString(), defaultSelection);
options.Add(this);
}
- public static CustomOption Create(int id, CustomOptionType type, string name, string[] selections, CustomOption parent = null, bool isHeader = false) {
- return new CustomOption(id, type, name, selections, "", parent, isHeader);
+ public static CustomOption Create(int id, CustomOptionType type, string name, string[] selections, CustomOption parent = null, bool isHeader = false, Action onChange = null) {
+ return new CustomOption(id, type, name, selections, "", parent, isHeader, onChange);
}
- public static CustomOption Create(int id, CustomOptionType type, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false) {
+ public static CustomOption Create(int id, CustomOptionType type, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false, Action onChange = null) {
List<object> selections = new();
for (float s = min; s <= max; s += step)
selections.Add(s);
- return new CustomOption(id, type, name, selections.ToArray(), defaultValue, parent, isHeader);
+ return new CustomOption(id, type, name, selections.ToArray(), defaultValue, parent, isHeader, onChange);
}
- public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false) {
- return new CustomOption(id, type, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader);
+ public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false, Action onChange = null) {
+ return new CustomOption(id, type, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader, onChange);
}
// Static behaviour
public void updateSelection(int newSelection) {
selection = Mathf.Clamp((newSelection + selections.Length) % selections.Length, 0, selections.Length - 1);
+ try {
+ if (onChange != null) onChange();
+ } catch { }
if (optionBehaviour != null && optionBehaviour is StringOption stringOption) {
stringOption.oldValue = stringOption.Value = selection;
stringOption.ValueText.text = selections[selection].ToString();
-
if (AmongUsClient.Instance?.AmHost == true && CachedPlayer.LocalPlayer.PlayerControl) {
if (id == 0 && selection != preset) {
switchPreset(selection); // Switch presets
switchPreset(selection);
ShareOptionSelections();// Share all selections
}
+
}
public static byte[] serializeOptions() {
case CustomGamemodes.HideNSeek:
createHideNSeekTabs(__instance);
break;
+ case CustomGamemodes.PropHunt:
+ createPropHuntTabs(__instance);
+ break;
}
// create copy to clipboard and paste from clipboard buttons.
gameSettingMenu.GameSettingsHightlight.enabled = false;
}
+
+ private static void createPropHuntTabs(GameOptionsMenu __instance) {
+ bool isReturn = setNames(
+ new Dictionary<string, string>() {
+ ["TORSettings"] = "Prop Hunt Settings"
+ });
+
+ if (isReturn) return;
+
+ // Setup TOR tab
+ var template = UnityEngine.Object.FindObjectsOfType<StringOption>().FirstOrDefault();
+ if (template == null) return;
+ var gameSettings = GameObject.Find("Game Settings");
+ var gameSettingMenu = UnityEngine.Object.FindObjectsOfType<GameSettingMenu>().FirstOrDefault();
+
+ var torSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent);
+ var torMenu = getMenu(torSettings, "TORSettings");
+ var roleTab = GameObject.Find("RoleTab");
+ var gameTab = GameObject.Find("GameTab");
+
+ var torTab = UnityEngine.Object.Instantiate(roleTab, gameTab.transform.parent);
+ var torTabHighlight = getTabHighlight(torTab, "TheOtherRolesTab", "TheOtherRoles.Resources.TabIconPropHuntSettings.png");
+
+ roleTab.active = false;
+ gameTab.active = false;
+
+ // Position of Tab Icons
+ torTab.transform.position += Vector3.left * 3f;
+
+ var tabs = new GameObject[] {torTab};
+ var settingsHighlightMap = new Dictionary<GameObject, SpriteRenderer> {
+ [torSettings.gameObject] = torTabHighlight
+ };
+ for (int i = 0; i < tabs.Length; i++) {
+ var button = tabs[i].GetComponentInChildren<PassiveButton>();
+ if (button == null) continue;
+ int copiedIndex = i;
+ button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
+ button.OnClick.AddListener((System.Action)(() => {
+ setListener(settingsHighlightMap, copiedIndex);
+ }));
+ }
+
+ destroyOptions(new List<List<OptionBehaviour>>{
+ torMenu.GetComponentsInChildren<OptionBehaviour>().ToList(),
+ });
+
+ List<OptionBehaviour> torOptions = new List<OptionBehaviour>();
+
+ List<Transform> menus = new List<Transform>() { torMenu.transform };
+ List<List<OptionBehaviour>> optionBehaviours = new List<List<OptionBehaviour>>() { torOptions };
+
+ for (int i = 0; i < CustomOption.options.Count; i++) {
+ CustomOption option = CustomOption.options[i];
+ if (option.type != CustomOptionType.PropHunt) continue;
+ if (option.optionBehaviour == null) {
+ int index = 0;
+ StringOption stringOption = UnityEngine.Object.Instantiate(template, menus[index]);
+ optionBehaviours[index].Add(stringOption);
+ stringOption.OnValueChanged = new Action<OptionBehaviour>((o) => { });
+ stringOption.TitleText.text = option.name;
+ stringOption.Value = stringOption.oldValue = option.selection;
+ stringOption.ValueText.text = option.selections[option.selection].ToString();
+
+ option.optionBehaviour = stringOption;
+ }
+ option.optionBehaviour.gameObject.SetActive(true);
+ }
+
+ setOptions(
+ new List<GameOptionsMenu> { torMenu},
+ new List<List<OptionBehaviour>> { torOptions },
+ new List<GameObject> { torSettings }
+ );
+
+ torSettings.gameObject.SetActive(true);
+ torTabHighlight.enabled = true;
+ gameSettingMenu.RegularGameSettings.SetActive(false);
+ gameSettingMenu.GameSettingsHightlight.enabled = false;
+ }
private static void setListener(Dictionary<GameObject, SpriteRenderer> settingsHighlightMap, int index) {
foreach (KeyValuePair<GameObject, SpriteRenderer> entry in settingsHighlightMap) {
return false;
}
}
-
+
[HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))]
public class StringOptionIncreasePatch
{
CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
if (option == null) return true;
option.updateSelection(option.selection + 1);
+ if (CustomOptionHolder.isMapSelectionOption(option)) {
+ IGameOptions currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
+ currentGameOptions.SetByte(ByteOptionNames.MapId, (byte)option.selection);
+ GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
+ GameManager.Instance.LogicOptions.SyncOptions();
+ }
return false;
}
}
CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
if (option == null) return true;
option.updateSelection(option.selection - 1);
+ if (CustomOptionHolder.isMapSelectionOption(option)) {
+ IGameOptions currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
+ currentGameOptions.SetByte(ByteOptionNames.MapId, (byte)option.selection);
+ GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
+ GameManager.Instance.LogicOptions.SyncOptions();
+ }
return false;
}
}
+ [HarmonyPatch(typeof(StringOption), nameof(StringOption.FixedUpdate))]
+ public class StringOptionFixedUpdate {
+ public static void Postfix(StringOption __instance) {
+ if (!IL2CPPChainloader.Instance.Plugins.TryGetValue("com.DigiWorm.LevelImposter", out PluginInfo _)) return;
+ CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
+ if (option == null || !CustomOptionHolder.isMapSelectionOption(option)) return;
+ if (GameOptionsManager.Instance.CurrentGameOptions.MapId == 6)
+ if (option.optionBehaviour != null && option.optionBehaviour is StringOption stringOption) {
+ stringOption.ValueText.text = option.selections[option.selection].ToString();
+ }
+ else if (option.optionBehaviour != null && option.optionBehaviour is StringOption stringOptionToo) {
+ stringOptionToo.oldValue = stringOptionToo.Value = option.selection;
+ stringOptionToo.ValueText.text = option.selections[option.selection].ToString();
+ }
+ }
+ }
+
+
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))]
public class RpcSyncSettingsPatch
{
float offset = 2.75f;
foreach (CustomOption option in CustomOption.options) {
- if (GameObject.Find("TORSettings") && option.type != CustomOption.CustomOptionType.General && option.type != CustomOptionType.HideNSeekMain)
+ if (GameObject.Find("TORSettings") && option.type != CustomOption.CustomOptionType.General && option.type != CustomOptionType.HideNSeekMain && option.type != CustomOptionType.PropHunt)
continue;
if (GameObject.Find("ImpostorSettings") && option.type != CustomOption.CustomOptionType.Impostor)
continue;
[HarmonyPatch]
class GameOptionsDataPatch
{
- /*private static IEnumerable<MethodBase> TargetMethods() {
- return typeof(IGameOptionsExtensions.).GetMethods().Where(x => x.ReturnType == typeof(string) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(int));
- }*/
-
private static string buildRoleOptions() {
var impRoles = buildOptionsOfType(CustomOption.CustomOptionType.Impostor, true) + "\n";
var neutralRoles = buildOptionsOfType(CustomOption.CustomOptionType.Neutral, true) + "\n";
options = options.Where(x => !(x.type == CustomOption.CustomOptionType.Guesser || x == CustomOptionHolder.crewmateRolesFill));
else if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek)
options = options.Where(x => (x.type == CustomOption.CustomOptionType.HideNSeekMain || x.type == CustomOption.CustomOptionType.HideNSeekRoles));
+ else if (TORMapOptions.gameMode == CustomGamemodes.PropHunt)
+ options = options.Where(x => (x.type == CustomOption.CustomOptionType.PropHunt));
foreach (var option in options) {
if (option.parent == null) {
foreach (CustomOption option in options) {
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek && option.type != CustomOptionType.HideNSeekMain && option.type != CustomOptionType.HideNSeekRoles) continue;
+ if (TORMapOptions.gameMode == CustomGamemodes.PropHunt && option.type != CustomOptionType.PropHunt) continue;
if (option.parent != null) {
bool isIrrelevant = option.parent.getSelection() == 0 || (option.parent.parent != null && option.parent.parent.getSelection() == 0);
return sb.ToString();
}
+
+ public static int maxPage = 7;
public static string buildAllOptions(string vanillaSettings = "", bool hideExtras = false) {
if (vanillaSettings == "")
vanillaSettings = GameOptionsManager.Instance.CurrentGameOptions.ToHudString(PlayerControl.AllPlayerControls.Count);
int counter = TheOtherRolesPlugin.optionsPage;
string hudString = counter != 0 && !hideExtras ? Helpers.cs(DateTime.Now.Second % 2 == 0 ? Color.white : Color.red, "(Use scroll wheel if necessary)\n\n") : "";
- int maxPage = 7;
+
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
if (TheOtherRolesPlugin.optionsPage > 1) TheOtherRolesPlugin.optionsPage = 0;
maxPage = 2;
hudString += "Page 2: Hide N Seek Role Settings \n\n" + buildOptionsOfType(CustomOption.CustomOptionType.HideNSeekRoles, false);
break;
}
+ } else if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) {
+ maxPage = 1;
+ switch (counter) {
+ case 0:
+ hudString += "Page 1: Prop Hunt Settings \n\n" + buildOptionsOfType(CustomOption.CustomOptionType.PropHunt, false);
+ break;
+ }
} else {
+ maxPage = 7;
switch (counter) {
case 0:
- hudString += (!hideExtras ? "" :"Page 1: Vanilla Settings \n\n") + vanillaSettings;
+ hudString += (!hideExtras ? "" : "Page 1: Vanilla Settings \n\n") + vanillaSettings;
break;
case 1:
hudString += "Page 2: The Other Roles Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.General, false);
}
if (Input.GetKeyDown(KeyCode.F1))
HudManagerUpdate.ToggleSettings(HudManager.Instance);
+ if (TheOtherRolesPlugin.optionsPage >= GameOptionsDataPatch.maxPage) TheOtherRolesPlugin.optionsPage = 0;
if (page != TheOtherRolesPlugin.optionsPage) {
Vector3 position = (Vector3)FastDestroyableSingleton<HudManager>.Instance?.GameSettings?.transform.localPosition;
blockCount++;
}
for (int i = 0; i < blockCount; i++) {
- TheOtherRolesPlugin.Logger.LogMessage(blockCount);
settingsTMPs[i].transform.localPosition = new Vector3(- blockCount * 1.2f + 2.7f * i, 2.2f, -500f);
}
}
{
// 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 (Il2CppSystem.IO.File.Exists(System.IO.Directory.GetCurrentDirectory() + "\\noupdater.txt")) yield break;
+ 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)))
float colorDuration = CustomOptionHolder.ninjaTraceColorTime.getFloat();
FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(colorDuration, new Action<float>((p) => {
Color c = Palette.PlayerColors[(int)Ninja.ninja.Data.DefaultOutfit.ColorId];
- if (Helpers.isLighterColor(Ninja.ninja.Data.DefaultOutfit.ColorId)) c = Color.white;
+ if (Helpers.isLighterColor(Ninja.ninja)) c = Color.white;
else c = Palette.PlayerColors[6];
//if (Camouflager.camouflageTimer > 0) {
// c = Palette.PlayerColors[6];
//}
- Color g = Color.green; // Usual display color. could also be Palette.PlayerColors[6] for default grey like camo
- // if this stays black (0,0,0), it can ofc be removed.
+ Color g = Color.green; // Usual display color.
Color combinedColor = Mathf.Clamp01(p) * g + Mathf.Clamp01(1 - p) * c;
firstPortal.animationFgRenderer.flipX = flip;
secondPortal.animationFgRenderer.flipX = flip;
if (Morphling.morphling != null && Morphling.morphTimer > 0) playerControl = Morphling.morphTarget; // Will output info of morph-target instead
- string playerNameDisplay = Portalmaker.logOnlyHasColors ? "A player (" + (Helpers.isLighterColor(playerControl.Data.DefaultOutfit.ColorId) ? "L" : "D") + ")" : playerControl.Data.PlayerName;
+ string playerNameDisplay = Portalmaker.logOnlyHasColors ? "A player (" + (Helpers.isLighterColor(playerControl) ? "L" : "D") + ")" : playerControl.Data.PlayerName;
int colorId = playerControl.Data.DefaultOutfit.ColorId;
return true;
}
- __instance.SetGameMode(GameModes.Normal); //__instance.Refresh();
-
- if ((int)mode == 3) {
+ __instance.SetGameMode(GameModes.Normal);
+ CustomGamemodes gm = (CustomGamemodes)((int) mode - 2);
+ if (gm == CustomGamemodes.Guesser) {
__instance.GameModeText.text = "TOR Guesser";
TORMapOptions.gameMode = CustomGamemodes.Guesser;
- } else {
+ } else if (gm == CustomGamemodes.HideNSeek) {
__instance.GameModeText.text = "TOR Hide N Seek";
TORMapOptions.gameMode = CustomGamemodes.HideNSeek;
+ } else if (gm == CustomGamemodes.PropHunt) {
+ __instance.GameModeText.text = "TOR Prop Hunt";
+ TORMapOptions.gameMode = CustomGamemodes.PropHunt;
}
return false;
}
}
else if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
__instance.GameModeText.text = "TOR Hide N Seek";
+ } else if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) {
+ __instance.GameModeText.text = "TOR Prop Hunt";
}
}
}
float num = ((float)Mathf.CeilToInt(4f / 10f) / 2f - 0.5f) * -2.5f; // 4 for 4 buttons!
__instance.controllerSelectable.Clear();
int num2 = 0;
- __instance.ButtonPool.poolSize = 4;
- for (int i=0; i <= 4; i++) {
+ __instance.ButtonPool.poolSize = 5;
+ for (int i=0; i <= 5; i++) {
GameModes entry = (GameModes)i;
if (entry != GameModes.None) {
ChatLanguageButton chatLanguageButton = __instance.ButtonPool.Get<ChatLanguageButton>();
chatLanguageButton.Text.text = DestroyableSingleton<TranslationController>.Instance.GetString(GameModesHelpers.ModeToName[entry], new Il2CppReferenceArray<Il2CppSystem.Object>(0));
else {
chatLanguageButton.Text.text = i == 3 ? "TOR Guesser" : "TOR Hide N Seek";
+ if (i == 5)
+ chatLanguageButton.Text.text = "TOR Prop Hunt";
}
chatLanguageButton.Button.OnClick.RemoveAllListeners();
chatLanguageButton.Button.OnClick.AddListener((System.Action)delegate {
__instance.ChooseOption(entry);
});
- bool isCurrentMode = i <= 2 && TORMapOptions.gameMode == CustomGamemodes.Classic ? (long)entry == (long)((ulong)gameMode) : (i == 3 && TORMapOptions.gameMode == CustomGamemodes.Guesser || i == 4 && TORMapOptions.gameMode == CustomGamemodes.HideNSeek);
+ bool isCurrentMode = i <= 2 && TORMapOptions.gameMode == CustomGamemodes.Classic ? (long)entry == (long)((ulong)gameMode) : (i == 3 && TORMapOptions.gameMode == CustomGamemodes.Guesser || i == 4 && TORMapOptions.gameMode == CustomGamemodes.HideNSeek || i == 5 && TORMapOptions.gameMode == CustomGamemodes.PropHunt);
chatLanguageButton.SetSelected(isCurrentMode);
__instance.controllerSelectable.Add(chatLanguageButton.Button);
if (isCurrentMode) {
string gameModeText = $"";
if (HideNSeek.isHideNSeekGM) gameModeText = $"Hide 'N Seek";
else if (HandleGuesser.isGuesserGm) gameModeText = $"Guesser";
+ else if (PropHunt.isPropHuntGM) gameModeText = "Prop Hunt";
if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n";
__instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays > 0 ? "-BETA" : "")}\n{gameModeText}" + __instance.text.text;
if (CachedPlayer.LocalPlayer.Data.IsDead || (!(CachedPlayer.LocalPlayer.PlayerControl == null) && (CachedPlayer.LocalPlayer.PlayerControl == Lovers.lover1 || CachedPlayer.LocalPlayer.PlayerControl == Lovers.lover2))) {
string gameModeText = $"";
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek";
else if (TORMapOptions.gameMode == CustomGamemodes.Guesser) gameModeText = $"Guesser";
+ else if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) gameModeText = $"Prop Hunt";
if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n";
__instance.text.text = $"{fullCredentialsVersion}\n {gameModeText + fullCredentials}\n {__instance.text.text}";
AdditionalTempData.additionalWinConditions.Add(WinCondition.AdditionalAlivePursuerWin);
}
- AdditionalTempData.timer = ((float)(DateTime.UtcNow - HideNSeek.startTime).TotalMilliseconds) / 1000;
+ AdditionalTempData.timer = ((float)(DateTime.UtcNow - (HideNSeek.isHideNSeekGM ? HideNSeek.startTime : PropHunt.startTime)).TotalMilliseconds) / 1000;
// Reset Settings
- if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) ShipStatusPatch.resetVanillaSettings();
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek ) ShipStatusPatch.resetVanillaSettings();
RPCProcedure.resetVariables();
EventUtility.gameEndsUpdate();
}
}
}
- if (TORMapOptions.showRoleSummary || HideNSeek.isHideNSeekGM) {
+ if (TORMapOptions.showRoleSummary || HideNSeek.isHideNSeekGM || PropHunt.isPropHuntGM) {
var position = Camera.main.ViewportToWorldPoint(new Vector3(0f, 1f, Camera.main.nearClipPlane));
GameObject roleSummary = UnityEngine.Object.Instantiate(__instance.WinText.gameObject);
roleSummary.transform.position = new Vector3(__instance.Navigation.ExitButton.transform.position.x + 0.1f, position.y - 0.1f, -214f);
roleSummary.transform.localScale = new Vector3(1f, 1f, 1f);
var roleSummaryText = new StringBuilder();
- if (HideNSeek.isHideNSeekGM) {
+ if (HideNSeek.isHideNSeekGM || PropHunt.isPropHuntGM) {
int minutes = (int)AdditionalTempData.timer / 60;
int seconds = (int)AdditionalTempData.timer % 60;
roleSummaryText.AppendLine($"<color=#FAD934FF>Time: {minutes:00}:{seconds:00}</color> \n");
}
private static bool CheckAndEndGameForTaskWin(ShipStatus __instance) {
- if (HideNSeek.isHideNSeekGM && !HideNSeek.taskWinPossible) return false;
+ if (HideNSeek.isHideNSeekGM && !HideNSeek.taskWinPossible || PropHunt.isPropHuntGM) return false;
if (GameData.Instance.TotalTasks > 0 && GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) {
//__instance.enabled = false;
GameManager.Instance.RpcEndGame(GameOverReason.HumansByTask, false);
}
private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerStatistics statistics) {
- if (HideNSeek.isHideNSeekGM)
+ if (HideNSeek.isHideNSeekGM || PropHunt.isPropHuntGM)
if ((0 != statistics.TotalAlive - statistics.TeamImpostorsAlive)) return false;
if (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && statistics.TeamJackalAlive == 0 && !(statistics.TeamImpostorHasAliveLover && statistics.TeamLoversAlive == 2)) {
GameManager.Instance.RpcEndGame(GameOverReason.HumansByVote, false);
return true;
}
+ if (PropHunt.isPropHuntGM && PropHunt.timer <= 0 && PropHunt.timerRunning) {
+ GameManager.Instance.RpcEndGame(GameOverReason.HumansByVote, false);
+ return true;
+ }
if (statistics.TeamImpostorsAlive == 0 && statistics.TeamJackalAlive == 0) {
//__instance.enabled = false;
GameManager.Instance.RpcEndGame(GameOverReason.HumansByVote, false);
break;
}
}
- if (continueStart && TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
- byte mapId = (byte) CustomOptionHolder.hideNSeekMap.getSelection();
+ 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();
if (mapId >= 3) mapId++;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.DynamicMapOption, Hazel.SendOption.Reliable, -1);
writer.Write(mapId);
using System.Linq;
using TheOtherRoles.Players;
using System;
+using TheOtherRoles.CustomGameModes;
namespace TheOtherRoles.Patches {
[HarmonyPatch]
[HarmonyPostfix]
[HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.Update))]
public static void showOrHideAbilityButtonPostfix(AbilityButton __instance) {
- bool isHideNSeek = GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek;
- if (CachedPlayer.LocalPlayer.Data.IsDead && (CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool() || isHideNSeek)) {
+ bool isGameMode = GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek || PropHunt.isPropHuntGM || HideNSeek.isHideNSeekGM;
+ if (CachedPlayer.LocalPlayer.Data.IsDead && (CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool() || isGameMode)) {
// player has haunt button.
var (playerCompleted, playerTotal) = TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data);
int numberOfLeftTasks = playerTotal - playerCompleted;
- if (numberOfLeftTasks <= 0 || isHideNSeek)
+ if (numberOfLeftTasks <= 0 || isGameMode)
__instance.Show();
else
__instance.Hide();
player.gameObject.SetActive(true);
}
+ } else if (PropHunt.isPropHuntGM) {
+ player.transform.localPosition = bottomLeft + new Vector3(-1.25f, -0.1f, 0) + Vector3.right * hideNSeekCounter++ * 0.4f;
+ player.transform.localScale = Vector3.one * 0.24f;
+ player.setSemiTransparent(false);
+ player.cosmetics.nameText.transform.localPosition += Vector3.up * 0.2f * (hideNSeekCounter % 2 == 0 ? 1 : -1);
+ player.SetFlipX(false);
+ player.gameObject.SetActive(true);
} else { // This can be done for all players not just for the bounty hunter as it was before. Allows the thief to have the correct position and scaling
player.transform.localPosition = bottomLeft;
player.transform.localScale = Vector3.one * 0.4f;
SoundEffectsManager.Load();
// First kill
- if (AmongUsClient.Instance.AmHost && TORMapOptions.shieldFirstKill && TORMapOptions.firstKillName != "" && !HideNSeek.isHideNSeekGM) {
+ if (AmongUsClient.Instance.AmHost && TORMapOptions.shieldFirstKill && TORMapOptions.firstKillName != "" && !HideNSeek.isHideNSeekGM && !PropHunt.isPropHuntGM) {
PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(TORMapOptions.firstKillName));
if (target != null) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetFirstKill, Hazel.SendOption.Reliable, -1);
Ottomated - Idea for the Morphling, Snitch and Camouflager role came from Ottomated
Crowded-Mod - Our implementation for 10+ player lobbies was inspired by the one from the Crowded Mod Team
Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose
-TheEpicRoles - Idea for the first kill shield (partly) and the tabbed option menu (fully + some code), by LaicosVK DasMonschta Nova</size>";
+TheEpicRoles - Idea for the first kill shield (partly) and the tabbed option menu (fully + some code), by LaicosVK DasMonschta Nova
+ugackMiner53 - Idea and core code for the Prop Hunt game mode</size>";
creditsString += "</align>";
Assets.InnerNet.Announcement creditsAnnouncement = new() {
template.OnClick();
}));
+ var PropHuntButton = GameObject.Instantiate<Transform>(gameButton, gameButton.parent);
+ PropHuntButton.transform.localPosition += new Vector3(3.4f, -0.5f);
+ var PropHuntButtonText = PropHuntButton.GetComponentInChildren<TMPro.TextMeshPro>();
+ var PropHuntButtonPassiveButton = PropHuntButton.GetComponentInChildren<PassiveButton>();
+
+ PropHuntButtonPassiveButton.OnClick = new Button.ButtonClickedEvent();
+ PropHuntButtonPassiveButton.OnClick.AddListener((System.Action)(() => {
+ TORMapOptions.gameMode = CustomGamemodes.PropHunt;
+ template.OnClick();
+ }));
+
template.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => {
guesserButtonText.SetText("TOR Guesser");
HideNSeekButtonText.SetText("TOR Hide N Seek");
- })));
+ PropHuntButtonText.SetText("TOR Prop Hunt");
+ })));
}));
}
}
using static UnityEngine.GraphicsBuffer;
using AmongUs.GameOptions;
using Sentry.Internal.Extensions;
+using Reactor.Utilities.Extensions;
namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
Snitch.text.text = $"Snitch is alive: " + playerCompleted + "/" + playerTotal;
if (snitchIsDead) Snitch.text.text = $"Snitch is dead!";
}
- }
+ } else if (Snitch.text != null)
+ Snitch.text.Destroy();
if (snitchIsDead) {
if (MeetingHud.Instance == null) Snitch.needsUpdate = false;
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return;
// Mini and Morphling shrink
- playerSizeUpdate(__instance);
+ if (!PropHunt.isPropHuntGM) playerSizeUpdate(__instance);
// set position of colorblind text
foreach (var pc in PlayerControl.AllPlayerControls) {
// -- GAME MODE --
hunterUpdate();
+ PropHunt.update();
}
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
class PlayerControlCmdReportDeadBodyPatch {
public static bool Prefix(PlayerControl __instance) {
- if (HideNSeek.isHideNSeekGM) return false;
+ if (HideNSeek.isHideNSeekGM || PropHunt.isPropHuntGM) return false;
Helpers.handleVampireBiteOnBodyReport();
return true;
}
if (timeSinceDeath < Detective.reportNameDuration * 1000) {
msg = $"Body Report: The killer appears to be {deadPlayer.killerIfExisting.Data.PlayerName}!";
} else if (timeSinceDeath < Detective.reportColorDuration * 1000) {
- var typeOfColor = Helpers.isLighterColor(deadPlayer.killerIfExisting.Data.DefaultOutfit.ColorId) ? "lighter" : "darker";
+ var typeOfColor = Helpers.isLighterColor(deadPlayer.killerIfExisting) ? "lighter" : "darker";
msg = $"Body Report: The killer appears to be a {typeOfColor} color!";
} else {
msg = $"Body Report: The corpse is too old to gain information from!";
[HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.GetAdjustedNumImpostors))]
class GameOptionsDataGetAdjustedNumImpostorsPatch {
public static void Postfix(ref int __result) {
- if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
- int impCount = Mathf.RoundToInt(CustomOptionHolder.hideNSeekHunterCount.getFloat());
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt) {
+ int impCount = TORMapOptions.gameMode == CustomGamemodes.HideNSeek ? Mathf.RoundToInt(CustomOptionHolder.hideNSeekHunterCount.getFloat()) : CustomOptionHolder.propHuntNumberOfHunters.getQuantity();
__result = impCount; ; // Set Imp Num
} else if (GameOptionsManager.Instance.CurrentGameOptions.GameMode == GameModes.Normal) { // Ignore Vanilla impostor limits in TOR Games.
__result = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.NumImpostors, 1, 3);
class GameOptionsDataValidatePatch {
public static void Postfix(GameOptionsData __instance) {
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || GameOptionsManager.Instance.CurrentGameOptions.GameMode != GameModes.Normal) return;
+ if (TORMapOptions.gameMode == CustomGamemodes.PropHunt)
+ __instance.NumImpostors = CustomOptionHolder.propHuntNumberOfHunters.getQuantity();
__instance.NumImpostors = GameOptionsManager.Instance.CurrentGameOptions.NumImpostors;
}
}
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.resetVariables();
- if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; // Don't assign Roles in Hide N Seek
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; // Don't assign Roles in Hide N Seek
if (CustomOptionHolder.activateRoles.getBool()) // Don't assign Roles in Tutorial or if deactivated
assignRoles();
}
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 Game Mode is PropHunt:
+ if (PropHunt.isPropHuntGM) {
+ if (!PropHunt.timerRunning) {
+ float progress = (PropHunt.blackOutTimer > 0f && PropHunt.blackOutTimer < 1f) ? 1 - PropHunt.blackOutTimer : 0f;
+ float minVision = __instance.MaxLightRadius * (PropHunt.propBecomesHunterWhenFound ? 0.25f : PropHunt.propVision);
+ __result = Mathf.Lerp(minVision, __instance.MaxLightRadius * PropHunt.propVision, progress); // For future start animation
+ } else {
+ __result = __instance.MaxLightRadius * (PlayerControl.LocalPlayer.Data.Role.IsImpostor ? PropHunt.hunterVision : PropHunt.propVision);
+ }
+ return false;
+ }
+
if (!HideNSeek.isHideNSeekGM || (HideNSeek.isHideNSeekGM && !Hunter.lightActive.Contains(player.PlayerId))) {
// If player is a role which has Impostor vision
if (Helpers.hasImpVision(player)) {
else if (HideNSeek.isHideNSeekGM && Hunter.isLightActive(player.PlayerId)) {
float unlerped = Mathf.InverseLerp(__instance.MinLightRadius, __instance.MaxLightRadius, GetNeutralLightRadius(__instance, false));
__result = Mathf.Lerp(__instance.MaxLightRadius * Hunter.lightVision, __instance.MaxLightRadius * Hunter.lightVision, unlerped);
+ return false;
}
// If there is a Trickster with their ability active
var normalTaskCount = __instance.NormalTasks.Count;
var longTaskCount = __instance.LongTasks.Count;
+ if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) {
+ commonTaskCount = normalTaskCount = longTaskCount = 0;
+ }
+
+
if (GameOptionsManager.Instance.currentNormalGameOptions.NumCommonTasks > commonTaskCount) GameOptionsManager.Instance.currentNormalGameOptions.NumCommonTasks = commonTaskCount;
if (GameOptionsManager.Instance.currentNormalGameOptions.NumShortTasks > normalTaskCount) GameOptionsManager.Instance.currentNormalGameOptions.NumShortTasks = normalTaskCount;
if (GameOptionsManager.Instance.currentNormalGameOptions.NumLongTasks > longTaskCount) GameOptionsManager.Instance.currentNormalGameOptions.NumLongTasks = longTaskCount;
if (CachedPlayer.LocalPlayer != null && MeetingHud.Instance != null && TORMapOptions.showLighterDarker) {
foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
var target = Helpers.playerById(player.TargetPlayerId);
- if (target != null) player.NameText.text += $" ({(Helpers.isLighterColor(target.Data.DefaultOutfit.ColorId) ? "L" : "D")})";
+ if (target != null) player.NameText.text += $" ({(Helpers.isLighterColor(target) ? "L" : "D")})";
}
}
}
}
static void updateSabotageButton(HudManager __instance) {
- if (MeetingHud.Instance || TORMapOptions.gameMode == CustomGamemodes.HideNSeek) __instance.SabotageButton.Hide();
+ if (MeetingHud.Instance || TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt) __instance.SabotageButton.Hide();
}
static void updateMapButton(HudManager __instance) {
[HarmonyPatch(typeof(KillButton), nameof(KillButton.DoClick))]
class KillButtonDoClickPatch {
public static bool Prefix(KillButton __instance) {
+ if (PropHunt.isPropHuntGM) {
+ KillAnimationCoPerformKillPatch.hideNextAnimation = true; // dont jump out of bounds!
+ return false;
+ }
if (__instance.isActiveAndEnabled && __instance.currentTarget && !__instance.isCoolingDown && !CachedPlayer.LocalPlayer.Data.IsDead && CachedPlayer.LocalPlayer.PlayerControl.CanMove) {
// Deputy handcuff update.
if (Deputy.handcuffedPlayers.Contains(CachedPlayer.LocalPlayer.PlayerId)) {
if (playerInfo != null) {
var color = Palette.PlayerColors[playerInfo.DefaultOutfit.ColorId];
if (Hacker.onlyColorType)
- color = Helpers.isLighterColor(playerInfo.DefaultOutfit.ColorId) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
+ color = Helpers.isD(playerInfo.PlayerId) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
roomColors.Add(color);
}
}
- } else {
+ } else if (!collider2D.isTrigger) {
PlayerControl component = collider2D.GetComponent<PlayerControl>();
if (component && component.Data != null && !component.Data.Disconnected && !component.Data.IsDead && (__instance.showLivePlayerPosition || !component.AmOwner) && hashSet.Add((int)component.PlayerId)) {
num2++;
if (component?.cosmetics?.currentBodySprite?.BodySprite?.material != null) {
Color color = component.cosmetics.currentBodySprite.BodySprite.material.GetColor("_BodyColor");
if (Hacker.onlyColorType) {
- var id = Mathf.Max(0, Palette.PlayerColors.IndexOf(color));
- color = Helpers.isLighterColor((byte)id) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
+ color = Helpers.isLighterColor(component) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
}
roomColors.Add(color);
}
static bool Prefix(MapBehaviour __instance) {
if (HideNSeek.isHideNSeekGM)
return HideNSeek.canSabotage;
+ if (PropHunt.isPropHuntGM) return false;
return true;
}
}
SetGuesserGm,
HuntedShield,
HuntedRewindTime,
+ SetProp,
+ SetRevealed,
+ PropHuntStartTimer,
+ PropHuntSetInvis,
+ PropHuntSetSpeedboost,
// Other functionality
ShareTimer,
CachedPlayer.LocalPlayer.PlayerControl.moveable = false;
}
+ public static void propHuntStartTimer(bool blackout = false) {
+ if (blackout) {
+ PropHunt.blackOutTimer = PropHunt.initialBlackoutTime;
+ PropHunt.transformLayers();
+ } else {
+ PropHunt.timerRunning = true;
+ PropHunt.blackOutTimer = 0f;
+ }
+ PropHunt.startTime = DateTime.UtcNow;
+ foreach (var pc in PlayerControl.AllPlayerControls.ToArray().Where(x => x.Data.Role.IsImpostor)) {
+ pc.MyPhysics.SetBodyType(PlayerBodyTypes.Seeker);
+ }
+ }
+
+ public static void propHuntSetProp(byte playerId, string propName, float posX) {
+ PlayerControl player = Helpers.playerById(playerId);
+ var prop = PropHunt.FindPropByNameAndPos(propName, posX);
+ if (prop == null) return;
+ player.GetComponent<SpriteRenderer>().sprite = prop.GetComponent<SpriteRenderer>().sprite;
+ player.transform.localScale = prop.transform.lossyScale;
+ player.Visible = false;
+ PropHunt.currentObject[player.PlayerId] = new Tuple<string, float>(propName, posX);
+ }
+
+ public static void propHuntSetRevealed(byte playerId) {
+ SoundEffectsManager.play("morphlingMorph");
+ PropHunt.isCurrentlyRevealed.Add(playerId, PropHunt.revealDuration);
+ PropHunt.timer -= PropHunt.revealPunish;
+ }
+ public static void propHuntSetInvis(byte playerId) {
+ PropHunt.invisPlayers.Add(playerId, PropHunt.invisDuration);
+ }
+ public static void propHuntSetSpeedboost(byte playerId) {
+ PropHunt.speedboostActive.Add(playerId, PropHunt.speedboostDuration);
+ }
+
public enum GhostInfoTypes {
HandcuffNoticed,
HandcuffOver,
byte rewindPlayer = reader.ReadByte();
RPCProcedure.huntedRewindTime(rewindPlayer);
break;
+ case (byte)CustomRPC.PropHuntStartTimer:
+ RPCProcedure.propHuntStartTimer(reader.ReadBoolean());
+ break;
+ case (byte)CustomRPC.SetProp:
+ byte targetPlayer = reader.ReadByte();
+ string propName = reader.ReadString();
+ float posX = reader.ReadSingle();
+ RPCProcedure.propHuntSetProp(targetPlayer, propName, posX);
+ break;
+ case (byte)CustomRPC.SetRevealed:
+ RPCProcedure.propHuntSetRevealed(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.PropHuntSetInvis:
+ RPCProcedure.propHuntSetInvis(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.PropHuntSetSpeedboost:
+ RPCProcedure.propHuntSetSpeedboost(reader.ReadByte());
+ break;
case (byte)CustomRPC.ShareGhostInfo:
RPCProcedure.receiveGhostInfo(reader.ReadByte(), reader);
break;
--- /dev/null
+liprop
+snowman
+Door
+nav_chair
+onsole
+comms-top
+SecurityLogger
+Table
+divertElevStand
+launch-pad-right
+barrier
+gun0001
+table
+bridge
+bigRock
+rockblock
+engine_railback
+medBayBedHort
+medBayBed
+lockersBench
+reactor_Border-Bottom (1)
+reactor_Border-Mid (1)
+reactor_Border-Top (1)
+cafShelf
+storage-boxes1
+storage-box2 (2)
+storage-top
+storage-mop
+weatherPanel
+BushesBottom
+PlanterFern
+PlanterBushTop
+admin-tablelower
+MapTable
+Admin
+Surv_Panel
+transformer0001 (1)
+transformer0001 (2)
+transformer0001 (3)
+panel_vitals
+projector
+EmergencyButton
+panel_map
+telescope
+storagebox
+rock3 (1)
+blockrock
+panel_cockpit_map
+task_records_right
+task_records_rightcen
+task_records_left_cen
+records_admin_map
+lounge_bathroomdoor (1)
+lounge_bathroomdoor (2)
+lounge_bathroomdoor (3)
+TopLeftVert
+LeftDoorBottom
+task_cams
+task_tapes_left
+panel_dummy
+vault_dummie (1)
+vault_dummie (2)
+vault_dummie (3)
+door_vault
+door_gap
+task_emergency
+LowerCentralVent
+LowerTank
+NormalPipesVent
+NormalElectricalVent
+Shelves
+NormalStorageVent
+NormalLobbyVent
+TankDeco
+PipeDeco
+NormalEnginesVent
+Main Turbine
+OpenEngineVent
+NormalSecurityVent
+NormalObservationVent
+NormalCafeteriaVent
+deco_labbench
+NormalMedicalVent
+UpperTank
+UpperCentralVent
+NormalCommsVent
+NormalUpperLobbyVent
+NormalMeetingVent
+console-mr-callmeeting
+deco-mr-table
+console-mr-fishfeed
+NormalAdminVent
\ No newline at end of file
public static RoleInfo hunter = new RoleInfo("Hunter", Palette.ImpostorRed, Helpers.cs(Palette.ImpostorRed, "Seek and kill everyone"), "Seek and kill everyone", RoleId.Impostor);
public static RoleInfo hunted = new RoleInfo("Hunted", Color.white, "Hide", "Hide", RoleId.Crewmate);
+ public static RoleInfo prop = new RoleInfo("Prop", Color.white, "Disguise As An Object and Survive", "Disguise As An Object", RoleId.Crewmate);
+
// Modifier
if (p == Pursuer.pursuer) infos.Add(pursuer);
if (p == Thief.thief) infos.Add(thief);
- // Default roles (just impostor, just crewmate, or hunter / hunted for hide n seek
+ // Default roles (just impostor, just crewmate, or hunter / hunted for hide n seek, prop hunt prop ...
if (infos.Count == count) {
if (p.Data.Role.IsImpostor)
- infos.Add(TORMapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunter : RoleInfo.impostor);
+ infos.Add(TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt ? RoleInfo.hunter : RoleInfo.impostor);
else
- infos.Add(TORMapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunted : RoleInfo.crewmate);
+ infos.Add(TORMapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunted : TORMapOptions.gameMode == CustomGamemodes.PropHunt ? RoleInfo.prop : RoleInfo.crewmate);
}
return infos;
}
- public static void play(string path, float volume=0.8f)
+ public static void play(string path, float volume=0.8f, bool loop = false)
{
if (!TORMapOptions.enableSoundEffects) return;
AudioClip clipToPlay = get(path);
// if (false) clipToPlay = get("exampleClip"); for april fools?
stop(path);
- if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(clipToPlay, false, volume);
+ if (Constants.ShouldPlaySfx()) {
+ AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, volume);
+ source.loop = loop;
+ }
}
public static void playAtPosition(string path, Vector2 position, float maxDuration = 15f, float range = 5f, bool loop = false) {
if (!TORMapOptions.enableSoundEffects || !Constants.ShouldPlaySfx()) return;
// Gamemodes
HandleGuesser.clearAndReload();
HideNSeek.clearAndReload();
+ PropHunt.clearAndReload();
}
}
} else {
int randomNumber = rnd.Next(4);
- string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting.Data.DefaultOutfit.ColorId) ? "lighter" : "darker";
+ string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting) ? "lighter" : "darker";
float timeSinceDeath = ((float)(Medium.meetingStartTime - Medium.target.timeOfDeath).TotalMilliseconds);
-
- if (randomNumber == 0) msg = "If my role hasn't been saved, there's no " + RoleInfo.GetRolesString(Medium.target.player, false) + " in the game anymore.";
- else if (randomNumber == 1) msg = "I'm not sure, but I guess a " + typeOfColor + " color killed me.";
+ var roleString = RoleInfo.GetRolesString(Medium.target.player, false);
+ if (randomNumber == 0) {
+ if (!roleString.Contains("Impostor") && !roleString.Contains("Crewmate"))
+ msg = "If my role hasn't been saved, there's no " + roleString + " in the game anymore.";
+ else
+ msg = "I am a " + roleString + " without an other role.";
+ } else if (randomNumber == 1) msg = "I'm not sure, but I guess a " + typeOfColor + " color killed me.";
else if (randomNumber == 2) msg = "If I counted correctly, I died " + Math.Round(timeSinceDeath / 1000) + "s before the next meeting started.";
- else msg = "It seems like my killer was the " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false, false, true) + ".";
+ else msg = "It seems like my killer is the " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false, false, true) + ".";
}
if (rnd.NextDouble() < chanceAdditionalInfo) {
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
- <Version>4.3.4</Version>
+ <Version>4.4.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
<LangVersion>latest</LangVersion>
},
"Samboy063.Cpp2IL.Core": {
"type": "Transitive",
- "resolved": "2022.1.0-development.876",
- "contentHash": "yomWiPcMGaQ2JmsP5C22taGSJ75IOGTDme1ysw5V4DLfIt1FBBpxC1XBom2RYG6MZ1SbI1yvEaYNCuAHLZ7+xw==",
+ "resolved": "2022.1.0-development.866",
+ "contentHash": "QPMuix/3jRngWBdNSLhAbm9BHYf95Ln1Ph1eCK/cDZdVibxFDFP3a6Yczyx1p7GbN7bRQwrhs2skPJSS/z3m2w==",
"dependencies": {
"AsmResolver.DotNet": "5.1.0",
"Disarm": "2022.1.0-master.26",
"Iced": "1.18.0",
- "Samboy063.LibCpp2IL": "2022.1.0-development.876",
- "StableNameDotNet": "0.1.0-development.876",
+ "Samboy063.LibCpp2IL": "2022.1.0-development.866",
+ "StableNameDotNet": "0.1.0-development.866",
"js6pak.Gee.External.Capstone": "2.1.0"
}
},
"Samboy063.LibCpp2IL": {
"type": "Transitive",
- "resolved": "2022.1.0-development.876",
- "contentHash": "KrpK5sySZHMPdeatiCqv5Pw8F7oV4fTRfVBvzKVmRlnO7Ye6Lg6n6t/PQ7VjLH6FiF2kH3YPM2uIp4xMJhGXGg==",
+ "resolved": "2022.1.0-development.866",
+ "contentHash": "EcJSWTfQko8SQozozoUT+aQJVWC8wj8C0YUiPBF4rNRId19yTKp2IXcXnI747v+8/aal222QR6WHfoBK2WlVLg==",
"dependencies": {
"AssetRipper.VersionUtilities": "1.3.1",
- "Samboy063.WasmDisassembler": "2022.1.0-development.876"
+ "Samboy063.WasmDisassembler": "2022.1.0-development.866"
}
},
"Samboy063.WasmDisassembler": {
"type": "Transitive",
- "resolved": "2022.1.0-development.876",
- "contentHash": "QDDrTjL/WoGP66Fc1HBtPXpmPn7F+0JYiV1xaZetkYtZ4UNMT7k6i2b6Ge4gq2xkK5NV0YpLyuQcVPeuy7u4XA=="
+ "resolved": "2022.1.0-development.866",
+ "contentHash": "2QK8F3eISkLcoW/Nt7wtxyQh31EXAqEMBVXXjEnuxyvXpBXxOIGpjmE3pWhGwxQwDCbe4zBsLF1zUiqq3/pXRA=="
},
"SemanticVersioning": {
"type": "Transitive",
},
"StableNameDotNet": {
"type": "Transitive",
- "resolved": "0.1.0-development.876",
- "contentHash": "e+yo57rKEJoQmTJ8scEbt6l/bqZvZu4hZUZMau/u1tyZI1I42JiXsjcH1+gE/nSQJlKPCykUrO/h+tUVm9I2nw=="
+ "resolved": "0.1.0-development.866",
+ "contentHash": "DJb8x4dV3ucDgM12sU28HxCdeqY/JudJbeJG6mgXWDJzpFZw2Jd7JF1pKFRNLzh/0gpo8bCGLQS9gOWZmBjdtg=="
},
"System.Collections": {
"type": "Transitive",