using System; using System.Collections.Generic; using System.Collections.ObjectModel; using RoR2.ConVar; using RoR2.Navigation; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Serialization; namespace RoR2; public class CombatDirector : MonoBehaviour { [Serializable] public class OnSpawnedServer : UnityEvent { } public class EliteTierDef { public float costMultiplier; public EliteDef[] eliteTypes; public Func isAvailable = (SpawnCard.EliteRules rules) => true; public bool canSelectWithoutAvailableEliteDef; private List availableDefs = new List(); public bool CanSelect(SpawnCard.EliteRules rules) { if (isAvailable(rules)) { if (!canSelectWithoutAvailableEliteDef) { return HasAnyAvailableEliteDefs(); } return true; } return false; } public bool HasAnyAvailableEliteDefs() { EliteDef[] array = eliteTypes; foreach (EliteDef eliteDef in array) { if ((bool)eliteDef && eliteDef.IsAvailable()) { return true; } } return false; } public EliteDef GetRandomAvailableEliteDef(Xoroshiro128Plus rng) { availableDefs.Clear(); EliteDef[] array = eliteTypes; foreach (EliteDef eliteDef in array) { if ((bool)eliteDef && eliteDef.IsAvailable()) { availableDefs.Add(eliteDef); } } if (availableDefs.Count > 0) { return rng.NextElementUniform(availableDefs); } return null; } } private class DirectorMoneyWave { public float interval; public float timer; public float multiplier; private float accumulatedAward; public float Update(float deltaTime, float difficultyCoefficient) { timer += deltaTime; if (timer > interval) { float num = 0.5f + (float)Run.instance.participatingPlayerCount * 0.5f; timer -= interval; float num2 = 1f; float num3 = 0.4f; accumulatedAward += interval * multiplier * (num2 + num3 * difficultyCoefficient) * num; } float num4 = Mathf.FloorToInt(accumulatedAward); accumulatedAward -= num4; return num4; } } [Header("Core Director Values")] public string customName; public float monsterCredit; [Tooltip("Monster credit that's been refunded from culling non-elite enemies. Can only be used to buy non-elite enemies.")] public float refundedMonsterCredit; public float expRewardCoefficient = 0.2f; public float goldRewardCoefficient = 1f; public float minSeriesSpawnInterval = 0.1f; public float maxSeriesSpawnInterval = 1f; public float minRerollSpawnInterval = 2.3333333f; public float maxRerollSpawnInterval = 4.3333335f; public RangeFloat[] moneyWaveIntervals; public TeamIndex teamIndex = TeamIndex.Monster; [Tooltip("How much to multiply money wave yield by.")] [Header("Optional Behaviors")] public float creditMultiplier = 1f; [Tooltip("The coefficient to multiply spawn distances. Used for combat shrines, to keep spawns nearby.")] public float spawnDistanceMultiplier = 1f; [Tooltip("The maximum distance at which enemies will spawn.")] public float maxSpawnDistance = float.PositiveInfinity; [Tooltip("Ensure that the minimum spawn distance is at least this many units away from the maxSpawnDistance")] public float minSpawnRange; public bool shouldSpawnOneWave; public bool targetPlayers = true; public bool skipSpawnIfTooCheap = true; [Tooltip("If skipSpawnIfTooCheap is true, we'll behave as though it's not set after this many consecutive skips")] public int maxConsecutiveCheapSkips = int.MaxValue; public bool resetMonsterCardIfFailed = true; public int maximumNumberToSpawnBeforeSkipping = 6; public float eliteBias = 1f; public OnSpawnedServer onSpawnedServer; [FormerlySerializedAs("_combatSquad")] public CombatSquad combatSquad; [Tooltip("A special effect for when a monster appears will be instantiated at its position. Used for combat shrine.")] public GameObject spawnEffectPrefab; public bool ignoreTeamSizeLimit; [SerializeField] private DirectorCardCategorySelection _monsterCards; public bool fallBackToStageMonsterCards = true; public static readonly List instancesList = new List(); private bool hasStartedWave; private Xoroshiro128Plus rng; private DirectorCard currentMonsterCard; private EliteTierDef currentActiveEliteTier; private EliteDef currentActiveEliteDef; private int currentMonsterCardCost; private WeightedSelection monsterCardsSelection; private int consecutiveCheapSkips; public GameObject currentSpawnTarget; private float playerRetargetTimer; private static readonly float baseEliteCostMultiplier = 6f; private static EliteTierDef[] eliteTiers; private int spawnCountInCurrentWave; public static readonly BoolConVar cvDirectorCombatDisable = new BoolConVar("director_combat_disable", ConVarFlags.SenderMustBeServer | ConVarFlags.Cheat, "0", "Disables all combat directors."); private static readonly BoolConVar cvDirectorCombatEnableInternalLogs = new BoolConVar("director_combat_enable_internal_logs", ConVarFlags.None, "0", "Enables all combat directors to print internal logging."); private DirectorMoneyWave[] moneyWaves; private bool isHalcyonShrineSpawn; private int shrineHalcyoniteDifficultyLevel; public float monsterSpawnTimer { get; set; } public DirectorCard lastAttemptedMonsterCard { get; set; } public float totalCreditsSpent { get; private set; } private WeightedSelection finalMonsterCardsSelection { get { WeightedSelection monsterSelection = monsterCardsSelection; if (monsterSelection == null) { ClassicStageInfo instance = ClassicStageInfo.instance; if ((object)instance == null) { return null; } monsterSelection = instance.monsterSelection; } return monsterSelection; } } private DirectorCardCategorySelection monsterCards { get { return _monsterCards; } set { if (_monsterCards != value) { _monsterCards = value; monsterCardsSelection = _monsterCards?.GenerateDirectorCardWeightedSelection(); } } } public static float lowestEliteCostMultiplier => eliteTiers[1].costMultiplier; private int mostExpensiveMonsterCostInDeck { get { int num = 0; for (int i = 0; i < finalMonsterCardsSelection.Count; i++) { DirectorCard value = finalMonsterCardsSelection.GetChoice(i).value; int num2 = value.cost; if (!(value.spawnCard as CharacterSpawnCard).noElites) { num2 = (int)((float)num2 * CalcHighestEliteCostMultiplier(value.spawnCard.eliteRules)); } num = Mathf.Max(num, num2); } return num; } } private void Awake() { if (NetworkServer.active) { rng = new Xoroshiro128Plus(Run.instance.stageRng.nextUint); moneyWaves = new DirectorMoneyWave[moneyWaveIntervals.Length]; for (int i = 0; i < moneyWaveIntervals.Length; i++) { moneyWaves[i] = new DirectorMoneyWave { interval = rng.RangeFloat(moneyWaveIntervals[i].min, moneyWaveIntervals[i].max), multiplier = creditMultiplier }; } monsterCardsSelection = monsterCards?.GenerateDirectorCardWeightedSelection(); } } private void OnEnable() { instancesList.Add(this); } private void OnDisable() { instancesList.Remove(this); if (NetworkServer.active && instancesList.Count > 0) { float num = 0.4f; CombatDirector combatDirector = rng.NextElementUniform(instancesList); monsterCredit *= num; combatDirector.monsterCredit += monsterCredit; Debug.LogFormat("Transfered {0} monster credits from {1} to {2}", monsterCredit, base.gameObject, combatDirector.gameObject); monsterCredit = 0f; } } private void GenerateAmbush(Vector3 victimPosition) { NodeGraph groundNodes = SceneInfo.instance.groundNodes; NodeGraph.NodeIndex nodeIndex = groundNodes.FindClosestNode(victimPosition, HullClassification.Human); NodeGraphSpider nodeGraphSpider = new NodeGraphSpider(groundNodes, HullMask.Human); nodeGraphSpider.AddNodeForNextStep(nodeIndex); List list = new List(); int num = 0; List collectedSteps = nodeGraphSpider.collectedSteps; while (nodeGraphSpider.PerformStep() && num < 8) { num++; for (int i = 0; i < collectedSteps.Count; i++) { if (IsAcceptableAmbushSpiderStep(groundNodes, nodeIndex, collectedSteps[i])) { list.Add(collectedSteps[i]); } } collectedSteps.Clear(); } for (int j = 0; j < list.Count; j++) { groundNodes.GetNodePosition(list[j].node, out var position); LegacyResourcesAPI.Load("SpawnCards/scLemurian").DoSpawn(position, Quaternion.identity, null); } } private static bool IsAcceptableAmbushSpiderStep(NodeGraph nodeGraph, NodeGraph.NodeIndex startNode, NodeGraphSpider.StepInfo stepInfo) { int num = 0; while (stepInfo.previousStep != null) { if (nodeGraph.TestNodeLineOfSight(startNode, stepInfo.node)) { return false; } stepInfo = stepInfo.previousStep; num++; if (num > 2) { return true; } } return false; } public void OverrideCurrentMonsterCard(DirectorCard overrideMonsterCard) { PrepareNewMonsterWave(overrideMonsterCard); } public void SetNextSpawnAsBoss() { WeightedSelection weightedSelection = new WeightedSelection(); int i = 0; for (int count = finalMonsterCardsSelection.Count; i < count; i++) { WeightedSelection.ChoiceInfo choice = finalMonsterCardsSelection.GetChoice(i); SpawnCard spawnCard = choice.value.spawnCard; bool isChampion = spawnCard.prefab.GetComponent().bodyPrefab.GetComponent().isChampion; bool flag = (spawnCard as CharacterSpawnCard)?.forbiddenAsBoss ?? false; if (isChampion && !flag && choice.value.IsAvailable()) { weightedSelection.AddChoice(choice); } } if (weightedSelection.Count > 0) { DirectorCard monsterCard = weightedSelection.Evaluate(rng.nextNormalizedFloat); PrepareNewMonsterWave(monsterCard); } monsterSpawnTimer = -600f; } private void PickPlayerAsSpawnTarget() { ReadOnlyCollection instances = PlayerCharacterMasterController.instances; List list = new List(); foreach (PlayerCharacterMasterController item in instances) { if (item.master.hasBody) { list.Add(item); } } if (list.Count > 0) { currentSpawnTarget = rng.NextElementUniform(list).master.GetBodyObject(); } } public void SpendAllCreditsOnMapSpawns(Transform mapSpawnTarget) { int num = 0; int num2 = 10; while (monsterCredit > 0f) { PrepareNewMonsterWave(finalMonsterCardsSelection.Evaluate(rng.nextNormalizedFloat)); bool flag = false; if ((!mapSpawnTarget) ? AttemptSpawnOnTarget(null, SceneInfo.instance.approximateMapBoundMesh ? DirectorPlacementRule.PlacementMode.RandomNormalized : DirectorPlacementRule.PlacementMode.Random) : AttemptSpawnOnTarget(mapSpawnTarget)) { num = 0; continue; } num++; if (num < num2) { continue; } break; } } public void ToggleAllCombatDirectorsOnThisObject(bool newValue) { CombatDirector[] components = GetComponents(); for (int i = 0; i < components.Length; i++) { components[i].enabled = newValue; } } private void Simulate(float deltaTime) { if (targetPlayers) { playerRetargetTimer -= deltaTime; if (playerRetargetTimer <= 0f) { playerRetargetTimer = rng.RangeFloat(1f, 10f); PickPlayerAsSpawnTarget(); } } monsterSpawnTimer -= deltaTime; if (!(monsterSpawnTimer <= 0f)) { return; } if (AttemptSpawnOnTarget(currentSpawnTarget ? currentSpawnTarget.transform : null)) { if (shouldSpawnOneWave) { hasStartedWave = true; } monsterSpawnTimer += rng.RangeFloat(minSeriesSpawnInterval, maxSeriesSpawnInterval); return; } monsterSpawnTimer += rng.RangeFloat(minRerollSpawnInterval, maxRerollSpawnInterval); if (resetMonsterCardIfFailed) { currentMonsterCard = null; } if (shouldSpawnOneWave && hasStartedWave) { base.enabled = false; } } public static bool IsEliteOnlyArtifactActive() { return RunArtifactManager.instance.IsArtifactEnabled(RoR2Content.Artifacts.eliteOnlyArtifactDef); } private static bool NotEliteOnlyArtifactActive() { return !IsEliteOnlyArtifactActive(); } [SystemInitializer(new Type[] { typeof(EliteCatalog) })] private static void Init() { eliteTiers = new EliteTierDef[6] { new EliteTierDef { costMultiplier = 1f, eliteTypes = new EliteDef[1], isAvailable = (SpawnCard.EliteRules rules) => NotEliteOnlyArtifactActive(), canSelectWithoutAvailableEliteDef = true }, new EliteTierDef { costMultiplier = baseEliteCostMultiplier, eliteTypes = new EliteDef[4] { RoR2Content.Elites.Lightning, RoR2Content.Elites.Ice, RoR2Content.Elites.Fire, DLC1Content.Elites.Earth }, isAvailable = (SpawnCard.EliteRules rules) => NotEliteOnlyArtifactActive() && rules == SpawnCard.EliteRules.Default, canSelectWithoutAvailableEliteDef = false }, new EliteTierDef { costMultiplier = Mathf.LerpUnclamped(1f, baseEliteCostMultiplier, 0.5f), eliteTypes = new EliteDef[4] { RoR2Content.Elites.LightningHonor, RoR2Content.Elites.IceHonor, RoR2Content.Elites.FireHonor, DLC1Content.Elites.EarthHonor }, isAvailable = (SpawnCard.EliteRules rules) => IsEliteOnlyArtifactActive(), canSelectWithoutAvailableEliteDef = false }, new EliteTierDef { costMultiplier = Mathf.LerpUnclamped(1f, baseEliteCostMultiplier, 0.5f), eliteTypes = new EliteDef[5] { RoR2Content.Elites.Lightning, RoR2Content.Elites.Ice, RoR2Content.Elites.Fire, DLC1Content.Elites.Earth, DLC2Content.Elites.Aurelionite }, isAvailable = (SpawnCard.EliteRules rules) => NotEliteOnlyArtifactActive() && rules == SpawnCard.EliteRules.Default && Run.instance.stageClearCount >= 2, canSelectWithoutAvailableEliteDef = false }, new EliteTierDef { costMultiplier = baseEliteCostMultiplier * 6f, eliteTypes = new EliteDef[3] { RoR2Content.Elites.Poison, RoR2Content.Elites.Haunted, DLC2Content.Elites.Bead }, isAvailable = (SpawnCard.EliteRules rules) => Run.instance.loopClearCount > 0 && rules == SpawnCard.EliteRules.Default, canSelectWithoutAvailableEliteDef = false }, new EliteTierDef { costMultiplier = baseEliteCostMultiplier, eliteTypes = new EliteDef[1] { RoR2Content.Elites.Lunar }, isAvailable = (SpawnCard.EliteRules rules) => rules == SpawnCard.EliteRules.Lunar, canSelectWithoutAvailableEliteDef = false } }; } public static float CalcHighestEliteCostMultiplier(SpawnCard.EliteRules eliteRules) { float num = 1f; for (int i = 1; i < eliteTiers.Length; i++) { if (eliteTiers[i].CanSelect(eliteRules)) { num = Mathf.Max(num, eliteTiers[i].costMultiplier); } } return num; } private void ResetEliteType() { currentActiveEliteTier = eliteTiers[0]; for (int i = 0; i < eliteTiers.Length; i++) { if (eliteTiers[i].CanSelect(currentMonsterCard.spawnCard.eliteRules)) { currentActiveEliteTier = eliteTiers[i]; break; } } currentActiveEliteDef = currentActiveEliteTier.GetRandomAvailableEliteDef(rng); } private void PrepareNewMonsterWave(DirectorCard monsterCard) { if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Preparing monster wave {0}", monsterCard.spawnCard); } currentMonsterCard = monsterCard; ResetEliteType(); if (!(currentMonsterCard.spawnCard as CharacterSpawnCard).noElites) { for (int i = 1; i < eliteTiers.Length; i++) { EliteTierDef eliteTierDef = eliteTiers[i]; if (!eliteTierDef.CanSelect(currentMonsterCard.spawnCard.eliteRules)) { if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Elite tier index {0} is unavailable", i); } continue; } float num = (float)currentMonsterCard.cost * eliteTierDef.costMultiplier * eliteBias; if (num <= monsterCredit) { currentActiveEliteTier = eliteTierDef; if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Found valid elite tier index {0}", i); } } else if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Elite tier index {0} is too expensive ({1}/{2})", i, num, monsterCredit); } } } else if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Card {0} cannot be elite. Skipping elite procedure.", currentMonsterCard.spawnCard); } currentActiveEliteDef = currentActiveEliteTier.GetRandomAvailableEliteDef(rng); if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Assigned elite index {0}", currentActiveEliteDef); } lastAttemptedMonsterCard = currentMonsterCard; spawnCountInCurrentWave = 0; } private bool AttemptSpawnOnTarget(Transform spawnTarget, DirectorPlacementRule.PlacementMode placementMode = DirectorPlacementRule.PlacementMode.Approximate) { if (currentMonsterCard == null) { _ = cvDirectorCombatEnableInternalLogs.value; if (finalMonsterCardsSelection == null) { return false; } PrepareNewMonsterWave(finalMonsterCardsSelection.Evaluate(rng.nextNormalizedFloat)); } if (spawnCountInCurrentWave >= maximumNumberToSpawnBeforeSkipping) { spawnCountInCurrentWave = 0; if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Spawn count has hit the max ({0}/{1}). Aborting spawn.", spawnCountInCurrentWave, maximumNumberToSpawnBeforeSkipping); } return false; } int num = currentMonsterCard.cost; int cost = currentMonsterCard.cost; float num2 = 1f; EliteDef eliteDef = currentActiveEliteDef; cost = (int)((float)num * currentActiveEliteTier.costMultiplier); if ((float)cost <= monsterCredit) { num = cost; num2 = currentActiveEliteTier.costMultiplier; } else { ResetEliteType(); eliteDef = currentActiveEliteDef; } if (!currentMonsterCard.IsAvailable()) { if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Spawn card {0} is invalid, aborting spawn.", currentMonsterCard.spawnCard); } return false; } if (monsterCredit < (float)num) { if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Spawn card {0} is too expensive, aborting spawn.", currentMonsterCard.spawnCard); } return false; } if (skipSpawnIfTooCheap && consecutiveCheapSkips < maxConsecutiveCheapSkips && (float)(cost * maximumNumberToSpawnBeforeSkipping) < monsterCredit) { if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Card {0} seems too cheap ({1}/{2}). Comparing against most expensive possible ({3})", currentMonsterCard.spawnCard, num * maximumNumberToSpawnBeforeSkipping, monsterCredit, mostExpensiveMonsterCostInDeck); } if (mostExpensiveMonsterCostInDeck > num) { consecutiveCheapSkips++; if (cvDirectorCombatEnableInternalLogs.value) { Debug.LogFormat("Spawn card {0} is too cheap, aborting spawn.", currentMonsterCard.spawnCard); } return false; } } SpawnCard spawnCard = currentMonsterCard.spawnCard; if (Spawn(spawnCard, eliteDef, spawnTarget, valueMultiplier: num2, placementMode: placementMode, preventOverhead: currentMonsterCard.preventOverhead, spawnDistance: currentMonsterCard.spawnDistance)) { monsterCredit -= num; totalCreditsSpent += num; spawnCountInCurrentWave++; consecutiveCheapSkips = 0; return true; } return false; } public bool Spawn(SpawnCard spawnCard, EliteDef eliteDef, Transform spawnTarget, DirectorCore.MonsterSpawnDistance spawnDistance, bool preventOverhead, float valueMultiplier = 1f, DirectorPlacementRule.PlacementMode placementMode = DirectorPlacementRule.PlacementMode.Approximate) { DirectorPlacementRule directorPlacementRule = new DirectorPlacementRule { placementMode = placementMode, spawnOnTarget = spawnTarget, preventOverhead = preventOverhead }; DirectorCore.GetMonsterSpawnDistance(spawnDistance, out directorPlacementRule.minDistance, out directorPlacementRule.maxDistance); directorPlacementRule.maxDistance = Mathf.Min(maxSpawnDistance, directorPlacementRule.maxDistance * spawnDistanceMultiplier); directorPlacementRule.minDistance = Mathf.Max(0f, Mathf.Min(directorPlacementRule.maxDistance - minSpawnRange, directorPlacementRule.minDistance * spawnDistanceMultiplier)); DirectorSpawnRequest directorSpawnRequest = new DirectorSpawnRequest(spawnCard, directorPlacementRule, rng); directorSpawnRequest.ignoreTeamMemberLimit = ignoreTeamSizeLimit; directorSpawnRequest.teamIndexOverride = teamIndex; directorSpawnRequest.onSpawnedServer = OnCardSpawned; if (!DirectorCore.instance.TrySpawnObject(directorSpawnRequest)) { Debug.LogFormat("Spawn card {0} failed to spawn. Aborting cost procedures.", spawnCard); return false; } return true; void OnCardSpawned(SpawnCard.SpawnResult result) { if (result.success) { float num = 1f; float num2 = 1f; CharacterMaster component = result.spawnedInstance.GetComponent(); GameObject bodyObject = component.GetBodyObject(); CharacterBody component2 = bodyObject.GetComponent(); if ((bool)component2) { component2.cost = valueMultiplier * (float)spawnCard.directorCreditCost; } if ((bool)combatSquad) { combatSquad.AddMember(component); } num = eliteDef?.healthBoostCoefficient ?? 1f; num2 = eliteDef?.damageBoostCoefficient ?? 1f; if (isHalcyonShrineSpawn) { if (shrineHalcyoniteDifficultyLevel > 20) { shrineHalcyoniteDifficultyLevel = 20 + shrineHalcyoniteDifficultyLevel / 100; } num += (float)shrineHalcyoniteDifficultyLevel * 0.6f; num2 += (float)shrineHalcyoniteDifficultyLevel * 0.25f; eliteDef = DLC2Content.Elites.Aurelionite; } EquipmentIndex equipmentIndex = eliteDef?.eliteEquipmentDef?.equipmentIndex ?? EquipmentIndex.None; if (equipmentIndex != EquipmentIndex.None) { component.inventory.SetEquipmentIndex(equipmentIndex); } if ((bool)combatSquad && combatSquad.grantBonusHealthInMultiplayer) { int livingPlayerCount = Run.instance.livingPlayerCount; num *= Mathf.Pow(livingPlayerCount, 1f); } component.inventory.GiveItem(RoR2Content.Items.BoostHp, Mathf.RoundToInt((num - 1f) * 10f)); component.inventory.GiveItem(RoR2Content.Items.BoostDamage, Mathf.RoundToInt((num2 - 1f) * 10f)); DeathRewards component3 = bodyObject.GetComponent(); if ((bool)component3) { float num3 = (float)spawnCard.directorCreditCost * valueMultiplier * expRewardCoefficient; component3.spawnValue = (int)Mathf.Max(1f, num3); if (num3 > Mathf.Epsilon) { component3.expReward = (uint)Mathf.Max(1f, num3 * Run.instance.compensatedDifficultyCoefficient); component3.goldReward = (uint)Mathf.Max(1f, num3 * goldRewardCoefficient * 2f * Run.instance.compensatedDifficultyCoefficient); } else { component3.expReward = 0u; component3.goldReward = 0u; } } if ((bool)spawnEffectPrefab && NetworkServer.active) { Vector3 origin = result.position; CharacterBody characterBody = component2; if ((bool)characterBody) { origin = characterBody.corePosition; } EffectManager.SpawnEffect(spawnEffectPrefab, new EffectData { origin = origin }, transmit: true); } onSpawnedServer?.Invoke(result.spawnedInstance); } } } private void FixedUpdate() { if (!cvDirectorCombatDisable.value && NetworkServer.active && (bool)Run.instance) { float compensatedDifficultyCoefficient = Run.instance.compensatedDifficultyCoefficient; for (int i = 0; i < moneyWaves.Length; i++) { float num = moneyWaves[i].Update(Time.fixedDeltaTime, compensatedDifficultyCoefficient); monsterCredit += num; } Simulate(Time.fixedDeltaTime); } } public void CombatShrineActivation(Interactor interactor, float monsterCredit, DirectorCard chosenDirectorCard) { base.enabled = true; this.monsterCredit += monsterCredit; OverrideCurrentMonsterCard(chosenDirectorCard); monsterSpawnTimer = 0f; CharacterMaster component = chosenDirectorCard.spawnCard.prefab.GetComponent(); if ((bool)component) { CharacterBody component2 = component.bodyPrefab.GetComponent(); if ((bool)component2) { Chat.SubjectFormatChatMessage subjectFormatChatMessage = new Chat.SubjectFormatChatMessage(); subjectFormatChatMessage.subjectAsCharacterBody = interactor.GetComponent(); subjectFormatChatMessage.baseToken = "SHRINE_COMBAT_USE_MESSAGE"; subjectFormatChatMessage.paramTokens = new string[1] { component2.baseNameToken }; Chat.SendBroadcastChat(subjectFormatChatMessage); } } } public DirectorCard SelectMonsterCardForCombatShrine(float monsterCredit) { WeightedSelection weightedSelection = Util.CreateReasonableDirectorCardSpawnList(monsterCredit, maximumNumberToSpawnBeforeSkipping, 1); if (weightedSelection.Count == 0) { return null; } return weightedSelection.Evaluate(rng.nextNormalizedFloat); } public void HalcyoniteShrineActivation(float monsterCredit, DirectorCard chosenDirectorCard, int difficultyLevel, Transform shrineTransform) { base.enabled = true; this.monsterCredit += monsterCredit; isHalcyonShrineSpawn = true; shrineHalcyoniteDifficultyLevel = difficultyLevel; OverrideCurrentMonsterCard(chosenDirectorCard); monsterSpawnTimer = 0f; Util.PlaySound("Play_halcyonite_spawn", shrineTransform.gameObject); } public void SetMonsterCredit(float newMonsterCredit) { monsterCredit = newMonsterCredit; } }