Files
ArachnaeSwarm/Source/ArachnaeSwarm/FacialAnimation/FaceAnimationStaticGenerator.cs
2025-10-22 23:06:35 +08:00

238 lines
9.1 KiB
C#

using RimWorld;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Verse;
namespace ArachnaeSwarm
{
[StaticConstructorOnStartup]
public static class FaceAnimationFileGenerator
{
private static readonly string OutputDirectory = "1.6/1.6/GeneratedDefs/FacialAnimation/";
static FaceAnimationFileGenerator()
{
GenerateAllFaceAnimationFiles();
}
private static void GenerateAllFaceAnimationFiles()
{
try
{
// 获取模组根目录
string modRootDir = GetModRootDirectory();
if (string.IsNullOrEmpty(modRootDir))
{
Log.Error("Cannot find mod root directory");
return;
}
// 确保输出目录存在
string fullOutputDir = Path.Combine(modRootDir, OutputDirectory);
Directory.CreateDirectory(fullOutputDir);
// 配置:种族和对应的抽象动画路径
var raceConfigs = new Dictionary<string, List<string>>
{
{
"ArachnaeNode_Race_Myrmecocystus",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_ShieldHead",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_WeaponSmith",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_Fighter",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_Facehugger",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_Smokepop",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_NeuroSwarm",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_Praetorian",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
},
{
"ArachnaeNode_Race_Skyraider",
new List<string> { "1.6/1.6/Defs/AnimationDefs/FacialAnimation/ARA_JobAnim.xml" }
}
};
int totalFilesGenerated = 0;
foreach (var config in raceConfigs)
{
string raceDefName = config.Key;
List<string> paths = config.Value;
// 检查种族是否存在
if (DefDatabase<ThingDef>.GetNamedSilentFail(raceDefName) == null)
{
Log.Warning($"Race {raceDefName} not found, skipping face animation generation");
continue;
}
foreach (string relativePath in paths)
{
string fullSourcePath = Path.Combine(modRootDir, relativePath);
totalFilesGenerated += GenerateAnimationFilesForRace(fullSourcePath, raceDefName, fullOutputDir);
}
}
Log.Message($"Generated {totalFilesGenerated} face animation files in {fullOutputDir}");
}
catch (Exception ex)
{
Log.Error($"Error in FaceAnimationFileGenerator: {ex}");
}
}
/// <summary>
/// 获取模组根目录
/// </summary>
private static string GetModRootDirectory()
{
try
{
// 通过查找当前程序集所在的模组来获取根目录
var currentMod = LoadedModManager.RunningMods.FirstOrDefault(mod =>
mod.assemblies.loadedAssemblies.Contains(typeof(FaceAnimationFileGenerator).Assembly));
if (currentMod != null)
{
return currentMod.RootDir;
}
// 备选方法:通过反射获取
var assembly = typeof(FaceAnimationFileGenerator).Assembly;
string assemblyLocation = assembly.Location;
if (!string.IsNullOrEmpty(assemblyLocation))
{
// 向上查找直到找到模组根目录
DirectoryInfo dir = new FileInfo(assemblyLocation).Directory;
while (dir != null)
{
if (File.Exists(Path.Combine(dir.FullName, "About.xml")) ||
File.Exists(Path.Combine(dir.FullName, "LoadFolders.xml")))
{
return dir.FullName;
}
dir = dir.Parent;
}
}
Log.Error("Cannot determine mod root directory");
return null;
}
catch (Exception ex)
{
Log.Error($"Error getting mod root directory: {ex}");
return null;
}
}
private static int GenerateAnimationFilesForRace(string sourcePath, string raceDefName, string outputDir)
{
int filesGenerated = 0;
try
{
// 检查源文件是否存在
if (!File.Exists(sourcePath))
{
Log.Warning($"Source animation file not found: {sourcePath}");
return 0;
}
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(sourcePath);
XmlNodeList abstractDefNodes = xmlDoc.SelectNodes("//FacialAnimation.FaceAnimationDef[@Abstract='True']");
if (abstractDefNodes == null || abstractDefNodes.Count == 0)
{
Log.Message($"No abstract FaceAnimationDef found in: {sourcePath}");
return 0;
}
// 为每个种族创建单独的文件
string outputFile = Path.Combine(outputDir, $"ARA_{raceDefName}_JobAnim.xml");
using (StreamWriter writer = new StreamWriter(outputFile, false, Encoding.UTF8))
{
writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
writer.WriteLine("<Defs>");
foreach (XmlNode abstractNode in abstractDefNodes)
{
string concreteXml = GenerateConcreteAnimationXml(abstractNode, raceDefName);
if (!string.IsNullOrEmpty(concreteXml))
{
writer.WriteLine(concreteXml);
filesGenerated++;
}
}
writer.WriteLine("</Defs>");
}
Log.Message($"Generated {filesGenerated} animations for {raceDefName} in {outputFile}");
}
catch (Exception ex)
{
Log.Error($"Error generating animation files for race {raceDefName}: {ex}");
}
return filesGenerated;
}
private static string GenerateConcreteAnimationXml(XmlNode abstractNode, string raceDefName)
{
try
{
string abstractName = abstractNode.Attributes?["Name"]?.Value;
if (string.IsNullOrEmpty(abstractName))
{
Log.Warning("Abstract FaceAnimationDef has no Name attribute");
return null;
}
string concreteDefName = $"{raceDefName}_{abstractName.Replace("ARA_", "")}";
// 创建XML字符串
StringBuilder xmlBuilder = new StringBuilder();
xmlBuilder.AppendLine($" <FacialAnimation.FaceAnimationDef ParentName=\"{abstractName}\" MayRequire=\"Nals.FacialAnimation\">");
xmlBuilder.AppendLine($" <defName>{concreteDefName}</defName>");
xmlBuilder.AppendLine($" <raceName>{raceDefName}</raceName>");
xmlBuilder.Append(" </FacialAnimation.FaceAnimationDef>");
return xmlBuilder.ToString();
}
catch (Exception ex)
{
Log.Error($"Error generating XML for {raceDefName}: {ex}");
return null;
}
}
}
}