Exemple #1
0
        /// <summary>
        /// Set block data corresponding to interior.
        /// </summary>
        private void AssignBlockData(StaticDoor door)
        {
            // Get block data
            DFLocation location = GameManager.Instance.PlayerGPS.CurrentLocation;

            DFBlock[] blocks;
            RMBLayout.GetLocationBuildingData(location, out blocks);
            bool foundBlock = false;

            for (int index = 0; index < blocks.Length && !foundBlock; ++index)
            {
                if (blocks[index].Index == door.blockIndex)
                {
                    this.blockData = blocks[index];
                    foundBlock     = true;
                }
            }

            if (!foundBlock || this.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);
            }
        }
        /// <summary>
        /// Undiscover the specified building in current location.
        /// used to undiscover residences when they are a quest resource (named residence) when "add dialog" is done for this quest resource or on quest startup
        /// otherwise previously discovered residences will automatically show up on the automap when used in a quest
        /// </summary>
        /// <param name="buildingKey">Building key of building to be undiscovered</param>
        /// <param name="onlyIfResidence">gets undiscovered only if buildingType is residence</param>
        public void UndiscoverBuilding(int buildingKey, bool onlyIfResidence = false)
        {
            // Must have a location loaded
            if (!CurrentLocation.Loaded)
            {
                return;
            }

            // Get building information
            DiscoveredBuilding db;

            if (!GetBuildingDiscoveryData(buildingKey, out db))
            {
                return;
            }

            // Get location discovery
            int mapPixelID        = MapsFile.GetMapPixelIDFromLongitudeLatitude((int)CurrentLocation.MapTableData.Longitude, CurrentLocation.MapTableData.Latitude);
            DiscoveredLocation dl = new DiscoveredLocation();

            if (discoveredLocations.ContainsKey(mapPixelID))
            {
                dl = discoveredLocations[mapPixelID];
            }

            if (onlyIfResidence && !RMBLayout.IsResidence(db.buildingType))
            {
                return;
            }

            if (dl.discoveredBuildings != null && dl.discoveredBuildings.ContainsKey(db.buildingKey))
            {
                dl.discoveredBuildings.Remove(db.buildingKey);
            }
        }
Exemple #3
0
        /// <summary>
        /// Sets a variant for a new location.
        /// </summary>
        /// <param name="regionIndex">Region index</param>
        /// <param name="locationName">Location name (as index is assigned at runtime)</param>
        /// <param name="variant">Variant name</param>
        /// <returns>True if overwriting an existing variant set for this location</returns>
        public static bool SetNewLocationVariant(int regionIndex, string locationName, string variant)
        {
            int locationIndex = WorldDataReplacement.GetNewDFLocationIndex(regionIndex, locationName);

            if (locationIndex >= 0)
            {
                int  locationKey = WorldDataReplacement.MakeLocationKey(regionIndex, locationIndex);
                bool overwrite   = !locationVariants.ContainsKey(locationKey);
                if (variant == NoVariant)
                {
                    locationVariants.Remove(locationKey);
                }
                else
                {
                    locationVariants[locationKey] = variant;
                }

                if (!newLocationVariants.Contains(locationKey))
                {
                    newLocationVariants.Add(locationKey);
                }

                Debug.LogFormat("Set variant \"{0}\" for the new location \"{1}\" in region {2}", variant, locationName, regionIndex);
                RMBLayout.ClearLocationCache();
                return(overwrite);
            }
            DaggerfallUnity.LogMessage("Failed to set a new location variant.", true);
            return(false);
        }
 /// <summary>
 /// Sets block information during scene layout.
 /// </summary>
 /// <param name="blockData">DFBlock data.</param>
 public void SetBlockData(DFBlock blockData)
 {
     // Create block summary
     Name          = blockData.Name;
     BuildingCount = blockData.RmbBlock.SubRecords.Length;
     Buildings     = RMBLayout.GetBuildingData(blockData);
 }
Exemple #5
0
        /// <summary>
        /// Add interior people flats.
        /// </summary>
        private void AddPeople(PlayerGPS.DiscoveredBuilding buildingData)
        {
            GameObject node = new GameObject("People Flats");

            node.transform.parent = this.transform;
            bool isMemberOfBuildingGuild = GameManager.Instance.GuildManager.GetGuild(buildingData.factionID).IsMember();

            // Add block flats
            foreach (DFBlock.RmbBlockPeopleRecord obj in recordData.Interior.BlockPeopleRecords)
            {
                // Calculate position
                Vector3 billboardPosition = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale;

                // Import 3D character instead of billboard
                if (MeshReplacement.ImportCustomFlatGameobject(obj.TextureArchive, obj.TextureRecord, billboardPosition, node.transform) != null)
                {
                    continue;
                }

                // Spawn billboard gameobject
                GameObject go = GameObjectHelper.CreateDaggerfallBillboardGameObject(obj.TextureArchive, obj.TextureRecord, node.transform);

                // Set position
                DaggerfallBillboard dfBillboard = go.GetComponent <DaggerfallBillboard>();
                go.transform.position  = billboardPosition;
                go.transform.position += new Vector3(0, dfBillboard.Summary.Size.y / 2, 0);

                // Add RMB data to billboard
                dfBillboard.SetRMBPeopleData(obj);

                // Add StaticNPC behaviour
                StaticNPC npc = go.AddComponent <StaticNPC>();
                npc.SetLayoutData(obj, entryDoor.buildingKey);

                // Disable people if shop or building is closed
                DFLocation.BuildingTypes buildingType = buildingData.buildingType;
                if ((RMBLayout.IsShop(buildingType) && !GameManager.Instance.PlayerEnterExit.IsPlayerInsideOpenShop) ||
                    (buildingType <= DFLocation.BuildingTypes.Palace && !RMBLayout.IsShop(buildingType) && !PlayerActivate.IsBuildingOpen(buildingType)))
                {
                    go.SetActive(false);
                }
                // Disable people if player owns this house
                else if (DaggerfallBankManager.IsHouseOwned(buildingData.buildingKey))
                {
                    go.SetActive(false);
                }
                // Disable people if this is TG/DB house and player is not a member
                else if (buildingData.buildingType == DFLocation.BuildingTypes.House2 && buildingData.factionID != 0 && !isMemberOfBuildingGuild)
                {
                    go.SetActive(false);
                }
                // Disable people if they are TG spymaster, but not in a legit TG house (TODO: spot any other instances for TG/DB)
                else if (buildingData.buildingType == DFLocation.BuildingTypes.House2 && buildingData.factionID == 0 &&
                         npc.Data.factionID == (int)GuildNpcServices.TG_Spymaster)
                {
                    go.SetActive(false);
                }
            }
        }
