From c893e8bec07fb197ad1426873a797457d8763730 Mon Sep 17 00:00:00 2001 From: Kirill Danshin Date: Mon, 16 Mar 2026 01:39:21 +0400 Subject: [PATCH] Add zeroing menu items to bolt menu --- .../Interactions/GunInteractionController.cs | 152 +++++++++++++++++- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/Source/Player/Interactions/GunInteractionController.cs b/Source/Player/Interactions/GunInteractionController.cs index caaaef7..6202ae2 100644 --- a/Source/Player/Interactions/GunInteractionController.cs +++ b/Source/Player/Interactions/GunInteractionController.cs @@ -1,7 +1,9 @@ -using EFT; -using EFT.UI.Ragfair; +using EFT; +using EFT.InventoryLogic; +using EFT.UI; using JetBrains.Annotations; using System; +using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; @@ -47,6 +49,12 @@ public class GunInteractionController : MonoBehaviour public bool hasExaminedAfterMalfunction = false; public Vector3 armsOffset = new Vector3(-0.05f, -0.075f, -0.075f); + // Zeroing: label action for text display, and a reference to the bolt menu list + // so RefreshZeroingLabel can rebuild the UI with the updated text. + private ActionsTypesClass zeroingLabelAction = null; + private ActionsReturnClass boltActionsList = null; + private ActionPanel cachedActionPanel = null; + private Dictionary interactablesDictionary; private Dictionary tacDeviceDictionary; private Dictionary malfunctionMeshDictionary; @@ -140,6 +148,7 @@ private void OnEnable() { public void OnDisable() { + cachedActionPanel = null; // Check if gunRaycastReciever exists before using it if (initialized && gunRaycastReciever != null && gunRaycastReciever.GetComponent() != null) { @@ -635,9 +644,108 @@ public bool IsMagazineSet() { return (magazine || internalMag); } //------------------------------------------------------------------------------------------------------------------------------------------------------------ - public void SetChargingHandleOrBolt(Transform chargeOrBoltTransform, bool isBolt) { - //if (chargingHandle || bolt) - // return; + + /// + /// Switches zeroing by directly mutating SightComponent.ScopesCurrentCalibPointIndexes — + /// the same data that OpticCalibrationSwitch modifies, but without the IsAiming guard + /// that would block the call when the player is not aiming. + /// After changing the index we call method_2() to flag calibration for recalculation + /// on the next frame, which updates the reticle / zeroing panel. + /// + private void DoZeroingChange(bool increase) + { + try + { + if (VRGlobals.player == null) return; + var pwa = VRGlobals.player.ProceduralWeaponAnimation; + if (pwa == null) return; + + var sight = pwa.CurrentAimingMod; + if (sight == null) return; + if (!sight.HasOpticCalibrationPoints(sight.SelectedScopeIndex)) return; + + bool changed = increase + ? sight.OpticCalibrationPointUp() + : sight.OpticCalibrationPointDown(); + + if (changed) + { + // Flag PWA to recalculate calibration on next frame + pwa.method_2(); + } + } + catch (Exception ex) + { + UnityEngine.Debug.LogWarning("[VR Zeroing] DoZeroingChange failed: " + ex.Message); + } + } + + /// + /// Returns the current zeroing distance from the active sight, e.g. "100m". + /// Returns "---" when no calibration data is available. + /// + private string GetCurrentZeroingText() + { + try + { + if (VRGlobals.player == null) return "---"; + var pwa = VRGlobals.player.ProceduralWeaponAnimation; + if (pwa == null) return "---"; + var sight = pwa.CurrentAimingMod; + if (sight == null) return "---"; + if (!sight.HasOpticCalibrationPoints(sight.SelectedScopeIndex)) return "---"; + int dist = sight.GetCurrentOpticCalibrationDistance(); + return dist + "m"; + } + catch { return "---"; } + } + + /// + /// Updates the zeroing label text and immediately rebuilds the ActionPanel button list + /// so the new distance is visible without reopening the menu. + /// + /// InteractionButton.Show() writes _text.text once at creation; the CurrentActionChanged + /// event only refreshes highlight colours, never text. The only way to update button text + /// is to call AvailableInteractionState.method_0() which rebuilds GClass3822 from Actions[]. + /// We save/restore SelectedAction so the selection does not jump back to Actions[0]. + /// + private void RefreshZeroingLabel() + { + if (zeroingLabelAction == null || boltActionsList == null || playerOwner == null) + return; + + zeroingLabelAction.Name = "Zeroing: " + GetCurrentZeroingText(); + + if (lastHitCompIndex != boltIndex) + return; + + // Cache ActionPanel — FindObjectOfType is expensive and would cause a frame hitch + // if called every time. We look it up once and reuse the reference. + if (cachedActionPanel == null) + cachedActionPanel = UnityEngine.Object.FindObjectOfType(); + + if (cachedActionPanel == null) + return; + + // Call ActionPanel.method_0 directly instead of going through + // AvailableInteractionState.method_0, which skips the update when the same + // object reference is already the current Value (reference equality check). + var savedAction = boltActionsList.SelectedAction; + cachedActionPanel.method_0(boltActionsList); + if (savedAction != null) + boltActionsList.SelectedAction = savedAction; + } + + private IEnumerator RefreshZeroingLabelDelayed() + { + yield return null; + yield return null; + RefreshZeroingLabel(); + } + + //------------------------------------------------------------------------------------------------------------------------------------------------------------ + public void SetChargingHandleOrBolt(Transform chargeOrBoltTransform, bool isBolt) + { if (isBolt) bolt = chargeOrBoltTransform; else @@ -648,6 +756,8 @@ public void SetChargingHandleOrBolt(Transform chargeOrBoltTransform, bool isBolt GetMesh(chargeOrBoltTransform); GetMalfunctionMeshes(chargeOrBoltTransform); List listComponents = new List(); + + // --- Examine Weapon --- ActionsTypesClass examineWeapon = new ActionsTypesClass(); examineWeapon.Name = "Examine Weapon"; IInputHandler baseHandler; @@ -659,7 +769,7 @@ public void SetChargingHandleOrBolt(Transform chargeOrBoltTransform, bool isBolt } listComponents.Add(examineWeapon); - + // --- Check Chamber / Fix Malfunction --- ActionsTypesClass checkChamber = new ActionsTypesClass(); checkChamber.Name = "Check Chamber/Fix Malfunction"; VRInputManager.inputHandlers.TryGetValue(EFT.InputSystem.ECommand.CheckChamber, out baseHandler); @@ -670,10 +780,40 @@ public void SetChargingHandleOrBolt(Transform chargeOrBoltTransform, bool isBolt } listComponents.Add(checkChamber); + // --- Zeroing label (display-only) --- + // Disabled = true so SelectNextAction/SelectPreviousAction skips it on scroll. + zeroingLabelAction = new ActionsTypesClass(); + zeroingLabelAction.Name = "Zeroing: " + GetCurrentZeroingText(); + zeroingLabelAction.Disabled = true; + zeroingLabelAction.Action = null; + listComponents.Add(zeroingLabelAction); + + // --- Zeroing + --- + // Calls OpticCalibrationSwitchUp DIRECTLY on firearmController — no command queue, + // no VRInputManager dependency. Then refreshes the label after 2 frames. + ActionsTypesClass zeroingUp = new ActionsTypesClass(); + zeroingUp.Name = " Zeroing +"; + zeroingUp.Action = () => + { + DoZeroingChange(true); + RefreshZeroingLabel(); + }; + listComponents.Add(zeroingUp); + + // --- Zeroing - --- + ActionsTypesClass zeroingDown = new ActionsTypesClass(); + zeroingDown.Name = " Zeroing -"; + zeroingDown.Action = () => + { + DoZeroingChange(false); + RefreshZeroingLabel(); + }; + listComponents.Add(zeroingDown); ActionsReturnClass chamberList = new ActionsReturnClass(); chamberList.Actions = listComponents; weaponUiLists.Add(chamberList); + boltActionsList = chamberList; } //------------------------------------------------------------------------------------------------------------------------------------------------------------ public void SetFireModeSwitch(Transform fireModeSwitch) {