using System; using System.Collections.Generic; using HG; using UnityEngine; namespace RoR2; public static class BackstabManager { private class BackstabVisualizer { private class IndicatorInfo { public GameObject gameObject; public Transform transform; public ParticleSystem particleSystem; public ParticleSystemRenderer renderer; public float lastDisplayTime; } private CameraRigController camera; public CharacterBody targetBody; private readonly Dictionary bodyToIndicator = new Dictionary(); private static readonly Dictionary buffer = new Dictionary(); private readonly Stack indicatorPool = new Stack(); private static GameObject indicatorPrefab; public void Install(CameraRigController newCamera) { camera = newCamera; RoR2Application.onLateUpdate += UpdateIndicators; } public void Uninstall() { RoR2Application.onLateUpdate -= UpdateIndicators; camera = null; foreach (KeyValuePair item in bodyToIndicator) { UnityEngine.Object.Destroy(item.Value.gameObject); } bodyToIndicator.Clear(); foreach (IndicatorInfo item2 in indicatorPool) { UnityEngine.Object.Destroy(item2.gameObject); } indicatorPool.Clear(); } [InitDuringStartup] private static void OnLoad() { LegacyResourcesAPI.LoadAsyncCallback("Prefabs/VFX/BackstabIndicator", delegate(GameObject operationResult) { indicatorPrefab = operationResult; }); } private void UpdateIndicators() { Vector3 corePosition = targetBody.corePosition; TeamIndex teamIndex = targetBody.teamComponent.teamIndex; buffer.Clear(); float unscaledTime = Time.unscaledTime; foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { bool num = TeamManager.IsTeamEnemy(teamIndex, readOnlyInstances.teamComponent.teamIndex); bool flag = false; if (num) { flag = IsBackstab(readOnlyInstances.corePosition - corePosition, readOnlyInstances); } if (num && flag) { IndicatorInfo value = null; if (!bodyToIndicator.TryGetValue(readOnlyInstances, out value)) { value = AddIndicator(readOnlyInstances); } value.lastDisplayTime = unscaledTime; } } List list = CollectionPool>.RentCollection(); foreach (KeyValuePair item in bodyToIndicator) { if (item.Value.lastDisplayTime != unscaledTime) { list.Add(item.Key); } } foreach (CharacterBody item2 in list) { RemoveIndicator(item2); } list = CollectionPool>.ReturnCollection(list); } private IndicatorInfo AddIndicator(CharacterBody victimBody) { IndicatorInfo indicatorInfo = null; if (indicatorPool.Count > 0) { indicatorInfo = indicatorPool.Pop(); } else { indicatorInfo = new IndicatorInfo(); indicatorInfo.gameObject = UnityEngine.Object.Instantiate(indicatorPrefab); indicatorInfo.transform = indicatorInfo.gameObject.transform; indicatorInfo.particleSystem = indicatorInfo.gameObject.GetComponent(); indicatorInfo.renderer = indicatorInfo.gameObject.GetComponent(); indicatorInfo.renderer.enabled = false; } indicatorInfo.gameObject.SetActive(value: true); indicatorInfo.particleSystem.Play(); bodyToIndicator[victimBody] = indicatorInfo; return indicatorInfo; } private void RemoveIndicator(CharacterBody victimBody) { IndicatorInfo indicatorInfo = bodyToIndicator[victimBody]; if ((bool)indicatorInfo.gameObject) { indicatorInfo.particleSystem.Stop(); indicatorInfo.gameObject.SetActive(value: false); } bodyToIndicator.Remove(victimBody); indicatorPool.Push(indicatorInfo); } public void OnPreCull() { foreach (KeyValuePair item in bodyToIndicator) { CharacterBody key = item.Key; Transform transform = item.Value.transform; if ((bool)key) { Vector3? bodyForward = GetBodyForward(key); if (bodyForward.HasValue) { transform.forward = -bodyForward.Value; } transform.position = key.corePosition - transform.forward * key.radius; item.Value.renderer.enabled = true; } } } public void OnPostRender() { foreach (KeyValuePair item in bodyToIndicator) { item.Value.renderer.enabled = false; } } private void Update() { UpdateIndicators(); } } public static GameObject backstabImpactEffectPrefab = null; private static readonly bool enableVisualizerSystem = false; private static readonly float showBackstabThreshold = Mathf.Cos(MathF.PI / 4f); private static readonly Dictionary camToVisualizer = new Dictionary(); public static bool IsBackstab(Vector3 attackerCorePositionToHitPosition, CharacterBody victimBody) { if (!victimBody.canReceiveBackstab) { return false; } Vector3? bodyForward = GetBodyForward(victimBody); if (bodyForward.HasValue) { return Vector3.Dot(attackerCorePositionToHitPosition, bodyForward.Value) > 0f; } return false; } private static Vector3? GetBodyForward(CharacterBody characterBody) { Vector3? vector = null; return (!characterBody.characterDirection) ? new Vector3?(characterBody.transform.forward) : new Vector3?(characterBody.characterDirection.forward); } [InitDuringStartup] private static void Init() { if (enableVisualizerSystem) { InitVisualizerSystem(); } LegacyResourcesAPI.LoadAsyncCallback("Effects/ImpactEffects/BackstabSpark", delegate(GameObject operationResult) { backstabImpactEffectPrefab = operationResult; }); } private static bool ShouldShowBackstab(Vector3 attackerCorePosition, CharacterBody victimBody) { if (!victimBody.canReceiveBackstab) { return false; } Vector3? bodyForward = GetBodyForward(victimBody); if (bodyForward.HasValue) { return Vector3.Dot((attackerCorePosition - victimBody.corePosition).normalized, bodyForward.Value) > showBackstabThreshold; } return false; } private static void InitVisualizerSystem() { CameraRigController.onCameraTargetChanged += OnCameraTargetChanged; CameraRigController.onCameraEnableGlobal += OnCameraDiscovered; CameraRigController.onCameraDisableGlobal += OnCameraLost; SceneCamera.onSceneCameraPreCull += OnSceneCameraPreCull; SceneCamera.onSceneCameraPostRender += OnSceneCameraPostRender; } private static void OnCameraTargetChanged(CameraRigController camera, GameObject target) { RefreshCamera(camera); } private static void OnCameraDiscovered(CameraRigController camera) { RefreshCamera(camera); } private static void OnCameraLost(CameraRigController camera) { RefreshCamera(camera); } private static void OnSceneCameraPreCull(SceneCamera sceneCam) { if (camToVisualizer.TryGetValue(sceneCam.cameraRigController, out var value)) { value.OnPreCull(); } } private static void OnSceneCameraPostRender(SceneCamera sceneCam) { if (camToVisualizer.TryGetValue(sceneCam.cameraRigController, out var value)) { value.OnPostRender(); } } private static void RefreshCamera(CameraRigController camera) { BackstabVisualizer value; bool num = camToVisualizer.TryGetValue(camera, out value); GameObject gameObject = (camera.isActiveAndEnabled ? camera.target : null); CharacterBody characterBody = (gameObject ? gameObject.GetComponent() : null); bool flag = (bool)characterBody && characterBody.canPerformBackstab; if (num != flag) { if (flag) { value = new BackstabVisualizer(); camToVisualizer.Add(camera, value); value.Install(camera); } else { value.Uninstall(); camToVisualizer.Remove(camera); value = null; } } if (value != null) { value.targetBody = characterBody; } } }