631 lines
16 KiB
C#
631 lines
16 KiB
C#
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<CombatDirector>();
|
|
}
|
|
}
|
|
|
|
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<Highlight>().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<GameObject>("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);
|
|
}
|
|
}
|
|
}
|