/// <summary> /// Add actions doors to block. /// </summary> public static void AddActionDoors(GameObject go, Dictionary<int, ActionLink> actionLinkDict, ref DFBlock blockData, int[] textureTable, bool serialize = true) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return; // Use default texture table if one not specified if (textureTable == null) textureTable = StaticTextureTables.DefaultTextureTable; // Add parent node GameObject actionDoorsNode = new GameObject("Action Doors"); actionDoorsNode.transform.parent = go.transform; // Iterate all groups foreach (DFBlock.RdbObjectRoot group in blockData.RdbBlock.ObjectRootList) { // Skip empty object groups if (null == group.RdbObjects) continue; // Look for models in this group foreach (DFBlock.RdbObject obj in group.RdbObjects) { if (obj.Type == DFBlock.RdbResourceTypes.Model) { // Create unique LoadID for save sytem long loadID = 0; if (serialize) loadID = (blockData.Index << 24) + obj.This; // Look for action doors int modelReference = obj.Resources.ModelResource.ModelIndex; uint modelId = blockData.RdbBlock.ModelReferenceList[modelReference].ModelIdNum; if (IsActionDoor(ref blockData, obj, modelReference)) { GameObject cgo = AddActionDoor(dfUnity, modelId, obj, actionDoorsNode.transform, loadID); cgo.GetComponent<DaggerfallMesh>().SetDungeonTextures(textureTable); // Add action component to door if it also has an action if (HasAction(obj)) { AddActionModelHelper(cgo, actionLinkDict, obj, ref blockData, serialize); } } } } } }
/// <summary> /// Add simple ground plane to block layout. /// </summary> public static GameObject AddGroundPlane( ref DFBlock blockData, Transform parent = null, ClimateBases climateBase = ClimateBases.Temperate, ClimateSeason climateSeason = ClimateSeason.Summer) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return null; GameObject go = new GameObject("Ground"); if (parent != null) go.transform.parent = parent; // Assign components DaggerfallGroundPlane dfGround = go.AddComponent<DaggerfallGroundPlane>(); MeshFilter meshFilter = go.GetComponent<MeshFilter>(); // Assign climate and mesh Color32[] tileMap; Mesh mesh = dfUnity.MeshReader.GetSimpleGroundPlaneMesh( ref blockData, out tileMap, dfUnity.MeshReader.AddMeshTangents, dfUnity.MeshReader.AddMeshLightmapUVs); if (mesh) { meshFilter.sharedMesh = mesh; } // Assign tileMap and climate dfGround.tileMap = tileMap; dfGround.SetClimate(dfUnity, climateBase, climateSeason); // Assign collider if (dfUnity.Option_AddMeshColliders) go.AddComponent<BoxCollider>(); // Assign static if (dfUnity.Option_SetStaticFlags) go.isStatic = true; return go; }
/// <summary> /// Add actions doors to block. /// </summary> public static void AddActionDoors(GameObject go, ref DFBlock blockData, int[] textureTable) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return; // Use default texture table if one not specified if (textureTable == null) textureTable = StaticTextureTables.DefaultTextureTable; // Add parent node GameObject actionDoorsNode = new GameObject("Action Doors"); actionDoorsNode.transform.parent = go.transform; // Iterate all groups foreach (DFBlock.RdbObjectRoot group in blockData.RdbBlock.ObjectRootList) { // Skip empty object groups if (null == group.RdbObjects) continue; // Look for models in this group foreach (DFBlock.RdbObject obj in group.RdbObjects) { if (obj.Type == DFBlock.RdbResourceTypes.Model) { // Look for action doors int modelReference = obj.Resources.ModelResource.ModelIndex; uint modelId = blockData.RdbBlock.ModelReferenceList[modelReference].ModelIdNum; if (IsActionDoor(ref blockData, obj, modelReference)) { GameObject cgo = AddActionDoor(dfUnity, modelId, obj, actionDoorsNode.transform); cgo.GetComponent<DaggerfallMesh>().SetDungeonTextures(textureTable); } } } } }
private static void AddLight(DaggerfallUnity dfUnity, DFBlock.RmbBlockFlatObjectRecord obj, Transform parent = null) { if (dfUnity.Option_CityLightPrefab == null) return; Vector2 size = dfUnity.MeshReader.GetScaledBillboardSize(210, obj.TextureRecord); Vector3 position = new Vector3( obj.XPos, -obj.YPos + size.y, obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; GameObjectHelper.InstantiatePrefab(dfUnity.Option_CityLightPrefab.gameObject, string.Empty, parent, position); }
/// <summary> /// Adds models and their actions to scene. /// </summary> private static void AddModels( DaggerfallUnity dfUnity, ref DFBlock blockData, Dictionary<int, ActionLink> actionLinkDict, int[] textureTable, bool allowExitDoors, out List<StaticDoor> exitDoorsOut, bool serialize, ModelCombiner combiner = null, Transform modelsParent = null, Transform actionModelsParent = null) { exitDoorsOut = new List<StaticDoor>(); // Iterate object groups foreach (DFBlock.RdbObjectRoot group in blockData.RdbBlock.ObjectRootList) { // Skip empty object groups if (null == group.RdbObjects) { continue; } // Iterate objects in this group foreach (DFBlock.RdbObject obj in group.RdbObjects) { // Add models if (obj.Type == DFBlock.RdbResourceTypes.Model) { // Get model reference index and id int modelReference = obj.Resources.ModelResource.ModelIndex; uint modelId = blockData.RdbBlock.ModelReferenceList[modelReference].ModelIdNum; // Filter exit door models where flag not set if (modelId == exitDoorModelID && !allowExitDoors) continue; // Filter action door models // These must be added by AddActionDoors() if (IsActionDoor(ref blockData, obj, modelReference)) continue; // Get matrix Matrix4x4 modelMatrix = GetModelMatrix(obj); // Get model data ModelData modelData; dfUnity.MeshReader.GetModelData(modelId, out modelData); // Add to static doors exitDoorsOut.AddRange(GameObjectHelper.GetStaticDoors(ref modelData, blockData.Index, 0, modelMatrix)); // Check if model has an action record bool hasAction = HasAction(obj); // Special handling for tapestries and banners // Some of these are so far out from wall player can become stuck behind them // Adding model invidually without collider to avoid problem // Not sure if these object ever actions, but bypass this hack if they do if (modelId >= minTapestryID && modelId <= maxTapestryID && !hasAction) { AddStandaloneModel(dfUnity, ref modelData, modelMatrix, modelsParent, hasAction, true); continue; } // Add or combine GameObject standaloneObject = null; Transform parent = (hasAction) ? actionModelsParent : modelsParent; if (combiner == null || hasAction) { standaloneObject = AddStandaloneModel(dfUnity, ref modelData, modelMatrix, parent, hasAction); standaloneObject.GetComponent<DaggerfallMesh>().SetDungeonTextures(textureTable); } else { combiner.Add(ref modelData, modelMatrix); } // Add action if (hasAction && standaloneObject != null) AddActionModelHelper(standaloneObject, actionLinkDict, obj, ref blockData, serialize); } } } }
private static GameObject AddFlat(DFBlock.RdbObject obj, Transform parent) { int archive = obj.Resources.FlatResource.TextureArchive; int record = obj.Resources.FlatResource.TextureRecord; // Spawn billboard gameobject GameObject go = GameObjectHelper.CreateDaggerfallBillboardGameObject(archive, record, parent, true); Vector3 billboardPosition = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale; // Add RDB data to billboard DaggerfallBillboard dfBillboard = go.GetComponent<DaggerfallBillboard>(); dfBillboard.SetResourceData(obj.Resources.FlatResource); // Set transform go.transform.position = billboardPosition; // Disable enemy flats if (archive == TextureReader.EditorFlatsTextureArchive && (record == 15 || record == 16)) go.SetActive(false); // Add torch burning sound if (archive == TextureReader.LightsTextureArchive) { switch (record) { case 0: case 1: case 6: case 16: case 17: case 18: case 19: case 20: AddTorchAudioSource(go); break; } } return go; }
private static void AddEnemy( DFBlock.RdbObject obj, MobileTypes type, Transform parent = null, long loadID = 0) { // Get default reaction MobileReactions reaction = MobileReactions.Hostile; if (obj.Resources.FlatResource.Action == (int)DFBlock.EnemyReactionTypes.Passive) reaction = MobileReactions.Passive; // Just setup demo enemies at this time string name = string.Format("DaggerfallEnemy [{0}]", type.ToString()); Vector3 position = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale; GameObject go = GameObjectHelper.InstantiatePrefab(DaggerfallUnity.Instance.Option_EnemyPrefab.gameObject, name, parent, position); SetupDemoEnemy setupEnemy = go.GetComponent<SetupDemoEnemy>(); if (setupEnemy != null) { // Configure enemy setupEnemy.ApplyEnemySettings(type, reaction); // Align non-flying units with ground DaggerfallMobileUnit mobileUnit = setupEnemy.GetMobileBillboardChild(); if (mobileUnit.Summary.Enemy.Behaviour != MobileBehaviour.Flying) GameObjectHelper.AlignControllerToGround(go.GetComponent<CharacterController>()); } DaggerfallEnemy enemy = go.GetComponent<DaggerfallEnemy>(); if (enemy) { enemy.LoadID = loadID; } }
private static void AddActionFlatHelper( GameObject go, Dictionary<int, ActionLink> actionLinkDict, ref DFBlock blockData, DFBlock.RdbObject rdbObj, bool serialize = true) { DFBlock.RdbFlatResource obj = rdbObj.Resources.FlatResource; string description = "FLT"; int soundID_Index = obj.Sound_index; float duration = 0.0f; float magnitude = obj.Magnitude; int axis = obj.Magnitude; DFBlock.RdbTriggerFlags triggerFlag = DFBlock.RdbTriggerFlags.None; DFBlock.RdbActionFlags actionFlag = DFBlock.RdbActionFlags.None; //set action flag if valid / known if (Enum.IsDefined(typeof(DFBlock.RdbActionFlags), (DFBlock.RdbActionFlags)obj.Action)) actionFlag = (DFBlock.RdbActionFlags)obj.Action; //set trigger flag if valid / known if (Enum.IsDefined(typeof(DFBlock.RdbTriggerFlags), (DFBlock.RdbTriggerFlags)obj.TriggerFlag)) triggerFlag = (DFBlock.RdbTriggerFlags)obj.TriggerFlag; //add action node to actionLink dictionary if (!actionLinkDict.ContainsKey(rdbObj.This)) { ActionLink link; link.nextKey = obj.NextObjectOffset; link.prevKey = -1; link.gameObject = go; actionLinkDict.Add(rdbObj.This, link); } // Create unique LoadID for save sytem long loadID = 0; if (serialize) loadID = (blockData.Index << 24) + rdbObj.This; AddAction(go, description, soundID_Index, duration, magnitude, axis, triggerFlag, actionFlag, loadID); }
/// <summary> /// Instantiate base RDB block by DFBlock data. /// </summary> /// <param name="blockData">Block data.</param> /// <param name="textureTable">Optional texture table for dungeon.</param> /// <param name="allowExitDoors">Add exit doors to block.</param> /// <param name="cloneFrom">Clone and build on a prefab object template.</param> /// <param name="serialize">Allow for serialization of supported sub-objects.</param> /// <returns>Block GameObject.</returns> public static GameObject CreateBaseGameObject( ref DFBlock blockData, Dictionary<int, ActionLink> actionLinkDict, int[] textureTable = null, bool allowExitDoors = true, DaggerfallRDBBlock cloneFrom = null, bool serialize = true) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return null; // Use default texture table if one not specified if (textureTable == null) textureTable = StaticTextureTables.DefaultTextureTable; // Create gameobject GameObject go; string name = string.Format("DaggerfallBlock [{0}]", blockData.Name); if (cloneFrom != null) { go = GameObjectHelper.InstantiatePrefab(cloneFrom.gameObject, name, null, Vector3.zero); } else { go = new GameObject(name); go.AddComponent<DaggerfallRDBBlock>(); } // Setup combiner ModelCombiner combiner = null; if (dfUnity.Option_CombineRDB) combiner = new ModelCombiner(); // Add parent node GameObject modelsNode = new GameObject("Models"); GameObject actionModelsNode = new GameObject("Action Models"); modelsNode.transform.parent = go.transform; actionModelsNode.transform.parent = go.transform; // Add models List<StaticDoor> exitDoors; AddModels( dfUnity, ref blockData, actionLinkDict, textureTable, allowExitDoors, out exitDoors, serialize, combiner, modelsNode.transform, actionModelsNode.transform); // Apply combiner if (combiner != null) { if (combiner.VertexCount > 0) { combiner.Apply(); GameObject cgo = GameObjectHelper.CreateCombinedMeshGameObject( combiner, "CombinedModels", modelsNode.transform, dfUnity.Option_SetStaticFlags); cgo.GetComponent<DaggerfallMesh>().SetDungeonTextures(textureTable); } } // Add exit doors if (exitDoors.Count > 0) { DaggerfallStaticDoors c = go.AddComponent<DaggerfallStaticDoors>(); c.Doors = exitDoors.ToArray(); } return go; }
/// <summary> /// Extracts correct matrix from model data. /// </summary> private static Matrix4x4 GetModelMatrix(DFBlock.RdbObject obj) { // Get rotation angle for each axis float degreesX = -obj.Resources.ModelResource.XRotation / BlocksFile.RotationDivisor; float degreesY = -obj.Resources.ModelResource.YRotation / BlocksFile.RotationDivisor; float degreesZ = -obj.Resources.ModelResource.ZRotation / BlocksFile.RotationDivisor; // Calcuate transform Vector3 position = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale; // Calculate matrix Vector3 rx = new Vector3(degreesX, 0, 0); Vector3 ry = new Vector3(0, degreesY, 0); Vector3 rz = new Vector3(0, 0, degreesZ); Matrix4x4 modelMatrix = Matrix4x4.identity; modelMatrix *= Matrix4x4.TRS(position, Quaternion.identity, Vector3.one); modelMatrix *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(rz), Vector3.one); modelMatrix *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(rx), Vector3.one); modelMatrix *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(ry), Vector3.one); return modelMatrix; }
/// <summary> /// Layout interior based on data in exterior door and optional location for climate settings. /// </summary> /// <param name="doorOwner">Parent transform owning door array.</param> /// <param name="door">Exterior door player clicked on.</param> /// <returns>True if successful.</returns> public bool DoLayout(Transform doorOwner, StaticDoor door, ClimateBases climateBase) { if (dfUnity == null) dfUnity = DaggerfallUnity.Instance; // Use specified climate this.climateBase = climateBase; // Save exterior information this.entryDoor = door; this.doorOwner = doorOwner; // Get block data blockData = dfUnity.ContentReader.BlockFileReader.GetBlock(door.blockIndex); if (blockData.Type != DFBlock.BlockTypes.Rmb) throw new Exception(string.Format("Could not load RMB block index {0}", door.blockIndex), null); // Get record data recordData = blockData.RmbBlock.SubRecords[door.recordIndex]; if (recordData.Interior.Header.Num3dObjectRecords == 0) throw new Exception(string.Format("No interior 3D models found for record index {0}", door.recordIndex), null); // Layout interior data AddModels(); AddFlats(); AddPeople(); AddActionDoors(); return true; }
/// <summary> /// Adds interior point light. /// </summary> private static void AddLight(DFBlock.RmbBlockFlatObjectRecord obj, Transform parent = null) { if (DaggerfallUnity.Instance.Option_InteriorLightPrefab == null) return; // Create gameobject GameObject go = GameObjectHelper.InstantiatePrefab(DaggerfallUnity.Instance.Option_InteriorLightPrefab.gameObject, string.Empty, parent, Vector3.zero); // Set local position to billboard origin, otherwise light transform is at base of billboard go.transform.localPosition = Vector3.zero; // Adjust position of light for standing lights as their source comes more from top than middle Vector2 size = DaggerfallUnity.Instance.MeshReader.GetScaledBillboardSize(210, obj.TextureRecord) * MeshReader.GlobalScale; switch (obj.TextureRecord) { case 0: // Bowl with fire go.transform.localPosition += new Vector3(0, -0.1f, 0); break; case 1: // Campfire // todo break; case 2: // Skull candle go.transform.localPosition += new Vector3(0, 0.1f, 0); break; case 3: // Candle go.transform.localPosition += new Vector3(0, 0.1f, 0); break; case 4: // Candle in bowl // todo break; case 5: // Candleholder with 3 candles go.transform.localPosition += new Vector3(0, 0.15f, 0); break; case 6: // Skull torch go.transform.localPosition += new Vector3(0, 0.6f, 0); break; case 7: // Wooden chandelier with extinguished candles // todo break; case 8: // Turkis lamp // do nothing break; case 9: // Metallic chandelier with burning candles go.transform.localPosition += new Vector3(0, 0.4f, 0); break; case 10: // Metallic chandelier with extinguished candles // todo break; case 11: // Candle in lamp go.transform.localPosition += new Vector3(0, -0.4f, 0); break; case 12: // Extinguished lamp // todo break; case 13: // Round lamp (e.g. main lamp in mages guild) go.transform.localPosition += new Vector3(0, -0.35f, 0); break; case 14: // Standing lantern go.transform.localPosition += new Vector3(0, size.y / 2, 0); break; case 15: // Standing lantern round go.transform.localPosition += new Vector3(0, size.y / 2, 0); break; case 16: // Mounted Torch with thin holder // todo break; case 17: // Mounted torch 1 go.transform.localPosition += new Vector3(0, 0.2f, 0); break; case 18: // Mounted Torch 2 // todo break; case 19: // Pillar with firebowl // todo break; case 20: // Brazier torch go.transform.localPosition += new Vector3(0, 0.6f, 0); break; case 21: // Standing candle go.transform.localPosition += new Vector3(0, size.y / 2.4f, 0); break; case 22: // Round lantern with medium chain go.transform.localPosition += new Vector3(0, -0.5f, 0); break; case 23: // Wooden chandelier with burning candles // todo break; case 24: // Lantern with long chain go.transform.localPosition += new Vector3(0, -1.85f, 0); break; case 25: // Lantern with medium chain go.transform.localPosition += new Vector3(0, -1.0f, 0); break; case 26: // Lantern with short chain // todo break; case 27: // Lantern with no chain go.transform.localPosition += new Vector3(0, -0.02f, 0); break; case 28: // Street Lantern 1 // todo break; case 29: // Street Lantern 2 // todo break; } // adjust properties of light sources (e.g. Shrink light radius of candles) Light light = go.GetComponent<Light>(); switch (obj.TextureRecord) { case 0: // Bowl with fire light.range = 20.0f; light.intensity = 1.1f; light.color = new Color(0.95f, 0.91f, 0.63f); break; case 1: // Campfire // todo break; case 2: // Skull candle light.range /= 3f; light.intensity = 0.6f; light.color = new Color(1.0f, 0.99f, 0.82f); break; case 3: // Candle light.range /= 3f; break; case 4: // Candle with base light.range /= 3f; break; case 5: // Candleholder with 3 candles light.range = 7.5f; light.intensity = 0.33f; light.color = new Color(1.0f, 0.89f, 0.61f); break; case 6: // Skull torch light.range = 15.0f; light.intensity = 0.75f; light.color = new Color(1.0f, 0.93f, 0.62f); break; case 7: // Wooden chandelier with extinguished candles // todo break; case 8: // Turkis lamp light.color = new Color(0.68f, 1.0f, 0.94f); break; case 9: // metallic chandelier with burning candles light.range = 15.0f; light.intensity = 0.65f; light.color = new Color(1.0f, 0.92f, 0.6f); break; case 10: // Metallic chandelier with extinguished candles // todo break; case 11: // Candle in lamp light.range = 5.0f; light.intensity = 0.5f; break; case 12: // Extinguished lamp // todo break; case 13: // Round lamp (e.g. main lamp in mages guild) light.range *= 1.2f; light.intensity = 1.1f; light.color = new Color(0.93f, 0.84f, 0.49f); break; case 14: // Standing lantern // todo break; case 15: // Standing lantern round // todo break; case 16: // Mounted Torch with thin holder // todo break; case 17: // Mounted torch 1 light.intensity = 0.8f; light.color = new Color(1.0f, 0.97f, 0.87f); break; case 18: // Mounted Torch 2 // todo break; case 19: // Pillar with firebowl // todo break; case 20: // Brazier torch light.range = 12.0f; light.intensity = 0.75f; light.color = new Color(1.0f, 0.92f, 0.72f); break; case 21: // Standing candle light.range /= 3f; light.intensity = 0.5f; light.color = new Color(1.0f, 0.95f, 0.67f); break; case 22: // Round lantern with medium chain light.intensity = 1.5f; light.color = new Color(1.0f, 0.95f, 0.78f); break; case 23: // Wooden chandelier with burning candles // todo break; case 24: // Lantern with long chain light.intensity = 1.4f; light.color = new Color(1.0f, 0.98f, 0.64f); break; case 25: // Lantern with medium chain light.intensity = 1.4f; light.color = new Color(1.0f, 0.98f, 0.64f); break; case 26: // Lantern with short chain light.intensity = 1.4f; light.color = new Color(1.0f, 0.98f, 0.64f); break; case 27: // Lantern with no chain light.intensity = 1.4f; light.color = new Color(1.0f, 0.98f, 0.64f); break; case 28: // Street Lantern 1 // todo break; case 29: // Street Lantern 2 // todo break; } // TODO: Could also adjust light colour and intensity, or change prefab entirely above for any obj.TextureRecord }
/// <summary> /// Attempts to get a Daggerfall block from BLOCKS.BSA. /// </summary> /// <param name="name">Name of block.</param> /// <param name="blockOut">DFBlock data out.</param> /// <returns>True if successful.</returns> public bool GetBlock(string name, out DFBlock blockOut) { blockOut = new DFBlock(); if (!isReady) return false; // Get block data blockOut = blockFileReader.GetBlock(name); if (blockOut.Type == DFBlock.BlockTypes.Unknown) { DaggerfallUnity.LogMessage(string.Format("Unknown block '{0}'.", name), true); return false; } return true; }
private static void AddProps( DaggerfallUnity dfUnity, ref DFBlock blockData, out List<StaticDoor> doorsOut, ModelCombiner combiner = null, Transform parent = null) { doorsOut = new List<StaticDoor>(); // Iterate through all misc records foreach (DFBlock.RmbBlock3dObjectRecord obj in blockData.RmbBlock.Misc3dObjectRecords) { // Get model transform Vector3 modelPosition = new Vector3(obj.XPos, -obj.YPos + propsOffsetY, obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; Vector3 modelRotation = new Vector3(0, -obj.YRotation / BlocksFile.RotationDivisor, 0); Matrix4x4 modelMatrix = Matrix4x4.TRS(modelPosition, Quaternion.Euler(modelRotation), Vector3.one); // Get model data ModelData modelData; dfUnity.MeshReader.GetModelData(obj.ModelIdNum, out modelData); // Does this model have doors? if (modelData.Doors != null) doorsOut.AddRange(GameObjectHelper.GetStaticDoors(ref modelData, blockData.Index, 0, modelMatrix)); // Add or combine if (combiner == null) AddStandaloneModel(dfUnity, ref modelData, modelMatrix, parent); else combiner.Add(ref modelData, modelMatrix); } }
private static void AddModels( DaggerfallUnity dfUnity, ref DFBlock blockData, out List<StaticDoor> doorsOut, ModelCombiner combiner = null, Transform parent = null) { doorsOut = new List<StaticDoor>(); // Iterate through all subrecords int recordCount = 0; foreach (DFBlock.RmbSubRecord subRecord in blockData.RmbBlock.SubRecords) { // Get subrecord transform Vector3 subRecordPosition = new Vector3(subRecord.XPos, 0, BlocksFile.RMBDimension - subRecord.ZPos) * MeshReader.GlobalScale; Vector3 subRecordRotation = new Vector3(0, -subRecord.YRotation / BlocksFile.RotationDivisor, 0); Matrix4x4 subRecordMatrix = Matrix4x4.TRS(subRecordPosition, Quaternion.Euler(subRecordRotation), Vector3.one); // Iterate through models in this subrecord foreach (DFBlock.RmbBlock3dObjectRecord obj in subRecord.Exterior.Block3dObjectRecords) { // Get model transform Vector3 modelPosition = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale; Vector3 modelRotation = new Vector3(0, -obj.YRotation / BlocksFile.RotationDivisor, 0); Matrix4x4 modelMatrix = subRecordMatrix * Matrix4x4.TRS(modelPosition, Quaternion.Euler(modelRotation), Vector3.one); // Get model data ModelData modelData; dfUnity.MeshReader.GetModelData(obj.ModelIdNum, out modelData); // Does this model have doors? if (modelData.Doors != null) doorsOut.AddRange(GameObjectHelper.GetStaticDoors(ref modelData, blockData.Index, recordCount, modelMatrix)); // Add or combine if (combiner == null || IsCityGate(obj.ModelIdNum)) AddStandaloneModel(dfUnity, ref modelData, modelMatrix, parent); else combiner.Add(ref modelData, modelMatrix); } // Increment record count recordCount++; } }
/// <summary> /// Add random enemies from encounter tables based on dungeon type, monster power, and seed. /// </summary> /// <param name="go">GameObject to add monsters to.</param> /// <param name="editorObjects">Editor objects containing flats.</param> /// <param name="dungeonType">Dungeon type selects the encounter table.</param> /// <param name="monsterPower">Value between 0-1 for lowest monster power to highest.</param> /// <param name="monsterVariance">Adjust final index +/- this value in encounter table.</param> /// <param name="seed">Random seed for encounters.</param> /// <param name="serialize">Allow for serialization when available.</param> public static void AddRandomEnemies( GameObject go, DFBlock.RdbObject[] editorObjects, DFRegion.DungeonTypes dungeonType, float monsterPower, ref DFBlock blockData, int monsterVariance = 4, int seed = 0, bool serialize = true) { const int randomMonsterFlatIndex = 15; DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return; // Must have import enabled and prefab set if (!dfUnity.Option_ImportEnemyPrefabs || dfUnity.Option_EnemyPrefab == null) return; // Editor objects array must be populated if (editorObjects == null || editorObjects.Length == 0) return; // Add parent node GameObject randomEnemiesNode = new GameObject("Random Enemies"); randomEnemiesNode.transform.parent = go.transform; // Seed random generator UnityEngine.Random.seed = seed; // Iterate editor flats for enemies for (int i = 0; i < editorObjects.Length; i++) { // Add random enemy objects if (editorObjects[i].Resources.FlatResource.TextureRecord == randomMonsterFlatIndex) AddRandomRDBEnemy(editorObjects[i], dungeonType, monsterPower, monsterVariance, randomEnemiesNode.transform, ref blockData, serialize); } }
/// <summary> /// Create base RDB block by name and get back DFBlock data. /// </summary> /// <param name="blockName">Name of block.</param> /// <param name="blockDataOut">DFBlock data out.</param> /// <param name="textureTable">Optional texture table for dungeon.</param> /// <param name="allowExitDoors">Add exit doors to block.</param> /// <param name="cloneFrom">Clone and build on a prefab object template.</param> /// <param name="serialize">Allow for serialization of supported sub-objects.</param> /// <returns>Block GameObject.</returns> public static GameObject CreateBaseGameObject( string blockName, Dictionary<int, ActionLink> actionLinkDict, out DFBlock blockDataOut, int[] textureTable = null, bool allowExitDoors = true, DaggerfallRDBBlock cloneFrom = null, bool serialize = true) { blockDataOut = new DFBlock(); // Validate if (string.IsNullOrEmpty(blockName)) return null; if (!blockName.EndsWith(".RDB", StringComparison.InvariantCultureIgnoreCase)) return null; DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return null; // Get block data blockDataOut = dfUnity.ContentReader.BlockFileReader.GetBlock(blockName); return CreateBaseGameObject(ref blockDataOut, actionLinkDict, textureTable, allowExitDoors, cloneFrom, serialize); }
/// <summary> /// Constructs a Vector3 from magnitude and direction in RDB action resource. /// </summary> private static void GetRotationActionVector(ref DaggerfallAction action, DFBlock.RdbActionAxes axis) { Vector3 vector = Vector3.zero; float magnitude = action.Magnitude; switch (axis) { case DFBlock.RdbActionAxes.NegativeX: vector.x = -magnitude; break; case DFBlock.RdbActionAxes.NegativeY: vector.y = -magnitude; break; case DFBlock.RdbActionAxes.NegativeZ: vector.z = -magnitude; break; case DFBlock.RdbActionAxes.PositiveX: vector.x = magnitude; break; case DFBlock.RdbActionAxes.PositiveY: vector.y = magnitude; break; case DFBlock.RdbActionAxes.PositiveZ: vector.z = magnitude; break; default: magnitude = 0f; break; } action.ActionRotation = vector / BlocksFile.RotationDivisor; }
private static void AddAction( GameObject go, string description, int soundID_and_index, float duration, float magnitude, int axis_raw, DFBlock.RdbTriggerFlags triggerFlag, DFBlock.RdbActionFlags actionFlag, long loadID = 0 ) { DaggerfallAction action = go.AddComponent<DaggerfallAction>(); action.ModelDescription = description; action.ActionDuration = duration; action.Magnitude = magnitude; action.Index = soundID_and_index; action.TriggerFlag = triggerFlag; action.ActionFlag = actionFlag; action.LoadID = loadID; // If SaveLoadManager present in game then attach SerializableActionObject if (SaveLoadManager.Instance != null) { go.AddComponent<SerializableActionObject>(); } //if a collision type action or action flat, add DaggerFallActionCollision component if (action.TriggerFlag == DFBlock.RdbTriggerFlags.Collision01 || action.TriggerFlag == DFBlock.RdbTriggerFlags.Collision03 || action.TriggerFlag == DFBlock.RdbTriggerFlags.DualTrigger || action.TriggerFlag == DFBlock.RdbTriggerFlags.Collision09) { DaggerfallActionCollision collision = go.AddComponent<DaggerfallActionCollision>(); collision.isFlat = false; } else if (description == "FLT") { DaggerfallActionCollision collision = go.AddComponent<DaggerfallActionCollision>(); collision.isFlat = true; } switch (action.ActionFlag) { case DFBlock.RdbActionFlags.Translation: { action.Magnitude = magnitude; GetTranslationActionVector(ref action, (DFBlock.RdbActionAxes)axis_raw); } break; case DFBlock.RdbActionFlags.Rotation: { action.Magnitude = magnitude; GetRotationActionVector(ref action, (DFBlock.RdbActionAxes)axis_raw); } break; case DFBlock.RdbActionFlags.PositiveX: { action.ActionDuration = 50; action.Magnitude = axis_raw * 8; GetTranslationActionVector(ref action, DFBlock.RdbActionAxes.PositiveX); } break; case DFBlock.RdbActionFlags.NegativeX: { action.ActionDuration = 50; action.Magnitude = axis_raw * 8; GetTranslationActionVector(ref action, DFBlock.RdbActionAxes.NegativeX); } break; case DFBlock.RdbActionFlags.PositiveY: { action.ActionDuration = 50; action.Magnitude = axis_raw * 8; GetTranslationActionVector(ref action, DFBlock.RdbActionAxes.PositiveY); } break; case DFBlock.RdbActionFlags.NegativeY: { action.ActionDuration = 50; action.Magnitude = axis_raw * 8; GetTranslationActionVector(ref action, DFBlock.RdbActionAxes.NegativeY); } break; case DFBlock.RdbActionFlags.PositiveZ: { action.ActionDuration = 50; action.Magnitude = axis_raw * 8; GetTranslationActionVector(ref action, DFBlock.RdbActionAxes.PositiveZ); } break; case DFBlock.RdbActionFlags.NegativeZ: { action.ActionDuration = 50; action.Magnitude = axis_raw * 8; GetTranslationActionVector(ref action, DFBlock.RdbActionAxes.NegativeZ); } break; default: { //Dmg actions use axis value to modifiy tot. dmg. action.Magnitude = axis_raw; } break; } // A quick hack to fix special-case rotation issues. // Currently unknown if there is data indicating different rotation behaviour or if something else is happening. switch (description) { case "LID": action.ActionRotation = new Vector3(0, 0, -90f); // Coffin lids (e.g. Scourg barrow) break; case "WHE": action.ActionRotation = new Vector3(0, -360f, 0); // Wheels (e.g. Direnni Tower) break; } //Add audio AddActionAudioSource(go, (uint)action.Index); }
/// <summary> /// Constructs a Vector3 from magnitude and direction in RDB action resource. /// </summary> private static void GetTranslationActionVector(ref DaggerfallAction action, DFBlock.RdbActionAxes axis) { Vector3 vector = Vector3.zero; float magnitude = action.Magnitude; switch (axis) { case DFBlock.RdbActionAxes.NegativeX: vector.x = magnitude; break; case DFBlock.RdbActionAxes.NegativeY: vector.y = -magnitude; break; case DFBlock.RdbActionAxes.NegativeZ: vector.z = magnitude; break; case DFBlock.RdbActionAxes.PositiveX: vector.x = -magnitude; break; case DFBlock.RdbActionAxes.PositiveY: vector.y = magnitude; break; case DFBlock.RdbActionAxes.PositiveZ: vector.z = -magnitude; break; default: magnitude = 0f; break; } action.ActionTranslation = vector * MeshReader.GlobalScale; }
/// <summary> /// Adds action door to scene. /// </summary> private static GameObject AddActionDoor(DaggerfallUnity dfUnity, uint modelId, DFBlock.RdbObject obj, Transform parent, long loadID = 0) { if (dfUnity.Option_DungeonDoorPrefab == null) return null; // Get model data and matrix ModelData modelData; dfUnity.MeshReader.GetModelData(modelId, out modelData); Matrix4x4 modelMatrix = GetModelMatrix(obj); // Instantiate door prefab and add model GameObject go = GameObjectHelper.InstantiatePrefab(dfUnity.Option_DungeonDoorPrefab.gameObject, string.Empty, parent, Vector3.zero); GameObjectHelper.CreateDaggerfallMeshGameObject(modelId, parent, false, go, true); // Resize box collider to new mesh bounds BoxCollider boxCollider = go.GetComponent<BoxCollider>(); MeshRenderer meshRenderer = go.GetComponent<MeshRenderer>(); if (boxCollider != null && meshRenderer != null) { boxCollider.center = meshRenderer.bounds.center; boxCollider.size = meshRenderer.bounds.size; } // Get rotation angle for each axis float degreesX = -obj.Resources.ModelResource.XRotation / BlocksFile.RotationDivisor; float degreesY = -obj.Resources.ModelResource.YRotation / BlocksFile.RotationDivisor; float degreesZ = -obj.Resources.ModelResource.ZRotation / BlocksFile.RotationDivisor; // Apply transforms go.transform.Rotate(0, degreesY, 0, Space.World); go.transform.Rotate(degreesX, 0, 0, Space.World); go.transform.Rotate(0, 0, degreesZ, Space.World); go.transform.localPosition = modelMatrix.GetColumn(3); // Get action door script DaggerfallActionDoor actionDoor = go.GetComponent<DaggerfallActionDoor>(); // Set starting lock value if (obj.Resources.ModelResource.TriggerFlag_StartingLock >= 16) { actionDoor.StartingLockValue = (int)obj.Resources.ModelResource.TriggerFlag_StartingLock; } // Set LoadID actionDoor.LoadID = loadID; return go; }
/// <summary> /// Add fixed enemies. /// </summary> /// <param name="go">GameObject to add monsters to.</param> /// <param name="editorObjects">Editor objects containing flats.</param> /// <param name="serialize">Allow for serialization when available.</param> public static void AddFixedEnemies( GameObject go, DFBlock.RdbObject[] editorObjects, ref DFBlock blockData, bool serialize = true) { const int fixedMonsterFlatIndex = 16; DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return; // Must have import enabled and prefab set if (!dfUnity.Option_ImportEnemyPrefabs || dfUnity.Option_EnemyPrefab == null) return; // Editor objects array must be populated if (editorObjects == null || editorObjects.Length == 0) return; // Add parent node GameObject fixedEnemiesNode = new GameObject("Fixed Enemies"); fixedEnemiesNode.transform.parent = go.transform; // Iterate editor flats for enemies for (int i = 0; i < editorObjects.Length; i++) { // Add fixed enemy objects if (editorObjects[i].Resources.FlatResource.TextureRecord == fixedMonsterFlatIndex) AddFixedRDBEnemy(editorObjects[i], fixedEnemiesNode.transform, ref blockData, serialize); } }
private static void AddActionModelHelper( GameObject go, Dictionary<int, ActionLink> actionLinkDict, DFBlock.RdbObject rdbObj, ref DFBlock blockData, bool serialize) { DFBlock.RdbModelResource obj = rdbObj.Resources.ModelResource; string description = blockData.RdbBlock.ModelReferenceList[obj.ModelIndex].Description; int soundID_Index = obj.SoundIndex; float duration = obj.ActionResource.Duration; float magnitude = obj.ActionResource.Magnitude; int axis = obj.ActionResource.Axis; DFBlock.RdbTriggerFlags triggerFlag = DFBlock.RdbTriggerFlags.None; DFBlock.RdbActionFlags actionFlag = DFBlock.RdbActionFlags.None; if (actionLinkDict != null) { // Set action flag if valid / known if (Enum.IsDefined(typeof(DFBlock.RdbActionFlags), (DFBlock.RdbActionFlags)obj.ActionResource.Flags)) actionFlag = (DFBlock.RdbActionFlags)obj.ActionResource.Flags; // Set trigger flag if valid / known if (Enum.IsDefined(typeof(DFBlock.RdbTriggerFlags), (DFBlock.RdbTriggerFlags)obj.TriggerFlag_StartingLock)) triggerFlag = (DFBlock.RdbTriggerFlags)obj.TriggerFlag_StartingLock; // Add action node to actionLink dictionary if (!actionLinkDict.ContainsKey(rdbObj.This)) { ActionLink link; link.nextKey = obj.ActionResource.NextObjectOffset; link.prevKey = obj.ActionResource.PreviousObjectOffset; link.gameObject = go; actionLinkDict.Add(rdbObj.This, link); } } // Create unique LoadID for save sytem long loadID = 0; if (serialize) loadID = (blockData.Index << 24) + rdbObj.This; AddAction(go, description, soundID_Index, duration, magnitude, axis, triggerFlag, actionFlag, loadID); }
/// <summary> /// Check is model has action record. /// </summary> private static bool HasAction(DFBlock.RdbObject obj) { DFBlock.RdbActionResource action = obj.Resources.ModelResource.ActionResource; if (action.Flags != 0) return true; return false; }
private static void AddFixedRDBEnemy(DFBlock.RdbObject obj, Transform parent, ref DFBlock blockData, bool serialize) { // Get type value and ignore known invalid types int typeValue = (int)(obj.Resources.FlatResource.FactionMobileId & 0xff); if (typeValue == 99) return; // Create unique LoadID for save sytem long loadID = 0; if (serialize) loadID = (blockData.Index << 24) + obj.This; // Cast to enum MobileTypes type = (MobileTypes)(obj.Resources.FlatResource.FactionMobileId & 0xff); AddEnemy(obj, type, parent, loadID); }
/// <summary> /// Check if model is a hinged action door. /// </summary> private static bool IsActionDoor(ref DFBlock blockData, DFBlock.RdbObject obj, int modelReference) { // Always reject red brick doors, they are not action doors despite having "DOR" attached if (blockData.RdbBlock.ModelReferenceList[modelReference].ModelIdNum == redBrickDoorModelID) return false; // Otherwise Check if this is a door (DOR) or double-door (DDR) string description = blockData.RdbBlock.ModelReferenceList[modelReference].Description; if (description == "DOR" || description == "DDR") return true; return false; }
private static GameObject AddLight(DaggerfallUnity dfUnity, DFBlock.RdbObject obj, Transform parent) { // Spawn light gameobject float range = obj.Resources.LightResource.Radius * MeshReader.GlobalScale; Vector3 position = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale; GameObject go = GameObjectHelper.InstantiatePrefab(dfUnity.Option_DungeonLightPrefab.gameObject, string.Empty, parent, position); Light light = go.GetComponent<Light>(); if (light != null) { light.range = range * 3; } return go; }
/// <summary> /// Add all block flats. /// </summary> public static void AddFlats( GameObject go, Dictionary<int, ActionLink> actionLinkDict, ref DFBlock blockData, out DFBlock.RdbObject[] editorObjectsOut, out GameObject[] startMarkersOut, out GameObject[] enterMarkersOut) { List<DFBlock.RdbObject> editorObjects = new List<DFBlock.RdbObject>(); List<GameObject> startMarkers = new List<GameObject>(); List<GameObject> enterMarkers = new List<GameObject>(); editorObjectsOut = null; startMarkersOut = null; enterMarkersOut = null; DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return; // Add parent node GameObject flatsNode = new GameObject("Flats"); flatsNode.transform.parent = go.transform; // Iterate all groups foreach (DFBlock.RdbObjectRoot group in blockData.RdbBlock.ObjectRootList) { // Skip empty object groups if (null == group.RdbObjects) continue; // Look for flats in this group foreach (DFBlock.RdbObject obj in group.RdbObjects) { if (obj.Type == DFBlock.RdbResourceTypes.Flat) { // Add flat GameObject flatObject = AddFlat(obj, flatsNode.transform); // Store editor objects and start markers int archive = obj.Resources.FlatResource.TextureArchive; int record = obj.Resources.FlatResource.TextureRecord; if (archive == TextureReader.EditorFlatsTextureArchive) { editorObjects.Add(obj); if (record == 10) startMarkers.Add(flatObject); else if (record == 8) enterMarkers.Add(flatObject); //add editor flats to actionLinkDict if (!actionLinkDict.ContainsKey(obj.This)) { ActionLink link; link.gameObject = flatObject; link.nextKey = obj.Resources.FlatResource.NextObjectOffset; link.prevKey = -1; actionLinkDict.Add(obj.This, link); } } //add action component to flat if it has an action if (obj.Resources.FlatResource.Action > 0) { AddActionFlatHelper(flatObject, actionLinkDict, ref blockData, obj); } } } } // Output editor objects editorObjectsOut = editorObjects.ToArray(); startMarkersOut = startMarkers.ToArray(); enterMarkersOut = enterMarkers.ToArray(); }
private static void AddRandomRDBEnemy( DFBlock.RdbObject obj, DFRegion.DungeonTypes dungeonType, float monsterPower, int monsterVariance, Transform parent, ref DFBlock blockData, bool serialize) { // Must have a dungeon type if (dungeonType == DFRegion.DungeonTypes.NoDungeon) return; // Get dungeon type index int dungeonIndex = (int)dungeonType >> 8; if (dungeonIndex < RandomEncounters.EncounterTables.Length) { // Get encounter table RandomEncounterTable table = RandomEncounters.EncounterTables[dungeonIndex]; // Get base monster index into table int baseMonsterIndex = (int)((float)table.Enemies.Length * monsterPower); // Set min index int minMonsterIndex = baseMonsterIndex - monsterVariance; if (minMonsterIndex < 0) minMonsterIndex = 0; // Set max index int maxMonsterIndex = baseMonsterIndex + monsterVariance; if (maxMonsterIndex >= table.Enemies.Length) maxMonsterIndex = table.Enemies.Length; // Get random monster from table MobileTypes type = table.Enemies[UnityEngine.Random.Range(minMonsterIndex, maxMonsterIndex)]; // Create unique LoadID for save sytem long loadID = 0; if (serialize) loadID = (blockData.Index << 24) + obj.This; // Add enemy AddEnemy(obj, type, parent, loadID); } else { DaggerfallUnity.LogMessage(string.Format("RDBLayout: Dungeon type {0} is out of range or unknown.", dungeonType), true); } }
/// <summary> /// Add light prefabs. /// </summary> public static void AddLights(GameObject go, ref DFBlock blockData) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) return; // Do nothing if import option not enabled or missing prefab if (!dfUnity.Option_ImportLightPrefabs || dfUnity.Option_DungeonLightPrefab == null) return; // Add parent node GameObject lightsNode = new GameObject("Lights"); lightsNode.transform.parent = go.transform; // Iterate all groups foreach (DFBlock.RdbObjectRoot group in blockData.RdbBlock.ObjectRootList) { // Skip empty object groups if (null == group.RdbObjects) continue; // Look for lights in this group foreach (DFBlock.RdbObject obj in group.RdbObjects) { if (obj.Type == DFBlock.RdbResourceTypes.Light) AddLight(dfUnity, obj, lightsNode.transform); } } }