using System.Runtime.InteropServices; using EntityStates; using EntityStates.ShrineHalcyonite; using UnityEngine; using UnityEngine.Networking; namespace RoR2; public class HalcyoniteShrineInteractable : NetworkBehaviour { [Header("Shrine Core Variables")] public bool HalcyoniteShrineActive = true; [Tooltip("Flat max gold cost for accessing top quality. *Note, without difficulty cost scaling.")] public int maxGoldCost = 300; [Tooltip("Flat mid gold cost for accessing top quality. *Note, without difficulty cost scaling.")] public int midGoldCost = 150; [Tooltip("Minimum amount of gold required to activate boss fight and end drain. *Note, without difficulty cost scaling.")] public int lowGoldCost = 75; [Tooltip("True if shrine gold mechanic should increase with difficulty. False if it should not.")] [SerializeField] private bool automaticallyScaleCostWithDifficulty = true; [SerializeField] private GameObject shrineCollisionObject; [Tooltip("Detection radius for picking up the players within range of the Halcyonite Shrine.")] [Header("Draining Gold Variables")] public float radius = 30f; [Tooltip("Amount drained per tick.")] public int goldDrainValue = 1; [Tooltip("Total amount of gold stored in Shrine.")] public int goldDrained; [Min(float.Epsilon)] [Tooltip("How many ticks per second.")] public float tickRate = 5f; [SyncVar] [Tooltip("Maximum amount of player targets allowed.")] public int maxTargets = 4; [Tooltip("Game Object for the network game object responsible for draining the player's gold.")] public GameObject siphonObject; [SyncVar] [HideInInspector] public int interactions; [SerializeField] [Tooltip("Renderer reference for when changing target renderers for the highlight script.")] private Renderer shrineRenderer; [SyncVar] public bool isDraining; [SerializeField] [Tooltip("Reference to the crystal object for changing its materials.")] [Header("Gauge Mechanic")] public GameObject crystalObject; [SerializeField] [SyncVar] public GameObject shrineGoldBottom; [SyncVar] [SerializeField] public GameObject shrineGoldMid; [SerializeField] [SyncVar] public GameObject shrineGoldTop; [SerializeField] public Material goldMaterialController; [Tooltip("Color of VFX and Crystal once the encounter is finished and reward has been dispersed.")] [SerializeField] public GameObject shrineBaseObject; [Tooltip("Halcyonite Shrine bubble Game Object reference.")] [SerializeField] public GameObject shrineHalcyoniteBubble; [Tooltip("Entity State Machine reference for handling changes to gauge mechanics for the network.")] [SerializeField] public EntityStateMachine stateMachine; public SerializableEntityStateType noQualityState = new SerializableEntityStateType(typeof(ShrineHalcyoniteNoQuality)); public SerializableEntityStateType finishedState = new SerializableEntityStateType(typeof(ShrineHalcyoniteFinished)); public SfxLocator sfxLocator; [SerializeField] private PurchaseInteraction purchaseInteraction; [SerializeField] [Header("Shrine Encounter and Rewards")] private DirectorCard chosenDirectCard; [SerializeField] [Tooltip("Drop table dictating what rewards are available at minimum gold value.")] private BasicPickupDropTable halcyoniteDropTableTier1; [SerializeField] [Tooltip("Drop table dictating what rewards are available at middling gold value.")] private BasicPickupDropTable halcyoniteDropTableTier2; [SerializeField] [Tooltip("Drop table dictating what rewards are available at maximum gold value.")] private BasicPickupDropTable halcyoniteDropTableTier3; [Tooltip("Use this tier to get a pickup index for the reward. The droplet's visuals will correspond to this.")] [SerializeField] private ItemTier rewardDisplayTier; [SerializeField] [Tooltip("The number of options to display when the player interacts with the reward pickup.")] private int rewardOptionCount; [SerializeField] [Tooltip("The prefab to use for the reward pickup.")] private GameObject rewardPickupPrefab; [SerializeField] [Tooltip("Where to spawn the reward droplets relative to the spawn target (the center of the safe ward).")] private Vector3 rewardOffset; [SerializeField] [Header("Colossus Portal")] public ChildLocator modelChildLocator; public PortalSpawner[] portalSpawners; [Tooltip("Combat Director managing the monster wave that spawns on Halcyon Shrine's gold drain behavior activation.")] public CombatDirector activationDirector; [Tooltip("Monster credits given to combat director to determine how many monsters to spawn.")] public float monsterCredit; [SerializeField] private bool scaleMonsterCreditWithDifficultyCoefficient = true; [SerializeField] private CombatDirector combatDirector; private int quantityIncreaseFromBuyIn = 1; private BasicPickupDropTable rewardDropTable; private Xoroshiro128Plus rng; [SyncVar] public float goldMaterialModifier = -2.2f; [SyncVar] private bool visualsDone; private NetworkInstanceId ___shrineGoldBottomNetId; private NetworkInstanceId ___shrineGoldMidNetId; private NetworkInstanceId ___shrineGoldTopNetId; public static bool isCommandEnabled => RunArtifactManager.instance.IsArtifactEnabled(RoR2Content.Artifacts.Command); public int NetworkmaxTargets { get { return maxTargets; } [param: In] set { SetSyncVar(value, ref maxTargets, 1u); } } public int Networkinteractions { get { return interactions; } [param: In] set { SetSyncVar(value, ref interactions, 2u); } } public bool NetworkisDraining { get { return isDraining; } [param: In] set { SetSyncVar(value, ref isDraining, 4u); } } public GameObject NetworkshrineGoldBottom { get { return shrineGoldBottom; } [param: In] set { SetSyncVarGameObject(value, ref shrineGoldBottom, 8u, ref ___shrineGoldBottomNetId); } } public GameObject NetworkshrineGoldMid { get { return shrineGoldMid; } [param: In] set { SetSyncVarGameObject(value, ref shrineGoldMid, 16u, ref ___shrineGoldMidNetId); } } public GameObject NetworkshrineGoldTop { get { return shrineGoldTop; } [param: In] set { SetSyncVarGameObject(value, ref shrineGoldTop, 32u, ref ___shrineGoldTopNetId); } } public float NetworkgoldMaterialModifier { get { return goldMaterialModifier; } [param: In] set { SetSyncVar(value, ref goldMaterialModifier, 64u); } } public bool NetworkvisualsDone { get { return visualsDone; } [param: In] set { SetSyncVar(value, ref visualsDone, 128u); } } protected void Awake() { if (NetworkServer.active) { if (automaticallyScaleCostWithDifficulty) { lowGoldCost = Run.instance.GetDifficultyScaledCost(lowGoldCost); midGoldCost = Run.instance.GetDifficultyScaledCost(midGoldCost); maxGoldCost = Run.instance.GetDifficultyScaledCost(maxGoldCost); goldDrainValue = Run.instance.GetDifficultyScaledCost(goldDrainValue); } CalculateCredits(); HalcyoniteShrineActive = true; goldMaterialController.EnableKeyword("_SLICEHEIGHT"); combatDirector = GetComponent(); } } private void Start() { HalcyoniteShrineActive = true; shrineCollisionObject.layer = LayerIndex.world.intVal; } private void CalculateCredits() { if (scaleMonsterCreditWithDifficultyCoefficient) { monsterCredit = Mathf.CeilToInt(monsterCredit * Run.instance.difficultyCoefficient); } } [Server] public void TrackInteractions() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.HalcyoniteShrineInteractable::TrackInteractions()' called on client"); return; } if (interactions == 0) { GetComponent().targetRenderer = shrineRenderer; stateMachine.SetNextStateToMain(); } Networkinteractions = interactions + 1; if (interactions >= 3) { stateMachine.SetNextState(new ShrineHalcyoniteMaxQuality()); } } [Server] public void IsDraining(bool drainingActive) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.HalcyoniteShrineInteractable::IsDraining(System.Boolean)' called on client"); } else { NetworkisDraining = drainingActive; } } public void DestroyDrainVFX() { if ((bool)siphonObject) { NetworkServer.Destroy(siphonObject); } if ((bool)shrineHalcyoniteBubble && shrineHalcyoniteBubble.activeInHierarchy) { shrineHalcyoniteBubble.SetActive(value: false); } IsDraining(drainingActive: false); } public void DrainConditionMet() { DestroyDrainVFX(); if ((bool)purchaseInteraction && purchaseInteraction.available) { purchaseInteraction.SetAvailable(newAvailable: false); } if (goldDrained > lowGoldCost && goldDrained < midGoldCost) { quantityIncreaseFromBuyIn = 1; rewardDropTable = halcyoniteDropTableTier1; rewardOptionCount = 3; rng = new Xoroshiro128Plus(Run.instance.treasureRng.nextUlong); } if (goldDrained > midGoldCost && goldDrained < maxGoldCost) { quantityIncreaseFromBuyIn = 1; rewardDropTable = halcyoniteDropTableTier2; rewardOptionCount = 4; rng = new Xoroshiro128Plus(Run.instance.treasureRng.nextUlong); } if (goldDrained >= maxGoldCost) { quantityIncreaseFromBuyIn = 1; rewardDropTable = halcyoniteDropTableTier3; rewardOptionCount = 5; rng = new Xoroshiro128Plus(Run.instance.treasureRng.nextUlong); } Transform transform = modelChildLocator.FindChild("ModelBase"); if (transform != null) { Vector3 origin = transform.position + new Vector3(0f, 16f, 0f); EffectManager.SpawnEffect(LegacyResourcesAPI.Load("Prefabs/Effects/HalcyonShrineTierReachVFX"), new EffectData { origin = origin }, transmit: false); } int difficultyLevel = (int)((float)goldDrained / 100f); combatDirector.HalcyoniteShrineActivation(chosenDirectCard.cost, chosenDirectCard, difficultyLevel, base.gameObject.transform); } public void StoreDrainValue(int value) { goldDrained += value; } [Server] public void AttemptSpawnPortal() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.HalcyoniteShrineInteractable::AttemptSpawnPortal()' called on client"); return; } PortalSpawner[] array = portalSpawners; for (int i = 0; i < array.Length; i++) { array[i].AttemptSpawnPortalServer(); } } [Server] public void DropRewards() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.HalcyoniteShrineInteractable::DropRewards()' called on client"); return; } stateMachine.SetNextState(EntityStateCatalog.InstantiateState(ref finishedState)); int participatingPlayerCount = Run.instance.participatingPlayerCount; if (participatingPlayerCount <= 0 || !base.gameObject || !rewardDropTable) { return; } int num = participatingPlayerCount * quantityIncreaseFromBuyIn; float angle = 360f / (float)num; Vector3 vector = Quaternion.AngleAxis(Random.Range(0, 360), Vector3.up) * (Vector3.up * 40f + Vector3.forward * 5f); Quaternion quaternion = Quaternion.AngleAxis(angle, Vector3.up); Vector3 position = base.gameObject.transform.position + rewardOffset; int num2 = 0; while (num2 < num) { if (isCommandEnabled) { int num3 = rewardOptionCount - 2; for (int i = 0; i < num3; i++) { PickupIndex pickupIndex = rewardDropTable.GenerateDrop(rng); GenericPickupController.CreatePickupInfo createPickupInfo = default(GenericPickupController.CreatePickupInfo); createPickupInfo.pickupIndex = pickupIndex; createPickupInfo.rotation = Quaternion.identity; createPickupInfo.position = position; GenericPickupController.CreatePickupInfo pickupInfo = createPickupInfo; PickupDropletController.CreatePickupDroplet(pickupInfo, pickupInfo.position, vector); } } else { GenericPickupController.CreatePickupInfo createPickupInfo = default(GenericPickupController.CreatePickupInfo); createPickupInfo.pickupIndex = PickupCatalog.FindPickupIndex(rewardDisplayTier); createPickupInfo.pickerOptions = PickupPickerController.GenerateOptionsFromDropTablePlusForcedStorm(rewardOptionCount, halcyoniteDropTableTier3, halcyoniteDropTableTier2, rng); createPickupInfo.rotation = Quaternion.identity; createPickupInfo.prefabOverride = rewardPickupPrefab; GenericPickupController.CreatePickupInfo pickupInfo2 = createPickupInfo; pickupInfo2.position = position; PickupDropletController.CreatePickupDroplet(pickupInfo2, position, vector); } num2++; vector = quaternion * vector; } } private void UNetVersion() { } public override bool OnSerialize(NetworkWriter writer, bool forceAll) { if (forceAll) { writer.WritePackedUInt32((uint)maxTargets); writer.WritePackedUInt32((uint)interactions); writer.Write(isDraining); writer.Write(shrineGoldBottom); writer.Write(shrineGoldMid); writer.Write(shrineGoldTop); writer.Write(goldMaterialModifier); writer.Write(visualsDone); return true; } bool flag = false; if ((base.syncVarDirtyBits & (true ? 1u : 0u)) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.WritePackedUInt32((uint)maxTargets); } if ((base.syncVarDirtyBits & 2u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.WritePackedUInt32((uint)interactions); } if ((base.syncVarDirtyBits & 4u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(isDraining); } if ((base.syncVarDirtyBits & 8u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(shrineGoldBottom); } if ((base.syncVarDirtyBits & 0x10u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(shrineGoldMid); } if ((base.syncVarDirtyBits & 0x20u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(shrineGoldTop); } if ((base.syncVarDirtyBits & 0x40u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(goldMaterialModifier); } if ((base.syncVarDirtyBits & 0x80u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(visualsDone); } if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); } return flag; } public override void OnDeserialize(NetworkReader reader, bool initialState) { if (initialState) { maxTargets = (int)reader.ReadPackedUInt32(); interactions = (int)reader.ReadPackedUInt32(); isDraining = reader.ReadBoolean(); ___shrineGoldBottomNetId = reader.ReadNetworkId(); ___shrineGoldMidNetId = reader.ReadNetworkId(); ___shrineGoldTopNetId = reader.ReadNetworkId(); goldMaterialModifier = reader.ReadSingle(); visualsDone = reader.ReadBoolean(); return; } int num = (int)reader.ReadPackedUInt32(); if (((uint)num & (true ? 1u : 0u)) != 0) { maxTargets = (int)reader.ReadPackedUInt32(); } if (((uint)num & 2u) != 0) { interactions = (int)reader.ReadPackedUInt32(); } if (((uint)num & 4u) != 0) { isDraining = reader.ReadBoolean(); } if (((uint)num & 8u) != 0) { shrineGoldBottom = reader.ReadGameObject(); } if (((uint)num & 0x10u) != 0) { shrineGoldMid = reader.ReadGameObject(); } if (((uint)num & 0x20u) != 0) { shrineGoldTop = reader.ReadGameObject(); } if (((uint)num & 0x40u) != 0) { goldMaterialModifier = reader.ReadSingle(); } if (((uint)num & 0x80u) != 0) { visualsDone = reader.ReadBoolean(); } } public override void PreStartClient() { if (!___shrineGoldBottomNetId.IsEmpty()) { NetworkshrineGoldBottom = ClientScene.FindLocalObject(___shrineGoldBottomNetId); } if (!___shrineGoldMidNetId.IsEmpty()) { NetworkshrineGoldMid = ClientScene.FindLocalObject(___shrineGoldMidNetId); } if (!___shrineGoldTopNetId.IsEmpty()) { NetworkshrineGoldTop = ClientScene.FindLocalObject(___shrineGoldTopNetId); } } }