From fcea383739d194683a135bf5a65d03e54cb55a7b Mon Sep 17 00:00:00 2001 From: msk <15199219+muskit@users.noreply.github.com> Date: Wed, 26 Jan 2022 01:23:39 -0800 Subject: [PATCH] win/lose hold stats, hp crystal expiration --- Assets/MeatKit/BuildProfile.asset | 2 +- Assets/MeatKit/MeatKitPlugin.cs | 19 +- Assets/README.md | 7 +- Assets/_Prefabs/HealthCrystalTimer.prefab | 187 ++++++++++++++++++ .../_Prefabs/HealthCrystalTimer.prefab.meta | 9 + Assets/_Prefabs/HoldCounter.prefab | 94 ++++++++- .../_Prefabs/PlayerCount_LoadingText.prefab | 4 +- Assets/_Prefabs/Store.asset | 1 + Assets/_Scripts/HoldCounter.cs | 41 +++- Assets/_Scripts/InPlay.cs | 7 +- Assets/_Scripts/LeaderboardPlayerCount.cs | 52 ++--- .../_Scripts/LeaderboardPlayerCountPatch.cs | 17 +- Assets/_Scripts/MeshRendererFlicker.cs | 64 ++++++ Assets/_Scripts/MeshRendererFlicker.cs.meta | 12 ++ Assets/_Scripts/TimedHealthCrystalPatch.cs | 57 ++++++ .../_Scripts/TimedHealthCrystalPatch.cs.meta | 12 ++ Assets/_Scripts/TokenCounter.cs | 2 - Assets/_Scripts/UIRingTimer.cs | 33 ++++ Assets/_Scripts/UIRingTimer.cs.meta | 12 ++ Assets/_Textures.meta | 9 + Assets/_Textures/White Ring.png | Bin 0 -> 10035 bytes Assets/_Textures/White Ring.png.meta | 76 +++++++ 22 files changed, 657 insertions(+), 60 deletions(-) create mode 100644 Assets/_Prefabs/HealthCrystalTimer.prefab create mode 100644 Assets/_Prefabs/HealthCrystalTimer.prefab.meta create mode 100644 Assets/_Scripts/MeshRendererFlicker.cs create mode 100644 Assets/_Scripts/MeshRendererFlicker.cs.meta create mode 100644 Assets/_Scripts/TimedHealthCrystalPatch.cs create mode 100644 Assets/_Scripts/TimedHealthCrystalPatch.cs.meta create mode 100644 Assets/_Scripts/UIRingTimer.cs create mode 100644 Assets/_Scripts/UIRingTimer.cs.meta create mode 100644 Assets/_Textures.meta create mode 100644 Assets/_Textures/White Ring.png create mode 100644 Assets/_Textures/White Ring.png.meta diff --git a/Assets/MeatKit/BuildProfile.asset b/Assets/MeatKit/BuildProfile.asset index eec7587..afcbe6f 100644 --- a/Assets/MeatKit/BuildProfile.asset +++ b/Assets/MeatKit/BuildProfile.asset @@ -13,7 +13,7 @@ MonoBehaviour: m_EditorClassIdentifier: PackageName: TNH_Quality_of_Life_Improvements Author: muskit - Version: 1.0.1 + Version: 1.1.0 Icon: {fileID: 2800000, guid: 785b7946398f5314b95bf593d2d77d67, type: 3} ReadMe: {fileID: 102900000, guid: ab1d6dea017447a48ac348db588a6f35, type: 3} WebsiteURL: https://github.com/muskit/TNH-Quality-of-Life-Improvements diff --git a/Assets/MeatKit/MeatKitPlugin.cs b/Assets/MeatKit/MeatKitPlugin.cs index 88c6a61..1bbc749 100644 --- a/Assets/MeatKit/MeatKitPlugin.cs +++ b/Assets/MeatKit/MeatKitPlugin.cs @@ -1,4 +1,5 @@ #if H3VR_IMPORTED +using HarmonyLib; using System.IO; using System.Reflection; using BepInEx; @@ -42,10 +43,11 @@ public class MeatKitPlugin : BaseUnityPlugin private static InPlay instance; - private LeaderboardPlayerCountPatch lpc; private bool lpcModGone = false; private float lpcModSearchTimeEnd; + private Harmony harmony; + private void SceneChanged(Scene from, Scene to) { //Logger.LogInfo(string.Format("scene chg: {0} --> {1}", from.name, to.name)); @@ -63,6 +65,11 @@ public class MeatKitPlugin : BaseUnityPlugin } } + public MeatKitPlugin(): base() + { + harmony = new Harmony("muskit.TNHQualityOfLifeImprovements"); + } + private void Awake() { // load asset bundle @@ -96,11 +103,17 @@ public class MeatKitPlugin : BaseUnityPlugin true, "Shows how many holds the player has completed by their radar hand."); + // patch KillAll code (only acts w/ health crystals) + TimedHealthCrystalPatch.Patch(harmony); + // patch leaderboard code if (cfgShowLPC.Value) - lpc = new LeaderboardPlayerCountPatch(); + LeaderboardPlayerCountPatch.Patch(harmony); + + if(cfgShowHolds.Value) + HoldCounter.Patch(harmony); - // give 120 seconds to search for old mod + // give 120 seconds to search for old mod, which we want to kill lpcModSearchTimeEnd = Time.realtimeSinceStartup + 120; } // DO NOT EDIT. diff --git a/Assets/README.md b/Assets/README.md index f1f38c3..3083209 100644 --- a/Assets/README.md +++ b/Assets/README.md @@ -12,12 +12,15 @@ Enable/disable these features in your mod manager's *Config editor*. For any issues/ideas, please create an issue on the GitHub repo (linked on Thunderstore page). ## Changelog +1.1.0 +* Added Health Crystals expiration indicator + 1.0.1 -* Fixed the in-play improvements only applying in Classic Hallways map (whoops!!) +* Fixed the in-play improvements only applying to Classic Hallways map (whoops!!) * Added option to enable/disable showing player count of online leaderboards * Added option to enable/disable HP text opacity/shadow change * (Surprisingly, the HP text normally doesn't have full opacity) -* Searching for TNH Leaderboards Player Count now stops after 120s +* Searching for the deprecated TNH Leaderboards Player Count mod to kill now stops after 120s 1.0.0 diff --git a/Assets/_Prefabs/HealthCrystalTimer.prefab b/Assets/_Prefabs/HealthCrystalTimer.prefab new file mode 100644 index 0000000..2f90aec --- /dev/null +++ b/Assets/_Prefabs/HealthCrystalTimer.prefab @@ -0,0 +1,187 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &100100000 +Prefab: + m_ObjectHideFlags: 1 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: [] + m_RemovedComponents: [] + m_ParentPrefab: {fileID: 0} + m_RootGameObject: {fileID: 1634027973393822} + m_IsPrefabParent: 1 +--- !u!1 &1475934434838188 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 5 + m_Component: + - component: {fileID: 224853189626207054} + - component: {fileID: 222684863573680672} + - component: {fileID: 114673309682266402} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!1 &1634027973393822 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 5 + m_Component: + - component: {fileID: 224077453978722080} + - component: {fileID: 223544683124447678} + - component: {fileID: 114094006499494552} + - component: {fileID: 114774830210974956} + - component: {fileID: 114654661998195444} + m_Layer: 5 + m_Name: HealthCrystalTimer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &114094006499494552 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1634027973393822} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1980459831, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!114 &114654661998195444 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1634027973393822} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c0cc182175a3417499d53e43378f028c, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &114673309682266402 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1475934434838188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 21300000, guid: 3677786155d617b4cb39bf42eddd24ad, type: 3} + m_Type: 3 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 0 + m_FillOrigin: 2 +--- !u!114 &114774830210974956 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1634027973393822} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1301386320, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!222 &222684863573680672 +CanvasRenderer: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1475934434838188} +--- !u!223 &223544683124447678 +Canvas: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1634027973393822} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 2 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &224077453978722080 +RectTransform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1634027973393822} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 224853189626207054} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 574, y: 308.5} + m_SizeDelta: {x: 468, y: 472} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!224 &224853189626207054 +RectTransform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1475934434838188} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 224077453978722080} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 468, y: 472} + m_Pivot: {x: 0.5, y: 0.5} diff --git a/Assets/_Prefabs/HealthCrystalTimer.prefab.meta b/Assets/_Prefabs/HealthCrystalTimer.prefab.meta new file mode 100644 index 0000000..5f1abd6 --- /dev/null +++ b/Assets/_Prefabs/HealthCrystalTimer.prefab.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a97af7648bcd6394b867989bf8fb9ed0 +timeCreated: 1643099434 +licenseType: Free +NativeFormatImporter: + mainObjectFileID: 100100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Prefabs/HoldCounter.prefab b/Assets/_Prefabs/HoldCounter.prefab index 8e771c3..95ba49b 100644 --- a/Assets/_Prefabs/HoldCounter.prefab +++ b/Assets/_Prefabs/HoldCounter.prefab @@ -66,6 +66,24 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 +--- !u!1 &1774018057659698 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 5 + m_Component: + - component: {fileID: 224420167260936368} + - component: {fileID: 222388691975761020} + - component: {fileID: 114828117127462556} + - component: {fileID: 114042133408737726} + m_Layer: 5 + m_Name: Text (2) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 --- !u!114 &114032145135548160 MonoBehaviour: m_ObjectHideFlags: 1 @@ -99,6 +117,20 @@ MonoBehaviour: m_VerticalOverflow: 0 m_LineSpacing: 1 m_Text: HOLDS COMPLETED +--- !u!114 &114042133408737726 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1774018057659698} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1573420865, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_EffectColor: {r: 0, g: 0, b: 0, a: 0.734} + m_EffectDistance: {x: 3.5, y: -3.5} + m_UseGraphicAlpha: 1 --- !u!114 &114133878122943296 MonoBehaviour: m_ObjectHideFlags: 1 @@ -192,6 +224,39 @@ MonoBehaviour: m_VerticalOverflow: 0 m_LineSpacing: 1 m_Text: "-1 / \u221E" +--- !u!114 &114828117127462556 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1774018057659698} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 12800000, guid: 05d48c500227c8a4bbb7c02e3ccbb0b3, type: 3} + m_FontSize: 26 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 100 + m_Alignment: 7 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 0 0 --- !u!114 &114972162530505676 MonoBehaviour: m_ObjectHideFlags: 1 @@ -208,6 +273,12 @@ MonoBehaviour: m_BlockingMask: serializedVersion: 2 m_Bits: 4294967295 +--- !u!222 &222388691975761020 +CanvasRenderer: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1774018057659698} --- !u!222 &222541359112789466 CanvasRenderer: m_ObjectHideFlags: 1 @@ -255,8 +326,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: -45} - m_SizeDelta: {x: 0, y: -90} + m_AnchoredPosition: {x: 0, y: -12.550003} + m_SizeDelta: {x: 0, y: -100.3} m_Pivot: {x: 0.5, y: 0.5} --- !u!224 &224364969672532764 RectTransform: @@ -276,6 +347,24 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 105.9} m_SizeDelta: {x: 0, y: -211.8} m_Pivot: {x: 0.5, y: 0.5} +--- !u!224 &224420167260936368 +RectTransform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 1774018057659698} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 224724389550513542} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 308.5, y: 64.57} + m_Pivot: {x: 0.5, y: 0} --- !u!224 &224724389550513542 RectTransform: m_ObjectHideFlags: 1 @@ -288,6 +377,7 @@ RectTransform: m_Children: - {fileID: 224364969672532764} - {fileID: 224142695019321682} + - {fileID: 224420167260936368} m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} diff --git a/Assets/_Prefabs/PlayerCount_LoadingText.prefab b/Assets/_Prefabs/PlayerCount_LoadingText.prefab index 9b01004..b3c4779 100644 --- a/Assets/_Prefabs/PlayerCount_LoadingText.prefab +++ b/Assets/_Prefabs/PlayerCount_LoadingText.prefab @@ -113,9 +113,9 @@ MonoBehaviour: m_AlignByGeometry: 0 m_RichText: 1 m_HorizontalOverflow: 0 - m_VerticalOverflow: 0 + m_VerticalOverflow: 1 m_LineSpacing: 1 - m_Text: LOADING PLAYER COUNT + m_Text: RETRIEVING PLAYER COUNT --- !u!222 &222658238543524802 CanvasRenderer: m_ObjectHideFlags: 1 diff --git a/Assets/_Prefabs/Store.asset b/Assets/_Prefabs/Store.asset index 9274818..629e3d0 100644 --- a/Assets/_Prefabs/Store.asset +++ b/Assets/_Prefabs/Store.asset @@ -16,3 +16,4 @@ MonoBehaviour: - {fileID: 1428988585174978, guid: c034a3fdd4eaa554bb81a9a202bf37dd, type: 2} - {fileID: 1421894940388160, guid: 2e88fb286dba59a45b49fb0ae7d9449b, type: 2} - {fileID: 1395656030192232, guid: 6085354c72844664589bb5f21f9872b1, type: 2} + - {fileID: 1634027973393822, guid: a97af7648bcd6394b867989bf8fb9ed0, type: 2} diff --git a/Assets/_Scripts/HoldCounter.cs b/Assets/_Scripts/HoldCounter.cs index 2661bca..f27a649 100644 --- a/Assets/_Scripts/HoldCounter.cs +++ b/Assets/_Scripts/HoldCounter.cs @@ -1,5 +1,5 @@ -using System.Collections; -using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; using UnityEngine; using UnityEngine.UI; using FistVR; @@ -8,37 +8,62 @@ namespace TNHQoLImprovements { public class HoldCounter : MonoBehaviour { + private Text lblHoldCount; + private Text lblWinLose; + + public static int[] winLose = { -1, 1 }; + public const string WIN_LOSE_TEXT = "{0} {1}"; + + private void OnDeath(bool _) { Debug.Log("I died!"); + // TODO: bind stats to controller hand } - // TODO: lose counter. patch postfix FistVR.TNH_Manager.HoldPointCompleted - private void OnHoldLose() + // TODO: win/lose counter. patch postfix FistVR.TNH_Manager.HoldPointCompleted + public static void Patch(Harmony harmony) { - + var original = typeof(TNH_Manager).GetMethod("HoldPointCompleted", BindingFlags.Public | BindingFlags.Instance); + var patch = typeof(HoldCounter).GetMethod("OnHoldEnd"); + Debug.Log(string.Format("Original: {0} // Patch: {1}", original, patch)); + harmony.Patch(original, postfix: new HarmonyMethod(patch)); + } + public static void OnHoldEnd(TNH_HoldPoint p, bool success) + { + if (success) + winLose[0]++; + else + winLose[1]++; } void Start() { - //transform.parent = GameObject.Find("_NewTAHReticle/TAHReticle_HealthBar").transform; - transform.parent = FindObjectOfType().transform.GetChild(3); transform.localPosition = new Vector3(-1f, 0, -.5f); transform.localRotation = Quaternion.Euler(90, 0, 0); transform.localScale = new Vector3(0.002f, 0.002f, 0.002f); + lblHoldCount = transform.GetChild(1).GetComponent(); + lblWinLose = transform.GetChild(2).GetComponent(); + + winLose[0] = 0; + winLose[1] = 0; + FindObjectOfType().PlayerDeathEvent += OnDeath; } void Update() { + // Total hold count string display = ""; if (InPlay.tnhManager.ProgressionMode == TNHSetting_ProgressionType.Marathon) display = InPlay.tnhManager.m_level.ToString() + " / ∞"; else display = string.Format("{0} / {1}", InPlay.tnhManager.m_level, InPlay.tnhManager.m_maxLevels); + lblHoldCount.text = display; - transform.GetChild(1).GetComponent().text = display; + // Win/Lost holds + lblWinLose.text = string.Format(WIN_LOSE_TEXT, winLose[0], winLose[1]); } } } \ No newline at end of file diff --git a/Assets/_Scripts/InPlay.cs b/Assets/_Scripts/InPlay.cs index 7ec7d97..ec0053f 100644 --- a/Assets/_Scripts/InPlay.cs +++ b/Assets/_Scripts/InPlay.cs @@ -11,10 +11,8 @@ namespace TNHQoLImprovements public class InPlay : MonoBehaviour { private GameObject gObjHUD; - private GameObject gObjTokens; public static TNH_Manager tnhManager; - #region INITIALIZATION void ImproveHPTextReadability() { var canvas = gObjHUD.GetComponent(); @@ -60,10 +58,9 @@ namespace TNHQoLImprovements if(MeatKitPlugin.cfgShowHPBackground.Value || MeatKitPlugin.cfgSolidifyHPText.Value) ImproveHPTextReadability(); if (MeatKitPlugin.cfgShowTokens.Value) - Instantiate(MeatKitPlugin.bundle.LoadAsset("TokenCounter")); + Instantiate(MeatKitPlugin.bundle.LoadAsset("TokenCounter"), FindObjectOfType().transform.GetChild(3)); if (MeatKitPlugin.cfgShowHolds.Value) - Instantiate(MeatKitPlugin.bundle.LoadAsset("HoldCounter")); + Instantiate(MeatKitPlugin.bundle.LoadAsset("HoldCounter"), FindObjectOfType().transform.GetChild(3)); } - #endregion } } \ No newline at end of file diff --git a/Assets/_Scripts/LeaderboardPlayerCount.cs b/Assets/_Scripts/LeaderboardPlayerCount.cs index 75915aa..428f8c6 100644 --- a/Assets/_Scripts/LeaderboardPlayerCount.cs +++ b/Assets/_Scripts/LeaderboardPlayerCount.cs @@ -15,22 +15,13 @@ namespace TNHQoLImprovements private bool tnhTweakerInstalled = false; private string curID; + private string loadingStr; private TNH_ScoreDisplay scoreDisplay; private Text lblGlobalScores; private GameObject gObjLoading; #region INITIALIZATION - // public void Start() - // { - // Debug.Log("--- Installed BepInEx Plugins ---"); - // foreach (var plugin in Chainloader.PluginInfos) - // { - // Debug.Log(plugin.Key); - // } - // Debug.Log("--- End Plugins ---"); - //} - public void Init(TNH_ScoreDisplay tnhScore, Text scoreLabel, GameObject gObjLoading) { if (initialized) @@ -41,12 +32,13 @@ namespace TNHQoLImprovements this.lblGlobalScores.resizeTextForBestFit = true; this.lblGlobalScores.horizontalOverflow = HorizontalWrapMode.Overflow; this.gObjLoading = gObjLoading; + loadingStr = gObjLoading.GetComponentInChildren().text; var loadedAssemblies = System.AppDomain.CurrentDomain.GetAssemblies(); if (Array.Exists(loadedAssemblies, x => x.GetName().Name == "TakeAndHoldTweaker")) { tnhTweakerInstalled = true; - this.gObjLoading.transform.GetChild(0).GetComponent().text = "Online leaderboards player count is incompatible with TNHTweaker."; + this.gObjLoading.transform.GetChild(0).GetComponent().text = "Online player count is incompatible with TNHTweaker."; this.gObjLoading.SetActive(true); } @@ -66,23 +58,31 @@ namespace TNHQoLImprovements } private void UpdatePlayerCountDisplay(string id) - { - try - { - string playerCountText = Steamworks.SteamUserStats - .GetLeaderboardEntryCount(HighScoreManager.Leaderboards[id]) - .ToString("N0"); - lblGlobalScores.text = "Global Scores: (" + playerCountText + " players)"; - curID = id; + { + try + { + string playerCountText = Steamworks.SteamUserStats + .GetLeaderboardEntryCount(HighScoreManager.Leaderboards[id]) + .ToString("N0"); + + lblGlobalScores.text = "Global Scores: (" + playerCountText + " players)"; + curID = id; gObjLoading.SetActive(false); - } - catch (KeyNotFoundException e) - { - lblGlobalScores.text = "Global Scores:"; + } + catch (KeyNotFoundException e) + { + lblGlobalScores.text = "Global Scores:"; + gObjLoading.GetComponentInChildren().text = loadingStr; gObjLoading.SetActive(true); - curID = null; + curID = null; + } + catch (Exception e) + { + gObjLoading.GetComponentInChildren().text = string.Format("Unknown error occured trying to retrieve online player count.\n\n" + + "{0}", e); + gObjLoading.SetActive(true); } - } - } + } + } #endregion } \ No newline at end of file diff --git a/Assets/_Scripts/LeaderboardPlayerCountPatch.cs b/Assets/_Scripts/LeaderboardPlayerCountPatch.cs index e8d5dc7..e04f187 100644 --- a/Assets/_Scripts/LeaderboardPlayerCountPatch.cs +++ b/Assets/_Scripts/LeaderboardPlayerCountPatch.cs @@ -1,25 +1,24 @@ -using HarmonyLib; +using System.Reflection; +using HarmonyLib; using UnityEngine; using UnityEngine.UI; using FistVR; namespace TNHQoLImprovements { - [HarmonyPatch] - public class LeaderboardPlayerCountPatch + public static class LeaderboardPlayerCountPatch { - Harmony harmony; private static GameObject gObjLoading; private static Text uiGlobalText; - public LeaderboardPlayerCountPatch() + public static void Patch(Harmony harmony) { - harmony = new Harmony("me.muskit.TNHQualityOfLifeImprovements.LeaderboardPlayerCount"); - harmony.PatchAll(); + var original = typeof(TNH_ScoreDisplay).GetMethod("Start", BindingFlags.NonPublic | BindingFlags.Instance); + var newfunc = typeof(LeaderboardPlayerCountPatch).GetMethod("Setup"); + + harmony.Patch(original, postfix: new HarmonyMethod(newfunc)); } - [HarmonyPostfix] - [HarmonyPatch(typeof(TNH_ScoreDisplay), "Start")] public static void Setup(TNH_ScoreDisplay __instance) { GameObject gObjLeaderboard = null; diff --git a/Assets/_Scripts/MeshRendererFlicker.cs b/Assets/_Scripts/MeshRendererFlicker.cs new file mode 100644 index 0000000..78af9c1 --- /dev/null +++ b/Assets/_Scripts/MeshRendererFlicker.cs @@ -0,0 +1,64 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace TNHQoLImprovements +{ + /// + /// Flash attached MeshRenderer the timer runs out. + /// + public class MeshRendererFlicker : MonoBehaviour + { + private bool initialized = false; + + private float beginTime; + private float onLength; + private float offLength; + private float stateChangeTime = 0; + private bool visible = true; + + private MeshRenderer mesh; + + void Start() + { + mesh = GetComponent(); + } + + public void Init(float interval, float onToOffRatio = 0.5f, float beginAfter = 0) + { + beginTime = Time.time + beginAfter; + onLength = interval * onToOffRatio; + offLength = interval * (1 - onToOffRatio); + + initialized = true; + } + + void Update() + { + if (!initialized || Time.time < beginTime) + return; + + if (Time.time >= stateChangeTime) + { + visible = !visible; + mesh.enabled = visible; + + if (visible) // set time to stay on + { + stateChangeTime = Time.time + onLength; + } + else // set time to stay off + { + stateChangeTime = Time.time + offLength; + } + } + + //if (Time.time >= stateChangeTime) + // { + // stateChangeTime = Time.time + interval; + // visible = !visible; + // mesh.enabled = visible; + // } + } + } +} \ No newline at end of file diff --git a/Assets/_Scripts/MeshRendererFlicker.cs.meta b/Assets/_Scripts/MeshRendererFlicker.cs.meta new file mode 100644 index 0000000..40b93e3 --- /dev/null +++ b/Assets/_Scripts/MeshRendererFlicker.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 41ced368c613e2444bdbb07051989499 +timeCreated: 1643160950 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Scripts/TimedHealthCrystalPatch.cs b/Assets/_Scripts/TimedHealthCrystalPatch.cs new file mode 100644 index 0000000..b669c73 --- /dev/null +++ b/Assets/_Scripts/TimedHealthCrystalPatch.cs @@ -0,0 +1,57 @@ +using System.Reflection; +using HarmonyLib; +using UnityEngine; +using FistVR; + +namespace TNHQoLImprovements +{ + /// + /// If KillAfter is attached to a HealthCrystal, show visual representation of expiration. + /// + public static class TimedHealthCrystalPatch + { + private static GameObject timerAsset; + public const int VISUAL_APPROACH = 2; + + public static void Patch(Harmony harmony) + { + timerAsset = MeatKitPlugin.bundle.LoadAsset("HealthCrystalTimer"); + + var original = typeof(KillAfter).GetMethod("Start", BindingFlags.NonPublic | BindingFlags.Instance); + var newfunc = typeof(TimedHealthCrystalPatch).GetMethod("Setup"); + + harmony.Patch(original, postfix: new HarmonyMethod(newfunc)); + } + + public static void Setup(KillAfter __instance) + { + // only work with Health Crystals + if (__instance.transform.GetComponentInChildren() == null) + return; + + Debug.Log("KillAfter will expire in " + __instance.DieTime + " seconds."); + + GameObject timer; + Transform healthCrystal = __instance.transform.Find("HealthCrystal"); + switch (VISUAL_APPROACH) + { + case 0: // ring above + timer = GameObject.Instantiate(timerAsset, healthCrystal); + timer.GetComponent().Init(__instance.DieTime); + timer.transform.localScale = new Vector2(0.001f, 0.001f); + timer.transform.localPosition = new Vector3(0, .9f, 0); + break; + case 1: // ring around + timer = GameObject.Instantiate(timerAsset, healthCrystal); + timer.GetComponent().Init(__instance.DieTime); + timer.transform.localScale = new Vector2(0.0035f, 0.0035f); + timer.transform.localPosition = Vector3.zero; + break; + case 2: // flashing crystal + var flicker = healthCrystal.gameObject.AddComponent(); + flicker.Init(.4f, 0.6f, __instance.DieTime - 3f); + break; + } + } + } +} \ No newline at end of file diff --git a/Assets/_Scripts/TimedHealthCrystalPatch.cs.meta b/Assets/_Scripts/TimedHealthCrystalPatch.cs.meta new file mode 100644 index 0000000..24195a5 --- /dev/null +++ b/Assets/_Scripts/TimedHealthCrystalPatch.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e2d8d57a4a90c704da061837b5f7b16a +timeCreated: 1643082249 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Scripts/TokenCounter.cs b/Assets/_Scripts/TokenCounter.cs index 6db66b6..28d6f17 100644 --- a/Assets/_Scripts/TokenCounter.cs +++ b/Assets/_Scripts/TokenCounter.cs @@ -9,8 +9,6 @@ namespace TNHQoLImprovements { void Start() { - //transform.parent = GameObject.Find("_NewTAHReticle/TAHReticle_HealthBar").transform; - transform.parent = FindObjectOfType().transform.GetChild(3); transform.localPosition = new Vector3(1, 0, -.5f); transform.localRotation = Quaternion.Euler(90, 0, 0); transform.localScale = new Vector3(0.002f, 0.002f, 0.002f); diff --git a/Assets/_Scripts/UIRingTimer.cs b/Assets/_Scripts/UIRingTimer.cs new file mode 100644 index 0000000..2466d81 --- /dev/null +++ b/Assets/_Scripts/UIRingTimer.cs @@ -0,0 +1,33 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +public class UIRingTimer : MonoBehaviour { + + private bool initialized = false; + private float endTime; + private float length; + + private Image ringImg; + + private void Start() + { + ringImg = GetComponentInChildren(); + } + + public void Init(float timeInSeconds) + { + length = timeInSeconds; + endTime = Time.time + length; + initialized = true; + } + + void Update () { + if (!initialized) + return; + + float amount = (endTime - Time.time) / length; + ringImg.fillAmount = Mathf.Clamp01(amount); + } +} diff --git a/Assets/_Scripts/UIRingTimer.cs.meta b/Assets/_Scripts/UIRingTimer.cs.meta new file mode 100644 index 0000000..77e7b1a --- /dev/null +++ b/Assets/_Scripts/UIRingTimer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c0cc182175a3417499d53e43378f028c +timeCreated: 1643099834 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Textures.meta b/Assets/_Textures.meta new file mode 100644 index 0000000..bff12f7 --- /dev/null +++ b/Assets/_Textures.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 202273f3dee1d244dbfc63238c672310 +folderAsset: yes +timeCreated: 1643099086 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Textures/White Ring.png b/Assets/_Textures/White Ring.png new file mode 100644 index 0000000000000000000000000000000000000000..dbfb81afc3887307d2aa8d92318f9f44e2b80eb0 GIT binary patch literal 10035 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 m?;l?i{