443 lines
14 KiB
C#
443 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using RoR2.Navigation;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.Networking;
|
|
|
|
namespace RoR2;
|
|
|
|
public class SceneDirector : MonoBehaviour
|
|
{
|
|
private struct NodeDistanceSqrPair
|
|
{
|
|
public NodeGraph.NodeIndex nodeIndex;
|
|
|
|
public float distanceSqr;
|
|
}
|
|
|
|
public delegate void GenerateSpawnPointsDelegate(SceneDirector sceneDirector, ref Action generationMethod);
|
|
|
|
public SpawnCard teleporterSpawnCard;
|
|
|
|
public float expRewardCoefficient;
|
|
|
|
public float eliteBias;
|
|
|
|
public float spawnDistanceMultiplier;
|
|
|
|
private int monsterCredit;
|
|
|
|
public GameObject teleporterInstance;
|
|
|
|
private Xoroshiro128Plus rng;
|
|
|
|
public DirectorCard lumerianEgg;
|
|
|
|
private InteractableSpawnCard halcyonShrine;
|
|
|
|
private static readonly WeightedSelection<DirectorCard> cardSelector = new WeightedSelection<DirectorCard>();
|
|
|
|
public int interactableCredit { get; set; }
|
|
|
|
public float onPopulateCreditMultiplier { get; set; } = 1f;
|
|
|
|
|
|
public static event Action<SceneDirector, DirectorCardCategorySelection> onGenerateInteractableCardSelection;
|
|
|
|
public static event GenerateSpawnPointsDelegate onPreGeneratePlayerSpawnPointsServer;
|
|
|
|
public static event Action<SceneDirector> onPrePopulateSceneServer;
|
|
|
|
public static event Action<SceneDirector> onPrePopulateMonstersSceneServer;
|
|
|
|
public static event Action<SceneDirector> onPostPopulateSceneServer;
|
|
|
|
private void Start()
|
|
{
|
|
if (!NetworkServer.active)
|
|
{
|
|
return;
|
|
}
|
|
rng = new Xoroshiro128Plus(Run.instance.stageRng.nextUint);
|
|
float num = 0.5f + (float)Run.instance.participatingPlayerCount * 0.5f;
|
|
ClassicStageInfo component = SceneInfo.instance.GetComponent<ClassicStageInfo>();
|
|
if ((bool)component)
|
|
{
|
|
interactableCredit = (int)((float)component.sceneDirectorInteractibleCredits * num);
|
|
if (component.bonusInteractibleCreditObjects != null)
|
|
{
|
|
for (int i = 0; i < component.bonusInteractibleCreditObjects.Length; i++)
|
|
{
|
|
ClassicStageInfo.BonusInteractibleCreditObject bonusInteractibleCreditObject = component.bonusInteractibleCreditObjects[i];
|
|
if ((bool)bonusInteractibleCreditObject.objectThatGrantsPointsIfEnabled && bonusInteractibleCreditObject.objectThatGrantsPointsIfEnabled.activeSelf)
|
|
{
|
|
interactableCredit += bonusInteractibleCreditObject.points;
|
|
}
|
|
}
|
|
}
|
|
Debug.LogFormat("Spending {0} credits on interactables...", interactableCredit);
|
|
monsterCredit = (int)((float)component.sceneDirectorMonsterCredits * Run.instance.difficultyCoefficient);
|
|
}
|
|
halcyonShrine = Addressables.LoadAssetAsync<InteractableSpawnCard>("RoR2/DLC2/iscShrineHalcyoniteTier1.asset").WaitForCompletion();
|
|
SceneDirector.onPrePopulateSceneServer?.Invoke(this);
|
|
PopulateScene();
|
|
SceneDirector.onPostPopulateSceneServer?.Invoke(this);
|
|
}
|
|
|
|
private void PlaceTeleporter()
|
|
{
|
|
if (!teleporterInstance && (bool)teleporterSpawnCard)
|
|
{
|
|
teleporterInstance = DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(teleporterSpawnCard, new DirectorPlacementRule
|
|
{
|
|
placementMode = DirectorPlacementRule.PlacementMode.Random
|
|
}, rng));
|
|
Run.instance.OnServerTeleporterPlaced(this, teleporterInstance);
|
|
}
|
|
}
|
|
|
|
private static bool IsNodeSuitableForPod(NodeGraph nodeGraph, NodeGraph.NodeIndex nodeIndex)
|
|
{
|
|
if (nodeGraph.GetNodeFlags(nodeIndex, out var flags) && (flags & NodeFlags.NoCeiling) != 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void PlacePlayerSpawnsViaNodegraph()
|
|
{
|
|
bool usePod = Stage.instance.usePod;
|
|
NodeGraph groundNodes = SceneInfo.instance.groundNodes;
|
|
NodeFlags requiredFlags = NodeFlags.None;
|
|
NodeFlags nodeFlags = NodeFlags.None;
|
|
nodeFlags |= NodeFlags.NoCharacterSpawn;
|
|
List<NodeGraph.NodeIndex> list = groundNodes.GetActiveNodesForHullMaskWithFlagConditions(HullMask.Golem, requiredFlags, nodeFlags);
|
|
if (usePod)
|
|
{
|
|
int num = list.Count - 1;
|
|
while (num >= 0 && list.Count > 1)
|
|
{
|
|
if (!IsNodeSuitableForPod(groundNodes, list[num]))
|
|
{
|
|
list.RemoveAt(num);
|
|
}
|
|
num--;
|
|
}
|
|
}
|
|
if (PlayerSpawnInhibitor.readOnlyInstancesList.Count > 0)
|
|
{
|
|
List<NodeGraph.NodeIndex> list2 = new List<NodeGraph.NodeIndex>();
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
bool flag = false;
|
|
foreach (PlayerSpawnInhibitor readOnlyInstances in PlayerSpawnInhibitor.readOnlyInstancesList)
|
|
{
|
|
if (readOnlyInstances.IsInhibiting(groundNodes, list[i]))
|
|
{
|
|
flag = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!flag)
|
|
{
|
|
list2.Add(list[i]);
|
|
}
|
|
}
|
|
if (list2.Count > 0)
|
|
{
|
|
list = list2;
|
|
}
|
|
}
|
|
NodeGraph.NodeIndex nodeIndex;
|
|
if ((bool)teleporterInstance)
|
|
{
|
|
Vector3 position = teleporterInstance.transform.position;
|
|
List<NodeDistanceSqrPair> list3 = new List<NodeDistanceSqrPair>();
|
|
for (int j = 0; j < list.Count; j++)
|
|
{
|
|
groundNodes.GetNodePosition(list[j], out var position2);
|
|
list3.Add(new NodeDistanceSqrPair
|
|
{
|
|
nodeIndex = list[j],
|
|
distanceSqr = (position - position2).sqrMagnitude
|
|
});
|
|
}
|
|
list3.Sort((NodeDistanceSqrPair a, NodeDistanceSqrPair b) => a.distanceSqr.CompareTo(b.distanceSqr));
|
|
int index = rng.RangeInt(list3.Count * 3 / 4, list3.Count);
|
|
nodeIndex = list3[index].nodeIndex;
|
|
}
|
|
else
|
|
{
|
|
nodeIndex = rng.NextElementUniform(list);
|
|
}
|
|
NodeGraphSpider nodeGraphSpider = new NodeGraphSpider(groundNodes, HullMask.Human);
|
|
nodeGraphSpider.AddNodeForNextStep(nodeIndex);
|
|
while (nodeGraphSpider.PerformStep())
|
|
{
|
|
List<NodeGraphSpider.StepInfo> collectedSteps = nodeGraphSpider.collectedSteps;
|
|
for (int num2 = collectedSteps.Count - 1; num2 >= 0; num2--)
|
|
{
|
|
if ((RoR2Application.maxPlayers <= list.Count && !list.Contains(collectedSteps[num2].node)) || (usePod && !IsNodeSuitableForPod(groundNodes, collectedSteps[num2].node)))
|
|
{
|
|
collectedSteps.RemoveAt(num2);
|
|
}
|
|
}
|
|
if (collectedSteps.Count >= RoR2Application.maxPlayers)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
List<NodeGraphSpider.StepInfo> collectedSteps2 = nodeGraphSpider.collectedSteps;
|
|
Util.ShuffleList(collectedSteps2, Run.instance.stageRng);
|
|
int num3 = Math.Min(nodeGraphSpider.collectedSteps.Count, RoR2Application.maxPlayers);
|
|
for (int k = 0; k < num3; k++)
|
|
{
|
|
SpawnPoint.AddSpawnPoint(groundNodes, collectedSteps2[k].node, rng);
|
|
}
|
|
}
|
|
|
|
private void RemoveAllExistingSpawnPoints()
|
|
{
|
|
List<SpawnPoint> list = new List<SpawnPoint>(SpawnPoint.readOnlyInstancesList);
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
UnityEngine.Object.Destroy(list[i].gameObject);
|
|
}
|
|
}
|
|
|
|
private void CullExistingSpawnPoints()
|
|
{
|
|
List<SpawnPoint> list = new List<SpawnPoint>(SpawnPoint.readOnlyInstancesList);
|
|
if (!teleporterInstance)
|
|
{
|
|
return;
|
|
}
|
|
Vector3 teleporterPosition = teleporterInstance.transform.position;
|
|
list.Sort((SpawnPoint a, SpawnPoint b) => (teleporterPosition - a.transform.position).sqrMagnitude.CompareTo((teleporterPosition - b.transform.position).sqrMagnitude));
|
|
for (int num = list.Count; num >= 0; num--)
|
|
{
|
|
if (num < list.Count - RoR2Application.maxPlayers)
|
|
{
|
|
UnityEngine.Object.Destroy(list[num].gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DefaultPlayerSpawnPointGenerator()
|
|
{
|
|
bool num = SpawnPoint.readOnlyInstancesList.Count == 0;
|
|
bool flag = Run.instance.autoGenerateSpawnPoints && (bool)Stage.instance && !Stage.instance.usePod;
|
|
if (num || flag)
|
|
{
|
|
RemoveAllExistingSpawnPoints();
|
|
PlacePlayerSpawnsViaNodegraph();
|
|
}
|
|
else
|
|
{
|
|
CullExistingSpawnPoints();
|
|
}
|
|
}
|
|
|
|
private WeightedSelection<DirectorCard> GenerateInteractableCardSelection()
|
|
{
|
|
DirectorCardCategorySelection directorCardCategorySelection = ScriptableObject.CreateInstance<DirectorCardCategorySelection>();
|
|
if ((bool)ClassicStageInfo.instance && (bool)ClassicStageInfo.instance.interactableCategories)
|
|
{
|
|
directorCardCategorySelection.CopyFrom(ClassicStageInfo.instance.interactableCategories);
|
|
}
|
|
SceneDirector.onGenerateInteractableCardSelection?.Invoke(this, directorCardCategorySelection);
|
|
WeightedSelection<DirectorCard> result = directorCardCategorySelection.GenerateDirectorCardWeightedSelection();
|
|
UnityEngine.Object.Destroy(directorCardCategorySelection);
|
|
return result;
|
|
}
|
|
|
|
private void PopulateScene()
|
|
{
|
|
Run.instance.RecalculateDifficultyCoefficent();
|
|
WeightedSelection<DirectorCard> deck = GenerateInteractableCardSelection();
|
|
PlaceTeleporter();
|
|
Dictionary<DirectorCard, int> dictionary = new Dictionary<DirectorCard, int>();
|
|
interactableCredit = (int)Mathf.Floor((float)interactableCredit * onPopulateCreditMultiplier);
|
|
while (interactableCredit > 0)
|
|
{
|
|
DirectorCard directorCard = SelectCard(deck, interactableCredit);
|
|
if (directorCard == null)
|
|
{
|
|
break;
|
|
}
|
|
if (!directorCard.IsAvailable())
|
|
{
|
|
continue;
|
|
}
|
|
if (RunArtifactManager.instance.IsArtifactEnabled(CU8Content.Artifacts.Devotion) && (directorCard.spawnCard as InteractableSpawnCard).skipSpawnWhenDevotionArtifactEnabled)
|
|
{
|
|
directorCard = lumerianEgg;
|
|
}
|
|
if (!dictionary.ContainsKey(directorCard))
|
|
{
|
|
InteractableSpawnCard interactableSpawnCard = directorCard.spawnCard as InteractableSpawnCard;
|
|
if ((bool)interactableSpawnCard)
|
|
{
|
|
int value = int.MaxValue;
|
|
if (interactableSpawnCard.maxSpawnsPerStage >= 0)
|
|
{
|
|
value = interactableSpawnCard.maxSpawnsPerStage;
|
|
}
|
|
dictionary[directorCard] = value;
|
|
}
|
|
}
|
|
if (!dictionary.TryGetValue(directorCard, out var value2) || value2 <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
dictionary[directorCard] = value2 - 1;
|
|
if (Run.instance.isRunWeekly && rng.nextNormalizedFloat > (directorCard.spawnCard as InteractableSpawnCard).prismaticTrialSpawnChance)
|
|
{
|
|
continue;
|
|
}
|
|
interactableCredit -= directorCard.cost;
|
|
if (!Run.instance)
|
|
{
|
|
continue;
|
|
}
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
DirectorPlacementRule placementRule = new DirectorPlacementRule
|
|
{
|
|
placementMode = DirectorPlacementRule.PlacementMode.Random
|
|
};
|
|
GameObject gameObject = DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(directorCard.spawnCard, placementRule, rng));
|
|
if ((bool)gameObject)
|
|
{
|
|
PurchaseInteraction component = gameObject.GetComponent<PurchaseInteraction>();
|
|
if ((bool)component && component.costType == CostTypeIndex.Money)
|
|
{
|
|
component.Networkcost = Run.instance.GetDifficultyScaledCost(component.cost);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Action generationMethod = DefaultPlayerSpawnPointGenerator;
|
|
SceneDirector.onPreGeneratePlayerSpawnPointsServer?.Invoke(this, ref generationMethod);
|
|
generationMethod?.Invoke();
|
|
Run.instance.OnPlayerSpawnPointsPlaced(this);
|
|
SceneDirector.onPrePopulateMonstersSceneServer?.Invoke(this);
|
|
if ((bool)Run.instance && CombatDirector.cvDirectorCombatDisable.value)
|
|
{
|
|
monsterCredit = 0;
|
|
}
|
|
if (!SceneInfo.instance.sceneDef.hasSafeStart)
|
|
{
|
|
CombatDirector component2 = GetComponent<CombatDirector>();
|
|
if ((bool)component2)
|
|
{
|
|
float num = component2.expRewardCoefficient;
|
|
float num2 = component2.eliteBias;
|
|
float num3 = component2.spawnDistanceMultiplier;
|
|
component2.monsterCredit += monsterCredit;
|
|
component2.expRewardCoefficient = expRewardCoefficient;
|
|
component2.eliteBias = eliteBias;
|
|
component2.spawnDistanceMultiplier = spawnDistanceMultiplier;
|
|
monsterCredit = 0;
|
|
component2.onSpawnedServer.AddListener(OnMonsterSpawnedServer);
|
|
component2.SpendAllCreditsOnMapSpawns(TeleporterInteraction.instance ? TeleporterInteraction.instance.transform : null);
|
|
component2.onSpawnedServer.RemoveListener(OnMonsterSpawnedServer);
|
|
component2.expRewardCoefficient = num;
|
|
component2.eliteBias = num2;
|
|
component2.spawnDistanceMultiplier = num3;
|
|
}
|
|
}
|
|
if (!SceneInfo.instance.countsAsStage && !SceneInfo.instance.sceneDef.allowItemsToSpawnObjects)
|
|
{
|
|
return;
|
|
}
|
|
Xoroshiro128Plus xoroshiro128Plus = new Xoroshiro128Plus(rng.nextUlong);
|
|
int num4 = 0;
|
|
foreach (CharacterMaster readOnlyInstances in CharacterMaster.readOnlyInstancesList)
|
|
{
|
|
if (readOnlyInstances.inventory.GetItemCount(RoR2Content.Items.TreasureCache) > 0)
|
|
{
|
|
num4++;
|
|
}
|
|
}
|
|
for (int j = 0; j < num4; j++)
|
|
{
|
|
DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(LegacyResourcesAPI.Load<SpawnCard>("SpawnCards/InteractableSpawnCard/iscLockbox"), new DirectorPlacementRule
|
|
{
|
|
placementMode = DirectorPlacementRule.PlacementMode.Random
|
|
}, xoroshiro128Plus));
|
|
}
|
|
Xoroshiro128Plus xoroshiro128Plus2 = new Xoroshiro128Plus(rng.nextUlong);
|
|
int num5 = 0;
|
|
foreach (CharacterMaster readOnlyInstances2 in CharacterMaster.readOnlyInstancesList)
|
|
{
|
|
if (readOnlyInstances2.inventory.GetItemCount(DLC1Content.Items.TreasureCacheVoid) > 0)
|
|
{
|
|
num5++;
|
|
}
|
|
}
|
|
for (int k = 0; k < num5; k++)
|
|
{
|
|
DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(LegacyResourcesAPI.Load<SpawnCard>("SpawnCards/InteractableSpawnCard/iscLockboxVoid"), new DirectorPlacementRule
|
|
{
|
|
placementMode = DirectorPlacementRule.PlacementMode.Random
|
|
}, xoroshiro128Plus2));
|
|
}
|
|
Xoroshiro128Plus xoroshiro128Plus3 = new Xoroshiro128Plus(rng.nextUlong);
|
|
int num6 = 0;
|
|
foreach (CharacterMaster readOnlyInstances3 in CharacterMaster.readOnlyInstancesList)
|
|
{
|
|
if (readOnlyInstances3.inventory.GetItemCount(DLC1Content.Items.FreeChest) > 0)
|
|
{
|
|
num6++;
|
|
}
|
|
}
|
|
for (int l = 0; l < num6; l++)
|
|
{
|
|
DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(LegacyResourcesAPI.Load<SpawnCard>("SpawnCards/InteractableSpawnCard/iscFreeChest"), new DirectorPlacementRule
|
|
{
|
|
placementMode = DirectorPlacementRule.PlacementMode.Random
|
|
}, xoroshiro128Plus3));
|
|
}
|
|
static void OnMonsterSpawnedServer(GameObject masterObject)
|
|
{
|
|
GameObject bodyObject = masterObject.GetComponent<CharacterMaster>().GetBodyObject();
|
|
if ((bool)bodyObject)
|
|
{
|
|
EntityStateMachine[] components = bodyObject.GetComponents<EntityStateMachine>();
|
|
foreach (EntityStateMachine obj in components)
|
|
{
|
|
obj.initialStateType = obj.mainStateType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private DirectorCard SelectCard(WeightedSelection<DirectorCard> deck, int maxCost)
|
|
{
|
|
cardSelector.Clear();
|
|
int i = 0;
|
|
for (int count = deck.Count; i < count; i++)
|
|
{
|
|
WeightedSelection<DirectorCard>.ChoiceInfo choice = deck.GetChoice(i);
|
|
if (choice.value.cost <= maxCost)
|
|
{
|
|
cardSelector.AddChoice(choice);
|
|
}
|
|
}
|
|
if (cardSelector.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
return cardSelector.Evaluate(rng.nextNormalizedFloat);
|
|
}
|
|
|
|
public void ReduceMonsterCredits(int creditReduction)
|
|
{
|
|
monsterCredit = Mathf.Max(0, monsterCredit - creditReduction);
|
|
}
|
|
}
|