private static Mesh[][][] GetMeshesForExteriorFixture(int fixtureId, ref FFXIVHSLib.Transform[][] transformsPerModel) { //A little different this time! if (_exteriorFixtureMeshes == null) { _exteriorFixtureMeshes = new Dictionary <int, Mesh[][][]>(); } if (_exteriorFixtureMeshTransforms == null) { _exteriorFixtureMeshTransforms = new Dictionary <int, FFXIVHSLib.Transform[][]>(); } Mesh[][][] modelMeshes; if (!_exteriorFixtureMeshes.TryGetValue(fixtureId, out modelMeshes)) { string exteriorHousingObjectsFolder = FFXIVHSPaths.GetHousingExteriorObjectsDirectory(); //Load the meshes if not found HousingExteriorFixture fixture = _exteriorFixtures[fixtureId]; //Initialize variants dimensions int numVariants = HousingExteriorFixture.GetVariants(fixture.fixtureType); modelMeshes = new Mesh[numVariants][][]; transformsPerModel = new FFXIVHSLib.Transform[numVariants][]; int i = 0; foreach (HousingExteriorFixtureVariant variant in fixture.variants) { //Initialize model dimensions for this variant int numModels = variant.models.Length; modelMeshes[i] = new Mesh[numModels][]; transformsPerModel[i] = new FFXIVHSLib.Transform[numModels]; int j = 0; foreach (HousingExteriorFixtureModel model in variant.models) { modelMeshes[i][j] = new Mesh[model.numMeshes]; transformsPerModel[i][j] = model.transform; for (int k = 0; k < model.numMeshes; k++) { string meshFileName = string.Format("{0}{1}_{2}.obj", exteriorHousingObjectsFolder, model.modelName, k); modelMeshes[i][j][k] = FastObjImporter.Instance.ImportFile(meshFileName); } j++; } i++; } _exteriorFixtureMeshes.Add(fixtureId, modelMeshes); _exteriorFixtureMeshTransforms.Add(fixtureId, transformsPerModel); } else { //If the meshes are in the dictionary, so are the transforms :p transformsPerModel = _exteriorFixtureMeshTransforms[fixtureId]; } return(modelMeshes); }
/// <summary> /// Adds the housing territory default fences as exterior fixtures with an id. /// The game does not actually recognize these as fixtures, they are part of the map. /// However, currently there is no way to know which transforms within the map sgbs belongs to /// which house size. So we have to work around it. Thanks SE! /// </summary> /// <param name="realm"></param> /// <param name="fixtures"></param> private static void AddDefaultFences(ARealmReversed realm, ref Dictionary <int, HousingExteriorFixture> fixtures) { List <TerritoryType> teris = GetHousingTerritoryTypes(realm); //Obtain all housing TerritoryTypes IXivSheet <PlaceName> placeNames = realm.GameData.GetSheet <PlaceName>(); PlaceName[] pNames = placeNames.ToArray(); string[] fncPaths = { "bg/ffxiv/{0}/hou/dyna/c_fnc/0000/asset/{1}_f_fnc0000a.sgb", "bg/ffxiv/{0}/hou/dyna/c_fnc/0000/asset/{1}_f_fnc0000b.sgb", "bg/ffxiv/{0}/hou/dyna/c_fnc/0000/asset/{1}_f_fnc0000c.sgb", "bg/ffxiv/{0}/hou/dyna/c_fnc/0000/asset/{1}_f_fnc0000d.sgb", }; foreach (TerritoryType t in teris) { //Get usable strings string bgFolder = t.Bg.ToString().Split('/')[1]; string namePrefix = t.Name.ToString(); namePrefix = namePrefix.Substring(0, namePrefix.Length - 1) + '0'; //bgFolder is now 'sea_s1', 'est_e1', etc //namePrefix is now 's1h0', 'e1h0', etc int intUse = pNames.Where(_ => _.Name == t.RegionPlaceName.Name) .Select(_ => _.Key) .Min(_ => _); HousingExteriorFixture thisFence = new HousingExteriorFixture(); thisFence.itemId = 0; thisFence.fixtureId = int.Parse("102" + intUse); thisFence.fixtureModelKey = 0; //Not in the sheet, no key ¯\_(ツ)_/¯ thisFence.fixtureType = FixtureType.fnc; thisFence.fixtureIntendedUse = intUse; thisFence.size = Size.x; thisFence.name = $"Default {t.PlaceName.NameWithoutArticle} Fence"; List <string> sgbPaths = new List <string>(); foreach (string fnc in fncPaths) { sgbPaths.Add(string.Format(fnc, bgFolder, namePrefix)); } //I forgot this method existed. Thanks! thisFence.variants = ReadSgbForVariantInfo(realm, sgbPaths.ToArray()); fixtures.Add(thisFence.fixtureId, thisFence); } }
/// <summary> /// Returns a HousingExteriorBlueprintSet read from the small, medium, and large sgb paths in /// HousingExteriorBlueprintSet. /// </summary> /// <param name="realm"></param> /// <returns></returns> private static HousingExteriorBlueprintSet ReadExteriorBlueprintSet(ARealmReversed realm) { HousingExteriorBlueprintSet thisSet = new HousingExteriorBlueprintSet(); thisSet.set = new HousingExteriorBlueprint[HousingExteriorBlueprintSet.SgbPaths.Length]; for (int i = 0; i < HousingExteriorBlueprintSet.SgbPaths.Length; i++) { string thisPath = HousingExteriorBlueprintSet.SgbPaths[i]; HousingExteriorBlueprint thisBlueprint = new HousingExteriorBlueprint { size = (Size)i }; //These are hardcoded, double check they're there SaintCoinach.IO.File f; if (!realm.Packs.TryGetFile(thisPath, out f)) { throw new FileNotFoundException(); } SgbFile sgb = new SgbFile(f); foreach (SgbGroup group in sgb.Data.OfType <SgbGroup>()) { foreach (SgbGimmickEntry gim in group.Entries.OfType <SgbGimmickEntry>()) { string gimmickPath = gim.Gimmick.File.Path; /* * Group 1: s1h0 or opt * Group 2: size, dor/wid variant, f for fence, or opt's 2chars * Group 3: fixturetype or m for opt * Group 4: a/b/c/d for fence, . for all others */ Regex pattern = new Regex(@"asset\/(.*)?_(.{1,2})_(.{1,3})([0-9]{4})([a-z]|\.)"); Match m = pattern.Match(gimmickPath); if (!m.Success) { continue; } //Obtain fixture type string fType = m.Groups[3].Value; if (fType == "m") { fType = "o" + m.Groups[2].Value; } FixtureType fixtureType = (FixtureType)Enum.Parse(typeof(FixtureType), fType); //Obtains the variant string, examples: s, m, l, co, ca, ci, a, b, c, d string strVariant = m.Groups[5].Value == "." ? m.Groups[2].Value : m.Groups[5].Value; int variant = 0; //Attempt to parse group 2 into a Size to get variant if (!Enum.TryParse(strVariant, out Size fixtureSize)) { //If we can't parse variant to a size, we have to parse into a different fixture variant if (fixtureType == FixtureType.dor) { variant = (int)Enum.Parse(typeof(DoorVariants), strVariant); } else if (fixtureType == FixtureType.wid) { variant = (int)Enum.Parse(typeof(WindowVariants), strVariant); } else if (fixtureType == FixtureType.fnc) { variant = (int)Enum.Parse(typeof(FenceVariants), strVariant); } } else { //If rof/wal is not same size as blueprint, skip it if (fixtureSize != thisBlueprint.size) { continue; } } if (thisBlueprint.fixtureTransforms[fixtureType][variant] == null) { thisBlueprint.fixtureTransforms[fixtureType][variant] = new List <Transform>(); } List <Transform> listForVariant = thisBlueprint.fixtureTransforms[fixtureType][variant]; Transform t = TransformFromVectors(gim.Header.Translation, gim.Header.Rotation, gim.Header.Scale); listForVariant.Add(t); #region switch statement bad // switch (m.Groups[3].Value) // { // case nameof(FixtureType.rof): // if (m.Groups[2].Value == size.ToString()) // { // //exampe but cool // } // break; // case nameof(FixtureType.wal): // //Do stuff // break; // case nameof(FixtureType.wid): // //Do stuff // break; // case nameof(FixtureType.dor): // //Do stuff // break; // case "m": // switch ("o" + m.Groups[2].Value) // { // case nameof(FixtureType.orf): // //Do stuff // break; // case nameof(FixtureType.owl): // //Do stuff // break; // case nameof(FixtureType.osg): // //Do stuff // break; // } // break; // case nameof(FixtureType.fnc): // //Do stuff // break; // } #endregion } } /* * The transforms relevant to the current blueprint come first, * so figure out how many Transforms aren't for this size house * by checking how many transforms are collectively in the blueprints * below this size and variant, then remove that number of entries. */ if (i > 0) { int[][] numTransformsInSmallerBlueprints = new int[Enum.GetValues(typeof(FixtureType)).Length][]; //For every smaller blueprint for (int smallerBlueprintIndex = 0; smallerBlueprintIndex < i; smallerBlueprintIndex++) { //For every fixture type for (int fixtureTypeIndex = 0; fixtureTypeIndex < numTransformsInSmallerBlueprints.Length; fixtureTypeIndex++) { FixtureType fixtureType = (FixtureType)fixtureTypeIndex + 1; int numberOfVariants = HousingExteriorFixture.GetVariants(fixtureType); if (numTransformsInSmallerBlueprints[fixtureTypeIndex] == null) { numTransformsInSmallerBlueprints[fixtureTypeIndex] = new int[numberOfVariants]; } for (int variantIndex = 0; variantIndex < numberOfVariants; variantIndex++) { int?toAddn = thisSet.set[smallerBlueprintIndex].fixtureTransforms[fixtureType][variantIndex]?.Count; int toAdd = toAddn ?? 0; numTransformsInSmallerBlueprints[fixtureTypeIndex][variantIndex] += toAdd; } } } //Whoops, don't do it for fences for (int j = 0; j < numTransformsInSmallerBlueprints[(int)FixtureType.fnc - 1].Length; j++) { numTransformsInSmallerBlueprints[(int)FixtureType.fnc - 1][j] = 0; } //For every fixture type for (int fixtureTypeIndex = 0; fixtureTypeIndex < numTransformsInSmallerBlueprints.Length; fixtureTypeIndex++) { FixtureType fixtureType = (FixtureType)fixtureTypeIndex + 1; int numberOfVariants = HousingExteriorFixture.GetVariants(fixtureType); for (int variantIndex = 0; variantIndex < numberOfVariants; variantIndex++) { List <Transform> variantTransformsList = thisBlueprint.fixtureTransforms[fixtureType][variantIndex]; if (thisBlueprint.fixtureTransforms[fixtureType][variantIndex] == null) { continue; } if (variantTransformsList == null) { continue; } int numTransforms = variantTransformsList.Count; int difference = numTransforms - numTransformsInSmallerBlueprints[fixtureTypeIndex][variantIndex]; if (difference > 0) { List <Transform> newTransforms = new List <Transform>(); for (int tCount = 0; tCount < difference; tCount++) { newTransforms.Add(variantTransformsList[tCount]); } thisBlueprint.fixtureTransforms[fixtureType][variantIndex] = newTransforms; } } } } thisSet.set[i] = thisBlueprint; } return(thisSet); }
/// <summary> /// Reads from game data and serializes a dictionary containing exterior housing fixture information.<br /> /// /// Reads from sheets HousingExterior and Item. /// </summary> private static Dictionary <int, HousingExteriorFixture> ReadHousingExteriorSheet(ARealmReversed realm) { Dictionary <int, HousingExteriorFixture> fixtures = new Dictionary <int, HousingExteriorFixture>(); IXivSheet <XivRow> housingExteriorSheet = realm.GameData.GetSheet("HousingExterior"); IXivSheet <XivRow> itemSheet = realm.GameData.GetSheet("Item"); foreach (XivRow row in housingExteriorSheet) { if (row.Key == 0) { continue; } HousingExteriorFixture rowFixture = new HousingExteriorFixture(); rowFixture.size = (Size)(byte)row[3]; rowFixture.fixtureId = row.Key; rowFixture.fixtureModelKey = (byte)row[0]; rowFixture.fixtureIntendedUse = (ushort)row[2]; rowFixture.fixtureType = (FixtureType)(byte)row[1]; //Data from item sheet int[] stainResults = (from x in itemSheet where (uint)x.GetRaw("Stain") == row.Key select x.Key).ToArray(); if (stainResults.Length == 1) { rowFixture.itemId = stainResults[0]; rowFixture.name = (from x in itemSheet where x.Key == rowFixture.itemId select x["Name"]).Single().ToString(); } else { rowFixture.itemId = -1; rowFixture.name = ""; } //Get data for variants and modelEntries string p = row[4].ToString(); string[] sgbPaths = { String.IsNullOrEmpty(p) ? null : p }; //Fences too because row only has one .sgb for them - there are 4 if (sgbPaths[0] == null) { sgbPaths = rowFixture.GetPaths(); } else if (rowFixture.fixtureType == FixtureType.fnc) { sgbPaths = rowFixture.GetPaths(sgbPaths[0]); } /* * For testing * * foreach (string path in sgbPaths) * { * string name = rowFixture.name; * if (String.IsNullOrEmpty(name)) * name = rowFixture.fixtureIntendedUse.ToString(); * if (!realm.Packs.FileExists(path)) * Console.WriteLine(name + "|" + path); * }*/ //Load model data and things rowFixture.variants = ReadSgbForVariantInfo(realm, sgbPaths); fixtures.Add(rowFixture.fixtureId, rowFixture); } return(fixtures); }