Exemple #6
0
        /// <summary>
        /// Add interior people flats.
        /// </summary>
        private void AddPeople(PlayerGPS.DiscoveredBuilding buildingData)
        {
            GameObject node = new GameObject(peopleFlats);

            node.transform.parent = this.transform;
            IGuild guild = GameManager.Instance.GuildManager.GetGuild(buildingData.factionID);
            bool   isMemberOfBuildingGuild = guild.IsMember();

            // Add block flats
            foreach (DFBlock.RmbBlockPeopleRecord obj in recordData.Interior.BlockPeopleRecords)
            {
                // Calculate position
                Vector3 billboardPosition = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale;

                // Make person gameobject
                GameObject go = MeshReplacement.ImportCustomFlatGameobject(obj.TextureArchive, obj.TextureRecord, billboardPosition, node.transform);
                if (!go)
                {
                    // Spawn billboard gameobject
                    go = GameObjectHelper.CreateDaggerfallBillboardGameObject(obj.TextureArchive, obj.TextureRecord, node.transform);

                    // Set position
                    DaggerfallBillboard dfBillboard = go.GetComponent <DaggerfallBillboard>();
                    go.transform.position  = billboardPosition;
                    go.transform.position += new Vector3(0, dfBillboard.Summary.Size.y / 2, 0);

                    // Add RMB data to billboard
                    dfBillboard.SetRMBPeopleData(obj);
                }

                // Add StaticNPC behaviour
                StaticNPC npc = go.AddComponent <StaticNPC>();
                npc.SetLayoutData(obj, entryDoor.buildingKey);

                // Disable people if shop or building is closed
                DFLocation.BuildingTypes buildingType = buildingData.buildingType;
                if ((RMBLayout.IsShop(buildingType) && !GameManager.Instance.PlayerEnterExit.IsPlayerInsideOpenShop) ||
                    (buildingType <= DFLocation.BuildingTypes.Palace && !RMBLayout.IsShop(buildingType) &&
                     !(PlayerActivate.IsBuildingOpen(buildingType) || buildingType == DFLocation.BuildingTypes.GuildHall && guild.HallAccessAnytime())))
                {
                    go.SetActive(false);
                }
                // Disable people if player owns this house
                else if (DaggerfallBankManager.IsHouseOwned(buildingData.buildingKey))
                {
                    go.SetActive(false);
                }
                // Disable people if this is TG/DB house and player is not a member
                else if (buildingData.buildingType == DFLocation.BuildingTypes.House2 && buildingData.factionID != 0 && !isMemberOfBuildingGuild)
                {
                    go.SetActive(false);
                }
            }
        }
Exemple #7
0
        /// <summary>
        /// Gets building information from current location.
        /// Does not change discovery state for building.
        /// </summary>
        /// <param name="buildingKey">Key of building to query.</param>
        bool GetBuildingDiscoveryData(int buildingKey, out DiscoveredBuilding buildingDiscoveryData)
        {
            buildingDiscoveryData = new DiscoveredBuilding();

            // Get building directory for location
            BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory();

            if (!buildingDirectory)
            {
                return(false);
            }

            // Get detailed building data from directory
            BuildingSummary buildingSummary;

            if (!buildingDirectory.GetBuildingSummary(buildingKey, out buildingSummary))
            {
                int layoutX, layoutY, recordIndex;
                BuildingDirectory.ReverseBuildingKey(buildingKey, out layoutX, out layoutY, out recordIndex);
                Debug.LogFormat("Unable to find expected building key {0} in {1}.{2}", buildingKey, buildingDirectory.LocationData.RegionName, buildingDirectory.LocationData.Name);
                Debug.LogFormat("LayoutX={0}, LayoutY={1}, RecordIndex={2}", layoutX, layoutY, recordIndex);
                return(false);
            }

            // Resolve name by building type
            string buildingName;

            if (RMBLayout.IsResidence(buildingSummary.BuildingType))
            {
                // Residence
                // TODO: Link to quest system active sites
                buildingName = HardStrings.residence;
            }
            else
            {
                // Fixed building name
                buildingName = BuildingNames.GetName(
                    buildingSummary.NameSeed,
                    buildingSummary.BuildingType,
                    buildingSummary.FactionId,
                    buildingDirectory.LocationData.Name,
                    buildingDirectory.LocationData.RegionName);
            }

            // Add to data
            buildingDiscoveryData.buildingKey  = buildingKey;
            buildingDiscoveryData.displayName  = buildingName;
            buildingDiscoveryData.factionID    = buildingSummary.FactionId;
            buildingDiscoveryData.quality      = buildingSummary.Quality;
            buildingDiscoveryData.buildingType = buildingSummary.BuildingType;

            return(true);
        }
Exemple #8
0
        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);
                    }
                }
            }
        }
        private static void ShopShelfBurglar(RaycastHit hit)
        {
            PlayerGPS.DiscoveredBuilding buildingData = GameManager.Instance.PlayerEnterExit.BuildingDiscoveryData;
            if (RMBLayout.IsShop(buildingData.buildingType) && !PlayerActivate.IsBuildingOpen(buildingData.buildingType))
            {
                int stealthValue = playerEntity.Skills.GetLiveSkillValue(DFCareer.Skills.Stealth);
                stealthValue -= buildingData.quality * 2;

                if (Dice100.FailedRoll(StealthCalc(stealthValue, false)))
                {
                    burglaryCounter += Mathf.Clamp(UnityEngine.Random.Range(100, 200) - playerEntity.Stats.LiveLuck, 10, 100);
                }
            }
        }
