|
| 1 | +using HKX2; |
| 2 | +using Pandora.Core; |
| 3 | +using Pandora.Core.Patchers.Skyrim; |
| 4 | +using Pandora.MVVM.Model; |
| 5 | +using Pandora.Patch.Patchers.Skyrim.AnimData; |
| 6 | +using Pandora.Patch.Patchers.Skyrim.AnimSetData; |
| 7 | +using Pandora.Patch.Patchers.Skyrim.Hkx; |
| 8 | +using System; |
| 9 | +using System.Collections.Generic; |
| 10 | +using System.Collections.ObjectModel; |
| 11 | +using System.IO; |
| 12 | +using System.Linq; |
| 13 | +using System.Security.Cryptography.X509Certificates; |
| 14 | +using System.Text; |
| 15 | +using System.Text.RegularExpressions; |
| 16 | +using System.Threading.Tasks; |
| 17 | +using System.Threading.Tasks.Sources; |
| 18 | +using System.Xml.Linq; |
| 19 | + |
| 20 | +namespace Pandora.Patch.Patchers.Skyrim.FNIS; |
| 21 | +public class FNISParser |
| 22 | +{ |
| 23 | + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); |
| 24 | + |
| 25 | + private static readonly HashSet<string> animTypePrefixes = new HashSet<string>() { "b", "s", "so", "fu", "fuo", "+", "ofa", "pa", "km", "aa", "ch" }; |
| 26 | + |
| 27 | + private static readonly Regex hkxRegex = new Regex("\\S*\\.hkx", RegexOptions.IgnoreCase); |
| 28 | + |
| 29 | + private static readonly Dictionary<string, string> stateMachineMap = new Dictionary<string, string>() |
| 30 | + { |
| 31 | + {"atronachflame~atronachflamebehavior", "#0414" }, |
| 32 | + {"atronachfrostproject~atronachfrostbehavior", "#0439" }, |
| 33 | + {"atronachstormproject~atronachstormbehavior", "#0369" }, |
| 34 | + {"bearproject~bearbehavior", "#0151" }, |
| 35 | + {"dogproject~dogbehavior", "#0144" }, |
| 36 | + {"wolfproject~wolfbehavior", "#0169" }, |
| 37 | + {"defaultfemale~0_master", "#0340" }, |
| 38 | + {"defaultmale~0_master", "#0340" }, |
| 39 | + {"firstperson~0_master", "#0167" }, |
| 40 | + {"spherecenturion~scbehavior", "#0780" }, |
| 41 | + {"dwarvenspidercenturionproject~dwarvenspiderbehavior", "#0394" }, |
| 42 | + {"steamproject~steambehavior", "#0538" }, |
| 43 | + {"falmerproject~falmerbehavior", "#1294" }, |
| 44 | + {"frostbitespiderproject~frostbitespiderbehavior", "#0402" }, |
| 45 | + {"giantproject~giantbehavior", "#0795" }, |
| 46 | + {"goatproject~goatbehavior", "#0140" }, |
| 47 | + {"highlandcowproject~h-cowbehavior", "#0152" }, |
| 48 | + {"deerproject~deerbehavior", "#0145" }, |
| 49 | + {"dragonproject~dragonbehavior", "#1610" }, |
| 50 | + {"dragon_priest~dragon_priest", "#0758" }, |
| 51 | + {"draugrproject~draugrbehavior", "#1998" }, |
| 52 | + {"hagravenproject~hagravenbehavior", "#0634" }, |
| 53 | + {"horkerproject~horkerbehavior", "#0161" }, |
| 54 | + {"horseproject~horsebehavior", "#0493" }, |
| 55 | + {"icewraithproject~icewraithbehavior", "#0262" }, |
| 56 | + {"mammothproject~mammothbehavior", "#0155" }, |
| 57 | + {"mudcrabproject~mudcrabbehavior", "#0481" }, |
| 58 | + {"sabrecatproject~sabrecatbehavior", "#0140" }, |
| 59 | + {"skeeverproject~skeeverbehavior", "#0132" }, |
| 60 | + {"slaughterfishproject~slaughterfishbehavior", "#0128" }, |
| 61 | + {"spriggan~sprigganbehavior", "#0610" }, |
| 62 | + {"trollproject~trollbehavior", "#0708" }, |
| 63 | + {"vampirelord~vampirelord", "#1114" }, |
| 64 | + {"werewolfbeastproject~werewolfbehavior", "#1207" }, |
| 65 | + {"wispproject~wispbehavior", "#0410" }, |
| 66 | + {"witchlightproject~witchlightbehavior", "#0152" }, |
| 67 | + {"chickenproject~chickenbehavior", "#0322" }, |
| 68 | + {"hareproject~harebehavior", "#0297" }, |
| 69 | + {"chaurusflyer~chaurusflyerbehavior", "#0402" }, |
| 70 | + {"vampirebruteproject~vampirebrutebehavior", "#0502" }, |
| 71 | + {"benthiclurkerproject~benthiclurkerbehavior", "#0708" }, |
| 72 | + {"boarriekling~boarbehavior", "#0548" }, |
| 73 | + {"ballistacenturion~bcbehavior", "#0475" }, |
| 74 | + {"hmdaedra~hmdaedra", "#0490" }, |
| 75 | + {"netchproject~netchbehavior", "#0279" }, |
| 76 | + {"rieklingproject~rieklingbehavior", "#0730" }, |
| 77 | + {"scribproject~scribbehavior", "#0578" } |
| 78 | + }; |
| 79 | + |
| 80 | + private static readonly Dictionary<string, string> linkedProjectMap = new Dictionary<string, string>() |
| 81 | + { |
| 82 | + {"defaultmale", "defaultfemale" }, |
| 83 | + {"defaultfemale", "defaultmale" }, |
| 84 | + {"draugrskeletonproject", "draugrproject" }, |
| 85 | + {"draugrproject", "draugrskeletonproject" }, |
| 86 | + {"wolfproject", "dogproject" }, |
| 87 | + {"dogproject", "wolfproject" } |
| 88 | + }; |
| 89 | + |
| 90 | + private HashSet<string> parsedFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase); |
| 91 | + |
| 92 | + public HashSet<LimitedModInfo> ModInfos { get; private set; } = new HashSet<LimitedModInfo>(); |
| 93 | + |
| 94 | + public FNISParser(ProjectManager manager) |
| 95 | + { |
| 96 | + projectManager = manager; |
| 97 | + } |
| 98 | + private ProjectManager projectManager { get; } |
| 99 | + public void ScanProjectAnimlist(Project project) |
| 100 | + { |
| 101 | + var animationsFolder = project.OutputAnimationDirectory; |
| 102 | + var behaviorFolder = project.OutputBehaviorDirectory; |
| 103 | + if (animationsFolder == null || behaviorFolder == null || !animationsFolder.Exists || !behaviorFolder.Exists) { return; } |
| 104 | + lock (parsedFolders) |
| 105 | + { |
| 106 | + if (parsedFolders.Contains(animationsFolder.FullName) || parsedFolders.Contains(behaviorFolder.FullName)) { return; } |
| 107 | + |
| 108 | + parsedFolders.Add(animationsFolder.FullName); |
| 109 | + parsedFolders.Add(behaviorFolder.FullName); |
| 110 | + } |
| 111 | + |
| 112 | + var behaviorFiles = behaviorFolder.GetFiles("FNIS*.hkx"); |
| 113 | + |
| 114 | + if (behaviorFiles.Length > 0) { projectManager.ActivatePackFile(project.BehaviorFile); } |
| 115 | + |
| 116 | + foreach (var behaviorFile in behaviorFiles) |
| 117 | + { |
| 118 | + InjectGraphReference(behaviorFile, project.BehaviorFile); |
| 119 | + } |
| 120 | + |
| 121 | + var modAnimationFolders = animationsFolder.GetDirectories(); |
| 122 | + |
| 123 | + if (modAnimationFolders.Length == 0) { return; } |
| 124 | + |
| 125 | + Parallel.ForEach(modAnimationFolders, folder => { ParseAnimlistFolder(folder, project, projectManager); }); |
| 126 | + } |
| 127 | + private bool InjectGraphReference(FileInfo sourceFile, PackFileGraph destPackFile) |
| 128 | + { |
| 129 | + string stateFolderName; |
| 130 | + |
| 131 | + if (!stateMachineMap.TryGetValue(destPackFile.UniqueName, out stateFolderName!)) { return false; } |
| 132 | + |
| 133 | + destPackFile.MapNode(stateFolderName); |
| 134 | + |
| 135 | + //InjectEventsAndVariables(sourcePackFile, destPackFile, changeSet); |
| 136 | + string nameWithoutExtension = Path.GetFileNameWithoutExtension(sourceFile.Name); |
| 137 | + string refName = nameWithoutExtension.Replace(' ', '_'); |
| 138 | + var stateInfoPath = string.Format("{0}/states", stateFolderName); |
| 139 | + var graphPath = $"{destPackFile.OutputHandle.Directory?.Name}\\{nameWithoutExtension}.hkx"; |
| 140 | + var modInfo = new LimitedModInfo(sourceFile); |
| 141 | + lock (ModInfos) ModInfos.Add(modInfo); |
| 142 | + var changeSet = new PackFileChangeSet(modInfo); |
| 143 | + |
| 144 | + PatchNodeCreator nodeMaker = new PatchNodeCreator(changeSet.Origin.Code); |
| 145 | + |
| 146 | + string behaviorRefName; |
| 147 | + var behaviorRef = nodeMaker.CreateBehaviorReferenceGenerator(refName, graphPath, out behaviorRefName); |
| 148 | + XElement behaviorRefElement = nodeMaker.TranslateToLinq<hkbBehaviorReferenceGenerator>(behaviorRef, behaviorRefName); |
| 149 | + |
| 150 | + string stateInfoName; |
| 151 | + var stateInfo = nodeMaker.CreateSimpleStateInfo(behaviorRef, out stateInfoName); |
| 152 | + XElement stateInfoElement = nodeMaker.TranslateToLinq<hkbStateMachineStateInfo>(stateInfo, stateInfoName); |
| 153 | + |
| 154 | + changeSet.AddChange(new AppendElementChange(PackFile.ROOT_CONTAINER_NAME, behaviorRefElement)); |
| 155 | + changeSet.AddChange(new AppendElementChange(PackFile.ROOT_CONTAINER_NAME, stateInfoElement)); |
| 156 | + changeSet.AddChange(new AppendTextChange(stateInfoPath, stateInfoName)); |
| 157 | + |
| 158 | + destPackFile.Dispatcher.AddChangeSet(changeSet); |
| 159 | + return true; |
| 160 | + } |
| 161 | + private void ParseAnimlistFolder(DirectoryInfo folder, Project project, ProjectManager projectManager) |
| 162 | + { |
| 163 | + var animlistFiles = folder.GetFiles("*list.txt"); |
| 164 | + if (animlistFiles.Length == 0) { return; } |
| 165 | + |
| 166 | + |
| 167 | + |
| 168 | + List<PackFileCharacter> characterFiles = new List<PackFileCharacter> { project.CharacterFile }; |
| 169 | + |
| 170 | + if (linkedProjectMap.TryGetValue(project.Identifier, out var linkedProjectIdentifier) && projectManager.TryGetProject(linkedProjectIdentifier, out var linkedProject)) |
| 171 | + { |
| 172 | + characterFiles.Add(linkedProject!.CharacterFile); |
| 173 | + |
| 174 | + } |
| 175 | + foreach(var characterFile in characterFiles) { projectManager.ActivatePackFile(characterFile); } |
| 176 | + foreach (var animlistFile in animlistFiles) |
| 177 | + { |
| 178 | + ParseAnimlist(animlistFile, characterFiles); |
| 179 | + } |
| 180 | + } |
| 181 | + private void ParseAnimlist(FileInfo file, List<PackFileCharacter> characterPackFiles) |
| 182 | + { |
| 183 | + |
| 184 | + |
| 185 | + List<PackFileChangeSet> changeSets = new List<PackFileChangeSet>(); |
| 186 | + |
| 187 | + for(int i = 0; i < characterPackFiles.Count; i++) { changeSets.Add(new PackFileChangeSet(new LimitedModInfo(file))); } |
| 188 | + |
| 189 | + |
| 190 | + var parentFolder = file.Directory; |
| 191 | + if (parentFolder == null) { return; } |
| 192 | + |
| 193 | + |
| 194 | + var ancestorFolder = parentFolder.Parent; |
| 195 | + if (ancestorFolder == null) { return; } |
| 196 | + |
| 197 | + |
| 198 | + var root = Path.Combine(ancestorFolder.Name, parentFolder.Name); |
| 199 | + |
| 200 | + using (var readStream = file.OpenRead()) |
| 201 | + { |
| 202 | + using (var reader = new StreamReader(readStream)) |
| 203 | + { |
| 204 | + string? expectedLine; |
| 205 | + while((expectedLine = reader.ReadLine()) != null) |
| 206 | + { |
| 207 | + if (string.IsNullOrWhiteSpace(expectedLine) || expectedLine[0] == '\'') { continue; } |
| 208 | + |
| 209 | + var match = hkxRegex.Match(expectedLine); |
| 210 | + if (!match.Success) { continue; } |
| 211 | + |
| 212 | + var animationPath = Path.Combine(root, match.Value); |
| 213 | + for (int i = 0; i < changeSets.Count; i++) |
| 214 | + { |
| 215 | + changeSets[i].AddChange(new AppendElementChange(characterPackFiles[i].AnimationNamesPath, new XElement("hkcstring", animationPath))); |
| 216 | + } |
| 217 | + |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + for (int i = 0; i< characterPackFiles.Count ; i++) |
| 222 | + { |
| 223 | + characterPackFiles[i].Dispatcher.AddChangeSet(changeSets[i]); |
| 224 | + } |
| 225 | + |
| 226 | + } |
| 227 | + |
| 228 | + |
| 229 | +} |
0 commit comments