// Update terrain nature private void UpdateTerrainNature(TerrainDesc terrainDesc) { //System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); //long startTime = stopwatch.ElapsedMilliseconds; // Setup billboards DaggerfallTerrain dfTerrain = terrainDesc.terrainObject.GetComponent <DaggerfallTerrain>(); DaggerfallBillboardBatch dfBillboardBatch = terrainDesc.billboardBatchObject.GetComponent <DaggerfallBillboardBatch>(); if (dfTerrain && dfBillboardBatch) { // Get current climate and nature archive DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(dfTerrain.MapData.worldClimate); int natureArchive = climate.NatureArchive; if (dfUnity.WorldTime.SeasonValue == WorldTime.Seasons.Winter) { // Offset to snow textures natureArchive++; } dfBillboardBatch.SetMaterial(natureArchive); TerrainHelper.LayoutNatureBillboards(dfTerrain, dfBillboardBatch, TerrainScale); } // Only set active again once complete terrainDesc.billboardBatchObject.SetActive(true); //long totalTime = stopwatch.ElapsedMilliseconds - startTime; //DaggerfallUnity.LogMessage(string.Format("Time to update terrain nature: {0}ms", totalTime), true); }
/// <summary> /// Gets NameHelper.BankType in player's current region. /// In practice this will always be Redguard/Breton/Nord. /// Supporting a few other name banks for possible diversity later. /// </summary> public NameHelper.BankTypes GetNameBankOfCurrentRegion() { DFLocation.ClimateSettings settings = MapsFile.GetWorldClimateSettings(climateSettings.WorldClimate); NameHelper.BankTypes bankType; switch (settings.Names) { case FactionFile.FactionRaces.Redguard: bankType = NameHelper.BankTypes.Redguard; break; case FactionFile.FactionRaces.Nord: bankType = NameHelper.BankTypes.Nord; break; case FactionFile.FactionRaces.DarkElf: bankType = NameHelper.BankTypes.DarkElf; break; case FactionFile.FactionRaces.WoodElf: bankType = NameHelper.BankTypes.WoodElf; break; default: case FactionFile.FactionRaces.Breton: bankType = NameHelper.BankTypes.Breton; break; } return(bankType); }
/// <summary> /// Updates climate material based on current map pixel data. /// </summary> public void UpdateClimateMaterial() { // Update atlas texture if world climate changed if (currentWorldClimate != MapData.worldClimate || dfUnity.WorldTime.Now.SeasonValue != season) { // Get current climate and ground archive DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(MapData.worldClimate); int groundArchive = climate.GroundArchive; if (climate.ClimateType != DFLocation.ClimateBaseType.Desert && dfUnity.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter) { // Offset to snow textures groundArchive++; } // Get tileset material to "steal" atlas texture for our shader // TODO: Improve material system to handle custom shaders Material tileSetMaterial = dfUnity.MaterialReader.GetTerrainTilesetMaterial(groundArchive); currentWorldClimate = MapData.worldClimate; // Assign textures terrainMaterial.SetTexture("_TileAtlasTex", tileSetMaterial.mainTexture); terrainMaterial.SetTexture("_TilemapTex", tileMapTexture); terrainMaterial.SetInt("_TilemapDim", tileMapDim); } }
private void UpdateWorldInfo(int x, int y) { // Requires MAPS.BSA connection if (dfUnity.ContentReader.MapFileReader == null) { return; } // Get climate and politic data currentClimateIndex = dfUnity.ContentReader.MapFileReader.GetClimateIndex(x, y); currentPoliticIndex = dfUnity.ContentReader.MapFileReader.GetPoliticIndex(x, y); climateSettings = MapsFile.GetWorldClimateSettings(currentClimateIndex); if (currentPoliticIndex > 128) { regionName = dfUnity.ContentReader.MapFileReader.GetRegionName(currentPoliticIndex - 128); } else if (currentPoliticIndex == 64) { regionName = "Ocean"; } else { regionName = "Unknown"; } // Get region data currentRegion = dfUnity.ContentReader.MapFileReader.GetRegion(CurrentRegionIndex); // Get location data ContentReader.MapSummary mapSummary; if (dfUnity.ContentReader.HasLocation(x, y, out mapSummary)) { currentLocation = dfUnity.ContentReader.MapFileReader.GetLocation(mapSummary.RegionIndex, mapSummary.MapIndex); hasCurrentLocation = true; CalculateWorldLocationRect(); } else { currentLocation = new DFLocation(); hasCurrentLocation = false; ClearWorldLocationRect(); } // Get location type if (hasCurrentLocation) { if (currentRegion.MapTable == null) { DaggerfallUnity.LogMessage(string.Format("PlayerGPS: Location {0} in region{1} has a null MapTable.", currentLocation.Name, currentLocation.RegionName)); } else { currentLocationType = currentRegion.MapTable[mapSummary.MapIndex].LocationType; } } }
/// <summary> /// used to set remaining fields of person (with random values) and update billboards /// this function should be called after race, gender and personOutfitVariant has been set beforehand /// </summary> void SetPerson() { // do several things in switch statement: // get person's face texture record index for this race and gender and outfit variant // get correct nameBankType for this race int[] recordIndices = null; NameHelper.BankTypes nameBankType; switch (race) { case Races.Redguard: recordIndices = (gender == Genders.Male) ? maleRedguardFaceRecordIndex : femaleRedguardFaceRecordIndex; //nameBankType = NameHelper.BankTypes.Redguard; break; case Races.Nord: recordIndices = (gender == Genders.Male) ? maleNordFaceRecordIndex : femaleNordFaceRecordIndex; //nameBankType = NameHelper.BankTypes.Nord; break; case Races.Breton: default: recordIndices = (gender == Genders.Male) ? maleBretonFaceRecordIndex : femaleBretonFaceRecordIndex; //nameBankType = NameHelper.BankTypes.Breton; break; } // create name for npc DFLocation.ClimateSettings climateSettings = MapsFile.GetWorldClimateSettings(GameManager.Instance.PlayerGPS.ClimateSettings.WorldClimate); switch (climateSettings.Names) { case FactionFile.FactionRaces.Breton: default: nameBankType = NameHelper.BankTypes.Breton; break; case FactionFile.FactionRaces.Nord: nameBankType = NameHelper.BankTypes.Nord; break; case FactionFile.FactionRaces.Redguard: nameBankType = NameHelper.BankTypes.Redguard; break; } this.nameNPC = DaggerfallUnity.Instance.NameHelper.FullName(nameBankType, gender); // get face record id to use (randomize portrait for current person outfit variant) int personFaceVariant = Random.Range(0, numPersonFaceVariants); this.personFaceRecordId = recordIndices[personOutfitVariant] + personFaceVariant; // set billboard to correct race, gender and outfit variant billboard = GetComponentInChildren <MobilePersonBillboard>(); billboard.SetPerson(race, gender, personOutfitVariant, isGuard); }
/// <summary> /// Updates climate material based on current map pixel data. /// </summary> public void UpdateClimateMaterial() { // Update atlas texture if world climate changed if (currentWorldClimate != MapData.worldClimate || dfUnity.WorldTime.Now.SeasonValue != season) { // Get current climate and ground archive DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(MapData.worldClimate); int groundArchive = climate.GroundArchive; if (climate.ClimateType != DFLocation.ClimateBaseType.Desert && dfUnity.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter) { // Offset to snow textures groundArchive++; } if ((SystemInfo.supports2DArrayTextures) && DaggerfallUnity.Settings.EnableTextureArrays) { Material tileMaterial = dfUnity.MaterialReader.GetTerrainTextureArrayMaterial(groundArchive); currentWorldClimate = MapData.worldClimate; // Assign textures (propagate material settings from tileMaterial to terrainMaterial) terrainMaterial.SetTexture("_TileTexArr", tileMaterial.GetTexture("_TileTexArr")); terrainMaterial.SetTexture("_TileNormalMapTexArr", tileMaterial.GetTexture("_TileNormalMapTexArr")); if (tileMaterial.IsKeywordEnabled("_NORMALMAP")) { terrainMaterial.EnableKeyword("_NORMALMAP"); } else { terrainMaterial.DisableKeyword("_NORMALMAP"); } terrainMaterial.SetTexture("_TileMetallicGlossMapTexArr", tileMaterial.GetTexture("_TileMetallicGlossMapTexArr")); terrainMaterial.SetTexture("_TilemapTex", tileMapTexture); } else { // Get tileset material to "steal" atlas texture for our shader // TODO: Improve material system to handle custom shaders Material tileSetMaterial = dfUnity.MaterialReader.GetTerrainTilesetMaterial(groundArchive); currentWorldClimate = MapData.worldClimate; // Assign textures terrainMaterial.SetTexture("_TileAtlasTex", tileSetMaterial.GetTexture("_TileAtlasTex")); terrainMaterial.SetTexture("_TilemapTex", tileMapTexture); terrainMaterial.SetInt("_TilemapDim", tilemapDimension); } } }
/// <summary> /// Parses climate informations. /// </summary> /// <param name="worldClimate">Index of world climate.</param> /// <returns>Parsed climate informations.</returns> protected virtual (int GroundArchive, DFLocation.ClimateSettings Settings, bool IsWinter) GetClimateInfo(int worldClimate) { // Get current climate and ground archive DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(worldClimate); int groundArchive = climate.GroundArchive; bool isWinter = false; if (climate.ClimateType != DFLocation.ClimateBaseType.Desert && DaggerfallUnity.Instance.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter) { // Offset to snow textures groundArchive++; isWinter = true; } return(groundArchive, climate, isWinter); }
private void UpdateWorldInfo(int x, int y) { // Get climate and politic data currentClimateIndex = dfUnity.ContentReader.MapFileReader.GetClimateIndex(x, y); currentPoliticIndex = dfUnity.ContentReader.MapFileReader.GetPoliticIndex(x, y); climateSettings = MapsFile.GetWorldClimateSettings(currentClimateIndex); if (currentPoliticIndex > 128) { regionName = dfUnity.ContentReader.MapFileReader.GetRegionName(currentPoliticIndex - 128); } else if (currentPoliticIndex == 64) { regionName = "Ocean"; } else { regionName = "Unknown"; } // Get region data currentRegion = dfUnity.ContentReader.MapFileReader.GetRegion(CurrentRegionIndex); // Get location data ContentReader.MapSummary mapSummary; if (dfUnity.ContentReader.HasLocation(x, y, out mapSummary)) { currentLocation = dfUnity.ContentReader.MapFileReader.GetLocation(mapSummary.RegionIndex, mapSummary.MapIndex); hasCurrentLocation = true; CalculateWorldLocationRect(); } else { currentLocation = new DFLocation(); hasCurrentLocation = false; ClearWorldLocationRect(); } // Get location type if (hasCurrentLocation) { currentLocationType = currentRegion.MapTable[mapSummary.MapIndex].Type; } }
/// <summary> /// Gets the dominant race in player's current region. /// This seems to be based on subclimate rather than FACTION.TXT. /// The faction data has very little diversity and does not match observed race in many regions. /// </summary> public Races GetRaceOfCurrentRegion() { // Racial distribution in Daggerfall: // * Desert, Desert2, Rainforest = Redguard // * Mountain, MountainWoods = Nord // * Swamp, Subtropical, Woodlands, Default = Breton DFLocation.ClimateSettings settings = MapsFile.GetWorldClimateSettings(climateSettings.WorldClimate); switch (settings.People) { case FactionFile.FactionRaces.Redguard: return(Races.Redguard); case FactionFile.FactionRaces.Nord: return(Races.Nord); case FactionFile.FactionRaces.Breton: default: return(Races.Breton); } }
/// <summary> /// Creates a new block node. /// </summary> /// <param name="name">Block name.</param> /// <param name="climate">Climate settings.</param> /// <param name="clearGroundTextures">Clear ground plane texture dictionary.</param> /// <returns>BlockNode.</returns> public BlockNode CreateBlockNode(string name, DFLocation.ClimateSettings?climate, bool clearGroundTextures) { // Load block DFBlock block; if (!LoadDaggerfallBlock(name, out block)) { return(null); } // Set default world climate if (climate == null) { climate = MapsFile.GetWorldClimateSettings(defaultWorldClimate); } // Reset ground plane texture cache if (clearGroundTextures) { textureManager.ClearGroundTextures(); } // Build node BlockNode node = null; switch (block.Type) { case DFBlock.BlockTypes.Rmb: textureManager.ClimateType = climate.Value.ClimateType; node = BuildRMBNode(ref block, climate.Value); break; case DFBlock.BlockTypes.Rdb: textureManager.ClimateType = DFLocation.ClimateBaseType.None; node = BuildRDBNode(ref block); break; } return(node); }
// Drops nature flats based on random chance scaled by simple rules public static void LayoutNatureBillboards(DaggerfallTerrain dfTerrain, DaggerfallBillboardBatch dfBillboardBatch, float terrainScale, int terrainDist) { const float maxSteepness = 50f; // 50 const float slopeSinkRatio = 70f; // Sink flats slightly into ground as slope increases to prevent floaty trees. const float baseChanceOnDirt = 0.2f; // 0.2 const float baseChanceOnGrass = 0.9f; // 0.4 const float baseChanceOnStone = 0.05f; // 0.05 // Location Rect is expanded slightly to give extra clearance around locations const int natureClearance = 4; Rect rect = dfTerrain.MapData.locationRect; if (rect.x > 0 && rect.y > 0) { rect.xMin -= natureClearance; rect.xMax += natureClearance; rect.yMin -= natureClearance; rect.yMax += natureClearance; } // Chance scaled based on map pixel height // This tends to produce sparser lowlands and denser highlands // Adjust or remove clamp range to influence nature generation float elevationScale = (dfTerrain.MapData.worldHeight / 128f); elevationScale = Mathf.Clamp(elevationScale, 0.4f, 1.0f); // Chance scaled by base climate type float climateScale = 1.0f; DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(dfTerrain.MapData.worldClimate); switch (climate.ClimateType) { case DFLocation.ClimateBaseType.Desert: // Just lower desert for now climateScale = 0.25f; break; } float chanceOnDirt = baseChanceOnDirt * elevationScale * climateScale; float chanceOnGrass = baseChanceOnGrass * elevationScale * climateScale; float chanceOnStone = baseChanceOnStone * elevationScale * climateScale; // Get terrain Terrain terrain = dfTerrain.gameObject.GetComponent <Terrain>(); if (!terrain) { return; } // Get terrain data TerrainData terrainData = terrain.terrainData; if (!terrainData) { return; } // Remove exiting billboards dfBillboardBatch.Clear(); MeshReplacement.ClearNatureGameObjects(terrain); // Seed random with terrain key Random.InitState(MakeTerrainKey(dfTerrain.MapPixelX, dfTerrain.MapPixelY)); // Just layout some random flats spread evenly across entire map pixel area // Flats are aligned with tiles, max 16129 billboards per batch Vector2 tilePos = Vector2.zero; int tDim = MapsFile.WorldMapTileDim; int hDim = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension; float scale = terrainData.heightmapScale.x * (float)hDim / (float)tDim; float maxTerrainHeight = DaggerfallUnity.Instance.TerrainSampler.MaxTerrainHeight; float beachLine = DaggerfallUnity.Instance.TerrainSampler.BeachElevation; for (int y = 0; y < tDim; y++) { for (int x = 0; x < tDim; x++) { // Reject based on steepness float steepness = terrainData.GetSteepness((float)x / tDim, (float)y / tDim); if (steepness > maxSteepness) { continue; } // Reject if inside location rect (expanded slightly to give extra clearance around locations) tilePos.x = x; tilePos.y = y; if (rect.x > 0 && rect.y > 0 && rect.Contains(tilePos)) { continue; } // Chance also determined by tile type int tile = dfTerrain.MapData.tilemapSamples[x, y] & 0x3F; if (tile == 1) { // Dirt if (Random.Range(0f, 1f) > chanceOnDirt) { continue; } } else if (tile == 2) { // Grass if (Random.Range(0f, 1f) > chanceOnGrass) { continue; } } else if (tile == 3) { // Stone if (Random.Range(0f, 1f) > chanceOnStone) { continue; } } else { // Anything else continue; } int hx = (int)Mathf.Clamp(hDim * ((float)x / (float)tDim), 0, hDim - 1); int hy = (int)Mathf.Clamp(hDim * ((float)y / (float)tDim), 0, hDim - 1); float height = dfTerrain.MapData.heightmapSamples[hy, hx] * maxTerrainHeight; // x & y swapped in heightmap for TerrainData.SetHeights() // Reject if too close to water if (height < beachLine) { continue; } // Sample height and position billboard Vector3 pos = new Vector3(x * scale, 0, y * scale); float height2 = terrain.SampleHeight(pos + terrain.transform.position); pos.y = height2 - (steepness / slopeSinkRatio); // Add to batch unless a mesh replacement is found int record = Random.Range(1, 32); if (terrainDist > 1 || !MeshReplacement.ImportNatureGameObject(dfBillboardBatch.TextureArchive, record, terrain, x, y)) { dfBillboardBatch.AddItem(record, pos); } else if (!NatureMeshUsed) { NatureMeshUsed = true; // Signal that nature mesh has been used to initiate extra terrain updates } } } // Apply new batch dfBillboardBatch.Apply(); }
// Drops nature flats based on random chance scaled by simple rules public static void LayoutNatureBillboards(DaggerfallTerrain dfTerrain, DaggerfallBillboardBatch dfBillboardBatch, float terrainScale) { const float maxSteepness = 50f; // 50 const float chanceOnDirt = 0.2f; // 0.2 const float chanceOnGrass = 0.9f; // 0.4 const float chanceOnStone = 0.05f; // 0.05 // Get terrain Terrain terrain = dfTerrain.gameObject.GetComponent <Terrain>(); if (!terrain) { return; } // Get terrain data TerrainData terrainData = terrain.terrainData; if (!terrainData) { return; } // Remove exiting billboards dfBillboardBatch.Clear(); // Seed random with terrain key UnityEngine.Random.seed = MakeTerrainKey(dfTerrain.MapPixelX, dfTerrain.MapPixelY); // Just layout some random flats spread evenly across entire map pixel area // Flats are aligned with tiles, max 127x127 in billboard batch Vector2 tilePos = Vector2.zero; float scale = terrainData.heightmapScale.x; int dim = TerrainHelper.terrainTileDim - 1; for (int y = 0; y < dim; y++) { for (int x = 0; x < dim; x++) { // Reject based on steepness float steepness = terrainData.GetSteepness((float)x / dim, (float)y / dim); if (steepness > maxSteepness) { continue; } // Reject if inside location rect // Rect is expanded slightly to give extra clearance around locations tilePos.x = x; tilePos.y = y; const int natureClearance = 4; Rect rect = dfTerrain.MapData.locationRect; if (rect.x > 0 && rect.y > 0) { rect.xMin -= natureClearance; rect.xMin += natureClearance; rect.yMin -= natureClearance; rect.yMax += natureClearance; if (rect.Contains(tilePos)) { continue; } } // Chance scaled based on map pixel height // This tends to produce sparser lowlands and denser highlands // Adjust or remove clamp range to influence nature generation float elevationScale = (dfTerrain.MapData.worldHeight / 128f); elevationScale = Mathf.Clamp(elevationScale, 0.4f, 1.0f); // Chance scaled by base climate type float climateScale = 1.0f; DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(dfTerrain.MapData.worldClimate); switch (climate.ClimateType) { case DFLocation.ClimateBaseType.Desert: // Just lower desert for now climateScale = 0.25f; break; } // Chance also determined by tile type WorldSample sample = TerrainHelper.GetSample(ref dfTerrain.MapData.samples, x, y); if (sample.record == 1) { // Dirt if (UnityEngine.Random.Range(0f, 1f) > chanceOnDirt * elevationScale * climateScale) { continue; } } else if (sample.record == 2) { // Grass if (UnityEngine.Random.Range(0f, 1f) > chanceOnGrass * elevationScale * climateScale) { continue; } } else if (sample.record == 3) { // Stone if (UnityEngine.Random.Range(0f, 1f) > chanceOnStone * elevationScale * climateScale) { continue; } } else { // Anything else continue; } // Sample height and position billboard Vector3 pos = new Vector3(x * scale, 0, y * scale); float height = terrain.SampleHeight(pos + terrain.transform.position); pos.y = height; // Reject if too close to water float beachLine = DaggerfallUnity.Instance.TerrainSampler.BeachElevation * terrainScale; if (height < beachLine) { continue; } // Add to batch int record = UnityEngine.Random.Range(1, 32); dfBillboardBatch.AddItem(record, pos); } } // Apply new batch dfBillboardBatch.Apply(); }