# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2024.11.26| v4.8.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.8.0/TheOtherRoles.zip)
| 2024.11.26| v4.7.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.7.0/TheOtherRoles.zip)
| 2024.6.18| v4.6.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.6.0/TheOtherRoles.zip)
# Changelog
<details>
<summary>Click to show the Changelog</summary>
+
+**Version 4.8.0**
+- Added an optional Role Draft mode, where players can select their role out of some roles that are shown to them.
+- Added a new option to allow the medic to shift the medic shield as well - no more invincible medics.
+- Added partial key rebinding - all kill buttons will now use the vanilla kill button shortcut, same for vent and each roles first ability.
+- Fixed the way options view panel to match vanilla changes
+- Fixed options not showing/hiding sub-options when switching Presets
+- Fixed a bug where voting the witch would not save the target
+- Fixed a some bugs with the trapper, bomber and portal
+- Fixed a bug in PropHunt where you players could not transform into props
+- Fixed the summary button for the last game appearing outside the lobby sometimes
+- Changed the positioning of the ping tracker in meetings
**Version 4.7.0**
- Updated to Among Us version 2024.11.26 (Vanilla Updates)
[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) \
[ugackMiner53](https://github.com/ugackMiner53/PropHunt) - Idea and core code for the Prop Hunt game mode
+Role Draft Music: [Unreal Superhero 3 by Kenët & Rez](https://www.youtube.com/watch?v=9STiQ8cCIo0)
# Settings
The mod adds a few new settings to Among Us (in addition to the role settings):
private static CustomButton propHuntSpeedboostButton;
public static CustomButton propHuntAdminButton;
public static CustomButton propHuntFindButton;
+ public static CustomButton eventKickButton;
public static Dictionary<byte, List<CustomButton>> deputyHandcuffedButtons = null;
public static PoolablePlayer targetDisplay;
Hacker.getAdminSprite(),
CustomButton.ButtonPositions.lowerRowRight,
__instance,
- KeyCode.Q,
+ KeyCode.G,
true,
0f,
() => {
Hacker.getVitalsSprite(),
CustomButton.ButtonPositions.lowerRowCenter,
__instance,
- KeyCode.Q,
+ KeyCode.H,
true,
0f,
() => {
Tracker.getTrackCorpsesButtonSprite(),
CustomButton.ButtonPositions.lowerRowCenter,
__instance,
- KeyCode.Q,
+ KeyCode.G,
true,
Tracker.corpsesTrackingDuration,
() => {
Portalmaker.getUsePortalButtonSprite(),
new Vector3(0.9f, -0.06f, 0),
__instance,
- KeyCode.H,
+ KeyCode.J,
mirror: true
);
Portalmaker.getUsePortalButtonSprite(),
new Vector3(0.9f, 1f, 0),
__instance,
- KeyCode.J,
+ KeyCode.G,
mirror: true
);
SecurityGuard.getCamSprite(),
CustomButton.ButtonPositions.lowerRowRight,
__instance,
- KeyCode.Q,
+ KeyCode.G,
true,
0f,
() => {
buttonText: "FIND"
);
+ eventKickButton = new CustomButton(
+ () => {
+ EventUtility.kickTarget();
+ },
+ () => { return EventUtility.isEnabled && Mini.mini != null && !Mini.mini.Data.IsDead && PlayerControl.LocalPlayer != Mini.mini; },
+ () => { return EventUtility.currentTarget != null; },
+ () => { },
+ EventUtility.getKickButtonSprite(),
+ CustomButton.ButtonPositions.highRowRight,
+ __instance,
+ KeyCode.K,
+ true,
+ 3f,
+ () => {
+ // onEffectEnds
+ eventKickButton.Timer = 69;
+ },
+ buttonText: "KICK"
+ );
+
// Set the default (or settings from the previous game) timers / durations when spawning the buttons
initialized = true;
setCustomButtonCooldowns();
using TMPro;
using UnityEngine;
using UnityEngine.Video;
-using static TheOtherRoles.Snitch;
-using static UnityEngine.GraphicsBuffer;
namespace TheOtherRoles.CustomGameModes {
[HarmonyPatch]
public static float dangerMeterActive = 0f;
private static List<GameObject> duplicatedCollider = new();
- private static GameObject introObject;
public static void clearAndReload() {
remainingShots.Clear();
}
bool whiteListed = false;
foreach (var whiteListedWord in whitelistedObjects) {
- if (collider.gameObject.name.Contains(whiteListedWord)) whiteListed = true;
+ if ((bool)(collider.gameObject?.name?.Contains(whiteListedWord))) whiteListed = true;
}
if (collider.GetComponent<Console>() != null || whiteListed) {
float dist = Vector2.Distance(origin.transform.position, collider.transform.position);
}
}
return bestCollider.gameObject;
- } catch { return null; }
+ } catch (Exception e) {
+ TheOtherRolesPlugin.Logger.LogError($"Error in find closest disguise object: {e}");
+ return null; }
}
public static GameObject FindPropByNameAndPos(string propName, float posX) {
HudManager.Instance.FullScreen.enabled = false;
videoPlayer.Destroy();
assetBundle.Unload(false);
- introObject.Destroy();
} else {
HudManager.Instance.FullScreen.enabled = true;
HudManager.Instance.FullScreen.gameObject.SetActive(true);
[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;
+ int? map = GameOptionsManager.Instance?.currentGameOptions?.MapId;
+ if (map == null) return;
if (map > 3) map--;
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek)
if (CustomOptionHolder.hideNSeekMap.selection != map)
- CustomOptionHolder.hideNSeekMap.updateSelection(map);
+ CustomOptionHolder.hideNSeekMap.updateSelection((int)map);
if (TORMapOptions.gameMode == CustomGamemodes.PropHunt)
if (CustomOptionHolder.propHuntMap.selection != map)
- CustomOptionHolder.propHuntMap.updateSelection(map);
+ CustomOptionHolder.propHuntMap.updateSelection((int)map);
}
public static CustomOption modifiersCountMin;
public static CustomOption modifiersCountMax;
+ public static CustomOption isDraftMode;
+ public static CustomOption draftModeAmountOfChoices;
+ public static CustomOption draftModeTimeToChoose;
+ public static CustomOption draftModeShowRoles;
+ public static CustomOption draftModeHideImpRoles;
+ public static CustomOption draftModeHideNeutralRoles;
+
public static CustomOption anyPlayerCanStopStart;
public static CustomOption enableEventMode;
+ public static CustomOption eventReallyNoMini;
+ public static CustomOption eventKicksPerRound;
+ public static CustomOption eventHeavyAge;
public static CustomOption deadImpsBlockSabotage;
public static CustomOption mafiaSpawnRate;
public static CustomOption modifierArmored;
public static CustomOption modifierShifter;
+ public static CustomOption modifierShifterShiftsMedicShield;
public static CustomOption maxNumberOfMeetings;
public static CustomOption blockSkippingInEmergencyMeetings;
if (Utilities.EventUtility.canBeEnabled) enableEventMode = CustomOption.Create(10423, Types.General, cs(Color.green, "Enable Special Mode"), true, null, true);
+ isDraftMode = CustomOption.Create(600, Types.General, cs(Color.yellow, "Enable Role Draft"), false, null, true, null, "Role Draft");
+ draftModeAmountOfChoices = CustomOption.Create(601, Types.General, cs(Color.yellow, "Max Amount Of Roles\nTo Choose From"), 5f, 2f, 15f, 1f, isDraftMode, false);
+ draftModeTimeToChoose = CustomOption.Create(602, Types.General, cs(Color.yellow, "Time For Selection"), 5f, 3f, 20f, 1f, isDraftMode, false);
+ draftModeShowRoles = CustomOption.Create(603, Types.General, cs(Color.yellow, "Show Picked Roles"), false, isDraftMode, false);
+ draftModeHideImpRoles = CustomOption.Create(604, Types.General, cs(Color.yellow, "Hide Impostor Roles"), false, draftModeShowRoles, false);
+ draftModeHideNeutralRoles = CustomOption.Create(605, Types.General, cs(Color.yellow, "Hide Neutral Roles"), false, draftModeShowRoles, false);
+
// Using new id's for the options to not break compatibilty with older versions
crewmateRolesCountMin = CustomOption.Create(300, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 15f, 0f, 15f, 1f, null, true, heading: "Min/Max Roles");
crewmateRolesCountMax = CustomOption.Create(301, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 15f, 0f, 15f, 1f);
modifierMini = CustomOption.Create(1061, Types.Modifier, cs(Color.yellow, "Mini"), rates, null, true);
modifierMiniGrowingUpDuration = CustomOption.Create(1062, Types.Modifier, "Mini Growing Up Duration", 400f, 100f, 1500f, 100f, modifierMini);
modifierMiniGrowingUpInMeeting = CustomOption.Create(1063, Types.Modifier, "Mini Grows Up In Meeting", true, modifierMini);
+ if (Utilities.EventUtility.canBeEnabled || Utilities.EventUtility.isEnabled) {
+ eventKicksPerRound = CustomOption.Create(10424, Types.Modifier, cs(Color.green, "Maximum Kicks Mini Suffers"), 4f, 0f, 14f, 1f, modifierMini);
+ eventHeavyAge = CustomOption.Create(10425, Types.Modifier, cs(Color.green, "Age At Which Mini Is Heavy"), 12f, 6f, 18f, 0.5f, modifierMini);
+ eventReallyNoMini = CustomOption.Create(10426, Types.Modifier, cs(Color.green, "Really No Mini :("), false, modifierMini, invertedParent: true);
+ }
modifierVip = CustomOption.Create(1070, Types.Modifier, cs(Color.yellow, "VIP"), rates, null, true);
modifierVipQuantity = CustomOption.Create(1071, Types.Modifier, cs(Color.yellow, "VIP Quantity"), ratesModifier, modifierVip);
modifierArmored = CustomOption.Create(1101, Types.Modifier, cs(Color.yellow, "Armored"), rates, null, true);
modifierShifter = CustomOption.Create(1100, Types.Modifier, cs(Color.yellow, "Shifter"), rates, null, true);
+ modifierShifterShiftsMedicShield = CustomOption.Create(1102, Types.Modifier, "Can Shift Medic Shield", false, modifierShifter);
// Guesser Gamemode (2000 - 2999)
guesserGamemodeCrewNumber = CustomOption.Create(2001, Types.Guesser, cs(Guesser.color, "Number of Crew Guessers"), 15f, 0f, 15f, 1f, null, true, heading: "Amount of Guessers");
}
public static class Helpers
{
-
+ public static string previousEndGameSummary = "";
public static Dictionary<string, Sprite> CachedSprites = new();
public static Sprite loadSpriteFromResources(string path, float pixelsPerUnit, bool cache=true) {
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "4.7.0";
+ public const string VersionString = "4.8.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);
str = ColorUtility.ToHtmlStringRGB(c);
color = c.r + c.g + c.b > 180 ? Palette.Black : Palette.White;
- TheOtherRolesPlugin.Logger.LogMessage($"{c.r}, {c.g}, {c.b}");
}
__instance.playerColorText.text = __instance.player.ColorBlindName;
__instance.playerNameText.text = "<color=#" + str + ">" + (string.IsNullOrEmpty(sender.Data.PlayerName) ? "..." : sender.Data.PlayerName);
public CustomOptionType type;
public Action onChange = null;
public string heading = "";
+ public bool invertedParent;
// Option creation
- public CustomOption(int id, CustomOptionType type, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader, Action onChange = null, string heading = "") {
+ public CustomOption(int id, CustomOptionType type, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader, Action onChange = null, string heading = "", bool invertedParent = false) {
this.id = id;
this.name = parent == null ? name : "- " + name;
this.selections = selections;
this.type = type;
this.onChange = onChange;
this.heading = heading;
+ this.invertedParent = invertedParent;
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, Action onChange = null, string heading = "") {
- return new CustomOption(id, type, name, selections, "", parent, isHeader, onChange, heading);
+ public static CustomOption Create(int id, CustomOptionType type, string name, string[] selections, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "", bool invertedParent = false) {
+ return new CustomOption(id, type, name, selections, "", parent, isHeader, onChange, heading, invertedParent);
}
- 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, string heading = "") {
+ 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, string heading = "", bool invertedParent = false) {
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, onChange, heading);
+ return new CustomOption(id, type, name, selections.ToArray(), defaultValue, parent, isHeader, onChange, heading, invertedParent);
}
- public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "") {
- return new CustomOption(id, type, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader, onChange, heading);
+ public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "", bool invertedParent = false) {
+ return new CustomOption(id, type, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader, onChange, heading, invertedParent);
}
// Static behaviour
stringOption.ValueText.text = option.selections[option.selection].ToString();
}
}
+
+ // make sure to reload all tabs, even the ones in the background, because they might have changed when the preset was switched!
+ if (AmongUsClient.Instance?.AmHost == true) {
+ foreach (var entry in GameOptionsMenuStartPatch.currentGOMs) {
+ CustomOptionType optionType = (CustomOptionType)entry.Key;
+ GameOptionsMenu gom = entry.Value;
+ if (gom != null) {
+ GameOptionsMenuStartPatch.updateGameOptionsMenu(optionType, gom);
+ }
+ }
+ }
}
public static void saveVanillaOptions() {
try {
if (onChange != null) onChange();
} catch { }
- if (AmongUsClient.Instance?.AmHost == true) {
- var currentTab = GameOptionsMenuStartPatch.currentTabs.FirstOrDefault(x => x.active).GetComponent<GameOptionsMenu>();
- if (currentTab != null) {
- var optionType = options.First(x => x.optionBehaviour == currentTab.Children[0]).type;
- GameOptionsMenuStartPatch.updateGameOptionsMenu(optionType, currentTab);
- }
- }
+
if (optionBehaviour != null && optionBehaviour is StringOption stringOption) {
stringOption.oldValue = stringOption.Value = selection;
ShareOptionSelections();// Share all selections
}
+ if (AmongUsClient.Instance?.AmHost == true) {
+ var currentTab = GameOptionsMenuStartPatch.currentTabs.FirstOrDefault(x => x.active).GetComponent<GameOptionsMenu>();
+ if (currentTab != null) {
+ var optionType = options.First(x => x.optionBehaviour == currentTab.Children[0]).type;
+ GameOptionsMenuStartPatch.updateGameOptionsMenu(optionType, currentTab);
+ }
+
+ }
+
}
public static byte[] serializeOptions() {
float num = 1.44f;
int i = 0;
- int singles = 0;
+ int singles = 1;
int headers = 0;
int lines = 0;
var curType = CustomOptionType.Modifier;
+ int numBonus = 0;
foreach (var option in relevantOptions) {
if (option.isHeader && (int)optionType != 99 || (int)optionType == 99 && curType != option.type) {
curType = option.type;
- if (i != 0) num -= 0.59f;
+ if (i != 0) {
+ num -= 0.85f;
+ numBonus++;
+ }
if (i % 2 != 0) singles++;
headers++; // for header
CategoryHeaderMasked categoryHeaderMasked = UnityEngine.Object.Instantiate<CategoryHeaderMasked>(__instance.categoryHeaderOrigin);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f);
__instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
- num -= 0.85f;
+ num -= 1.05f;
i = 0;
} else if (option.parent != null && (option.parent.selection == 0 || option.parent.parent != null && option.parent.parent.selection == 0)) continue; // Hides options, for which the parent is disabled!
if (option == CustomOptionHolder.crewmateRolesCountMax || option == CustomOptionHolder.neutralRolesCountMax || option == CustomOptionHolder.impostorRolesCountMax || option == CustomOptionHolder.modifiersCountMax || option == CustomOptionHolder.crewmateRolesFill)
lines++;
num2 = -8.95f;
if (i > 0) {
- num -= 0.59f;
+ num -= 0.85f;
}
} else {
num2 = -3f;
i++;
}
- float actual_spacing = (headers * 0.85f + lines * 0.59f) / (headers + lines);
- __instance.scrollBar.CalculateAndSetYBounds((float)(__instance.settingsInfo.Count + singles * 2 + headers), 2f, 6f, actual_spacing);
+ float actual_spacing = (headers * 1.05f + lines * 0.85f) / (headers + lines) * 1.01f;
+ __instance.scrollBar.CalculateAndSetYBounds((float)(__instance.settingsInfo.Count + singles * 2 + headers), 2f, 5f, actual_spacing);
}
class GameOptionsMenuStartPatch {
public static List<GameObject> currentTabs = new();
public static List<PassiveButton> currentButtons = new();
-
+ public static Dictionary<byte, GameOptionsMenu> currentGOMs = new();
public static void Postfix(GameSettingMenu __instance) {
currentTabs.ForEach(x => x?.Destroy());
currentButtons.ForEach(x => x?.Destroy());
currentTabs = new();
currentButtons = new();
+ currentGOMs.Clear();
if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return;
categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f);
num -= 0.63f;
- } else if (option.parent != null && (option.parent.selection == 0 || option.parent.parent != null && option.parent.parent.selection == 0)) continue; // Hides options, for which the parent is disabled!
+ } else if (option.parent != null && (option.parent.selection == 0 && !option.invertedParent || option.parent.parent != null && option.parent.parent.selection == 0 && !option.parent.invertedParent)) continue; // Hides options, for which the parent is disabled!
+ else if (option.parent != null && option.parent.selection != 0 && option.invertedParent) continue;
OptionBehaviour optionBehaviour = UnityEngine.Object.Instantiate<StringOption>(menu.stringOptionOrigin, Vector3.zero, Quaternion.identity, menu.settingsContainer);
optionBehaviour.transform.localPosition = new Vector3(0.952f, num, -2f);
optionBehaviour.SetClickMask(menu.ButtonClickMask);
currentTabs.Add(torSettingsTab);
torSettingsTab.SetActive(false);
+ currentGOMs.Add((byte)optionType, torSettingsGOM);
}
public static void updateGameOptionsMenu(CustomOptionType optionType, GameOptionsMenu torSettingsGOM) {
if (type == CustomOption.CustomOptionType.Modifier) line += buildModifierExtras(option);
sb.AppendLine(line);
}
- else if (option.parent.getSelection() > 0) {
+ else if (option.parent.getSelection() > 0 || option.invertedParent && option.parent.getSelection() == 0) {
if (option.id == 103) //Deputy
sb.AppendLine($"- {Helpers.cs(Deputy.color, "Deputy")}: {option.selections[option.selection].ToString()}");
else if (option.id == 224) //Sidekick
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);
+ bool isIrrelevant = (option.parent.getSelection() == 0 && !option.invertedParent) || (option.parent.parent != null && option.parent.parent.getSelection() == 0 && !option.parent.invertedParent);
Color c = isIrrelevant ? Color.grey : Color.white; // No use for now
if (isIrrelevant) continue;
}
if (Input.GetKeyDown(KeyCode.F1))
HudManagerUpdate.ToggleSettings(HudManager.Instance);
+ if (Input.GetKeyDown(KeyCode.F2) && LobbyBehaviour.Instance)
+ HudManagerUpdate.ToggleSummary(HudManager.Instance);
if (TheOtherRolesPlugin.optionsPage >= GameOptionsDataPatch.maxPage) TheOtherRolesPlugin.optionsPage = 0;
}
}
private static GameObject settingsBackground;
public static void OpenSettings(HudManager __instance) {
if (__instance.FullScreen == null || MapBehaviour.Instance && MapBehaviour.Instance.IsOpen) return;
+ if (summaryTMP) {
+ CloseSummary();
+ }
settingsBackground = GameObject.Instantiate(__instance.FullScreen.gameObject, __instance.transform);
settingsBackground.SetActive(true);
var renderer = settingsBackground.GetComponent<SpriteRenderer>();
else OpenSettings(__instance);
}
+ [HarmonyPrefix]
+ public static void Prefix3(HudManager __instance) {
+ if (!summaryTMP) return;
+ summaryTMP.text = Helpers.previousEndGameSummary;
+
+ summaryTMP.transform.localPosition = new Vector3(- 3 * 1.2f, 2.2f, -500f);
+
+ }
+
+ private static TMPro.TextMeshPro summaryTMP = null;
+ private static GameObject summaryBackground;
+ public static void OpenSummary(HudManager __instance) {
+ if (__instance.FullScreen == null || MapBehaviour.Instance && MapBehaviour.Instance.IsOpen || Helpers.previousEndGameSummary.IsNullOrWhiteSpace()) return;
+ if (settingsTMPs[0]) {
+ CloseSettings();
+ }
+ summaryBackground = GameObject.Instantiate(__instance.FullScreen.gameObject, __instance.transform);
+ summaryBackground.SetActive(true);
+ var renderer = summaryBackground.GetComponent<SpriteRenderer>();
+ renderer.color = new Color(0.2f, 0.2f, 0.2f, 0.9f);
+ renderer.enabled = true;
+
+
+ summaryTMP = GameObject.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform);
+ summaryTMP.alignment = TMPro.TextAlignmentOptions.TopLeft;
+ summaryTMP.enableWordWrapping = false;
+ summaryTMP.transform.localScale = Vector3.one * 0.3f;
+ summaryTMP.gameObject.SetActive(true);
+
+ }
+
+ public static void CloseSummary() {
+ summaryTMP?.gameObject.Destroy();
+ summaryTMP = null;
+ if (summaryBackground) summaryBackground.Destroy();
+ }
+
+ public static void ToggleSummary(HudManager __instance) {
+ if (summaryTMP) CloseSummary();
+ else OpenSummary(__instance);
+ }
+
static PassiveButton toggleSettingsButton;
static GameObject toggleSettingsButtonObject;
+ static PassiveButton toggleSummaryButton;
+ static GameObject toggleSummaryButtonObject;
+
static GameObject toggleZoomButtonObject;
static PassiveButton toggleZoomButton;
toggleSettingsButtonObject.SetActive(__instance.MapButton.gameObject.active && !(MapBehaviour.Instance && MapBehaviour.Instance.IsOpen) && GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.HideNSeek);
toggleSettingsButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -0.8f, -500f);
-
if (!toggleZoomButton || !toggleZoomButtonObject) {
// add a special button for settings viewing:
toggleZoomButtonObject = GameObject.Instantiate(__instance.MapButton.gameObject, __instance.MapButton.transform.parent);
var posOffset = Helpers.zoomOutStatus ? new Vector3(-1.27f, -7.92f, -52f) : new Vector3(0, -1.6f, -52f);
toggleZoomButtonObject.transform.localPosition = HudManager.Instance.MapButton.transform.localPosition + posOffset;
}
+
+ [HarmonyPostfix]
+ public static void Postfix2(HudManager __instance) {
+ if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) {
+ if (toggleSummaryButtonObject != null) {
+ toggleSummaryButtonObject.SetActive(false);
+ toggleSummaryButtonObject.Destroy();
+ toggleSummaryButton.Destroy();
+ }
+ return;
+ }
+ if (!toggleSummaryButton || !toggleSummaryButtonObject) {
+ // add a special button for settings viewing:
+ toggleSummaryButtonObject = GameObject.Instantiate(__instance.MapButton.gameObject, __instance.MapButton.transform.parent);
+ toggleSummaryButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -1.25f, -500f);
+ toggleSummaryButtonObject.name = "TOGGLESUMMARYSBUTTON";
+ SpriteRenderer renderer = toggleSummaryButtonObject.transform.Find("Inactive").GetComponent<SpriteRenderer>();
+ SpriteRenderer rendererActive = toggleSummaryButtonObject.transform.Find("Active").GetComponent<SpriteRenderer>();
+ toggleSummaryButtonObject.transform.Find("Background").localPosition = Vector3.zero;
+ renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Endscreen.png", 100f);
+ rendererActive.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EndscreenActive.png", 100f);
+ toggleSummaryButton = toggleSummaryButtonObject.GetComponent<PassiveButton>();
+ toggleSummaryButton.OnClick.RemoveAllListeners();
+ toggleSummaryButton.OnClick.AddListener((Action)(() => ToggleSummary(__instance)));
+ }
+ toggleSummaryButtonObject.SetActive(__instance.SettingsButton.gameObject.active && LobbyBehaviour.Instance && !Helpers.previousEndGameSummary.IsNullOrWhiteSpace() && GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.HideNSeek
+ && AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started);
+ toggleSummaryButtonObject.transform.localPosition = __instance.SettingsButton.transform.localPosition + new Vector3(-1.45f, 0.03f, -500f);
+ }
}
}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using HarmonyLib;
+using Hazel;
+using BepInEx.Unity.IL2CPP.Utils.Collections;
+using System.Collections;
+using TheOtherRoles.Patches;
+using static TheOtherRoles.TheOtherRoles;
+using UnityEngine.UI;
+using Reactor.Utilities.Extensions;
+
+namespace TheOtherRoles.Modules
+{
+ [HarmonyPatch]
+ class RoleDraft
+ {
+ public static bool isEnabled => CustomOptionHolder.isDraftMode.getBool() && (TORMapOptions.gameMode == CustomGamemodes.Classic || TORMapOptions.gameMode == CustomGamemodes.Guesser);
+ public static bool isRunning = false;
+
+ public static List<byte> pickOrder = new();
+ private static bool picked = false;
+ private static float timer = 0f;
+ private static List<ActionButton> buttons = new List<ActionButton>();
+ private static TMPro.TextMeshPro feedText;
+ public static List<byte> alreadyPicked = new();
+ public static IEnumerator CoSelectRoles(IntroCutscene __instance)
+ {
+ isRunning = true;
+ SoundEffectsManager.play("draft", volume: 1f, true, true);
+ alreadyPicked.Clear();
+ bool playedAlert = false;
+ feedText = UnityEngine.Object.Instantiate(__instance.TeamTitle, __instance.transform);
+ var aspectPosition = feedText.gameObject.AddComponent<AspectPosition>();
+ aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftTop;
+ aspectPosition.DistanceFromEdge = new Vector2(1.62f, 1.2f);
+ aspectPosition.AdjustPosition();
+ feedText.transform.localScale = new Vector3(0.6f, 0.6f, 1);
+ feedText.text = "<size=200%>Player's Picks:</size>\n\n";
+ feedText.alignment = TMPro.TextAlignmentOptions.TopLeft;
+ feedText.autoSizeTextContainer = true;
+ feedText.fontSize = 3f;
+ feedText.enableAutoSizing = false;
+ __instance.TeamTitle.transform.localPosition = __instance.TeamTitle.transform.localPosition + new Vector3(1f, 0f);
+ __instance.TeamTitle.text = "Currently Picking:";
+ __instance.BackgroundBar.enabled = false;
+ __instance.TeamTitle.transform.localScale = new Vector3(0.25f, 0.25f, 1f);
+ __instance.TeamTitle.autoSizeTextContainer = true;
+ __instance.TeamTitle.enableAutoSizing = false;
+ __instance.TeamTitle.fontSize = 5;
+ __instance.TeamTitle.alignment = TMPro.TextAlignmentOptions.Top;
+ __instance.ImpostorText.gameObject.SetActive(false);
+ GameObject.Find("BackgroundLayer")?.SetActive(false);
+ foreach (var player in UnityEngine.Object.FindObjectsOfType<PoolablePlayer>())
+ {
+ if (player.name.Contains("Dummy"))
+ {
+ player.gameObject.SetActive(false);
+ }
+ }
+ __instance.FrontMost.gameObject.SetActive(false);
+
+ if (AmongUsClient.Instance.AmHost)
+ {
+ sendPickOrder();
+ }
+
+ while (pickOrder.Count == 0)
+ {
+ yield return null;
+ }
+
+ while (pickOrder.Count > 0) {
+ picked = false;
+ timer = 0;
+ float maxTimer = CustomOptionHolder.draftModeTimeToChoose.getFloat();
+ string playerText = "";
+ while (timer < maxTimer || !picked) {
+ if (pickOrder.Count == 0)
+ break;
+ // wait for pick
+ timer += Time.deltaTime;
+ if (PlayerControl.LocalPlayer.PlayerId == pickOrder[0]) {
+ if (!playedAlert) {
+ playedAlert = true;
+ SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 1f, null);
+ }
+ // Animate beginning of choice, by changing background color
+ float min = 50 / 255f;
+ Color backGroundColor = new Color(min, min, min, 1);
+ if (timer < 1) {
+ float max = 230 / 255f;
+ if (timer < 0.5f) { // White flash
+ float p = timer / 0.5f;
+ float value = (float)Math.Pow(p, 2f) * max;
+ backGroundColor = new Color(value, value, value, 1);
+ } else {
+ float p = (1 - timer) / 0.5f;
+ float value = (float)Math.Pow(p, 2f) * max + (1 - (float)Math.Pow(p, 2f)) * min;
+ backGroundColor = new Color(value, value, value, 1);
+ }
+
+ }
+ HudManager.Instance.FullScreen.color = backGroundColor;
+ GameObject.Find("BackgroundLayer")?.SetActive(false);
+
+ // enable pick, wait for pick
+ Color youColor = timer - (int)timer > 0.5 ? Color.red : Color.yellow;
+ playerText = Helpers.cs(youColor, "You!");
+ // Available Roles:
+ List<RoleInfo> availableRoles = new();
+ foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) {
+ int impostorCount = PlayerControl.AllPlayerControls.ToArray().ToList().Where(x => x.Data.Role.IsImpostor).Count();
+ if (roleInfo.isModifier) continue;
+ // Remove Impostor Roles
+ if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && !roleInfo.isImpostor) continue;
+ if (!PlayerControl.LocalPlayer.Data.Role.IsImpostor && roleInfo.isImpostor) continue;
+
+ RoleManagerSelectRolesPatch.RoleAssignmentData roleData = RoleManagerSelectRolesPatch.getRoleAssignmentData();
+ roleData.crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection());
+ if (CustomOptionHolder.sheriffSpawnRate.getSelection() > 0)
+ roleData.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection());
+ if (roleData.neutralSettings.ContainsKey((byte)roleInfo.roleId) && roleData.neutralSettings[(byte)roleInfo.roleId] == 0) continue;
+ else if (roleData.impSettings.ContainsKey((byte)roleInfo.roleId) && roleData.impSettings[(byte)roleInfo.roleId] == 0) continue;
+ else if (roleData.crewSettings.ContainsKey((byte)roleInfo.roleId) && roleData.crewSettings[(byte)roleInfo.roleId] == 0) continue;
+ else if (new List<RoleId>() { RoleId.Janitor, RoleId.Godfather, RoleId.Mafioso }.Contains(roleInfo.roleId) && (CustomOptionHolder.mafiaSpawnRate.getSelection() == 0 || GameOptionsManager.Instance.currentGameOptions.NumImpostors < 3)) continue;
+ else if (roleInfo.roleId == RoleId.Sidekick) continue;
+ if (roleInfo.roleId == RoleId.Deputy && Sheriff.sheriff == null) continue;
+ if (roleInfo.roleId == RoleId.Pursuer) continue;
+ if (roleInfo.roleId == RoleId.Spy && impostorCount < 2) continue;
+ if (roleInfo.roleId == RoleId.Prosecutor && (CustomOptionHolder.lawyerIsProsecutorChance.getSelection() == 0 || CustomOptionHolder.lawyerSpawnRate.getSelection() == 0)) continue;
+ if (roleInfo.roleId == RoleId.Lawyer && (CustomOptionHolder.lawyerIsProsecutorChance.getSelection() == 10 || CustomOptionHolder.lawyerSpawnRate.getSelection() == 0)) continue;
+ if (TORMapOptions.gameMode == CustomGamemodes.Guesser && (roleInfo.roleId == RoleId.EvilGuesser || roleInfo.roleId == RoleId.NiceGuesser)) continue;
+ if (alreadyPicked.Contains((byte)roleInfo.roleId) && roleInfo.roleId != RoleId.Crewmate) continue;
+ if (CustomOptionHolder.crewmateRolesFill.getBool() && roleInfo.roleId == RoleId.Crewmate) continue;
+
+ int impsPicked = alreadyPicked.Where(x => RoleInfo.roleInfoById[(RoleId)x].isImpostor).Count();
+
+ // Hanlde forcing of 100% roles for impostors
+ if (PlayerControl.LocalPlayer.Data.Role.IsImpostor) {
+ int impsMax = CustomOptionHolder.impostorRolesCountMax.getSelection();
+ int impsMin = CustomOptionHolder.impostorRolesCountMin.getSelection();
+ if (impsMin > impsMax) impsMin = impsMax;
+ int impsLeft = pickOrder.Where(x => Helpers.playerById(x).Data.Role.IsImpostor).Count();
+ int imps100 = roleData.impSettings.Where(x => x.Value == 10).Count();
+ if (imps100 > impsMax) imps100 = impsMax;
+ int imps100Picked = alreadyPicked.Where(x => roleData.impSettings.GetValueSafe(x) == 10).Count();
+ if (imps100 - imps100Picked >= impsLeft && !(roleData.impSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue;
+ if (impsMin - impsPicked >= impsLeft && roleInfo.roleId == RoleId.Impostor) continue;
+ if (impsPicked >= impsMax && roleInfo.roleId != RoleId.Impostor) continue;
+ }
+
+ // Player is no impostor! Handle forcing of 100% roles for crew and neutral
+ else {
+ // No more neutrals possible!
+ int neutralsPicked = alreadyPicked.Where(x => RoleInfo.roleInfoById[(RoleId)x].isNeutral).Count();
+ int crewPicked = alreadyPicked.Count - impsPicked - neutralsPicked;
+ int neutralsMax = CustomOptionHolder.neutralRolesCountMax.getSelection();
+ int neutralsMin = CustomOptionHolder.neutralRolesCountMin.getSelection();
+ int neutrals100 = roleData.neutralSettings.Where(x => x.Value == 10).Count();
+ if (neutrals100 > neutralsMin) neutralsMin = neutrals100;
+ if (neutralsMin > neutralsMax) neutralsMin = neutralsMax;
+
+ // If crewmate fill disabled and crew picked the amount of allowed crewmates alreay: no more crewmate except vanilla crewmate allowed!
+ int crewLimit = PlayerControl.AllPlayerControls.Count - impostorCount - (neutralsMin > neutrals100 ? neutralsMin : neutrals100 > neutralsMax ? neutralsMax : neutrals100);
+ int maxCrew = CustomOptionHolder.crewmateRolesFill.getBool() ? CustomOptionHolder.crewmateRolesCountMax.getSelection() : crewLimit;
+ if (maxCrew > crewLimit)
+ maxCrew = crewLimit;
+ if (crewPicked >= crewLimit && !roleInfo.isNeutral && roleInfo.roleId != RoleId.Crewmate) continue;
+ // Fill roles means no crewmates allowed!
+ if (CustomOptionHolder.crewmateRolesFill.getBool() && roleInfo.roleId == RoleId.Crewmate) continue;
+
+ bool allowAnyNeutral = false;
+ if (neutralsPicked >= neutralsMax && roleInfo.isNeutral) continue;
+ // More neutrals needed? Then no more crewmates! This takes precedence over crew roles set to 100%!
+ var crewmatesLeft = pickOrder.Count - pickOrder.Where(x => Helpers.playerById(x).Data.Role.IsImpostor).Count();
+
+ if (crewmatesLeft <= neutralsMin - neutralsPicked && !roleInfo.isNeutral) {
+ continue;
+ } else if (neutralsMin - neutrals100 > neutralsPicked)
+ allowAnyNeutral = true;
+ // Handle 100% Roles PER Faction.
+
+ int neutrals100Picked = alreadyPicked.Where(x => roleData.neutralSettings.GetValueSafe(x) == 10).Count();
+ if (neutrals100 > neutralsMax) neutrals100 = neutralsMax;
+
+ int crew100 = roleData.crewSettings.Where(x => x.Value == 10).Count();
+ int crew100Picked = alreadyPicked.Where(x => roleData.crewSettings.GetValueSafe(x) == 10).Count();
+ if (neutrals100 > neutralsMax) neutrals100 = neutralsMax;
+
+ if (crew100 > maxCrew) crew100 = maxCrew;
+ if ((neutrals100 - neutrals100Picked >= crewmatesLeft || roleInfo.isNeutral && neutrals100 - neutrals100Picked >= neutralsMax - neutralsPicked) && !(neutrals100Picked >= neutralsMax) && !(roleData.neutralSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue;
+ if (!(allowAnyNeutral && roleInfo.isNeutral) && crew100 - crew100Picked >= crewmatesLeft && !(roleData.crewSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue;
+
+ if (!(allowAnyNeutral && roleInfo.isNeutral) && neutrals100 + crew100 - neutrals100Picked - crew100Picked >= crewmatesLeft && !(roleData.crewSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0 || roleData.neutralSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue;
+
+ }
+ // Handle role pairings that are blocked, e.g. Vampire Warlock, Cleaner Vulture etc.
+ bool blocked = false;
+ foreach (var blockedRoleId in CustomOptionHolder.blockedRolePairings) {
+ if (alreadyPicked.Contains(blockedRoleId.Key) && blockedRoleId.Value.ToList().Contains((byte)roleInfo.roleId)) {
+ blocked = true;
+ break;
+ }
+ }
+ if (blocked) continue;
+
+
+ availableRoles.Add(roleInfo);
+ }
+
+ // Fallback for if all roles are somehow removed. (This is only the case if there is a bug, hence print a warning
+ if (availableRoles.Count == 0) {
+ if (PlayerControl.LocalPlayer.Data.Role.IsImpostor)
+ availableRoles.Add(RoleInfo.impostor);
+ else
+ availableRoles.Add(RoleInfo.crewmate);
+ TheOtherRolesPlugin.Logger.LogWarning("Draft Mode: Fallback triggered, because no roles were left. Forced addition of basegame Imp/Crewmate");
+ }
+
+ List<RoleInfo> originalAvailable = new(availableRoles);
+
+ // remove some roles, so that you can't always get the same roles:
+ if (availableRoles.Count > CustomOptionHolder.draftModeAmountOfChoices.getFloat()) {
+ int countToRemove = availableRoles.Count - (int)CustomOptionHolder.draftModeAmountOfChoices.getFloat();
+ while (countToRemove-- > 0) {
+ var toRemove = availableRoles.OrderBy(_ => Guid.NewGuid()).First();
+ availableRoles.Remove(toRemove);
+ }
+ }
+
+ if (timer >= maxTimer) {
+ sendPick((byte)originalAvailable.OrderBy(_ => Guid.NewGuid()).First().roleId);
+ }
+
+
+ if (GameObject.Find("RoleButton") == null) {
+ SoundEffectsManager.play("timemasterShield");
+ int i = 0;
+ int buttonsPerRow = 4;
+ int lastRow = availableRoles.Count / buttonsPerRow;
+ int buttonsInLastRow = availableRoles.Count % buttonsPerRow;
+
+ foreach (RoleInfo roleInfo in availableRoles) {
+ float row = i / buttonsPerRow;
+ float col = i % buttonsPerRow;
+ if (buttonsInLastRow != 0 && row == lastRow) {
+ col += (buttonsPerRow - buttonsInLastRow) / 2f;
+ }
+ // planned rows: maximum of 4, hence the following calculation for rows as well:
+ row += (4 - lastRow - 1) / 2f;
+
+ ActionButton actionButton = UnityEngine.Object.Instantiate(HudManager.Instance.KillButton, __instance.TeamTitle.transform);
+ actionButton.gameObject.SetActive(true);
+ actionButton.gameObject.name = "RoleButton";
+ actionButton.transform.localPosition = new Vector3(-8.4f + col * 5.5f, -10 - row * 3f);
+ actionButton.transform.localScale = new Vector3(2f, 2f);
+ actionButton.SetCoolDown(0, 0);
+ GameObject textHolder = new GameObject("textHolder");
+ var text = textHolder.AddComponent<TMPro.TextMeshPro>();
+ text.text = roleInfo.name.Replace(" ", "\n");
+ text.horizontalAlignment = TMPro.HorizontalAlignmentOptions.Center;
+ text.fontSize = 5;
+ textHolder.layer = actionButton.gameObject.layer;
+ text.outlineWidth = 0.1f;
+ text.outlineColor = Color.white;
+ text.color = roleInfo.color;
+ textHolder.transform.SetParent(actionButton.transform, false);
+ textHolder.transform.localPosition = new Vector3(0, text.text.Contains("\n") ? -1.975f : -2.2f, -1);
+ GameObject actionButtonGameObject = actionButton.gameObject;
+ SpriteRenderer actionButtonRenderer = actionButton.graphic;
+ Material actionButtonMat = actionButtonRenderer.material;
+
+ PassiveButton button = actionButton.GetComponent<PassiveButton>();
+ button.OnClick = new Button.ButtonClickedEvent();
+ button.OnClick.AddListener((Action)(() => {
+ sendPick((byte)roleInfo.roleId);
+ }));
+ HudManager.Instance.StartCoroutine(Effects.Lerp(0.5f, new Action<float>((p) => {
+ actionButton.OverrideText("");
+ })));
+ buttons.Add(actionButton);
+ i++;
+ }
+ }
+
+ } else {
+ int currentPick = PlayerControl.AllPlayerControls.Count - pickOrder.Count + 1;
+ playerText = $"Anonymous Player {currentPick}";
+ HudManager.Instance.FullScreen.color = Color.black;
+ }
+ __instance.TeamTitle.text = $"{Helpers.cs(Color.white, "<size=280%>Welcome to the Role Draft!</size>")}\n\n\n<size=200%> Currently Picking:</size>\n\n\n<size=250%>{playerText}</size>";
+ int waitMore = pickOrder.IndexOf(PlayerControl.LocalPlayer.PlayerId);
+ string waitMoreText = "";
+ if (waitMore > 0) {
+ waitMoreText = $" ({waitMore} rounds until your turn)";
+ }
+ __instance.TeamTitle.text += $"\n\n{waitMoreText}\nRandom Selection In... {(int)(maxTimer + 1 - timer)}\n {(SoundManager.MusicVolume > -80 ? "♫ Music: Ultimate Superhero 3 - Kenët & Rez ♫" : "")}";
+ yield return null;
+ }
+ }
+ HudManager.Instance.FullScreen.color = Color.black;
+ __instance.FrontMost.gameObject.SetActive(true);
+ GameObject.Find("BackgroundLayer")?.SetActive(true);
+ if (AmongUsClient.Instance.AmHost)
+ {
+ RoleManagerSelectRolesPatch.assignRoleTargets(null); // Assign targets for Lawyer & Prosecutor
+ if (RoleManagerSelectRolesPatch.isGuesserGamemode) RoleManagerSelectRolesPatch.assignGuesserGamemode();
+ RoleManagerSelectRolesPatch.assignModifiers(); // Assign modifier
+ }
+
+ float myTimer = 0f;
+ while (myTimer < 3f)
+ {
+ myTimer += Time.deltaTime;
+ Color c = new Color(0, 0, 0, myTimer / 3.0f);
+ __instance.FrontMost.color = c;
+ yield return null;
+ }
+
+ SoundEffectsManager.stop("draft");
+ isRunning = false;
+ yield break;
+ }
+
+ public static void receivePick(byte playerId, byte roleId)
+ {
+ if (!isEnabled) return;
+ RPCProcedure.setRole(roleId, playerId);
+ alreadyPicked.Add(roleId);
+ try
+ {
+ pickOrder.Remove(playerId);
+ timer = 0;
+ picked = true;
+ RoleInfo roleInfo = RoleInfo.allRoleInfos.First(x => (byte)x.roleId == roleId);
+ string roleString = Helpers.cs(roleInfo.color, roleInfo.name);
+ int roleLength = roleInfo.name.Length; // Not used for now, but stores the amount of charactes of the roleString.
+ if (!CustomOptionHolder.draftModeShowRoles.getBool() && !(playerId == PlayerControl.LocalPlayer.PlayerId)) {
+ roleString = "Unknown Role";
+ roleLength = roleString.Length;
+ }
+ else if (CustomOptionHolder.draftModeHideImpRoles.getBool() && roleInfo.isImpostor && !(playerId == PlayerControl.LocalPlayer.PlayerId)) {
+ roleString = Helpers.cs(Palette.ImpostorRed, "Impostor Role");
+ roleLength = "Impostor Role".Length;
+ }
+ else if (CustomOptionHolder.draftModeHideNeutralRoles.getBool() && roleInfo.isNeutral && !(playerId == PlayerControl.LocalPlayer.PlayerId)) {
+ roleString = Helpers.cs(Palette.Blue, "Neutral Role");
+ roleLength = "Neutral Role".Length;
+ }
+ string line = $"{(playerId == PlayerControl.LocalPlayer.PlayerId ? "You" : alreadyPicked.Count)}:";
+ line = line + string.Concat(Enumerable.Repeat(" ", 6 - line.Length)) + roleString;
+ feedText.text += line + "\n";
+ SoundEffectsManager.play("select");
+ }
+ catch (Exception e) { TheOtherRolesPlugin.Logger.LogError(e); }
+ }
+
+ public static void sendPick(byte RoleId)
+ {
+ SoundEffectsManager.stop("timeMasterShield");
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DraftModePick, SendOption.Reliable, -1);
+ writer.Write(PlayerControl.LocalPlayer.PlayerId);
+ writer.Write(RoleId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ receivePick(PlayerControl.LocalPlayer.PlayerId, RoleId);
+
+ // destroy all the buttons:
+ foreach (var button in buttons)
+ {
+ button?.gameObject?.Destroy();
+ }
+ buttons.Clear();
+ }
+
+
+ public static void sendPickOrder()
+ {
+ pickOrder = PlayerControl.AllPlayerControls.ToArray().Select(x => x.PlayerId).OrderBy(_ => Guid.NewGuid()).ToList().ToList();
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DraftModePickOrder, SendOption.Reliable, -1);
+ writer.Write((byte)pickOrder.Count);
+ foreach (var item in pickOrder)
+ {
+ writer.Write(item);
+ }
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ }
+
+
+ public static void receivePickOrder(int amount, MessageReader reader)
+ {
+ pickOrder.Clear();
+ for (int i = 0; i < amount; i++)
+ {
+ pickOrder.Add(reader.ReadByte());
+ }
+ }
+
+ class PatchedEnumerator() : IEnumerable
+ {
+ public IEnumerator enumerator;
+ public IEnumerator Postfix;
+ public IEnumerator GetEnumerator()
+ {
+ while (enumerator.MoveNext())
+ {
+ yield return enumerator.Current;
+ }
+ while (Postfix.MoveNext())
+ yield return Postfix.Current;
+ }
+ }
+
+
+ [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowTeam))]
+
+ class ShowRolePatch
+ {
+ [HarmonyPostfix]
+ public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.IEnumerator __result)
+ {
+ if (!isEnabled) return;
+ var newEnumerator = new PatchedEnumerator()
+ {
+ enumerator = __result.WrapToManaged(),
+ Postfix = CoSelectRoles(__instance)
+ };
+ __result = newEnumerator.GetEnumerator().WrapToIl2Cpp();
+ }
+
+ }
+ }
+}
AmongUsClient.Instance.FinishRpcImmediately(writer);
GameHistory.overrideDeathReasonAndKiller(PlayerControl.LocalPlayer, DeadPlayer.CustomDeathReason.Bomb, killer: Bomber.bomber);
}
- SoundEffectsManager.playAtPosition("bombExplosion", position, range: Bomber.hearRange) ;
+ try {
+ SoundEffectsManager.playAtPosition("bombExplosion", position, maxDuration: 1.6f, range: Bomber.hearRange);
+ } catch (Exception e) {
+ TheOtherRolesPlugin.Logger.LogWarning($"Exception in Sound Effect for Bomb explosion: {e}");
+ }
}
Bomber.clearBomb();
canDefuse = false;
using Il2CppSystem.Runtime.ExceptionServices;
+using Rewired;
using System;
using System.Collections.Generic;
+using TheOtherRoles.Modules;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace TheOtherRoles.Objects {
public class CustomButton {
public static List<CustomButton> buttons = new List<CustomButton>();
+ public static KeyCode Action2Keycode = KeyCode.G; //TheOtherRolesPlugin.Instance.Config.Bind("Buttons", "Action2Keycode", KeyCode.G, "Second Ability Button Key").Value;
+ public static KeyCode Action3Keycode = KeyCode.H; // TheOtherRolesPlugin.Instance.Config.Bind("Buttons", "Action3Keycode", KeyCode.H, "Third Ability Button Key").Value;
public ActionButton actionButton;
public GameObject actionButtonGameObject;
public SpriteRenderer actionButtonRenderer;
public HudManager hudManager;
public bool mirror;
public KeyCode? hotkey;
+ public KeyCode? originalHotkey;
public string buttonText;
public bool isHandcuffed = false;
private static readonly int Desat = Shader.PropertyToID("_Desat");
public static readonly Vector3 upperRowCenter = new Vector3(-1f, 1f, 0f); // Not usable for imps beacuse of new button positions!
public static readonly Vector3 upperRowLeft = new Vector3(-2f, 1f, 0f);
public static readonly Vector3 upperRowFarLeft = new Vector3(-3f, 1f, 0f);
+ public static readonly Vector3 highRowRight = new Vector3(0f, 2.06f, 0f);
}
public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false, string buttonText = "")
this.mirror = mirror;
this.hotkey = hotkey;
this.buttonText = buttonText;
+ originalHotkey = hotkey;
Timer = 16.2f;
buttons.Add(this);
actionButton = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.KillButton.transform.parent);
this.showButtonText = (actionButtonRenderer.sprite == Sprite || buttonText != "");
button.OnClick = new Button.ButtonClickedEvent();
button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent);
-
setActive(false);
}
}
}
+
+ // Reload the rebound hotkeys from the among us settings.
+ public static void ReloadHotkeys() {
+ foreach (var button in buttons) {
+ // Q button is used only for killing! This rebinds every button that would use Q to use the currently set killing button in among us.
+ if (button.originalHotkey == KeyCode.Q) {
+ Player player = Rewired.ReInput.players.GetPlayer(0);
+ string keycode = player.controllers.maps.GetFirstButtonMapWithAction(8, true).elementIdentifierName;
+ button.hotkey = (KeyCode)Enum.Parse(typeof(KeyCode), keycode);
+ }
+ // F is the default ability button. All buttons that would use F now use the ability button.
+ if (button.originalHotkey == KeyCode.F) {
+ Player player = Rewired.ReInput.players.GetPlayer(0);
+ string keycode = player.controllers.maps.GetFirstButtonMapWithAction(49, true).elementIdentifierName;
+ button.hotkey = (KeyCode)Enum.Parse(typeof(KeyCode), keycode);
+ }
+
+ if (button.originalHotkey == KeyCode.G) {
+ button.hotkey = Action2Keycode;
+ }
+ if (button.originalHotkey == KeyCode.H) {
+ button.hotkey = Action3Keycode;
+ }
+ }
+
+ }
+
public void setActive(bool isActive) {
if (isActive) {
actionButtonGameObject.SetActive(true);
actionButtonMat.SetFloat(Desat, 1f);
}
- if (Timer >= 0) {
+ if (Timer >= 0 && !RoleDraft.isRunning) { // Make sure role draft has finished or isnt running
if (HasEffect && isEffectActive)
Timer -= Time.deltaTime;
else if (!localPlayer.inVent && moveable)
else if (secondPortal == null) {
secondPortal = this;
}
- var lastRoom = FastDestroyableSingleton<HudManager>.Instance?.roomTracker.LastRoom.RoomId;
+ var lastRoom = FastDestroyableSingleton<HudManager>.Instance?.roomTracker?.LastRoom?.RoomId;
this.room = lastRoom != null ? DestroyableSingleton<TranslationController>.Instance.GetString((SystemTypes)lastRoom) : "Open Field";
}
public bool triggerable = false;
private int usedCount = 0;
private int neededCount = Trapper.trapCountToReveal;
- public List<PlayerControl> trappedPlayer = new List<PlayerControl>();
+ public List<byte> trappedPlayer = new List<byte>();
private Arrow arrow = new Arrow(Color.blue);
private static Sprite trapSprite;
}
player.moveable = false;
player.NetTransform.Halt();
- Trapper.playersOnMap.Add(player);
+ Trapper.playersOnMap.Add(player.PlayerId);
if (localIsTrapper) t.arrow.arrow.SetActive(true);
FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(Trapper.trapDuration, new Action<float>((p) => {
if (p == 1f) {
player.moveable = true;
- Trapper.playersOnMap.RemoveAll(x => x == player);
+ Trapper.playersOnMap.RemoveAll(x => x == player.PlayerId);
if (trapPlayerIdMap.ContainsKey(playerId)) trapPlayerIdMap.Remove(playerId);
t.arrow.arrow.SetActive(false);
}
t.revealed = true;
}
- t.trappedPlayer.Add(player);
+ t.trappedPlayer.Add(player.PlayerId);
t.triggerable = true;
-
}
public static void Update() {
Trap target = null;
foreach (Trap trap in traps) {
if (trap.arrow.arrow.active) trap.arrow.Update();
- if (trap.revealed || !trap.triggerable || trap.trappedPlayer.Contains(player)) continue;
+ if (trap.revealed || !trap.triggerable || trap.trappedPlayer.Contains(player.PlayerId)) continue;
if (player.inVent || !player.CanMove) continue;
float distance = Vector2.Distance(trap.trap.transform.position, player.GetTruePosition());
if (distance <= ud && distance < closestDistance) {
new("Ghosts Can Additionally See Modifier", () => TORMapOptions.ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value = !TheOtherRolesPlugin.GhostsSeeModifier.Value, TheOtherRolesPlugin.GhostsSeeModifier.Value),
new("Show Role Summary", () => TORMapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value, TheOtherRolesPlugin.ShowRoleSummary.Value),
new("Show Lighter / Darker", () => TORMapOptions.showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value = !TheOtherRolesPlugin.ShowLighterDarker.Value, TheOtherRolesPlugin.ShowLighterDarker.Value),
- new("Enable Sound Effects", () => TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value, TheOtherRolesPlugin.EnableSoundEffects.Value),
+ new("Enable Sound Effects", () => {
+ TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value;
+ if (!TORMapOptions.enableSoundEffects) SoundEffectsManager.stopAll();
+ return TORMapOptions.enableSoundEffects;
+ }, TheOtherRolesPlugin.EnableSoundEffects.Value),
new("Show Vents On Map", () => TORMapOptions.ShowVentsOnMap = TheOtherRolesPlugin.ShowVentsOnMap.Value = !TheOtherRolesPlugin.ShowVentsOnMap.Value, TheOtherRolesPlugin.ShowVentsOnMap.Value),
new("Show Chat Notifications", () => TORMapOptions.ShowChatNotifications = TheOtherRolesPlugin.ShowChatNotifications.Value = !TheOtherRolesPlugin.ShowChatNotifications.Value, TheOtherRolesPlugin.ShowChatNotifications.Value),
};
button.Background.color = button.onState ? Color.green : Palette.ImpostorRed;
}));
- passiveButton.OnMouseOver.AddListener((Action) (() => button.Background.color = new Color32(34 ,139, 34, byte.MaxValue)));
+ passiveButton.OnMouseOver.AddListener((Action) (() => button.Background.color = button.onState ? new Color32(34 ,139, 34, byte.MaxValue): new Color32(139, 34, 34, byte.MaxValue)));
passiveButton.OnMouseOut.AddListener((Action) (() => button.Background.color = button.onState ? Color.green : Palette.ImpostorRed));
foreach (var spr in button.gameObject.GetComponentsInChildren<SpriteRenderer>())
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";
+ if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + (MeetingHud.Instance ? " " : "\n");
__instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays > 0 ? "-BETA" : "")}\n{gameModeText}" + __instance.text.text;
- position.DistanceFromEdge = new Vector3(2.25f, 0.11f, 0);
+ position.DistanceFromEdge = MeetingHud.Instance ? new Vector3(1.25f, 0.15f, 0) : new Vector3(1.55f, 0.15f, 0);
} else {
string gameModeText = $"";
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek";
var roleSummaryTextMeshRectTransform = roleSummaryTextMesh.GetComponent<RectTransform>();
roleSummaryTextMeshRectTransform.anchoredPosition = new Vector2(position.x + 3.5f, position.y - 0.1f);
roleSummaryTextMesh.text = roleSummaryText.ToString();
+ Helpers.previousEndGameSummary = $"<size=110%>{roleSummaryText.ToString()}</size>";
}
AdditionalTempData.clear();
}
using UnityEngine;
namespace TheOtherRoles.Patches {
- [HarmonyPatch(typeof(ExileController), nameof(ExileController.Begin))]
+ [HarmonyPatch(typeof(ExileController), nameof(ExileController.BeginForGameplay))]
[HarmonyPriority(Priority.First)]
class ExileControllerBeginPatch {
public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref NetworkedPlayerInfo exiled) {
if (Witch.witch != null && Witch.futureSpelled != null && AmongUsClient.Instance.AmHost) {
bool exiledIsWitch = exiled != null && exiled.PlayerId == Witch.witch.PlayerId;
bool witchDiesWithExiledLover = exiled != null && Lovers.existing() && Lovers.bothDie && (Lovers.lover1.PlayerId == Witch.witch.PlayerId || Lovers.lover2.PlayerId == Witch.witch.PlayerId) && (exiled.PlayerId == Lovers.lover1.PlayerId || exiled.PlayerId == Lovers.lover2.PlayerId);
-
+
if ((witchDiesWithExiledLover || exiledIsWitch) && Witch.witchVoteSavesTargets) Witch.futureSpelled = new List<PlayerControl>();
foreach (PlayerControl target in Witch.futureSpelled) {
if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill){
}
}
}
+
+
// Display message to the host
if (AmongUsClient.Instance.AmHost) {
if (versionMismatch) {
PassiveButton startButtonPassiveButton = copiedStartButton.GetComponent<PassiveButton>();
void StopStartFunc() {
__instance.ResetStartState();
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, -1);
+ writer.Write(PlayerControl.LocalPlayer.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
copiedStartButton.Destroy();
startingTimer = 0;
+ SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound);
}
startButtonPassiveButton.OnClick.AddListener((Action)(() => StopStartFunc()));
__instance.StartCoroutine(Effects.Lerp(.1f, new System.Action<float>((p) => {
PassiveButton startButtonPassiveButton = copiedStartButton.GetComponent<PassiveButton>();
void StopStartFunc() {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, AmongUsClient.Instance.HostId);
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, -1);
writer.Write(PlayerControl.LocalPlayer.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
copiedStartButton.Destroy();
__instance.GameStartText.text = String.Empty;
startingTimer = 0;
+ SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound);
}
startButtonPassiveButton.OnClick.AddListener((Action)(() => StopStartFunc()));
__instance.StartCoroutine(Effects.Lerp(.1f, new System.Action<float>((p) => {
using TheOtherRoles.Utilities;
using TheOtherRoles.CustomGameModes;
+using TheOtherRoles.Modules;
namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))]
BountyHunter.cooldownText.transform.localScale = Vector3.one * 0.4f;
BountyHunter.cooldownText.gameObject.SetActive(true);
}
- }
-
- // Force Reload of SoundEffectHolder
- SoundEffectsManager.Load();
+ }
// First kill
if (AmongUsClient.Instance.AmHost && TORMapOptions.shieldFirstKill && TORMapOptions.firstKillName != "" && !HideNSeek.isHideNSeekGM && !PropHunt.isPropHuntGM) {
}
yourTeam = fakeImpostorTeam;
}
+
+ // Role draft: If spy is enabled, don't show the team
+ if (CustomOptionHolder.spySpawnRate.getSelection() > 0 && PlayerControl.AllPlayerControls.ToArray().ToList().Where(x => x.Data.Role.IsImpostor).Count() > 1) {
+ var fakeImpostorTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>(); // The local player always has to be the first one in the list (to be displayed in the center)
+ fakeImpostorTeam.Add(PlayerControl.LocalPlayer);
+ yourTeam = fakeImpostorTeam;
+ }
}
public static void setupIntroTeam(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
List<RoleInfo> infos = RoleInfo.getRoleInfoForPlayer(PlayerControl.LocalPlayer);
RoleInfo roleInfo = infos.Where(info => !info.isModifier).FirstOrDefault();
- if (roleInfo == null) return;
- if (roleInfo.isNeutral) {
- var neutralColor = new Color32(76, 84, 78, 255);
+ var neutralColor = new Color32(76, 84, 78, 255);
+ if (roleInfo == null || roleInfo == RoleInfo.crewmate) {
+ if (RoleDraft.isEnabled && CustomOptionHolder.neutralRolesCountMax.getSelection() > 0) {
+ __instance.TeamTitle.text = "<size=60%>Crewmate" + Helpers.cs(Color.white, " / ") + Helpers.cs(neutralColor, "Neutral") + "</size>";
+ }
+ return;
+ }
+ if (roleInfo.isNeutral) {
__instance.BackgroundBar.material.color = neutralColor;
__instance.TeamTitle.text = "Neutral";
__instance.TeamTitle.color = neutralColor;
private static AnnouncementPopUp popUp;
private static void Prefix(MainMenuManager __instance) {
+
+ // Force Reload of SoundEffectHolder
+ SoundEffectsManager.Load();
+
var template = GameObject.Find("ExitGameButton");
var template2 = GameObject.Find("CreditsButton");
if (template == null || template2 == null) return;
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
-ugackMiner53 - Idea and core code for the Prop Hunt game mode</size>";
+TheEpicRoles - Idea for the first kill shield (partly) and the (old) tabbed option menu (fully + some code), by LaicosVK DasMonschta Nova
+ugackMiner53 - Idea and core code for the Prop Hunt game mode
+Role Draft Music: [https://www.youtube.com/watch?v=9STiQ8cCIo0]Unreal Superhero 3 by Kenët & Rez[]
+
+License: TheOtherRoles is licensed under the [https://github.com/TheOtherRolesAU/TheOtherRoles?tab=GPL-3.0-1-ov-file#readme]GPLv3[]
+</size>";
creditsString += "</align>";
Assets.InnerNet.Announcement creditsAnnouncement = new() {
__instance.HerePoint.transform.SetLocalZ(-2.1f);
if (Trapper.trapper != null && PlayerControl.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) {
- foreach (PlayerControl player in Trapper.playersOnMap) {
- if (herePoints.ContainsKey(player.PlayerId)) continue;
- Vector3 v = Trap.trapPlayerIdMap[player.PlayerId].trap.transform.position;
+ foreach (byte playerId in Trapper.playersOnMap) {
+ if (herePoints.ContainsKey(playerId)) continue;
+ Vector3 v = Trap.trapPlayerIdMap[playerId].trap.transform.position;
v /= MapUtilities.CachedShipStatus.MapScale;
v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x);
v.z = -2.1f;
var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true);
herePoint.transform.localPosition = v;
herePoint.enabled = true;
- int colorId = player.CurrentOutfit.ColorId;
+ PlayerControl player = Helpers.playerById(playerId);
+ if (player == null) continue;
+ int colorId = player.CurrentOutfit.ColorId;
if (Trapper.anonymousMap) player.CurrentOutfit.ColorId = 6;
player.SetPlayerMaterialColors(herePoint);
player.CurrentOutfit.ColorId = colorId;
- herePoints.Add(player.PlayerId, herePoint);
+ herePoints.Add(playerId, herePoint);
}
- foreach (var s in herePoints.Where(x => !Trapper.playersOnMap.Contains(Helpers.playerById(x.Key))).ToList()) {
- UnityEngine.Object.Destroy(s.Value);
+ foreach (var s in herePoints.Where(x => !Trapper.playersOnMap.Contains(x.Key)).ToList()) {
+ UnityEngine.Object.Destroy(s.Value.gameObject);
herePoints.Remove(s.Key);
}
} else if (Snitch.snitch != null && PlayerControl.LocalPlayer.PlayerId == Snitch.snitch.PlayerId && !Snitch.snitch.Data.IsDead && Snitch.mode != Snitch.Mode.Chat) {
}
} else {
foreach (var s in herePoints) {
- UnityEngine.Object.Destroy(s.Value);
+ UnityEngine.Object.Destroy(s.Value.gameObject);
herePoints.Remove(s.Key);
}
}
if (!trap.revealed) continue;
string message = $"Trap {trap.instanceId}: \n";
trap.trappedPlayer = trap.trappedPlayer.OrderBy(x => rnd.Next()).ToList();
- foreach (PlayerControl p in trap.trappedPlayer) {
+ foreach (byte playerId in trap.trappedPlayer) {
+ PlayerControl p = Helpers.playerById(playerId);
if (Trapper.infoType == 0) message += RoleInfo.GetRolesString(p, false, false, true) + "\n";
else if (Trapper.infoType == 1) {
if (Helpers.isNeutral(p) || p.Data.Role.IsImpostor) message += "Evil Role \n";
if (PlayerControl.LocalPlayer.Data.IsDead && output != "") FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{output}");
- Trapper.playersOnMap = new List<PlayerControl>();
+ Trapper.playersOnMap = new ();
Snitch.playerRoomMap = new Dictionary<byte, byte>();
// Remove revealed traps
public static class PlayerControlFixedUpdatePatch {
// Helpers
- static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List<PlayerControl> untargetablePlayers = null, PlayerControl targetingPlayer = null) {
+ public static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List<PlayerControl> untargetablePlayers = null, PlayerControl targetingPlayer = null) {
PlayerControl result = null;
float num = AmongUs.GameOptions.GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)];
if (!MapUtilities.CachedShipStatus) return result;
return result;
}
- static void setPlayerOutline(PlayerControl target, Color color) {
+ public static void setPlayerOutline(PlayerControl target, Color color) {
if (target == null || target.cosmetics?.currentBodySprite?.BodySprite == null) return;
color = color.SetAlpha(Chameleon.visibility(target.PlayerId));
using TheOtherRoles.Utilities;
using static TheOtherRoles.TheOtherRoles;
using TheOtherRoles.CustomGameModes;
+using TheOtherRoles.Modules;
-namespace TheOtherRoles.Patches {
+namespace TheOtherRoles.Patches
+{
[HarmonyPatch(typeof(RoleOptionsCollectionV08), nameof(RoleOptionsCollectionV08.GetNumPerGame))]
class RoleOptionsDataGetNumPerGamePatch{
public static void Postfix(ref int __result) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.resetVariables();
- 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 (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek
+ || RoleDraft.isEnabled) return; // Don't assign Roles in Hide N Seek
assignRoles();
}
}
}
- private static void assignRoleTargets(RoleAssignmentData data) {
+ public static void assignRoleTargets(RoleAssignmentData data) {
// Set Lawyer or Prosecutor Target
if (Lawyer.lawyer != null) {
var possibleTargets = new List<PlayerControl>();
}
}
- private static void assignModifiers() {
+ public static void assignModifiers() {
var modifierMin = CustomOptionHolder.modifiersCountMin.getSelection();
var modifierMax = CustomOptionHolder.modifiersCountMax.getSelection();
if (modifierMin > modifierMax) modifierMin = modifierMax;
assignModifiersToPlayers(chanceModifierToAssign, players, modifierCount); // Assign chance modifier
}
- private static void assignGuesserGamemode() {
+ public static void assignGuesserGamemode() {
List<PlayerControl> impPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
List<PlayerControl> neutralPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
List<PlayerControl> crewPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
case RoleId.Tiebreaker:
selection = CustomOptionHolder.modifierTieBreaker.getSelection(); break;
case RoleId.Mini:
- selection = CustomOptionHolder.modifierMini.getSelection(); break;
+ selection = CustomOptionHolder.modifierMini.getSelection();
+ if (EventUtility.isEnabled) {
+ selection = 10;
+ if (CustomOptionHolder.modifierMini.getSelection() == 0 && CustomOptionHolder.eventReallyNoMini.getBool())
+ selection = 0;
+ }
+ break;
case RoleId.Bait:
selection = CustomOptionHolder.modifierBait.getSelection();
if (multiplyQuantity) selection *= CustomOptionHolder.modifierBaitQuantity.getQuantity();
{
if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return;
if (Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] > 0 || MeetingHud.Instance) __instance.ImpostorVentButton.Hide();
- else if (PlayerControl.LocalPlayer.roleCanUseVents() && !__instance.ImpostorVentButton.isActiveAndEnabled) __instance.ImpostorVentButton.Show();
+ else if (PlayerControl.LocalPlayer.roleCanUseVents() && !__instance.ImpostorVentButton.isActiveAndEnabled) {
+ __instance.ImpostorVentButton.Show();
+
+ }
+ if (Rewired.ReInput.players.GetPlayer(0).GetButtonDown(RewiredConsts.Action.UseVent) && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && PlayerControl.LocalPlayer.roleCanUseVents()) {
+ __instance.ImpostorVentButton.DoClick();
+ }
}
Deputy.setHandcuffedKnows();
return false;
}
- if (Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer)) return false;
+ if (Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer.PlayerId)) return false;
bool canUse;
bool couldUse;
__instance.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
- bool canMoveInVents = PlayerControl.LocalPlayer != Spy.spy && !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer);
+ bool canMoveInVents = PlayerControl.LocalPlayer != Spy.spy && !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer.PlayerId);
if (!canUse) return false; // No need to execute the native method as using is disallowed anyways
bool isEnter = !PlayerControl.LocalPlayer.inVent;
[HarmonyPatch(typeof(Vent), nameof(Vent.TryMoveToVent))]
public static class MoveToVentPatch {
public static bool Prefix(Vent otherVent) {
- return !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer);
+ return !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer.PlayerId);
}
}
using AmongUs.GameOptions;
using Assets.CoreScripts;
using Reactor.Utilities.Extensions;
+using TheOtherRoles.Modules;
namespace TheOtherRoles
{
public enum RoleId {
PropHuntStartTimer,
PropHuntSetInvis,
PropHuntSetSpeedboost,
+ DraftModePickOrder,
+ DraftModePick,
// Other functionality
ShareTimer,
ShareGhostInfo,
+ EventKick,
}
public static class RPCProcedure {
clearAndReloadRoles();
clearGameHistory();
setCustomButtonCooldowns();
+ CustomButton.ReloadHotkeys();
reloadPluginOptions();
Helpers.toggleZoom(reset : true);
GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 0;
SurveillanceMinigamePatch.nightVisionOverlays = null;
EventUtility.clearAndReload();
MapBehaviourPatch.clearAndReload();
+ HudManagerUpdate.CloseSummary();
}
public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) {
}
public static void stopStart(byte playerId) {
- if (AmongUsClient.Instance.AmHost && CustomOptionHolder.anyPlayerCanStopStart.getBool()) {
+ if (!CustomOptionHolder.anyPlayerCanStopStart.getBool())
+ return;
+ SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound);
+ if (AmongUsClient.Instance.AmHost) {
GameStartManager.Instance.ResetStartState();
PlayerControl.LocalPlayer.RpcSendChat($"{Helpers.playerById(playerId).Data.PlayerName} stopped the game start!");
}
case (byte)CustomRPC.PropHuntSetSpeedboost:
RPCProcedure.propHuntSetSpeedboost(reader.ReadByte());
break;
+ case (byte)CustomRPC.DraftModePickOrder:
+ RoleDraft.receivePickOrder(reader.ReadByte(), reader);
+ break;
+ case (byte)CustomRPC.DraftModePick:
+ RoleDraft.receivePick(reader.ReadByte(), reader.ReadByte());
+ break;
case (byte)CustomRPC.ShareGhostInfo:
RPCProcedure.receiveGhostInfo(reader.ReadByte(), reader);
break;
byte roomId = reader.ReadByte();
RPCProcedure.shareRoom(roomPlayer, roomId);
break;
+ case (byte)CustomRPC.EventKick:
+ byte kickSource = reader.ReadByte();
+ byte kickTarget = reader.ReadByte();
+ EventUtility.handleKick(Helpers.playerById(kickSource), Helpers.playerById(kickTarget), reader.ReadSingle());
+ break;
}
}
}
|portalUse | | [Static electronic noise - Xbox 360](https://freesound.org/people/scenes/sounds/431654/ ) + [Teleport Slurp](https://freesound.org/people/GameAudio/sounds/220163/ ) | CC0 1.0 both |
|fail | | [twitch: SmeggyTV: Mein Auge](https://www.twitch.tv/smeggytv/clip/KindQuaintEndiveDancingBanana-Zoc-bXUnqNCoqQD5 ) | License granted for use in TOR |
|trapperTrap | | [Karabiner_Click_04.wav](https://freesound.org/people/Rudmer_Rotteveel/sounds/457454/ ) & [Steel Spring Bear Trap](https://freesound.org/people/fractionalist/sounds/644245/ )| CC0 1.0 both |
+|select | | [Game Menu Select Sound 2](https://freesound.org/people/digimistic/sounds/705174/) | CC0 1.0 |
+|draft| [Unreal Superhero 3 by Kenët & Rez](https://www.youtube.com/watch?v=9STiQ8cCIo0) | License granted for use in TOR |
public RoleId roleId;
public bool isNeutral;
public bool isModifier;
+ public bool isImpostor => color == Palette.ImpostorRed && !(roleId == RoleId.Spy);
+ public static Dictionary<RoleId, RoleInfo> roleInfoById = new();
public RoleInfo(string name, Color color, string introDescription, string shortDescription, RoleId roleId, bool isNeutral = false, bool isModifier = false) {
this.color = color;
this.roleId = roleId;
this.isNeutral = isNeutral;
this.isModifier = isModifier;
+ roleInfoById.TryAdd(roleId, this);
}
public static RoleInfo jester = new RoleInfo("Jester", Jester.color, "Get voted out", "Get voted out", RoleId.Jester, true);
{
private static Dictionary<string, AudioClip> soundEffects = new();
+ //private static List<AudioSource> currentSources = new();
public static void Load()
{
{
// Convenience: As as SoundEffects are stored in the same folder, allow using just the name as well
//if (!path.Contains(".")) path = "TheOtherRoles.Resources.SoundEffects." + path + ".raw";
- path = "assets/audio/" + path.ToLower() + ".ogg";
+ if (!path.Contains("assets")) path = "assets/audio/" + path.ToLower() + ".ogg";
AudioClip returnValue;
return soundEffects.TryGetValue(path, out returnValue) ? returnValue : null;
}
- public static void play(string path, float volume=0.8f, bool loop = false)
+ public static AudioSource play(string path, float volume=0.8f, bool loop = false, bool musicChannel=false)
{
- if (!TORMapOptions.enableSoundEffects) return;
+ if (!TORMapOptions.enableSoundEffects) return null;
AudioClip clipToPlay = get(path);
stop(path);
if (Constants.ShouldPlaySfx() && clipToPlay != null) {
- AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, volume);
+ AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, volume, audioMixer: musicChannel ? SoundManager.Instance.MusicChannel : null);
+ //currentSources.Add(source);
source.loop = loop;
+ return source;
}
+ return null;
}
public static void playAtPosition(string path, Vector2 position, float maxDuration = 15f, float range = 5f, bool loop = false) {
if (!TORMapOptions.enableSoundEffects || !Constants.ShouldPlaySfx()) return;
AudioClip clipToPlay = get(path);
+ TheOtherRolesPlugin.Logger.LogMessage("play at position");
+ if (clipToPlay == null) {
+ TheOtherRolesPlugin.Logger.LogMessage("clip is null");
+ return;
+ }
AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, 1f);
+ if (source == null) {
+ TheOtherRolesPlugin.Logger.LogMessage("source is null");
+ return;
+ }
+ //currentSources.Add(source);
source.loop = loop;
HudManager.Instance.StartCoroutine(Effects.Lerp(maxDuration, new Action<float>((p) => {
if (source != null) {
- if (p == 1) {
+ if (p == 1 && source.isPlaying) {
source.Stop();
+ try {
+ //currentSources.Remove(source);
+ source.Destroy();
+ }
+ catch { }
}
float distance, volume;
distance = Vector2.Distance(position, PlayerControl.LocalPlayer.GetTruePosition());
source.volume = volume;
}
})));
+ TheOtherRolesPlugin.Logger.LogMessage("end play at position");
}
public static void stop(string path) {
var soundToStop = get(path);
- if (soundToStop != null)
- if (Constants.ShouldPlaySfx()) SoundManager.Instance.StopSound(soundToStop);
+ if (soundToStop != null) {
+ try {
+ SoundManager.Instance?.StopSound(soundToStop);
+ }
+ catch (Exception e) { TheOtherRolesPlugin.Logger.LogWarning($"Exception in stop sound: {e}"); }
+ }
}
public static void stopAll() {
if (soundEffects == null) return;
- foreach (var path in soundEffects.Keys) stop(path);
+ try {
+ foreach (var path in soundEffects.Keys) {
+ stop(path);
+ }
+ }
+ catch { }
+
+ /*try {
+ foreach (var source in currentSources) {
+ source?.Stop();
+ }
+ currentSources.Clear();
+ }
+ catch { }*/
}
}
}
vision = CustomOptionHolder.lawyerVision.getFloat();
lawyerKnowsRole = CustomOptionHolder.lawyerKnowsRole.getBool();
targetCanBeJester = CustomOptionHolder.lawyerTargetCanBeJester.getBool();
- canCallEmergency = CustomOptionHolder.jesterCanCallEmergency.getBool();
+ canCallEmergency = CustomOptionHolder.lawyerCanCallEmergency.getBool();
}
}
public static int rechargedTasks = 3;
public static int charges = 1;
public static int trapCountToReveal = 2;
- public static List<PlayerControl> playersOnMap = new List<PlayerControl>();
+ public static List<byte> playersOnMap = new List<Byte>();
public static bool anonymousMap = false;
public static int infoType = 0; // 0 = Role, 1 = Good/Evil, 2 = Name
public static float trapDuration = 5f;
rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.trapperRechargeTasksNumber.getFloat());
charges = Mathf.RoundToInt(CustomOptionHolder.trapperMaxCharges.getFloat()) / 2;
trapCountToReveal = Mathf.RoundToInt(CustomOptionHolder.trapperTrapNeededTriggerToReveal.getFloat());
- playersOnMap = new List<PlayerControl>();
+ playersOnMap = new ();
anonymousMap = CustomOptionHolder.trapperAnonymousMap.getBool();
infoType = CustomOptionHolder.trapperInfoType.getSelection();
trapDuration = CustomOptionHolder.trapperTrapDuration.getFloat();
}
public static void clearBomb(bool flag = true) {
+ TheOtherRolesPlugin.Logger.LogDebug("Clearing Bomb!");
if (bomb != null) {
UnityEngine.Object.Destroy(bomb.bomb);
UnityEngine.Object.Destroy(bomb.background);
public static PlayerControl futureShift;
public static PlayerControl currentTarget;
+ public static bool shiftsMedicShield = false;
+
private static Sprite buttonSprite;
public static Sprite getButtonSprite() {
if (buttonSprite) return buttonSprite;
} else if (Medic.medic != null && Medic.medic == player2) {
if (repeat) shiftRole(player2, player1, false);
Medic.medic = player1;
+ if (Medic.shielded != null && Medic.shielded == player1 && shiftsMedicShield)
+ Medic.shielded = player2;
} else if (Swapper.swapper != null && Swapper.swapper == player2) {
if (repeat) shiftRole(player2, player1, false);
Swapper.swapper = player1;
shifter = null;
currentTarget = null;
futureShift = null;
+ shiftsMedicShield = CustomOptionHolder.modifierShifterShiftsMedicShield.getBool();
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
- <Version>4.7.0</Version>
+ <Version>4.8.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
<LangVersion>latest</LangVersion>
using TheOtherRoles;
using TheOtherRoles.Patches;
-using static TheOtherRoles.TheOtherRoles;
using System.Linq;
using InnerNet;
using TheOtherRoles.Modules;
using HarmonyLib;
+using Hazel;
namespace TheOtherRoles.Utilities;
[HarmonyPatch]
public static class EventUtility {
+ private static Sprite kickButtonSprite;
+
+ public static Sprite getKickButtonSprite() {
+
+ if (kickButtonSprite) return kickButtonSprite;
+ kickButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EventKickButton.png", 115f);
+ return kickButtonSprite;
+ }
+
public static void Load() {
if (!isEnabled) return;
}
public static void clearAndReload() {
+ kickCounter = 0;
}
public static void Update() {
- if (!isEnabled || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return;
+ //if (!isEnabled || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return;
+
+ // set Target
+ var untargetablePlayers = new List<PlayerControl>();
+ foreach (var player in PlayerControl.AllPlayerControls) {
+ if (Mini.mini != player)
+ untargetablePlayers.Add(player);
+ }
+ currentTarget = PlayerControlFixedUpdatePatch.setTarget(untargetablePlayers: untargetablePlayers);
+ PlayerControlFixedUpdatePatch.setPlayerOutline(currentTarget, Color.yellow);
}
- public static DateTime enabled = DateTime.FromBinary(638475264000000000);
+ public static DateTime enabled = new DateTime(DateTime.Today.Year, 4, 1);
public static bool isEventDate => DateTime.Today.Date == enabled;
public static bool canBeEnabled => DateTime.Today.Date >= enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT
public static void meetingEndsUpdate() {
if (!isEnabled) return;
- // TODO - Implement Horse hats
- // PlayerControl.LocalPlayer.RpcSetHat(CustomHatLoader.horseHatProductIds[rnd.Next(CustomHatLoader.horseHatProductIds.Count)]);
}
}
public static void gameEndsUpdate() {
- if (!isEnabled) return;
+
+ }
+
+
+ public static PlayerControl currentTarget;
+ private static bool currentlyKicking;
+ private static int kickCounter = 0;
+
+ public static void kickTarget() {
+ // send rpc to kick target
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EventKick, Hazel.SendOption.Reliable, -1);
+ writer.Write(PlayerControl.LocalPlayer.PlayerId);
+ writer.Write(currentTarget.PlayerId);
+ System.Random rnd = new System.Random();
+ float kickDistance = 1 + (float)rnd.NextDouble() * 1.5f; // 1- 2.5
+ writer.Write(kickDistance);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ handleKick(PlayerControl.LocalPlayer, currentTarget, kickDistance);
+ }
+
+ public static void handleKick(PlayerControl source, PlayerControl target, float kickDistance) {
+ if (currentlyKicking || !isEnabled) return;
+
+ kickCounter++;
+
+ // actual movement, canceled if meeting started
+ if (Mini.growingProgress() * 18 >= CustomOptionHolder.eventHeavyAge.getFloat() || kickCounter > CustomOptionHolder.eventKicksPerRound.getFloat()) { // boing flip
+ target = source;
+ source = Mini.mini;
+ }
+
+ SoundEffectsManager.playAtPosition("fail" , target.GetTruePosition(), 3, 3);
+
+ if (target == PlayerControl.LocalPlayer) {
+ PlayerControl.LocalPlayer.moveable = false;
+ PlayerControl.LocalPlayer.NetTransform.Halt();
+ }
+ Vector2 direction = Vector3.Normalize(target.transform.position - source.transform.position);
+
+
+ float kickDuration = 3f;
+ float speed = kickDistance / kickDuration;
+ Vector2 targetPosition = (Vector2)target.transform.position + direction * kickDistance;
+ Vector2 startPosition = target.transform.position;
+
+ HudManager.Instance.StartCoroutine(Effects.Lerp(kickDuration, new Action<float>((p) => {
+ float rotAngle = 360 * 4 * (1 - Mathf.Pow(1 - p, 4)) * (direction.x > 0 ? 1 : -1);
+ currentlyKicking = true;
+
+ if (MeetingHud.Instance) {
+ currentlyKicking = false;
+ rotAngle = 0f;
+ } else {
+ if (p == 1) {
+ if (target == PlayerControl.LocalPlayer) {
+ PlayerControl.LocalPlayer.moveable = true;
+ PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(PlayerControl.LocalPlayer.transform.position);
+ }
+ rotAngle = 0f;
+ currentlyKicking = false;
+ target.NetTransform.Halt();
+ }
+ target.transform.SetLocalEulerAngles(new Vector3(0f, 0f, rotAngle), RotationOrder.OrderXYZ);
+
+ // move the player:
+ Vector3 targetStep = startPosition + (1 - Mathf.Pow(1 - p, 4)) * direction * kickDistance;
+
+ if (!PhysicsHelpers.AnythingBetween(target.GetTruePosition(), target.GetTruePosition() + direction * 1f, Constants.ShipAndObjectsMask, false)) {
+ target.transform.position = targetStep;
+ }
+ }
+ })));
+
+
}