From: Eisbison Date: Thu, 29 Apr 2021 15:48:33 +0000 (+0200) Subject: Version 2.5.1 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=188463e7749c04e864fe95a4908da762b0c2da66;p=rhonda%2Ftheotherroles.git Version 2.5.1 --- diff --git a/Images/Preview_2.5.0.png b/Images/Preview_2.5.0.png deleted file mode 100644 index 3c43d5d..0000000 Binary files a/Images/Preview_2.5.0.png and /dev/null differ diff --git a/Images/Preview_2.5.1.png b/Images/Preview_2.5.1.png new file mode 100644 index 0000000..c0684b3 Binary files /dev/null and b/Images/Preview_2.5.1.png differ diff --git a/README.md b/README.md index 72cf984..df97db9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Discord](https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg)](https://discord.gg/77RkMJHWsM) ![eisbison banner](./Images/Banner.png) -![eisbison infographic](./Images/Preview_2.5.0.png) +![eisbison infographic](./Images/Preview_2.5.1.png) # The Other Roles @@ -18,10 +18,11 @@ Even more roles are coming soon :) | [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | | | [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | | | [Janitor (Mafia)](#mafia) | [Medic](#medic) | | -| [Morphling](#morphling) | [Seer](#seer) | | -| [Trickster](#trickster) | [Sheriff](#sheriff) | | -| [Vampire](#vampire) | [Shifter](#shifter) | | -| [Warlock](#warlock) | [Snitch](#snitch) | | +| [Morphling](#morphling) | [Security Guard](#security-guard) | | +| [Trickster](#trickster) | [Seer](#seer) | | +| [Vampire](#vampire) | [Sheriff](#sheriff) | | +| [Warlock](#warlock) | [Shifter](#shifter) | | +| | [Snitch](#snitch) | | | | [Spy](#spy) | | | | [Swapper](#swapper) | | | | [Time Master](#time-master) | | @@ -32,6 +33,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2021.4.14s| v2.5.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.1/TheOtherRoles.zip) | 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip) | 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip) | 2021.4.14s| v2.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.3.0/TheOtherRoles.zip) @@ -56,6 +58,13 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 2.5.1** +- **New Hats:** We added the support for custom hats and there are already a few hats inside the game. We can add new hats without updating the mod and we're awaiting your hat designs on our discord server. +- Changed Lovers to ignore Lover's Tasks for Taskwin, while an ImpLover is alive +- Fixed a bug where garlic was not visible in some places +- The Security Guard can't place cameras on MiraHQ anymore +- Fixed a bug on there Airship, where the view of the cameras that the Security Guard placed wasn't centered on the camera. + **Version 2.5.0** - **New Role:** [Security Guard](#security-guard) - Fixed a bug where the game would stop after the first meeting @@ -514,6 +523,7 @@ The Lovers never know the role of their partner, they only see who their partner The Lovers win if they are both alive among the last 3 players, however they can also win with their respective role.\ If both Lovers are Crewmates, they can achieve a "double victory" but the Crewmates then also win.\ If one Lover is an Impostor and one a Crewmate, they can only get a "Lovers solo win" where only they win (even if there is another Impostor alive).\ +If there's an ImpLover in the game, the tasks of the Crewmate Lover won't be counted (for a task win) as long as he's alive. If the Lover dies, his tasks will also be counted.\ \ **NOTE:** - If there is an equal amount of Impostors and Crewmates left in the game and one of the Lovers is an Impostor, the game is not automatically over since the Lovers can still achieve a solo win. E.g. if there are the following roles Impostor + ImpLover + Lover + Crewmate left, the game will not end and the next kill will decide if the Impostors or Lovers win. @@ -876,6 +886,7 @@ The new camera will be visible after the next meeting and accessible by everyone The vents will be sealed after the next meeting, players can't enter or exit sealed vents, but they can still "move to them" underground.\ **NOTE:** - Tickster boxes can't be sealed +- The Security Guard can't place Cameras on MiraHQ - The remaining number of screws can be seen above his special button - On Skeld the four cameras will be replaced every 3 seconds (with the next four cameras). You can also navigate manually using the arrow keys. diff --git a/Source Code/Buttons.cs b/Source Code/Buttons.cs index fb54c6e..368d18b 100644 --- a/Source Code/Buttons.cs +++ b/Source Code/Buttons.cs @@ -682,7 +682,13 @@ namespace TheOtherRoles // SecurityGuard button securityGuardButton = new CustomButton( () => { - if (SecurityGuard.ventTarget == null) { // Place camera + if (SecurityGuard.ventTarget != null) { // Seal vent + MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SealVent, Hazel.SendOption.Reliable); + writer.WritePacked(SecurityGuard.ventTarget.Id); + writer.EndMessage(); + RPCProcedure.sealVent(SecurityGuard.ventTarget.Id); + SecurityGuard.ventTarget = null; + } else if (PlayerControl.GameOptions.MapId != 1) { // Place camera if there's no vent and it's not MiraHQ 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)); @@ -692,20 +698,17 @@ namespace TheOtherRoles writer.WriteBytesAndSize(buff); writer.EndMessage(); RPCProcedure.placeCamera(buff); - } else { // Seal vent - MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SealVent, Hazel.SendOption.Reliable); - writer.WritePacked(SecurityGuard.ventTarget.Id); - writer.EndMessage(); - RPCProcedure.sealVent(SecurityGuard.ventTarget.Id); - SecurityGuard.ventTarget = null; } securityGuardButton.Timer = securityGuardButton.MaxTimer; }, () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; }, () => { - securityGuardButton.killButtonManager.renderer.sprite = (SecurityGuard.ventTarget == null) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); + securityGuardButton.killButtonManager.renderer.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}"; - return SecurityGuard.remainingScrews >= (SecurityGuard.ventTarget == null ? SecurityGuard.camPrice : SecurityGuard.ventPrice) && PlayerControl.LocalPlayer.CanMove; + + if (SecurityGuard.ventTarget != null) + return SecurityGuard.remainingScrews >= SecurityGuard.ventPrice && PlayerControl.LocalPlayer.CanMove; + return PlayerControl.GameOptions.MapId != 1 && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove; }, () => { securityGuardButton.Timer = securityGuardButton.MaxTimer; }, SecurityGuard.getPlaceCameraButtonSprite(), diff --git a/Source Code/ChatCommands.cs b/Source Code/ChatCommands.cs index 930fa85..98d3daf 100644 --- a/Source Code/ChatCommands.cs +++ b/Source Code/ChatCommands.cs @@ -1,4 +1,6 @@ using System; +using System.Security.Cryptography; +using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.IL2CPP; @@ -15,18 +17,22 @@ namespace TheOtherRoles { string text = __instance.TextArea.text; bool handled = false; if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) { - if (text.ToLower().Equals("i'm a panda")) { - handled = true; - byte colorId = (byte)CustomColors.pickableColors; - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (text.ToLower().Equals("nightsky")) { - handled = true; - byte colorId = (byte)(CustomColors.pickableColors + 1); - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); + using(MD5 md5 = MD5.Create()) { + string hash = System.BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(text.ToLower()))).Replace("-", "").ToLowerInvariant(); + if (hash.Equals("f92af861c8b7aa5f2d05165abc9ba04f")) { + handled = true; + byte colorId = (byte)CustomColors.pickableColors; + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) + PlayerControl.LocalPlayer.CmdCheckColor(colorId); + } else if (hash.Equals("5a2ee6de2e42d2b326556640f838b0d7")) { + handled = true; + byte colorId = (byte)(CustomColors.pickableColors + 1); + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) + PlayerControl.LocalPlayer.CmdCheckColor(colorId); + } + System.Console.WriteLine(hash); } } if (handled) { diff --git a/Source Code/CustomHats.cs b/Source Code/CustomHats.cs index 2a1fb98..a5a29e9 100644 --- a/Source Code/CustomHats.cs +++ b/Source Code/CustomHats.cs @@ -1,15 +1,26 @@ +extern alias Il2CppNewtonsoft; using System; using BepInEx; using BepInEx.Configuration; using BepInEx.IL2CPP; +using Il2CppSystem; using HarmonyLib; using UnityEngine; -using System.Collections.Generic; +using UnhollowerBaseLib; using System.IO; using System.Reflection; using System.Collections; -using UnhollowerBaseLib; +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 Il2CppNewtonsoft::Newtonsoft.Json.Linq; +using Il2CppNewtonsoft::Newtonsoft.Json; namespace TheOtherRoles { [HarmonyPatch] @@ -17,14 +28,31 @@ namespace TheOtherRoles { private static bool LOADED = false; public static Material hatShader; - protected internal struct CustomHat { - public string name; - public string resource; - public string backresource; - public string climbresource; - public bool bounce; - public bool adaptive; - public bool behind; + public static Dictionary CustomHatRegistry = new Dictionary(); + + public class HatExtension { + public string author { get; set;} + public string package { get; set;} + public string condition { get; set;} + + public bool isUnlocked() { + if (condition == null || condition.ToLower() == "none") + return true; + return false; + } + } + + public class CustomHat { + public string author { get; set;} + public string package { get; set;} + public string condition { get; set;} + public string name { get; set;} + public string resource { get; set;} + public string backresource { get; set;} + public string climbresource { get; set;} + public bool bounce { get; set;} + public bool adaptive { get; set;} + public bool behind { get; set;} } private static List createCustomHatDetails(string[] hats, bool fromDisk = false) { @@ -44,7 +72,6 @@ namespace TheOtherRoles { climbs.Add(p[0], hats[i]); if (options.Contains("back")) backs.Add(p[0], hats[i]); - if (!options.Contains("back") && !options.Contains("climb")) { CustomHat custom = new CustomHat { resource = hats[i] }; custom.name = p[0].Replace('-', ' '); @@ -60,10 +87,16 @@ namespace TheOtherRoles { foreach (string k in fronts.Keys) { CustomHat hat = fronts[k]; - backs.TryGetValue(k, out hat.backresource); - climbs.TryGetValue(k, out hat.climbresource); + string br, cr; + backs.TryGetValue(k, out br); + climbs.TryGetValue(k, out cr); + if (br != null) + hat.backresource = br; + if (cr != null) + hat.climbresource = cr; if (hat.backresource != null) hat.behind = true; + customhats.Add(hat); } @@ -75,11 +108,22 @@ namespace TheOtherRoles { return texture == null ? null : Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.8f), texture.width * 0.75f); } - private static HatBehaviour CreateHatBehaviour(CustomHat ch, bool fromDisk = false) { + private static HatBehaviour CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) { + if (hatShader == null && DestroyableSingleton.InstanceExists) { + foreach (HatBehaviour h in DestroyableSingleton.Instance.AllHats) { + if (h.AltShader != null) { + hatShader = h.AltShader; + break; + } + } + } + HatBehaviour hat = new HatBehaviour(); hat.MainImage = CreateHatSprite(ch.resource, fromDisk); - if (ch.backresource != null) + if (ch.backresource != null) { hat.BackImage = CreateHatSprite(ch.backresource, fromDisk); + ch.behind = true; // Required to view backresource + } if (ch.climbresource != null) hat.ClimbImage = CreateHatSprite(ch.climbresource, fromDisk); hat.name = ch.name; @@ -87,23 +131,31 @@ namespace TheOtherRoles { hat.ProductId = "hat_" + ch.name.Replace(' ', '_'); hat.InFront = !ch.behind; hat.NoBounce = !ch.bounce; - hat.ChipOffset = new Vector2(0f, 0.35f); + hat.ChipOffset = new Vector2(0f, 0.2f); if (ch.adaptive && hatShader != null) hat.AltShader = hatShader; + if (!testOnly) { + HatExtension extend = new HatExtension(); + extend.author = ch.author != null ? ch.author : "Unknown"; + extend.package = ch.package != null ? ch.package : "Misc."; + extend.condition = ch.condition != null ? ch.condition : "none"; + + CustomHatRegistry.Add(hat.name, extend); + } + return hat; } - private static void loadHatShader() { - if (hatShader != null || !DestroyableSingleton.InstanceExists) return; - - foreach (HatBehaviour hat in DestroyableSingleton.Instance.AllHats) { - if (hat.AltShader != null) { - hatShader = hat.AltShader; - break; - } - } + private static HatBehaviour CreateHatBehaviour(CustomHatLoader.CustomHatOnline chd) { + string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; + chd.resource = filePath + chd.resource; + if (chd.backresource != null) + chd.backresource = filePath + chd.backresource; + if (chd.climbresource != null) + chd.climbresource = filePath + chd.climbresource; + return CreateHatBehaviour(chd, true); } [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))] @@ -111,8 +163,6 @@ namespace TheOtherRoles { static bool Prefix(HatManager __instance) { try { if (!LOADED) { - loadHatShader(); - Assembly assembly = Assembly.GetExecutingAssembly(); string hatres = $"{assembly.GetName().Name}.Resources.CustomHats"; string[] hats = (from r in assembly.GetManifestResourceNames() @@ -123,17 +173,22 @@ namespace TheOtherRoles { foreach (CustomHat ch in customhats) __instance.AllHats.Add(CreateHatBehaviour(ch)); + while (CustomHatLoader.hatdetails.Count > 0) { + __instance.AllHats.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0])); + CustomHatLoader.hatdetails.RemoveAt(0); + } + LOADED = true; } return true; - } catch (Exception e) { + } catch (System.Exception e) { System.Console.WriteLine("Unable to add Custom Hats\n" + e); return false; } } } - [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), new Type[] { typeof(uint), typeof(int) })] + [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), new System.Type[] { typeof(uint), typeof(int) })] private static class HatParentSetHatPatch { static void Postfix(HatParent __instance, [HarmonyArgument(0)]uint hatId, [HarmonyArgument(1)]int color) { if (DestroyableSingleton.InstanceExists) { @@ -141,17 +196,281 @@ namespace TheOtherRoles { string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test"; DirectoryInfo d = new DirectoryInfo(filePath); string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Text files - List customHats = createCustomHatDetails(filePaths, true); - if (customHats.Count > 0) { - loadHatShader(); - __instance.Hat = CreateHatBehaviour(customHats[0], true); + List hats = createCustomHatDetails(filePaths, true); + if (hats.Count > 0) { + __instance.Hat = CreateHatBehaviour(hats[0], true, true); __instance.SetHat(color); } - } catch (Exception e) { + } catch (System.Exception e) { System.Console.WriteLine("Unable to create test hat\n" + e); } } } } + + private static List hatsTabCustomTexts = new List(); + + [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.OnEnable))] + public class HatsTabOnEnablePatch { + public static string innerslothPackageName = "Innersloth Hats"; + private static TMPro.TMP_Text textTemplate; + + public static float createHatPackage(List> hats, string packageName, float YStart, HatsTab __instance) { + bool isDefaultPackage = innerslothPackageName == packageName; + float offset = YStart; + + if (textTemplate != null) { + TMPro.TMP_Text title = UnityEngine.Object.Instantiate(textTemplate, __instance.scroller.Inner); + title.transform.localPosition = new Vector3(2.25f, YStart, -1f); + title.transform.localScale = Vector3.one * 1.5f; + // title.currentFontSize + title.fontSize *= 0.5f; + title.enableAutoSizing = false; + __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { title.SetText(packageName); }))); + offset -= 0.8f * __instance.YOffset; + hatsTabCustomTexts.Add(title); + } + for (int i = 0; i < hats.Count; i++) { + HatBehaviour hat = hats[i].Item1; + HatExtension ext = hats[i].Item2; + + float xpos = __instance.XRange.Lerp((i % __instance.NumPerRow) / (__instance.NumPerRow - 1f)); + float ypos = offset - i / __instance.NumPerRow * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset; + ColorChip colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); + if (ext != null) { + Transform background = colorChip.transform.FindChild("Background"); + Transform foreground = colorChip.transform.FindChild("ForeGround"); + + if (background != null) { + background.localScale = new Vector3(1, 1.5f, 1); + background.localPosition = Vector3.down * 0.243f; + } + if (foreground != null) { + foreground.localPosition = Vector3.down * 0.243f; + } + + if (textTemplate != null) { + TMPro.TMP_Text description = UnityEngine.Object.Instantiate(textTemplate, colorChip.transform); + description.transform.localPosition = new Vector3(0f, -0.75f, -1f); + description.transform.localScale = Vector3.one * 0.7f; + __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { description.SetText($"{hat.name}\nby {ext.author}"); }))); + hatsTabCustomTexts.Add(description); + } + + if (!ext.isUnlocked()) { // Hat is locked + UnityEngine.Object.Destroy(colorChip.Button); + var overlay = UnityEngine.Object.Instantiate(colorChip.InUseForeground, colorChip.transform); + overlay.SetActive(true); + } + } + + colorChip.transform.localPosition = new Vector3(xpos, ypos, -1f); + colorChip.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { __instance.SelectHat(hat); })); + colorChip.Inner.SetHat(hat, PlayerControl.LocalPlayer.Data.ColorId); + colorChip.Inner.transform.localPosition = hat.ChipOffset; + colorChip.Tag = hat; + __instance.ColorChips.Add(colorChip); + } + return offset - ((hats.Count - 1) / __instance.NumPerRow * (isDefaultPackage ? 1f : 1.5f) + 0.7f) * __instance.YOffset; + } + + public static bool Prefix(HatsTab __instance) { + PlayerControl.SetPlayerMaterialColors(PlayerControl.LocalPlayer.Data.ColorId, __instance.DemoImage); + __instance.HatImage.SetHat(SaveManager.LastHat, PlayerControl.LocalPlayer.Data.ColorId); + PlayerControl.SetSkinImage(SaveManager.LastSkin, __instance.SkinImage); + PlayerControl.SetPetImage(SaveManager.LastPet, PlayerControl.LocalPlayer.Data.ColorId, __instance.PetImage); + + HatBehaviour[] unlockedHats = DestroyableSingleton.Instance.GetUnlockedHats(); + Dictionary>> packages = new Dictionary>>(); + hatsTabCustomTexts = new List(); + + foreach (HatBehaviour hatBehaviour in unlockedHats) { + HatExtension ext = hatBehaviour.getHatExtension(); + + if (ext != null) { + if (!packages.ContainsKey(ext.package)) + packages[ext.package] = new List>(); + packages[ext.package].Add(new System.Tuple(hatBehaviour, ext)); + } else { + if (!packages.ContainsKey(innerslothPackageName)) + packages[innerslothPackageName] = new List>(); + packages[innerslothPackageName].Add(new System.Tuple(hatBehaviour, null)); + } + } + + float YOffset = __instance.YStart; + + var hatButton = GameObject.Find("HatButton"); + + if (hatButton != null && hatButton.transform.FindChild("ButtonText_TMP") != null) { + textTemplate = hatButton.transform.FindChild("ButtonText_TMP").GetComponent(); + } + + var orderedKeys = packages.Keys.OrderBy((string x) => x == innerslothPackageName ? 1000 : 0); + foreach (string key in orderedKeys) { + List> value = packages[key]; + YOffset += createHatPackage(value, key, YOffset, __instance); + } + __instance.scroller.YBounds.max = (YOffset - 0.6f) * -0.75f; + return false; + } + } + + [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))] + public class HatsTabUpdatePatch { + public static void Postfix(HatsTab __instance) { + // Manually hide all custom TMPro.TMP_Text objects that are outside the ScrollRect + foreach (TMPro.TMP_Text customText in hatsTabCustomTexts) { + if (customText != null && customText.transform != null && customText.gameObject != null) { + bool active = customText.transform.position.y <= 3.75f && customText.transform.position.y >= 0.3f; + float epsilon = Mathf.Min(Mathf.Abs(customText.transform.position.y - 3.75f), Mathf.Abs(customText.transform.position.y - 0.35f)); + if (active != customText.gameObject.active && epsilon > 0.1f) customText.gameObject.SetActive(active); + } + } + } + } + } + + public class CustomHatLoader { + public bool running = false; + private const string REPO = "https://raw.githubusercontent.com/Eisbison/TheOtherHats/master"; + + public static List hatdetails = new List(); + + public void LaunchHatFetcher() { + if (this.running) + return; + this.running = true; + this.LaunchHatFetcherAsync(); + } + + private async void LaunchHatFetcherAsync() { + try { + HttpStatusCode status = await this.FetchHats(); + if (status != HttpStatusCode.OK) + System.Console.WriteLine("Custom Hats could not be loaded\n"); + } catch (System.Exception e) { + System.Console.WriteLine("Unable to fetch hats\n" + e.Message); + } + this.running = false; + } + + private string sanitizeResourcePath(string res) { + if (res == null || !res.EndsWith(".png")) + return null; + + res = res.Replace("\\", "") + .Replace("/", "") + .Replace("*", "") + .Replace("..", ""); + return res; + } + + public async Task FetchHats() { + using (HttpClient http = new HttpClient()) { + http.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue{ NoCache = true }; + using (HttpRequestMessage msg = new HttpRequestMessage()) { + msg.Method = HttpMethod.Get; + msg.RequestUri = new System.Uri($"{REPO}/CustomHats.json"); + HttpResponseMessage httpResponseMessage = await http.SendAsync(msg); + using (HttpResponseMessage res = httpResponseMessage) { + try { + if (res.Content == null) { + System.Console.WriteLine("Server returned no data: " + res.StatusCode.ToString()); + return HttpStatusCode.ExpectationFailed; + } + if (res.StatusCode != HttpStatusCode.OK) + return res.StatusCode; + + string json = await res.Content.ReadAsStringAsync(); + // System.Console.WriteLine(json); + + JToken jobj = JObject.Parse(json)["hats"]; + if (!jobj.HasValues) + return HttpStatusCode.ExpectationFailed; + + List hatdatas = new List(); + + for (JToken current = jobj.First; current != null; current = current.Next) { + if (current.HasValues) { + CustomHatOnline info = new CustomHatOnline(); + + info.name = current["name"]?.ToString(); + info.resource = sanitizeResourcePath(current["resource"]?.ToString()); + if (info.resource == null || info.name == null) // required + continue; + info.reshasha = current["reshasha"]?.ToString(); + info.backresource = sanitizeResourcePath(current["backresource"]?.ToString()); + info.reshashb = current["reshashb"]?.ToString(); + info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString()); + info.reshashc = current["reshashc"]?.ToString(); + info.author = current["author"]?.ToString(); + info.package = current["package"]?.ToString(); + info.condition = current["condition"]?.ToString(); + info.bounce = current["bounce"] != null; + info.adaptive = current["adaptive"] != null; + info.behind = current["behind"] != null; + hatdatas.Add(info); + } + } + + List markedfordownload = new List(); + + string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; + using (MD5 md5 = MD5.Create()) { + foreach (CustomHatOnline data in hatdatas) { + if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5)) + markedfordownload.Add(data.resource); + if (data.backresource != null && doesResourceRequireDownload(filePath + data.backresource, data.reshashb, md5)) + markedfordownload.Add(data.backresource); + if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5)) + markedfordownload.Add(data.climbresource); + } + } + markedfordownload.AsParallel().ForAll(file => { + System.Uri url = new System.Uri($"{REPO}/hats/{file}"); + // System.Console.WriteLine($"Downloading {file}"); + var fres = http.GetAsync(url, HttpCompletionOption.ResponseContentRead); + if (fres.Result.StatusCode != HttpStatusCode.OK) + return; + using (var resStream = fres.Result.Content.ReadAsStreamAsync().Result) { + using (var fileStream = File.Create($"{filePath}\\{file}")) { + resStream.CopyTo(fileStream); + } + } + }); + + hatdetails = hatdatas; + } catch (System.Exception ex) { + System.Console.WriteLine(ex); + } + } + } + } + return HttpStatusCode.OK; + } + + private bool doesResourceRequireDownload(string respath, string reshash, MD5 md5) { + if (reshash == null || !File.Exists(respath)) + return true; + + using (var stream = File.OpenRead(respath)) { + var hash = System.BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); + return !reshash.Equals(hash); + } + } + + public class CustomHatOnline : CustomHats.CustomHat { + public string reshasha { get; set;} + public string reshashb { get; set;} + public string reshashc { get; set;} + } + } + public static class CustomHatExtensions { + public static CustomHats.HatExtension getHatExtension(this HatBehaviour hat) { + CustomHats.HatExtension ret = null; + CustomHats.CustomHatRegistry.TryGetValue(hat.name, out ret); + return ret; + } } -} \ No newline at end of file +} diff --git a/Source Code/Garlic.cs b/Source Code/Garlic.cs index e47b3e0..e988bd4 100644 --- a/Source Code/Garlic.cs +++ b/Source Code/Garlic.cs @@ -28,10 +28,10 @@ namespace TheOtherRoles{ garlic = new GameObject("Garlic"); background = new GameObject("Background"); background.transform.SetParent(garlic.transform); - Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f); + Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player garlic.transform.position = position; garlic.transform.localPosition = position; - background.transform.localPosition = new Vector3(0 , 0, 0.01f); + background.transform.localPosition = new Vector3(0 , 0, -0.01f); // before player var garlicRenderer = garlic.AddComponent(); garlicRenderer.sprite = getGarlicSprite(); diff --git a/Source Code/Main.cs b/Source Code/Main.cs index a20402a..6483940 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -19,10 +19,10 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string Version = "2.5.0"; + public const string Version = "2.5.1"; public const byte Major = 2; public const byte Minor = 5; - public const byte Patch = 0; + public const byte Patch = 1; public Harmony Harmony { get; } = new Harmony(Id); public static TheOtherRolesPlugin Instance; @@ -52,6 +52,8 @@ namespace TheOtherRoles } public override void Load() { + new CustomHatLoader().LaunchHatFetcher(); + DebugMode = Config.Bind("Custom", "Enable Debug Mode", false); StreamerMode = Config.Bind("Custom", "Enable Streamer Mode", false); GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true); diff --git a/Source Code/RPC.cs b/Source Code/RPC.cs index 3d62221..6dcdfcd 100644 --- a/Source Code/RPC.cs +++ b/Source Code/RPC.cs @@ -614,7 +614,8 @@ namespace TheOtherRoles var camera = UnityEngine.Object.Instantiate(referenceCamera); camera.transform.position = new Vector3(position.x, position.y, referenceCamera.transform.position.z - 1f); - camera.CamName = $"Security Guard Camera {SecurityGuard.placedCameras}"; + camera.CamName = $"Security Camera {SecurityGuard.placedCameras}"; + 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 camera.gameObject.SetActive(false); MapOptions.camerasToAdd.Add(camera); diff --git a/Source Code/TasksHandler.cs b/Source Code/TasksHandler.cs index af1ebf3..43d24fb 100644 --- a/Source Code/TasksHandler.cs +++ b/Source Code/TasksHandler.cs @@ -15,7 +15,7 @@ namespace TheOtherRoles { playerInfo.Object && (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) && !playerInfo.IsImpostor && - !Helpers.hasFakeTasks(playerInfo.Object) + !playerInfo.Object.hasFakeTasks() ) { for (int j = 0; j < playerInfo.Tasks.Count; j++) { @@ -35,6 +35,8 @@ namespace TheOtherRoles { __instance.CompletedTasks = 0; for (int i = 0; i < __instance.AllPlayers.Count; i++) { GameData.PlayerInfo playerInfo = __instance.AllPlayers[i]; + if (playerInfo.Object && playerInfo.Object.hasAliveKillingLover()) + continue; var (playerCompleted, playerTotal) = taskInfo(playerInfo); __instance.TotalTasks += playerTotal; __instance.CompletedTasks += playerCompleted; diff --git a/Source Code/TheOtherRoles.cs b/Source Code/TheOtherRoles.cs index 8c43366..28519b9 100644 --- a/Source Code/TheOtherRoles.cs +++ b/Source Code/TheOtherRoles.cs @@ -322,6 +322,16 @@ namespace TheOtherRoles return !(lover1.Data.IsImpostor || lover2.Data.IsImpostor || lover1 == Jackal.jackal || lover2 == Jackal.jackal || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick); // Not Impostor, Sidekick or Jackal } + public static bool hasAliveKillingLover(this PlayerControl player) { + if (!Lovers.existingAndAlive()) + return false; + if (player != null && player != lover1 && player != lover2) // Is Player a Lover? + return false; + return (lover1 == Jackal.jackal || lover2 == Jackal.jackal + || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick + || lover1.Data.IsImpostor || lover2.Data.IsImpostor); // Either of the lovers is a killer + } + public static void clearAndReload() { lover1 = null; lover2 = null; @@ -758,7 +768,7 @@ namespace TheOtherRoles public static void clearAndReload() { cleaner = null; - cooldown = CustomOptionHolder.janitorCooldown.getFloat(); + cooldown = CustomOptionHolder.cleanerCooldown.getFloat(); } } @@ -861,4 +871,4 @@ namespace TheOtherRoles ventPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardVentPrice.getFloat()); } } -} \ No newline at end of file +} diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index 88a74b4..4aa7a90 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 2.5.0 + 2.5.1 TheOtherRoles Eisbison @@ -10,6 +10,7 @@ 2021.4.14s EoF-1141/Mappings:0.4.1 $(DefineConstants);STEAM + true @@ -25,4 +26,11 @@ - + + + + $(AmongUs)\BepInEx\unhollowed\Newtonsoft.Json.dll + Il2CppNewtonsoft + + + \ No newline at end of file