Exemple #10
0
        private void AddFurnitureAction(DFBlock.RmbBlock3dObjectRecord obj, GameObject go, PlayerGPS.DiscoveredBuilding buildingData)
        {
            // Create unique LoadID for save system, using 9 lsb and the sign bit from each coord pos int
            ulong loadID = ((ulong)buildingData.buildingKey) << 30 |
                           (uint)(obj.XPos << 1 & posMask) << 20 |
                           (uint)(obj.YPos << 1 & posMask) << 10 |
                           (uint)(obj.ZPos << 1 & posMask);

            DFLocation.BuildingTypes buildingType = buildingData.buildingType;

            // Handle shelves:
            if (shopShelvesObjectGroupIndices.Contains(obj.ModelIdNum - containerObjectGroupOffset))
            {
                if (RMBLayout.IsShop(buildingType))
                {
                    // Shop shelves, so add a DaggerfallLoot component
                    DaggerfallLoot loot = go.AddComponent <DaggerfallLoot>();
                    if (loot)
                    {
                        // Set as shelves, assign load id and create serialization object
                        loot.ContainerType  = LootContainerTypes.ShopShelves;
                        loot.ContainerImage = InventoryContainerImages.Shelves;
                        loot.LoadID         = loadID;
                        if (SaveLoadManager.Instance != null)
                        {
                            go.AddComponent <SerializableLootContainer>();
                        }
                    }
                }
                else if (buildingType == DFLocation.BuildingTypes.Library ||
                         buildingType == DFLocation.BuildingTypes.GuildHall ||
                         buildingType == DFLocation.BuildingTypes.Temple)
                {
                    // Bookshelves, add DaggerfallBookshelf component
                    go.AddComponent <DaggerfallBookshelf>();
                }
                else if (DaggerfallBankManager.IsHouseOwned(buildingData.buildingKey))
                {   // Player owned house, everything is a house container
                    MakeHouseContainer(obj, go, loadID);
                }
            }
            // Handle generic furniture as (private) house containers:
            // (e.g. shelves, boxes, wardrobes, drawers etc)
            else if (obj.ModelIdNum / 100 == houseContainerObjectGroup ||
                     houseContainerObjectGroupIndices.Contains(obj.ModelIdNum - containerObjectGroupOffset))
            {
                MakeHouseContainer(obj, go, loadID);
            }
        }
        // Output building info to HUD
        private void PresentBuildingInfo(StaticBuilding building)
        {
            // Get building directory for location
            BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory();

            if (!buildingDirectory)
            {
                return;
            }

            // Get detailed building data from directory
            BuildingSummary buildingSummary;

            if (!buildingDirectory.GetBuildingSummary(building.buildingKey, out buildingSummary))
            {
                int layoutX, layoutY, recordIndex;
                BuildingDirectory.ReverseBuildingKey(building.buildingKey, out layoutX, out layoutY, out recordIndex);
                Debug.LogFormat("Unable to find expected building key {0} in {1}.{2}", building.buildingKey, buildingDirectory.LocationData.RegionName, buildingDirectory.LocationData.Name);
                Debug.LogFormat("LayoutX={0}, LayoutY={1}, RecordIndex={2}", layoutX, layoutY, recordIndex);
                throw new Exception("Error finding building key in directory.");
            }

            // Resolve name by building type
            string buildingName;

            if (RMBLayout.IsResidence(buildingSummary.BuildingType))
            {
                // Residence
                // TODO: Link to quest system active sites
                buildingName = HardStrings.residence;
            }
            else
            {
                // Fixed building name
                buildingName = BuildingNames.GetName(
                    buildingSummary.NameSeed,
                    buildingSummary.BuildingType,
                    buildingSummary.FactionId,
                    buildingDirectory.LocationData.Name,
                    buildingDirectory.LocationData.RegionName);
            }

            // Output building name to HUD
            DaggerfallUI.AddHUDText(buildingName);
        }
Exemple #12
0
        /// <summary>
        /// Sets a variant for a location.
        /// </summary>
        /// <param name="regionIndex">Region index</param>
        /// <param name="locationIndex">Location index</param>
        /// <param name="variant">Variant name</param>
        /// <returns>True if overwriting an existing variant set for this location</returns>
        public static bool SetLocationVariant(int regionIndex, int locationIndex, string variant)
        {
            int  locationKey = WorldDataReplacement.MakeLocationKey(regionIndex, locationIndex);
            bool overwrite   = !locationVariants.ContainsKey(locationKey);

            if (variant == NoVariant)
            {
                locationVariants.Remove(locationKey);
            }
            else
            {
                locationVariants[locationKey] = variant;
            }

            Debug.LogFormat("Set variant \"{0}\" for the location index \"{1}\" in region {2}", variant, locationIndex, regionIndex);
            RMBLayout.ClearLocationCache();
            return(overwrite);
        }
Exemple #13
0
        /// <summary>
        /// Sets a variant for a block.
        /// </summary>
        /// <param name="blockName">Block name</param>
        /// <param name="variant">Variant name</param>
        /// <param name="locationKey">Location key if the variant is only for a specific location</param>
        /// <returns>True if overwriting an existing variant set for this location</returns>
        public static bool SetBlockVariant(string blockName, string variant, int locationKey = AnyLocationKey)
        {
            VariantBlockKey blockKey  = new VariantBlockKey(locationKey, blockName);
            bool            overwrite = !blockVariants.ContainsKey(blockKey);

            if (variant == NoVariant)
            {
                blockVariants.Remove(blockKey);
            }
            else
            {
                blockVariants[blockKey] = variant;
            }

            Debug.LogFormat("Set variant \"{0}\" for the block {1} at locationKey {2}", variant, blockName, locationKey);
            RMBLayout.ClearLocationCache();
            return(overwrite);
        }
Exemple #14
0
        /// <summary>
        /// Sets a variant for a building. (block record)
        /// </summary>
        /// <param name="blockName">Block name</param>
        /// <param name="variant">Variant name</param>
        /// <param name="locationKey">Location key if the variant is only for a specific location</param>
        /// <returns>True if overwriting an existing variant set for this location</returns>
        public static bool SetBuildingVariant(string blockName, int recordIndex, string variant, int locationKey = AnyLocationKey)
        {
            VariantBuildingKey buildingKey = new VariantBuildingKey(locationKey, blockName, recordIndex);
            bool overwrite = !buildingVariants.ContainsKey(buildingKey);

            if (variant == NoVariant)
            {
                buildingVariants.Remove(buildingKey);
            }
            else
            {
                buildingVariants[buildingKey] = variant;
            }

            Debug.LogFormat("Set variant \"{0}\" for building {2} of {1} at locationKey {3}", variant, blockName, recordIndex, locationKey);
            RMBLayout.ClearLocationCache();
            return(overwrite);
        }
Exemple #15
0
        /// <summary>
        /// Update NPC presence for shops and guilds after resting/idling.
        /// </summary>
        public void UpdateNpcPresence()
        {
            PlayerEnterExit playerEnterExit = GameManager.Instance.PlayerEnterExit;

            DFLocation.BuildingTypes buildingType = playerEnterExit.BuildingType;
            if ((RMBLayout.IsShop(buildingType) && !playerEnterExit.IsPlayerInsideOpenShop) ||
                (!RMBLayout.IsShop(buildingType) && buildingType <= DFLocation.BuildingTypes.Palace && buildingType != DFLocation.BuildingTypes.HouseForSale))
            {
                Transform npcTransforms = transform.Find(peopleFlats);
                if (PlayerActivate.IsBuildingOpen(buildingType))
                {
                    foreach (Transform npcTransform in npcTransforms)
                    {
                        npcTransform.gameObject.SetActive(true);
                    }
                    Debug.Log("Updated npcs to be present.");
                }
            }
        }
        private void LayoutLocation(ref DFLocation location)
        {
            // Get city dimensions
            int width  = location.Exterior.ExteriorData.Width;
            int height = location.Exterior.ExteriorData.Height;

            // Import blocks
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    string     blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y));
                    GameObject go        = RMBLayout.CreateGameObject(blockName);
                    go.transform.parent   = this.transform;
                    go.transform.position = new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide));
                }
            }

            // Enumerate start marker game objects
            EnumerateStartMarkers();
        }
