Skip to content

Commit 56fefcf

Browse files
committed
message improvements, default fnis support
1 parent 407e661 commit 56fefcf

File tree

9 files changed

+366
-50
lines changed

9 files changed

+366
-50
lines changed
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Pandora.Core;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace Pandora.MVVM.Model;
10+
public class LimitedModInfo : IModInfo
11+
{
12+
public string Name { get; private set; }
13+
14+
public string Description { get; private set; } = string.Empty;
15+
16+
public string Author { get; private set; } = "unknown";
17+
18+
public Version Version { get; private set; } = new Version();
19+
20+
public string URL { get; private set; } = "null";
21+
22+
public string Code { get; private set; } = "n/a";
23+
24+
public DirectoryInfo Folder { get; private set; }
25+
26+
public bool Active { get; set; } = true;
27+
public uint Priority { get; set; } = 0;
28+
29+
public LimitedModInfo(FileInfo file)
30+
{
31+
Name = file.Name;
32+
Folder = file.Directory!;
33+
}
34+
}

PandoraPlus/Patch/Patchers/IPatchFile.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public interface IPatchFile
1313

1414
public FileInfo OutputHandle { get; }
1515

16-
public void Export();
16+
public bool Export();
1717
}
1818
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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+
}

PandoraPlus/Patch/Patchers/Skyrim/Hkx/PackFile.cs

+33-30
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class PackFile : IPatchFile, IEquatable<PackFile>
3434

3535
public PackFileDispatcher Dispatcher { get; private set; } = new PackFileDispatcher();
3636

37+
public bool ExportSuccess { get; private set; } = true;
38+
3739
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
3840

3941
public void ApplyChanges() => Dispatcher.ApplyChanges(this);
@@ -145,46 +147,46 @@ public void MapNode(string nodeName)
145147
}
146148
}
147149

148-
public void Export()
150+
public bool Export()
149151
{
150152

151153

152-
if (OutputHandle.Directory == null) return;
154+
if (OutputHandle.Directory == null) return false;
153155
if (!OutputHandle.Directory.Exists) { OutputHandle.Directory.Create(); }
154156
if (OutputHandle.Exists) { OutputHandle.Delete(); }
155157
HKXHeader header = HKXHeader.SkyrimSE();
156158
IHavokObject rootObject;
157159
#if DEBUG
158-
159-
using (var memoryStream = new MemoryStream())
160-
{
161-
Map.Save(memoryStream);
162-
memoryStream.Position = 0;
163-
var deserializer = new XmlDeserializer();
164-
rootObject = deserializer.Deserialize(memoryStream, header, false);
165-
}
166-
using (var writeStream = OutputHandle.Create())
167-
{
168-
var binaryWriter = new BinaryWriterEx(writeStream);
169-
var serializer = new PackFileSerializer();
170-
serializer.Serialize(rootObject, binaryWriter, header);
171-
}
160+
Debug.WriteLine($"Export: {OutputHandle.FullName}");
161+
using (var memoryStream = new MemoryStream())
162+
{
163+
Map.Save(memoryStream);
164+
memoryStream.Position = 0;
165+
var deserializer = new XmlDeserializer();
166+
rootObject = deserializer.Deserialize(memoryStream, header, false);
167+
}
168+
using (var writeStream = OutputHandle.Create())
169+
{
170+
var binaryWriter = new BinaryWriterEx(writeStream);
171+
var serializer = new PackFileSerializer();
172+
serializer.Serialize(rootObject, binaryWriter, header);
173+
}
172174

173175

174-
var debugOuputHandle = new FileInfo(OutputHandle.FullName + ".xml");
176+
var debugOuputHandle = new FileInfo(OutputHandle.FullName + ".xml");
175177

176-
using (var writeStream = debugOuputHandle.Create())
177-
{
178+
using (var writeStream = debugOuputHandle.Create())
179+
{
178180

179-
var xmlSerializer = new HKX2.XmlSerializer();
180-
xmlSerializer.Serialize(rootObject, header, writeStream);
181-
}
182-
debugOuputHandle = new FileInfo(debugOuputHandle.DirectoryName + "\\m_" + debugOuputHandle.Name);
181+
var xmlSerializer = new HKX2.XmlSerializer();
182+
xmlSerializer.Serialize(rootObject, header, writeStream);
183+
}
184+
debugOuputHandle = new FileInfo(debugOuputHandle.DirectoryName + "\\m_" + debugOuputHandle.Name);
183185

184-
using (var writeStream = debugOuputHandle.Create())
185-
{
186-
Map.Save(writeStream);
187-
}
186+
using (var writeStream = debugOuputHandle.Create())
187+
{
188+
Map.Save(writeStream);
189+
}
188190

189191
#else
190192
try
@@ -210,10 +212,11 @@ public void Export()
210212
{
211213
Map.Save(writeStream);
212214
}
215+
return false;
213216
}
214217
#endif
215218

216-
219+
return true;
217220
}
218221
public static void Unpack(FileInfo inputHandle)
219222
{
@@ -265,11 +268,11 @@ public static FileInfo GetUnpackedHandle(FileInfo inputHandle)
265268

266269
public bool Equals(PackFile? other)
267270
{
268-
return other != null && other.OutputHandle.FullName.Equals(this.OutputHandle.FullName);
271+
return other != null && other.OutputHandle.FullName.Equals(this.OutputHandle.FullName, StringComparison.OrdinalIgnoreCase);
269272
}
270273
public override int GetHashCode()
271274
{
272-
return OutputHandle.FullName.GetHashCode();
275+
return OutputHandle.FullName.GetHashCode(StringComparison.OrdinalIgnoreCase);
273276
}
274277

275278
public override bool Equals(object? obj)

PandoraPlus/Patch/Patchers/Skyrim/Hkx/PackFileCache.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Pandora.Patch.Patchers.Skyrim.Hkx;
1010
public class PackFileCache
1111
{
12-
private Dictionary<string, PackFile> pathMap = new Dictionary<string, PackFile>();
12+
private Dictionary<string, PackFile> pathMap = new Dictionary<string, PackFile>(StringComparer.OrdinalIgnoreCase);
1313

1414

1515
public PackFile LoadPackFile(FileInfo file)

0 commit comments

Comments
 (0)