/// <summary> /// Gets BuildingSummary array generated from DFBlock data. /// DFBlock data should be provided from RMBLayout.GetLocationBuildingData() output. /// Otherwise not all building data will be present. /// </summary> /// <param name="blockData">DFBlock data.</param> /// <param name="layoutX">X coordindate in map layout used to generate building key.</param> /// <param name="layoutY">Y coordindate in map layout used to generate building key.</param> /// <returns>BuildingSummary.</returns> public static BuildingSummary[] GetBuildingData(DFBlock blockData, int layoutX = -1, int layoutY = -1) { // Store building information int buildingCount = blockData.RmbBlock.SubRecords.Length; BuildingSummary[] buildings = new BuildingSummary[buildingCount]; for (int i = 0; i < buildingCount; i++) { // Create building summary buildings[i] = new BuildingSummary(); // Set building data DFLocation.BuildingData buildingData = blockData.RmbBlock.FldHeader.BuildingDataList[i]; buildings[i].buildingKey = BuildingDirectory.MakeBuildingKey((byte)layoutX, (byte)layoutY, (byte)i); buildings[i].NameSeed = buildingData.NameSeed; buildings[i].FactionId = buildingData.FactionId; buildings[i].BuildingType = buildingData.BuildingType; buildings[i].Quality = buildingData.Quality; // Set building transform info DFBlock.RmbSubRecord subRecord = blockData.RmbBlock.SubRecords[i]; buildings[i].Position = new Vector3(subRecord.XPos, 0, BlocksFile.RMBDimension - subRecord.ZPos) * MeshReader.GlobalScale; buildings[i].Rotation = new Vector3(0, -subRecord.YRotation / BlocksFile.RotationDivisor, 0); buildings[i].Matrix = Matrix4x4.TRS(buildings[i].Position, Quaternion.Euler(buildings[i].Rotation), Vector3.one); // First model of record is building model if (subRecord.Exterior.Block3dObjectRecords.Length > 0) { buildings[i].ModelID = subRecord.Exterior.Block3dObjectRecords[0].ModelIdNum; } } return(buildings); }
/// <summary> /// Gets BuildingSummary array generated from DFBlock data. /// </summary> /// <param name="blockData"></param> /// <returns></returns> public static BuildingSummary[] GetBuildingData(DFBlock blockData) { // Store building information int buildingCount = blockData.RmbBlock.SubRecords.Length; BuildingSummary[] buildings = new BuildingSummary[buildingCount]; for (int i = 0; i < buildingCount; i++) { // Create building summary buildings[i] = new BuildingSummary(); // Set building data DFLocation.BuildingData buildingData = blockData.RmbBlock.FldHeader.BuildingDataList[i]; buildings[i].NameSeed = buildingData.NameSeed; buildings[i].FactionId = buildingData.FactionId; buildings[i].LocationId = buildingData.LocationId; buildings[i].BuildingType = buildingData.BuildingType; buildings[i].Quality = buildingData.Quality; // Set building transform info DFBlock.RmbSubRecord subRecord = blockData.RmbBlock.SubRecords[i]; buildings[i].Position = new Vector3(subRecord.XPos, 0, BlocksFile.RMBDimension - subRecord.ZPos) * MeshReader.GlobalScale; buildings[i].Rotation = new Vector3(0, -subRecord.YRotation / BlocksFile.RotationDivisor, 0); buildings[i].Matrix = Matrix4x4.TRS(buildings[i].Position, Quaternion.Euler(buildings[i].Rotation), Vector3.one); } return(buildings); }
private static void OnTransitionToInterior_VariantShopTavernNPCsprites(PlayerEnterExit.TransitionEventArgs args) { PlayerEnterExit playerEnterExit = GameManager.Instance.PlayerEnterExit; DFLocation.BuildingData buildingData = playerEnterExit.Interior.BuildingData; if (buildingData.BuildingType == DFLocation.BuildingTypes.Tavern || RMBLayout.IsShop(buildingData.BuildingType)) { Billboard[] dfBillboards = playerEnterExit.Interior.GetComponentsInChildren <Billboard>(); foreach (Billboard billboard in dfBillboards) { int record = -1; if (billboard.Summary.Archive == 182 && billboard.Summary.Record == 0) { record = GetRecord_182_0(buildingData.Quality); // (buildingData.Quality - 1) / 4; #if UNITY_EDITOR Debug.LogFormat("Shop quality {0} using record {1} to replace 182_0", buildingData.Quality, record); #endif } else if (billboard.Summary.Archive == 182 && billboard.Summary.Record == 1) { if (buildingData.Quality < 12) { // Using big test flats version record = 4; } else if (buildingData.Quality > 14) { record = 5; } #if UNITY_EDITOR Debug.LogFormat("Tavern quality {0} using record {1} to replace 182_1", buildingData.Quality, record); #endif } else if (billboard.Summary.Archive == 182 && billboard.Summary.Record == 2) { if (buildingData.Quality > 12) { record = 6; } #if UNITY_EDITOR Debug.LogFormat("Tavern quality {0} using record {1} to replace 182_2", buildingData.Quality, record); #endif } if (record > -1) { billboard.SetMaterial(197, record); GameObjectHelper.AlignBillboardToGround(billboard.gameObject, billboard.Summary.Size); } } } }
/// <summary> /// Gets all RMB blocks from a location populated with building data from MAPS.BSA. /// This method is using "best effort" process at this point in time. /// However, it does yield very accurate results most of the time. /// Please use exception handling when calling this method for now. /// It will be progressed over time. /// </summary> /// <param name="location">Location to use.</param> /// <param name="blocksOut">Array of blocks populated with data from MAPS.BSA.</param> public static void GetLocationBuildingData(DFLocation location, out DFBlock[] blocksOut) { List <BuildingPoolItem> namedBuildingPool = new List <BuildingPoolItem>(); List <DFBlock> blocks = new List <DFBlock>(); // Get content reader ContentReader contentReader = DaggerfallUnity.Instance.ContentReader; if (contentReader == null) { throw new Exception("GetCompleteBuildingData() could not find ContentReader."); } // Store named buildings in pool for distribution for (int i = 0; i < location.Exterior.BuildingCount; i++) { DFLocation.BuildingData building = location.Exterior.Buildings[i]; if (IsNamedBuilding(building.BuildingType)) { BuildingPoolItem bpi = new BuildingPoolItem(); bpi.buildingData = building; bpi.used = false; namedBuildingPool.Add(bpi); } } // Get building summary of all blocks in this location int width = location.Exterior.ExteriorData.Width; int height = location.Exterior.ExteriorData.Height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Get block name string blockName = contentReader.BlockFileReader.CheckName(contentReader.MapFileReader.GetRmbBlockName(ref location, x, y)); // Get block data DFBlock block; if (!contentReader.GetBlock(blockName, out block)) { throw new Exception("GetCompleteBuildingData() could not read block " + blockName); } // Make a copy of the building data array for our block copy since we're modifying it DFLocation.BuildingData[] buildingArray = new DFLocation.BuildingData[block.RmbBlock.FldHeader.BuildingDataList.Length]; Array.Copy(block.RmbBlock.FldHeader.BuildingDataList, buildingArray, block.RmbBlock.FldHeader.BuildingDataList.Length); block.RmbBlock.FldHeader.BuildingDataList = buildingArray; // Assign building data for this block BuildingReplacementData buildingReplacementData; for (int i = 0; i < block.RmbBlock.SubRecords.Length; i++) { DFLocation.BuildingData building = block.RmbBlock.FldHeader.BuildingDataList[i]; if (IsNamedBuilding(building.BuildingType)) { // Try to find next building and merge data BuildingPoolItem item; if (!GetNextBuildingFromPool(namedBuildingPool, building.BuildingType, out item)) { Debug.LogFormat("End of city building list reached without finding building type {0} in location {1}.{2}", building.BuildingType, location.RegionName, location.Name); } // Copy found city building data to block level building.NameSeed = item.buildingData.NameSeed; building.FactionId = item.buildingData.FactionId; building.Sector = item.buildingData.Sector; building.LocationId = item.buildingData.LocationId; building.Quality = item.buildingData.Quality; // Check for replacement building data and use it if found if (WorldDataReplacement.GetBuildingReplacementData(blockName, block.Index, i, out buildingReplacementData)) { // Use custom building values from replacement data, but only use up pool item if factionId is zero if (buildingReplacementData.FactionId != 0) { // Don't use up pool item and set factionId, NameSeed, Quality from replacement data item.used = false; building.FactionId = buildingReplacementData.FactionId; building.Quality = buildingReplacementData.Quality; building.NameSeed = (ushort)(buildingReplacementData.NameSeed + location.LocationIndex); // Vary name seed by location } // Always override type building.BuildingType = (DFLocation.BuildingTypes)buildingReplacementData.BuildingType; } // Matched to classic: special handling for some Order of the Raven buildings if (block.RmbBlock.FldHeader.OtherNames != null && block.RmbBlock.FldHeader.OtherNames[i] == "KRAVE01.HS2") { building.BuildingType = DFLocation.BuildingTypes.GuildHall; building.FactionId = 414; } // Set whatever building data we could find block.RmbBlock.FldHeader.BuildingDataList[i] = building; } } // Save block data blocks.Add(block); } } // Send blocks array back to caller blocksOut = blocks.ToArray(); }
/// <summary> /// Gets all RMB blocks from a location populated with building data from MAPS.BSA. /// This method is using "best effort" process at this point in time. /// However, it does yield very accurate results most of the time. /// Please use exception handling when calling this method for now. /// It will be progressed over time. /// </summary> /// <param name="location">Location to use.</param> /// <param name="blocksOut">Array of blocks populated with data from MAPS.BSA.</param> public static void GetLocationBuildingData(DFLocation location, out DFBlock[] blocksOut) { List <BuildingPoolItem> namedBuildingPool = new List <BuildingPoolItem>(); List <DFBlock> blocks = new List <DFBlock>(); // Get content reader ContentReader contentReader = DaggerfallUnity.Instance.ContentReader; if (contentReader == null) { throw new Exception("GetCompleteBuildingData() could not find ContentReader."); } // Store named buildings in pool for distribution for (int i = 0; i < location.Exterior.BuildingCount; i++) { DFLocation.BuildingData building = location.Exterior.Buildings[i]; if (IsNamedBuilding(building.BuildingType)) { BuildingPoolItem bpi = new BuildingPoolItem(); bpi.buildingData = building; bpi.used = false; namedBuildingPool.Add(bpi); } } // Get building summary of all blocks in this location int width = location.Exterior.ExteriorData.Width; int height = location.Exterior.ExteriorData.Height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Get block name string blockName = contentReader.BlockFileReader.CheckName(contentReader.MapFileReader.GetRmbBlockName(ref location, x, y)); // Get block data DFBlock block; if (!contentReader.GetBlock(blockName, out block)) { throw new Exception("GetCompleteBuildingData() could not read block " + blockName); } // Assign building data for this block BuildingReplacementData buildingReplacementData; for (int i = 0; i < block.RmbBlock.SubRecords.Length; i++) { DFLocation.BuildingData building = block.RmbBlock.FldHeader.BuildingDataList[i]; if (IsNamedBuilding(building.BuildingType)) { // Check for replacement building data and use it if found if (WorldDataReplacement.GetBuildingReplacementData(blockName, block.Index, i, out buildingReplacementData)) { // Use custom building values from replacement data, don't use pool or maps file building.NameSeed = location.Exterior.Buildings[0].NameSeed; building.FactionId = buildingReplacementData.FactionId; building.BuildingType = (DFLocation.BuildingTypes)buildingReplacementData.BuildingType; building.LocationId = location.Exterior.Buildings[0].LocationId; building.Quality = buildingReplacementData.Quality; } else { // Try to find next building and merge data BuildingPoolItem item; if (!GetNextBuildingFromPool(namedBuildingPool, building.BuildingType, out item)) { Debug.LogFormat("End of city building list reached without finding building type {0} in location {1}.{2}", building.BuildingType, location.RegionName, location.Name); } else { // Copy found city building data to block level building.NameSeed = item.buildingData.NameSeed; building.FactionId = item.buildingData.FactionId; building.Sector = item.buildingData.Sector; building.LocationId = item.buildingData.LocationId; building.Quality = item.buildingData.Quality; } } // Set whatever building data we could find block.RmbBlock.FldHeader.BuildingDataList[i] = building; } } // Save block data blocks.Add(block); } } // Send blocks array back to caller blocksOut = blocks.ToArray(); }
private static void OnTransitionToInterior_VariantResidenceNPCsprites(PlayerEnterExit.TransitionEventArgs args) { if (villagerVarietyMod != null && villagerVarietyNumVariants == 0) { ModManager.Instance.SendModMessage(VILLAGERVARIETY_MODNAME, "getNumVariants", null, (string message, object data) => { villagerVarietyNumVariants = (int)data; }); } PlayerEnterExit playerEnterExit = GameManager.Instance.PlayerEnterExit; DFLocation.BuildingData buildingData = playerEnterExit.Interior.BuildingData; if (RMBLayout.IsResidence(buildingData.BuildingType)) { Races race = GetClimateRace(); int gender = -1; Billboard[] dfBillboards = playerEnterExit.Interior.GetComponentsInChildren <Billboard>(); foreach (Billboard billboard in dfBillboards) { if (billboard.Summary.Archive == 182) { gender = GetGender182(billboard.Summary.Record); } else if (billboard.Summary.Archive == 184) { gender = GetGender184(billboard.Summary.Record); } if (gender != -1) { StaticNPC npc = billboard.GetComponent <StaticNPC>(); if (npc && npc.Data.factionID == 0) { int faceVariant = npc.Data.nameSeed % 29; Debug.LogFormat("Replace house NPC {0}.{1} with faceVariant {2} - {3}", billboard.Summary.Archive, billboard.Summary.Record, faceVariant, faceVariant < 24); if (faceVariant < 24) { int outfitVariant = npc.Data.nameSeed % 4; int archive = gender == (int)Genders.Male ? raceArchivesMale[race][outfitVariant] : raceArchivesFemale[race][outfitVariant]; int record = 5; int faceRecord = gender == (int)Genders.Male ? raceFaceRecordMale[race][outfitVariant] : raceFaceRecordFemale[race][outfitVariant]; faceRecord += faceVariant; bool materialSet = false; if (villagerVarietyMod != null) { int variant = npc.Data.nameSeed % villagerVarietyNumVariants; string season = ""; //ModManager.Instance.SendModMessage(VILLAGERVARIETY_MODNAME, "getSeasonStr", null, (string message, object data) => { season = (string)data; }); Debug.LogFormat("Replace house NPC {0}.{1} with {2}.{3}, outfit: {4} faceRecord: {5} ({6}) variant: {7} season: {8}", billboard.Summary.Archive, billboard.Summary.Record, archive, record, outfitVariant, faceRecord, faceVariant, variant, season); string imageName = null; ModManager.Instance.SendModMessage(VILLAGERVARIETY_MODNAME, "getImageName", new object[] { archive, record, 0, faceRecord, variant, season }, (string message, object data) => { imageName = (string)data; }); if (!string.IsNullOrEmpty(imageName) && villagerVarietyMod.HasAsset(imageName)) { // Get texture and create material Texture2D texture = villagerVarietyMod.GetAsset <Texture2D>(imageName); Material material = MaterialReader.CreateStandardMaterial(MaterialReader.CustomBlendMode.Cutout); material.mainTexture = texture; // Apply material to mesh renderer MeshRenderer meshRenderer = billboard.GetComponent <MeshRenderer>(); meshRenderer.sharedMaterial = material; // Create mesh and setup UV map for mesh filter Vector2 size; Mesh mesh = DaggerfallUnity.Instance.MeshReader.GetBillboardMesh(new Rect(0, 0, 1, 1), archive, record, out size); mesh.uv = new Vector2[] { new Vector2(0, 1), new Vector2(1, 1), new Vector2(0, 0), new Vector2(1, 0) }; MeshFilter meshFilter = billboard.GetComponent <MeshFilter>(); Destroy(meshFilter.sharedMesh); meshFilter.sharedMesh = mesh; materialSet = true; } } if (!materialSet) { billboard.SetMaterial(archive, record); billboard.FramesPerSecond = 1; } GameObjectHelper.AlignBillboardToGround(billboard.gameObject, billboard.Summary.Size); Dictionary <int, FlatsFile.FlatData> flatsDict = DaggerfallUnity.Instance.ContentReader.FlatsFileReader.FlatsDict; int flatId = FlatsFile.GetFlatID(npc.Data.billboardArchiveIndex, npc.Data.billboardRecordIndex); #if UNITY_EDITOR Debug.LogFormat("Replacing face dict for {0} with {1} (for {2}.{3} / {4}.{5})", flatsDict[flatId].faceIndex, faceRecord, npc.Data.billboardArchiveIndex, npc.Data.billboardRecordIndex, billboard.Summary.Archive, billboard.Summary.Record); #endif flatsDict[flatId] = new FlatsFile.FlatData() { archive = billboard.Summary.Archive, record = billboard.Summary.Record, faceIndex = faceRecord, }; } } } } } }