Exemple #17
0
        /// <summary>
        /// Setup building directory from specified location.
        /// </summary>
        /// <param name="location">Source location data.</param>
        public void SetLocation(DFLocation location)
        {
            // Clear existing buildings
            buildingDict.Clear();

            // Get block data pre-populated with map building data.
            DFBlock[] blocks;
            RMBLayout.GetLocationBuildingData(location, out blocks);

            // Construct building directory
            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 all buildings for this block
                    // Some blocks have zero buildings
                    int index = y * width + x;
                    BuildingSummary[] buildings = RMBLayout.GetBuildingData(blocks[index], x, y);
                    if (buildings == null || buildings.Length == 0)
                    {
                        continue;
                    }

                    // Add all buildings to directory
                    for (int i = 0; i < buildings.Length; i++)
                    {
                        buildingDict.Add(buildings[i].buildingKey, buildings[i]);
                    }
                }
            }

            // Store location info
            locationId   = location.Exterior.ExteriorData.LocationId;
            mapId        = location.MapTableData.MapId;
            locationData = location;
        }
Exemple #18
0
        /// <summary>
        /// Add interior models.
        /// </summary>
        private void AddModels(PlayerGPS.DiscoveredBuilding buildingData)
        {
            List <StaticDoor> doors     = new List <StaticDoor>();
            GameObject        node      = new GameObject("Models");
            GameObject        doorsNode = new GameObject("Doors");

            node.transform.parent      = this.transform;
            doorsNode.transform.parent = this.transform;

            // Iterate through models in this subrecord
            combiner.NewCombiner();
            foreach (DFBlock.RmbBlock3dObjectRecord obj in recordData.Interior.Block3dObjectRecords)
            {
                bool stopCombine = false;

                // Filter out bad interior models
                if (IsBadInteriorModel(obj.ModelIdNum))
                {
                    continue;
                }

                // Get model data
                ModelData modelData;
                dfUnity.MeshReader.GetModelData(obj.ModelIdNum, out modelData);

                // Get model position by type (3 seems to indicate props/clutter)
                // Also stop these from being combined as some may carry a loot container
                Vector3 modelPosition;
                if (obj.ObjectType == propModelType)
                {
                    // Props axis needs to be transformed to lowest Y point
                    Vector3 bottom = modelData.Vertices[0];
                    for (int i = 0; i < modelData.Vertices.Length; i++)
                    {
                        if (modelData.Vertices[i].y < bottom.y)
                        {
                            bottom = modelData.Vertices[i];
                        }
                    }
                    modelPosition  = new Vector3(obj.XPos, obj.YPos, obj.ZPos) * MeshReader.GlobalScale;
                    modelPosition += new Vector3(0, -bottom.y, 0);
                    stopCombine    = true;
                }
                else
                {
                    modelPosition = new Vector3(obj.XPos, -obj.YPos, obj.ZPos) * MeshReader.GlobalScale;
                }

                // Stop special object from being combined
                if (obj.ModelIdNum == ladderModelId)
                {
                    stopCombine = true;
                }

                // Get model transform
                Vector3   modelRotation = new Vector3(-obj.XRotation / BlocksFile.RotationDivisor, -obj.YRotation / BlocksFile.RotationDivisor, -obj.ZRotation / BlocksFile.RotationDivisor);
                Vector3   modelScale    = RMBLayout.GetModelScaleVector(obj);
                Matrix4x4 modelMatrix   = Matrix4x4.TRS(modelPosition, Quaternion.Euler(modelRotation), modelScale);

                // Does this model have doors?
                if (modelData.Doors != null)
                {
                    doors.AddRange(GameObjectHelper.GetStaticDoors(ref modelData, entryDoor.blockIndex, entryDoor.recordIndex, modelMatrix));
                }

                // Inject custom GameObject if available
                GameObject go = MeshReplacement.ImportCustomGameobject(obj.ModelIdNum, node.transform, modelMatrix);

                // Otherwise use Daggerfall mesh - combine or add
                if (!go)
                {
                    if (dfUnity.Option_CombineRMB && !stopCombine)
                    {
                        combiner.Add(ref modelData, modelMatrix);
                    }
                    else
                    {
                        // Add individual GameObject
                        go = GameObjectHelper.CreateDaggerfallMeshGameObject(obj.ModelIdNum, node.transform, dfUnity.Option_SetStaticFlags);
                        go.transform.position   = modelMatrix.GetColumn(3);
                        go.transform.rotation   = modelMatrix.rotation;
                        go.transform.localScale = modelMatrix.lossyScale;

                        // Update climate
                        DaggerfallMesh dfMesh = go.GetComponent <DaggerfallMesh>();
                        dfMesh.SetClimate(climateBase, climateSeason, WindowStyle.Disabled);
                    }
                }

                // Make ladder collider convex and ladder functionality, if set up as propModelType
                if (obj.ModelIdNum == ladderModelId && obj.ObjectType == propModelType)
                {
                    var meshCollider = go.GetComponent <MeshCollider>();
                    if (meshCollider)
                    {
                        meshCollider.convex = true;
                    }
                    go.AddComponent <DaggerfallLadder>();
                }

                // Optionally add action objects to specific furniture items (e.g. loot containers), except when laying out map (buildingType=AllValid)
                if (obj.ObjectType == propModelType && buildingData.buildingType != DFLocation.BuildingTypes.AllValid)
                {
                    AddFurnitureAction(obj, go, buildingData);
                }
            }

            // Add combined GameObject
            if (dfUnity.Option_CombineRMB)
            {
                if (combiner.VertexCount > 0)
                {
                    combiner.Apply();
                    GameObject go = GameObjectHelper.CreateCombinedMeshGameObject(combiner, "CombinedModels", node.transform, dfUnity.Option_SetStaticFlags);

                    // Update climate
                    DaggerfallMesh dfMesh = go.GetComponent <DaggerfallMesh>();
                    dfMesh.SetClimate(climateBase, climateSeason, WindowStyle.Disabled);
                }
            }

            // Add static doors component
            DaggerfallStaticDoors c = this.gameObject.AddComponent <DaggerfallStaticDoors>();

            c.Doors = doors.ToArray();
        }
