using System; using System.Runtime.InteropServices; using RoR2.Navigation; using UnityEngine; using UnityEngine.Networking; namespace RoR2; public class InfiniteTowerRun : Run { [Serializable] public struct EnemyItemEntry { public PickupDropTable dropTable; public int stacks; } private const ulong waveRngSalt = 14312uL; private const ulong enemyItemRngSalt = 1535uL; private const ulong safeWardRngSalt = 769876uL; [SerializeField] [Header("Infinite Tower Settings")] [Tooltip("If all else fails, use this wave prefab")] private GameObject defaultWavePrefab; [Tooltip("Selects a wave from the first available category in this list")] [SerializeField] private InfiniteTowerWaveCategory[] waveCategories; [Tooltip("Use this indicator for enemies by default")] [SerializeField] private GameObject defaultWaveEnemyIndicatorPrefab; [Tooltip("The repeating pattern of drop tables to use when selecting items for the enemy team")] [SerializeField] private EnemyItemEntry[] enemyItemPattern; [SerializeField] [Tooltip("The number of waves before you give the enemy team the next item in the pattern (e.g., \"every Nth wave\")")] private int enemyItemPeriod; [SerializeField] [Tooltip("The reference inventory we use to store which items enemies should get")] private Inventory enemyInventory; [Tooltip("The number of waves before you transition to the next stage (e.g., \"every Nth wave\").")] [SerializeField] private int stageTransitionPeriod; [Tooltip("Spawn card for the stage transition portal")] [SerializeField] private InteractableSpawnCard stageTransitionPortalCard; [Tooltip("Maximum spawn distance for the stage transition portal.")] [SerializeField] private float stageTransitionPortalMaxDistance; [Tooltip("The chat message that's broadcasted when spawning the stage transition portal.")] [SerializeField] private string stageTransitionChatToken; [Tooltip("The prefab with the FogDamageController attached")] [SerializeField] private GameObject fogDamagePrefab; [SerializeField] [Tooltip("The maximum distance to spawn players from the safe ward")] private float spawnMaxRadius; [Tooltip("Spawn card for the safe ward that is spawned at the beginning of the run")] [SerializeField] private InteractableSpawnCard initialSafeWardCard; [Tooltip("Spawn card for the safe wards (after the first one)")] [SerializeField] private InteractableSpawnCard safeWardCard; [Tooltip("The effect to spawn when a player is revived at the end of a wave")] [SerializeField] private GameObject playerRespawnEffectPrefab; [Tooltip("The number of credits the SceneDirector uses to spawn interactables")] [SerializeField] private int interactableCredits; [Tooltip("Remove all items with these tags from the item pools")] [SerializeField] private ItemTag[] blacklistedTags; [Tooltip("Remove these items from the pool")] [SerializeField] private ItemDef[] blacklistedItems; [SyncVar] private int _waveIndex; [SyncVar] private NetworkInstanceId waveInstanceId; private InfiniteTowerWaveController _waveController; private Xoroshiro128Plus waveRng; private Xoroshiro128Plus enemyItemRng; private Xoroshiro128Plus safeWardRng; private int enemyItemPatternIndex; private InfiniteTowerSafeWardController safeWardController; private FogDamageController fogDamageController; public int waveIndex => _waveIndex; public InfiniteTowerWaveController waveController => _waveController; public override bool spawnWithPod => false; public override bool canFamilyEventTrigger => false; public override bool autoGenerateSpawnPoints => false; public GameObject waveInstance => Util.FindNetworkObject(waveInstanceId); public int Network_waveIndex { get { return _waveIndex; } [param: In] set { SetSyncVar(value, ref _waveIndex, 64u); } } public NetworkInstanceId NetworkwaveInstanceId { get { return waveInstanceId; } [param: In] set { SetSyncVar(value, ref waveInstanceId, 128u); } } public static event Action onWaveInitialized; public static event Action onAllEnemiesDefeatedServer; public override GameObject InstantiateUi(Transform uiRoot) { GameObject gameObject = base.InstantiateUi(uiRoot); if ((bool)_waveController) { _waveController.InstantiateUi(gameObject.transform); } return gameObject; } public override void OverrideRuleChoices(RuleChoiceMask mustInclude, RuleChoiceMask mustExclude, ulong runSeed) { base.OverrideRuleChoices(mustInclude, mustExclude, base.seed); ItemIndex itemIndex = ItemIndex.Count; for (ItemIndex itemCount = (ItemIndex)ItemCatalog.itemCount; itemIndex < itemCount; itemIndex++) { ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex); bool flag = Array.IndexOf(blacklistedItems, itemDef) != -1; if (!flag) { ItemTag[] array = blacklistedTags; foreach (ItemTag itemTag in array) { if (itemDef.ContainsTag(itemTag)) { flag = true; break; } } } if (flag) { RuleChoiceDef ruleChoiceDef = RuleCatalog.FindRuleDef("Items." + itemDef.name)?.FindChoice("Off"); if (ruleChoiceDef != null) { ForceChoice(mustInclude, mustExclude, ruleChoiceDef); } } } } [Server] public void ResetSafeWard() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::ResetSafeWard()' called on client"); return; } if ((bool)safeWardController) { if ((bool)fogDamageController) { fogDamageController.RemoveSafeZone(safeWardController.safeZone); } safeWardController.SelfDestruct(); } SpawnSafeWard(safeWardCard, new DirectorPlacementRule { placementMode = DirectorPlacementRule.PlacementMode.Random }); } [Server] public void MoveSafeWard() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::MoveSafeWard()' called on client"); } else if ((bool)safeWardController) { safeWardController.RandomizeLocation(safeWardRng); safeWardController.onActivated += OnSafeWardActivated; } } public bool IsStageTransitionWave() { if (stageTransitionPeriod != 0) { return waveIndex % stageTransitionPeriod == 0; } return true; } public override Vector3 FindSafeTeleportPosition(CharacterBody characterBody, Transform targetDestination) { if ((bool)safeWardController && !targetDestination) { return base.FindSafeTeleportPosition(characterBody, safeWardController.transform); } return base.FindSafeTeleportPosition(characterBody, targetDestination); } public override Vector3 FindSafeTeleportPosition(CharacterBody characterBody, Transform targetDestination, float idealMinDistance, float idealMaxDistance) { if ((bool)safeWardController && !targetDestination) { return base.FindSafeTeleportPosition(characterBody, safeWardController.transform, idealMinDistance, idealMaxDistance); } return base.FindSafeTeleportPosition(characterBody, targetDestination, idealMinDistance, idealMaxDistance); } [Server] private void OnSafeWardActivated(InfiniteTowerSafeWardController safeWard) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::OnSafeWardActivated(RoR2.InfiniteTowerSafeWardController)' called on client"); return; } safeWardController.onActivated -= OnSafeWardActivated; if ((bool)_waveController) { _waveController.ForceFinish(); CleanUpCurrentWave(); } BeginNextWave(); } [Server] private void AdvanceWave() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::AdvanceWave()' called on client"); return; } Network_waveIndex = _waveIndex + 1; if (_waveIndex % enemyItemPeriod != 0) { return; } EnemyItemEntry enemyItemEntry = enemyItemPattern[enemyItemPatternIndex++ % enemyItemPattern.Length]; if (!enemyItemEntry.dropTable) { return; } PickupIndex pickupIndex = enemyItemEntry.dropTable.GenerateDrop(enemyItemRng); if (pickupIndex != PickupIndex.none) { PickupDef pickupDef = PickupCatalog.GetPickupDef(pickupIndex); if (pickupDef != null) { enemyInventory.GiveItem(pickupDef.itemIndex, enemyItemEntry.stacks); Chat.SendBroadcastChat(new Chat.PlayerPickupChatMessage { baseToken = "INFINITETOWER_ADD_ITEM", pickupToken = pickupDef.nameToken, pickupColor = pickupDef.baseColor }); } } } [Server] private void BeginNextWave() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::BeginNextWave()' called on client"); return; } AdvanceWave(); GameObject original = defaultWavePrefab; InfiniteTowerWaveCategory[] array = waveCategories; foreach (InfiniteTowerWaveCategory infiniteTowerWaveCategory in array) { if (infiniteTowerWaveCategory.IsAvailable(this)) { original = infiniteTowerWaveCategory.SelectWavePrefab(this, waveRng); break; } } GameObject gameObject = UnityEngine.Object.Instantiate(original, base.transform); NetworkServer.Spawn(gameObject); NetworkwaveInstanceId = gameObject.GetComponent().netId; RecalculateDifficultyCoefficentInternal(); } protected override void Start() { Stage.onServerStageBegin += OnServerStageBegin; Stage.onServerStageComplete += OnServerStageComplete; SceneDirector.onPrePopulateSceneServer += OnPrePopulateSceneServer; base.Start(); } protected override void OnDestroy() { Stage.onServerStageBegin -= OnServerStageBegin; Stage.onServerStageComplete -= OnServerStageComplete; SceneDirector.onPrePopulateSceneServer -= OnPrePopulateSceneServer; if ((bool)safeWardController) { UnityEngine.Object.Destroy(safeWardController.gameObject); safeWardController = null; } CleanUpCurrentWave(); base.OnDestroy(); } private void OnServerStageBegin(Stage stage) { } private void OnServerStageComplete(Stage stage) { PerformStageCleanUp(); } private void OnPrePopulateSceneServer(SceneDirector sceneDirector) { PerformStageCleanUp(); if ((bool)fogDamagePrefab) { GameObject gameObject = UnityEngine.Object.Instantiate(fogDamagePrefab, Stage.instance.transform); NetworkServer.Spawn(gameObject); fogDamageController = gameObject.GetComponent(); } sceneDirector.interactableCredit = interactableCredits; DirectorPlacementRule placementRule = new DirectorPlacementRule { placementMode = DirectorPlacementRule.PlacementMode.Random }; SpawnSafeWard(initialSafeWardCard, placementRule); if (!safeWardController) { return; } Vector3 position = safeWardController.transform.position; NodeGraph nodeGraph = SceneInfo.instance.GetNodeGraph(MapNodeGroup.GraphType.Ground); foreach (NodeGraph.NodeIndex item in nodeGraph.FindNodesInRangeWithFlagConditions(position, 0f, spawnMaxRadius, HullMask.Human, NodeFlags.None, NodeFlags.NoCharacterSpawn, preventOverhead: false)) { if (nodeGraph.GetNodePosition(item, out var position2)) { SpawnPoint.AddSpawnPoint(position2, Quaternion.LookRotation(position, Vector3.up)); } } } protected override void FixedUpdate() { base.FixedUpdate(); if (NetworkServer.active && (bool)waveInstance && (bool)waveController && waveController.isFinished && !IsStageTransitionWave()) { CleanUpCurrentWave(); BeginNextWave(); } if ((bool)waveInstance) { if (!_waveController) { InitializeWaveController(); } } else { _waveController = null; } } protected override void RecalculateDifficultyCoefficentInternal() { DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(base.selectedDifficulty); float num = 1.5f * (float)waveIndex; float num2 = 0.0506f * difficultyDef.scalingValue; float num3 = Mathf.Pow(1.02f, waveIndex); difficultyCoefficient = (1f + num2 * num) * num3; compensatedDifficultyCoefficient = difficultyCoefficient; base.ambientLevel = Mathf.Min((difficultyCoefficient - 1f) / 0.33f + 1f, 9999f); int num4 = base.ambientLevelFloor; base.ambientLevelFloor = Mathf.FloorToInt(base.ambientLevel); if (num4 != base.ambientLevelFloor && num4 != 0 && base.ambientLevelFloor > num4) { OnAmbientLevelUp(); } } private void InitializeWaveController() { _waveController = waveInstance.GetComponent(); if (!_waveController) { return; } if (NetworkServer.active) { _waveController.Initialize(_waveIndex, enemyInventory, safeWardController.gameObject); } foreach (GameObject uiInstance in base.uiInstances) { _waveController.InstantiateUi(uiInstance.transform); } _waveController.PlayBeginSound(); _waveController.defaultEnemyIndicatorPrefab = defaultWaveEnemyIndicatorPrefab; _waveController.onAllEnemiesDefeatedServer += OnWaveAllEnemiesDefeatedServer; InfiniteTowerRun.onWaveInitialized?.Invoke(_waveController); } private void OnWaveAllEnemiesDefeatedServer(InfiniteTowerWaveController wc) { InfiniteTowerRun.onAllEnemiesDefeatedServer?.Invoke(wc); if (base.isGameOverServer) { return; } foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { CharacterMaster master = instance.master; if (!instance.isConnected || !master.IsDeadAndOutOfLivesServer()) { continue; } Vector3 vector = master.deathFootPosition; if ((bool)safeWardController) { vector = TeleportHelper.FindSafeTeleportDestination(safeWardController.transform.position, master.bodyPrefab.GetComponent(), RoR2Application.rng) ?? vector; } master.Respawn(vector, Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f)); CharacterBody body = master.GetBody(); if ((bool)body) { body.AddTimedBuff(RoR2Content.Buffs.Immune, 3f); EntityStateMachine[] components = body.GetComponents(); foreach (EntityStateMachine obj in components) { obj.initialStateType = obj.mainStateType; } if ((bool)playerRespawnEffectPrefab) { EffectManager.SpawnEffect(playerRespawnEffectPrefab, new EffectData { origin = vector, rotation = body.transform.rotation }, transmit: true); } } } if (IsStageTransitionWave()) { PickNextStageSceneFromCurrentSceneDestinations(); DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(stageTransitionPortalCard, new DirectorPlacementRule { minDistance = 0f, maxDistance = stageTransitionPortalMaxDistance, placementMode = DirectorPlacementRule.PlacementMode.Approximate, position = safeWardController.transform.position, spawnOnTarget = safeWardController.transform }, safeWardRng)); Chat.SendBroadcastChat(new Chat.SimpleChatMessage { baseToken = stageTransitionChatToken }); if ((bool)safeWardController) { safeWardController.WaitForPortal(); } } } protected override bool ShouldUpdateRunStopwatch() { return true; } protected override void OnSeedSet() { waveRng = new Xoroshiro128Plus(base.seed ^ 0x37E8); enemyItemRng = new Xoroshiro128Plus(base.seed ^ 0x5FF); safeWardRng = new Xoroshiro128Plus(base.seed ^ 0xBBF54); } private void CleanUpCurrentWave() { if ((bool)_waveController) { _waveController.onAllEnemiesDefeatedServer -= OnWaveAllEnemiesDefeatedServer; _waveController = null; } if ((bool)waveInstance) { UnityEngine.Object.Destroy(waveInstance); } NetworkwaveInstanceId = NetworkInstanceId.Invalid; } private void SpawnSafeWard(InteractableSpawnCard spawnCard, DirectorPlacementRule placementRule) { GameObject gameObject = DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(spawnCard, placementRule, safeWardRng)); if ((bool)gameObject) { NetworkServer.Spawn(gameObject); safeWardController = gameObject.GetComponent(); if ((bool)safeWardController) { safeWardController.onActivated += OnSafeWardActivated; } HoldoutZoneController component = gameObject.GetComponent(); if ((bool)component) { component.calcAccumulatedCharge += CalcHoldoutZoneCharge; } if ((bool)fogDamageController) { fogDamageController.AddSafeZone(safeWardController.safeZone); } } else { Debug.LogError("Unable to spawn safe ward instance. Are there any ground nodes?"); } } private void CalcHoldoutZoneCharge(ref float charge) { if ((bool)waveController) { float num = waveController.GetNormalizedProgress(); if (waveController.GetSquadCount() > 0) { num = Mathf.Min(num, 0.99f); } charge = num; } else { charge = 0f; } } [Server] public override void HandlePlayerFirstEntryAnimation(CharacterBody body, Vector3 spawnPosition, Quaternion spawnRotation) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::HandlePlayerFirstEntryAnimation(RoR2.CharacterBody,UnityEngine.Vector3,UnityEngine.Quaternion)' called on client"); } else { body.SetBodyStateToPreferredInitialState(); } } private void PerformStageCleanUp() { safeWardController = null; if ((bool)fogDamageController) { UnityEngine.Object.Destroy(fogDamageController.gameObject); } CleanUpCurrentWave(); } private void UNetVersion() { } public override bool OnSerialize(NetworkWriter writer, bool forceAll) { bool flag = base.OnSerialize(writer, forceAll); if (forceAll) { writer.WritePackedUInt32((uint)_waveIndex); writer.Write(waveInstanceId); return true; } bool flag2 = false; if ((base.syncVarDirtyBits & 0x40u) != 0) { if (!flag2) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag2 = true; } writer.WritePackedUInt32((uint)_waveIndex); } if ((base.syncVarDirtyBits & 0x80u) != 0) { if (!flag2) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag2 = true; } writer.Write(waveInstanceId); } if (!flag2) { writer.WritePackedUInt32(base.syncVarDirtyBits); } return flag2 || flag; } public override void OnDeserialize(NetworkReader reader, bool initialState) { base.OnDeserialize(reader, initialState); if (initialState) { _waveIndex = (int)reader.ReadPackedUInt32(); waveInstanceId = reader.ReadNetworkId(); return; } int num = (int)reader.ReadPackedUInt32(); if (((uint)num & 0x40u) != 0) { _waveIndex = (int)reader.ReadPackedUInt32(); } if (((uint)num & 0x80u) != 0) { waveInstanceId = reader.ReadNetworkId(); } } public override void PreStartClient() { base.PreStartClient(); } }