/// <summary> /// Groups children by MapKey and ParentTemplateId /// </summary> /// <returns>Whether the GameObjects list was sorted.</returns> private bool SortRootTemplate() { if (GameObjectsCached) { return(true); } GeneralHelper.WriteToConsole($"Sorting GameObjects...\n"); GameObjects = GameObjectBag.OrderBy(go => string.IsNullOrEmpty(go.Name)).ThenBy(go => go.Name).ToList(); var children = GameObjects.Where(go => !string.IsNullOrEmpty(go.ParentTemplateId)).ToList(); var lookup = GameObjects.Where(go => !string.IsNullOrEmpty(go.MapKey)).GroupBy(go => go.MapKey).ToDictionary(go => go.Key, go => go.Last()); Parallel.ForEach(children.AsParallel().OrderBy(go => string.IsNullOrEmpty(go.Name)).ThenBy(go => go.Name), gameObject => { var goChildren = lookup.FirstOrDefault(l => l.Key == gameObject.ParentTemplateId).Value?.Children; if (goChildren != null) { lock (goChildren) goChildren.Add(gameObject); } }); GameObjects = GameObjects.Where(go => string.IsNullOrEmpty(go.ParentTemplateId)).ToList(); foreach (var gameObject in GameObjects) { gameObject.PassOnStats(); } FileHelper.SerializeObject(GameObjects, "GameObjects"); return(true); }
/// <summary> /// Reads the visual banks for a list of id/filepath references for quick lookup. /// </summary> /// <returns>Whether the visual bank lists were created.</returns> private bool ReadVisualBanks() { var deserializedCharacterVisualBanks = FileHelper.DeserializeObject <Dictionary <string, string> >("CharacterVisualBanks"); var deserializedVisualBanks = FileHelper.DeserializeObject <Dictionary <string, string> >("VisualBanks"); var deserializedBodySetVisuals = FileHelper.DeserializeObject <Dictionary <string, string> >("BodySetVisuals"); var deserializedMaterialBanks = FileHelper.DeserializeObject <Dictionary <string, string> >("MaterialBanks"); var deserializedTextureBanks = FileHelper.DeserializeObject <Dictionary <string, string> >("TextureBanks"); if (deserializedVisualBanks != null && deserializedCharacterVisualBanks != null && deserializedBodySetVisuals != null && deserializedMaterialBanks != null && deserializedTextureBanks != null) { CharacterVisualBanks = deserializedCharacterVisualBanks; VisualBanks = deserializedVisualBanks; BodySetVisuals = deserializedBodySetVisuals; MaterialBanks = deserializedMaterialBanks; TextureBanks = deserializedTextureBanks; return(true); } // Lookup CharacterVisualBank file from CharacterVisualResourceID var characterVisualBanks = new ConcurrentDictionary <string, string>(); var visualBanks = new ConcurrentDictionary <string, string>(); var bodySetVisuals = new ConcurrentDictionary <string, string>(); var materialBanks = new ConcurrentDictionary <string, string>(); var textureBanks = new ConcurrentDictionary <string, string>(); var visualBankFiles = GetFileList("VisualBank"); var materialBankFiles = GetFileList("MaterialBank"); var textureBankFiles = GetFileList("TextureBank"); visualBankFiles.AddRange(materialBankFiles); visualBankFiles.AddRange(textureBankFiles); visualBankFiles = visualBankFiles.Distinct().ToList(); Parallel.ForEach(visualBankFiles, visualBankFile => { if (File.Exists(visualBankFile)) { var visualBankFilePath = FileHelper.Convert(visualBankFile, "lsx", visualBankFile.Replace(".lsf", ".lsx")); var filePath = visualBankFilePath.Replace($"\\\\?\\{Directory.GetCurrentDirectory()}\\UnpackedData", string.Empty); var stream = File.OpenText(visualBankFilePath); using (var fileStream = stream) using (var reader = new XmlTextReader(fileStream)) { reader.Read(); while (!reader.EOF) { try { var sectionId = reader.GetAttribute("id"); var isNode = reader.NodeType == XmlNodeType.Element && reader.IsStartElement() && reader.Name == "node"; if (isNode && (sectionId == "CharacterVisualBank" || sectionId == "VisualBank")) { // read children for resource nodes var xml = (XElement)XNode.ReadFrom(reader); var children = xml.Element("children"); if (children != null) { var nodes = children.Elements("node"); foreach (XElement node in nodes) { var id = node.Elements("attribute").Single(a => a.Attribute("id").Value == "ID").Attribute("value").Value; if (sectionId == "CharacterVisualBank") { characterVisualBanks.TryAdd(id, filePath); var bodySetVisual = node.Elements("attribute").Single(a => a.Attribute("id").Value == "BodySetVisual").Attribute("value").Value; if (bodySetVisual != null) { bodySetVisuals.TryAdd(bodySetVisual, filePath); } } else { visualBanks.TryAdd(id, filePath); } } } reader.Skip(); } else if (isNode && sectionId == "MaterialBank") { var xml = (XElement)XNode.ReadFrom(reader); var children = xml.Element("children"); if (children != null) { var nodes = children.Elements("node"); foreach (XElement node in nodes) { var id = node.Elements("attribute").Single(a => a.Attribute("id").Value == "ID").Attribute("value").Value; materialBanks.TryAdd(id, filePath); } } reader.Skip(); } else if (isNode && sectionId == "TextureBank") { var xml = (XElement)XNode.ReadFrom(reader); var children = xml.Element("children"); if (children != null) { var nodes = children.Elements("node"); foreach (XElement node in nodes) { var id = node.Elements("attribute").Single(a => a.Attribute("id").Value == "ID").Attribute("value").Value; textureBanks.TryAdd(id, filePath); } } reader.Skip(); } else { reader.Read(); } } catch (Exception ex) { GeneralHelper.WriteToConsole($"Failed to load {filePath}.\n"); break; } } reader.Close(); } } }); CharacterVisualBanks = characterVisualBanks.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); VisualBanks = visualBanks.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); BodySetVisuals = bodySetVisuals.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); MaterialBanks = materialBanks.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); TextureBanks = textureBanks.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); FileHelper.SerializeObject(CharacterVisualBanks, "CharacterVisualBanks"); FileHelper.SerializeObject(VisualBanks, "VisualBanks"); FileHelper.SerializeObject(BodySetVisuals, "BodySetVisuals"); FileHelper.SerializeObject(MaterialBanks, "MaterialBanks"); FileHelper.SerializeObject(TextureBanks, "TextureBanks"); return(true); }
/// <summary> /// Reads the root template and converts it into a GameObject list. /// </summary> /// <returns>Whether the root template was read.</returns> private bool ReadRootTemplate() { var deserializedGameObjects = FileHelper.DeserializeObject <List <GameObject> >("GameObjects"); if (deserializedGameObjects != null) { GameObjects = deserializedGameObjects; GameObjectsCached = true; return(true); } var rootTemplates = GetFileList("GameObjects"); var typeBag = new ConcurrentBag <string>(); #if DEBUG var idBag = new ConcurrentBag <string>(); var classBag = new ConcurrentBag <Tuple <string, string> >(); #endif Parallel.ForEach(rootTemplates, rootTemplate => { if (File.Exists(rootTemplate)) { var rootTemplatePath = FileHelper.Convert(rootTemplate, "lsx", rootTemplate.Replace(".lsf", ".lsx")); var pak = Regex.Match(rootTemplatePath, @"(?<=UnpackedData\\).*?(?=\\)").Value; var stream = File.OpenText(rootTemplatePath); using (var fileStream = stream) using (var reader = new XmlTextReader(fileStream)) { reader.Read(); while (!reader.EOF) { if (reader.NodeType == XmlNodeType.Element && reader.IsStartElement() && reader.GetAttribute("id") == "GameObjects") { var xml = (XElement)XNode.ReadFrom(reader); var gameObject = new GameObject { Pak = pak, Children = new List <GameObject>(), FileLocation = rootTemplatePath.Replace($"\\\\?\\{Directory.GetCurrentDirectory()}\\UnpackedData", string.Empty) }; var attributes = xml.Elements("attribute"); foreach (XElement attribute in attributes) { var id = attribute.Attribute("id").Value; var handle = attribute.Attribute("handle")?.Value; var value = handle ?? attribute.Attribute("value").Value; var type = attribute.Attribute("type").Value; if (int.TryParse(type, out int typeInt)) { type = GeneralHelper.LarianTypeEnumConvert(type); } #if DEBUG typeBag.Add(type); idBag.Add(id); classBag.Add(new Tuple <string, string>(id, type)); #endif if (string.IsNullOrEmpty(handle)) { gameObject.LoadProperty(id, type, value); } else { gameObject.LoadProperty($"{id}Handle", type, value); var translationText = TranslationLookup.FirstOrDefault(tl => tl.Key.Equals(value)).Value?.Value; gameObject.LoadProperty(id, type, translationText); } } if (string.IsNullOrEmpty(gameObject.ParentTemplateId)) { gameObject.ParentTemplateId = gameObject.TemplateName; } if (string.IsNullOrEmpty(gameObject.Name)) { gameObject.Name = gameObject.DisplayName; } if (string.IsNullOrEmpty(gameObject.Name)) { gameObject.Name = gameObject.Stats; } GameObjectBag.Add(gameObject); reader.Skip(); } else { reader.Read(); } } reader.Close(); } } }); #if DEBUG FileHelper.SerializeObject(typeBag.ToList().Distinct().ToList(), "GameObjectTypes"); FileHelper.SerializeObject(idBag.ToList().Distinct().ToList(), "GameObjectAttributeIds"); GeneralHelper.ClassBuilder(classBag.ToList().Distinct().ToList()); #endif return(true); }
/// <summary> /// Creates a file containing class properties for GameObjects. For development use. /// </summary> /// <param name="attributeClasses">The distinct list of ids and types</param> public static void ClassBuilder(List <Tuple <string, string> > attributeClasses) { FileHelper.SerializeObject(attributeClasses, "GameObjectAttributeClasses"); var classList = string.Empty; foreach (var attribute in attributeClasses.OrderBy(at => at.Item1)) { if (!char.IsLetter(attribute.Item1[0])) { continue; } var type = string.Empty; switch (attribute.Item2) { case "guid": type = "Guid"; break; case "bool": type = "bool"; break; case "float": type = "float"; break; case "double": type = "double"; break; case "int8": type = "sbyte"; break; case "int16": type = "short"; break; case "int": case "int32": type = "int"; break; case "uint8": type = "byte"; break; case "uint16": type = "uint16"; break; case "uint32": type = "uint"; break; case "uint64": type = "ulong"; break; case "fvec2": type = "Tuple<float, float>"; break; case "fvec3": type = "Tuple<float, float, float>"; break; case "fvec4": type = "Tuple<float, float, float, float>"; break; case "FixedString": case "LSString": case "TranslatedString": type = attribute.Item2; break; case "mat4x4": type = "List<Tuple<float, float, float, float>>"; break; default: throw new Exception($"Attribute type not covered: {attribute.Item2}"); } var camelCaseId = char.ToLowerInvariant(attribute.Item1[0]) + attribute.Item1.Substring(1); classList += $"private {type} _{camelCaseId};\n\n" + $"public {type} {attribute.Item1} {{\n" + $"\tget {{ return _{camelCaseId}; }}\n" + $"\tset {{ _{camelCaseId} = value; }}\n" + $"}}\n\n"; } if (!Directory.Exists("Development")) { Directory.CreateDirectory("Development"); } File.WriteAllText("Development/classList.txt", classList); }