Exemple #19
0
        private void GetBuildingList()
        {
            listBuildings = new List <BuildingInfo>();

            ContentReader.MapSummary mapSummary;
            DFPosition mapPixel = GameManager.Instance.PlayerGPS.CurrentMapPixel;

            if (!DaggerfallUnity.Instance.ContentReader.HasLocation(mapPixel.X, mapPixel.Y, out mapSummary))
            {
                // no location found
                return; // do nothing
            }
            DFLocation location = DaggerfallUnity.Instance.ContentReader.MapFileReader.GetLocation(mapSummary.RegionIndex, mapSummary.MapIndex);

            if (!location.Loaded)
            {
                // Location not loaded, something went wrong
                DaggerfallUnity.LogMessage("error when loading location for in TalkManager.GetBuildingList", true);
            }


            DaggerfallExteriorAutomap.BlockLayout[] blockLayout = GameManager.Instance.ExteriorAutomap.ExteriorLayout;

            DFBlock[] blocks;
            RMBLayout.GetLocationBuildingData(location, out blocks);
            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++)
                {
                    int index = y * width + x;
                    BuildingSummary[] buildingsInBlock = RMBLayout.GetBuildingData(blocks[index], x, y);

                    foreach (BuildingSummary buildingSummary in buildingsInBlock)
                    {
                        try
                        {
                            string       locationName = BuildingNames.GetName(buildingSummary.NameSeed, buildingSummary.BuildingType, buildingSummary.FactionId, location.Name, location.RegionName);
                            BuildingInfo item;
                            item.buildingType = buildingSummary.BuildingType;
                            item.name         = locationName;
                            item.buildingKey  = buildingSummary.buildingKey;
                            // compute building position in map coordinate system
                            float xPosBuilding = blockLayout[index].rect.xpos + (int)(buildingSummary.Position.x / (BlocksFile.RMBDimension * MeshReader.GlobalScale) * DaggerfallExteriorAutomap.blockSizeWidth) - GameManager.Instance.ExteriorAutomap.LocationWidth * DaggerfallExteriorAutomap.blockSizeWidth * 0.5f;
                            float yPosBuilding = blockLayout[index].rect.ypos + (int)(buildingSummary.Position.z / (BlocksFile.RMBDimension * MeshReader.GlobalScale) * DaggerfallExteriorAutomap.blockSizeHeight) - GameManager.Instance.ExteriorAutomap.LocationHeight * DaggerfallExteriorAutomap.blockSizeHeight * 0.5f;
                            item.position = new Vector2(xPosBuilding, yPosBuilding);
                            listBuildings.Add(item);
                        }
                        catch (Exception e)
                        {
                            string exceptionMessage = String.Format("exception occured in function BuildingNames.GetName (exception message: " + e.Message + @") with params: 
                                                                        seed: {0}, type: {1}, factionID: {2}, locationName: {3}, regionName: {4}",
                                                                    buildingSummary.NameSeed, buildingSummary.BuildingType, buildingSummary.FactionId, location.Name, location.RegionName);
                            DaggerfallUnity.LogMessage(exceptionMessage, true);
                        }
                    }
                }
            }
        }
        static void ThiefEffects_OnNewMagicRound()
        {
            //Debug.Log("[ThiefOverhaul] Magic Round");
            if (playerEnterExit.IsPlayerInsideBuilding)
            {
                PlayerGPS.DiscoveredBuilding buildingData = GameManager.Instance.PlayerEnterExit.BuildingDiscoveryData;
                if (RMBLayout.IsShop(buildingData.buildingType) && !PlayerActivate.IsBuildingOpen(buildingData.buildingType))
                {
                    int stealthValue = playerEntity.Skills.GetLiveSkillValue(DFCareer.Skills.Stealth);
                    stealthValue -= buildingData.quality * 2;

                    if (Dice100.FailedRoll(StealthCalc(stealthValue, false)))
                    {
                        if (burglaryCounter >= 100)
                        {
                            DaggerfallUI.MessageBox("'Guards! Guards! We're being robbed!'");
                            if (Dice100.SuccessRoll(playerEntity.Stats.LiveLuck))
                            {
                                playerEntity.MagicalConcealmentFlags = MagicalConcealmentFlags.None;
                                DaggerfallUI.AddHUDText("Your magical concealment is broken");
                            }

                            playerEntity.CrimeCommitted = PlayerEntity.Crimes.Breaking_And_Entering;
                            playerEntity.SpawnCityGuards(true);
                        }
                        else if (burglaryCounter == 0)
                        {
                            DaggerfallUI.MessageBox(burglaryString1());
                            burglaryCounter += Mathf.Clamp(UnityEngine.Random.Range(100, 200) - playerEntity.Stats.LiveLuck, 10, 100);
                        }
                        else if (burglaryCounter < 50)
                        {
                            DaggerfallUI.MessageBox(burglaryString2());
                            burglaryCounter += Mathf.Clamp(UnityEngine.Random.Range(100, 200) - playerEntity.Stats.LiveLuck, 10, 100);
                        }
                        else
                        {
                            burglaryCounter += Mathf.Clamp(UnityEngine.Random.Range(100, 200) - playerEntity.Stats.LiveLuck, 10, 100);
                        }
                    }
                }
            }

            DaggerfallUnityItem ringSlot0     = playerEntity.ItemEquipTable.GetItem(EquipSlots.Ring0);
            DaggerfallUnityItem ringSlot1     = playerEntity.ItemEquipTable.GetItem(EquipSlots.Ring1);
            DaggerfallUnityItem markSlot0     = playerEntity.ItemEquipTable.GetItem(EquipSlots.Mark0);
            DaggerfallUnityItem markSlot1     = playerEntity.ItemEquipTable.GetItem(EquipSlots.Mark1);
            DaggerfallUnityItem braceletSlot0 = playerEntity.ItemEquipTable.GetItem(EquipSlots.Bracelet0);
            DaggerfallUnityItem braceletSlot1 = playerEntity.ItemEquipTable.GetItem(EquipSlots.Bracelet1);
            DaggerfallUnityItem bracerSlot0   = playerEntity.ItemEquipTable.GetItem(EquipSlots.Bracer0);
            DaggerfallUnityItem bracerSlot1   = playerEntity.ItemEquipTable.GetItem(EquipSlots.Bracer1);
            DaggerfallUnityItem crystalSlot0  = playerEntity.ItemEquipTable.GetItem(EquipSlots.Crystal0);
            DaggerfallUnityItem crystalSlot1  = playerEntity.ItemEquipTable.GetItem(EquipSlots.Crystal1);

            if (ringSlot0 != null && ringSlot0.TemplateIndex == templateIndex_Ring)
            {
                lockpickingBonus = 20;
            }
            else if (ringSlot1 != null && ringSlot1.TemplateIndex == templateIndex_Ring)
            {
                lockpickingBonus = 20;
            }
            else
            {
                lockpickingBonus = 0;
            }

            if (markSlot0 != null && markSlot0.TemplateIndex == templateIndex_Mark)
            {
                streetwiseBonus = 20;
            }
            else if (markSlot1 != null && markSlot1.TemplateIndex == templateIndex_Mark)
            {
                streetwiseBonus = 20;
            }
            else
            {
                streetwiseBonus = 0;
            }

            if (braceletSlot0 != null && braceletSlot0.TemplateIndex == templateIndex_Bracelet)
            {
                pickpocketBonus = 20;
            }
            else if (braceletSlot1 != null && braceletSlot1.TemplateIndex == templateIndex_Bracelet)
            {
                pickpocketBonus = 20;
            }
            else
            {
                pickpocketBonus = 0;
            }

            if (bracerSlot0 != null && bracerSlot0.TemplateIndex == templateIndex_Bracer)
            {
                climbingBonus = 20;
            }
            else if (bracerSlot1 != null && bracerSlot1.TemplateIndex == templateIndex_Bracer)
            {
                climbingBonus = 20;
            }
            else
            {
                climbingBonus = 0;
            }

            if (crystalSlot0 != null && crystalSlot0.TemplateIndex == templateIndex_Crystal)
            {
                stealthBonus = 20;
            }
            else if (crystalSlot1 != null && crystalSlot1.TemplateIndex == templateIndex_Crystal)
            {
                stealthBonus = 20;
            }
            else
            {
                stealthBonus = 0;
            }


            if (!GameManager.IsGamePaused && playerEntity.CurrentHealth > 0)
            {
                int[] skillMods = new int[DaggerfallSkills.Count];
                skillMods[(int)DFCareer.Skills.Lockpicking] = +lockpickingBonus;
                skillMods[(int)DFCareer.Skills.Streetwise]  = +streetwiseBonus;
                skillMods[(int)DFCareer.Skills.Pickpocket]  = +pickpocketBonus;
                skillMods[(int)DFCareer.Skills.Climbing]    = +climbingBonus;
                skillMods[(int)DFCareer.Skills.Stealth]     = +stealthBonus;
                playerEffectManager.MergeDirectSkillMods(skillMods);
            }
        }
