406 lines
17 KiB
C#
406 lines
17 KiB
C#
//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $//
|
|
using System.Collections.Generic;
|
|
using DungeonArchitect.Flow.Domains;
|
|
using DungeonArchitect.Flow.Domains.Layout;
|
|
using DungeonArchitect.Flow.Exec;
|
|
using DungeonArchitect.Flow.Impl.SnapGridFlow;
|
|
using DungeonArchitect.Flow.Impl.SnapGridFlow.Components;
|
|
using DungeonArchitect.Flow.Items;
|
|
using DungeonArchitect.Frameworks.Snap;
|
|
using DungeonArchitect.Themeing;
|
|
using DungeonArchitect.Utils;
|
|
using UnityEngine;
|
|
|
|
namespace DungeonArchitect.Builders.SnapGridFlow
|
|
{
|
|
public class SnapGridFlowBuilder : DungeonBuilder
|
|
{
|
|
new System.Random random;
|
|
|
|
public override bool IsThemingSupported()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool DestroyDungeonOnRebuild()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override void BuildNonThemedDungeon(DungeonSceneProvider sceneProvider, IDungeonSceneObjectInstantiator objectInstantiator)
|
|
{
|
|
base.BuildNonThemedDungeon(sceneProvider, objectInstantiator);
|
|
|
|
|
|
random = new System.Random((int)config.Seed);
|
|
markers.Clear();
|
|
|
|
// We know that the dungeon prefab would have the appropriate config and models attached to it
|
|
// Cast and save it for future reference
|
|
var sgfConfig = config as SnapGridFlowConfig;
|
|
var sgfModel = model as SnapGridFlowModel;
|
|
|
|
if (sgfConfig == null)
|
|
{
|
|
Debug.LogError("No snap config script found in dungeon game object");
|
|
return;
|
|
}
|
|
|
|
if (sgfModel == null)
|
|
{
|
|
Debug.LogError("No snap model script found in dungeon game object");
|
|
return;
|
|
}
|
|
|
|
{
|
|
string errorMessage = "";
|
|
if (!sgfConfig.HasValidConfig(ref errorMessage))
|
|
{
|
|
Debug.LogError(errorMessage);
|
|
return;
|
|
}
|
|
}
|
|
|
|
SgfModuleNode[] snapModules = null;
|
|
FlowLayoutGraph layoutGraph = null;
|
|
var numRetriesLeft = sgfConfig.numGraphRetries;
|
|
bool buildSuccess = false;
|
|
while (!buildSuccess && numRetriesLeft > 0) {
|
|
var domainExtensions = new FlowDomainExtensions();
|
|
var snapDomainExtension = domainExtensions.GetExtension<SnapGridFlowDomainExtension>();
|
|
snapDomainExtension.ModuleDatabase = sgfConfig.moduleDatabase;
|
|
|
|
var execGraph = sgfConfig.flowGraph.execGraph;
|
|
if (execGraph == null || execGraph.resultNode == null)
|
|
{
|
|
Debug.LogError("Invalid flow exec graph");
|
|
return;
|
|
}
|
|
|
|
FlowExecutor executor = new FlowExecutor();
|
|
FlowExecNodeOutputRegistry nodeOutputRegistry;
|
|
|
|
//var sw = System.Diagnostics.Stopwatch.StartNew();
|
|
if (!executor.Execute(execGraph, random, domainExtensions, numRetriesLeft, out nodeOutputRegistry))
|
|
{
|
|
Debug.LogError("Failed to produce graph");
|
|
return;
|
|
}
|
|
//Debug.Log("Time elapsed: " + (sw.ElapsedMilliseconds / 1000.0f) + " s");
|
|
|
|
numRetriesLeft = Mathf.Max(0, numRetriesLeft - executor.RetriesUsed);
|
|
|
|
var execResult = nodeOutputRegistry.Get(execGraph.resultNode.Id);
|
|
if (execResult == null || execResult.State == null)
|
|
{
|
|
Debug.LogError("Invalid flow exec result");
|
|
return;
|
|
}
|
|
|
|
var execState = execResult.State;
|
|
layoutGraph = execState.GetState<FlowLayoutGraph>();
|
|
|
|
if (layoutGraph == null)
|
|
{
|
|
Debug.LogError("Invalid layout graph state");
|
|
return;
|
|
}
|
|
|
|
var boundsAsset = sgfConfig.moduleDatabase.ModuleBoundsAsset;
|
|
var chunkSize = boundsAsset.chunkSize;
|
|
var baseYOffset = chunkSize.y * 0.5f - boundsAsset.doorOffsetY;
|
|
|
|
var settings = new SgfLayoutModuleResolverSettings();
|
|
settings.Seed = (int)config.Seed;
|
|
settings.BaseTransform = transform.localToWorldMatrix * Matrix4x4.Translate(new Vector3(0, baseYOffset, 0));
|
|
settings.LayoutGraph = layoutGraph;
|
|
settings.ModuleDatabase = sgfConfig.moduleDatabase;
|
|
settings.MaxResolveFrames = sgfConfig.maxResolverFrames;
|
|
settings.NonRepeatingRooms = sgfConfig.nonRepeatingRooms;
|
|
|
|
sgfModel.layoutGraph = layoutGraph;
|
|
sgfModel.snapModules = new SgfModuleNode[0];
|
|
|
|
buildSuccess = SgfLayoutModuleResolver.Resolve(settings, out snapModules);
|
|
}
|
|
|
|
if (buildSuccess && snapModules != null)
|
|
{
|
|
// Spawn the module prefabs
|
|
sceneProvider.OnDungeonBuildStart();
|
|
|
|
// Spawn the modules and register them in the model
|
|
foreach (var moduleInfo in snapModules)
|
|
{
|
|
if (moduleInfo.ModuleDBItem == null || moduleInfo.ModuleDBItem.ModulePrefab == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var templateInfo = new GameObjectDungeonThemeItem();
|
|
templateInfo.Template = moduleInfo.ModuleDBItem.ModulePrefab.gameObject;
|
|
templateInfo.NodeId = moduleInfo.ModuleInstanceId.ToString();
|
|
templateInfo.Offset = Matrix4x4.identity;
|
|
templateInfo.StaticState = DungeonThemeItemStaticMode.Unchanged;
|
|
templateInfo.externallyManaged = true;
|
|
|
|
var moduleGameObject = sceneProvider.AddGameObject(templateInfo, moduleInfo.WorldTransform, objectInstantiator);
|
|
moduleInfo.SpawnedModule = moduleGameObject.GetComponent<SnapGridFlowModule>();
|
|
|
|
var spawnedConnections = moduleInfo.SpawnedModule.GetComponentsInChildren<SnapConnection>();
|
|
|
|
var doorInfoValid = spawnedConnections.Length == moduleInfo.Doors.Length;
|
|
Debug.Assert(doorInfoValid);
|
|
if (doorInfoValid)
|
|
{
|
|
for (var doorIdx = 0; doorIdx < moduleInfo.Doors.Length; doorIdx++)
|
|
{
|
|
moduleInfo.Doors[doorIdx].SpawnedDoor = spawnedConnections[doorIdx];
|
|
}
|
|
}
|
|
}
|
|
|
|
sceneProvider.OnDungeonBuildStop();
|
|
|
|
FixupDoorStates(snapModules, layoutGraph);
|
|
|
|
SpawnItems(snapModules, sceneProvider, objectInstantiator);
|
|
|
|
sgfModel.snapModules = snapModules;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Cannot build snap graph. Retries exhausted. Try adjusting your flow graph or increasing the num retries parameter");
|
|
}
|
|
|
|
Cleanup(snapModules);
|
|
}
|
|
|
|
public override void OnDestroyed()
|
|
{
|
|
base.OnDestroyed();
|
|
|
|
var sgfModel = GetComponent<SnapGridFlowModel>();
|
|
if (sgfModel != null)
|
|
{
|
|
sgfModel.layoutGraph = new FlowLayoutGraph();
|
|
sgfModel.snapModules = new SgfModuleNode[0];
|
|
}
|
|
}
|
|
|
|
private void SpawnItems(SgfModuleNode[] modules, DungeonSceneProvider sceneProvider, IDungeonSceneObjectInstantiator objectInstantiator)
|
|
{
|
|
var levelMarkers = new LevelMarkerList();
|
|
var sgfConfig = config as SnapGridFlowConfig;
|
|
if (sgfConfig == null)
|
|
{
|
|
Debug.LogError("No snap config script found in dungeon game object");
|
|
return;
|
|
}
|
|
|
|
foreach (var module in modules)
|
|
{
|
|
if (module == null || module.SpawnedModule == null) continue;
|
|
|
|
var markers = new List<PlaceableMarker>(module.SpawnedModule.GetComponentsInChildren<PlaceableMarker>());
|
|
MathUtils.Shuffle(markers, random);
|
|
|
|
foreach (var item in module.LayoutNode.items)
|
|
{
|
|
if (item == null) continue;
|
|
|
|
PlaceableMarker bestMarker = null;
|
|
foreach (var markerInfo in markers)
|
|
{
|
|
if (markerInfo.supportedMarkers == null) continue;
|
|
|
|
var supportedMarkers = new List<string>(markerInfo.supportedMarkers);
|
|
if (supportedMarkers.Contains(item.markerName))
|
|
{
|
|
bestMarker = markerInfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bestMarker != null)
|
|
{
|
|
markers.Remove(bestMarker);
|
|
|
|
var flowItemMetadata = new FlowItemMetadata();
|
|
flowItemMetadata.itemId = item.itemId;
|
|
flowItemMetadata.itemType = item.type;
|
|
flowItemMetadata.referencedItems = item.referencedItemIds.ToArray();
|
|
flowItemMetadata.parentTransform = sgfConfig.spawnItemsUnderRoomPrefabs ? bestMarker.transform.parent : null;
|
|
|
|
var themeMarkerEntry = new PropSocket();
|
|
themeMarkerEntry.SocketType = item.markerName;
|
|
themeMarkerEntry.Transform = bestMarker.transform.localToWorldMatrix;
|
|
themeMarkerEntry.metadata = flowItemMetadata;
|
|
levelMarkers.Add(themeMarkerEntry);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning(string.Format("Cannot spawn item: {0}. Make sure you have a placeable marker in the module prefab", item.markerName));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run the theme engine
|
|
if (levelMarkers.Count > 0)
|
|
{
|
|
var dungeon = GetComponent<Dungeon>();
|
|
if (dungeon != null)
|
|
{
|
|
var itemSpawnListeners = new List<DungeonItemSpawnListener>();
|
|
itemSpawnListeners.Add(GetComponent<FlowItemMetadataHandler>());
|
|
itemSpawnListeners.AddRange(GetComponents<DungeonItemSpawnListener>());
|
|
|
|
var context = new DungeonThemeExecutionContext();
|
|
context.builder = this;
|
|
context.config = config;
|
|
context.model = model;
|
|
context.spatialConstraintProcessor = null;
|
|
context.themeOverrideVolumes = new ThemeOverrideVolume[0];
|
|
context.sceneProvider = sceneProvider;
|
|
context.objectSpawner = new SyncDungeonSceneObjectSpawner();
|
|
context.objectInstantiator = objectInstantiator;
|
|
context.spawnListeners = itemSpawnListeners.ToArray();
|
|
|
|
var themeEngine = new DungeonThemeEngine(context);
|
|
themeEngine.ApplyTheme(levelMarkers, dungeon.GetThemeAssets());
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Invalid dungeon reference");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Cleanup(SgfModuleNode[] modules)
|
|
{
|
|
// Disable Bounds rendering
|
|
foreach (var module in modules)
|
|
{
|
|
if (module.SpawnedModule != null)
|
|
{
|
|
module.SpawnedModule.drawBounds = false;
|
|
|
|
// Hide the debug data of the placeable markers
|
|
var placeableMarkers = module.SpawnedModule.GetComponentsInChildren<PlaceableMarker>();
|
|
foreach (var placeableMarker in placeableMarkers)
|
|
{
|
|
if (placeableMarker != null)
|
|
{
|
|
placeableMarker.drawDebugVisuals = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FixupDoorStates(SgfModuleNode[] snapModules, FlowLayoutGraph layoutGraph)
|
|
{
|
|
var graphQuery = new FlowLayoutGraphQuery(layoutGraph);
|
|
|
|
foreach (var moduleInfo in snapModules)
|
|
{
|
|
var moduleComponent = moduleInfo.SpawnedModule;
|
|
if (moduleComponent == null) continue;
|
|
var connectionComponents = moduleComponent.gameObject.GetComponentsInChildren<SnapConnection>();
|
|
|
|
for (var doorIdx = 0; doorIdx < moduleInfo.Doors.Length; doorIdx++)
|
|
{
|
|
var doorInfo = moduleInfo.Doors[doorIdx];
|
|
doorInfo.SpawnedDoor = connectionComponents[doorIdx];
|
|
|
|
bool foundDoor = false;
|
|
GameObject spawnedObject = null;
|
|
|
|
bool containsLock = false;
|
|
FlowItem lockItem = null;
|
|
|
|
if (doorInfo.CellInfo.connectionIdx != -1)
|
|
{
|
|
var link = graphQuery.GetLink(doorInfo.CellInfo.linkId);
|
|
if (link != null)
|
|
{
|
|
if (link.state.type != FlowLayoutGraphLinkType.Unconnected)
|
|
{
|
|
if (link.source == moduleInfo.ModuleInstanceId)
|
|
{
|
|
// Check if we have a lock here
|
|
if (link.state.items != null)
|
|
{
|
|
foreach (var item in link.state.items)
|
|
{
|
|
if (item.type == FlowGraphItemType.Lock)
|
|
{
|
|
containsLock = true;
|
|
lockItem = item;
|
|
// TODO: Setup Key-Lock metadata
|
|
}
|
|
}
|
|
}
|
|
|
|
if (containsLock)
|
|
{
|
|
spawnedObject = doorInfo.SpawnedDoor.UpdateDoorState(SnapConnectionState.DoorLocked, lockItem.markerName);
|
|
}
|
|
else if (link.state.type == FlowLayoutGraphLinkType.OneWay)
|
|
{
|
|
spawnedObject = doorInfo.SpawnedDoor.UpdateDoorState(SnapConnectionState.DoorOneWay);
|
|
}
|
|
else
|
|
{
|
|
spawnedObject = doorInfo.SpawnedDoor.UpdateDoorState(SnapConnectionState.Door);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hide the other door so we don't have duplicates.
|
|
spawnedObject = doorInfo.SpawnedDoor.UpdateDoorState(SnapConnectionState.None);
|
|
}
|
|
|
|
foundDoor = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundDoor)
|
|
{
|
|
spawnedObject = doorInfo.SpawnedDoor.UpdateDoorState(SnapConnectionState.Wall);
|
|
}
|
|
|
|
if (spawnedObject != null)
|
|
{
|
|
if (containsLock && lockItem != null)
|
|
{
|
|
var metaDataComponent = spawnedObject.GetComponent<FlowItemMetadataComponent>();
|
|
if (metaDataComponent == null)
|
|
{
|
|
metaDataComponent = spawnedObject.AddComponent<FlowItemMetadataComponent>();
|
|
}
|
|
|
|
metaDataComponent.itemType = FlowGraphItemType.Lock;
|
|
metaDataComponent.itemId = lockItem.itemId.ToString();
|
|
var referencesIds = new List<string>();
|
|
foreach (var lockRefId in lockItem.referencedItemIds)
|
|
{
|
|
referencesIds.Add(lockRefId.ToString());
|
|
}
|
|
metaDataComponent.referencedItemIds = referencesIds.ToArray();
|
|
}
|
|
else
|
|
{
|
|
var metaDataComponent = spawnedObject.GetComponent<FlowItemMetadataComponent>();
|
|
if (metaDataComponent != null)
|
|
{
|
|
DungeonUtils.DestroyObject(metaDataComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |