From: dwd0tcom Date: Tue, 17 May 2022 19:57:15 +0000 (+0200) Subject: Version 4.1.2 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=0b47cc89c86374985e5932b069cc856a1a8f7dae;p=rhonda%2Ftheotherroles.git Version 4.1.2 --- diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore index 375e88d..ed41176 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,362 @@ -.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 diff --git a/README.md b/README.md index c7907c9..188ba0b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # 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) @@ -103,6 +104,16 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**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 @@ -644,8 +655,10 @@ The mod adds a few new settings to Among Us (in addition to the role settings): - **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 @@ -702,8 +715,6 @@ Here are a few instructions, on how to create a custom hat: # 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: @@ -927,6 +938,7 @@ The Ninja is an Impostor who has the ability to kill another player all over the 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 @@ -944,6 +956,7 @@ If the Ninja uses its ability, it will leave a trace (leaves) for a configurable | Ninja Knows Location Of Target | - | Trace Duration | - | Time Till Trace Color Has Faded | - +| Time The Ninja Is Invisible | - ----------------------- ## Guesser @@ -1672,7 +1685,6 @@ The Invert can affect all teams (Impostor, Neutral, Crewmate). ----------------------- # 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 diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index deb34a1..f059789 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -6,6 +6,7 @@ using static TheOtherRoles.TheOtherRoles; using TheOtherRoles.Objects; using System.Linq; using System.Collections.Generic; +using TheOtherRoles.Utilities; namespace TheOtherRoles { @@ -48,9 +49,13 @@ namespace TheOtherRoles public static CustomButton witchSpellButton; public static CustomButton ninjaButton; public static CustomButton mayorMeetingButton; + public static CustomButton zoomOutButton; public static Dictionary> 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; @@ -112,6 +117,7 @@ namespace TheOtherRoles 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() { @@ -164,11 +170,11 @@ namespace TheOtherRoles // 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.Instance.KillButton.isActiveAndEnabled) addReplacementHandcuffedButton(arsonistButton, new Vector3(0, 1f, 0), couldUse: () => { return FastDestroyableSingleton.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.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.Instance.ReportButton.graphic.color == Palette.EnabledColor; }); } else if (!handcuffed && deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) // Reset to original. Disables the replacements, enables the original buttons. { @@ -197,25 +203,25 @@ namespace TheOtherRoles 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(); @@ -226,9 +232,9 @@ namespace TheOtherRoles () => { 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; }, @@ -409,6 +415,7 @@ namespace TheOtherRoles ); // Morphling morph + morphlingButton = new CustomButton( () => { if (Morphling.sampledTarget != null) { @@ -418,10 +425,25 @@ namespace TheOtherRoles 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(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; }, @@ -432,6 +454,11 @@ namespace TheOtherRoles 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), @@ -443,6 +470,11 @@ namespace TheOtherRoles 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; } } ); @@ -494,7 +526,7 @@ namespace TheOtherRoles hackerAdminTableButton = new CustomButton( () => { if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled) - DestroyableSingleton.Instance.ShowMap((System.Action)(m => m.ShowCountOverlay())); + FastDestroyableSingleton.Instance.ShowMap((System.Action)(m => m.ShowCountOverlay())); if (Hacker.cantMove) PlayerControl.LocalPlayer.moveable = false; PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement @@ -656,7 +688,7 @@ namespace TheOtherRoles AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0); - HudManager.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action((p) => { // Delayed action + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action((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); @@ -765,11 +797,11 @@ namespace TheOtherRoles } RPCProcedure.usePortal(PlayerControl.LocalPlayer.PlayerId); usePortalButton.Timer = usePortalButton.MaxTimer; - HudManager.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action((p) => { // Delayed action + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action((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); @@ -978,7 +1010,7 @@ namespace TheOtherRoles 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((p) => { // Delayed action + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Warlock.rootTime, new Action((p) => { // Delayed action if (p == 1f) { PlayerControl.LocalPlayer.moveable = true; } @@ -1016,7 +1048,7 @@ namespace TheOtherRoles 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)); @@ -1031,12 +1063,12 @@ namespace TheOtherRoles }, () => { 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(), @@ -1082,7 +1114,7 @@ namespace TheOtherRoles 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(); @@ -1242,7 +1274,7 @@ namespace TheOtherRoles 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 + ")"; @@ -1251,9 +1283,9 @@ namespace TheOtherRoles 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.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}"); + FastDestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}"); // Remove soul if (Medium.oneTimeUse) { @@ -1276,7 +1308,7 @@ namespace TheOtherRoles } } - HudManager.Instance.StartCoroutine(Effects.Lerp(5f, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(5f, new Action((p) => { if (target != null) { var tmp = target.color; tmp.a = Mathf.Clamp01(1 - p); @@ -1398,13 +1430,19 @@ namespace TheOtherRoles 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); @@ -1465,9 +1503,9 @@ namespace TheOtherRoles () => { 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; }, @@ -1483,6 +1521,23 @@ namespace TheOtherRoles "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; diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 833b498..fcfbfbe 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -1,12 +1,5 @@ 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; @@ -96,6 +89,7 @@ namespace TheOtherRoles { public static CustomOption ninjaKnowsTargetLocation; public static CustomOption ninjaTraceTime; public static CustomOption ninjaTraceColorTime; + public static CustomOption ninjaInvisibleDuration; public static CustomOption shifterSpawnRate; public static CustomOption shifterShiftsModifiers; @@ -360,6 +354,8 @@ namespace TheOtherRoles { 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); diff --git a/TheOtherRoles/GameHistory.cs b/TheOtherRoles/GameHistory.cs index 0594e87..90611b3 100644 --- a/TheOtherRoles/GameHistory.cs +++ b/TheOtherRoles/GameHistory.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; -using System.Collections; using System; using UnityEngine; -using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles { public class DeadPlayer diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index ab8a144..853afb2 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -2,14 +2,13 @@ using System; 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 { @@ -62,7 +61,7 @@ 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; @@ -71,7 +70,7 @@ namespace TheOtherRoles { public static Dictionary allPlayersById() { Dictionary res = new Dictionary(); - foreach (PlayerControl player in PlayerControl.AllPlayerControls) + foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator()) res.Add(player.PlayerId, player); return res; } @@ -87,20 +86,24 @@ namespace TheOtherRoles { } public static void refreshRoleDescription(PlayerControl player) { - if (player == null) return; - List infos = RoleInfo.getRoleInfoForPlayer(player); + List taskTexts = new(infos.Count); + foreach (var roleInfo in infos) + { + taskTexts.Add(getRoleString(roleInfo)); + } + var toRemove = new List(); - foreach (PlayerTask t in player.myTasks) { - var textTask = t.gameObject.GetComponent(); - 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(); + 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) { @@ -110,30 +113,37 @@ namespace TheOtherRoles { } // Add TextTask for remaining RoleInfos - foreach (RoleInfo roleInfo in infos) { + foreach (string title in taskTexts) { var task = new GameObject("RoleTask").AddComponent(); 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.Instance == null) return false; - StringNames n = DestroyableSingleton.Instance.CurrentRegion.TranslateName; + if (FastDestroyableSingleton.Instance == null) return false; + StringNames n = FastDestroyableSingleton.Instance.CurrentRegion.TranslateName; return n != StringNames.ServerNA && n != StringNames.ServerEU && n != StringNames.ServerAS; } @@ -147,7 +157,7 @@ namespace TheOtherRoles { 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); @@ -198,6 +208,7 @@ namespace TheOtherRoles { 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 @@ -218,7 +229,7 @@ namespace TheOtherRoles { target.RawSetHat(hatId, colorId); target.RawSetName(hidePlayerName(PlayerControl.LocalPlayer, target) ? "" : playerName); - SkinViewData nextSkin = DestroyableSingleton.Instance.GetSkinById(skinId).viewData.viewData; + SkinViewData nextSkin = FastDestroyableSingleton.Instance.GetSkinById(skinId).viewData.viewData; PlayerPhysics playerPhysics = target.MyPhysics; AnimationClip clip = null; var spriteAnim = playerPhysics.Skin.animator; @@ -231,14 +242,14 @@ namespace TheOtherRoles { else clip = nextSkin.IdleAnim; float progress = playerPhysics.Animator.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime; playerPhysics.Skin.skin = nextSkin; - if (playerPhysics.Skin.layer.material == DestroyableSingleton.Instance.PlayerMaterial) + if (playerPhysics.Skin.layer.material == FastDestroyableSingleton.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(DestroyableSingleton.Instance.GetPetById(petId).viewData.viewData); + target.CurrentPet = UnityEngine.Object.Instantiate(FastDestroyableSingleton.Instance.GetPetById(petId).viewData.viewData); target.CurrentPet.transform.position = target.transform.position; target.CurrentPet.Source = target; target.CurrentPet.Visible = target.Visible; @@ -246,11 +257,11 @@ namespace TheOtherRoles { } 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((p) => { - var renderer = HudManager.Instance.FullScreen; + if (FastDestroyableSingleton.Instance == null || FastDestroyableSingleton.Instance.FullScreen == null) return; + FastDestroyableSingleton.Instance.FullScreen.gameObject.SetActive(true); + FastDestroyableSingleton.Instance.FullScreen.enabled = true; + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(duration, new Action((p) => { + var renderer = FastDestroyableSingleton.Instance.FullScreen; if (p < 0.5) { if (renderer != null) @@ -361,7 +372,7 @@ namespace TheOtherRoles { public static List getKillerTeamMembers(PlayerControl player) { List team = new List(); - 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); @@ -369,6 +380,23 @@ namespace TheOtherRoles { 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) { diff --git a/TheOtherRoles/Il2CppHelpers.cs b/TheOtherRoles/Il2CppHelpers.cs new file mode 100644 index 0000000..34cd019 --- /dev/null +++ b/TheOtherRoles/Il2CppHelpers.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq.Expressions; +using UnityEngine; + +namespace TheOtherRoles; + +public static class Il2CppHelpers +{ + private static class CastHelper where T : Il2CppObjectBase + { + public static Func 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>(create, ptr); + Cast = lambda.Compile(); + } + } + + public static T CastFast(this Il2CppObjectBase obj) where T : Il2CppObjectBase + { + if (obj is T casted) return casted; + return obj.Pointer.CastFast(); + } + + public static T CastFast(this IntPtr ptr)where T : Il2CppObjectBase + { + return CastHelper.Cast(ptr); + } +} \ No newline at end of file diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index ee4de2b..54a5ca3 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -1,18 +1,18 @@ -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 { @@ -22,7 +22,7 @@ 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; @@ -51,11 +51,11 @@ namespace TheOtherRoles public static IRegionInfo[] defaultRegions; public static void UpdateRegions() { - ServerManager serverManager = DestroyableSingleton.Instance; + ServerManager serverManager = FastDestroyableSingleton.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() }).ToArray(); + regions = regions.Concat(new IRegionInfo[] { CustomRegion.CastFast() }).ToArray(); ServerManager.DefaultRegions = regions; serverManager.AvailableRegions = regions; } diff --git a/TheOtherRoles/MapOptions.cs b/TheOtherRoles/MapOptions.cs index e2b165f..06339ba 100644 --- a/TheOtherRoles/MapOptions.cs +++ b/TheOtherRoles/MapOptions.cs @@ -1,8 +1,5 @@ using System.Collections.Generic; -using System.Collections; -using System; using UnityEngine; -using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles{ static class MapOptions { diff --git a/TheOtherRoles/Modules/ChatCommands.cs b/TheOtherRoles/Modules/ChatCommands.cs index 2631a79..91ff506 100644 --- a/TheOtherRoles/Modules/ChatCommands.cs +++ b/TheOtherRoles/Modules/ChatCommands.cs @@ -1,13 +1,7 @@ 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] @@ -46,7 +40,7 @@ namespace TheOtherRoles.Modules { 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.Instance.KillOverlay.ShowKillAnimation(PlayerControl.LocalPlayer.Data, PlayerControl.LocalPlayer.Data); handled = true; } else if (text.ToLower().StartsWith("/color ")) { handled = true; @@ -95,7 +89,7 @@ namespace TheOtherRoles.Modules { [HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))] public static class AddChat { public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer) { - if (__instance != DestroyableSingleton.Instance.Chat) + if (__instance != FastDestroyableSingleton.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); diff --git a/TheOtherRoles/Modules/CustomColors.cs b/TheOtherRoles/Modules/CustomColors.cs index d0c9265..11919d6 100644 --- a/TheOtherRoles/Modules/CustomColors.cs +++ b/TheOtherRoles/Modules/CustomColors.cs @@ -1,12 +1,8 @@ -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 { @@ -186,7 +182,7 @@ namespace TheOtherRoles.Modules { [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; diff --git a/TheOtherRoles/Modules/CustomHats.cs b/TheOtherRoles/Modules/CustomHats.cs index ee49664..f30a2c2 100644 --- a/TheOtherRoles/Modules/CustomHats.cs +++ b/TheOtherRoles/Modules/CustomHats.cs @@ -1,25 +1,15 @@ -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] @@ -128,7 +118,7 @@ namespace TheOtherRoles.Modules { private static HatData CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) { if (hatShader == null) { - Material tmpShader = DestroyableSingleton.Instance.PlayerMaterial; + Material tmpShader = FastDestroyableSingleton.Instance.PlayerMaterial; hatShader = tmpShader; } @@ -239,6 +229,8 @@ namespace TheOtherRoles.Modules { if (DestroyableSingleton.InstanceExists) { try { string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test"; + if (!Directory.Exists(filePath)) + Directory.CreateDirectory(filePath); DirectoryInfo d = new DirectoryInfo(filePath); string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Text files List hats = createCustomHatDetails(filePaths, true); @@ -282,7 +274,7 @@ namespace TheOtherRoles.Modules { ColorChip colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) { colorChip.Button.OnMouseOver.AddListener((System.Action)(() => __instance.SelectHat(hat))); - colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(DestroyableSingleton.Instance.GetHatById(SaveManager.LastHat)))); + colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(FastDestroyableSingleton.Instance.GetHatById(SaveManager.LastHat)))); colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.ClickEquip())); } else { colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.SelectHat(hat))); @@ -324,7 +316,7 @@ namespace TheOtherRoles.Modules { UnityEngine.Object.Destroy(__instance.scroller.Inner.GetChild(i).gameObject); __instance.ColorChips = new Il2CppSystem.Collections.Generic.List(); - HatData[] unlockedHats = DestroyableSingleton.Instance.GetUnlockedHats(); + HatData[] unlockedHats = FastDestroyableSingleton.Instance.GetUnlockedHats(); Dictionary>> packages = new Dictionary>>(); foreach (HatData hatBehaviour in unlockedHats) { diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index bfbdbc0..bb909d6 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -7,7 +7,7 @@ using HarmonyLib; using Hazel; using System.Reflection; using System.Text; -using static TheOtherRoles.TheOtherRoles; +using TheOtherRoles.Utilities; namespace TheOtherRoles { public class CustomOption { @@ -58,10 +58,10 @@ namespace TheOtherRoles { } 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 selections = new List(); + List selections = new(); for (float s = min; s <= max; s += step) selections.Add(s); - return new CustomOption(id, type, name, selections.Cast().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) { @@ -591,9 +591,9 @@ namespace TheOtherRoles { TheOtherRolesPlugin.optionsPage = 6; } if (page != TheOtherRolesPlugin.optionsPage) { - Vector3 position = (Vector3)HudManager.Instance?.GameSettings?.transform.localPosition; + Vector3 position = (Vector3)FastDestroyableSingleton.Instance?.GameSettings?.transform.localPosition; if (position != null) { - HudManager.Instance.GameSettings.transform.localPosition = new Vector3(position.x, 2.9f, position.z); + FastDestroyableSingleton.Instance.GameSettings.transform.localPosition = new Vector3(position.x, 2.9f, position.z); } } } diff --git a/TheOtherRoles/Modules/DynamicLobbies.cs b/TheOtherRoles/Modules/DynamicLobbies.cs index 152770d..727f86c 100644 --- a/TheOtherRoles/Modules/DynamicLobbies.cs +++ b/TheOtherRoles/Modules/DynamicLobbies.cs @@ -1,8 +1,8 @@ using System; using HarmonyLib; -using UnityEngine; using Hazel; using InnerNet; +using TheOtherRoles.Utilities; namespace TheOtherRoles.Modules { [HarmonyPatch] @@ -23,7 +23,7 @@ namespace TheOtherRoles.Modules { LobbyLimit = Math.Clamp(LobbyLimit, 4, 15); if (LobbyLimit != PlayerControl.GameOptions.MaxPlayers) { PlayerControl.GameOptions.MaxPlayers = LobbyLimit; - DestroyableSingleton.Instance.LastPlayerCount = LobbyLimit; + FastDestroyableSingleton.Instance.LastPlayerCount = LobbyLimit; PlayerControl.LocalPlayer.RpcSyncSettings(PlayerControl.GameOptions); __instance.AddChat(PlayerControl.LocalPlayer, $"Lobby Size changed to {LobbyLimit} players"); } else { diff --git a/TheOtherRoles/Modules/ModUpdater.cs b/TheOtherRoles/Modules/ModUpdater.cs index e26c8f9..86f5712 100644 --- a/TheOtherRoles/Modules/ModUpdater.cs +++ b/TheOtherRoles/Modules/ModUpdater.cs @@ -1,18 +1,23 @@ 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 @@ -21,15 +26,16 @@ 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) { @@ -41,7 +47,7 @@ namespace TheOtherRoles.Modules public bool IsNewer(Version version) { if (!Version.TryParse(Tag, out var myVersion)) return false; - return myVersion > version; + return myVersion.BaseVersion() > version.BaseVersion(); } } @@ -56,7 +62,7 @@ namespace TheOtherRoles.Modules if (Instance) Destroy(this); Instance = this; - SceneManager.add_sceneLoaded((Action) (OnSceneLoaded)); + SceneManager.add_sceneLoaded((System.Action) (OnSceneLoaded)); this.StartCoroutine(CoCheckUpdates()); foreach (var file in Directory.GetFiles(Paths.PluginPath, "*.old")) @@ -67,7 +73,7 @@ namespace TheOtherRoles.Modules 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; @@ -93,9 +99,9 @@ namespace TheOtherRoles.Modules var text = button.transform.GetChild(0).GetComponent(); 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)(p => text.SetText(t)))); + StartCoroutine(Effects.Lerp(0.1f, (System.Action)(p => text.SetText(t)))); buttonSprite.color = text.color = Color.red; passiveButton.OnMouseOut.AddListener((Action)(() => buttonSprite.color = text.color = Color.red)); @@ -111,19 +117,25 @@ namespace TheOtherRoles.Modules [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] @@ -159,6 +171,8 @@ namespace TheOtherRoles.Modules Instance.SubmergedUpdate = submergedUpdateCheck.Result; } } + + Instance.OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single); } [HideFromIl2Cpp] @@ -175,10 +189,38 @@ namespace TheOtherRoles.Modules 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 DownloadUpdate() { var isSubmerged = TORUpdate is null; + if (isSubmerged && TryUpdateSubmergedInternally()) return true; var data = isSubmerged ? SubmergedUpdate : TORUpdate; var client = new HttpClient(); @@ -201,26 +243,15 @@ namespace TheOtherRoles.Modules 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 +} diff --git a/TheOtherRoles/Objects/Arrow.cs b/TheOtherRoles/Objects/Arrow.cs index 5324489..a2ec98f 100644 --- a/TheOtherRoles/Objects/Arrow.cs +++ b/TheOtherRoles/Objects/Arrow.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Collections; using UnityEngine; namespace TheOtherRoles.Objects { diff --git a/TheOtherRoles/Objects/Bloodytrail.cs b/TheOtherRoles/Objects/Bloodytrail.cs index 3fc312f..1619a3a 100644 --- a/TheOtherRoles/Objects/Bloodytrail.cs +++ b/TheOtherRoles/Objects/Bloodytrail.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections; +using TheOtherRoles.Utilities; using UnityEngine; using static TheOtherRoles.TheOtherRoles; @@ -26,8 +26,8 @@ namespace TheOtherRoles.Objects { 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; @@ -37,14 +37,14 @@ namespace TheOtherRoles.Objects { spriteRenderer = blood.AddComponent(); spriteRenderer.sprite = sp[index]; - spriteRenderer.material = DestroyableSingleton.Instance.PlayerMaterial; + spriteRenderer.material = FastDestroyableSingleton.Instance.PlayerMaterial; PlayerControl.SetPlayerMaterialColors(color, spriteRenderer); // spriteRenderer.color = color; blood.SetActive(true); bloodytrail.Add(this); - HudManager.Instance.StartCoroutine(Effects.Lerp(10f, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(10f, new Action((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)); diff --git a/TheOtherRoles/Objects/CustomButton.cs b/TheOtherRoles/Objects/CustomButton.cs index ba7347a..a547e6b 100644 --- a/TheOtherRoles/Objects/CustomButton.cs +++ b/TheOtherRoles/Objects/CustomButton.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Reflection; +using TMPro; using UnityEngine; using UnityEngine.UI; using static TheOtherRoles.TheOtherRoles; @@ -11,6 +10,10 @@ namespace TheOtherRoles.Objects { { public static List buttons = new List(); 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; @@ -31,6 +34,7 @@ namespace TheOtherRoles.Objects { public KeyCode? hotkey; private string buttonText; public bool isHandcuffed = false; + private static readonly int Desat = Shader.PropertyToID("_Desat"); public CustomButton(Action OnClick, Func HasButton, Func CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false, string buttonText = "") { @@ -51,8 +55,12 @@ namespace TheOtherRoles.Objects { 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(); - 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); @@ -66,7 +74,7 @@ namespace TheOtherRoles.Objects { { 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 @@ -132,17 +140,20 @@ namespace TheOtherRoles.Objects { 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; } @@ -151,7 +162,7 @@ namespace TheOtherRoles.Objects { 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; } @@ -166,28 +177,28 @@ namespace TheOtherRoles.Objects { 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; } @@ -203,7 +214,7 @@ namespace TheOtherRoles.Objects { 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(); }; diff --git a/TheOtherRoles/Objects/CustomMessage.cs b/TheOtherRoles/Objects/CustomMessage.cs index ec11542..b1b4137 100644 --- a/TheOtherRoles/Objects/CustomMessage.cs +++ b/TheOtherRoles/Objects/CustomMessage.cs @@ -1,6 +1,7 @@ using UnityEngine; using System.Collections.Generic; using System; +using TheOtherRoles.Utilities; namespace TheOtherRoles.Objects { @@ -10,11 +11,11 @@ namespace TheOtherRoles.Objects { private static List customMessages = new List(); public CustomMessage(string message, float duration) { - RoomTracker roomTracker = HudManager.Instance?.roomTracker; + RoomTracker roomTracker = FastDestroyableSingleton.Instance?.roomTracker; if (roomTracker != null) { GameObject gameObject = UnityEngine.Object.Instantiate(roomTracker.gameObject); - gameObject.transform.SetParent(HudManager.Instance.transform); + gameObject.transform.SetParent(FastDestroyableSingleton.Instance.transform); UnityEngine.Object.DestroyImmediate(gameObject.GetComponent()); text = gameObject.GetComponent(); text.text = message; @@ -23,7 +24,7 @@ namespace TheOtherRoles.Objects { gameObject.transform.localPosition = new Vector3(0, -1.8f, gameObject.transform.localPosition.z); customMessages.Add(this); - HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(duration, new Action((p) => { bool even = ((int)(p * duration / 0.25f)) % 2 == 0; // Bool flips every 0.25 seconds string prefix = (even ? "" : ""); text.text = prefix + message + ""; diff --git a/TheOtherRoles/Objects/Footprint.cs b/TheOtherRoles/Objects/Footprint.cs index 7eb18e5..d5aa86c 100644 --- a/TheOtherRoles/Objects/Footprint.cs +++ b/TheOtherRoles/Objects/Footprint.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections; +using TheOtherRoles.Utilities; using UnityEngine; -using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles.Objects { class Footprint { @@ -28,7 +27,7 @@ namespace TheOtherRoles.Objects { 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; @@ -45,7 +44,7 @@ namespace TheOtherRoles.Objects { footprint.SetActive(true); footprints.Add(this); - HudManager.Instance.StartCoroutine(Effects.Lerp(footprintDuration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(footprintDuration, new Action((p) => { Color c = color; if (!anonymousFootprints && owner != null) { if (owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget?.Data != null) @@ -63,4 +62,4 @@ namespace TheOtherRoles.Objects { }))); } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Objects/Garlic.cs b/TheOtherRoles/Objects/Garlic.cs index 0812fe8..bc7d5aa 100644 --- a/TheOtherRoles/Objects/Garlic.cs +++ b/TheOtherRoles/Objects/Garlic.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Collections; using UnityEngine; namespace TheOtherRoles.Objects { diff --git a/TheOtherRoles/Objects/JackInTheBox.cs b/TheOtherRoles/Objects/JackInTheBox.cs index 3195ff3..738800b 100644 --- a/TheOtherRoles/Objects/JackInTheBox.cs +++ b/TheOtherRoles/Objects/JackInTheBox.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Collections; using UnityEngine; using System.Linq; +using TheOtherRoles.Utilities; namespace TheOtherRoles.Objects { @@ -24,7 +24,7 @@ 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((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(0.6f, new Action((p) => { if (box.boxRenderer != null) { box.boxRenderer.sprite = getBoxAnimationSprite((int)(p * boxAnimationSprites.Length)); if (p == 1f) box.boxRenderer.sprite = getBoxAnimationSprite(0); @@ -58,13 +58,13 @@ namespace TheOtherRoles.Objects { vent.ExitVentAnim = null; vent.Offset = new Vector3(0f, 0.25f, 0f); vent.GetComponent()?.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(); 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; diff --git a/TheOtherRoles/Objects/NinjaTrace.cs b/TheOtherRoles/Objects/NinjaTrace.cs index 87e3efe..d2dddac 100644 --- a/TheOtherRoles/Objects/NinjaTrace.cs +++ b/TheOtherRoles/Objects/NinjaTrace.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections; +using TheOtherRoles.Utilities; using UnityEngine; namespace TheOtherRoles.Objects { @@ -18,7 +18,7 @@ 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); @@ -32,7 +32,7 @@ namespace TheOtherRoles.Objects { // display the ninjas color in the trace float colorDuration = CustomOptionHolder.ninjaTraceColorTime.getFloat(); - HudManager.Instance.StartCoroutine(Effects.Lerp(colorDuration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(colorDuration, new Action((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]; @@ -50,7 +50,7 @@ namespace TheOtherRoles.Objects { float fadeOutDuration = 1f; if (fadeOutDuration > duration) fadeOutDuration = 0.5f * duration; - HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(duration, new Action((p) => { float interP = 0f; if (p < (duration - fadeOutDuration) / duration) interP = 0f; @@ -79,4 +79,4 @@ namespace TheOtherRoles.Objects { } } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Objects/Portal.cs b/TheOtherRoles/Objects/Portal.cs index 513ecb9..6ffd755 100644 --- a/TheOtherRoles/Objects/Portal.cs +++ b/TheOtherRoles/Objects/Portal.cs @@ -1,6 +1,7 @@ using System; using UnityEngine; using System.Collections.Generic; +using TheOtherRoles.Utilities; using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles.Objects { @@ -55,7 +56,7 @@ namespace TheOtherRoles.Objects { } teleportedPlayers.Add(new tpLogEntry(playerId, playerNameDisplay, DateTime.UtcNow)); - HudManager.Instance.StartCoroutine(Effects.Lerp(teleportDuration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(teleportDuration, new Action((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)); @@ -87,12 +88,12 @@ namespace TheOtherRoles.Objects { 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(); - animationFgRenderer.material = DestroyableSingleton.Instance.PlayerMaterial; + animationFgRenderer.material = FastDestroyableSingleton.Instance.PlayerMaterial; // Only render the inactive portals for the Portalmaker bool playerIsPortalmaker = PlayerControl.LocalPlayer == TheOtherRoles.Portalmaker.portalmaker; @@ -157,4 +158,4 @@ namespace TheOtherRoles.Objects { } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/ClientOptionsPatch.cs b/TheOtherRoles/Patches/ClientOptionsPatch.cs index 5de2618..b34ddd5 100644 --- a/TheOtherRoles/Patches/ClientOptionsPatch.cs +++ b/TheOtherRoles/Patches/ClientOptionsPatch.cs @@ -2,9 +2,9 @@ using HarmonyLib; 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; @@ -110,9 +110,9 @@ namespace TheOtherRoles.Patches { if (!popUp) return; - if (__instance.transform.parent && __instance.transform.parent == HudManager.Instance.transform) + if (__instance.transform.parent && __instance.transform.parent == FastDestroyableSingleton.Instance.transform) { - popUp.transform.SetParent(HudManager.Instance.transform); + popUp.transform.SetParent(FastDestroyableSingleton.Instance.transform); popUp.transform.localPosition = new Vector3(0, 0, -800f); } else diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index dde3159..c31d186 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -1,9 +1,6 @@ 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 { @@ -21,7 +18,7 @@ Design by Bavari"; public static string contributorsCredentials = $@" Special thanks to K3ndo & Smeggy -GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit"; +GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit, probablyadnf"; [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] private static class VersionShowerPatch @@ -41,9 +38,9 @@ GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit.Instance.MapButton.transform.position + Vector3.down * offset; } static void Postfix(PingTracker __instance){ diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index 7045c49..26c7b5b 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -1,15 +1,12 @@ 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 { @@ -63,12 +60,15 @@ namespace TheOtherRoles.Patches { 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 }); @@ -87,7 +87,7 @@ namespace TheOtherRoles.Patches { notWinners.AddRange(Jackal.formerJackals); List winnersToRemove = new List(); - 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); @@ -141,7 +141,7 @@ namespace TheOtherRoles.Patches { if (!Lovers.existingWithKiller()) { AdditionalTempData.winCondition = WinCondition.LoversTeamWin; TempData.winners = new Il2CppSystem.Collections.Generic.List(); - 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)); @@ -192,7 +192,7 @@ namespace TheOtherRoles.Patches { // 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; } @@ -408,8 +408,8 @@ namespace TheOtherRoles.Patches { } 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(); if (lifeSuppSystemType != null && lifeSuppSystemType.Countdown < 0f) { @@ -418,9 +418,9 @@ namespace TheOtherRoles.Patches { 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(); @@ -522,7 +522,7 @@ namespace TheOtherRoles.Patches { bool impLover = false; bool jackalLover = false; - foreach (var playerInfo in GameData.Instance.AllPlayers) + foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) { if (!playerInfo.Disconnected) { @@ -557,4 +557,4 @@ namespace TheOtherRoles.Patches { TeamJackalHasAliveLover = jackalLover; } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 9ecc9fe..7fa42fd 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -2,15 +2,11 @@ using HarmonyLib; 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))] @@ -78,13 +74,13 @@ namespace TheOtherRoles.Patches { Witch.futureSpelled = new List(); // 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().color = Color.white; allCameras.Add(camera); }); - ShipStatus.Instance.AllCameras = allCameras.ToArray(); + MapUtilities.CachedShipStatus.AllCameras = allCameras.ToArray(); MapOptions.camerasToAdd = new List(); foreach (Vent vent in MapOptions.ventsToSeal) { @@ -92,8 +88,8 @@ namespace TheOtherRoles.Patches { 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; } @@ -121,7 +117,7 @@ namespace TheOtherRoles.Patches { // 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); } @@ -158,7 +154,7 @@ namespace TheOtherRoles.Patches { rend.sprite = Seer.getSoulSprite(); if(Seer.limitSoulDuration) { - HudManager.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action((p) => { if (rend != null) { var tmp = rend.color; tmp.a = Mathf.Clamp01(1 - p); @@ -177,9 +173,9 @@ namespace TheOtherRoles.Patches { // 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.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton.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); @@ -226,7 +222,7 @@ namespace TheOtherRoles.Patches { // 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); } } diff --git a/TheOtherRoles/Patches/FreeNamePatch.cs b/TheOtherRoles/Patches/FreeNamePatch.cs index 3af6fe8..1e4b0d4 100644 --- a/TheOtherRoles/Patches/FreeNamePatch.cs +++ b/TheOtherRoles/Patches/FreeNamePatch.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -using HarmonyLib; +using TheOtherRoles.Utilities; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; @@ -17,7 +17,7 @@ namespace TheOtherRoles.Patches { if (!scene.name.Equals("MMOnline")) return; if (!TryMoveObjects()) return; - var editName = DestroyableSingleton.Instance.accountTab.editNameScreen; + var editName = FastDestroyableSingleton.Instance.accountTab.editNameScreen; var nameText = Object.Instantiate(editName.nameText.gameObject); nameText.transform.localPosition += Vector3.up * 2.2f; diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index abcbe32..42cbb69 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Collections.Generic; using Hazel; using System; -using UnhollowerBaseLib; +using TheOtherRoles.Utilities; namespace TheOtherRoles.Patches { public class GameStartManagerPatch { @@ -36,7 +36,7 @@ namespace TheOtherRoles.Patches { // Copy lobby code string code = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId); GUIUtility.systemCopyBuffer = code; - lobbyCodeText = DestroyableSingleton.Instance.GetString(StringNames.RoomCode, new Il2CppReferenceArray(0)) + "\r\n" + code; + lobbyCodeText = FastDestroyableSingleton.Instance.GetString(StringNames.RoomCode, new Il2CppReferenceArray(0)) + "\r\n" + code; } } @@ -140,7 +140,7 @@ namespace TheOtherRoles.Patches { 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(); if (dummyComponent != null && dummyComponent.enabled) diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs index 8689dcd..6334fa8 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -5,19 +5,22 @@ using UnityEngine; 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.Instance != null) { + Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.z); + foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) { GameData.PlayerInfo data = p.Data; - PoolablePlayer player = UnityEngine.Object.Instantiate(__instance.PlayerPrefab, HudManager.Instance.transform); + PoolablePlayer player = UnityEngine.Object.Instantiate(__instance.PlayerPrefab, FastDestroyableSingleton.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); @@ -44,9 +47,9 @@ namespace TheOtherRoles.Patches { // 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(HudManager.Instance.KillButton.cooldownTimerText, HudManager.Instance.transform); + if (FastDestroyableSingleton.Instance != null) { + Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.z) + new Vector3(-0.25f, 1f, 0); + BountyHunter.cooldownText = UnityEngine.Object.Instantiate(FastDestroyableSingleton.Instance.KillButton.cooldownTimerText, FastDestroyableSingleton.Instance.transform); BountyHunter.cooldownText.alignment = TMPro.TextAlignmentOptions.Center; BountyHunter.cooldownText.transform.localPosition = bottomLeft + new Vector3(0f, -1f, -1f); BountyHunter.cooldownText.gameObject.SetActive(true); @@ -142,7 +145,7 @@ namespace TheOtherRoles.Patches { } public static bool Prefix(IntroCutscene __instance) { if (!CustomOptionHolder.activateRoles.getBool()) return true; - HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(1f, new Action((p) => { SetRoleTexts(__instance); }))); return true; diff --git a/TheOtherRoles/Patches/LightSourcePatch.cs b/TheOtherRoles/Patches/LightSourcePatch.cs index 0112baa..a388421 100644 --- a/TheOtherRoles/Patches/LightSourcePatch.cs +++ b/TheOtherRoles/Patches/LightSourcePatch.cs @@ -1,36 +1,15 @@ 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 diff --git a/TheOtherRoles/Patches/MapBehaviourPatch.cs b/TheOtherRoles/Patches/MapBehaviourPatch.cs index cc79580..0a7867c 100644 --- a/TheOtherRoles/Patches/MapBehaviourPatch.cs +++ b/TheOtherRoles/Patches/MapBehaviourPatch.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using TheOtherRoles.Utilities; using UnityEngine; @@ -10,12 +11,12 @@ namespace TheOtherRoles.Patches { [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); @@ -31,7 +32,7 @@ namespace TheOtherRoles.Patches { __instance.GenericShow(); __instance.taskOverlay.Show(); __instance.ColorControl.SetColor(new Color(0.05f, 0.2f, 1f, 1f)); - DestroyableSingleton.Instance.SetHudActive(false); + FastDestroyableSingleton.Instance.SetHudActive(false); return false; } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index e08a052..89f050c 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -2,15 +2,12 @@ using HarmonyLib; 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] @@ -156,7 +153,7 @@ namespace TheOtherRoles.Patches { } - __instance.TitleText.text = DestroyableSingleton.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray(0)); + __instance.TitleText.text = FastDestroyableSingleton.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray(0)); int num = 0; for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; @@ -321,7 +318,7 @@ namespace TheOtherRoles.Patches { Transform button = UnityEngine.Object.Instantiate(buttonTemplate, buttonParent); Transform buttonMask = UnityEngine.Object.Instantiate(maskTemplate, buttonParent); TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button); - button.GetComponent().sprite = DestroyableSingleton.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image; + button.GetComponent().sprite = FastDestroyableSingleton.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); @@ -438,7 +435,7 @@ namespace TheOtherRoles.Patches { Transform confirmSwapButtonMask = UnityEngine.Object.Instantiate(maskTemplate, confirmSwapButtonParent); swapperConfirmButtonLabel = UnityEngine.Object.Instantiate(textTemplate, confirmSwapButton); - confirmSwapButton.GetComponent().sprite = DestroyableSingleton.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image; + confirmSwapButton.GetComponent().sprite = FastDestroyableSingleton.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"); @@ -448,7 +445,7 @@ namespace TheOtherRoles.Patches { PassiveButton passiveButton = confirmSwapButton.GetComponent(); 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((p) => { // Button appears delayed, so that its visible in the voting screen only! if (p == 1f) { @@ -539,12 +536,16 @@ namespace TheOtherRoles.Patches { 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.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}"); + FastDestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}"); } } // Remove first kill shield MapOptions.firstKillPlayer = null; + + + // Reset zoomed out ghosts + Helpers.toggleZoom(reset: true); } } diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index dac61b6..9722ea4 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -3,11 +3,10 @@ using Hazel; 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 { @@ -18,12 +17,12 @@ namespace TheOtherRoles.Patches { static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List 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; @@ -55,7 +54,7 @@ namespace TheOtherRoles.Patches { // 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; @@ -92,7 +91,7 @@ namespace TheOtherRoles.Patches { 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); @@ -108,7 +107,7 @@ namespace TheOtherRoles.Patches { 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); } @@ -184,7 +183,7 @@ namespace TheOtherRoles.Patches { 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); } @@ -280,8 +279,8 @@ namespace TheOtherRoles.Patches { 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) { @@ -300,7 +299,7 @@ namespace TheOtherRoles.Patches { 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.Instance.KillButton.SetTarget(null); return; } @@ -317,7 +316,7 @@ namespace TheOtherRoles.Patches { target = setTarget(true, true, new List() { Sidekick.wasImpostor ? Sidekick.sidekick : null, Jackal.wasImpostor ? Jackal.jackal : null}); } - HudManager.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed); + FastDestroyableSingleton.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed); } static void warlockSetTarget() { @@ -338,6 +337,14 @@ namespace TheOtherRoles.Patches { 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) { @@ -422,7 +429,7 @@ namespace TheOtherRoles.Patches { public static void playerSizeUpdate(PlayerControl p) { // Set default player size - CircleCollider2D collider = p.GetComponent(); + CircleCollider2D collider = p.Collider.CastFast(); p.transform.localScale = new Vector3(0.7f, 0.7f, 1f); collider.radius = Mini.defaultColliderRadius; @@ -446,7 +453,7 @@ namespace TheOtherRoles.Patches { } 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() : null; @@ -484,8 +491,8 @@ namespace TheOtherRoles.Patches { if (p.Data.IsDead) roleNames = roleText; playerInfoText = $"{roleNames}"; if (p == Swapper.swapper) playerInfoText = $"{roleNames}" + Helpers.cs(Swapper.color, $" ({Swapper.charges})"); - if (DestroyableSingleton.InstanceExists) { - TMPro.TextMeshPro tabText = DestroyableSingleton.Instance.tab.transform.FindChild("TabText_TMP").GetComponent(); + if (TaskPanelBehaviour.InstanceExists) { + TMPro.TextMeshPro tabText = TaskPanelBehaviour.Instance.tab.transform.FindChild("TabText_TMP").GetComponent(); tabText.SetText($"Tasks {taskInfo}"); } meetingInfoText = $"{roleNames} {taskInfo}".Trim(); @@ -511,15 +518,15 @@ namespace TheOtherRoles.Patches { } 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; @@ -568,7 +575,7 @@ namespace TheOtherRoles.Patches { } 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); @@ -611,14 +618,14 @@ namespace TheOtherRoles.Patches { BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration; var possibleTargets = new List(); - 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.Instance != null && FastDestroyableSingleton.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); @@ -669,12 +676,12 @@ namespace TheOtherRoles.Patches { } 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) { @@ -816,7 +823,6 @@ namespace TheOtherRoles.Patches { HudManagerStartPatch.witchSpellButton.MaxTimer = (Witch.cooldown + Witch.currentCooldownAddition) * multiplier; } } - public static void Postfix(PlayerControl __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; @@ -896,7 +902,6 @@ namespace TheOtherRoles.Patches { ninjaSetTarget(); NinjaTrace.UpdateAll(); ninjaUpdate(); - hackerUpdate(); swapperUpdate(); @@ -965,13 +970,13 @@ namespace TheOtherRoles.Patches { if (!string.IsNullOrWhiteSpace(msg)) { - if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) + if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance) { - DestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, msg); + FastDestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, msg); } if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0) { - DestroyableSingleton.Instance.SendWho(); + FastDestroyableSingleton.Instance.SendWho(); } } } @@ -1123,7 +1128,7 @@ namespace TheOtherRoles.Patches { if (BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) addition = BountyHunter.punishmentTime; __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier + addition); - DestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition); + FastDestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition); return false; } } @@ -1188,7 +1193,7 @@ namespace TheOtherRoles.Patches { 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(); diff --git a/TheOtherRoles/Patches/PlayerPhysicsPatch.cs b/TheOtherRoles/Patches/PlayerPhysicsPatch.cs new file mode 100644 index 0000000..178e28a --- /dev/null +++ b/TheOtherRoles/Patches/PlayerPhysicsPatch.cs @@ -0,0 +1,15 @@ +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 diff --git a/TheOtherRoles/Patches/RegionMenuPatch.cs b/TheOtherRoles/Patches/RegionMenuPatch.cs index aa009fc..bfa7be8 100644 --- a/TheOtherRoles/Patches/RegionMenuPatch.cs +++ b/TheOtherRoles/Patches/RegionMenuPatch.cs @@ -27,6 +27,7 @@ using HarmonyLib; using UnityEngine; using UnityEngine.UI; using System; +using TheOtherRoles.Utilities; using UnityEngine.Events; namespace TheOtherRoles.Patches { @@ -37,7 +38,8 @@ namespace TheOtherRoles.Patches { private static TextBoxTMP portField; public static void Postfix(RegionMenu __instance) { - var template = DestroyableSingleton.Instance; + if (!__instance.TryCast()) return; + var template = FastDestroyableSingleton.Instance; var joinGameButtons = GameObject.FindObjectsOfType(); foreach (var t in joinGameButtons) { // The correct button has a background, the other 2 dont if (t.GameIdText != null && t.GameIdText.Background != null) { diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index bdce8c3..750cea4 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -2,9 +2,9 @@ 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 { @@ -25,7 +25,7 @@ namespace TheOtherRoles.Patches { AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.resetVariables(); - if (!DestroyableSingleton.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(); } @@ -330,7 +330,7 @@ namespace TheOtherRoles.Patches { // Set Lawyer Target if (Lawyer.lawyer != null) { var possibleTargets = new List(); - 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); } diff --git a/TheOtherRoles/Patches/ShipStatusPatch.cs b/TheOtherRoles/Patches/ShipStatusPatch.cs index a5cdbb3..29f6fb5 100644 --- a/TheOtherRoles/Patches/ShipStatusPatch.cs +++ b/TheOtherRoles/Patches/ShipStatusPatch.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using HarmonyLib; -using UnhollowerRuntimeLib; +using TheOtherRoles.Utilities; using static TheOtherRoles.TheOtherRoles; using UnityEngine; @@ -62,13 +62,13 @@ namespace TheOtherRoles.Patches { } 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 = MapUtilities.Systems[SystemTypes.Electrical].CastFast(); float lerpValue = switchSystem.Value / 255f; return Mathf.Lerp(shipStatus.MinLightRadius, shipStatus.MaxLightRadius, lerpValue) * PlayerControl.GameOptions.CrewLightMod; diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 4bb2ca2..2d591d6 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -1,57 +1,59 @@ 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 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(); + + 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 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) { @@ -62,46 +64,48 @@ namespace TheOtherRoles.Patches { 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) { @@ -111,28 +115,28 @@ namespace TheOtherRoles.Patches { 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) { @@ -141,13 +145,13 @@ namespace TheOtherRoles.Patches { } // 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); } @@ -158,7 +162,7 @@ namespace TheOtherRoles.Patches { 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) @@ -216,12 +220,14 @@ namespace TheOtherRoles.Patches { } 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() { diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index 2286552..3672fd7 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -7,7 +7,7 @@ using static TheOtherRoles.TheOtherRoles; using static TheOtherRoles.GameHistory; using static TheOtherRoles.MapOptions; using System.Collections.Generic; -using TheOtherRoles.Objects; +using TheOtherRoles.Utilities; namespace TheOtherRoles.Patches { @@ -28,7 +28,7 @@ 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; @@ -132,8 +132,8 @@ namespace TheOtherRoles.Patches { [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.Instance.ReportButton.isActiveAndEnabled) { + FastDestroyableSingleton.Instance.ImpostorVentButton.Show(); } } } @@ -191,7 +191,7 @@ namespace TheOtherRoles.Patches { 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.Instance.SabotageButton.SetDisabled(); } } } @@ -353,7 +353,7 @@ namespace TheOtherRoles.Patches { __instance.timer = 0f; players = new Dictionary>(); 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; @@ -379,7 +379,7 @@ namespace TheOtherRoles.Patches { if (!commsActive) { - PlainShipRoom plainShipRoom = ShipStatus.Instance.FastRooms[counterArea.RoomType]; + PlainShipRoom plainShipRoom = MapUtilities.CachedShipStatus.FastRooms[counterArea.RoomType]; if (plainShipRoom != null && plainShipRoom.roomArea) { @@ -441,7 +441,7 @@ namespace TheOtherRoles.Patches { if (players.ContainsKey(__instance.RoomType)) { List 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(); @@ -480,10 +480,10 @@ namespace TheOtherRoles.Patches { // 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(__instance.CameraPrefab); camera.transform.SetParent(__instance.transform); camera.transform.position = new Vector3(surv.transform.position.x, surv.transform.position.y, 8f); @@ -502,7 +502,7 @@ namespace TheOtherRoles.Patches { 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; @@ -548,4 +548,4 @@ namespace TheOtherRoles.Patches { } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 3247ca5..6b276d9 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; using System; +using TheOtherRoles.Utilities; namespace TheOtherRoles { @@ -127,7 +128,8 @@ namespace TheOtherRoles Bloody, SetFirstKill, Invert, - SetTiebreak + SetTiebreak, + SetInvisible } public static class RPCProcedure { @@ -160,7 +162,7 @@ namespace TheOtherRoles } public static void forceEnd() { - foreach (PlayerControl player in PlayerControl.AllPlayerControls) + foreach (PlayerControl player in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (!player.Data.Role.IsImpostor) { @@ -172,7 +174,7 @@ namespace TheOtherRoles } 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: @@ -384,7 +386,7 @@ namespace TheOtherRoles // Role functionality public static void engineerFixLights() { - SwitchSystem switchSystem = ShipStatus.Instance.Systems[SystemTypes.Electrical].Cast(); + SwitchSystem switchSystem = MapUtilities.Systems[SystemTypes.Electrical].CastFast(); switchSystem.ActualSwitches = switchSystem.ExpectedSwitches; } @@ -410,11 +412,11 @@ namespace TheOtherRoles 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((p) => { - if (p == 1f) HudManager.Instance.FullScreen.enabled = false; + FastDestroyableSingleton.Instance.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f); + FastDestroyableSingleton.Instance.FullScreen.enabled = true; + FastDestroyableSingleton.Instance.FullScreen.gameObject.SetActive(true); + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(TimeMaster.rewindTime / 2, new Action((p) => { + if (p == 1f) FastDestroyableSingleton.Instance.FullScreen.enabled = false; }))); if (TimeMaster.timeMaster == null || PlayerControl.LocalPlayer == TimeMaster.timeMaster) return; // Time Master himself does not rewind @@ -430,7 +432,7 @@ namespace TheOtherRoles public static void timeMasterShield() { TimeMaster.shieldActive = true; - HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.shieldDuration, new Action((p) => { + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(TimeMaster.shieldDuration, new Action((p) => { if (p == 1f) TimeMaster.shieldActive = false; }))); } @@ -549,7 +551,7 @@ namespace TheOtherRoles 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, "", "", "", ""); } @@ -560,7 +562,7 @@ namespace TheOtherRoles } 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; } @@ -576,7 +578,7 @@ namespace TheOtherRoles 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; } @@ -606,7 +608,13 @@ namespace TheOtherRoles } 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.Instance.SetRole(player, RoleTypes.Crewmate); + FastDestroyableSingleton.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() : null; + if (playerInfo != null) playerInfo.text = ""; + } erasePlayerRoles(player.PlayerId, true); Sidekick.sidekick = player; if (player.PlayerId == PlayerControl.LocalPlayer.PlayerId) PlayerControl.LocalPlayer.moveable = true; @@ -732,6 +740,26 @@ namespace TheOtherRoles 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)); @@ -775,7 +803,7 @@ namespace TheOtherRoles 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) { @@ -795,7 +823,7 @@ namespace TheOtherRoles } 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; @@ -804,8 +832,8 @@ namespace TheOtherRoles 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; } @@ -815,7 +843,7 @@ namespace TheOtherRoles 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(); } } @@ -848,8 +876,10 @@ namespace TheOtherRoles 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); @@ -873,20 +903,20 @@ namespace TheOtherRoles MeetingHud.Instance.CheckForEndVoting(); } PlayerControl guesser = Helpers.playerById(killerId); - if (HudManager.Instance != null && guesser != null) + if (FastDestroyableSingleton.Instance != null && guesser != null) if (PlayerControl.LocalPlayer == dyingTarget) - HudManager.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data); + FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data); else if (dyingLoverPartner != null && PlayerControl.LocalPlayer == dyingLoverPartner) - HudManager.Instance.KillOverlay.ShowKillAnimation(dyingLoverPartner.Data, dyingLoverPartner.Data); + FastDestroyableSingleton.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.Instance) - DestroyableSingleton.Instance.Chat.AddChat(guesser, msg); + if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance) + FastDestroyableSingleton.Instance.Chat.AddChat(guesser, msg); if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0) - DestroyableSingleton.Instance.SendWho(); + FastDestroyableSingleton.Instance.SendWho(); } } @@ -1126,6 +1156,11 @@ namespace TheOtherRoles case (byte)CustomRPC.SetTiebreak: RPCProcedure.setTiebreak(); break; + case (byte)CustomRPC.SetInvisible: + byte invisiblePlayer = reader.ReadByte(); + byte invisibleFlag = reader.ReadByte(); + RPCProcedure.setInvisible(invisiblePlayer, invisibleFlag); + break; } } } diff --git a/TheOtherRoles/Resources/MinusButton.png b/TheOtherRoles/Resources/MinusButton.png new file mode 100644 index 0000000..cd1bba6 Binary files /dev/null and b/TheOtherRoles/Resources/MinusButton.png differ diff --git a/TheOtherRoles/Resources/MorphButton.png b/TheOtherRoles/Resources/MorphButton.png index e44ddc2..e68441d 100644 Binary files a/TheOtherRoles/Resources/MorphButton.png and b/TheOtherRoles/Resources/MorphButton.png differ diff --git a/TheOtherRoles/Resources/PlusButton.png b/TheOtherRoles/Resources/PlusButton.png new file mode 100644 index 0000000..e0ae50c Binary files /dev/null and b/TheOtherRoles/Resources/PlusButton.png differ diff --git a/TheOtherRoles/Resources/ThirdParty/Submerged/LICENSE.txt b/TheOtherRoles/Resources/ThirdParty/Submerged/LICENSE.txt new file mode 100644 index 0000000..85a2e8e --- /dev/null +++ b/TheOtherRoles/Resources/ThirdParty/Submerged/LICENSE.txt @@ -0,0 +1,179 @@ +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 diff --git a/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll b/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll new file mode 100644 index 0000000..483e962 Binary files /dev/null and b/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll differ diff --git a/TheOtherRoles/RoleInfo.cs b/TheOtherRoles/RoleInfo.cs index 384f300..1d9a81e 100644 --- a/TheOtherRoles/RoleInfo.cs +++ b/TheOtherRoles/RoleInfo.cs @@ -1,4 +1,3 @@ -using HarmonyLib; using System.Linq; using System; using System.Collections.Generic; diff --git a/TheOtherRoles/SubmergedCompatibility.cs b/TheOtherRoles/SubmergedCompatibility.cs index 2b4569c..08b5416 100644 --- a/TheOtherRoles/SubmergedCompatibility.cs +++ b/TheOtherRoles/SubmergedCompatibility.cs @@ -5,8 +5,9 @@ using System.Reflection; using BepInEx; using BepInEx.IL2CPP; using HarmonyLib; -using UnhollowerRuntimeLib; +using TheOtherRoles.Patches; using UnityEngine; +using Object = UnityEngine.Object; namespace TheOtherRoles { @@ -22,43 +23,16 @@ 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 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 @@ -67,6 +41,21 @@ namespace TheOtherRoles 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; @@ -85,19 +74,59 @@ namespace TheOtherRoles 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().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) AccessTools.PropertyGetter(Types.FirstOrDefault(t => t.Name == "RegisterInIl2CppAttribute"), "RegisteredTypes") @@ -120,8 +149,8 @@ namespace TheOtherRoles 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"); } @@ -152,17 +181,13 @@ namespace TheOtherRoles 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()), 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 diff --git a/TheOtherRoles/TasksHandler.cs b/TheOtherRoles/TasksHandler.cs index 640327e..ff4b9f1 100644 --- a/TheOtherRoles/TasksHandler.cs +++ b/TheOtherRoles/TasksHandler.cs @@ -1,8 +1,6 @@ using HarmonyLib; -using static TheOtherRoles.TheOtherRoles; -using System.Collections; -using System.Collections.Generic; using System; +using TheOtherRoles.Utilities; namespace TheOtherRoles { [HarmonyPatch] @@ -13,11 +11,10 @@ namespace TheOtherRoles { 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++; @@ -29,9 +26,12 @@ namespace TheOtherRoles { [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 @@ -40,9 +40,12 @@ namespace TheOtherRoles { ) continue; var (playerCompleted, playerTotal) = taskInfo(playerInfo); - __instance.TotalTasks += playerTotal; - __instance.CompletedTasks += playerCompleted; + totalTasks += playerTotal; + completedTasks += playerCompleted; } + + __instance.TotalTasks = totalTasks; + __instance.CompletedTasks = completedTasks; return false; } } diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 368c0e6..0e5aaa3 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -1,16 +1,10 @@ -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 { @@ -115,7 +109,7 @@ namespace TheOtherRoles public static Sprite getLogSprite() { if (logSprite) return logSprite; - logSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image; + logSprite = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image; return logSprite; } @@ -622,7 +616,7 @@ namespace TheOtherRoles public static void resetCamouflage() { camouflageTimer = 0f; - foreach (PlayerControl p in PlayerControl.AllPlayerControls) + foreach (PlayerControl p in PlayerControl.AllPlayerControls.GetFastEnumerator()) p.setDefaultLook(); } @@ -665,22 +659,22 @@ namespace TheOtherRoles public static Sprite getVitalsSprite() { if (vitalsSprite) return vitalsSprite; - vitalsSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.VitalsButton].Image; + vitalsSprite = FastDestroyableSingleton.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.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.Instance.UseButton.fastUseSettings[ImageNames.PolusAdminButton]; // Polus + if (mapId == 0 || mapId == 3) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks + else if (mapId == 1) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ + else if (mapId == 4) button = FastDestroyableSingleton.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship adminSprite = button.Image; return adminSprite; } @@ -1092,7 +1086,7 @@ namespace TheOtherRoles 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; @@ -1126,14 +1120,14 @@ namespace TheOtherRoles private static Sprite camSprite; public static Sprite getCamSprite() { if (camSprite) return camSprite; - camSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.CamsButton].Image; + camSprite = FastDestroyableSingleton.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.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image; return logSprite; } @@ -1378,6 +1372,7 @@ namespace TheOtherRoles 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; @@ -1387,7 +1382,10 @@ namespace TheOtherRoles 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(); @@ -1479,7 +1477,10 @@ namespace TheOtherRoles 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); @@ -1501,7 +1502,9 @@ namespace TheOtherRoles 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); diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index b978702..3ce2e4f 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,27 +1,21 @@  netstandard2.1 - 4.1.1 + 4.1.2 TheOtherRoles Eisbison latest - - - - $(DefineConstants);STEAM true - - - - + - - + + + diff --git a/TheOtherRoles/Utilities/EnumerationHelpers.cs b/TheOtherRoles/Utilities/EnumerationHelpers.cs new file mode 100644 index 0000000..fcb3f47 --- /dev/null +++ b/TheOtherRoles/Utilities/EnumerationHelpers.cs @@ -0,0 +1,84 @@ +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 GetFastEnumerator(this List list) where T : Il2CppSystem.Object => new Il2CppListEnumerable(list); +} + +public unsafe class Il2CppListEnumerable : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator 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 _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>(create, ptr); + _objFactory = lambda.Compile(); + } + + private readonly IntPtr _arrayPointer; + private readonly int _count; + private int _index = -1; + + public Il2CppListEnumerable(List 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 GetEnumerator() + { + return this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/TheOtherRoles/Utilities/FastDestroyableSingleton.cs b/TheOtherRoles/Utilities/FastDestroyableSingleton.cs new file mode 100644 index 0000000..c76284d --- /dev/null +++ b/TheOtherRoles/Utilities/FastDestroyableSingleton.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq.Expressions; +using UnityEngine; + +namespace TheOtherRoles.Utilities; + +public static unsafe class FastDestroyableSingleton where T : MonoBehaviour +{ + private static readonly IntPtr _fieldPtr; + private static readonly Func _createObject; + static FastDestroyableSingleton() + { + _fieldPtr = IL2CPP.GetIl2CppField(Il2CppClassPointerStore>.NativeClassPtr, nameof (DestroyableSingleton._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>(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.Instance; + return _createObject(objectPointer); + } + } +} \ No newline at end of file diff --git a/TheOtherRoles/Utilities/MapUtilities.cs b/TheOtherRoles/Utilities/MapUtilities.cs new file mode 100644 index 0000000..5729c02 --- /dev/null +++ b/TheOtherRoles/Utilities/MapUtilities.cs @@ -0,0 +1,62 @@ +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 _systems = new(); + public static Dictionary 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(); + } + } +} + +[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 diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..9a4ac66 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file