--- /dev/null
+* text=auto
\ No newline at end of file
-.vs
-.vscode/tasks.json
-TODO
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.DS_Store
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
.DS_Store
+*/.DS_Store
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Bb]in/
+[Bb]inPublic/
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+*/[Bb]in/*
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# .idea stuff
+.idea
+*.iml
+
+*.suo
+*.user
+.vs/
+[Bb]in/
+[Oo]bj/
+_UpgradeReport_Files/
+
+Thumbs.db
+Desktop.ini
+.DS_Store
+
+.build
+tools
+
+Among Us/*.*
+Among Us/*/*
\ No newline at end of file
# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2022.3.29| v4.1.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.2/TheOtherRoles.zip)
| 2022.3.29| v4.1.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.1/TheOtherRoles.zip)
| 2022.3.29| v4.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.0/TheOtherRoles.zip)
| 2022.3.29s| v4.0.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.0.0/TheOtherRoles.zip)
<details>
<summary>Click to show the Changelog</summary>
+**Version 4.1.2**
+- Made some general performance fixes. Special thanks to [probablyadnf](https://github.com/simonkellly)
+- Added an option to the ninja to get invisible after kill for x-seconds
+- Added a zoom out/overview function if you're dead and done with tasks
+- Added the sampled player to the morphling button
+- Fixed a bug where footsteps of the Detective and certain other game objects were visible in the fog of war
+- Fixed lawyer being exiled when their client is guessed in a meeting
+- Fixed multiple mod icons being shown when Submerged is loaded
+
+
**Version 4.1.1**
- Fixed a bug where the download submerged button did not work
- Fixed a bug where the medium revealed a evil mini
- **Play On A Random Map** If enabled it allows you to set a rotation of all current maps, except ehT dlekS
- **Ghosts Can See Roles**
- **Ghosts Can See Votes**
+- **Ghosts Can Additionally See Modifier**
- **Ghosts Can See The Number Of Remaining Tasks**
- **The map is accessable during a meeting and will show your last location when a body gets reported/meeting gets called**
+- **When you're a ghost and done with tasks, you'll get a zoom out/overview function**
- **Task Counts:** You are now able to select more tasks.
- **Role Summary:** When a game ends there will be a list of all players and their roles and their task progress
- **Darker/Lighter:** Displays color type of each player in meetings
# Roles
## Role Assignment
-We are still improving the role assignment system. It's not that intuitive right now, but it's more flexible than the older one
-if you're using it right.
First you need to choose how many special roles of each kind (Impostor/Neutral/Crewmate) you want in the game.
The count you set will only be reached, if there are enough Crewmates/Impostors in the game and if enough roles are set to be in the game (i.e. they are set to > 0%). The roles are then being distributed as follows:
You can mark a player with your ability and by using the ability again, you jump to the position of the marked player and kill it.\
Depending on the options you know where your marked player is.\
If the Ninja uses its ability, it will leave a trace (leaves) for a configurable amount of time where it activated the ability and additionally where it killed the before marked player.\
+When performing a ninja ability kill, the ninja can be invisible for some seconds (depends on options)\
\
**NOTE:**
- The Ninja has a 5 second cooldown after marking a player
| Ninja Knows Location Of Target | -
| Trace Duration | -
| Time Till Trace Color Has Faded | -
+| Time The Ninja Is Invisible | -
-----------------------
## Guesser
-----------------------
# Source code
-It's bad I know, this is a side project and my second week of modding. So there are no best practices around here.
You can use parts of the code but don't copy paste the whole thing. Make sure you give credits to the other developers, because some parts of the code are based on theirs.
# Bugs, suggestions and requests
using TheOtherRoles.Objects;
using System.Linq;
using System.Collections.Generic;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles
{
public static CustomButton witchSpellButton;
public static CustomButton ninjaButton;
public static CustomButton mayorMeetingButton;
+ public static CustomButton zoomOutButton;
public static Dictionary<byte, List<CustomButton>> deputyHandcuffedButtons = null;
+ public static bool zoomOutStatus = false;
+ public static PoolablePlayer morphTargetDisplay;
+
public static TMPro.TMP_Text securityGuardButtonScrewsText;
public static TMPro.TMP_Text securityGuardChargesText;
public static TMPro.TMP_Text deputyButtonHandcuffsText;
securityGuardCamButton.EffectDuration = SecurityGuard.duration;
// 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;
}
public static void resetTimeMasterButton() {
// Non Custom (Vanilla) Buttons. The Originals are disabled / hidden in UpdatePatch.cs already, just need to replace them. Can use any button, as we replace onclick etc anyways.
// Kill Button if enabled for the Role
- if (HudManager.Instance.KillButton.isActiveAndEnabled) addReplacementHandcuffedButton(arsonistButton, new Vector3(0, 1f, 0), couldUse: () => { return HudManager.Instance.KillButton.currentTarget != null; });
+ if (FastDestroyableSingleton<HudManager>.Instance.KillButton.isActiveAndEnabled) addReplacementHandcuffedButton(arsonistButton, new Vector3(0, 1f, 0), couldUse: () => { return FastDestroyableSingleton<HudManager>.Instance.KillButton.currentTarget != null; });
// Vent Button if enabled
- if (PlayerControl.LocalPlayer.roleCanUseVents()) addReplacementHandcuffedButton(arsonistButton, new Vector3(-1.8f, 1f, 0), couldUse: () => { return HudManager.Instance.ImpostorVentButton.currentTarget != null; });
+ if (PlayerControl.LocalPlayer.roleCanUseVents()) addReplacementHandcuffedButton(arsonistButton, new Vector3(-1.8f, 1f, 0), couldUse: () => { return FastDestroyableSingleton<HudManager>.Instance.ImpostorVentButton.currentTarget != null; });
// Report Button
- addReplacementHandcuffedButton(arsonistButton, new Vector3(-0.9f, -0.06f, 0), () => { return HudManager.Instance.ReportButton.graphic.color == Palette.EnabledColor; });
+ addReplacementHandcuffedButton(arsonistButton, new Vector3(-0.9f, -0.06f, 0), () => { return FastDestroyableSingleton<HudManager>.Instance.ReportButton.graphic.color == Palette.EnabledColor; });
}
else if (!handcuffed && deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) // Reset to original. Disables the replacements, enables the original buttons.
{
AmongUsClient.Instance.FinishRpcImmediately(usedRepairWriter);
RPCProcedure.engineerUsedRepair();
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) {
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks.GetFastEnumerator()) {
if (task.TaskType == TaskTypes.FixLights) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerFixLights, Hazel.SendOption.Reliable, -1);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.engineerFixLights();
} else if (task.TaskType == TaskTypes.RestoreOxy) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, 0 | 64);
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, 1 | 64);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.LifeSupp, 0 | 64);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.LifeSupp, 1 | 64);
} else if (task.TaskType == TaskTypes.ResetReactor) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 16);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.Reactor, 16);
} else if (task.TaskType == TaskTypes.ResetSeismic) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Laboratory, 16);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.Laboratory, 16);
} else if (task.TaskType == TaskTypes.FixComms) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 16 | 0);
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 16 | 1);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.Comms, 16 | 0);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.Comms, 16 | 1);
} else if (task.TaskType == TaskTypes.StopCharles) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 0 | 16);
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 1 | 16);
- } else if (SubmergedCompatibility.isSubmerged() && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask) {
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.Reactor, 0 | 16);
+ MapUtilities.CachedShipStatus.RpcRepairSystem(SystemTypes.Reactor, 1 | 16);
+ } else if (SubmergedCompatibility.IsSubmerged && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerFixSubmergedOxygen, Hazel.SendOption.Reliable, -1);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.engineerFixSubmergedOxygen();
() => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && Engineer.remainingFixes > 0 && !PlayerControl.LocalPlayer.Data.IsDead; },
() => {
bool sabotageActive = false;
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks.GetFastEnumerator())
if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles
- || SubmergedCompatibility.isSubmerged() && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask)
+ || SubmergedCompatibility.IsSubmerged && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask)
sabotageActive = true;
return sabotageActive && Engineer.remainingFixes > 0 && PlayerControl.LocalPlayer.CanMove;
},
);
// Morphling morph
+
morphlingButton = new CustomButton(
() => {
if (Morphling.sampledTarget != null) {
RPCProcedure.morphlingMorph(Morphling.sampledTarget.PlayerId);
Morphling.sampledTarget = null;
morphlingButton.EffectDuration = Morphling.duration;
+
} else if (Morphling.currentTarget != null) {
Morphling.sampledTarget = Morphling.currentTarget;
morphlingButton.Sprite = Morphling.getMorphSprite();
morphlingButton.EffectDuration = 1f;
+
+ // Add poolable player to the button so that the target outfit is shown
+ morphlingButton.actionButton.cooldownTimerText.transform.localPosition = new Vector3(0, 0, -1f); // Before the poolable player
+ morphTargetDisplay = UnityEngine.Object.Instantiate<PoolablePlayer>(Patches.IntroCutsceneOnDestroyPatch.playerPrefab, morphlingButton.actionButton.transform);
+ GameData.PlayerInfo data = Morphling.sampledTarget.Data;
+ PlayerControl.SetPlayerMaterialColors(data.DefaultOutfit.ColorId, morphTargetDisplay.CurrentBodySprite.BodySprite);
+ morphTargetDisplay.SetSkin(data.DefaultOutfit.SkinId, data.DefaultOutfit.ColorId);
+ morphTargetDisplay.HatSlot.SetHat(data.DefaultOutfit.HatId, data.DefaultOutfit.ColorId);
+ PlayerControl.SetPetImage(data.DefaultOutfit.PetId, data.DefaultOutfit.ColorId, morphTargetDisplay.PetSlot);
+ morphTargetDisplay.NameText.text = ""; // Hide the name!
+ morphTargetDisplay.transform.localPosition = new Vector3(0f, 0.22f, -0.01f);
+ morphTargetDisplay.transform.localScale = Vector3.one * 0.33f;
+ morphTargetDisplay.setSemiTransparent(false);
+ morphTargetDisplay.gameObject.SetActive(true);
}
},
() => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
morphlingButton.isEffectActive = false;
morphlingButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
Morphling.sampledTarget = null;
+ if (morphTargetDisplay != null) { // Reset the poolable player
+ morphTargetDisplay.gameObject.SetActive(false);
+ GameObject.Destroy(morphTargetDisplay.gameObject);
+ morphTargetDisplay = null;
+ }
},
Morphling.getSampleSprite(),
new Vector3(-1.8f, -0.06f, 0),
if (Morphling.sampledTarget == null) {
morphlingButton.Timer = morphlingButton.MaxTimer;
morphlingButton.Sprite = Morphling.getSampleSprite();
+
+ // Reset the poolable player
+ morphTargetDisplay.gameObject.SetActive(false);
+ GameObject.Destroy(morphTargetDisplay.gameObject);
+ morphTargetDisplay = null;
}
}
);
hackerAdminTableButton = new CustomButton(
() => {
if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled)
- DestroyableSingleton<HudManager>.Instance.ShowMap((System.Action<MapBehaviour>)(m => m.ShowCountOverlay()));
+ FastDestroyableSingleton<HudManager>.Instance.ShowMap((System.Action<MapBehaviour>)(m => m.ShowCountOverlay()));
if (Hacker.cantMove) PlayerControl.LocalPlayer.moveable = false;
PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0);
- HudManager.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action<float>((p) => { // Delayed action
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action<float>((p) => { // Delayed action
if (p == 1f) {
// Perform kill if possible and reset bitten (regardless whether the kill was successful or not)
Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten, showAnimation: false);
}
RPCProcedure.usePortal(PlayerControl.LocalPlayer.PlayerId);
usePortalButton.Timer = usePortalButton.MaxTimer;
- HudManager.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action<float>((p) => { // Delayed action
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action<float>((p) => { // Delayed action
PlayerControl.LocalPlayer.moveable = false;
PlayerControl.LocalPlayer.NetTransform.Halt();
if (p >= 0.5f && p <= 0.53f && !didTeleport && !MeetingHud.Instance) {
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
SubmergedCompatibility.ChangeFloor(exit.y > -7);
}
PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(exit);
if(Warlock.rootTime > 0) {
PlayerControl.LocalPlayer.moveable = false;
PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement so the warlock is not just running straight into the next object
- HudManager.Instance.StartCoroutine(Effects.Lerp(Warlock.rootTime, new Action<float>((p) => { // Delayed action
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(Warlock.rootTime, new Action<float>((p) => { // Delayed action
if (p == 1f) {
PlayerControl.LocalPlayer.moveable = true;
}
RPCProcedure.sealVent(SecurityGuard.ventTarget.Id);
SecurityGuard.ventTarget = null;
- } else if (PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.isSubmerged()) { // Place camera if there's no vent and it's not MiraHQ or Submerged
+ } else if (PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.IsSubmerged) { // Place camera if there's no vent and it's not MiraHQ or Submerged
var pos = PlayerControl.LocalPlayer.transform.position;
byte[] buff = new byte[sizeof(float) * 2];
Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
},
() => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews >= Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); },
() => {
- securityGuardButton.actionButton.graphic.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.isSubmerged()) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite();
+ securityGuardButton.actionButton.graphic.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.IsSubmerged) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite();
if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}";
if (SecurityGuard.ventTarget != null)
return SecurityGuard.remainingScrews >= SecurityGuard.ventPrice && PlayerControl.LocalPlayer.CanMove;
- return PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.isSubmerged() && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove;
+ return PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.IsSubmerged && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove;
},
() => { securityGuardButton.Timer = securityGuardButton.MaxTimer; },
SecurityGuard.getPlaceCameraButtonSprite(),
PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement
},
() => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice)
- && !SubmergedCompatibility.isSubmerged(); },
+ && !SubmergedCompatibility.IsSubmerged; },
() => {
if (securityGuardChargesText != null) securityGuardChargesText.text = $"{SecurityGuard.charges} / {SecurityGuard.maxCharges}";
securityGuardCamButton.actionButton.graphic.sprite = PlayerControl.GameOptions.MapId == 1 ? SecurityGuard.getLogSprite() : SecurityGuard.getCamSprite();
if (Medium.target == null || Medium.target.player == null) return;
string msg = "";
- int randomNumber = Medium.target.killerIfExisting?.PlayerId == Mini.mini?.PlayerId ? TheOtherRoles.rnd.Next(3) : TheOtherRoles.rnd.Next(4);
+ int randomNumber = TheOtherRoles.rnd.Next(4);
string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting.Data.DefaultOutfit.ColorId) ? "lighter" : "darker";
float timeSinceDeath = ((float)(Medium.meetingStartTime - Medium.target.timeOfDeath).TotalMilliseconds);
string name = " (" + Medium.target.player.Data.PlayerName + ")";
if (randomNumber == 0) msg = "What is your role? My role is " + RoleInfo.GetRolesString(Medium.target.player, false) + name;
else if (randomNumber == 1) msg = "What is your killer`s color type? My killer is a " + typeOfColor + " color" + name;
else if (randomNumber == 2) msg = "When did you die? I have died " + Math.Round(timeSinceDeath / 1000) + "s before meeting started" + name;
- else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, true) + name;
+ else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false, false) + name;
- DestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}");
+ FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}");
// Remove soul
if (Medium.oneTimeUse) {
}
}
- HudManager.Instance.StartCoroutine(Effects.Lerp(5f, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(5f, new Action<float>((p) => {
if (target != null) {
var tmp = target.color;
tmp.a = Mathf.Clamp01(1 - p);
writer.EndMessage();
RPCProcedure.placeNinjaTrace(buff);
+ MessageWriter invisibleWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetInvisible, Hazel.SendOption.Reliable, -1);
+ invisibleWriter.Write(Ninja.ninja.PlayerId);
+ invisibleWriter.Write(byte.MinValue);
+ AmongUsClient.Instance.FinishRpcImmediately(invisibleWriter);
+ RPCProcedure.setInvisible(Ninja.ninja.PlayerId, byte.MinValue);
+
// Perform Kill
MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
writer2.Write(PlayerControl.LocalPlayer.PlayerId);
writer2.Write(Ninja.ninjaMarked.PlayerId);
writer2.Write(byte.MaxValue);
AmongUsClient.Instance.FinishRpcImmediately(writer2);
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
SubmergedCompatibility.ChangeFloor(Ninja.ninjaMarked.transform.localPosition.y > -7);
}
RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, Ninja.ninjaMarked.PlayerId, byte.MaxValue);
() => {
mayorMeetingButton.actionButton.OverrideText("Emergency");
bool sabotageActive = false;
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks.GetFastEnumerator())
if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles
- || SubmergedCompatibility.isSubmerged() && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask)
+ || SubmergedCompatibility.IsSubmerged && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask)
sabotageActive = true;
return !sabotageActive && PlayerControl.LocalPlayer.CanMove;
},
"Meeting"
);
+ zoomOutButton = new CustomButton(
+ () => { Helpers.toggleZoom();
+ },
+ () => { if (PlayerControl.LocalPlayer == null || !PlayerControl.LocalPlayer.Data.IsDead || PlayerControl.LocalPlayer.Data.Role.IsImpostor) return false;
+ var (playerCompleted, playerTotal) = TasksHandler.taskInfo(PlayerControl.LocalPlayer.Data);
+ int numberOfLeftTasks = playerTotal - playerCompleted;
+ return numberOfLeftTasks <= 0;
+ },
+ () => { return true; },
+ () => { return; },
+ Helpers.loadSpriteFromResources("TheOtherRoles.Resources.MinusButton.png", 150f), // Invisible button!
+ new Vector3(0.4f, 2.8f, 0),
+ __instance,
+ KeyCode.KeypadPlus
+ );
+ zoomOutButton.Timer = 0f;
+
// Set the default (or settings from the previous game) timers / durations when spawning the buttons
setCustomButtonCooldowns();
deputyHandcuffedButtons = null;
using System.Collections.Generic;
using UnityEngine;
-using BepInEx.Configuration;
-using System;
-using System.Linq;
-using HarmonyLib;
-using Hazel;
-using System.Reflection;
-using System.Text;
using static TheOtherRoles.TheOtherRoles;
using Types = TheOtherRoles.CustomOption.CustomOptionType;
public static CustomOption ninjaKnowsTargetLocation;
public static CustomOption ninjaTraceTime;
public static CustomOption ninjaTraceColorTime;
+ public static CustomOption ninjaInvisibleDuration;
public static CustomOption shifterSpawnRate;
public static CustomOption shifterShiftsModifiers;
ninjaKnowsTargetLocation = CustomOption.Create(382, Types.Impostor, "Ninja Knows Location Of Target", true, ninjaSpawnRate);
ninjaTraceTime = CustomOption.Create(383, Types.Impostor, "Trace Duration", 5f, 1f, 20f, 0.5f, ninjaSpawnRate);
ninjaTraceColorTime = CustomOption.Create(384, Types.Impostor, "Time Till Trace Color Has Faded", 2f, 0f, 20f, 0.5f, ninjaSpawnRate);
+ ninjaInvisibleDuration = CustomOption.Create(385, Types.Impostor, "Time The Ninja Is Invisble", 3f, 0f, 20f, 1f, ninjaSpawnRate);
+
guesserSpawnRate = CustomOption.Create(310, Types.Neutral, cs(Guesser.color, "Guesser"), rates, null, true);
guesserIsImpGuesserRate = CustomOption.Create(311, Types.Neutral, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate);
guesserNumberOfShots = CustomOption.Create(312, Types.Neutral, "Guesser Number Of Shots", 2f, 1f, 15f, 1f, guesserSpawnRate);
using System.Collections.Generic;
-using System.Collections;
using System;
using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
namespace TheOtherRoles {
public class DeadPlayer
using System.Collections.Generic;
using System.IO;
using System.Reflection;
-using System.Collections;
-using UnhollowerBaseLib;
using UnityEngine;
using System.Linq;
using static TheOtherRoles.TheOtherRoles;
using TheOtherRoles.Modules;
using HarmonyLib;
using Hazel;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles {
public static PlayerControl playerById(byte id)
{
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
if (player.PlayerId == id)
return player;
return null;
public static Dictionary<byte, PlayerControl> allPlayersById()
{
Dictionary<byte, PlayerControl> res = new Dictionary<byte, PlayerControl>();
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
res.Add(player.PlayerId, player);
return res;
}
}
public static void refreshRoleDescription(PlayerControl player) {
- if (player == null) return;
-
List<RoleInfo> infos = RoleInfo.getRoleInfoForPlayer(player);
+ List<string> taskTexts = new(infos.Count);
+ foreach (var roleInfo in infos)
+ {
+ taskTexts.Add(getRoleString(roleInfo));
+ }
+
var toRemove = new List<PlayerTask>();
- foreach (PlayerTask t in player.myTasks) {
- var textTask = t.gameObject.GetComponent<ImportantTextTask>();
- if (textTask != null) {
- var info = infos.FirstOrDefault(x => textTask.Text.StartsWith(x.name));
- if (info != null)
- infos.Remove(info); // TextTask for this RoleInfo does not have to be added, as it already exists
- else
- toRemove.Add(t); // TextTask does not have a corresponding RoleInfo and will hence be deleted
- }
+ foreach (PlayerTask t in player.myTasks.GetFastEnumerator())
+ {
+ var textTask = t.TryCast<ImportantTextTask>();
+ if (textTask == null) continue;
+
+ var currentText = textTask.Text;
+
+ if (taskTexts.Contains(currentText)) taskTexts.Remove(currentText); // TextTask for this RoleInfo does not have to be added, as it already exists
+ else toRemove.Add(t); // TextTask does not have a corresponding RoleInfo and will hence be deleted
}
foreach (PlayerTask t in toRemove) {
}
// Add TextTask for remaining RoleInfos
- foreach (RoleInfo roleInfo in infos) {
+ foreach (string title in taskTexts) {
var task = new GameObject("RoleTask").AddComponent<ImportantTextTask>();
task.transform.SetParent(player.transform, false);
-
- if (roleInfo.name == "Jackal") {
- var getSidekickText = Jackal.canCreateSidekick ? " and recruit a Sidekick" : "";
- task.Text = cs(roleInfo.color, $"{roleInfo.name}: Kill everyone{getSidekickText}");
- } else if (roleInfo.name == "Invert") {
- task.Text = cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription} ({Invert.meetings})");
- } else {
- task.Text = cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription}");
- }
-
+ task.Text = title;
player.myTasks.Insert(0, task);
}
}
+ internal static string getRoleString(RoleInfo roleInfo)
+ {
+ if (roleInfo.name == "Jackal")
+ {
+ var getSidekickText = Jackal.canCreateSidekick ? " and recruit a Sidekick" : "";
+ return cs(roleInfo.color, $"{roleInfo.name}: Kill everyone{getSidekickText}");
+ }
+
+ if (roleInfo.name == "Invert")
+ {
+ return cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription} ({Invert.meetings})");
+ }
+
+ return cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription}");
+ }
+
public static bool isLighterColor(int colorId) {
return CustomColors.lighterColors.Contains(colorId);
}
public static bool isCustomServer() {
- if (DestroyableSingleton<ServerManager>.Instance == null) return false;
- StringNames n = DestroyableSingleton<ServerManager>.Instance.CurrentRegion.TranslateName;
+ if (FastDestroyableSingleton<ServerManager>.Instance == null) return false;
+ StringNames n = FastDestroyableSingleton<ServerManager>.Instance.CurrentRegion.TranslateName;
return n != StringNames.ServerNA && n != StringNames.ServerEU && n != StringNames.ServerAS;
}
public static void clearAllTasks(this PlayerControl player) {
if (player == null) return;
- foreach (var playerTask in player.myTasks)
+ foreach (var playerTask in player.myTasks.GetFastEnumerator())
{
playerTask.OnRemove();
UnityEngine.Object.Destroy(playerTask.gameObject);
public static bool hidePlayerName(PlayerControl source, PlayerControl target) {
if (Camouflager.camouflageTimer > 0f) return true; // No names are visible
+ else if (Ninja.isInvisble && Ninja.ninja == target) return true;
else if (!MapOptions.hidePlayerNames) return false; // All names are visible
else if (source == null || target == null) return true;
else if (source == target) return false; // Player sees his own name
target.RawSetHat(hatId, colorId);
target.RawSetName(hidePlayerName(PlayerControl.LocalPlayer, target) ? "" : playerName);
- SkinViewData nextSkin = DestroyableSingleton<HatManager>.Instance.GetSkinById(skinId).viewData.viewData;
+ SkinViewData nextSkin = FastDestroyableSingleton<HatManager>.Instance.GetSkinById(skinId).viewData.viewData;
PlayerPhysics playerPhysics = target.MyPhysics;
AnimationClip clip = null;
var spriteAnim = playerPhysics.Skin.animator;
else clip = nextSkin.IdleAnim;
float progress = playerPhysics.Animator.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
playerPhysics.Skin.skin = nextSkin;
- if (playerPhysics.Skin.layer.material == DestroyableSingleton<HatManager>.Instance.PlayerMaterial)
+ if (playerPhysics.Skin.layer.material == FastDestroyableSingleton<HatManager>.Instance.PlayerMaterial)
PlayerControl.SetPlayerMaterialColors(colorId, playerPhysics.Skin.layer);
spriteAnim.Play(clip, 1f);
spriteAnim.m_animator.Play("a", 0, progress % 1);
spriteAnim.m_animator.Update(0f);
if (target.CurrentPet) UnityEngine.Object.Destroy(target.CurrentPet.gameObject);
- target.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.GetPetById(petId).viewData.viewData);
+ target.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(FastDestroyableSingleton<HatManager>.Instance.GetPetById(petId).viewData.viewData);
target.CurrentPet.transform.position = target.transform.position;
target.CurrentPet.Source = target;
target.CurrentPet.Visible = target.Visible;
}
public static void showFlash(Color color, float duration=1f) {
- if (HudManager.Instance == null || HudManager.Instance.FullScreen == null) return;
- HudManager.Instance.FullScreen.gameObject.SetActive(true);
- HudManager.Instance.FullScreen.enabled = true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
- var renderer = HudManager.Instance.FullScreen;
+ if (FastDestroyableSingleton<HudManager>.Instance == null || FastDestroyableSingleton<HudManager>.Instance.FullScreen == null) return;
+ FastDestroyableSingleton<HudManager>.Instance.FullScreen.gameObject.SetActive(true);
+ FastDestroyableSingleton<HudManager>.Instance.FullScreen.enabled = true;
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
+ var renderer = FastDestroyableSingleton<HudManager>.Instance.FullScreen;
if (p < 0.5) {
if (renderer != null)
public static List<PlayerControl> getKillerTeamMembers(PlayerControl player) {
List<PlayerControl> team = new List<PlayerControl>();
- foreach(PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach(PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (player.Data.Role.IsImpostor && p.Data.Role.IsImpostor && player.PlayerId != p.PlayerId && team.All(x => x.PlayerId != p.PlayerId)) team.Add(p);
else if (player == Jackal.jackal && p == Sidekick.sidekick) team.Add(p);
else if (player == Sidekick.sidekick && p == Jackal.jackal) team.Add(p);
return team;
}
+
+
+ public static void toggleZoom(bool reset=false) {
+ float zoomFactor = 4f;
+ if (HudManagerStartPatch.zoomOutStatus)
+ zoomFactor = 1 / zoomFactor;
+ else if (reset) return; // Dont zoom out if meant to reset.
+ HudManagerStartPatch.zoomOutStatus = !HudManagerStartPatch.zoomOutStatus;
+ Camera.main.orthographicSize *= zoomFactor;
+ foreach (var cam in Camera.allCameras) {
+ if (cam != null && cam.gameObject.name == "UI Camera") cam.orthographicSize *= zoomFactor; // The UI is scaled too, else we cant click the buttons. Downside: map is super small.
+ }
+
+ HudManagerStartPatch.zoomOutButton.Sprite = HudManagerStartPatch.zoomOutStatus ? Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlusButton.png", 150f / zoomFactor * 2) : Helpers.loadSpriteFromResources("TheOtherRoles.Resources.MinusButton.png", 150f);
+ HudManagerStartPatch.zoomOutButton.PositionOffset = HudManagerStartPatch.zoomOutStatus ? new Vector3(0f, 3f, 0) : new Vector3(0.4f, 2.8f, 0);
+ ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height); // This will move button positions to the correct position.
+ }
public static object TryCast(this Il2CppObjectBase self, Type type)
{
--- /dev/null
+using System;
+using System.Linq.Expressions;
+using UnityEngine;
+
+namespace TheOtherRoles;
+
+public static class Il2CppHelpers
+{
+ private static class CastHelper<T> where T : Il2CppObjectBase
+ {
+ public static Func<IntPtr, T> Cast;
+ static CastHelper()
+ {
+ var constructor = typeof(T).GetConstructor(new[] {typeof(IntPtr)});
+ var ptr = Expression.Parameter(typeof(IntPtr));
+ var create = Expression.New(constructor!, ptr);
+ var lambda = Expression.Lambda<Func<IntPtr, T>>(create, ptr);
+ Cast = lambda.Compile();
+ }
+ }
+
+ public static T CastFast<T>(this Il2CppObjectBase obj) where T : Il2CppObjectBase
+ {
+ if (obj is T casted) return casted;
+ return obj.Pointer.CastFast<T>();
+ }
+
+ public static T CastFast<T>(this IntPtr ptr)where T : Il2CppObjectBase
+ {
+ return CastHelper<T>.Cast(ptr);
+ }
+}
\ No newline at end of file
-using BepInEx;
+global using UnhollowerBaseLib;
+global using UnhollowerBaseLib.Attributes;
+global using UnhollowerRuntimeLib;
+
+using BepInEx;
using BepInEx.Configuration;
using BepInEx.IL2CPP;
using HarmonyLib;
using Hazel;
using System.Collections.Generic;
-using System.Security.Cryptography;
using System.Linq;
-using System.Net;
-using System.IO;
using System;
-using System.Reflection;
-using UnhollowerBaseLib;
using UnityEngine;
using TheOtherRoles.Modules;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles
{
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "4.1.1";
+ public const string VersionString = "4.1.2";
public static Version Version = Version.Parse(VersionString);
internal static BepInEx.Logging.ManualLogSource Logger;
public static IRegionInfo[] defaultRegions;
public static void UpdateRegions() {
- ServerManager serverManager = DestroyableSingleton<ServerManager>.Instance;
+ ServerManager serverManager = FastDestroyableSingleton<ServerManager>.Instance;
IRegionInfo[] regions = defaultRegions;
var CustomRegion = new DnsRegionInfo(Ip.Value, "Custom", StringNames.NoTranslation, Ip.Value, Port.Value, false);
- regions = regions.Concat(new IRegionInfo[] { CustomRegion.Cast<IRegionInfo>() }).ToArray();
+ regions = regions.Concat(new IRegionInfo[] { CustomRegion.CastFast<IRegionInfo>() }).ToArray();
ServerManager.DefaultRegions = regions;
serverManager.AvailableRegions = regions;
}
using System.Collections.Generic;
-using System.Collections;
-using System;
using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
namespace TheOtherRoles{
static class MapOptions {
using System;
-using System.Security.Cryptography;
-using System.Text;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
using HarmonyLib;
-using UnityEngine;
using System.Linq;
-using UnhollowerBaseLib;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Modules {
[HarmonyPatch]
if (AmongUsClient.Instance.GameMode == GameModes.FreePlay) {
if (text.ToLower().Equals("/murder")) {
PlayerControl.LocalPlayer.Exiled();
- HudManager.Instance.KillOverlay.ShowKillAnimation(PlayerControl.LocalPlayer.Data, PlayerControl.LocalPlayer.Data);
+ FastDestroyableSingleton<HudManager>.Instance.KillOverlay.ShowKillAnimation(PlayerControl.LocalPlayer.Data, PlayerControl.LocalPlayer.Data);
handled = true;
} else if (text.ToLower().StartsWith("/color ")) {
handled = true;
[HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))]
public static class AddChat {
public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer) {
- if (__instance != DestroyableSingleton<HudManager>.Instance.Chat)
+ if (__instance != FastDestroyableSingleton<HudManager>.Instance.Chat)
return true;
PlayerControl localPlayer = PlayerControl.LocalPlayer;
return localPlayer == null || (MeetingHud.Instance != null || LobbyBehaviour.Instance != null || (localPlayer.Data.IsDead || localPlayer.isLover() && Lovers.enableChat) || (int)sourcePlayer.PlayerId == (int)PlayerControl.LocalPlayer.PlayerId);
-using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using UnityEngine;
-using Il2CppSystem;
using HarmonyLib;
-using UnhollowerBaseLib;
-using Assets.CoreScripts;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Modules {
public class CustomColors {
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckColor))]
private static class PlayerControlCheckColorPatch {
private static bool isTaken(PlayerControl player, uint color) {
- foreach (GameData.PlayerInfo p in GameData.Instance.AllPlayers)
+ foreach (GameData.PlayerInfo p in GameData.Instance.AllPlayers.GetFastEnumerator())
if (!p.Disconnected && p.PlayerId != player.PlayerId && p.DefaultOutfit.ColorId == color)
return true;
return false;
-using System;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
-using Il2CppSystem;
using HarmonyLib;
using UnityEngine;
-using UnhollowerBaseLib;
using System.IO;
-using System.Reflection;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Security.Cryptography;
using Newtonsoft.Json.Linq;
-using Newtonsoft.Json;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Modules {
[HarmonyPatch]
private static HatData CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) {
if (hatShader == null) {
- Material tmpShader = DestroyableSingleton<HatManager>.Instance.PlayerMaterial;
+ Material tmpShader = FastDestroyableSingleton<HatManager>.Instance.PlayerMaterial;
hatShader = tmpShader;
}
if (DestroyableSingleton<TutorialManager>.InstanceExists) {
try {
string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test";
+ if (!Directory.Exists(filePath))
+ Directory.CreateDirectory(filePath);
DirectoryInfo d = new DirectoryInfo(filePath);
string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Text files
List<CustomHat> hats = createCustomHatDetails(filePaths, true);
ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(__instance.ColorTabPrefab, __instance.scroller.Inner);
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) {
colorChip.Button.OnMouseOver.AddListener((System.Action)(() => __instance.SelectHat(hat)));
- colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(DestroyableSingleton<HatManager>.Instance.GetHatById(SaveManager.LastHat))));
+ colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(FastDestroyableSingleton<HatManager>.Instance.GetHatById(SaveManager.LastHat))));
colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.ClickEquip()));
} else {
colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.SelectHat(hat)));
UnityEngine.Object.Destroy(__instance.scroller.Inner.GetChild(i).gameObject);
__instance.ColorChips = new Il2CppSystem.Collections.Generic.List<ColorChip>();
- HatData[] unlockedHats = DestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
+ HatData[] unlockedHats = FastDestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
Dictionary<string, List<System.Tuple<HatData, HatExtension>>> packages = new Dictionary<string, List<System.Tuple<HatData, HatExtension>>>();
foreach (HatData hatBehaviour in unlockedHats) {
using Hazel;
using System.Reflection;
using System.Text;
-using static TheOtherRoles.TheOtherRoles;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles {
public class CustomOption {
}
public static CustomOption Create(int id, CustomOptionType type, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false) {
- List<float> selections = new List<float>();
+ List<object> selections = new();
for (float s = min; s <= max; s += step)
selections.Add(s);
- return new CustomOption(id, type, name, selections.Cast<object>().ToArray(), defaultValue, parent, isHeader);
+ return new CustomOption(id, type, name, selections.ToArray(), defaultValue, parent, isHeader);
}
public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false) {
TheOtherRolesPlugin.optionsPage = 6;
}
if (page != TheOtherRolesPlugin.optionsPage) {
- Vector3 position = (Vector3)HudManager.Instance?.GameSettings?.transform.localPosition;
+ Vector3 position = (Vector3)FastDestroyableSingleton<HudManager>.Instance?.GameSettings?.transform.localPosition;
if (position != null) {
- HudManager.Instance.GameSettings.transform.localPosition = new Vector3(position.x, 2.9f, position.z);
+ FastDestroyableSingleton<HudManager>.Instance.GameSettings.transform.localPosition = new Vector3(position.x, 2.9f, position.z);
}
}
}
using System;
using HarmonyLib;
-using UnityEngine;
using Hazel;
using InnerNet;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Modules {
[HarmonyPatch]
LobbyLimit = Math.Clamp(LobbyLimit, 4, 15);
if (LobbyLimit != PlayerControl.GameOptions.MaxPlayers) {
PlayerControl.GameOptions.MaxPlayers = LobbyLimit;
- DestroyableSingleton<GameStartManager>.Instance.LastPlayerCount = LobbyLimit;
+ FastDestroyableSingleton<GameStartManager>.Instance.LastPlayerCount = LobbyLimit;
PlayerControl.LocalPlayer.RpcSyncSettings(PlayerControl.GameOptions);
__instance.AddChat(PlayerControl.LocalPlayer, $"Lobby Size changed to {LobbyLimit} players");
} else {
using System;
using System.Collections;
using System.IO;
+using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using BepInEx;
+using BepInEx.Bootstrap;
+using BepInEx.IL2CPP;
using BepInEx.IL2CPP.Utils;
+using Mono.Cecil;
using Newtonsoft.Json.Linq;
using TMPro;
using Twitch;
-using UnhollowerBaseLib.Attributes;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
+using Action = System.Action;
+using IntPtr = System.IntPtr;
using Version = SemanticVersioning.Version;
namespace TheOtherRoles.Modules
{
public static readonly bool CheckForSubmergedUpdates = true;
public static bool showPopUp = true;
-
+ public static bool updateInProgress = false;
+
public static ModUpdateBehaviour Instance { get; private set; }
public ModUpdateBehaviour(IntPtr ptr) : base(ptr) { }
-
public class UpdateData
{
public string Content;
public string Tag;
public JObject Request;
+ public Version Version => Version.Parse(Tag);
public UpdateData(JObject data)
{
public bool IsNewer(Version version)
{
if (!Version.TryParse(Tag, out var myVersion)) return false;
- return myVersion > version;
+ return myVersion.BaseVersion() > version.BaseVersion();
}
}
if (Instance) Destroy(this);
Instance = this;
- SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) (OnSceneLoaded));
+ SceneManager.add_sceneLoaded((System.Action<Scene, LoadSceneMode>) (OnSceneLoaded));
this.StartCoroutine(CoCheckUpdates());
foreach (var file in Directory.GetFiles(Paths.PluginPath, "*.old"))
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
- if (scene.name != "MainMenu") return;
+ if (updateInProgress || scene.name != "MainMenu") return;
if (RequiredUpdateData is null) {
showPopUp = false;
return;
var text = button.transform.GetChild(0).GetComponent<TMP_Text>();
string t = "Update";
- if (TORUpdate is null) t = SubmergedCompatibility.Loaded ? $"Update\nSubmerged" : $"Download\nSubmerged";
+ if (TORUpdate is null && SubmergedUpdate is not null) t = SubmergedCompatibility.Loaded ? $"Update\nSubmerged" : $"Download\nSubmerged";
- StartCoroutine(Effects.Lerp(0.1f, (Action<float>)(p => text.SetText(t))));
+ StartCoroutine(Effects.Lerp(0.1f, (System.Action<float>)(p => text.SetText(t))));
buttonSprite.color = text.color = Color.red;
passiveButton.OnMouseOut.AddListener((Action)(() => buttonSprite.color = text.color = Color.red));
[HideFromIl2Cpp]
public IEnumerator CoUpdate()
{
+ updateInProgress = true;
var isSubmerged = TORUpdate is null;
var updateName = (isSubmerged ? "Submerged" : "The Other Roles");
var popup = Instantiate(TwitchManager.Instance.TwitchPopup);
popup.TextAreaTMP.fontSize *= 0.7f;
popup.TextAreaTMP.enableAutoSizing = false;
+
popup.Show();
- popup.TextAreaTMP.text = $"Updating {updateName}\nPlease wait...";
+ var button = popup.transform.GetChild(2).gameObject;
+ button.SetActive(false);
+ popup.TextAreaTMP.text = $"Updating {updateName}\nPlease wait...";
+
var download = Task.Run(DownloadUpdate);
while (!download.IsCompleted) yield return null;
+
+ button.SetActive(true);
popup.TextAreaTMP.text = download.Result ? $"{updateName}\nupdated successfully\nPlease restart the game." : "Update wasn't successful\nTry again later,\nor update manually.";
-
}
[HideFromIl2Cpp]
Instance.SubmergedUpdate = submergedUpdateCheck.Result;
}
}
+
+ Instance.OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
}
[HideFromIl2Cpp]
return new UpdateData(data);
}
+ private bool TryUpdateSubmergedInternally()
+ {
+ if (SubmergedUpdate == null) return false;
+ try
+ {
+ if (!SubmergedCompatibility.LoadedExternally) return false;
+ var thisAsm = Assembly.GetCallingAssembly();
+ var resourceName = thisAsm.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith("Submerged.dll"));
+ if (resourceName == default) return false;
+
+ using var submergedStream = thisAsm.GetManifestResourceStream(resourceName)!;
+ var asmDef = AssemblyDefinition.ReadAssembly(submergedStream, TypeLoader.ReaderParameters);
+ var pluginType = asmDef.MainModule.Types.FirstOrDefault(t => t.IsSubtypeOf(typeof(BasePlugin)));
+ var info = IL2CPPChainloader.ToPluginInfo(pluginType, "");
+ if (SubmergedUpdate.IsNewer(info.Metadata.Version)) return false;
+ File.Delete(SubmergedCompatibility.Assembly.Location);
+
+ }
+ catch (Exception e)
+ {
+ TheOtherRolesPlugin.Logger.LogError(e);
+ return false;
+ }
+ return true;
+ }
+
+
[HideFromIl2Cpp]
public async Task<bool> DownloadUpdate()
{
var isSubmerged = TORUpdate is null;
+ if (isSubmerged && TryUpdateSubmergedInternally()) return true;
var data = isSubmerged ? SubmergedUpdate : TORUpdate;
var client = new HttpClient();
if (downloadURI.Length == 0) return false;
var res = await client.GetAsync(downloadURI, HttpCompletionOption.ResponseContentRead);
- string codeBase = "";
- if (!isSubmerged)
- codeBase = Assembly.GetExecutingAssembly().CodeBase;
- else if (SubmergedCompatibility.Loaded)
- codeBase = SubmergedCompatibility.Assembly.CodeBase;
- else {
- Uri pluginsFolder = new Uri(new Uri(Assembly.GetExecutingAssembly().CodeBase), ".");
- codeBase = pluginsFolder.OriginalString + "/Submerged.dll";
- }
-
- UriBuilder uri = new UriBuilder(codeBase);
- string fullname = Uri.UnescapeDataString(uri.Path);
- if (File.Exists(fullname + ".old")) File.Delete(fullname + ".old");
- if (File.Exists(fullname)) File.Move(fullname, fullname + ".old");
+ string filePath = Path.Combine(Paths.PluginPath, isSubmerged ? "Submerged.dll" : "TheOtherRoles.dll");
+ if (File.Exists(filePath + ".old")) File.Delete(filePath + ".old");
+ if (File.Exists(filePath)) File.Move(filePath, filePath + ".old");
await using var responseStream = await res.Content.ReadAsStreamAsync();
- await using var fileStream = File.Create(fullname);
+ await using var fileStream = File.Create(filePath);
await responseStream.CopyToAsync(fileStream);
return true;
}
}
-}
\ No newline at end of file
+}
-using System;
-using System.Collections.Generic;
-using System.Collections;
using UnityEngine;
namespace TheOtherRoles.Objects {
using System;
using System.Collections.Generic;
-using System.Collections;
+using TheOtherRoles.Utilities;
using UnityEngine;
using static TheOtherRoles.TheOtherRoles;
var index = rnd.Next(0, sp.Count);
- blood = new GameObject("Blood" + index) { layer = 11 };
- Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.y / 1000 + +0.001f);
+ blood = new GameObject("Blood" + index);
+ Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.y / 1000 + 0.001f);
blood.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
blood.transform.position = position;
blood.transform.localPosition = position;
spriteRenderer = blood.AddComponent<SpriteRenderer>();
spriteRenderer.sprite = sp[index];
- spriteRenderer.material = DestroyableSingleton<HatManager>.Instance.PlayerMaterial;
+ spriteRenderer.material = FastDestroyableSingleton<HatManager>.Instance.PlayerMaterial;
PlayerControl.SetPlayerMaterialColors(color, spriteRenderer);
// spriteRenderer.color = color;
blood.SetActive(true);
bloodytrail.Add(this);
- HudManager.Instance.StartCoroutine(Effects.Lerp(10f, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(10f, new Action<float>((p) => {
Color c = color;
if (Camouflager.camouflageTimer > 0) c = Palette.PlayerColors[6];
if (spriteRenderer) spriteRenderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01(1 - p));
using System;
using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
+using TMPro;
using UnityEngine;
using UnityEngine.UI;
using static TheOtherRoles.TheOtherRoles;
{
public static List<CustomButton> buttons = new List<CustomButton>();
public ActionButton actionButton;
+ public GameObject actionButtonGameObject;
+ public SpriteRenderer actionButtonRenderer;
+ public Material actionButtonMat;
+ public TextMeshPro actionButtonLabelText;
public Vector3 PositionOffset;
public float MaxTimer = float.MaxValue;
public float Timer = 0f;
public KeyCode? hotkey;
private string buttonText;
public bool isHandcuffed = false;
+ private static readonly int Desat = Shader.PropertyToID("_Desat");
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 = "")
{
Timer = 16.2f;
buttons.Add(this);
actionButton = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.KillButton.transform.parent);
+ actionButtonGameObject = actionButton.gameObject;
+ actionButtonRenderer = actionButton.graphic;
+ actionButtonMat = actionButtonRenderer.material;
+ actionButtonLabelText = actionButton.buttonLabelText;
PassiveButton button = actionButton.GetComponent<PassiveButton>();
- this.showButtonText = (actionButton.graphic.sprite == Sprite || buttonText != "");
+ this.showButtonText = (actionButtonRenderer.sprite == Sprite || buttonText != "");
button.OnClick = new Button.ButtonClickedEvent();
button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent);
{
if (this.Timer < 0f && HasButton() && CouldUse())
{
- actionButton.graphic.color = new Color(1f, 1f, 1f, 0.3f);
+ actionButtonRenderer.color = new Color(1f, 1f, 1f, 0.3f);
this.OnClick();
// Deputy skip onClickEvent if handcuffed
public void setActive(bool isActive) {
if (isActive) {
- actionButton.gameObject.SetActive(true);
- actionButton.graphic.enabled = true;
+ actionButtonGameObject.SetActive(true);
+ actionButtonRenderer.enabled = true;
} else {
- actionButton.gameObject.SetActive(false);
- actionButton.graphic.enabled = false;
+ actionButtonGameObject.SetActive(false);
+ actionButtonRenderer.enabled = false;
}
}
public void Update()
{
- if (PlayerControl.LocalPlayer.Data == null || MeetingHud.Instance || ExileController.Instance || !HasButton()) {
+ var localPlayer = PlayerControl.LocalPlayer;
+ var moveable = localPlayer.moveable;
+
+ if (localPlayer.Data == null || MeetingHud.Instance || ExileController.Instance || !HasButton()) {
setActive(false);
return;
}
if (DeputyTimer >= 0) { // This had to be reordered, so that the handcuffs do not stop the underlying timers from running
if (HasEffect && isEffectActive)
DeputyTimer -= Time.deltaTime;
- else if (!PlayerControl.LocalPlayer.inVent && PlayerControl.LocalPlayer.moveable)
+ else if (!localPlayer.inVent && moveable)
DeputyTimer -= Time.deltaTime;
}
return;
}
- actionButton.graphic.sprite = Sprite;
+ actionButtonRenderer.sprite = Sprite;
if (showButtonText && buttonText != ""){
actionButton.OverrideText(buttonText);
}
- actionButton.buttonLabelText.enabled = showButtonText; // Only show the text if it's a kill button
+ actionButtonLabelText.enabled = showButtonText; // Only show the text if it's a kill button
if (hudManager.UseButton != null) {
Vector3 pos = hudManager.UseButton.transform.localPosition;
if (mirror) pos = new Vector3(-pos.x, pos.y, pos.z);
actionButton.transform.localPosition = pos + PositionOffset;
}
if (CouldUse()) {
- actionButton.graphic.color = actionButton.buttonLabelText.color = Palette.EnabledColor;
- actionButton.graphic.material.SetFloat("_Desat", 0f);
+ actionButtonRenderer.color = actionButtonLabelText.color = Palette.EnabledColor;
+ actionButtonMat.SetFloat(Desat, 0f);
} else {
- actionButton.graphic.color = actionButton.buttonLabelText.color = Palette.DisabledClear;
- actionButton.graphic.material.SetFloat("_Desat", 1f);
+ actionButtonRenderer.color = actionButtonLabelText.color = Palette.DisabledClear;
+ actionButtonMat.SetFloat(Desat, 1f);
}
if (Timer >= 0) {
if (HasEffect && isEffectActive)
Timer -= Time.deltaTime;
- else if (!PlayerControl.LocalPlayer.inVent && PlayerControl.LocalPlayer.moveable)
+ else if (!localPlayer.inVent && moveable)
Timer -= Time.deltaTime;
}
if (hotkey.HasValue && Input.GetKeyDown(hotkey.Value)) onClickEvent();
// Deputy disable the button and display Handcuffs instead...
- if (Deputy.handcuffedPlayers.Contains(PlayerControl.LocalPlayer.PlayerId)) {
+ if (Deputy.handcuffedPlayers.Contains(localPlayer.PlayerId)) {
OnClick = () => {
Deputy.setHandcuffedKnows();
};
using UnityEngine;
using System.Collections.Generic;
using System;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Objects {
private static List<CustomMessage> customMessages = new List<CustomMessage>();
public CustomMessage(string message, float duration) {
- RoomTracker roomTracker = HudManager.Instance?.roomTracker;
+ RoomTracker roomTracker = FastDestroyableSingleton<HudManager>.Instance?.roomTracker;
if (roomTracker != null) {
GameObject gameObject = UnityEngine.Object.Instantiate(roomTracker.gameObject);
- gameObject.transform.SetParent(HudManager.Instance.transform);
+ gameObject.transform.SetParent(FastDestroyableSingleton<HudManager>.Instance.transform);
UnityEngine.Object.DestroyImmediate(gameObject.GetComponent<RoomTracker>());
text = gameObject.GetComponent<TMPro.TMP_Text>();
text.text = message;
gameObject.transform.localPosition = new Vector3(0, -1.8f, gameObject.transform.localPosition.z);
customMessages.Add(this);
- HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
bool even = ((int)(p * duration / 0.25f)) % 2 == 0; // Bool flips every 0.25 seconds
string prefix = (even ? "<color=#FCBA03FF>" : "<color=#FF0000FF>");
text.text = prefix + message + "</color>";
using System;
using System.Collections.Generic;
-using System.Collections;
+using TheOtherRoles.Utilities;
using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
namespace TheOtherRoles.Objects {
class Footprint {
else
this.color = Palette.PlayerColors[(int) player.Data.DefaultOutfit.ColorId];
- footprint = new GameObject("Footprint") { layer = 11 };
+ footprint = new GameObject("Footprint");
footprint.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.y / 1000 + 0.001f);
footprint.transform.position = position;
footprint.SetActive(true);
footprints.Add(this);
- HudManager.Instance.StartCoroutine(Effects.Lerp(footprintDuration, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(footprintDuration, new Action<float>((p) => {
Color c = color;
if (!anonymousFootprints && owner != null) {
if (owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget?.Data != null)
})));
}
}
-}
\ No newline at end of file
+}
-using System;
using System.Collections.Generic;
-using System.Collections;
using UnityEngine;
namespace TheOtherRoles.Objects {
using System;
using System.Collections.Generic;
-using System.Collections;
using UnityEngine;
using System.Linq;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Objects {
JackInTheBox box = AllJackInTheBoxes.FirstOrDefault((x) => x?.vent != null && x.vent.Id == ventId);
if (box == null) return;
- HudManager.Instance.StartCoroutine(Effects.Lerp(0.6f, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(0.6f, new Action<float>((p) => {
if (box.boxRenderer != null) {
box.boxRenderer.sprite = getBoxAnimationSprite((int)(p * boxAnimationSprites.Length));
if (p == 1f) box.boxRenderer.sprite = getBoxAnimationSprite(0);
vent.ExitVentAnim = null;
vent.Offset = new Vector3(0f, 0.25f, 0f);
vent.GetComponent<PowerTools.SpriteAnim>()?.Stop();
- vent.Id = ShipStatus.Instance.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id
+ vent.Id = MapUtilities.CachedShipStatus.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id
var ventRenderer = vent.GetComponent<SpriteRenderer>();
ventRenderer.sprite = null; // Use the box.boxRenderer instead
vent.myRend = ventRenderer;
- var allVentsList = ShipStatus.Instance.AllVents.ToList();
+ var allVentsList = MapUtilities.CachedShipStatus.AllVents.ToList();
allVentsList.Add(vent);
- ShipStatus.Instance.AllVents = allVentsList.ToArray();
+ MapUtilities.CachedShipStatus.AllVents = allVentsList.ToArray();
vent.gameObject.SetActive(false);
vent.name = "JackInTheBoxVent_" + vent.Id;
using System;
using System.Collections.Generic;
-using System.Collections;
+using TheOtherRoles.Utilities;
using UnityEngine;
namespace TheOtherRoles.Objects {
}
public NinjaTrace(Vector2 p, float duration=1f) {
- trace = new GameObject("NinjaTrace") { layer = 11 };
+ trace = new GameObject("NinjaTrace");
trace.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
//Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player
Vector3 position = new Vector3(p.x, p.y, p.y / 1000f + 0.01f);
// display the ninjas color in the trace
float colorDuration = CustomOptionHolder.ninjaTraceColorTime.getFloat();
- HudManager.Instance.StartCoroutine(Effects.Lerp(colorDuration, new Action<float>((p) => {
+ 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;
else c = Palette.PlayerColors[6];
float fadeOutDuration = 1f;
if (fadeOutDuration > duration) fadeOutDuration = 0.5f * duration;
- HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
float interP = 0f;
if (p < (duration - fadeOutDuration) / duration)
interP = 0f;
}
}
}
-}
\ No newline at end of file
+}
using System;
using UnityEngine;
using System.Collections.Generic;
+using TheOtherRoles.Utilities;
using static TheOtherRoles.TheOtherRoles;
namespace TheOtherRoles.Objects {
}
teleportedPlayers.Add(new tpLogEntry(playerId, playerNameDisplay, DateTime.UtcNow));
- HudManager.Instance.StartCoroutine(Effects.Lerp(teleportDuration, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(teleportDuration, new Action<float>((p) => {
if (firstPortal != null && firstPortal.animationFgRenderer != null && secondPortal != null && secondPortal.animationFgRenderer != null) {
firstPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length));
secondPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length));
portalRenderer.sprite = portalSprite;
Vector3 fgPosition = new Vector3(0, 0, -1f);
- portalFgAnimationGameObject = new GameObject("PortalAnimationFG") { layer = 11 };
+ portalFgAnimationGameObject = new GameObject("PortalAnimationFG");
portalFgAnimationGameObject.transform.SetParent(portalGameObject.transform);
portalFgAnimationGameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
portalFgAnimationGameObject.transform.localPosition = fgPosition;
animationFgRenderer = portalFgAnimationGameObject.AddComponent<SpriteRenderer>();
- animationFgRenderer.material = DestroyableSingleton<HatManager>.Instance.PlayerMaterial;
+ animationFgRenderer.material = FastDestroyableSingleton<HatManager>.Instance.PlayerMaterial;
// Only render the inactive portals for the Portalmaker
bool playerIsPortalmaker = PlayerControl.LocalPlayer == TheOtherRoles.Portalmaker.portalmaker;
}
-}
\ No newline at end of file
+}
using UnityEngine;
using System;
using System.Collections.Generic;
+using TheOtherRoles.Utilities;
using TMPro;
using UnityEngine.Events;
-using UnityEngine.SceneManagement;
using static UnityEngine.UI.Button;
using Object = UnityEngine.Object;
{
if (!popUp) return;
- if (__instance.transform.parent && __instance.transform.parent == HudManager.Instance.transform)
+ if (__instance.transform.parent && __instance.transform.parent == FastDestroyableSingleton<HudManager>.Instance.transform)
{
- popUp.transform.SetParent(HudManager.Instance.transform);
+ popUp.transform.SetParent(FastDestroyableSingleton<HudManager>.Instance.transform);
popUp.transform.localPosition = new Vector3(0, 0, -800f);
}
else
using HarmonyLib;
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using TheOtherRoles.Utilities;
using UnityEngine;
namespace TheOtherRoles.Patches {
public static string contributorsCredentials =
$@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color>
-GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit</size>";
+GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit, probablyadnf</size>";
[HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
private static class VersionShowerPatch
}
[HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))]
- private static class PingTrackerPatch
+ internal static class PingTrackerPatch
{
- private static GameObject modStamp;
+ public static GameObject modStamp;
static void Prefix(PingTracker __instance) {
if (modStamp == null) {
modStamp = new GameObject("ModStamp");
modStamp.transform.localScale *= 0.6f;
}
float offset = (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) ? 0.75f : 0f;
- modStamp.transform.position = HudManager.Instance.MapButton.transform.position + Vector3.down * offset;
+ modStamp.transform.position = FastDestroyableSingleton<HudManager>.Instance.MapButton.transform.position + Vector3.down * offset;
}
static void Postfix(PingTracker __instance){
using HarmonyLib;
using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.GameHistory;
-using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
-using Hazel;
-using UnhollowerBaseLib;
using System;
using System.Text;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Patches {
enum CustomGameOverReason {
public static void Prefix(AmongUsClient __instance, [HarmonyArgument(0)]ref EndGameResult endGameResult) {
gameOverReason = endGameResult.GameOverReason;
if ((int)endGameResult.GameOverReason >= 10) endGameResult.GameOverReason = GameOverReason.ImpostorByKill;
+
+ // Reset zoomed out ghosts
+ Helpers.toggleZoom(reset: true);
}
public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref EndGameResult endGameResult) {
AdditionalTempData.clear();
- foreach(var playerControl in PlayerControl.AllPlayerControls) {
+ foreach(var playerControl in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
var roles = RoleInfo.getRoleInfoForPlayer(playerControl);
var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(playerControl.Data);
AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted });
notWinners.AddRange(Jackal.formerJackals);
List<WinningPlayerData> winnersToRemove = new List<WinningPlayerData>();
- foreach (WinningPlayerData winner in TempData.winners) {
+ foreach (WinningPlayerData winner in TempData.winners.GetFastEnumerator()) {
if (notWinners.Any(x => x.Data.PlayerName == winner.PlayerName)) winnersToRemove.Add(winner);
}
foreach (var winner in winnersToRemove) TempData.winners.Remove(winner);
if (!Lovers.existingWithKiller()) {
AdditionalTempData.winCondition = WinCondition.LoversTeamWin;
TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (p == null) continue;
if (p == Lovers.lover1 || p == Lovers.lover2)
TempData.winners.Add(new WinningPlayerData(p.Data));
// Possible Additional winner: Lawyer
if (!lawyerSoloWin && Lawyer.lawyer != null && Lawyer.target != null && (!Lawyer.target.Data.IsDead || Lawyer.target == Jester.jester) && !Pursuer.notAckedExiled) {
WinningPlayerData winningClient = null;
- foreach (WinningPlayerData winner in TempData.winners) {
+ foreach (WinningPlayerData winner in TempData.winners.GetFastEnumerator()) {
if (winner.PlayerName == Lawyer.target.Data.PlayerName)
winningClient = winner;
}
}
private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) {
- if (__instance.Systems == null) return false;
- ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.LifeSupp) ? __instance.Systems[SystemTypes.LifeSupp] : null;
+ if (MapUtilities.Systems == null) return false;
+ var systemType = MapUtilities.Systems.ContainsKey(SystemTypes.LifeSupp) ? MapUtilities.Systems[SystemTypes.LifeSupp] : null;
if (systemType != null) {
LifeSuppSystemType lifeSuppSystemType = systemType.TryCast<LifeSuppSystemType>();
if (lifeSuppSystemType != null && lifeSuppSystemType.Countdown < 0f) {
return true;
}
}
- ISystemType systemType2 = __instance.Systems.ContainsKey(SystemTypes.Reactor) ? __instance.Systems[SystemTypes.Reactor] : null;
+ var systemType2 = MapUtilities.Systems.ContainsKey(SystemTypes.Reactor) ? MapUtilities.Systems[SystemTypes.Reactor] : null;
if (systemType2 == null) {
- systemType2 = __instance.Systems.ContainsKey(SystemTypes.Laboratory) ? __instance.Systems[SystemTypes.Laboratory] : null;
+ systemType2 = MapUtilities.Systems.ContainsKey(SystemTypes.Laboratory) ? MapUtilities.Systems[SystemTypes.Laboratory] : null;
}
if (systemType2 != null) {
ICriticalSabotage criticalSystem = systemType2.TryCast<ICriticalSabotage>();
bool impLover = false;
bool jackalLover = false;
- foreach (var playerInfo in GameData.Instance.AllPlayers)
+ foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator())
{
if (!playerInfo.Disconnected)
{
TeamJackalHasAliveLover = jackalLover;
}
}
-}
\ No newline at end of file
+}
using Hazel;
using System.Collections.Generic;
using System.Linq;
-using UnhollowerBaseLib;
using static TheOtherRoles.TheOtherRoles;
using TheOtherRoles.Objects;
-using static TheOtherRoles.MapOptions;
-using System.Collections;
using System;
-using System.Text;
+using TheOtherRoles.Utilities;
using UnityEngine;
-using System.Reflection;
namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(ExileController), nameof(ExileController.Begin))]
Witch.futureSpelled = new List<PlayerControl>();
// SecurityGuard vents and cameras
- var allCameras = ShipStatus.Instance.AllCameras.ToList();
+ var allCameras = MapUtilities.CachedShipStatus.AllCameras.ToList();
MapOptions.camerasToAdd.ForEach(camera => {
camera.gameObject.SetActive(true);
camera.gameObject.GetComponent<SpriteRenderer>().color = Color.white;
allCameras.Add(camera);
});
- ShipStatus.Instance.AllCameras = allCameras.ToArray();
+ MapUtilities.CachedShipStatus.AllCameras = allCameras.ToArray();
MapOptions.camerasToAdd = new List<SurvCamera>();
foreach (Vent vent in MapOptions.ventsToSeal) {
animator?.Stop();
vent.EnterVentAnim = vent.ExitVentAnim = null;
vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
- if (SubmergedCompatibility.isSubmerged() && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite();
- if (SubmergedCompatibility.isSubmerged() && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite();
+ if (SubmergedCompatibility.IsSubmerged && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite();
+ if (SubmergedCompatibility.IsSubmerged && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite();
vent.myRend.color = Color.white;
vent.name = "SealedVent_" + vent.name;
}
// Workaround to add a "postfix" to the destroying of the exile controller (i.e. cutscene) of submerged
[HarmonyPatch(typeof(UnityEngine.Object), nameof(UnityEngine.Object.Destroy), new Type[] { typeof(GameObject) })]
public static void Prefix(GameObject obj) {
- if (!SubmergedCompatibility.isSubmerged()) return;
+ if (!SubmergedCompatibility.IsSubmerged) return;
if (obj.name.Contains("ExileCutscene")) {
WrapUpPostfix(ExileControllerBeginPatch.lastExiled);
}
rend.sprite = Seer.getSoulSprite();
if(Seer.limitSoulDuration) {
- HudManager.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action<float>((p) => {
if (rend != null) {
var tmp = rend.color;
tmp.a = Mathf.Clamp01(1 - p);
// Arsonist deactivate dead poolable players
if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) {
int visibleCounter = 0;
- Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z);
+ Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.z);
bottomLeft += new Vector3(-0.25f, -0.25f, 0);
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (!MapOptions.playerIcons.ContainsKey(p.PlayerId)) continue;
if (p.Data.IsDead || p.Data.Disconnected) {
MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
// AntiTeleport set position
if (AntiTeleport.antiTeleport.FindAll(x => x.PlayerId == PlayerControl.LocalPlayer.PlayerId).Count > 0) {
PlayerControl.LocalPlayer.transform.position = AntiTeleport.position;
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
SubmergedCompatibility.ChangeFloor(AntiTeleport.position.y > -7);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
-using HarmonyLib;
+using TheOtherRoles.Utilities;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
if (!scene.name.Equals("MMOnline")) return;
if (!TryMoveObjects()) return;
- var editName = DestroyableSingleton<AccountManager>.Instance.accountTab.editNameScreen;
+ var editName = FastDestroyableSingleton<AccountManager>.Instance.accountTab.editNameScreen;
var nameText = Object.Instantiate(editName.nameText.gameObject);
nameText.transform.localPosition += Vector3.up * 2.2f;
using System.Collections.Generic;
using Hazel;
using System;
-using UnhollowerBaseLib;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Patches {
public class GameStartManagerPatch {
// Copy lobby code
string code = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId);
GUIUtility.systemCopyBuffer = code;
- lobbyCodeText = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.RoomCode, new Il2CppReferenceArray<Il2CppSystem.Object>(0)) + "\r\n" + code;
+ lobbyCodeText = FastDestroyableSingleton<TranslationController>.Instance.GetString(StringNames.RoomCode, new Il2CppReferenceArray<Il2CppSystem.Object>(0)) + "\r\n" + code;
}
}
bool continueStart = true;
if (AmongUsClient.Instance.AmHost) {
- foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) {
+ foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients.GetFastEnumerator()) {
if (client.Character == null) continue;
var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
if (dummyComponent != null && dummyComponent.enabled)
using System.Collections.Generic;
using System.Linq;
using Hazel;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))]
class IntroCutsceneOnDestroyPatch
{
+ public static PoolablePlayer playerPrefab;
public static void Prefix(IntroCutscene __instance) {
// Generate and initialize player icons
int playerCounter = 0;
- if (PlayerControl.LocalPlayer != null && HudManager.Instance != null) {
- Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z);
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ if (PlayerControl.LocalPlayer != null && FastDestroyableSingleton<HudManager>.Instance != null) {
+ Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.z);
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
GameData.PlayerInfo data = p.Data;
- PoolablePlayer player = UnityEngine.Object.Instantiate<PoolablePlayer>(__instance.PlayerPrefab, HudManager.Instance.transform);
+ PoolablePlayer player = UnityEngine.Object.Instantiate<PoolablePlayer>(__instance.PlayerPrefab, FastDestroyableSingleton<HudManager>.Instance.transform);
+ playerPrefab = __instance.PlayerPrefab;
PlayerControl.SetPlayerMaterialColors(data.DefaultOutfit.ColorId, player.CurrentBodySprite.BodySprite);
player.SetSkin(data.DefaultOutfit.SkinId, data.DefaultOutfit.ColorId);
player.HatSlot.SetHat(data.DefaultOutfit.HatId, data.DefaultOutfit.ColorId);
// Force Bounty Hunter to load a new Bounty when the Intro is over
if (BountyHunter.bounty != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) {
BountyHunter.bountyUpdateTimer = 0f;
- if (HudManager.Instance != null) {
- Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z) + new Vector3(-0.25f, 1f, 0);
- BountyHunter.cooldownText = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(HudManager.Instance.KillButton.cooldownTimerText, HudManager.Instance.transform);
+ if (FastDestroyableSingleton<HudManager>.Instance != null) {
+ Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton<HudManager>.Instance.UseButton.transform.localPosition.z) + new Vector3(-0.25f, 1f, 0);
+ BountyHunter.cooldownText = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(FastDestroyableSingleton<HudManager>.Instance.KillButton.cooldownTimerText, FastDestroyableSingleton<HudManager>.Instance.transform);
BountyHunter.cooldownText.alignment = TMPro.TextAlignmentOptions.Center;
BountyHunter.cooldownText.transform.localPosition = bottomLeft + new Vector3(0f, -1f, -1f);
BountyHunter.cooldownText.gameObject.SetActive(true);
}
public static bool Prefix(IntroCutscene __instance) {
if (!CustomOptionHolder.activateRoles.getBool()) return true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(1f, new Action<float>((p) => {
SetRoleTexts(__instance);
})));
return true;
using HarmonyLib;
-using System;
-using Hazel;
using UnityEngine;
-using UnityEngine.Rendering;
namespace TheOtherRoles.Patches {
- [HarmonyPatch(typeof(LightSource), nameof(LightSource.DrawOcclusion))]
+ [HarmonyPatch(typeof(LightSource), nameof(LightSource.Start))]
- class LightSourceUpdatePatch {
- static bool Prefix(LightSource __instance, float effectiveRadius) {
- if (__instance.cb == null) {
- __instance.cb = new CommandBuffer();
- __instance.cb.name = "Draw occlusion";
- }
- if (__instance.shadowTexture && __instance.shadowCasterMaterial) {
- float num = (float)__instance.shadowTexture.width;
- __instance.shadowCasterMaterial.SetFloat("_DepthCompressionValue", effectiveRadius);
- __instance.cb.Clear();
- __instance.cb.SetRenderTarget(__instance.shadowTexture);
- __instance.cb.ClearRenderTarget(true, true, new Color(1f, 1f, 1f, 1f));
- __instance.cb.SetGlobalTexture("_ShmapTexture", __instance.shadowTexture);
- __instance.cb.SetGlobalFloat("_Radius", __instance.LightRadius);
- __instance.cb.SetGlobalFloat("_Column", 0f);
- __instance.cb.SetGlobalVector("_lightPosition", __instance.transform.position + Vector3.down * 0.095f); ;
- __instance.cb.SetGlobalVector("_TexelSize", new Vector4(1f / num, 1f / num, num, num));
- __instance.cb.SetGlobalFloat("_DepthCompressionValue", effectiveRadius);
- __instance.cb.DrawMesh(__instance.occluderMesh, Matrix4x4.identity, __instance.shadowCasterMaterial);
- Graphics.ExecuteCommandBuffer(__instance.cb);
- }
- return false;
+ class LightSourceStartPatch {
+ static void Postfix(LightSource __instance) {
+ __instance.transform.position += Vector3.down * 0.095f; // Fixes Polus Rock / Garbage / Boxes reducing vision to 0
+ return;
}
}
}
\ No newline at end of file
using HarmonyLib;
+using TheOtherRoles.Utilities;
using UnityEngine;
[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.FixedUpdate))]
static bool Prefix(MapBehaviour __instance) {
if (!MeetingHud.Instance) return true; // Only run in meetings, and then set the Position of the HerePoint to the Position before the Meeting!
- if (!ShipStatus.Instance) {
+ if (!MapUtilities.CachedShipStatus) {
return false;
}
Vector3 vector = AntiTeleport.position != null? AntiTeleport.position : PlayerControl.LocalPlayer.transform.position;
- vector /= ShipStatus.Instance.MapScale;
- vector.x *= Mathf.Sign(ShipStatus.Instance.transform.localScale.x);
+ vector /= MapUtilities.CachedShipStatus.MapScale;
+ vector.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x);
vector.z = -1f;
__instance.HerePoint.transform.localPosition = vector;
PlayerControl.LocalPlayer.SetPlayerMaterialColors(__instance.HerePoint);
__instance.GenericShow();
__instance.taskOverlay.Show();
__instance.ColorControl.SetColor(new Color(0.05f, 0.2f, 1f, 1f));
- DestroyableSingleton<HudManager>.Instance.SetHudActive(false);
+ FastDestroyableSingleton<HudManager>.Instance.SetHudActive(false);
return false;
}
}
using Hazel;
using System.Collections.Generic;
using System.Linq;
-using UnhollowerBaseLib;
using static TheOtherRoles.TheOtherRoles;
using static TheOtherRoles.MapOptions;
using TheOtherRoles.Objects;
-using System.Collections;
using System;
-using System.Text;
+using TheOtherRoles.Utilities;
using UnityEngine;
-using System.Reflection;
namespace TheOtherRoles.Patches {
[HarmonyPatch]
}
- __instance.TitleText.text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray<Il2CppSystem.Object>(0));
+ __instance.TitleText.text = FastDestroyableSingleton<TranslationController>.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray<Il2CppSystem.Object>(0));
int num = 0;
for (int i = 0; i < __instance.playerStates.Length; i++) {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
Transform button = UnityEngine.Object.Instantiate(buttonTemplate, buttonParent);
Transform buttonMask = UnityEngine.Object.Instantiate(maskTemplate, buttonParent);
TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button);
- button.GetComponent<SpriteRenderer>().sprite = DestroyableSingleton<HatManager>.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image;
+ button.GetComponent<SpriteRenderer>().sprite = FastDestroyableSingleton<HatManager>.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image;
buttons.Add(button);
int row = i/5, col = i%5;
buttonParent.localPosition = new Vector3(-3.47f + 1.75f * col, 1.5f - 0.45f * row, -5);
Transform confirmSwapButtonMask = UnityEngine.Object.Instantiate(maskTemplate, confirmSwapButtonParent);
swapperConfirmButtonLabel = UnityEngine.Object.Instantiate(textTemplate, confirmSwapButton);
- confirmSwapButton.GetComponent<SpriteRenderer>().sprite = DestroyableSingleton<HatManager>.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image;
+ confirmSwapButton.GetComponent<SpriteRenderer>().sprite = FastDestroyableSingleton<HatManager>.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image;
confirmSwapButtonParent.localPosition = new Vector3(0, -2.225f, -5);
confirmSwapButtonParent.localScale = new Vector3(0.55f, 0.55f, 1f);
swapperConfirmButtonLabel.text = Helpers.cs(Color.red, "Confirm Swap");
PassiveButton passiveButton = confirmSwapButton.GetComponent<PassiveButton>();
passiveButton.OnClick.RemoveAllListeners();
- if (!PlayerControl.LocalPlayer.Data.IsDead) passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => swapperConfirm(__instance)));
+ if (!PlayerControl.LocalPlayer.Data.IsDead) passiveButton.OnClick.AddListener((Action)(() => swapperConfirm(__instance)));
confirmSwapButton.parent.gameObject.SetActive(false);
__instance.StartCoroutine(Effects.Lerp(7.27f, new Action<float>((p) => { // Button appears delayed, so that its visible in the voting screen only!
if (p == 1f) {
float timeBeforeMeeting = ((float)(DateTime.UtcNow - entry.time).TotalMilliseconds) / 1000;
string msg = Portalmaker.logShowsTime ? $"{(int)timeBeforeMeeting}s ago: " : "";
msg = msg + $"{entry.name} used the teleporter";
- DestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}");
+ FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}");
}
}
// Remove first kill shield
MapOptions.firstKillPlayer = null;
+
+
+ // Reset zoomed out ghosts
+ Helpers.toggleZoom(reset: true);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using static TheOtherRoles.TheOtherRoles;
using static TheOtherRoles.GameHistory;
using TheOtherRoles.Objects;
+using TheOtherRoles.Utilities;
using UnityEngine;
namespace TheOtherRoles.Patches {
static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List<PlayerControl> untargetablePlayers = null, PlayerControl targetingPlayer = null) {
PlayerControl result = null;
float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)];
- if (!ShipStatus.Instance) return result;
+ if (!MapUtilities.CachedShipStatus) return result;
if (targetingPlayer == null) targetingPlayer = PlayerControl.LocalPlayer;
if (targetingPlayer.Data.IsDead) return result;
Vector2 truePosition = targetingPlayer.GetTruePosition();
- foreach (var playerInfo in GameData.Instance.AllPlayers)
+ foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator())
{
if (!playerInfo.Disconnected && playerInfo.PlayerId != targetingPlayer.PlayerId && !playerInfo.IsDead && (!onlyCrewmates || !playerInfo.Role.IsImpostor)) {
PlayerControl @object = playerInfo.Object;
// Update functions
static void setBasePlayerOutlines() {
- foreach (PlayerControl target in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl target in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (target == null || target.MyRend == null) continue;
bool isMorphedMorphling = target == Morphling.morphling && Morphling.morphTarget != null && Morphling.morphTimer > 0f;
if (next.Item2 == true) {
// Exit current vent if necessary
if (PlayerControl.LocalPlayer.inVent) {
- foreach (Vent vent in ShipStatus.Instance.AllVents) {
+ foreach (Vent vent in MapUtilities.CachedShipStatus.AllVents) {
bool canUse;
bool couldUse;
vent.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
else if (localPlayerPositions.Any(x => x.Item2 == true)) {
PlayerControl.LocalPlayer.transform.position = next.Item1;
}
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
SubmergedCompatibility.ChangeFloor(next.Item1.y > -7);
}
Detective.timer -= Time.fixedDeltaTime;
if (Detective.timer <= 0f) {
Detective.timer = Detective.footprintIntervall;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (player != null && player != PlayerControl.LocalPlayer && !player.Data.IsDead && !player.inVent) {
new Footprint(Detective.footprintDuration, Detective.anonymousFootprints, player);
}
static void engineerUpdate() {
bool jackalHighlight = Engineer.highlightForTeamJackal && (PlayerControl.LocalPlayer == Jackal.jackal || PlayerControl.LocalPlayer == Sidekick.sidekick);
bool impostorHighlight = Engineer.highlightForImpostors && PlayerControl.LocalPlayer.Data.Role.IsImpostor;
- if ((jackalHighlight || impostorHighlight) && ShipStatus.Instance?.AllVents != null) {
- foreach (Vent vent in ShipStatus.Instance.AllVents) {
+ if ((jackalHighlight || impostorHighlight) && MapUtilities.CachedShipStatus?.AllVents != null) {
+ foreach (Vent vent in MapUtilities.CachedShipStatus.AllVents) {
try {
if (vent?.myRend?.material != null) {
if (Engineer.engineer != null && Engineer.engineer.inVent) {
static void impostorSetTarget() {
if (!PlayerControl.LocalPlayer.Data.Role.IsImpostor ||!PlayerControl.LocalPlayer.CanMove || PlayerControl.LocalPlayer.Data.IsDead) { // !isImpostor || !canMove || isDead
- HudManager.Instance.KillButton.SetTarget(null);
+ FastDestroyableSingleton<HudManager>.Instance.KillButton.SetTarget(null);
return;
}
target = setTarget(true, true, new List<PlayerControl>() { Sidekick.wasImpostor ? Sidekick.sidekick : null, Jackal.wasImpostor ? Jackal.jackal : null});
}
- HudManager.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed);
+ FastDestroyableSingleton<HudManager>.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed);
}
static void warlockSetTarget() {
static void ninjaUpdate()
{
+ if (Ninja.isInvisble && Ninja.invisibleTimer <= 0 && Ninja.ninja == PlayerControl.LocalPlayer)
+ {
+ MessageWriter invisibleWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetInvisible, Hazel.SendOption.Reliable, -1);
+ invisibleWriter.Write(Ninja.ninja.PlayerId);
+ invisibleWriter.Write(byte.MaxValue);
+ AmongUsClient.Instance.FinishRpcImmediately(invisibleWriter);
+ RPCProcedure.setInvisible(Ninja.ninja.PlayerId, byte.MaxValue);
+ }
if (Ninja.arrow?.arrow != null)
{
if (Ninja.ninja == null || Ninja.ninja != PlayerControl.LocalPlayer || !Ninja.knowsTargetLocation) {
public static void playerSizeUpdate(PlayerControl p) {
// Set default player size
- CircleCollider2D collider = p.GetComponent<CircleCollider2D>();
+ CircleCollider2D collider = p.Collider.CastFast<CircleCollider2D>();
p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
collider.radius = Mini.defaultColliderRadius;
}
public static void updatePlayerInfo() {
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if ((Lawyer.lawyerKnowsRole && PlayerControl.LocalPlayer == Lawyer.lawyer && p == Lawyer.target) || p == PlayerControl.LocalPlayer || PlayerControl.LocalPlayer.Data.IsDead) {
Transform playerInfoTransform = p.nameText.transform.parent.FindChild("Info");
TMPro.TextMeshPro playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent<TMPro.TextMeshPro>() : null;
if (p.Data.IsDead) roleNames = roleText;
playerInfoText = $"{roleNames}";
if (p == Swapper.swapper) playerInfoText = $"{roleNames}" + Helpers.cs(Swapper.color, $" ({Swapper.charges})");
- if (DestroyableSingleton<TaskPanelBehaviour>.InstanceExists) {
- TMPro.TextMeshPro tabText = DestroyableSingleton<TaskPanelBehaviour>.Instance.tab.transform.FindChild("TabText_TMP").GetComponent<TMPro.TextMeshPro>();
+ if (TaskPanelBehaviour.InstanceExists) {
+ TMPro.TextMeshPro tabText = TaskPanelBehaviour.Instance.tab.transform.FindChild("TabText_TMP").GetComponent<TMPro.TextMeshPro>();
tabText.SetText($"Tasks {taskInfo}");
}
meetingInfoText = $"{roleNames} {taskInfo}".Trim();
}
public static void securityGuardSetTarget() {
- if (SecurityGuard.securityGuard == null || SecurityGuard.securityGuard != PlayerControl.LocalPlayer || ShipStatus.Instance == null || ShipStatus.Instance.AllVents == null) return;
+ if (SecurityGuard.securityGuard == null || SecurityGuard.securityGuard != PlayerControl.LocalPlayer || MapUtilities.CachedShipStatus == null || MapUtilities.CachedShipStatus.AllVents == null) return;
Vent target = null;
Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
float closestDistance = float.MaxValue;
- for (int i = 0; i < ShipStatus.Instance.AllVents.Length; i++) {
- Vent vent = ShipStatus.Instance.AllVents[i];
+ for (int i = 0; i < MapUtilities.CachedShipStatus.AllVents.Length; i++) {
+ Vent vent = MapUtilities.CachedShipStatus.AllVents[i];
if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || vent.gameObject.name.StartsWith("SealedVent_") || vent.gameObject.name.StartsWith("FutureSealedVent_")) continue;
- if (SubmergedCompatibility.isSubmerged() && vent.Id == 9) continue; // cannot seal submergeds exit only vent!
+ if (SubmergedCompatibility.IsSubmerged && vent.Id == 9) continue; // cannot seal submergeds exit only vent!
float distance = Vector2.Distance(vent.transform.position, truePosition);
if (distance <= vent.UsableDistance && distance < closestDistance) {
closestDistance = distance;
}
else if (PlayerControl.LocalPlayer == Snitch.snitch && numberOfTasks == 0) {
int arrowIndex = 0;
- foreach (PlayerControl p in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator())
{
bool arrowForImp = p.Data.Role.IsImpostor;
bool arrowForTeamJackal = Snitch.includeTeamJackal && (p == Jackal.jackal || p == Sidekick.sidekick);
BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update
BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration;
var possibleTargets = new List<PlayerControl>();
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.Role.IsImpostor && p != Spy.spy && (p != Sidekick.sidekick || !Sidekick.wasTeamRed) && (p != Jackal.jackal || !Jackal.wasTeamRed) && (p != Mini.mini || Mini.isGrownUp()) && (Lovers.getPartner(BountyHunter.bountyHunter) == null || p != Lovers.getPartner(BountyHunter.bountyHunter))) possibleTargets.Add(p);
}
BountyHunter.bounty = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)];
if (BountyHunter.bounty == null) return;
// Show poolable player
- if (HudManager.Instance != null && HudManager.Instance.UseButton != null) {
+ if (FastDestroyableSingleton<HudManager>.Instance != null && FastDestroyableSingleton<HudManager>.Instance.UseButton != null) {
foreach (PoolablePlayer pp in MapOptions.playerIcons.Values) pp.gameObject.SetActive(false);
if (MapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null)
MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(true);
}
public static void mediumSetTarget() {
- if (Medium.medium == null || Medium.medium != PlayerControl.LocalPlayer || Medium.medium.Data.IsDead || Medium.deadBodies == null || ShipStatus.Instance?.AllVents == null) return;
+ if (Medium.medium == null || Medium.medium != PlayerControl.LocalPlayer || Medium.medium.Data.IsDead || Medium.deadBodies == null || MapUtilities.CachedShipStatus?.AllVents == null) return;
DeadPlayer target = null;
Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
float closestDistance = float.MaxValue;
- float usableDistance = ShipStatus.Instance.AllVents.FirstOrDefault().UsableDistance;
+ float usableDistance = MapUtilities.CachedShipStatus.AllVents.FirstOrDefault().UsableDistance;
foreach ((DeadPlayer dp, Vector3 ps) in Medium.deadBodies) {
float distance = Vector2.Distance(ps, truePosition);
if (distance <= usableDistance && distance < closestDistance) {
HudManagerStartPatch.witchSpellButton.MaxTimer = (Witch.cooldown + Witch.currentCooldownAddition) * multiplier;
}
}
-
public static void Postfix(PlayerControl __instance) {
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
ninjaSetTarget();
NinjaTrace.UpdateAll();
ninjaUpdate();
-
hackerUpdate();
swapperUpdate();
if (!string.IsNullOrWhiteSpace(msg))
{
- if (AmongUsClient.Instance.AmClient && DestroyableSingleton<HudManager>.Instance)
+ if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton<HudManager>.Instance)
{
- DestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, msg);
+ FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, msg);
}
if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
{
- DestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
+ FastDestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
}
}
}
if (BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) addition = BountyHunter.punishmentTime;
__instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
- DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
+ FastDestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
return false;
}
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.lawyerPromotesToPursuer();
}
- if (__instance == Lawyer.target)
+ if (__instance == Lawyer.target && !Lawyer.targetWasGuessed)
{
if (Lawyer.lawyer != null) Lawyer.lawyer.Exiled();
if (Pursuer.pursuer != null) Pursuer.pursuer.Exiled();
--- /dev/null
+using HarmonyLib;
+using UnityEngine;
+
+namespace TheOtherRoles.Patches;
+
+[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.Awake))]
+public static class PlayerPhysiscs_Awake_Patch
+{
+ [HarmonyPostfix]
+ public static void Postfix(PlayerPhysics __instance)
+ {
+ if (!__instance.body) return;
+ __instance.body.interpolation = RigidbodyInterpolation2D.Interpolate;
+ }
+}
\ No newline at end of file
using UnityEngine;
using UnityEngine.UI;
using System;
+using TheOtherRoles.Utilities;
using UnityEngine.Events;
namespace TheOtherRoles.Patches {
private static TextBoxTMP portField;
public static void Postfix(RegionMenu __instance) {
- var template = DestroyableSingleton<JoinGameButton>.Instance;
+ if (!__instance.TryCast<RegionMenu>()) return;
+ var template = FastDestroyableSingleton<JoinGameButton>.Instance;
var joinGameButtons = GameObject.FindObjectsOfType<JoinGameButton>();
foreach (var t in joinGameButtons) { // The correct button has a background, the other 2 dont
if (t.GameIdText != null && t.GameIdText.Background != null) {
using Hazel;
using System.Collections.Generic;
using System.Linq;
-using UnhollowerBaseLib;
using UnityEngine;
using System;
+using TheOtherRoles.Utilities;
using static TheOtherRoles.TheOtherRoles;
namespace TheOtherRoles.Patches {
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.resetVariables();
- if (!DestroyableSingleton<TutorialManager>.InstanceExists && CustomOptionHolder.activateRoles.getBool()) // Don't assign Roles in Tutorial or if deactivated
+ if (CustomOptionHolder.activateRoles.getBool()) // Don't assign Roles in Tutorial or if deactivated
assignRoles();
}
// Set Lawyer Target
if (Lawyer.lawyer != null) {
var possibleTargets = new List<PlayerControl>();
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (!p.Data.IsDead && !p.Data.Disconnected && p != Lovers.lover1 && p != Lovers.lover2 && (p.Data.Role.IsImpostor || p == Jackal.jackal || (Lawyer.targetCanBeJester && p == Jester.jester)))
possibleTargets.Add(p);
}
using System;
using System.Linq;
using HarmonyLib;
-using UnhollowerRuntimeLib;
+using TheOtherRoles.Utilities;
using static TheOtherRoles.TheOtherRoles;
using UnityEngine;
}
public static float GetNeutralLightRadius(ShipStatus shipStatus, bool isImpostor) {
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
return SubmergedCompatibility.GetSubmergedNeutralLightRadius(isImpostor);
}
if (isImpostor) return shipStatus.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod;
- SwitchSystem switchSystem = shipStatus.Systems[SystemTypes.Electrical].TryCast<SwitchSystem>();
+ SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast<SwitchSystem>();
float lerpValue = switchSystem.Value / 255f;
return Mathf.Lerp(shipStatus.MinLightRadius, shipStatus.MaxLightRadius, lerpValue) * PlayerControl.GameOptions.CrewLightMod;
using HarmonyLib;
using System;
-using System.IO;
-using System.Net.Http;
using UnityEngine;
using static TheOtherRoles.TheOtherRoles;
using TheOtherRoles.Objects;
using System.Collections.Generic;
using System.Linq;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
class HudManagerUpdatePatch
{
static void resetNameTagsAndColors() {
- Dictionary<byte, PlayerControl> playersById = Helpers.allPlayersById();
+ var localPlayer = PlayerControl.LocalPlayer;
+ var myData = localPlayer.Data;
+ var amImpostor = myData.Role.IsImpostor;
+ var morphTimerNotUp = Morphling.morphTimer > 0f;
+ var morphTargetNotNull = Morphling.morphTarget != null;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- String playerName = player.Data.PlayerName;
- if (Morphling.morphTimer > 0f && Morphling.morphling == player && Morphling.morphTarget != null) playerName = Morphling.morphTarget.Data.PlayerName; // Temporary hotfix for the Morphling's name
-
- player.nameText.text = Helpers.hidePlayerName(PlayerControl.LocalPlayer, player) ? "" : playerName;
- if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && player.Data.Role.IsImpostor) {
- player.nameText.color = Palette.ImpostorRed;
- } else {
- player.nameText.color = Color.white;
+ var dict = new Dictionary<byte, (string name, Color color)>();
+
+ foreach (var data in GameData.Instance.AllPlayers.GetFastEnumerator())
+ {
+ var player = data.Object;
+ string text;
+ Color color;
+ if (player)
+ {
+ String playerName = data.PlayerName;
+ if (morphTimerNotUp && morphTargetNotNull && Morphling.morphling == player) playerName = Morphling.morphTarget.Data.PlayerName;
+ var nameText = player.nameText;
+
+ nameText.text = text = Helpers.hidePlayerName(localPlayer, player) ? "" : playerName;
+ nameText.color = color = amImpostor && data.Role.IsImpostor ? Palette.ImpostorRed : Color.white;
}
- }
- if (MeetingHud.Instance != null) {
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
- PlayerControl playerControl = playersById.ContainsKey((byte)player.TargetPlayerId) ? playersById[(byte)player.TargetPlayerId] : null;
- if (playerControl != null) {
- player.NameText.text = playerControl.Data.PlayerName;
- if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && playerControl.Data.Role.IsImpostor) {
- player.NameText.color = Palette.ImpostorRed;
- } else {
- player.NameText.color = Color.white;
- }
- }
+ else
+ {
+ text = data.PlayerName;
+ color = Color.white;
}
+
+
+ dict.Add(data.PlayerId, (text, color));
}
- if (PlayerControl.LocalPlayer.Data.Role.IsImpostor) {
- List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList();
- impostors.RemoveAll(x => !x.Data.Role.IsImpostor);
- foreach (PlayerControl player in impostors)
- player.nameText.color = Palette.ImpostorRed;
- if (MeetingHud.Instance != null)
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
- PlayerControl playerControl = Helpers.playerById((byte)player.TargetPlayerId);
- if (playerControl != null && playerControl.Data.Role.IsImpostor)
- player.NameText.color = Palette.ImpostorRed;
- }
+
+ if (MeetingHud.Instance != null)
+ {
+ foreach (PlayerVoteArea playerVoteArea in MeetingHud.Instance.playerStates)
+ {
+ var data = dict[playerVoteArea.TargetPlayerId];
+ var text = playerVoteArea.NameText;
+ text.text = data.name;
+ text.color = data.color;
+ }
}
-
}
static void setPlayerNameColor(PlayerControl p, Color color) {
player.NameText.color = color;
}
- static void setNameColors() {
- if (Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer)
+ static void setNameColors()
+ {
+ var localPlayer = PlayerControl.LocalPlayer;
+ if (Jester.jester != null && Jester.jester == localPlayer)
setPlayerNameColor(Jester.jester, Jester.color);
- else if (Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer)
+ else if (Mayor.mayor != null && Mayor.mayor == localPlayer)
setPlayerNameColor(Mayor.mayor, Mayor.color);
- else if (Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer)
+ else if (Engineer.engineer != null && Engineer.engineer == localPlayer)
setPlayerNameColor(Engineer.engineer, Engineer.color);
- else if (Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer) {
+ else if (Sheriff.sheriff != null && Sheriff.sheriff == localPlayer) {
setPlayerNameColor(Sheriff.sheriff, Sheriff.color);
if (Deputy.deputy != null && Deputy.knowsSheriff) {
setPlayerNameColor(Deputy.deputy, Deputy.color);
}
- } else if (Deputy.deputy != null && Deputy.deputy == PlayerControl.LocalPlayer) {
+ } else if (Deputy.deputy != null && Deputy.deputy == localPlayer) {
setPlayerNameColor(Deputy.deputy, Deputy.color);
if (Sheriff.sheriff != null && Deputy.knowsSheriff) {
setPlayerNameColor(Sheriff.sheriff, Sheriff.color);
}
- } else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == PlayerControl.LocalPlayer)
+ } else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == localPlayer)
setPlayerNameColor(Portalmaker.portalmaker, Portalmaker.color);
- else if (Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer)
+ else if (Lighter.lighter != null && Lighter.lighter == localPlayer)
setPlayerNameColor(Lighter.lighter, Lighter.color);
- else if (Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer)
+ else if (Detective.detective != null && Detective.detective == localPlayer)
setPlayerNameColor(Detective.detective, Detective.color);
- else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer)
+ else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == localPlayer)
setPlayerNameColor(TimeMaster.timeMaster, TimeMaster.color);
- else if (Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer)
+ else if (Medic.medic != null && Medic.medic == localPlayer)
setPlayerNameColor(Medic.medic, Medic.color);
- else if (Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer)
+ else if (Shifter.shifter != null && Shifter.shifter == localPlayer)
setPlayerNameColor(Shifter.shifter, Shifter.color);
- else if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer)
+ else if (Swapper.swapper != null && Swapper.swapper == localPlayer)
setPlayerNameColor(Swapper.swapper, Swapper.color);
- else if (Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer)
+ else if (Seer.seer != null && Seer.seer == localPlayer)
setPlayerNameColor(Seer.seer, Seer.color);
- else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer)
+ else if (Hacker.hacker != null && Hacker.hacker == localPlayer)
setPlayerNameColor(Hacker.hacker, Hacker.color);
- else if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer)
+ else if (Tracker.tracker != null && Tracker.tracker == localPlayer)
setPlayerNameColor(Tracker.tracker, Tracker.color);
- else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer)
+ else if (Snitch.snitch != null && Snitch.snitch == localPlayer)
setPlayerNameColor(Snitch.snitch, Snitch.color);
- else if (Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer) {
+ else if (Jackal.jackal != null && Jackal.jackal == localPlayer) {
// Jackal can see his sidekick
setPlayerNameColor(Jackal.jackal, Jackal.color);
if (Sidekick.sidekick != null) {
setPlayerNameColor(Jackal.fakeSidekick, Jackal.color);
}
}
- else if (Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer) {
+ else if (Spy.spy != null && Spy.spy == localPlayer) {
setPlayerNameColor(Spy.spy, Spy.color);
- } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer) {
+ } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == localPlayer) {
setPlayerNameColor(SecurityGuard.securityGuard, SecurityGuard.color);
- } else if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) {
+ } else if (Arsonist.arsonist != null && Arsonist.arsonist == localPlayer) {
setPlayerNameColor(Arsonist.arsonist, Arsonist.color);
- } else if (Guesser.niceGuesser != null && Guesser.niceGuesser == PlayerControl.LocalPlayer) {
+ } else if (Guesser.niceGuesser != null && Guesser.niceGuesser == localPlayer) {
setPlayerNameColor(Guesser.niceGuesser, Guesser.color);
- } else if (Guesser.evilGuesser != null && Guesser.evilGuesser == PlayerControl.LocalPlayer) {
+ } else if (Guesser.evilGuesser != null && Guesser.evilGuesser == localPlayer) {
setPlayerNameColor(Guesser.evilGuesser, Palette.ImpostorRed);
- } else if (Vulture.vulture != null && Vulture.vulture == PlayerControl.LocalPlayer) {
+ } else if (Vulture.vulture != null && Vulture.vulture == localPlayer) {
setPlayerNameColor(Vulture.vulture, Vulture.color);
- } else if (Medium.medium != null && Medium.medium == PlayerControl.LocalPlayer) {
+ } else if (Medium.medium != null && Medium.medium == localPlayer) {
setPlayerNameColor(Medium.medium, Medium.color);
- } else if (Lawyer.lawyer != null && Lawyer.lawyer == PlayerControl.LocalPlayer) {
+ } else if (Lawyer.lawyer != null && Lawyer.lawyer == localPlayer) {
setPlayerNameColor(Lawyer.lawyer, Lawyer.color);
- } else if (Pursuer.pursuer != null && Pursuer.pursuer == PlayerControl.LocalPlayer) {
+ } else if (Pursuer.pursuer != null && Pursuer.pursuer == localPlayer) {
setPlayerNameColor(Pursuer.pursuer, Pursuer.color);
}
// No else if here, as a Lover of team Jackal needs the colors
- if (Sidekick.sidekick != null && Sidekick.sidekick == PlayerControl.LocalPlayer) {
+ if (Sidekick.sidekick != null && Sidekick.sidekick == localPlayer) {
// Sidekick can see the jackal
setPlayerNameColor(Sidekick.sidekick, Sidekick.color);
if (Jackal.jackal != null) {
}
// No else if here, as the Impostors need the Spy name to be colored
- if (Spy.spy != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor) {
+ if (Spy.spy != null && localPlayer.Data.Role.IsImpostor) {
setPlayerNameColor(Spy.spy, Spy.color);
}
- if (Sidekick.sidekick != null && Sidekick.wasTeamRed && PlayerControl.LocalPlayer.Data.Role.IsImpostor) {
+ if (Sidekick.sidekick != null && Sidekick.wasTeamRed && localPlayer.Data.Role.IsImpostor) {
setPlayerNameColor(Sidekick.sidekick, Spy.color);
}
- if (Jackal.jackal != null && Jackal.wasTeamRed && PlayerControl.LocalPlayer.Data.Role.IsImpostor) {
+ if (Jackal.jackal != null && Jackal.wasTeamRed && localPlayer.Data.Role.IsImpostor) {
setPlayerNameColor(Jackal.jackal, Spy.color);
}
static void setNameTags() {
// Mafia
if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
if (Godfather.godfather != null && Godfather.godfather == player)
player.nameText.text = player.Data.PlayerName + " (G)";
else if (Mafioso.mafioso != null && Mafioso.mafioso == player)
}
static void timerUpdate() {
- Hacker.hackerTimer -= Time.deltaTime;
- Lighter.lighterTimer -= Time.deltaTime;
- Trickster.lightsOutTimer -= Time.deltaTime;
- Tracker.corpsesTrackingTimer -= Time.deltaTime;
- foreach (byte key in Deputy.handcuffedKnows.Keys.ToList())
- Deputy.handcuffedKnows[key] -= Time.deltaTime;
+ var dt = Time.deltaTime;
+ Hacker.hackerTimer -= dt;
+ Lighter.lighterTimer -= dt;
+ Trickster.lightsOutTimer -= dt;
+ Tracker.corpsesTrackingTimer -= dt;
+ Ninja.invisibleTimer -= dt;
+ foreach (byte key in Deputy.handcuffedKnows.Keys)
+ Deputy.handcuffedKnows[key] -= dt;
}
public static void miniUpdate() {
using static TheOtherRoles.GameHistory;
using static TheOtherRoles.MapOptions;
using System.Collections.Generic;
-using TheOtherRoles.Objects;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles.Patches {
}
// Submerged Compatability if needed:
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
// as submerged does, only change stuff for vents 9 and 14 of submerged. Code partially provided by AlexejheroYTB
if (SubmergedCompatibility.getInTransition()) {
__result = float.MaxValue;
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
class VentButtonVisibilityPatch {
static void Postfix(PlayerControl __instance) {
- if (__instance.AmOwner && __instance.roleCanUseVents() && HudManager.Instance.ReportButton.isActiveAndEnabled) {
- HudManager.Instance.ImpostorVentButton.Show();
+ if (__instance.AmOwner && __instance.roleCanUseVents() && FastDestroyableSingleton<HudManager>.Instance.ReportButton.isActiveAndEnabled) {
+ FastDestroyableSingleton<HudManager>.Instance.ImpostorVentButton.Show();
}
}
}
bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer);
bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead);
if (blockSabotageJanitor || blockSabotageMafioso) {
- HudManager.Instance.SabotageButton.SetDisabled();
+ FastDestroyableSingleton<HudManager>.Instance.SabotageButton.SetDisabled();
}
}
}
__instance.timer = 0f;
players = new Dictionary<SystemTypes, List<Color>>();
bool commsActive = false;
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks.GetFastEnumerator())
if (task.TaskType == TaskTypes.FixComms) commsActive = true;
if (!commsActive)
{
- PlainShipRoom plainShipRoom = ShipStatus.Instance.FastRooms[counterArea.RoomType];
+ PlainShipRoom plainShipRoom = MapUtilities.CachedShipStatus.FastRooms[counterArea.RoomType];
if (plainShipRoom != null && plainShipRoom.roomArea)
{
if (players.ContainsKey(__instance.RoomType)) {
List<Color> colors = players[__instance.RoomType];
int i = -1;
- foreach (var icon in __instance.myIcons)
+ foreach (var icon in __instance.myIcons.GetFastEnumerator())
{
i += 1;
SpriteRenderer renderer = icon.GetComponent<SpriteRenderer>();
// Add securityGuard cameras
page = 0;
timer = 0;
- if (ShipStatus.Instance.AllCameras.Length > 4 && __instance.FilteredRooms.Length > 0) {
- __instance.textures = __instance.textures.ToList().Concat(new RenderTexture[ShipStatus.Instance.AllCameras.Length - 4]).ToArray();
- for (int i = 4; i < ShipStatus.Instance.AllCameras.Length; i++) {
- SurvCamera surv = ShipStatus.Instance.AllCameras[i];
+ if (MapUtilities.CachedShipStatus.AllCameras.Length > 4 && __instance.FilteredRooms.Length > 0) {
+ __instance.textures = __instance.textures.ToList().Concat(new RenderTexture[MapUtilities.CachedShipStatus.AllCameras.Length - 4]).ToArray();
+ for (int i = 4; i < MapUtilities.CachedShipStatus.AllCameras.Length; i++) {
+ SurvCamera surv = MapUtilities.CachedShipStatus.AllCameras[i];
Camera camera = UnityEngine.Object.Instantiate<Camera>(__instance.CameraPrefab);
camera.transform.SetParent(__instance.transform);
camera.transform.position = new Vector3(surv.transform.position.x, surv.transform.position.y, 8f);
public static bool Prefix(SurveillanceMinigame __instance) {
// Update normal and securityGuard cameras
timer += Time.deltaTime;
- int numberOfPages = Mathf.CeilToInt(ShipStatus.Instance.AllCameras.Length / 4f);
+ int numberOfPages = Mathf.CeilToInt(MapUtilities.CachedShipStatus.AllCameras.Length / 4f);
bool update = false;
}
}
-}
\ No newline at end of file
+}
using System.Linq;
using UnityEngine;
using System;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles
{
Bloody,
SetFirstKill,
Invert,
- SetTiebreak
+ SetTiebreak,
+ SetInvisible
}
public static class RPCProcedure {
}
public static void forceEnd() {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
{
if (!player.Data.Role.IsImpostor)
{
}
public static void setRole(byte roleId, byte playerId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
if (player.PlayerId == playerId) {
switch((RoleId)roleId) {
case RoleId.Jester:
// Role functionality
public static void engineerFixLights() {
- SwitchSystem switchSystem = ShipStatus.Instance.Systems[SystemTypes.Electrical].Cast<SwitchSystem>();
+ SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast<SwitchSystem>();
switchSystem.ActualSwitches = switchSystem.ExpectedSwitches;
}
if(TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer) {
resetTimeMasterButton();
}
- HudManager.Instance.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f);
- HudManager.Instance.FullScreen.enabled = true;
- HudManager.Instance.FullScreen.gameObject.SetActive(true);
- HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.rewindTime / 2, new Action<float>((p) => {
- if (p == 1f) HudManager.Instance.FullScreen.enabled = false;
+ FastDestroyableSingleton<HudManager>.Instance.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f);
+ FastDestroyableSingleton<HudManager>.Instance.FullScreen.enabled = true;
+ FastDestroyableSingleton<HudManager>.Instance.FullScreen.gameObject.SetActive(true);
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(TimeMaster.rewindTime / 2, new Action<float>((p) => {
+ if (p == 1f) FastDestroyableSingleton<HudManager>.Instance.FullScreen.enabled = false;
})));
if (TimeMaster.timeMaster == null || PlayerControl.LocalPlayer == TimeMaster.timeMaster) return; // Time Master himself does not rewind
public static void timeMasterShield() {
TimeMaster.shieldActive = true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.shieldDuration, new Action<float>((p) => {
+ FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(TimeMaster.shieldDuration, new Action<float>((p) => {
if (p == 1f) TimeMaster.shieldActive = false;
})));
}
if (Camouflager.camouflager == null) return;
Camouflager.camouflageTimer = Camouflager.duration;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
player.setLook("", 6, "", "", "", "");
}
}
if (Vampire.vampire == null) return;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (player.PlayerId == targetId && !player.Data.IsDead) {
Vampire.bitten = player;
}
public static void trackerUsedTracker(byte targetId) {
Tracker.usedTracker = true;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator())
if (player.PlayerId == targetId)
Tracker.tracked = player;
}
} else {
bool wasSpy = Spy.spy != null && player == Spy.spy;
bool wasImpostor = player.Data.Role.IsImpostor; // This can only be reached if impostors can be sidekicked.
- DestroyableSingleton<RoleManager>.Instance.SetRole(player, RoleTypes.Crewmate);
+ FastDestroyableSingleton<RoleManager>.Instance.SetRole(player, RoleTypes.Crewmate);
+ if (player == Lawyer.lawyer && Lawyer.target != null)
+ {
+ Transform playerInfoTransform = Lawyer.target.nameText.transform.parent.FindChild("Info");
+ TMPro.TextMeshPro playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent<TMPro.TextMeshPro>() : null;
+ if (playerInfo != null) playerInfo.text = "";
+ }
erasePlayerRoles(player.PlayerId, true);
Sidekick.sidekick = player;
if (player.PlayerId == PlayerControl.LocalPlayer.PlayerId) PlayerControl.LocalPlayer.moveable = true;
new NinjaTrace(position, Ninja.traceTime);
}
+ public static void setInvisible(byte playerId, byte flag)
+ {
+ PlayerControl target = Helpers.playerById(playerId);
+ if (target == null) return;
+ if (flag == byte.MaxValue)
+ {
+ target.MyRend.color = Color.white;
+ target.setDefaultLook();
+ Ninja.isInvisble = false;
+ return;
+ }
+
+ target.setLook("", 6, "", "", "", "");
+ Color color = Color.clear;
+ if (PlayerControl.LocalPlayer.Data.Role.IsImpostor || PlayerControl.LocalPlayer.Data.IsDead) color.a = 0.1f;
+ target.MyRend.color = color;
+ Ninja.invisibleTimer = Ninja.invisibleDuration;
+ Ninja.isInvisble = true;
+ }
+
public static void placePortal(byte[] buff) {
Vector3 position = Vector2.zero;
position.x = BitConverter.ToSingle(buff, 0 * sizeof(float));
camera.Offset = new Vector3(0f, 0f, camera.Offset.z);
if (PlayerControl.GameOptions.MapId == 2 || PlayerControl.GameOptions.MapId == 4) camera.transform.localRotation = new Quaternion(0, 0, 1, 1); // Polus and Airship
- if (SubmergedCompatibility.isSubmerged()) {
+ if (SubmergedCompatibility.IsSubmerged) {
// remove 2d box collider of console, so that no barrier can be created. (irrelevant for now, but who knows... maybe we need it later)
var fixConsole = camera.transform.FindChild("FixConsole");
if (fixConsole != null) {
}
public static void sealVent(int ventId) {
- Vent vent = ShipStatus.Instance.AllVents.FirstOrDefault((x) => x != null && x.Id == ventId);
+ Vent vent = MapUtilities.CachedShipStatus.AllVents.FirstOrDefault((x) => x != null && x.Id == ventId);
if (vent == null) return;
SecurityGuard.remainingScrews -= SecurityGuard.ventPrice;
animator?.Stop();
vent.EnterVentAnim = vent.ExitVentAnim = null;
vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
- if (SubmergedCompatibility.isSubmerged() && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite();
- if (SubmergedCompatibility.isSubmerged() && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite();
+ if (SubmergedCompatibility.IsSubmerged && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite();
+ if (SubmergedCompatibility.IsSubmerged && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite();
vent.myRend.color = new Color(1f, 1f, 1f, 0.5f);
vent.name = "FutureSealedVent_" + vent.name;
}
public static void arsonistWin() {
Arsonist.triggerArsonistWin = true;
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) {
if (p != Arsonist.arsonist) p.Exiled();
}
}
public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedTargetId, byte guessedRoleId) {
PlayerControl dyingTarget = Helpers.playerById(dyingTargetId);
if (dyingTarget == null ) return;
- dyingTarget.Exiled();
+ if (Lawyer.target != null && dyingTarget == Lawyer.target) Lawyer.targetWasGuessed = true; // Lawyer shouldn't be exiled with the client for guesses
PlayerControl dyingLoverPartner = Lovers.bothDie ? dyingTarget.getPartner() : null; // Lover check
+ if (Lawyer.target != null && dyingLoverPartner == Lawyer.target) Lawyer.targetWasGuessed = true; // Lawyer shouldn't be exiled with the client for guesses
+ dyingTarget.Exiled();
byte partnerId = dyingLoverPartner != null ? dyingLoverPartner.PlayerId : dyingTargetId;
Guesser.remainingShots(killerId, true);
MeetingHud.Instance.CheckForEndVoting();
}
PlayerControl guesser = Helpers.playerById(killerId);
- if (HudManager.Instance != null && guesser != null)
+ if (FastDestroyableSingleton<HudManager>.Instance != null && guesser != null)
if (PlayerControl.LocalPlayer == dyingTarget)
- HudManager.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data);
+ FastDestroyableSingleton<HudManager>.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data);
else if (dyingLoverPartner != null && PlayerControl.LocalPlayer == dyingLoverPartner)
- HudManager.Instance.KillOverlay.ShowKillAnimation(dyingLoverPartner.Data, dyingLoverPartner.Data);
+ FastDestroyableSingleton<HudManager>.Instance.KillOverlay.ShowKillAnimation(dyingLoverPartner.Data, dyingLoverPartner.Data);
PlayerControl guessedTarget = Helpers.playerById(guessedTargetId);
if (Guesser.showInfoInGhostChat && PlayerControl.LocalPlayer.Data.IsDead && guessedTarget != null) {
RoleInfo roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId);
string msg = $"Guesser guessed the role {roleInfo?.name ?? ""} for {guessedTarget.Data.PlayerName}!";
- if (AmongUsClient.Instance.AmClient && DestroyableSingleton<HudManager>.Instance)
- DestroyableSingleton<HudManager>.Instance.Chat.AddChat(guesser, msg);
+ if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton<HudManager>.Instance)
+ FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(guesser, msg);
if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
- DestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
+ FastDestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
}
}
case (byte)CustomRPC.SetTiebreak:
RPCProcedure.setTiebreak();
break;
+ case (byte)CustomRPC.SetInvisible:
+ byte invisiblePlayer = reader.ReadByte();
+ byte invisibleFlag = reader.ReadByte();
+ RPCProcedure.setInvisible(invisiblePlayer, invisibleFlag);
+ break;
}
}
}
--- /dev/null
+This mod is not affiliated with Among Us or Innersloth LLC, and the content contained
+therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials
+contained herein are property of Innersloth LLC. © Innersloth LLC.
+
+---
+
+VIDEO POLICY
+
+We allow and encourage our users to create and publish Submerged content or videos to
+any websites, including YouTube, Twitch, and similar video sharing services, and also
+monetize them.
+
+---
+---
+---
+
+This is a human-readable summary of (and not a substitute for) the license.
+
+- You may share, distribute, or include Submerged inon-commerciallyi in your own modpack
+or mod downloads, under the condition that you do not modify Submerged in any capacity,
+(including physically modifying the assembly or patching the mod) with the exception of
+ensuring compatibility between Submerged and your mod.
+ - For clarification, you are still allowed to request payments, donations, etc. or have
+ premium-only features in your mod, but you may not sell users the access to play on
+ Submerged with your modmodpack.
+- If Submerged stops being distributed, you must remove it from your modmodpack as well.
+
+---
+
+
+ Copyright © 2020-2022 5UP AND ASSOCIATES
+
+By exercising the Licensed Rights (defined below), You accept and agree to be bound by the
+terms and conditions of this license (License). To the extent this License may be
+interpreted as a contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You such rights in
+consideration of benefits the Licensor receives from making the Licensed Material available
+under these terms and conditions.
+
+ SECTION 1 – Definitions.
+
+a. Licensor means the individual(s) or entity(ies) granting rights under this Public
+License.
+
+b. You means the individual or entity exercising the Licensed Rights under this Public
+License. Your has a corresponding meaning.
+
+c. Licensed Material means the technological or artistic work, or other material to which
+the Licensor applied this License.
+
+d. Licensed Rights means the rights granted to You subject to the terms and conditions of
+this License and that the Licensor has authority to license.
+
+e. Exceptions means any exemptions to the License negociated between You and the Licensor.
+
+f. Limitations means any limitations to the License imposed on You by the Licensor.
+
+g. Share means to provide material to the public by any means or process, such as
+redistribution, copying of digital files, uploading, and to make material available to the
+public including in ways that members of the public may access the material from a place
+and at a time individually chosen by them.
+
+
+ SECTION 2 – Scope.
+
+a. License grant
+
+ 1. Subject to the terms and conditions of this Public License, the Licensor hereby
+ grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, but
+ revocable license to exercise the Licensed Rights in the Licensed Material, and
+ to Share the Licensed Material, in whole, for non-commercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and
+ Limitations apply to Your use, this Public License does not apply, and You do not
+ need to comply with its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section 5(a).
+
+ 4. Downstream recipients.
+ A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed
+ Material automatically receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this License.
+ B. No downstream restrictions. You may not offer or impose any additional or
+ different terms or conditions on the Licensed Material if doing so restricts
+ exercise of the Licensed Rights by any recipient of the Licensed Material.
+
+ 5. No endorsement. Nothing in this Public License constitutes or may be construed as
+ permission to assert or imply that You are, or that Your use of the Licensed Material
+ is, connected with, or sponsored, endorsed, or granted official status by, the Licensor
+ or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
+
+
+ SECTION 3 – License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the following conditions.
+
+a. Attribution;
+
+ 1. If You Share the Licensed Material, You must
+ A. retain the following if it is supplied by the Licensor with the Licensed Material
+ i. identification of the creator(s) of the Licensed Material and any others
+ designated to receive attribution, in any reasonable manner requested by the
+ Licensor (including by pseudonym if designated);
+ B. a third party notice file;
+ C. a notice that refers to this License;
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on
+ the medium, means, and context in which You Share the Licensed Material. For example,
+ it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a
+ resource that includes the required information.
+
+ 3. If requested by the Licensor, You must remove any of the information required by
+ Section 3(a)(1)(A) to the extent reasonably practicable.
+
+b. No Modification.
+
+ 1. If You Share the Licensed Material, You must
+ A. not modify the Licensed Material in any capacity, including digital modifications
+ of the Licensed Material, and extensions or attachments to the Licensed Material
+ which modify how the Licensed Material operates.
+
+
+ SECTION 4 – Disclaimer of Warranties and Limitation of Liability.
+
+a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the
+Licensor offers the Licensed Material as-is and as-available, and makes no representations
+or warranties of any kind concerning the Licensed Material, whether express, implied,
+statutory, or other. This includes, without limitation, warranties of title, merchantability,
+fitness for a particular purpose, non-infringement, absence of latent or other defects,
+accuracy, or the presence or absence of errors, whether or not known or discoverable. Where
+disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply
+to You.
+
+b. To the extent possible, in no event will the Licensor be liable to You on any legal theory
+(including, without limitation, negligence) or otherwise for any direct, special, indirect,
+incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages
+arising out of this Public License or use of the Licensed Material, even if the Licensor has
+been advised of the possibility of such losses, costs, expenses, or damages. Where a
+limitation of liability is not allowed in full or in part, this limitation may not apply to
+You.
+
+c. The disclaimer of warranties and limitation of liability provided above shall be
+interpreted in a manner that, to the extent possible, most closely approximates an absolute
+disclaimer and waiver of all liability.
+
+
+ SECTION 5 – Term and Termination.
+
+a. If You fail to comply with this License, then Your rights under this License terminate
+automatically. The Licensor may also choose to terminate Your rights under License at any
+time, without specifying a reason.
+
+b. Where Your right to use the Licensed Material has terminated under Section 5(a), it
+reinstates
+ 1. automatically as of the date the violation is cured, provided it is cured within
+ 15 days of Your discovery of the violation; or
+ 2. upon express reinstatement by the Licensor.
+
+For the avoidance of doubt, this Section 5(b) does not affect any right the Licensor may have
+to seek remedies for Your violations of this License.
+
+c. The Licensor may offer the Licensed Material under separate terms or conditions; however,
+doing so will not terminate this License.
+
+d. The Licensor may choose to stop distributing the Licensed Material at any time. In doing
+so, Your rights under License will be terminated unless they are expressly reinstated by the
+Licensor.
+
+e. Sections 1, 4, 5, and 6 survive termination of this License.
+
+ SECTION 6 – Other Terms and Conditions.
+
+a. The Licensor shall not be bound by any additional or different terms or conditions
+communicated by You unless expressly agreed.
+
+b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated
+herein are separate from and independent of the terms and conditions of this License.
+
+c. Exemptions from this License may be negotiated with the Licensor.
\ No newline at end of file
-using HarmonyLib;
using System.Linq;
using System;
using System.Collections.Generic;
using BepInEx;
using BepInEx.IL2CPP;
using HarmonyLib;
-using UnhollowerRuntimeLib;
+using TheOtherRoles.Patches;
using UnityEngine;
+using Object = UnityEngine.Object;
namespace TheOtherRoles
{
public static SemanticVersioning.Version Version { get; private set; }
public static bool Loaded { get; private set; }
+ public static bool LoadedExternally { get; private set; }
public static BasePlugin Plugin { get; private set; }
public static Assembly Assembly { get; private set; }
public static Type[] Types { get; private set; }
public static Dictionary<string, Type> InjectedTypes { get; private set; }
- private static MonoBehaviour _submarineStatus;
- public static MonoBehaviour SubmarineStatus
- {
- get
- {
- if (!Loaded) return null;
-
- if (_submarineStatus is null || _submarineStatus.WasCollected || !_submarineStatus || _submarineStatus == null)
- {
- if (ShipStatus.Instance is null || ShipStatus.Instance.WasCollected || !ShipStatus.Instance || ShipStatus.Instance == null)
- {
- return _submarineStatus = null;
- }
- else
- {
- if (ShipStatus.Instance.Type == SUBMERGED_MAP_TYPE)
- {
- return _submarineStatus = ShipStatus.Instance.GetComponent(Il2CppType.From(SubmarineStatusType))?.TryCast(SubmarineStatusType) as MonoBehaviour;
- }
- else
- {
- return _submarineStatus = null;
- }
- }
- }
- else
- {
- return _submarineStatus;
- }
- }
- }
+ public static MonoBehaviour SubmarineStatus { get; private set; }
+ public static bool IsSubmerged { get; private set; }
+
public static bool DisableO2MaskCheckForEmergency
{
set
DisableO2MaskCheckField.SetValue(null, value);
}
}
+
+ public static void SetupMap(ShipStatus map)
+ {
+ if (map == null)
+ {
+ IsSubmerged = false;
+ SubmarineStatus = null;
+ return;
+ }
+
+ IsSubmerged = map.Type == SubmergedCompatibility.SUBMERGED_MAP_TYPE;
+ if (!IsSubmerged) return;
+
+ SubmarineStatus = map.GetComponent(Il2CppType.From(SubmarineStatusType))?.TryCast(SubmarineStatusType) as MonoBehaviour;
+ }
private static Type SubmarineStatusType;
private static MethodInfo CalculateLightRadiusMethod;
private static FieldInfo RetrieveOxigenMaskField;
public static TaskTypes RetrieveOxygenMask;
private static Type SubmarineOxygenSystemType;
- private static FieldInfo SubmarineOxygenSystemInstanceField;
+ private static MethodInfo SubmarineOxygenSystemInstanceField;
private static MethodInfo RepairDamageMethod;
+ public static bool TryLoadSubmerged()
+ {
+ try
+ {
+ TheOtherRolesPlugin.Logger.LogMessage("Trying to load Submerged...");
+ var thisAsm = Assembly.GetCallingAssembly();
+ var resourceName = thisAsm.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith("Submerged.dll"));
+ if (resourceName == default) return false;
+
+ using var submergedStream = thisAsm.GetManifestResourceStream(resourceName)!;
+ byte[] assemblyBuffer = new byte[submergedStream.Length];
+ submergedStream.Read(assemblyBuffer, 0, assemblyBuffer.Length);
+ Assembly = Assembly.Load(assemblyBuffer);
+
+ var pluginType = Assembly.GetTypes().FirstOrDefault(t => t.IsSubclassOf(typeof(BasePlugin)));
+ Plugin = (BasePlugin) Activator.CreateInstance(pluginType!);
+ Plugin.Load();
+
+ Version = pluginType.GetCustomAttribute<BepInPlugin>().Version.BaseVersion();;
+
+ IL2CPPChainloader.Instance.Plugins[SUBMERGED_GUID] = new();
+ return true;
+ }
+ catch (Exception e)
+ {
+ TheOtherRolesPlugin.Logger.LogError(e);
+ }
+ return false;
+ }
+
public static void Initialize()
{
Loaded = IL2CPPChainloader.Instance.Plugins.TryGetValue(SUBMERGED_GUID, out PluginInfo plugin);
- if (!Loaded) return;
-
- Plugin = plugin!.Instance as BasePlugin;
- Version = plugin.Metadata.Version;
+ if (!Loaded)
+ {
+ if (TryLoadSubmerged()) Loaded = true;
+ else return;
+ }
+ else
+ {
+ LoadedExternally = true;
+ Plugin = plugin!.Instance as BasePlugin;
+ Version = plugin.Metadata.Version.BaseVersion();
+ Assembly = Plugin!.GetType().Assembly;
+ }
- Assembly = Plugin!.GetType().Assembly;
+ CredentialsPatch.PingTrackerPatch.modStamp = new GameObject();
+ Object.DontDestroyOnLoad(CredentialsPatch.PingTrackerPatch.modStamp);
+
Types = AccessTools.GetTypesFromAssembly(Assembly);
InjectedTypes = (Dictionary<string, Type>) AccessTools.PropertyGetter(Types.FirstOrDefault(t => t.Name == "RegisterInIl2CppAttribute"), "RegisteredTypes")
RetrieveOxigenMaskField = AccessTools.Field(CustomTaskTypesType, "RetrieveOxygenMask");
RetrieveOxygenMask = (TaskTypes)RetrieveOxigenMaskField.GetValue(null);
- SubmarineOxygenSystemType = Types.First(t => t.Name == "SubmarineOxygenSystem");
- SubmarineOxygenSystemInstanceField = AccessTools.Field(SubmarineOxygenSystemType, "Instance");
+ SubmarineOxygenSystemType = Types.First(t => t.Name == "SubmarineOxygenSystem" && t.Namespace == "Submerged.Systems.CustomSystems.Oxygen");
+ SubmarineOxygenSystemInstanceField = AccessTools.PropertyGetter(SubmarineOxygenSystemType, "Instance");
RepairDamageMethod = AccessTools.Method(SubmarineOxygenSystemType, "RepairDamage");
}
if (!Loaded) return;
try {
ShipStatus.Instance.RpcRepairSystem((SystemTypes)130, 64);
- RepairDamageMethod.Invoke(SubmarineOxygenSystemInstanceField.GetValue(null), new object[] { PlayerControl.LocalPlayer, 64 });
+ RepairDamageMethod.Invoke(SubmarineOxygenSystemInstanceField.Invoke(null, Array.Empty<object>()), new object[] { PlayerControl.LocalPlayer, 64 });
}
catch (System.NullReferenceException) {
TheOtherRolesPlugin.Logger.LogMessage("null reference in engineer oxygen fix");
}
}
-
- public static bool isSubmerged() {
- return Loaded && ShipStatus.Instance && ShipStatus.Instance.Type == SUBMERGED_MAP_TYPE;
- }
}
public class MissingSubmergedBehaviour : MonoBehaviour
using HarmonyLib;
-using static TheOtherRoles.TheOtherRoles;
-using System.Collections;
-using System.Collections.Generic;
using System;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles {
[HarmonyPatch]
int CompletedTasks = 0;
if (!playerInfo.Disconnected && playerInfo.Tasks != null &&
playerInfo.Object &&
- (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) &&
playerInfo.Role && playerInfo.Role.TasksCountTowardProgress &&
!playerInfo.Object.hasFakeTasks()
) {
- foreach (var playerInfoTask in playerInfo.Tasks)
+ foreach (var playerInfoTask in playerInfo.Tasks.GetFastEnumerator())
{
if (playerInfoTask.Complete) CompletedTasks++;
TotalTasks++;
[HarmonyPatch(typeof(GameData), nameof(GameData.RecomputeTaskCounts))]
private static class GameDataRecomputeTaskCountsPatch {
private static bool Prefix(GameData __instance) {
- __instance.TotalTasks = 0;
- __instance.CompletedTasks = 0;
- foreach (var playerInfo in GameData.Instance.AllPlayers)
+
+
+ var totalTasks = 0;
+ var completedTasks = 0;
+
+ foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator())
{
if (playerInfo.Object
&& playerInfo.Object.hasAliveKillingLover() // Tasks do not count if a Crewmate has an alive killing Lover
)
continue;
var (playerCompleted, playerTotal) = taskInfo(playerInfo);
- __instance.TotalTasks += playerTotal;
- __instance.CompletedTasks += playerCompleted;
+ totalTasks += playerTotal;
+ completedTasks += playerCompleted;
}
+
+ __instance.TotalTasks = totalTasks;
+ __instance.CompletedTasks = completedTasks;
return false;
}
}
-using System.Net;
using System.Linq;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
using HarmonyLib;
-using Hazel;
using System;
using System.Collections.Generic;
-using System.Collections;
-using System.IO;
using UnityEngine;
using TheOtherRoles.Objects;
+using TheOtherRoles.Utilities;
namespace TheOtherRoles
{
public static Sprite getLogSprite() {
if (logSprite) return logSprite;
- logSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
+ logSprite = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
return logSprite;
}
public static void resetCamouflage() {
camouflageTimer = 0f;
- foreach (PlayerControl p in PlayerControl.AllPlayerControls)
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator())
p.setDefaultLook();
}
public static Sprite getVitalsSprite() {
if (vitalsSprite) return vitalsSprite;
- vitalsSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.VitalsButton].Image;
+ vitalsSprite = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.VitalsButton].Image;
return vitalsSprite;
}
public static Sprite getLogSprite() {
if (logSprite) return logSprite;
- logSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
+ logSprite = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
return logSprite;
}
public static Sprite getAdminSprite() {
byte mapId = PlayerControl.GameOptions.MapId;
- UseButtonSettings button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.PolusAdminButton]; // Polus
- if (mapId == 0 || mapId == 3) button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks
- else if (mapId == 1) button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ
- else if (mapId == 4) button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship
+ UseButtonSettings button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.PolusAdminButton]; // Polus
+ if (mapId == 0 || mapId == 3) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks
+ else if (mapId == 1) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ
+ else if (mapId == 4) button = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship
adminSprite = button.Image;
return adminSprite;
}
private static float lastPPU;
public static Sprite getAnimatedVentSealedSprite() {
float ppu = 185f;
- if (SubmergedCompatibility.isSubmerged()) ppu = 120f;
+ if (SubmergedCompatibility.IsSubmerged) ppu = 120f;
if (lastPPU != ppu) {
animatedVentSealedSprite = null;
lastPPU = ppu;
private static Sprite camSprite;
public static Sprite getCamSprite() {
if (camSprite) return camSprite;
- camSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.CamsButton].Image;
+ camSprite = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.CamsButton].Image;
return camSprite;
}
private static Sprite logSprite;
public static Sprite getLogSprite() {
if (logSprite) return logSprite;
- logSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
+ logSprite = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
return logSprite;
}
public static float vision = 1f;
public static bool lawyerKnowsRole = false;
public static bool targetCanBeJester = false;
+ public static bool targetWasGuessed = false;
public static Sprite getTargetSprite() {
if (targetSprite) return targetSprite;
public static void clearAndReload(bool clearTarget = true) {
lawyer = null;
- if (clearTarget) target = null;
+ if (clearTarget) {
+ target = null;
+ targetWasGuessed = false;
+ }
triggerLawyerWin = false;
vision = CustomOptionHolder.lawyerVision.getFloat();
public static float cooldown = 30f;
public static float traceTime = 1f;
public static bool knowsTargetLocation = false;
+ public static float invisibleDuration = 5f;
+ public static float invisibleTimer = 0f;
+ public static bool isInvisble = false;
private static Sprite markButtonSprite;
private static Sprite killButtonSprite;
public static Arrow arrow = new Arrow(Color.black);
cooldown = CustomOptionHolder.ninjaCooldown.getFloat();
knowsTargetLocation = CustomOptionHolder.ninjaKnowsTargetLocation.getBool();
traceTime = CustomOptionHolder.ninjaTraceTime.getFloat();
-
+ invisibleDuration = CustomOptionHolder.ninjaInvisibleDuration.getFloat();
+ invisibleTimer = 0f;
+ isInvisble = false;
if (arrow?.arrow != null) UnityEngine.Object.Destroy(arrow.arrow);
arrow = new Arrow(Color.black);
if (arrow.arrow != null) arrow.arrow.SetActive(false);
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>4.1.1</Version>
+ <Version>4.1.2</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
<LangVersion>latest</LangVersion>
- </PropertyGroup>
-
- <PropertyGroup>
- <DefineConstants>$(DefineConstants);STEAM</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
- <EmbeddedResource Include="Resources\CustomHats\*.png" />
- <EmbeddedResource Include="Resources\*.png" />
- <EmbeddedResource Include="Resources\TricksterAnimation\*.png" />
- <EmbeddedResource Include="Resources\PortalAnimation\*.png" />
+ <EmbeddedResource Include="Resources\**" />
</ItemGroup>
<ItemGroup>
- <Reference Include="$(AmongUs)/BepInEx/core/*.dll" />
- <Reference Include="$(AmongUs)/BepInEx/unhollowed/*.dll" />
+ <PackageReference Include="AmongUs.GameLibs.Steam" Version="2022.3.29" />
+ <PackageReference Include="BepInEx.IL2CPP" Version="6.0.0-be.559" />
+ <PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="1.1.1" />
</ItemGroup>
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
--- /dev/null
+using System;
+using System.Collections;
+using System.Linq.Expressions;
+using Il2CppSystem.Collections.Generic;
+
+namespace TheOtherRoles.Utilities;
+
+public static class EnumerationHelpers
+{
+ public static System.Collections.Generic.IEnumerable<T> GetFastEnumerator<T>(this List<T> list) where T : Il2CppSystem.Object => new Il2CppListEnumerable<T>(list);
+}
+
+public unsafe class Il2CppListEnumerable<T> : System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IEnumerator<T> where T : Il2CppSystem.Object
+{
+ private struct Il2CppListStruct
+ {
+#pragma warning disable CS0169
+ private IntPtr _unusedPtr1;
+ private IntPtr _unusedPtr2;
+#pragma warning restore CS0169
+
+#pragma warning disable CS0649
+ public IntPtr _items;
+ public int _size;
+#pragma warning restore CS0649
+ }
+
+ private static readonly int _elemSize;
+ private static readonly int _offset;
+ private static Func<IntPtr, T> _objFactory;
+
+ static Il2CppListEnumerable()
+ {
+ _elemSize = IntPtr.Size;
+ _offset = 4 * IntPtr.Size;
+
+ var constructor = typeof(T).GetConstructor(new[] {typeof(IntPtr)});
+ var ptr = Expression.Parameter(typeof(IntPtr));
+ var create = Expression.New(constructor!, ptr);
+ var lambda = Expression.Lambda<Func<IntPtr, T>>(create, ptr);
+ _objFactory = lambda.Compile();
+ }
+
+ private readonly IntPtr _arrayPointer;
+ private readonly int _count;
+ private int _index = -1;
+
+ public Il2CppListEnumerable(List<T> list)
+ {
+ var listStruct = (Il2CppListStruct*) list.Pointer;
+ _count = listStruct->_size;
+ _arrayPointer = listStruct->_items;
+ }
+
+ object IEnumerator.Current => Current;
+ public T Current { get; private set; }
+
+ public bool MoveNext()
+ {
+ if (++_index >= _count) return false;
+ var refPtr = *(IntPtr*) IntPtr.Add(IntPtr.Add(_arrayPointer, _offset), _index * _elemSize);
+ Current = _objFactory(refPtr);
+ return true;
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ }
+
+ public System.Collections.Generic.IEnumerator<T> GetEnumerator()
+ {
+ return this;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this;
+ }
+
+ public void Dispose()
+ {
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Linq.Expressions;
+using UnityEngine;
+
+namespace TheOtherRoles.Utilities;
+
+public static unsafe class FastDestroyableSingleton<T> where T : MonoBehaviour
+{
+ private static readonly IntPtr _fieldPtr;
+ private static readonly Func<IntPtr, T> _createObject;
+ static FastDestroyableSingleton()
+ {
+ _fieldPtr = IL2CPP.GetIl2CppField(Il2CppClassPointerStore<DestroyableSingleton<T>>.NativeClassPtr, nameof (DestroyableSingleton<T>._instance));
+ var constructor = typeof(T).GetConstructor(new[] {typeof(IntPtr)});
+ var ptr = Expression.Parameter(typeof(IntPtr));
+ var create = Expression.New(constructor!, ptr);
+ var lambda = Expression.Lambda<Func<IntPtr, T>>(create, ptr);
+ _createObject = lambda.Compile();
+ }
+
+ public static T Instance
+ {
+ get
+ {
+ IntPtr objectPointer;
+ IL2CPP.il2cpp_field_static_get_value(_fieldPtr, &objectPointer);
+ if (objectPointer == IntPtr.Zero) return DestroyableSingleton<T>.Instance;
+ return _createObject(objectPointer);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System.Collections.Generic;
+using HarmonyLib;
+using Il2CppSystem;
+
+namespace TheOtherRoles.Utilities;
+
+public static class MapUtilities
+{
+ public static ShipStatus CachedShipStatus = ShipStatus.Instance;
+
+ public static void MapDestroyed()
+ {
+ CachedShipStatus = ShipStatus.Instance;
+ _systems.Clear();
+ }
+
+ private static readonly Dictionary<SystemTypes, Object> _systems = new();
+ public static Dictionary<SystemTypes, Object> Systems
+ {
+ get
+ {
+ if (_systems.Count == 0) GetSystems();
+ return _systems;
+ }
+ }
+
+ private static void GetSystems()
+ {
+ if (!CachedShipStatus) return;
+
+ var systems = CachedShipStatus.Systems;
+ if (systems.Count <= 0) return;
+
+ foreach (var systemTypes in SystemTypeHelpers.AllTypes)
+ {
+ if (!systems.ContainsKey(systemTypes)) continue;
+ _systems[systemTypes] = systems[systemTypes].TryCast<Object>();
+ }
+ }
+}
+
+[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Awake))]
+public static class ShipStatus_Awake_Patch
+{
+ [HarmonyPostfix, HarmonyPriority(Priority.Last)]
+ public static void Postfix(ShipStatus __instance)
+ {
+ MapUtilities.CachedShipStatus = __instance;
+ SubmergedCompatibility.SetupMap(__instance);
+ }
+}
+[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.OnDestroy))]
+public static class ShipStatus_OnDestroy_Patch
+{
+ [HarmonyPostfix, HarmonyPriority(Priority.Last)]
+ public static void Postfix()
+ {
+ MapUtilities.CachedShipStatus = null;
+ MapUtilities.MapDestroyed();
+ SubmergedCompatibility.SetupMap(null);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<configuration>
+ <packageSources>
+ <add key="BepInEx" value="https://nuget.bepinex.dev/v3/index.json" />
+ </packageSources>
+</configuration>
\ No newline at end of file