/// <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(); }
/// <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(); }
// Set texture and height data for city tiles public static void SetLocationTiles(ContentReader contentReader, ref MapPixelData mapPixel) { const int tileDim = 16; const int chunkDim = 8; // Get location DFLocation location = contentReader.MapFileReader.GetLocation(mapPixel.mapRegionIndex, mapPixel.mapLocationIndex); // Centre location tiles inside terrain area int startX = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Width * tileDim) / 2; int startY = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Height * tileDim) / 2; // Full 8x8 locations have "terrain blend space" around walls to smooth down random terrain towards flat area. // This is indicated by texture index > 55 (ground texture range is 0-55), larger values indicate blend space. // We need to know rect of actual city area so we can use blend space outside walls. int xmin = terrainSampleDim, ymin = terrainSampleDim; int xmax = 0, ymax = 0; // Iterate blocks of this location for (int blockY = 0; blockY < location.Exterior.ExteriorData.Height; blockY++) { for (int blockX = 0; blockX < location.Exterior.ExteriorData.Width; blockX++) { // Get block data DFBlock block; string blockName = contentReader.MapFileReader.GetRmbBlockName(ref location, blockX, blockY); if (!contentReader.GetBlock(blockName, out block)) continue; // Copy ground tile info for (int tileY = 0; tileY < tileDim; tileY++) { for (int tileX = 0; tileX < tileDim; tileX++) { DFBlock.RmbGroundTiles tile = block.RmbBlock.FldHeader.GroundData.GroundTiles[tileX, (tileDim - 1) - tileY]; int xpos = startX + blockX * tileDim + tileX; int ypos = startY + blockY * tileDim + tileY; int offset = (ypos * terrainSampleDim) + xpos; int record = tile.TextureRecord; if (tile.TextureRecord < 56) { // Track interior bounds of location tiled area if (xpos < xmin) xmin = xpos; if (xpos > xmax) xmax = xpos; if (ypos < ymin) ymin = ypos; if (ypos > ymax) ymax = ypos; // Store texture data from block mapPixel.samples[offset].record = record; mapPixel.samples[offset].flip = tile.IsFlipped; mapPixel.samples[offset].rotate = tile.IsRotated; mapPixel.samples[offset].location = true; } } } } } // Update location rect with extra clearance const int extraClearance = 2; Rect locationRect = new Rect(); locationRect.xMin = xmin - extraClearance; locationRect.xMax = xmax + extraClearance; locationRect.yMin = ymin - extraClearance; locationRect.yMax = ymax + extraClearance; mapPixel.locationRect = locationRect; }