Exemple #21
0
        /// <summary>
        /// Generate a list of potential sites based on building type.
        /// This uses actual map layout and block data rather than the (often inaccurate) list of building in map data.
        /// Specify BuildingTypes.AllValid to find all valid building types
        /// </summary>
        SiteDetails[] CollectQuestSitesOfBuildingType(DFLocation location, DFLocation.BuildingTypes buildingType)
        {
            // Valid building types for valid search
            int[] validBuildingTypes = { 0, 2, 3, 5, 6, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20 };

            List <SiteDetails> foundSites = new List <SiteDetails>();

            // Iterate through all blocks
            DFBlock[] blocks;
            RMBLayout.GetLocationBuildingData(location, out blocks);
            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++)
                {
                    // Iterate through all buildings in this block
                    int index = y * width + x;
                    BuildingSummary[] buildingSummary = RMBLayout.GetBuildingData(blocks[index], x, y);
                    for (int i = 0; i < buildingSummary.Length; i++)
                    {
                        // When enumAllValid is specified accept all valid building types
                        bool forceAccept = false;
                        if (buildingType == DFLocation.BuildingTypes.AllValid)
                        {
                            for (int j = 0; j < validBuildingTypes.Length; j++)
                            {
                                if (validBuildingTypes[j] == (int)buildingSummary[i].BuildingType)
                                {
                                    forceAccept = true;
                                    break;
                                }
                            }
                        }

                        // Match building against required type
                        if (buildingSummary[i].BuildingType == buildingType || forceAccept)
                        {
                            // Building must be a valid quest site
                            QuestMarker[] questSpawnMarkers, questItemMarkers;
                            EnumerateBuildingQuestMarkers(blocks[index], i, out questSpawnMarkers, out questItemMarkers);
                            if (!ValidateQuestMarkers(questSpawnMarkers, questItemMarkers))
                            {
                                continue;
                            }

                            // Get building name based on type
                            string buildingName;
                            if (RMBLayout.IsResidence(buildingType))
                            {
                                // Generate a random surname for this residence
                                DFRandom.srand(Time.renderedFrameCount);
                                string surname = DaggerfallUnity.Instance.NameHelper.Surname(Utility.NameHelper.BankTypes.Breton);
                                buildingName = HardStrings.theNamedResidence.Replace("%s", surname);
                            }
                            else
                            {
                                // Use fixed name
                                buildingName = BuildingNames.GetName(
                                    buildingSummary[i].NameSeed,
                                    buildingSummary[i].BuildingType,
                                    buildingSummary[i].FactionId,
                                    location.Name,
                                    location.RegionName);
                            }

                            // Configure new site details
                            SiteDetails site = new SiteDetails();
                            site.questUID          = ParentQuest.UID;
                            site.siteType          = SiteTypes.Building;
                            site.mapId             = location.MapTableData.MapId;
                            site.locationId        = location.Exterior.ExteriorData.LocationId;
                            site.regionName        = location.RegionName;
                            site.locationName      = location.Name;
                            site.buildingKey       = buildingSummary[i].buildingKey;
                            site.buildingName      = buildingName;
                            site.questSpawnMarkers = questSpawnMarkers;
                            site.questItemMarkers  = questItemMarkers;

                            // Asssign markers only if available
                            if (questSpawnMarkers != null)
                            {
                                siteDetails.selectedQuestSpawnMarker = UnityEngine.Random.Range(0, questSpawnMarkers.Length);
                            }
                            if (questItemMarkers != null)
                            {
                                siteDetails.selectedQuestItemMarker = UnityEngine.Random.Range(0, questItemMarkers.Length);
                            }

                            foundSites.Add(site);
                        }
                    }
                }
            }

            return(foundSites.ToArray());
        }
Exemple #22
0
        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,
                                };
                            }
                        }
                    }
                }
            }
        }
