using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.InteropServices; using EntityStates; using EntityStates.Missions.Arena.NullWard; using RoR2.CharacterAI; using RoR2.Navigation; using UnityEngine; using UnityEngine.Networking; namespace RoR2; [RequireComponent(typeof(EntityStateMachine))] [RequireComponent(typeof(Inventory))] public class ArenaMissionController : NetworkBehaviour { public class ArenaMissionBaseState : EntityState { protected ArenaMissionController arenaMissionController => instance; } public class MissionCompleted : ArenaMissionBaseState { public override void OnEnter() { base.OnEnter(); base.arenaMissionController.clearedEffect.SetActive(value: true); } } [Serializable] public struct MonsterItemStackData { public PickupDropTable dropTable; public int stacks; } [Header("Behavior Values")] public float baseMonsterCredit; public float creditMultiplierPerRound; public int minimumNumberToSpawnPerMonsterType; public int totalRoundsMax; public int maximumNumberToSpawnBeforeSkipping; public float spawnDistanceMultiplier; public float eliteBias; [Header("Cached Components")] public GameObject[] nullWards; public GameObject monsterSpawnPosition; public GameObject rewardSpawnPosition; public CombatDirector[] combatDirectors; public GameObject clearedEffect; public GameObject killEffectPrefab; public GameObject fogDamagePrefab; public PortalSpawner[] completionPortalSpawners; public MonsterItemStackData[] monsterItemStackOrder; public PickupDropTable[] playerRewardOrder; [SerializeField] private int numRewardOptions; [SerializeField] private GameObject pickupPrefab; private EntityStateMachine mainStateMachine; private Xoroshiro128Plus rng; private List activeMonsterCards = new List(); public readonly SyncListInt syncActiveMonsterBodies = new SyncListInt(); private WeightedSelection availableMonsterCards; private float cachedDifficultyCoefficient; [SyncVar] private int _clearedRounds; private int nextItemStackIndex; private GameObject fogDamageInstance; private static int kListsyncActiveMonsterBodies; public int currentRound { get; private set; } public int clearedRounds { get { return _clearedRounds; } private set { Network_clearedRounds = value; } } public static ArenaMissionController instance { get; private set; } private float creditsThisRound => (baseMonsterCredit + creditMultiplierPerRound * (float)(currentRound - 1)) * cachedDifficultyCoefficient; public Inventory inventory { get; private set; } public int Network_clearedRounds { get { return _clearedRounds; } [param: In] set { SetSyncVar(value, ref _clearedRounds, 2u); } } public static event Action onBeatArena; public static event Action onInstanceChangedGlobal; private void Awake() { mainStateMachine = EntityStateMachine.FindByCustomName(base.gameObject, "Main"); inventory = GetComponent(); syncActiveMonsterBodies.InitializeBehaviour(this, kListsyncActiveMonsterBodies); } private void OnEnable() { instance = SingletonHelper.Assign(instance, this); ArenaMissionController.onInstanceChangedGlobal?.Invoke(); SceneDirector.onPreGeneratePlayerSpawnPointsServer += OnPreGeneratePlayerSpawnPointsServer; } private void OnDisable() { SceneDirector.onPreGeneratePlayerSpawnPointsServer -= OnPreGeneratePlayerSpawnPointsServer; instance = SingletonHelper.Unassign(instance, this); ArenaMissionController.onInstanceChangedGlobal?.Invoke(); } private void OnPreGeneratePlayerSpawnPointsServer(SceneDirector sceneDirector, ref Action generationMethod) { generationMethod = GeneratePlayerSpawnPointsServer; } private void GeneratePlayerSpawnPointsServer() { if (nullWards.Length == 0) { return; } Vector3 position = nullWards[0].transform.position; NodeGraph groundNodes = SceneInfo.instance.groundNodes; NodeGraphSpider nodeGraphSpider = new NodeGraphSpider(SceneInfo.instance.groundNodes, HullMask.Human); nodeGraphSpider.AddNodeForNextStep(groundNodes.FindClosestNode(position, HullClassification.Human)); for (int i = 0; i < 4; i++) { nodeGraphSpider.PerformStep(); if (nodeGraphSpider.collectedSteps.Count > 16) { break; } } for (int j = 0; j < nodeGraphSpider.collectedSteps.Count; j++) { NodeGraphSpider.StepInfo stepInfo = nodeGraphSpider.collectedSteps[j]; SpawnPoint.AddSpawnPoint(groundNodes, stepInfo.node, rng); } } [Server] public override void OnStartServer() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::OnStartServer()' called on client"); return; } base.OnStartServer(); fogDamageInstance = UnityEngine.Object.Instantiate(fogDamagePrefab); FogDamageController component = fogDamageInstance.GetComponent(); GameObject[] array = nullWards; foreach (GameObject gameObject in array) { component.AddSafeZone(gameObject.GetComponent()); } NetworkServer.Spawn(fogDamageInstance); cachedDifficultyCoefficient = Run.instance.difficultyCoefficient; rng = new Xoroshiro128Plus(Run.instance.stageRng.nextUint); InitCombatDirectors(); Util.ShuffleArray(nullWards, rng); ReadyNextNullWard(); availableMonsterCards = Util.CreateReasonableDirectorCardSpawnList(baseMonsterCredit * cachedDifficultyCoefficient, maximumNumberToSpawnBeforeSkipping, minimumNumberToSpawnPerMonsterType); _ = availableMonsterCards.Count; } [Server] private void ReadyNextNullWard() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::ReadyNextNullWard()' called on client"); return; } if (currentRound > nullWards.Length) { Debug.LogError("Out of null wards! Aborting."); return; } EntityStateMachine component = nullWards[currentRound].GetComponent(); component.initialStateType = new SerializableEntityStateType(typeof(WardOnAndReady)); component.SetNextState(new WardOnAndReady()); } [Server] private void InitCombatDirectors() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::InitCombatDirectors()' called on client"); return; } for (int i = 0; i < combatDirectors.Length; i++) { CombatDirector obj = combatDirectors[i]; obj.maximumNumberToSpawnBeforeSkipping = maximumNumberToSpawnBeforeSkipping; obj.onSpawnedServer.AddListener(ModifySpawnedMasters); obj.spawnDistanceMultiplier = spawnDistanceMultiplier; obj.eliteBias = eliteBias; } } [Server] public void BeginRound() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::BeginRound()' called on client"); return; } currentRound++; switch (currentRound) { case 1: AddMonsterType(); break; case 2: AddItemStack(); break; case 3: AddMonsterType(); break; case 4: AddItemStack(); break; case 5: AddMonsterType(); break; case 6: AddItemStack(); break; case 7: AddMonsterType(); break; case 8: AddItemStack(); break; case 9: AddItemStack(); break; } int count = activeMonsterCards.Count; for (int i = 0; i < count; i++) { DirectorCard directorCard = activeMonsterCards[i]; float num = creditsThisRound / (float)count; float creditMultiplier = creditMultiplierPerRound * (float)currentRound / (float)count; if (i > combatDirectors.Length) { Debug.LogError("Trying to activate more combat directors than available. Aborting."); break; } CombatDirector obj = combatDirectors[i]; obj.monsterCredit += num; obj.creditMultiplier = creditMultiplier; obj.currentSpawnTarget = monsterSpawnPosition; obj.OverrideCurrentMonsterCard(directorCard); obj.monsterSpawnTimer = 0f; obj.enabled = true; Debug.LogFormat("Enabling director {0} with {1} credits to spawn {2}", i, num, directorCard.spawnCard.name); } } [Server] public void ModifySpawnedMasters(GameObject targetGameObject) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::ModifySpawnedMasters(UnityEngine.GameObject)' called on client"); return; } CharacterMaster component = targetGameObject.GetComponent(); BaseAI ai = component.GetComponent(); if ((bool)ai) { ai.onBodyDiscovered += OnBodyDiscovered; } CharacterBody body = component.GetBody(); if ((bool)body) { EntityStateMachine[] components = body.GetComponents(); foreach (EntityStateMachine obj in components) { obj.initialStateType = obj.mainStateType; } } component.inventory.AddItemsFrom(inventory); void OnBodyDiscovered(CharacterBody newBody) { ai.ForceAcquireNearestEnemyIfNoCurrentEnemy(); ai.onBodyDiscovered -= OnBodyDiscovered; } } [Server] public void EndRound() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::EndRound()' called on client"); return; } clearedRounds++; if (currentRound < totalRoundsMax) { ReadyNextNullWard(); } else { if ((bool)fogDamageInstance) { UnityEngine.Object.Destroy(fogDamageInstance); fogDamageInstance = null; } ArenaMissionController.onBeatArena?.Invoke(); mainStateMachine.SetNextState(new MissionCompleted()); Chat.SendBroadcastChat(new Chat.SimpleChatMessage { baseToken = "ARENA_END" }); PortalSpawner[] array = completionPortalSpawners; for (int i = 0; i < array.Length; i++) { array[i].AttemptSpawnPortalServer(); } } for (int j = 0; j < combatDirectors.Length; j++) { CombatDirector obj = combatDirectors[j]; obj.enabled = false; obj.monsterCredit = 0f; } ReadOnlyCollection teamMembers = TeamComponent.GetTeamMembers(TeamIndex.Monster); for (int num = teamMembers.Count - 1; num >= 0; num--) { teamMembers[num].body.healthComponent.Suicide(base.gameObject, base.gameObject, DamageType.VoidDeath); } int participatingPlayerCount = Run.instance.participatingPlayerCount; if (participatingPlayerCount == 0 || !rewardSpawnPosition) { return; } PickupIndex[] array2 = Array.Empty(); int num2 = currentRound - 1; if (num2 < playerRewardOrder.Length) { array2 = playerRewardOrder[num2]?.GenerateUniqueDrops(numRewardOptions, rng); } if (array2.Length != 0) { ItemTier itemTier = PickupCatalog.GetPickupDef(array2[0]).itemTier; int num3 = participatingPlayerCount; float angle = 360f / (float)num3; Vector3 vector = Quaternion.AngleAxis(UnityEngine.Random.Range(0, 360), Vector3.up) * (Vector3.up * 40f + Vector3.forward * 5f); Quaternion quaternion = Quaternion.AngleAxis(angle, Vector3.up); int num4 = 0; while (num4 < num3) { GenericPickupController.CreatePickupInfo createPickupInfo = default(GenericPickupController.CreatePickupInfo); createPickupInfo.pickerOptions = PickupPickerController.GenerateOptionsFromArray(array2); createPickupInfo.prefabOverride = pickupPrefab; createPickupInfo.position = rewardSpawnPosition.transform.position; createPickupInfo.rotation = Quaternion.identity; createPickupInfo.pickupIndex = PickupCatalog.FindPickupIndex(itemTier); GenericPickupController.CreatePickupInfo pickupInfo = createPickupInfo; PickupDropletController.CreatePickupDroplet(pickupInfo, pickupInfo.position, vector); num4++; vector = quaternion * vector; } } } [Server] private void AddMonsterType() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::AddMonsterType()' called on client"); } else if (availableMonsterCards.Count != 0) { int num = availableMonsterCards.EvaluateToChoiceIndex(rng.nextNormalizedFloat); DirectorCard value = availableMonsterCards.choices[num].value; activeMonsterCards.Add(value); syncActiveMonsterBodies.Add((int)(value.spawnCard.prefab.GetComponent()?.bodyPrefab.GetComponent()?.bodyIndex ?? BodyIndex.None)); availableMonsterCards.RemoveChoice(num); CharacterBody component = value.spawnCard.prefab.GetComponent().bodyPrefab.GetComponent(); Chat.SubjectFormatChatMessage subjectFormatChatMessage = new Chat.SubjectFormatChatMessage(); subjectFormatChatMessage.baseToken = "ARENA_ADD_MONSTER"; subjectFormatChatMessage.paramTokens = new string[1] { component.baseNameToken }; Chat.SendBroadcastChat(subjectFormatChatMessage); } } [Server] private void AddItemStack() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.ArenaMissionController::AddItemStack()' called on client"); return; } PickupIndex pickupIndex = PickupIndex.none; if (nextItemStackIndex < monsterItemStackOrder.Length) { PickupDropTable dropTable = monsterItemStackOrder[nextItemStackIndex].dropTable; if ((bool)dropTable) { pickupIndex = dropTable.GenerateDrop(rng); } } if (pickupIndex != PickupIndex.none) { PickupDef pickupDef = PickupCatalog.GetPickupDef(pickupIndex); ItemIndex itemIndex = pickupDef.itemIndex; inventory.GiveItem(itemIndex, monsterItemStackOrder[nextItemStackIndex].stacks); Chat.SendBroadcastChat(new Chat.PlayerPickupChatMessage { baseToken = "ARENA_ADD_ITEM", pickupToken = pickupDef.nameToken, pickupColor = pickupDef.baseColor }); } nextItemStackIndex++; } private void UNetVersion() { } protected static void InvokeSyncListsyncActiveMonsterBodies(NetworkBehaviour obj, NetworkReader reader) { if (!NetworkClient.active) { Debug.LogError("SyncList syncActiveMonsterBodies called on server."); } else { ((ArenaMissionController)obj).syncActiveMonsterBodies.HandleMsg(reader); } } static ArenaMissionController() { kListsyncActiveMonsterBodies = 1496902198; NetworkBehaviour.RegisterSyncListDelegate(typeof(ArenaMissionController), kListsyncActiveMonsterBodies, InvokeSyncListsyncActiveMonsterBodies); NetworkCRC.RegisterBehaviour("ArenaMissionController", 0); } public override bool OnSerialize(NetworkWriter writer, bool forceAll) { if (forceAll) { SyncListInt.WriteInstance(writer, syncActiveMonsterBodies); writer.WritePackedUInt32((uint)_clearedRounds); return true; } bool flag = false; if ((base.syncVarDirtyBits & (true ? 1u : 0u)) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } SyncListInt.WriteInstance(writer, syncActiveMonsterBodies); } if ((base.syncVarDirtyBits & 2u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.WritePackedUInt32((uint)_clearedRounds); } if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); } return flag; } public override void OnDeserialize(NetworkReader reader, bool initialState) { if (initialState) { SyncListInt.ReadReference(reader, syncActiveMonsterBodies); _clearedRounds = (int)reader.ReadPackedUInt32(); return; } int num = (int)reader.ReadPackedUInt32(); if (((uint)num & (true ? 1u : 0u)) != 0) { SyncListInt.ReadReference(reader, syncActiveMonsterBodies); } if (((uint)num & 2u) != 0) { _clearedRounds = (int)reader.ReadPackedUInt32(); } } public override void PreStartClient() { } }