using System; using EntityStates; using JetBrains.Annotations; using RoR2.Networking; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; namespace RoR2; [RequireComponent(typeof(PurchaseInteraction))] [RequireComponent(typeof(EntityStateMachine))] public class RouletteChestController : NetworkBehaviour { public struct Entry { public PickupIndex pickupIndex; public Run.FixedTimeStamp endTime; } private class RouletteChestControllerBaseState : EntityState { protected RouletteChestController rouletteChestController { get; private set; } public override void OnEnter() { base.OnEnter(); rouletteChestController = GetComponent(); } public virtual void HandleInteractionServer(Interactor activator) { } } private class Idle : RouletteChestControllerBaseState { private static int IdleStateHash = Animator.StringToHash("Idle"); public override void OnEnter() { base.OnEnter(); PlayAnimation("Body", IdleStateHash); base.rouletteChestController.purchaseInteraction.Networkavailable = true; } public override void HandleInteractionServer(Interactor activator) { base.HandleInteractionServer(activator); outer.SetNextState(new Startup()); } } private class Startup : RouletteChestControllerBaseState { public static float baseDuration; public static string soundEntryEvent; private static int IdleToActiveStateHash = Animator.StringToHash("IdleToActive"); public override void OnEnter() { base.OnEnter(); PlayAnimation("Body", IdleToActiveStateHash); base.rouletteChestController.purchaseInteraction.Networkavailable = false; base.rouletteChestController.purchaseInteraction.costType = CostTypeIndex.None; Util.PlaySound(soundEntryEvent, base.gameObject); } public override void FixedUpdate() { base.FixedUpdate(); if (NetworkServer.active && base.fixedAge > baseDuration) { outer.SetNextState(new Cycling()); } } } private class Cycling : RouletteChestControllerBaseState { public static string soundCycleEvent; public static string soundCycleSpeedRtpc; public static float soundCycleSpeedRtpcScale; private static int ActiveLoopStateHash = Animator.StringToHash("ActiveLoop"); private static int ActiveLoopParamHash = Animator.StringToHash("ActiveLoop.playbackRate"); public override void OnEnter() { base.OnEnter(); base.rouletteChestController.onChangedEntryClient.AddListener(OnChangedEntryClient); if (NetworkServer.active) { base.rouletteChestController.BeginCycleServer(); base.rouletteChestController.onCycleCompletedServer.AddListener(OnCycleCompleted); } base.rouletteChestController.purchaseInteraction.Networkavailable = true; base.rouletteChestController.purchaseInteraction.costType = CostTypeIndex.None; } private void OnCycleCompleted() { outer.SetNextState(new Opening()); } public override void OnExit() { base.rouletteChestController.onCycleCompletedServer.RemoveListener(OnCycleCompleted); base.rouletteChestController.onChangedEntryClient.RemoveListener(OnChangedEntryClient); base.OnExit(); } private void OnChangedEntryClient() { int entryIndexForTime = base.rouletteChestController.GetEntryIndexForTime(Run.FixedTimeStamp.now); float num = base.rouletteChestController.CalcEntryDuration(entryIndexForTime); PlayAnimation("Body", ActiveLoopStateHash, ActiveLoopParamHash, num); float num2 = Util.Remap(num, minTime, minTime + base.rouletteChestController.bonusTime, 1f, 0f); Util.PlaySound(soundCycleEvent, base.gameObject, soundCycleSpeedRtpc, num2 * soundCycleSpeedRtpcScale); } public override void HandleInteractionServer(Interactor activator) { base.HandleInteractionServer(activator); base.rouletteChestController.EndCycleServer(activator); } } private class Opening : RouletteChestControllerBaseState { public static float baseDuration; public static string soundEntryEvent; private static int ActiveToOpeningStateHash = Animator.StringToHash("ActiveToOpening"); public override void OnEnter() { base.OnEnter(); PlayAnimation("Body", ActiveToOpeningStateHash); base.rouletteChestController.purchaseInteraction.Networkavailable = false; base.rouletteChestController.purchaseInteraction.costType = CostTypeIndex.None; Util.PlaySound(soundEntryEvent, base.gameObject); } public override void FixedUpdate() { base.FixedUpdate(); if (NetworkServer.active && base.fixedAge > baseDuration) { outer.SetNextState(new Opened()); } } } private class Opened : RouletteChestControllerBaseState { private static int OpenedStateHash = Animator.StringToHash("Opened"); public override void OnEnter() { base.OnEnter(); PlayAnimation("Body", OpenedStateHash); base.rouletteChestController.purchaseInteraction.Networkavailable = false; base.rouletteChestController.purchaseInteraction.costType = CostTypeIndex.None; base.rouletteChestController.networkIdentity.isPingable = false; } } public int maxEntries = 1; public float bonusTime; public AnimationCurve bonusTimeDecay; public PickupDropTable dropTable; public Transform ejectionTransform; public Vector3 localEjectionVelocity; public Animator modelAnimator; public PickupDisplay pickupDisplay; private EntityStateMachine stateMachine; private PurchaseInteraction purchaseInteraction; private NetworkIdentity networkIdentity; private static readonly float averageHgReactionTime = 0.20366667f; private static readonly float lowestHgReactionTime = 0.15f; private static readonly float recognitionWindow = 0.15f; private static readonly float minTime = lowestHgReactionTime + recognitionWindow; private static readonly float rewindTime = 0.05f; private Run.FixedTimeStamp activationTime = Run.FixedTimeStamp.positiveInfinity; private Entry[] entries = Array.Empty(); private Xoroshiro128Plus rng; private static readonly uint activationTimeDirtyBit = 1u; private static readonly uint entriesDirtyBit = 2u; private static readonly uint enabledDirtyBit = 4u; private static readonly uint allDirtyBitsMask = activationTimeDirtyBit | entriesDirtyBit; public int dropCount = 1; private int previousEntryIndexClient = -1; public UnityEvent onCycleBeginServer; public UnityEvent onCycleCompletedServer; public UnityEvent onChangedEntryClient; private bool isCycling => !activationTime.isPositiveInfinity; private float CalcEntryDuration(int i) { float time = (float)i / (float)maxEntries; return bonusTimeDecay.Evaluate(time) * bonusTime + minTime; } public override bool OnSerialize(NetworkWriter writer, bool initialState) { uint num = base.syncVarDirtyBits; if (initialState) { num = allDirtyBitsMask; } writer.WritePackedUInt32(num); if ((num & activationTimeDirtyBit) != 0) { writer.Write(activationTime); } if ((num & entriesDirtyBit) != 0) { writer.WritePackedUInt32((uint)entries.Length); for (int i = 0; i < entries.Length; i++) { writer.Write(entries[i].pickupIndex); } } return num != 0; } public override void OnDeserialize(NetworkReader reader, bool initialState) { uint num = reader.ReadPackedUInt32(); if ((num & activationTimeDirtyBit) != 0) { activationTime = reader.ReadFixedTimeStamp(); } if ((num & entriesDirtyBit) != 0) { Array.Resize(ref entries, (int)reader.ReadPackedUInt32()); Run.FixedTimeStamp endTime = activationTime; for (int i = 0; i < entries.Length; i++) { ref Entry reference = ref entries[i]; reference.pickupIndex = reader.ReadPickupIndex(); reference.endTime = endTime + CalcEntryDuration(i); endTime = reference.endTime; } } } private void Awake() { stateMachine = GetComponent(); purchaseInteraction = GetComponent(); networkIdentity = GetComponent(); } private void Start() { if (NetworkServer.active) { rng = new Xoroshiro128Plus(Run.instance.treasureRng.nextUlong); } } private void OnEnable() { SetDirtyBit(enabledDirtyBit); if ((bool)pickupDisplay) { pickupDisplay.enabled = true; } } private void OnDisable() { if ((bool)pickupDisplay) { pickupDisplay.SetPickupIndex(PickupIndex.none); pickupDisplay.enabled = false; } SetDirtyBit(enabledDirtyBit); } [Server] private void GenerateEntriesServer(Run.FixedTimeStamp startTime) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.RouletteChestController::GenerateEntriesServer(RoR2.Run/FixedTimeStamp)' called on client"); return; } Array.Resize(ref entries, maxEntries); for (int i = 0; i < entries.Length; i++) { ref Entry reference = ref entries[i]; reference.endTime = startTime + CalcEntryDuration(i); startTime = reference.endTime; } PickupIndex pickupIndex = PickupIndex.none; for (int j = 0; j < entries.Length; j++) { ref Entry reference2 = ref entries[j]; PickupIndex pickupIndex2 = dropTable.GenerateDrop(rng); if (pickupIndex2 == pickupIndex) { pickupIndex2 = dropTable.GenerateDrop(rng); } reference2.pickupIndex = pickupIndex2; pickupIndex = pickupIndex2; } SetDirtyBit(entriesDirtyBit); } [Server] public void HandleInteractionServer(Interactor activator) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.RouletteChestController::HandleInteractionServer(RoR2.Interactor)' called on client"); } else { ((RouletteChestControllerBaseState)stateMachine.state).HandleInteractionServer(activator); } } [Server] private void BeginCycleServer() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.RouletteChestController::BeginCycleServer()' called on client"); return; } activationTime = Run.FixedTimeStamp.now; SetDirtyBit(activationTimeDirtyBit); GenerateEntriesServer(activationTime); onCycleBeginServer?.Invoke(); } [Server] private void EndCycleServer([CanBeNull] Interactor activator) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.RouletteChestController::EndCycleServer(RoR2.Interactor)' called on client"); return; } float num = 0f; if ((bool)activator) { NetworkUser networkUser = Util.LookUpBodyNetworkUser(activator.gameObject); if ((object)networkUser != null) { num = RttManager.GetConnectionRTT(networkUser.connectionToClient); } } Run.FixedTimeStamp time = Run.FixedTimeStamp.now - num - rewindTime; PickupIndex pickupIndexForTime = GetPickupIndexForTime(time); EjectPickupServer(pickupIndexForTime); activationTime = Run.FixedTimeStamp.positiveInfinity; onCycleCompletedServer.Invoke(); } private void FixedUpdate() { if ((bool)pickupDisplay) { pickupDisplay.SetPickupIndex(isCycling ? GetPickupIndexForTime(Run.FixedTimeStamp.now) : PickupIndex.none); } if (NetworkClient.active) { int entryIndexForTime = GetEntryIndexForTime(Run.FixedTimeStamp.now); if (entryIndexForTime != previousEntryIndexClient) { previousEntryIndexClient = entryIndexForTime; onChangedEntryClient.Invoke(); } } if (NetworkServer.active && isCycling && entries.Length != 0) { Run.FixedTimeStamp endTime = entries[entries.Length - 1].endTime; if (endTime.hasPassed) { EndCycleServer(null); } } } private int GetEntryIndexForTime(Run.FixedTimeStamp time) { for (int i = 0; i < entries.Length; i++) { if (time < entries[i].endTime) { return i; } } if (entries.Length != 0) { return entries.Length - 1; } return -1; } private PickupIndex GetPickupIndexForTime(Run.FixedTimeStamp time) { int entryIndexForTime = GetEntryIndexForTime(time); if (entryIndexForTime != -1) { return entries[entryIndexForTime].pickupIndex; } return PickupIndex.none; } private void EjectPickupServer(PickupIndex pickupIndex) { if (!(pickupIndex == PickupIndex.none)) { for (int i = 0; i < dropCount; i++) { PickupDropletController.CreatePickupDroplet(pickupIndex, ejectionTransform.position, ejectionTransform.rotation * localEjectionVelocity); } } } private void UNetVersion() { } public override void PreStartClient() { } }