Exemple #23
0
        internal void DisplayLocationInfo()
        {
            if (LocationSummary.LocationType == DFRegion.LocationTypes.Coven ||
                LocationSummary.LocationType == DFRegion.LocationTypes.DungeonKeep ||
                LocationSummary.LocationType == DFRegion.LocationTypes.DungeonLabyrinth ||
                LocationSummary.LocationType == DFRegion.LocationTypes.DungeonRuin ||
                LocationSummary.LocationType == DFRegion.LocationTypes.Graveyard ||
                LocationSummary.LocationType == DFRegion.LocationTypes.None)
            {
                return;
            }

            Dictionary <int, PlayerGPS.DiscoveredLocation> discoveryData = GameManager.Instance.PlayerGPS.GetDiscoverySaveData();

            if (discoveryData.ContainsKey(LocationSummary.ID))
            {
                PlayerGPS.DiscoveredLocation discoveredLocation             = discoveryData[locationSummary.ID];
                Dictionary <int, PlayerGPS.DiscoveredBuilding> locBuildings = discoveredLocation.discoveredBuildings;
                if (locBuildings != null && locBuildings.Count > 0)
                {
                    IDictionary <DFLocation.BuildingTypes, int> buildingTypeCounts = new SortedDictionary <DFLocation.BuildingTypes, int>();
                    List <string> guildNames = new List <string>();
                    foreach (PlayerGPS.DiscoveredBuilding building in locBuildings.Values)
                    {
                        if (RMBLayout.IsNamedBuilding(building.buildingType))
                        {
                            string guildName = building.displayName.StartsWith("The ") ? building.displayName.Substring(4) : building.displayName;
                            if (building.buildingType == DFLocation.BuildingTypes.GuildHall && !guildNames.Contains(guildName))
                            {
                                guildNames.Add(guildName);
                            }

                            if (building.buildingType != DFLocation.BuildingTypes.GuildHall)
                            {
                                if (buildingTypeCounts.ContainsKey(building.buildingType))
                                {
                                    buildingTypeCounts[building.buildingType]++;
                                }
                                else
                                {
                                    buildingTypeCounts.Add(building.buildingType, 1);
                                }
                            }
                        }
                    }
                    List <TextFile.Token> tokens = new List <TextFile.Token>();
                    tokens.Add(new TextFile.Token()
                    {
                        text       = GetLocationNameInCurrentRegion(locationSummary.MapIndex, true),
                        formatting = TextFile.Formatting.TextHighlight
                    });
                    tokens.Add(newLine);
                    tokens.Add(newLine);

                    guildNames.Sort();
                    string guilds = "";
                    foreach (string guildName in guildNames)
                    {
                        if (!string.IsNullOrWhiteSpace(guilds))
                        {
                            guilds += ", ";
                        }
                        guilds += guildName;
                    }
                    TextFile.Token tab1 = TextFile.TabToken;
                    tab1.x = 45;
                    TextFile.Token tab2 = TextFile.TabToken;
                    tab2.x = 100;
                    TextFile.Token tab3 = TextFile.TabToken;
                    tab3.x = 145;
                    if (!string.IsNullOrWhiteSpace(guilds))
                    {
                        tokens.Add(TextFile.CreateTextToken("Guild Halls:    " + guilds));
                    }
                    tokens.Add(newLine);
                    tokens.Add(TextFile.NewLineToken);

                    bool secondColumn = false;
                    foreach (DFLocation.BuildingTypes buildingType in buildingTypeCounts.Keys)
                    {
                        tokens.Add(TextFile.CreateTextToken(buildingType.ToString()));
                        tokens.Add(!secondColumn ? tab1 : tab3);
                        tokens.Add(TextFile.CreateTextToken(buildingTypeCounts[buildingType].ToString()));
                        if (!secondColumn)
                        {
                            tokens.Add(tab2);
                        }
                        else
                        {
                            tokens.Add(TextFile.NewLineToken);
                        }
                        secondColumn = !secondColumn;
                    }

                    infoBox = new DaggerfallMessageBox(uiManager, this);
                    infoBox.ClickAnywhereToClose = true;
                    infoBox.SetHighlightColor(Color.white);
                    infoBox.SetTextTokens(tokens.ToArray());
                    infoBox.OnClose += InfoBox_Close;
                    infoBox.Show();

                    return;
                }
            }
            DaggerfallUI.MessageBox("You have no knowledge of " + GetLocationNameInCurrentRegion(locationSummary.MapIndex, 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();
        }
        // Display a shop quality level
        private DaggerfallMessageBox PresentShopQuality(StaticBuilding building)
        {
            const int qualityLevel1TextId = 266;    // "Incense and soft music soothe your nerves"
            const int qualityLevel2TextId = 267;    // "The shop is better appointed than many"
            const int qualityLevel3TextId = 268;    // "The shop is laid out in a practical"
            const int qualityLevel4TextId = 269;    // "Sturdy shelves, cobbled together"
            const int qualityLevel5TextId = 270;    // "Rusty relics lie wherever they were last tossed"

            // Get building directory for location
            BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory();

            if (!buildingDirectory)
            {
                return(null);
            }

            // Get detailed building data from directory
            BuildingSummary buildingSummary;

            if (!buildingDirectory.GetBuildingSummary(building.buildingKey, out buildingSummary))
            {
                return(null);
            }

            // Do nothing if not a shop
            if (!RMBLayout.IsShop(buildingSummary.BuildingType))
            {
                return(null);
            }

            // Set quality level text ID from quality value 01-20
            // UESP states this is building quality / 4 but Daggerfall uses manual thresholds
            int qualityTextId;

            if (buildingSummary.Quality <= 3)
            {
                qualityTextId = qualityLevel5TextId;        // 01 - 03
            }
            else if (buildingSummary.Quality <= 7)
            {
                qualityTextId = qualityLevel4TextId;        // 04 - 07
            }
            else if (buildingSummary.Quality <= 13)
            {
                qualityTextId = qualityLevel3TextId;        // 08 - 13
            }
            else if (buildingSummary.Quality <= 17)
            {
                qualityTextId = qualityLevel2TextId;        // 14 - 17
            }
            else
            {
                qualityTextId = qualityLevel1TextId;        // 18 - 20
            }
            // Log quality of building entered for debugging
            //Debug.Log("Entered store with quality of " + buildingData.Quality);

            // Output quality text based on settings
            switch (DaggerfallUnity.Settings.ShopQualityPresentation)
            {
            case 0:         // Display popup as per classic
                return(DaggerfallUI.MessageBox(qualityTextId));

            case 1:         // Display HUD text only with variable delay
                TextFile.Token[] tokens = DaggerfallUnity.Instance.TextProvider.GetRSCTokens(qualityTextId);
                for (int i = 0; i < tokens.Length; i++)
                {
                    if (tokens[i].formatting == TextFile.Formatting.Text)
                    {
                        DaggerfallUI.AddHUDText(tokens[i].text, DaggerfallUnity.Settings.ShopQualityHUDDelay);
                    }
                }
                break;

            case 2:         // Display nothing about shop quality
            default:
                return(null);
            }

            return(null);
        }
        /// <summary>
        /// Gets building information from current location.
        /// Does not change discovery state for building.
        /// </summary>
        /// <param name="buildingKey">Key of building to query.</param>
        bool GetBuildingDiscoveryData(int buildingKey, out DiscoveredBuilding buildingDiscoveryData)
        {
            buildingDiscoveryData = new DiscoveredBuilding();

            // Get building directory for location
            BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory();

            if (!buildingDirectory)
            {
                return(false);
            }

            // Get detailed building data from directory
            BuildingSummary buildingSummary;

            if (!buildingDirectory.GetBuildingSummary(buildingKey, out buildingSummary))
            {
                int layoutX, layoutY, recordIndex;
                BuildingDirectory.ReverseBuildingKey(buildingKey, out layoutX, out layoutY, out recordIndex);
                Debug.LogFormat("Unable to find expected building key {0} in {1}.{2}", buildingKey, buildingDirectory.LocationData.RegionName, buildingDirectory.LocationData.Name);
                Debug.LogFormat("LayoutX={0}, LayoutY={1}, RecordIndex={2}", layoutX, layoutY, recordIndex);
                return(false);
            }

            // Resolve name by building type
            string buildingName;

            if (RMBLayout.IsResidence(buildingSummary.BuildingType))
            {
                // Residence
                buildingName = HardStrings.residence;

                // Link to quest system active sites
                // note Nystul: do this via TalkManager, this might seem odd at first glance but there is a reason to do so:
                //              get info from TalkManager if pc learned about existence of the building (i.e. its name)
                //              either through dialog ("add dialog" or by dialog-link) or quest (quest did not hide location via "dialog link" command)
                bool   pcLearnedAboutExistence     = false;
                bool   receivedDirectionalHints    = false;
                bool   locationWasMarkedOnMapByNPC = false;
                string overrideBuildingName        = string.Empty;
                if (GameManager.Instance.TalkManager.IsBuildingQuestResource(buildingSummary.buildingKey, ref overrideBuildingName, ref pcLearnedAboutExistence, ref receivedDirectionalHints, ref locationWasMarkedOnMapByNPC))
                {
                    if (pcLearnedAboutExistence)
                    {
                        buildingName = overrideBuildingName;
                    }
                }
            }
            else
            {
                // Fixed building name
                buildingName = BuildingNames.GetName(
                    buildingSummary.NameSeed,
                    buildingSummary.BuildingType,
                    buildingSummary.FactionId,
                    buildingDirectory.LocationData.Name,
                    buildingDirectory.LocationData.RegionName);
            }

            // Add to data
            buildingDiscoveryData.buildingKey  = buildingKey;
            buildingDiscoveryData.displayName  = buildingName;
            buildingDiscoveryData.factionID    = buildingSummary.FactionId;
            buildingDiscoveryData.quality      = buildingSummary.Quality;
            buildingDiscoveryData.buildingType = buildingSummary.BuildingType;

            return(true);
        }
        // Player has clicked on a static NPC
        void StaticNPCClick(StaticNPC npc)
        {
            // Do nothing if no NPC passed or fade in progress
            // Quest machine does not tick while fading (to prevent things happening while screen is black)
            // But this can result in player clicking a quest NPC before quest state ticks after load and breaking quest
            if (!npc || DaggerfallUI.Instance.FadeInProgress)
            {
                return;
            }

            // Store the NPC just clicked in quest engine
            QuestMachine.Instance.LastNPCClicked = npc;

            // Check if this NPC is a quest giver and show temp guild quest popup
            // This will be changed later when temp guild system replaced with real thing
            if (QuestorCheck(npc))
            {
                return;
            }

            // Handle quest NPC click and exit if linked to a Person resource
            QuestResourceBehaviour questResourceBehaviour = npc.gameObject.GetComponent <QuestResourceBehaviour>();

            if (questResourceBehaviour)
            {
                if (TriggerQuestResourceBehaviourClick(questResourceBehaviour))
                {
                    return;
                }
            }

            // Do nothing further if a quest is actively listening on this individual NPC
            // This NPC not reserved as a Person resource but has a WhenNpcIsAvailable action listening on it
            // This effectively shuts down several named NPCs during main quest, but not trivial to otherwise determine appropriate access
            // TODO: Try to find a good solution for releasing listeners when the owning action is disabled
            if (QuestMachine.Instance.HasFactionListener(npc.Data.factionID))
            {
                return;
            }

            // Get faction data.
            FactionFile.FactionData factionData;
            if (playerEnterExit.IsPlayerInsideBuilding &&
                GameManager.Instance.PlayerEntity.FactionData.GetFactionData(npc.Data.factionID, out factionData))
            {
                UserInterfaceManager uiManager = DaggerfallUI.Instance.UserInterfaceManager;
                Debug.LogFormat("faction id: {0}, social group: {1}, guild: {2}",
                                npc.Data.factionID, (FactionFile.SocialGroups)factionData.sgroup, (FactionFile.GuildGroups)factionData.ggroup);

                // Check if the NPC offers a guild service.
                if (Enum.IsDefined(typeof(GuildServices), npc.Data.factionID))
                {
                    FactionFile.GuildGroups guild   = (FactionFile.GuildGroups)factionData.ggroup;
                    GuildServices           service = (GuildServices)npc.Data.factionID;
                    Debug.Log("NPC offers guild service: " + service.ToString());
                    uiManager.PushWindow(new DaggerfallGuildServicePopupWindow(uiManager, npc, guild, service));
                }
                // Check if this NPC is a merchant.
                else if ((FactionFile.SocialGroups)factionData.sgroup == FactionFile.SocialGroups.Merchants)
                {
                    // Shop?
                    if (RMBLayout.IsShop(playerEnterExit.BuildingDiscoveryData.buildingType))
                    {
                        if (RMBLayout.IsRepairShop(playerEnterExit.BuildingDiscoveryData.buildingType))
                        {
                            uiManager.PushWindow(new DaggerfallMerchantRepairPopupWindow(uiManager, npc));
                        }
                        else
                        {
                            uiManager.PushWindow(new DaggerfallMerchantServicePopupWindow(uiManager, npc, DaggerfallMerchantServicePopupWindow.Services.Sell));
                        }
                    }
                    // Bank?
                    else if (playerEnterExit.BuildingDiscoveryData.buildingType == DFLocation.BuildingTypes.Bank)
                    {
                        uiManager.PushWindow(new DaggerfallMerchantServicePopupWindow(uiManager, npc, DaggerfallMerchantServicePopupWindow.Services.Banking));
                    }
                    // Tavern?
                    else if (playerEnterExit.BuildingDiscoveryData.buildingType == DFLocation.BuildingTypes.Tavern)
                    {
                        // for now only talk to all npc in taverns - TODO: add tavern option in here
                        GameManager.Instance.TalkManager.TalkToStaticNPC(npc);
                    }
                }
                // TODO - more checks for npc social types?
                else // if no special handling had to be done for npc with social group of type merchant: talk to the static npc
                {
                    GameManager.Instance.TalkManager.TalkToStaticNPC(npc);
                }
            }
            else // if no special handling had to be done (all remaining npcs of the remaining social groups not handled explicitely above): default is talk to the static npc
            {
                // with one exception: guards
                if (npc.Data.billboardArchiveIndex == 183 && npc.Data.billboardRecordIndex == 3) // detect if clicked guard (comment Nystul: didn't find a better mechanism than billboard texture check)
                {
                    return;                                                                      // if guard was clicked don't open talk window
                }
                // otherwise open talk window
                GameManager.Instance.TalkManager.TalkToStaticNPC(npc);
            }
        }