diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..212566614 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore index 375e88d6b..ed41176b8 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 c7907c9ba..188ba0b66 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 deb34a1e2..f05978987 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -6,6 +6,7 @@ using TheOtherRoles.Objects; using System.Linq; using System.Collections.Generic; +using TheOtherRoles.Utilities; namespace TheOtherRoles { @@ -48,9 +49,13 @@ static class HudManagerStartPatch 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 @@ public static void setCustomButtonCooldowns() { 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 @@ public static void setAllButtonsHandcuffedStatus(bool handcuffed, bool reset = f // 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) () => { 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 @@ public static void Postfix(HudManager __instance) ); // Morphling morph + morphlingButton = new CustomButton( () => { if (Morphling.sampledTarget != null) { @@ -418,10 +425,25 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) } 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) }, () => { 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) } } - 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 @@ public static void Postfix(HudManager __instance) 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 @@ public static void Postfix(HudManager __instance) () => { 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 @@ public static void Postfix(HudManager __instance) "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 833b4989f..fcfbfbeb9 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 @@ public class CustomOptionHolder { 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 @@ public static void Load() { 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 0594e870d..90611b3c6 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 ab8a14440..853afb22c 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -2,14 +2,13 @@ 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 @@ public static Texture2D loadTextureFromDisk(string path) { 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 @@ public static PlayerControl playerById(byte id) 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 @@ public static void handleVampireBiteOnBodyReport() { } 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 @@ public static void refreshRoleDescription(PlayerControl player) { } // 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 @@ public static bool canBeErased(this PlayerControl player) { 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 @@ public static KeyValuePair MaxPair(this Dictionary self, o 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 @@ public static void setLook(this PlayerControl target, String playerName, int col 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 @@ public static void setLook(this PlayerControl target, String playerName, int col 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 @@ public static void setLook(this PlayerControl target, String playerName, int col } 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 @@ public static void shareGameVersion() { 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 @@ public static List getKillerTeamMembers(PlayerControl player) { 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 000000000..34cd019a1 --- /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 ee4de2bc1..54a5ca343 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 @@ public class TheOtherRolesPlugin : BasePlugin 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 e2b165f79..06339bada 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 2631a797b..91ff50637 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 @@ static bool Prefix(ChatController __instance) { 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 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(0)] string pl [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 d0c9265bf..11919d64f 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 @@ public static void Postfix() { [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 ee49664fd..f30a2c22e 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 @@ private static Sprite CreateHatSprite(string path, bool fromDisk = false) { 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 @@ static void Postfix(HatParent __instance, string hatId, int color) { 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 @@ public static float createHatPackage(List> h 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 @@ public static void Postfix(HatsTab __instance) { 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 bfbdbc06b..bb909d6f5 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -7,7 +7,7 @@ using Hazel; using System.Reflection; using System.Text; -using static TheOtherRoles.TheOtherRoles; +using TheOtherRoles.Utilities; namespace TheOtherRoles { public class CustomOption { @@ -58,10 +58,10 @@ public static CustomOption Create(int id, CustomOptionType type, string name, st } 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 @@ public static void Postfix(KeyboardJoystick __instance) 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 152770d1a..727f86cb8 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 @@ static bool Prefix(ChatController __instance) { 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 e26c8f9e7..86f5712c1 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 @@ public class ModUpdateBehaviour : MonoBehaviour { public static readonly bool CheckForSubmergedUpdates = true; public static bool showPopUp = true; - + public static bool updateInProgress = false; + public static ModUpdateBehaviour Instance { get; private set; } public ModUpdateBehaviour(IntPtr ptr) : base(ptr) { } - public class UpdateData { public string Content; public string Tag; public JObject Request; + public Version Version => Version.Parse(Tag); public UpdateData(JObject data) { @@ -41,7 +47,7 @@ public UpdateData(JObject data) public bool IsNewer(Version version) { if (!Version.TryParse(Tag, out var myVersion)) return false; - return myVersion > version; + return myVersion.BaseVersion() > version.BaseVersion(); } } @@ -56,7 +62,7 @@ public void Awake() 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 @@ public void Awake() 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 @@ private void OnSceneLoaded(Scene scene, LoadSceneMode mode) 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 @@ private void OnSceneLoaded(Scene scene, LoadSceneMode mode) [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 @@ public static IEnumerator CoCheckUpdates() Instance.SubmergedUpdate = submergedUpdateCheck.Result; } } + + Instance.OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single); } [HideFromIl2Cpp] @@ -175,10 +189,38 @@ public async Task GetGithubUpdate(string owner, string repo) 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 @@ public async Task DownloadUpdate() 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 5324489ab..a2ec98f83 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 3fc312fd9..1619a3a33 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 @@ public Bloodytrail(PlayerControl player, PlayerControl bloodyPlayer) { 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 @@ public Bloodytrail(PlayerControl player, PlayerControl bloodyPlayer) { 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 ba7347a17..a547e6bdd 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 @@ public class CustomButton { 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 @@ public class CustomButton 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 @@ public CustomButton(Action OnClick, Func HasButton, Func CouldUse, A 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 @@ public void onClickEvent() { if (this.Timer < 0f && HasButton() && CouldUse()) { - actionButton.graphic.color = new Color(1f, 1f, 1f, 0.3f); + actionButtonRenderer.color = new Color(1f, 1f, 1f, 0.3f); this.OnClick(); // Deputy skip onClickEvent if handcuffed @@ -132,17 +140,20 @@ public static void ResetAllCooldowns() { 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 @@ public void Update() 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 @@ public void Update() 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 @@ public void Update() 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 ec1154291..b1b413737 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 @@ public class CustomMessage { 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 @@ public CustomMessage(string message, float duration) { 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 7eb18e57e..d5aa86c06 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 @@ public Footprint(float footprintDuration, bool anonymousFootprints, PlayerContro 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 @@ public Footprint(float footprintDuration, bool anonymousFootprints, PlayerContro 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 @@ public Footprint(float footprintDuration, bool anonymousFootprints, PlayerContro }))); } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Objects/Garlic.cs b/TheOtherRoles/Objects/Garlic.cs index 0812fe855..bc7d5aa7c 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 3195ff377..738800bef 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 @@ public static void startAnimation(int ventId) { 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 @@ public JackInTheBox(Vector2 p) { 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 87e3efe06..d2dddace6 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 @@ public static Sprite getTraceSprite() { } 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 @@ public NinjaTrace(Vector2 p, float duration=1f) { // 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 @@ public NinjaTrace(Vector2 p, float duration=1f) { 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 @@ public static void UpdateAll() { } } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Objects/Portal.cs b/TheOtherRoles/Objects/Portal.cs index 513ecb9d1..6ffd755cb 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 @@ public static void startTeleport(byte playerId) { } 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 @@ public Portal(Vector2 p) { 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 @@ public static void clearPortals() { } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/ClientOptionsPatch.cs b/TheOtherRoles/Patches/ClientOptionsPatch.cs index 5de2618f3..b34ddd564 100644 --- a/TheOtherRoles/Patches/ClientOptionsPatch.cs +++ b/TheOtherRoles/Patches/ClientOptionsPatch.cs @@ -2,9 +2,9 @@ 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 @@ private static void InitializeMoreButton(OptionsMenuBehaviour __instance) { 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 dde315922..c31d186b5 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 @@ public static class CredentialsPatch { 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 @@ static void Postfix(VersionShower __instance) { } [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] - private static class PingTrackerPatch + internal static class PingTrackerPatch { - private static GameObject modStamp; + public static GameObject modStamp; static void Prefix(PingTracker __instance) { if (modStamp == null) { modStamp = new GameObject("ModStamp"); @@ -54,7 +51,7 @@ static void Prefix(PingTracker __instance) { modStamp.transform.localScale *= 0.6f; } float offset = (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) ? 0.75f : 0f; - modStamp.transform.position = HudManager.Instance.MapButton.transform.position + Vector3.down * offset; + modStamp.transform.position = FastDestroyableSingleton.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 7045c49ab..26c7b5b3b 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 @@ public class OnGameEndPatch { 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 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref End 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 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref End 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 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref End // 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 @@ private static bool CheckAndEndGameForLawyerMeetingWin(ShipStatus __instance) { } 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 @@ private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) { 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 @@ private void GetPlayerCounts() { 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 @@ private void GetPlayerCounts() { TeamJackalHasAliveLover = jackalLover; } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 9ecc9feb6..7fa42fda9 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -2,15 +2,11 @@ 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 @@ public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref Ga 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 @@ public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref Ga 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 @@ public static void Postfix(AirshipExileController __instance) { // 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 @@ static void WrapUpPostfix(GameData.PlayerInfo exiled) { 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 @@ static void WrapUpPostfix(GameData.PlayerInfo exiled) { // 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 @@ static void WrapUpPostfix(GameData.PlayerInfo exiled) { // 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 3af6fe88e..1e4b0d410 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 @@ public static void Initialize() { 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 abcbe32d5..42cbb6932 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Hazel; using System; -using UnhollowerBaseLib; +using TheOtherRoles.Utilities; namespace TheOtherRoles.Patches { public class GameStartManagerPatch { @@ -36,7 +36,7 @@ public static void Postfix(GameStartManager __instance) { // 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 @@ public static bool Prefix(GameStartManager __instance) { 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 8689dcd4f..6334fa81b 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -5,19 +5,22 @@ 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 @@ public static void Prefix(IntroCutscene __instance) { // 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 @@ static public void SetRoleTexts(IntroCutscene __instance) { } 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 0112baac8..a38842151 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 cc7958081..0a7867c78 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 @@ class MapBehaviourPatch { [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 @@ static bool Prefix3(MapBehaviour __instance) { __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 e08a0522b..89f050c52 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -2,15 +2,12 @@ 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 @@ static bool Prefix(MeetingHud __instance, Il2CppStructArray.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 @@ static void guesserOnClick(int buttonTarget, MeetingHud __instance) { 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 @@ static void populateButtonsPostfix(MeetingHud __instance) { 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 @@ static void populateButtonsPostfix(MeetingHud __instance) { 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 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]GameData 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 dac61b6cc..9722ea426 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -3,11 +3,10 @@ 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 @@ public static class PlayerControlFixedUpdatePatch { 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 @@ static void setPlayerOutline(PlayerControl target, Color color) { // 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 @@ public static void bendTimeUpdate() { 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 @@ public static void bendTimeUpdate() { 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 @@ static void detectiveUpdateFootPrints() { 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 @@ static void deputyUpdate() 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 @@ static void engineerUpdate() { 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 @@ static void impostorSetTarget() { 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 @@ static void warlockSetTarget() { static void ninjaUpdate() { + if (Ninja.isInvisble && Ninja.invisibleTimer <= 0 && Ninja.ninja == PlayerControl.LocalPlayer) + { + MessageWriter invisibleWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetInvisible, Hazel.SendOption.Reliable, -1); + invisibleWriter.Write(Ninja.ninja.PlayerId); + invisibleWriter.Write(byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(invisibleWriter); + RPCProcedure.setInvisible(Ninja.ninja.PlayerId, byte.MaxValue); + } if (Ninja.arrow?.arrow != null) { if (Ninja.ninja == null || Ninja.ninja != PlayerControl.LocalPlayer || !Ninja.knowsTargetLocation) { @@ -422,7 +429,7 @@ static void trackerUpdate() { 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 @@ public static void playerSizeUpdate(PlayerControl p) { } 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 @@ public static void updatePlayerInfo() { 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 @@ public static void updatePlayerInfo() { } 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 @@ static void snitchUpdate() { } 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 @@ static void bountyHunterUpdate() { 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 @@ static void vultureUpdate() { } 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 @@ public static void miniCooldownUpdate() { 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 @@ public static void Postfix(PlayerControl __instance) { ninjaSetTarget(); NinjaTrace.UpdateAll(); ninjaUpdate(); - hackerUpdate(); swapperUpdate(); @@ -965,13 +970,13 @@ static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]GameData.Playe 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 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)]float ti 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 @@ public static void Postfix(PlayerControl __instance) 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 000000000..178e28af5 --- /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 aa009fcd2..bfa7be8a2 100644 --- a/TheOtherRoles/Patches/RegionMenuPatch.cs +++ b/TheOtherRoles/Patches/RegionMenuPatch.cs @@ -27,6 +27,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using UnityEngine; using UnityEngine.UI; using System; +using TheOtherRoles.Utilities; using UnityEngine.Events; namespace TheOtherRoles.Patches { @@ -37,7 +38,8 @@ public static class RegionMenuOpenPatch 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 bdce8c382..750cea42a 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 @@ public static void Postfix() { 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 @@ private static void assignRoleTargets(RoleAssignmentData data) { // 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 a5cdbb374..29f6fb53d 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 @@ public static bool Prefix(ref float __result, ShipStatus __instance, [HarmonyArg } 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 4bb2ca2a8..2d591d6b8 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 @@ static void setPlayerNameColor(PlayerControl p, Color color) { player.NameText.color = color; } - static void setNameColors() { - if (Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer) + static void setNameColors() + { + var localPlayer = PlayerControl.LocalPlayer; + if (Jester.jester != null && Jester.jester == localPlayer) setPlayerNameColor(Jester.jester, Jester.color); - else if (Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer) + else if (Mayor.mayor != null && Mayor.mayor == localPlayer) setPlayerNameColor(Mayor.mayor, Mayor.color); - else if (Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer) + else if (Engineer.engineer != null && Engineer.engineer == localPlayer) setPlayerNameColor(Engineer.engineer, Engineer.color); - else if (Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer) { + else if (Sheriff.sheriff != null && Sheriff.sheriff == localPlayer) { setPlayerNameColor(Sheriff.sheriff, Sheriff.color); if (Deputy.deputy != null && Deputy.knowsSheriff) { setPlayerNameColor(Deputy.deputy, Deputy.color); } - } else if (Deputy.deputy != null && Deputy.deputy == PlayerControl.LocalPlayer) { + } else if (Deputy.deputy != null && Deputy.deputy == localPlayer) { setPlayerNameColor(Deputy.deputy, Deputy.color); if (Sheriff.sheriff != null && Deputy.knowsSheriff) { setPlayerNameColor(Sheriff.sheriff, Sheriff.color); } - } else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == PlayerControl.LocalPlayer) + } else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == localPlayer) setPlayerNameColor(Portalmaker.portalmaker, Portalmaker.color); - else if (Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer) + else if (Lighter.lighter != null && Lighter.lighter == localPlayer) setPlayerNameColor(Lighter.lighter, Lighter.color); - else if (Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer) + else if (Detective.detective != null && Detective.detective == localPlayer) setPlayerNameColor(Detective.detective, Detective.color); - else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer) + else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == localPlayer) setPlayerNameColor(TimeMaster.timeMaster, TimeMaster.color); - else if (Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer) + else if (Medic.medic != null && Medic.medic == localPlayer) setPlayerNameColor(Medic.medic, Medic.color); - else if (Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer) + else if (Shifter.shifter != null && Shifter.shifter == localPlayer) setPlayerNameColor(Shifter.shifter, Shifter.color); - else if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer) + else if (Swapper.swapper != null && Swapper.swapper == localPlayer) setPlayerNameColor(Swapper.swapper, Swapper.color); - else if (Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer) + else if (Seer.seer != null && Seer.seer == localPlayer) setPlayerNameColor(Seer.seer, Seer.color); - else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer) + else if (Hacker.hacker != null && Hacker.hacker == localPlayer) setPlayerNameColor(Hacker.hacker, Hacker.color); - else if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer) + else if (Tracker.tracker != null && Tracker.tracker == localPlayer) setPlayerNameColor(Tracker.tracker, Tracker.color); - else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer) + else if (Snitch.snitch != null && Snitch.snitch == localPlayer) setPlayerNameColor(Snitch.snitch, Snitch.color); - else if (Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer) { + else if (Jackal.jackal != null && Jackal.jackal == localPlayer) { // Jackal can see his sidekick setPlayerNameColor(Jackal.jackal, Jackal.color); if (Sidekick.sidekick != null) { @@ -111,28 +115,28 @@ static void setNameColors() { 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 @@ static void setNameColors() { } // 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 @@ static void setNameColors() { 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 @@ static void updateShielded() { } 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 22865527b..3672fd77e 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -7,7 +7,7 @@ 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 @@ public static bool Prefix(Vent __instance, ref float __result, [HarmonyArgument( } // 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 @@ public static bool Prefix(Vent __instance) { [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 @@ static void Postfix() { 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 @@ static bool Prefix(MapCountOverlay __instance) { __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 @@ static bool Prefix(MapCountOverlay __instance) { 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 @@ static void Postfix(CounterArea __instance) { 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 @@ public static void Postfix(SurveillanceMinigame __instance) { // 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 @@ class SurveillanceMinigameUpdatePatch { 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 @@ static void Prefix(MedScanMinigame __instance) { } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 3247ca510..6b276d9f9 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -10,6 +10,7 @@ using System.Linq; using UnityEngine; using System; +using TheOtherRoles.Utilities; namespace TheOtherRoles { @@ -127,7 +128,8 @@ enum CustomRPC Bloody, SetFirstKill, Invert, - SetTiebreak + SetTiebreak, + SetInvisible } public static class RPCProcedure { @@ -160,7 +162,7 @@ public static void ShareOptions(int numberOfOptions, MessageReader reader) { } 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 @@ public static void forceEnd() { } 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 @@ public static void dynamicMapOption(byte mapId) { // 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 @@ public static void timeMasterRewindTime() { 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 @@ public static void timeMasterRewindTime() { 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 @@ public static void camouflagerCamouflage() { 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 @@ public static void vampireSetBitten(byte targetId, byte performReset) { } 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 @@ public static void placeGarlic(byte[] buff) { 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 @@ public static void jackalCreatesSidekick(byte targetId) { } 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 @@ public static void placeNinjaTrace(byte[] buff) { 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 @@ public static void placeCamera(byte[] buff) { 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 @@ public static void placeCamera(byte[] buff) { } 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 @@ public static void sealVent(int ventId) { 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 @@ public static void sealVent(int ventId) { 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 @@ public static void lawyerPromotesToPursuer() { 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 @@ public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedT 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 @@ static void Postfix([HarmonyArgument(0)]byte callId, [HarmonyArgument(1)]Message 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 000000000..cd1bba63d Binary files /dev/null and b/TheOtherRoles/Resources/MinusButton.png differ diff --git a/TheOtherRoles/Resources/MorphButton.png b/TheOtherRoles/Resources/MorphButton.png index e44ddc2aa..e68441d53 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 000000000..e0ae50c20 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 000000000..85a2e8eba --- /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 000000000..483e96278 Binary files /dev/null and b/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll differ diff --git a/TheOtherRoles/RoleInfo.cs b/TheOtherRoles/RoleInfo.cs index 384f30088..1d9a81ee3 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 2b4569ce8..08b5416c0 100644 --- a/TheOtherRoles/SubmergedCompatibility.cs +++ b/TheOtherRoles/SubmergedCompatibility.cs @@ -5,8 +5,9 @@ using BepInEx; using BepInEx.IL2CPP; using HarmonyLib; -using UnhollowerRuntimeLib; +using TheOtherRoles.Patches; using UnityEngine; +using Object = UnityEngine.Object; namespace TheOtherRoles { @@ -22,43 +23,16 @@ public static class Classes 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 @@ public static bool DisableO2MaskCheckForEmergency 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 @@ public static bool DisableO2MaskCheckForEmergency 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 @@ public static void Initialize() 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 @@ public static void RepairOxygen() { 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 640327e43..ff4b9f1b5 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 @@ public static Tuple taskInfo(GameData.PlayerInfo playerInfo) { 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 @@ public static Tuple taskInfo(GameData.PlayerInfo playerInfo) { [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 @@ private static bool Prefix(GameData __instance) { ) 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 368c0e666..0e5aaa321 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 @@ public static Sprite getUsePortalButtonSprite() { 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 @@ public static Sprite getButtonSprite() { 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 @@ public static Sprite getButtonSprite() { 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 @@ public static Sprite getPlaceCameraButtonSprite() { 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 @@ public static Sprite getSubmergedCentralLowerSealedSprite() { 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 @@ public static class Lawyer { 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 @@ public static Sprite getTargetSprite() { 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 @@ public static class Ninja { 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 @@ public static void clearAndReload() { 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 b97870263..3ce2e4f72 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 000000000..fcb3f475f --- /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 000000000..c76284d21 --- /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 000000000..5729c0260 --- /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 000000000..9a4ac6603 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file