public void SetLocation(DFLocation location, bool performLayout = true) { if (!ReadyCheck()) { return; } // Validate if (this.isSet) { throw new Exception("This location has already been set."); } if (!location.Loaded) { throw new Exception("DFLocation not loaded."); } // Set summary summary = new LocationSummary(); summary.MapID = location.MapTableData.MapId; summary.Longitude = (int)location.MapTableData.Longitude; summary.Latitude = (int)location.MapTableData.Latitude; DFPosition mapPixel = MapsFile.LongitudeLatitudeToMapPixel(summary.Longitude, summary.Latitude); DFPosition worldCoord = MapsFile.MapPixelToWorldCoord(mapPixel.X, mapPixel.Y); summary.MapPixelX = mapPixel.X; summary.MapPixelY = mapPixel.Y; summary.WorldCoordX = worldCoord.X; summary.WorldCoordZ = worldCoord.Y; summary.RegionName = location.RegionName; summary.LocationName = location.Name; summary.WorldClimate = (MapsFile.Climates)location.Climate.WorldClimate; summary.LocationType = location.MapTableData.LocationType; summary.DungeonType = location.MapTableData.DungeonType; summary.HasDungeon = location.HasDungeon; summary.Climate = ClimateSwaps.FromAPIClimateBase(location.Climate.ClimateType); summary.Nature = ClimateSwaps.FromAPITextureSet(location.Climate.NatureSet); summary.SkyBase = location.Climate.SkyBase; summary.BlockWidth = location.Exterior.ExteriorData.Width; summary.BlockHeight = location.Exterior.ExteriorData.Height; summary.LegacyLocation = location; // Assign starting climate CurrentSeason = ClimateSeason.Summer; CurrentClimate = summary.Climate; CurrentNatureSet = summary.Nature; // Perform layout if (performLayout) { LayoutLocation(ref location); ApplyClimateSettings(); } // Set location rect SetLocationRect(); // Seal location isSet = true; }
/// <summary> /// Get model name for current climate and season. /// </summary> private static string ClimateSeasonName(string modelName) { if (!GameManager.HasInstance) { return(modelName); } // Get climate ClimateBases climateBase = ClimateSwaps.FromAPIClimateBase(GameManager.Instance.PlayerGPS.ClimateSettings.ClimateType); string name = modelName + "_" + climateBase.ToString(); // Get season if (climateBase != ClimateBases.Desert) { if (DaggerfallUnity.Instance.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter) { name += "Winter"; } else { name += "Summer"; } } return(name); }
/// <summary> /// Get a new Material based on climate. /// </summary> /// <param name="key">Material key.</param> /// <param name="climate">New climate base.</param> /// <param name="season">New season.</param> /// <param name="windowStyle">New window style.</param> /// <returns>New material.</returns> public Material ChangeClimate(int key, ClimateBases climate, ClimateSeason season, WindowStyle windowStyle) { // Ready check if (!IsReady) { return(null); } // Reverse key and apply climate int archive, record, frame; ReverseTextureKey(key, out archive, out record, out frame); archive = ClimateSwaps.ApplyClimate(archive, record, climate, season); // Get new material Material material; CachedMaterial cm = GetMaterialFromCache(archive, record, out material); // Handle windows if (cm.isWindow) { ChangeWindowEmissionColor(material, windowStyle); } return(material); }
/// <summary> /// Set ground climate. /// </summary> /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param> /// <param name="climate">Climate to set.</param> /// <param name="season">Season to set.</param> public void SetClimate(DaggerfallUnity dfUnity, ClimateBases climate, ClimateSeason season) { int archive = ClimateSwaps.GetGroundArchive(climate, season); SetClimate(dfUnity, archive, season); summary.climate = climate; }
private void ApplyMaterials(bool force, int[] dungeonTextureTable = null) { if (Materials == null || Materials.Length == 0) { return; } if (UseDungeonTextureTable && dungeonTextureTable == null) { return; } try { var meshRenderer = GetComponent <MeshRenderer>(); if (!meshRenderer) { Debug.LogErrorFormat("Failed to find MeshRenderer on {0}.", name); return; } DFLocation.ClimateBaseType climateBaseType = GameManager.Instance.PlayerGPS.ClimateSettings.ClimateType; ClimateBases climate = ClimateSwaps.FromAPIClimateBase(climateBaseType); ClimateSeason season = DaggerfallUnity.Instance.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter ? ClimateSeason.Winter : ClimateSeason.Summer; Material[] materials = meshRenderer.sharedMaterials; for (int i = 0; i < Materials.Length; i++) { int index = Materials[i].Index; if (!force && materials[index]) { Debug.LogWarningFormat("A runtime material is being assigned to {0} (index {1}) but current material is not equal to null." + " Make sure you are not including unnecessary auto-generated materials.", meshRenderer.name, i); } materials[index] = GetMaterial(Materials[i], climateBaseType, climate, season, dungeonTextureTable); if (!materials[index]) { Debug.LogErrorFormat("Failed to find material for {0} (index {1}).", meshRenderer.name, i); } } meshRenderer.sharedMaterials = materials; } catch (Exception e) { Debug.LogException(e); } finally { hasAppliedMaterials = true; } }
private Material GetMaterial(RuntimeMaterial runtimeMaterial, ClimateBases climate, ClimateSeason season) { int archive = runtimeMaterial.Archive; int record = runtimeMaterial.Record; if (runtimeMaterial.ApplyClimate) { archive = ClimateSwaps.ApplyClimate(archive, record, climate, season); } return(DaggerfallUnity.Instance.MaterialReader.GetMaterial(archive, record)); }
private Material GetMaterial(RuntimeMaterial runtimeMaterial, DFLocation.ClimateBaseType climateBaseType, ClimateBases climate, ClimateSeason season, int[] dungeonTextureTable) { int archive = runtimeMaterial.Archive; int record = runtimeMaterial.Record; if (dungeonTextureTable != null) archive = DungeonTextureTables.ApplyTextureTable(archive, dungeonTextureTable, climateBaseType); if (runtimeMaterial.ApplyClimate) archive = ClimateSwaps.ApplyClimate(archive, record, climate, season); return DaggerfallUnity.Instance.MaterialReader.GetMaterial(archive, record); }
/// <summary> /// Makes texture results for given material. /// </summary> /// <param name="material">Unity material.</param> /// <param name="archive">Texture archive.</param> /// <param name="record">Record index.</param> /// <returns>Results for the given material.</returns> public static GetTextureResults MakeResults(Material material, int archive, int record) { string path = Path.Combine(DaggerfallUnity.Instance.MaterialReader.TextureReader.Arena2Path, TextureFile.IndexToFileName(archive)); TextureFile textureFile = new TextureFile(path, FileUsage.UseMemory, true); return(new GetTextureResults() { albedoMap = GetTextureOrDefault(material, Uniforms.MainTex), normalMap = GetTextureOrDefault(material, Uniforms.BumpMap), emissionMap = GetTextureOrDefault(material, Uniforms.EmissionMap), singleRect = new Rect(0, 0, 1, 1), isWindow = ClimateSwaps.IsExteriorWindow(archive, record), isEmissive = material.HasProperty(Uniforms.EmissionMap), textureFile = textureFile }); }
// Update terrain nature private void UpdateTerrainNature(TerrainDesc terrainDesc) { // Setup billboards DaggerfallTerrain dfTerrain = terrainDesc.terrainObject.GetComponent <DaggerfallTerrain>(); DaggerfallBillboardBatch dfBillboardBatch = terrainDesc.billboardBatchObject.GetComponent <DaggerfallBillboardBatch>(); if (dfTerrain && dfBillboardBatch) { // Get current climate and nature archive int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue); dfBillboardBatch.SetMaterial(natureArchive); TerrainHelper.LayoutNatureBillboards(dfTerrain, dfBillboardBatch, TerrainScale); } // Only set active again once complete terrainDesc.billboardBatchObject.SetActive(true); }
///<summary> /// Get all accepted names for a model ordered by priority. ///</summary> private static string[] GetName(uint modelID) { if (!GameManager.HasInstance) { return new string[] { modelID.ToString() } } ; ClimateBases climateBase = ClimateSwaps.FromAPIClimateBase(GameManager.Instance.PlayerGPS.ClimateSettings.ClimateType); string climateName = climateBase == ClimateBases.Desert ? string.Format("{0}_{1}", modelID, climateBase): string.Format("{0}_{1}{2}", modelID, climateBase, DaggerfallUnity.Instance.WorldTime.Now.SeasonValue); return(new string[] { climateName, modelID.ToString() }); }
private void Display3dModelSelection(int selectedIdx) { if (goModel) { Object.Destroy(goModel); goModel = null; } // Position camera and set model id uint modelId = 0; if (housesForSale == null) { camera.transform.position = new Vector3(0, 12, DaggerfallBankManager.GetShipCameraDist((ShipType)selectedIdx)); modelId = DaggerfallBankManager.GetShipModelId((ShipType)selectedIdx); } else { camera.transform.position = new Vector3(0, 3, -20); BuildingSummary house = housesForSale[selectedIdx]; modelId = house.ModelID; } // Inject custom GameObject if available else create standard mesh game object for the model goModel = MeshReplacement.ImportCustomGameobject(modelId, goBankPurchase.transform, new Matrix4x4()); if (goModel == null) { goModel = GameObjectHelper.CreateDaggerfallMeshGameObject(modelId, goBankPurchase.transform); } goModel.layer = layer; // Apply current climate ClimateBases climateBase = ClimateSwaps.FromAPIClimateBase(GameManager.Instance.PlayerGPS.ClimateSettings.ClimateType); ClimateSeason season = (DaggerfallUnity.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter) ? ClimateSeason.Winter : ClimateSeason.Summer; DaggerfallMesh dfMesh = goModel.GetComponent <DaggerfallMesh>(); dfMesh.SetClimate(climateBase, season, WindowStyle.Day); }
private int GetNatureArchive() { int natureArchive; switch (ClimateUse) { case LocationClimateUse.UseLocation: natureArchive = ClimateSwaps.GetNatureArchive(summary.Nature, CurrentSeason); break; case LocationClimateUse.Custom: natureArchive = ClimateSwaps.GetNatureArchive(CurrentNatureSet, CurrentSeason); break; case LocationClimateUse.Disabled: default: natureArchive = ClimateSwaps.GetNatureArchive(ClimateNatureSets.TemperateWoodland, ClimateSeason.Summer); break; } return(natureArchive); }
// 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 int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue); 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); }
// Update terrain data // Spreads loading across several frames to reduce gameplay stalls // This can also be done using true multi-threading, but at much greater // complexity for only minor visible gains. // Only yields after initial init complete private IEnumerator UpdateTerrains() { // First stage updates terrain heightmaps for (int i = 0; i < terrainArray.Length; i++) { if (terrainArray[i].active && terrainArray[i].updateHeights) { UpdateTerrainHeights(terrainArray[i]); terrainArray[i].updateHeights = false; if (!init) { yield return(new WaitForEndOfFrame()); } } } // Wait for physics update when streaming if (!init) { yield return(new WaitForFixedUpdate()); } // Second stage updates terrain nature for (int i = 0; i < terrainArray.Length; i++) { if (terrainArray[i].active && terrainArray[i].updateNature) { UpdateTerrainNature(terrainArray[i]); terrainArray[i].updateNature = false; if (!init) { yield return(new WaitForEndOfFrame()); } } } // Get key for central player terrain int playerKey = TerrainHelper.MakeTerrainKey(MapPixelX, MapPixelY); // Third stage updates location if present // Vast majority of terrains will not have a location // Locations are not optimised as yet and are quite heavy on drawcalls for (int i = 0; i < terrainArray.Length; i++) { // Get key for this terrain int key = TerrainHelper.MakeTerrainKey(terrainArray[i].mapPixelX, terrainArray[i].mapPixelY); if (terrainArray[i].active && terrainArray[i].hasLocation) { // Create location if not present if (!locationDict.ContainsKey(key)) { // Create location object DFLocation location; GameObject locationObject = CreateLocationGameObject(i, out location); if (!locationObject) { continue; } // Add location object to dictionary locationDict.Add(key, locationObject); // Create location beacon // This is parented to location and shares its lifetime if (AddLocationBeacon) { const float beaconHeight = 900f; const float beaconOffset = (MapsFile.WorldMapTerrainDim * MeshReader.GlobalScale) / 2f; GameObject locationMarker = (GameObject)GameObject.Instantiate(Resources.Load <GameObject>("LocationBeacon")); locationMarker.hideFlags = HideFlags.HideAndDontSave; locationMarker.transform.parent = locationObject.transform; locationMarker.transform.localPosition = new Vector3(beaconOffset, beaconHeight, beaconOffset); } // Add one nature batch for entire location // This is parented to location and shares its lifetime GameObject natureBatchObject = new GameObject("NatureBatch"); natureBatchObject.hideFlags = HideFlags.HideAndDontSave; natureBatchObject.transform.parent = locationObject.transform; natureBatchObject.transform.localPosition = Vector3.zero; DaggerfallBillboardBatch natureBatch = natureBatchObject.AddComponent <DaggerfallBillboardBatch>(); int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue); natureBatch.SetMaterial(natureArchive); // RMB blocks are laid out in centre of terrain to align with ground int width = location.Exterior.ExteriorData.Width; int height = location.Exterior.ExteriorData.Height; float offsetX = ((8 * RMBLayout.RMBSide) - (width * RMBLayout.RMBSide)) / 2; float offsetZ = ((8 * RMBLayout.RMBSide) - (height * RMBLayout.RMBSide)) / 2; Vector3 origin = new Vector3(offsetX, 2.0f * MeshReader.GlobalScale, offsetZ); // Perform layout and yield after each block is placed DaggerfallLocation dfLocation = locationObject.GetComponent <DaggerfallLocation>(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Set origin for billboard batch add // This causes next additions to be offset by this position Vector3 blockOrigin = origin + new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide)); natureBatch.origin = blockOrigin; // Add block and yield string blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y)); GameObject go = RMBLayout.CreateGameObject(blockName, true, natureBatch); go.hideFlags = HideFlags.HideAndDontSave; go.transform.parent = locationObject.transform; go.transform.localPosition = blockOrigin; dfLocation.ApplyClimateSettings(); if (!init) { yield return(new WaitForEndOfFrame()); } } } // If this is the player terrain we may need to reposition player if (playerKey == key && repositionPlayer) { // Position to location and use start marker for large cities bool useStartMarker = (dfLocation.Summary.LocationType == DFRegion.LocationTypes.TownCity); PositionPlayerToLocation(MapPixelX, MapPixelY, dfLocation, origin, width, height, useStartMarker); repositionPlayer = false; } // Apply nature batch natureBatch.Apply(); } } else if (terrainArray[i].active) { if (playerKey == key && repositionPlayer) { PositionPlayerToTerrain(MapPixelX, MapPixelY, Vector3.zero); repositionPlayer = false; } } } // If this is an init we can use the load time to unload unused assets // Keeps memory usage much lower over time if (init) { Resources.UnloadUnusedAssets(); } // Finish by collecting stale data and setting neighbours CollectTerrains(); CollectLocations(); UpdateNeighbours(); }
/// <summary> /// Transition player through an exterior door into building interior. /// </summary> /// <param name="doorOwner">Parent transform owning door array.</param> /// <param name="door">Exterior door player clicked on.</param> public void TransitionInterior(Transform doorOwner, StaticDoor door) { // Ensure we have component references if (!ReferenceComponents()) { return; } // Get current climate ClimateBases climateBase = ClimateBases.Temperate; if (playerGPS) { climateBase = ClimateSwaps.FromAPIClimateBase(playerGPS.ClimateSettings.ClimateType); } // Layout interior // This needs to be done first so we know where the enter markers are GameObject newInterior = new GameObject(string.Format("DaggerfallInterior [Block={0}, Record={1}]", door.blockIndex, door.recordIndex)); newInterior.hideFlags = HideFlags.HideAndDontSave; interior = newInterior.AddComponent <DaggerfallInterior>(); interior.DoLayout(doorOwner, door, climateBase); // Position interior directly inside of exterior // This helps with finding closest enter/exit point relative to player position interior.transform.position = doorOwner.position + (Vector3)door.buildingMatrix.GetColumn(3); interior.transform.rotation = GameObjectHelper.QuaternionFromMatrix(door.buildingMatrix); // Position player above closest enter marker Vector3 marker; if (!interior.FindClosestEnterMarker(transform.position, out marker)) { // Could not find an enter marker, probably not a valid interior Destroy(newInterior); return; } // Assign new interior to parent if (InteriorParent != null) { newInterior.transform.parent = InteriorParent.transform; } // Disable exterior parent if (ExteriorParent != null) { ExteriorParent.SetActive(false); } // Enable interior parent if (InteriorParent != null) { InteriorParent.SetActive(true); } // Cache some information about this interior buildingType = interior.BuildingData.BuildingType; // Set player to marker position // Not sure how to set facing here as player transitions to a marker, not a door // Could always find closest door and use that transform.position = marker + Vector3.up * (controller.height * 0.6f); SetStanding(); // Player is now inside building isPlayerInside = true; }
/// <summary> /// Performs a fully standalone in-place location layout. /// This method is used only by editor layouts, not by streaming world. /// </summary> private void LayoutLocation(ref DFLocation location) { // Get city dimensions int width = location.Exterior.ExteriorData.Width; int height = location.Exterior.ExteriorData.Height; // Create billboard batch game objects for this location TextureAtlasBuilder miscBillboardAtlas = null; summary.NatureBillboardBatch = null; DaggerfallBillboardBatch lightsBillboardBatch = null; DaggerfallBillboardBatch animalsBillboardBatch = null; DaggerfallBillboardBatch miscBillboardBatch = null; if (dfUnity.Option_BatchBillboards) { miscBillboardAtlas = dfUnity.MaterialReader.MiscBillboardAtlas; int natureArchive = ClimateSwaps.GetNatureArchive(CurrentNatureSet, CurrentSeason); summary.NatureBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(natureArchive, transform); lightsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.LightsTextureArchive, transform); animalsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.AnimalsTextureArchive, transform); miscBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(miscBillboardAtlas.AtlasMaterial, transform); } // Import blocks for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (dfUnity.Option_BatchBillboards) { // Set block origin for billboard batches // This causes next additions to be offset by this position Vector3 blockOrigin = new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide)); summary.NatureBillboardBatch.BlockOrigin = blockOrigin; lightsBillboardBatch.BlockOrigin = blockOrigin; animalsBillboardBatch.BlockOrigin = blockOrigin; miscBillboardBatch.BlockOrigin = blockOrigin; } string blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y)); GameObject go = GameObjectHelper.CreateRMBBlockGameObject( blockName, dfUnity.Option_RMBGroundPlane, dfUnity.Option_CityBlockPrefab, summary.NatureBillboardBatch, lightsBillboardBatch, animalsBillboardBatch, miscBillboardAtlas, miscBillboardBatch, CurrentNatureSet, CurrentSeason); go.transform.parent = this.transform; go.transform.position = new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide)); } } // Apply batches if (summary.NatureBillboardBatch) { summary.NatureBillboardBatch.Apply(); } if (lightsBillboardBatch) { lightsBillboardBatch.Apply(); } if (animalsBillboardBatch) { animalsBillboardBatch.Apply(); } if (miscBillboardBatch) { miscBillboardBatch.Apply(); } // Enumerate start marker game objects EnumerateStartMarkers(); }
/// <summary> /// Performs a fully standalone in-place location layout. /// This method is used only by editor layouts, not by streaming world. /// </summary> private void LayoutLocation(ref DFLocation location) { #if SHOW_LAYOUT_TIMES // Start timing System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); long startTime = stopwatch.ElapsedMilliseconds; #endif // Get city dimensions int width = location.Exterior.ExteriorData.Width; int height = location.Exterior.ExteriorData.Height; // Create billboard batch game objects for this location //TextureAtlasBuilder miscBillboardAtlas = null; summary.NatureBillboardBatch = null; DaggerfallBillboardBatch lightsBillboardBatch = null; DaggerfallBillboardBatch animalsBillboardBatch = null; //DaggerfallBillboardBatch miscBillboardBatch = null; if (dfUnity.Option_BatchBillboards) { //miscBillboardAtlas = dfUnity.MaterialReader.MiscBillboardAtlas; int natureArchive = ClimateSwaps.GetNatureArchive(CurrentNatureSet, CurrentSeason); summary.NatureBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(natureArchive, transform); lightsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.LightsTextureArchive, transform); animalsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.AnimalsTextureArchive, transform); //miscBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(miscBillboardAtlas.AtlasMaterial, transform); } // Import blocks for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (dfUnity.Option_BatchBillboards) { // Set block origin for billboard batches // This causes next additions to be offset by this position Vector3 blockOrigin = new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide)); summary.NatureBillboardBatch.BlockOrigin = blockOrigin; lightsBillboardBatch.BlockOrigin = blockOrigin; animalsBillboardBatch.BlockOrigin = blockOrigin; //miscBillboardBatch.BlockOrigin = blockOrigin; } string blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y)); GameObject go = GameObjectHelper.CreateRMBBlockGameObject( blockName, x, y, dfUnity.Option_RMBGroundPlane, dfUnity.Option_CityBlockPrefab, summary.NatureBillboardBatch, lightsBillboardBatch, animalsBillboardBatch, null, //miscBillboardAtlas, null, //miscBillboardBatch, CurrentNatureSet, CurrentSeason); go.transform.parent = this.transform; go.transform.position = new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide)); } } // Apply batches if (summary.NatureBillboardBatch) { summary.NatureBillboardBatch.Apply(); } if (lightsBillboardBatch) { lightsBillboardBatch.Apply(); } if (animalsBillboardBatch) { animalsBillboardBatch.Apply(); } //if (miscBillboardBatch) miscBillboardBatch.Apply(); // Enumerate start marker game objects EnumerateStartMarkers(); #if SHOW_LAYOUT_TIMES // Show timer long totalTime = stopwatch.ElapsedMilliseconds - startTime; DaggerfallUnity.LogMessage(string.Format("Time to layout location: {0}ms", totalTime), true); #endif }
private IEnumerator UpdateLocation(int index, bool allowYield) { int key = TerrainHelper.MakeTerrainKey(terrainArray[index].mapPixelX, terrainArray[index].mapPixelY); int playerKey = TerrainHelper.MakeTerrainKey(MapPixelX, MapPixelY); bool isPlayerTerrain = (key == playerKey); if (terrainArray[index].active && terrainArray[index].hasLocation && terrainArray[index].updateLocation) { // Disable update flag terrainArray[index].updateLocation = false; // Create location object DFLocation location; GameObject locationObject = CreateLocationGameObject(index, out location); if (locationObject) { // Add location object to dictionary LocationDesc locationDesc = new LocationDesc(); locationDesc.locationObject = locationObject; locationDesc.mapPixelX = terrainArray[index].mapPixelX; locationDesc.mapPixelY = terrainArray[index].mapPixelY; locationList.Add(locationDesc); // Create billboard batch game objects for this location // Streaming world always batches for performance, regardless of options int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue); TextureAtlasBuilder miscBillboardAtlas = dfUnity.MaterialReader.MiscBillboardAtlas; DaggerfallBillboardBatch natureBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(natureArchive, locationObject.transform); DaggerfallBillboardBatch lightsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.LightsTextureArchive, locationObject.transform); DaggerfallBillboardBatch animalsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.AnimalsTextureArchive, locationObject.transform); DaggerfallBillboardBatch miscBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(miscBillboardAtlas.AtlasMaterial, locationObject.transform); // Set hide flags natureBillboardBatch.hideFlags = HideFlags.HideAndDontSave; lightsBillboardBatch.hideFlags = HideFlags.HideAndDontSave; animalsBillboardBatch.hideFlags = HideFlags.HideAndDontSave; miscBillboardBatch.hideFlags = HideFlags.HideAndDontSave; // RMB blocks are laid out in centre of terrain to align with ground int width = location.Exterior.ExteriorData.Width; int height = location.Exterior.ExteriorData.Height; float offsetX = ((8 * RMBLayout.RMBSide) - (width * RMBLayout.RMBSide)) / 2; float offsetZ = ((8 * RMBLayout.RMBSide) - (height * RMBLayout.RMBSide)) / 2; Vector3 origin = new Vector3(offsetX, 2.0f * MeshReader.GlobalScale, offsetZ); // Get location data DaggerfallLocation dfLocation = locationObject.GetComponent <DaggerfallLocation>(); // Perform layout and yield after each block is placed for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Set block origin for billboard batches // This causes next additions to be offset by this position Vector3 blockOrigin = origin + new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide)); natureBillboardBatch.BlockOrigin = blockOrigin; lightsBillboardBatch.BlockOrigin = blockOrigin; animalsBillboardBatch.BlockOrigin = blockOrigin; miscBillboardBatch.BlockOrigin = blockOrigin; // Add block and yield string blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y)); GameObject go = GameObjectHelper.CreateRMBBlockGameObject( blockName, false, dfUnity.Option_CityBlockPrefab, natureBillboardBatch, lightsBillboardBatch, animalsBillboardBatch, miscBillboardAtlas, miscBillboardBatch); go.hideFlags = HideFlags.HideAndDontSave; go.transform.parent = locationObject.transform; go.transform.localPosition = blockOrigin; dfLocation.ApplyClimateSettings(); if (allowYield) { yield return(new WaitForEndOfFrame()); } } } // If this is the player terrain we may need to reposition player if (isPlayerTerrain && repositionPlayer) { // Position to location and use start marker for large cities bool useStartMarker = (dfLocation.Summary.LocationType == DFRegion.LocationTypes.TownCity); PositionPlayerToLocation(MapPixelX, MapPixelY, dfLocation, origin, width, height, useStartMarker); repositionPlayer = false; } // Apply billboard batches natureBillboardBatch.Apply(); lightsBillboardBatch.Apply(); animalsBillboardBatch.Apply(); miscBillboardBatch.Apply(); } } else if (terrainArray[index].active) { if (playerKey == key && repositionPlayer) { PositionPlayerToTerrain(MapPixelX, MapPixelY, Vector3.zero); repositionPlayer = false; } } }
/// <summary> /// Transition player through an exterior door into building interior. /// </summary> /// <param name="doorOwner">Parent transform owning door array..</param> /// <param name="door">Exterior door player clicked on.</param> public void TransitionInterior(Transform doorOwner, StaticDoor door, bool doFade = false, bool start = true) { // Ensure we have component references if (!ReferenceComponents()) { return; } // Copy owner position to door // This ensures the door itself is all we need to reposition interior // Useful when loading a save and doorOwner is null (as outside world does not exist) if (doorOwner) { door.ownerPosition = doorOwner.position; door.ownerRotation = doorOwner.rotation; } if (!start) { // Update scene cache from serializable state for exterior->interior transition SaveLoadManager.CacheScene(world.SceneName); // Explicitly deregister all stateful objects since exterior isn't destroyed SaveLoadManager.DeregisterAllSerializableGameObjects(true); // Clear all stateful objects from world loose object tracking world.ClearStatefulLooseObjects(); } // Raise event RaiseOnPreTransitionEvent(TransitionType.ToBuildingInterior, door); // Ensure expired rooms are removed GameManager.Instance.PlayerEntity.RemoveExpiredRentedRooms(); // Get climate ClimateBases climateBase = ClimateBases.Temperate; if (OverrideLocation) { climateBase = OverrideLocation.Summary.Climate; } else if (playerGPS) { climateBase = ClimateSwaps.FromAPIClimateBase(playerGPS.ClimateSettings.ClimateType); } // Layout interior // This needs to be done first so we know where the enter markers are GameObject newInterior = new GameObject(DaggerfallInterior.GetSceneName(playerGPS.CurrentLocation, door)); newInterior.hideFlags = defaultHideFlags; interior = newInterior.AddComponent <DaggerfallInterior>(); // Try to layout interior // If we fail for any reason, use that old chestnut "this house has nothing of value" try { interior.DoLayout(doorOwner, door, climateBase, buildingDiscoveryData); } catch { DaggerfallUI.AddHUDText(HardStrings.thisHouseHasNothingOfValue); Destroy(newInterior); return; } // Position interior directly inside of exterior // This helps with finding closest enter/exit point relative to player position interior.transform.position = door.ownerPosition + (Vector3)door.buildingMatrix.GetColumn(3); interior.transform.rotation = GameObjectHelper.QuaternionFromMatrix(door.buildingMatrix); // Position player above closest enter marker Vector3 marker; if (!interior.FindClosestEnterMarker(transform.position, out marker)) { // Could not find an enter marker, probably not a valid interior Destroy(newInterior); return; } // Enumerate all exterior doors belonging to this building DaggerfallStaticDoors exteriorStaticDoors = interior.ExteriorDoors; if (exteriorStaticDoors && doorOwner) { List <StaticDoor> buildingDoors = new List <StaticDoor>(); for (int i = 0; i < exteriorStaticDoors.Doors.Length; i++) { if (exteriorStaticDoors.Doors[i].recordIndex == door.recordIndex) { StaticDoor newDoor = exteriorStaticDoors.Doors[i]; newDoor.ownerPosition = doorOwner.position; newDoor.ownerRotation = doorOwner.rotation; buildingDoors.Add(newDoor); } } SetExteriorDoors(buildingDoors.ToArray()); } // Assign new interior to parent if (InteriorParent != null) { newInterior.transform.parent = InteriorParent.transform; } // Cache some information about this interior buildingType = interior.BuildingData.BuildingType; factionID = interior.BuildingData.FactionId; // Set player to marker position // TODO: Find closest door for player facing transform.position = marker + Vector3.up * (controller.height * 0.6f); SetStanding(); EnableInteriorParent(); // Add quest resources GameObjectHelper.AddQuestResourceObjects(SiteTypes.Building, interior.transform, interior.EntryDoor.buildingKey); // Update serializable state from scene cache for exterior->interior transition (unless new/load game) if (!start) { SaveLoadManager.RestoreCachedScene(interior.name); } // Raise event RaiseOnTransitionInteriorEvent(door, interior); // Fade in from black if (doFade) { DaggerfallUI.Instance.FadeHUDFromBlack(); } }
private void LoadVertices(ref ModelData model, float scale) { const int BuildingDoors = 74; const int DungeonEnterDoors = 56; const int DungeonRuinEnterDoors = 331; const int DungeonExitDoors = 95; // Allocate arrays model.Vertices = new Vector3[model.DFMesh.TotalVertices]; model.Normals = new Vector3[model.DFMesh.TotalVertices]; model.UVs = new Vector2[model.DFMesh.TotalVertices]; // Door list List <ModelDoor> modelDoors = new List <ModelDoor>(); // Loop through all submeshes int vertexCount = 0; foreach (DFMesh.DFSubMesh dfSubMesh in model.DFMesh.SubMeshes) { // Get cached material data CachedMaterial cm; dfUnity.MaterialReader.GetCachedMaterial(dfSubMesh.TextureArchive, dfSubMesh.TextureRecord, 0, out cm); Vector2 sz = cm.recordSizes[0]; // Get base climate archive for door check // All base door textures are < 100, except dungeon ruins doors int doorArchive = dfSubMesh.TextureArchive; if (doorArchive > 100 && doorArchive != DungeonRuinEnterDoors) { // Reduce to base texture set // This shifts all building climate doors to the same index ClimateTextureInfo ci = ClimateSwaps.GetClimateTextureInfo(dfSubMesh.TextureArchive); doorArchive = (int)ci.textureSet; } // Check if this is a door archive bool doorFound = false; DoorTypes doorType = DoorTypes.None; switch (doorArchive) { case BuildingDoors: doorFound = true; doorType = DoorTypes.Building; break; case DungeonEnterDoors: doorFound = true; doorType = DoorTypes.DungeonEntrance; break; case DungeonRuinEnterDoors: if (dfSubMesh.TextureRecord > 0) // Dungeon ruins index 0 is just a stone texture { doorFound = true; doorType = DoorTypes.DungeonEntrance; } break; case DungeonExitDoors: doorFound = true; doorType = DoorTypes.DungeonExit; break; } // Loop through all planes in this submesh int doorCount = 0; foreach (DFMesh.DFPlane dfPlane in dfSubMesh.Planes) { // If this is a door then each plane is a single door if (doorFound) { // Set door verts DFMesh.DFPoint p0 = dfPlane.Points[0]; DFMesh.DFPoint p1 = dfPlane.Points[1]; DFMesh.DFPoint p2 = dfPlane.Points[2]; ModelDoor modelDoor = new ModelDoor() { Index = doorCount++, Type = doorType, Vert0 = new Vector3(p0.X, -p0.Y, p0.Z) * scale, Vert1 = new Vector3(p1.X, -p1.Y, p1.Z) * scale, Vert2 = new Vector3(p2.X, -p2.Y, p2.Z) * scale, }; // Set door normal Vector3 u = modelDoor.Vert0 - modelDoor.Vert2; Vector3 v = modelDoor.Vert0 - modelDoor.Vert1; modelDoor.Normal = Vector3.Normalize(Vector3.Cross(u, v)); // Add door to list modelDoors.Add(modelDoor); } // Copy each point in this plane to vertex buffer foreach (DFMesh.DFPoint dfPoint in dfPlane.Points) { // Position and normal Vector3 position = new Vector3(dfPoint.X, -dfPoint.Y, dfPoint.Z) * scale; Vector3 normal = new Vector3(dfPoint.NX, -dfPoint.NY, dfPoint.NZ); // Store vertex data model.Vertices[vertexCount] = position; model.Normals[vertexCount] = Vector3.Normalize(normal); model.UVs[vertexCount] = new Vector2((dfPoint.U / sz.x), -(dfPoint.V / sz.y)); // Inrement count vertexCount++; } } } // Assign found doors model.Doors = modelDoors.ToArray(); }
/// <summary> /// Gets Unity Material from Daggerfall texture with more options. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="record">Record index.</param> /// <param name="frame">Frame index.</param> /// <param name="rectOut">Receives UV rect for texture inside border.</param> /// <param name="border">Number of pixels internal border around each texture.</param> /// <param name="dilate">Blend texture into surrounding empty pixels.</param> /// <param name="shader">Shader for material. If null, DefaultShaderName will be applied.</param> /// <returns>Material or null.</returns> public Material GetMaterial( int archive, int record, int frame, int alphaIndex, out Rect rectOut, int border = 0, bool dilate = false, Shader shader = null) { // Ready check if (!IsReady) { rectOut = new Rect(); return(null); } // HACK: Override shader for unlit textures // TODO: Find a better way to do this if (archive == 356 && (record == 0 || record == 2 || record == 3) || archive == 87 && record == 0) { // Only override if not specified if (shader == null) { shader = Shader.Find(dfUnity.MaterialReader.DefaultUnlitTextureShaderName); } } int key = MakeTextureKey((short)archive, (byte)record, (byte)frame); if (materialDict.ContainsKey(key)) { CachedMaterial cm = materialDict[key]; if (cm.filterMode == MainFilterMode) { // Properties are the same rectOut = cm.singleRect; return(cm.material); } else { // Properties don't match, remove material and reload materialDict.Remove(key); } } if (shader == null) { shader = Shader.Find(DefaultShaderName); } Material material = new Material(shader); material.name = FormatName(archive, record); Texture2D texture = textureReader.GetTexture2D(archive, record, frame, alphaIndex, out rectOut, border, dilate); material.mainTexture = texture; material.mainTexture.filterMode = MainFilterMode; DFSize size = textureReader.TextureFile.GetSize(record); DFSize scale = textureReader.TextureFile.GetScale(record); DFSize offset = textureReader.TextureFile.GetOffset(record); Vector2[] recordSizes = new Vector2[1] { new Vector2(size.Width, size.Height) }; Vector2[] recordScales = new Vector2[1] { new Vector2(scale.Width, scale.Height) }; Vector2[] recordOffsets = new Vector2[1] { new Vector2(offset.Width, offset.Height) }; CachedMaterial newcm = new CachedMaterial() { key = key, keyGroup = 0, singleRect = rectOut, material = material, filterMode = MainFilterMode, isWindow = ClimateSwaps.IsExteriorWindow(archive, record), recordSizes = recordSizes, recordScales = recordScales, recordOffsets = recordOffsets, recordFrameCount = textureReader.TextureFile.GetFrameCount(record), }; materialDict.Add(key, newcm); return(material); }
/// <summary> /// Transition player through an exterior door into building interior. /// </summary> /// <param name="doorOwner">Parent transform owning door array..</param> /// <param name="door">Exterior door player clicked on.</param> public void TransitionInterior(Transform doorOwner, StaticDoor door, bool doFade = false) { // Ensure we have component references if (!ReferenceComponents()) { return; } // Copy owner position to door // This ensures the door itself is all we need to reposition interior // Useful when loading a save and doorOwner is null (as outside world does not exist) if (doorOwner) { door.ownerPosition = doorOwner.position; door.ownerRotation = doorOwner.rotation; } // Raise event RaiseOnPreTransitionEvent(TransitionType.ToBuildingInterior, door); // Get climate ClimateBases climateBase = ClimateBases.Temperate; if (OverrideLocation) { climateBase = OverrideLocation.Summary.Climate; } else if (playerGPS) { climateBase = ClimateSwaps.FromAPIClimateBase(playerGPS.ClimateSettings.ClimateType); } // Layout interior // This needs to be done first so we know where the enter markers are GameObject newInterior = new GameObject(string.Format("DaggerfallInterior [Block={0}, Record={1}]", door.blockIndex, door.recordIndex)); newInterior.hideFlags = defaultHideFlags; interior = newInterior.AddComponent <DaggerfallInterior>(); // Try to layout interior // If we fail for any reason, use that old chestnut "this house has nothing of value" try { interior.DoLayout(doorOwner, door, climateBase); } catch { DaggerfallUI.AddHUDText(UserInterfaceWindows.HardStrings.thisHouseHasNothingOfValue); Destroy(newInterior); return; } // Position interior directly inside of exterior // This helps with finding closest enter/exit point relative to player position interior.transform.position = door.ownerPosition + (Vector3)door.buildingMatrix.GetColumn(3); interior.transform.rotation = GameObjectHelper.QuaternionFromMatrix(door.buildingMatrix); // Position player above closest enter marker Vector3 marker; if (!interior.FindClosestEnterMarker(transform.position, out marker)) { // Could not find an enter marker, probably not a valid interior Destroy(newInterior); return; } // Enumerate all exterior doors belonging to this building DaggerfallStaticDoors exteriorStaticDoors = interior.ExteriorDoors; if (exteriorStaticDoors && doorOwner) { List <StaticDoor> buildingDoors = new List <StaticDoor>(); for (int i = 0; i < exteriorStaticDoors.Doors.Length; i++) { if (exteriorStaticDoors.Doors[i].recordIndex == door.recordIndex) { StaticDoor newDoor = exteriorStaticDoors.Doors[i]; newDoor.ownerPosition = doorOwner.position; newDoor.ownerRotation = doorOwner.rotation; buildingDoors.Add(newDoor); } } SetExteriorDoors(buildingDoors.ToArray()); } // Assign new interior to parent if (InteriorParent != null) { newInterior.transform.parent = InteriorParent.transform; } // Cache some information about this interior buildingType = interior.BuildingData.BuildingType; // Set player to marker position // TODO: Find closest door for player facing transform.position = marker + Vector3.up * (controller.height * 0.6f); SetStanding(); EnableInteriorParent(); // Raise event RaiseOnTransitionInteriorEvent(door, interior); // Fade in from black if (doFade) { DaggerfallUI.Instance.FadeHUDFromBlack(); } }
public void ApplyClimateSettings() { // Do nothing if not ready if (!ReadyCheck()) { return; } // Process all DaggerfallMesh child components DaggerfallMesh[] meshArray = GetComponentsInChildren <DaggerfallMesh>(); foreach (var dm in meshArray) { switch (ClimateUse) { case LocationClimateUse.UseLocation: dm.SetClimate(dfUnity, Summary.Climate, CurrentSeason, WindowTextureStyle); break; case LocationClimateUse.Custom: dm.SetClimate(dfUnity, CurrentClimate, CurrentSeason, WindowTextureStyle); break; case LocationClimateUse.Disabled: dm.DisableClimate(dfUnity); break; } } // Process all DaggerfallGroundMesh child components DaggerfallGroundPlane[] groundMeshArray = GetComponentsInChildren <DaggerfallGroundPlane>(); foreach (var gm in groundMeshArray) { switch (ClimateUse) { case LocationClimateUse.UseLocation: gm.SetClimate(dfUnity, Summary.Climate, CurrentSeason); break; case LocationClimateUse.Custom: gm.SetClimate(dfUnity, CurrentClimate, CurrentSeason); break; case LocationClimateUse.Disabled: gm.SetClimate(dfUnity, ClimateBases.Temperate, ClimateSeason.Summer); break; } } // Determine correct nature archive int natureArchive; switch (ClimateUse) { case LocationClimateUse.UseLocation: natureArchive = ClimateSwaps.GetNatureArchive(summary.Nature, CurrentSeason); break; case LocationClimateUse.Custom: natureArchive = ClimateSwaps.GetNatureArchive(CurrentNatureSet, CurrentSeason); break; case LocationClimateUse.Disabled: default: natureArchive = ClimateSwaps.GetNatureArchive(ClimateNatureSets.TemperateWoodland, ClimateSeason.Summer); break; } // Process all DaggerfallBillboard child components DaggerfallBillboard[] billboardArray = GetComponentsInChildren <DaggerfallBillboard>(); foreach (var db in billboardArray) { if (db.Summary.FlatType == FlatTypes.Nature) { // Apply recalculated nature archive db.SetMaterial(dfUnity, natureArchive, db.Summary.Record, 0, db.Summary.InDungeon); } else { // All other flats are just reapplied to handle any other changes db.SetMaterial(dfUnity, db.Summary.Archive, db.Summary.Record, 0, db.Summary.InDungeon); } } }