RimWorld
FemaleBodyVariants
Three 2025년 11월 1일 오전 7시 50분
Adding sub-variants for all body types (proof of concept)
I added a rudimentary but functional means to support adding variations for each body type.

As comments below indicate, it's like

[base]_east.png
[base]_VaRi-0001_east.png
[base]_VaRi-0002_east.png

And I suppose if a person wanted to reserve certain sub-variants to not be auto-assigned via the random mechanism, you could use like _VaRi_9001_east.png (skipping numbers).

There's also a terrible-but-functional hack where you can put a string at the end of the pawn's last name to force a specific sub-variant number be used.




(Hopefully this code formats OK)


using HarmonyLib; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.Windows; using Verse; namespace MultiBodyVariants { ////===================================================== //[StaticConstructorOnStartup] //public static class MyMod //{ // static MyMod() //our constructor // { // Log.Message("====== Hello World! (MultiBodyVariants)"); //Outputs "Hello World!" to the dev console. // } //}//class=============================================== //===================================================== [HarmonyPatch(typeof(PawnRenderNode_Body), "GraphicFor")] public static class PawnRenderNode_Body_GraphicFor_Patch { static Dictionary<string, int> num_subvariants = new Dictionary<string, int>(); // the serach is only performed once; result is cached here ////----------------------------- //static PawnRenderNode_Body_GraphicFor_Patch() //{ // Log.Message("MultiBodyVariants.PawnRenderNode_Body_GraphicFor_Patch static-ctor"); //}//func------------------------ //----------------------------- // path needs to be like "abc/def/Someone_Female_VaRi-0002_east" (the 'direction' part of the pawn-body-texture-name is necessary, but the .png must be excluded) static bool GraphicExists(string path) { var tex = ContentFinder<Texture2D>.Get(path, reportFailure: false); return tex != null; }//func------------------------ //----------------------------- static int GetNumSubvariants(string baseGraphicPath) { if (num_subvariants.ContainsKey(baseGraphicPath)) { //Log.Message("===using cached=" + num_subvariants[baseGraphicPath] + " sub-variants for " + baseGraphicPath); return num_subvariants[baseGraphicPath]; } int i = 1; while(true) { string graphicPath = baseGraphicPath + "_VaRi-" + i.ToString("D4"); //Log.Message("----checking for " + graphicPath); if (!GraphicExists(graphicPath + "_south")) { break; } i += 1; } //Log.Message("===found " + i + " sub-variants for " + baseGraphicPath); num_subvariants[baseGraphicPath] = i; return i; }//func------------------------ //----------------------------- static string StripSubvariantFromPath(in string graphicPath) { return Regex.Replace(graphicPath, "_VaRi-\\d{4}", ""); }//func------------------------ //----------------------------- static string GetSubvariantGraphicPath(in string graphicPath, ref Pawn pawn) { int num_subvariants = GetNumSubvariants(graphicPath); // The preferred way to choose which subvariant to use (for now, for simplicity/PoC) is to extract it from the pawn's current last name (not birthLastName). // As a fallback, select the subariant based on a hash the pawn's nickname + current-last-name. int i = 0; string currentLastName = pawn.Name?.ToStringFull?.Split(' ').Last() ?? ""; Match name_subvar = Regex.Match(currentLastName, "-Sv(\\d{1,4})"); //Log.Message("### Match " + currentLastName + " with \"-Sv(\\\\d{1,4})\" gave " + name_subvar.Success + ", " + name_subvar.ToString() + ", " + name_subvar.Groups[1] + "; pawn.LabelShort = " + pawn.LabelShort); if (name_subvar.Success) { string digits_str = name_subvar.Groups[1].Value; i = int.Parse(digits_str); } else { i = (pawn.LabelShort?.GetHashCode() ?? 0) + currentLastName.GetHashCode(); } int subvar_to_use = i % num_subvariants; if (subvar_to_use > 0) { return graphicPath + "_VaRi-" + subvar_to_use.ToString("D4"); } else { return graphicPath; } }//func------------------------ //----------------------------- [HarmonyPostfix] public static void Postfix(PawnRenderNode_Body __instance, ref Pawn pawn, ref Graphic __result) { // Adapted from FemaleBodyVariants: // Splits the unisex _Thin, _Fat, and _Hulk pawn-body-types to each have a _Female variant // (but we'll do this for specifically only females, rather than 'non-males', which would include Generder.None). // And we'll add a second suffix (_VaRi-0001, _VaRi-0002, etc) to the graphicPath for the additional variants. // Note that there is no _VaRi-0000 (instead, the base name is used without the _VaRi-0000). // (Note that these suffixes come before the final suffixes tacked on by the game: _north.png, _south.png, _east.png.) // So for example you might have .pngs in RimWorld\Mods\local_bods\Common\Textures\Things\Pawn\Humanlike\Bodies like: // Naked_Hulk_east.png // Naked_Hulk_VaRi-0001_east.png // Naked_Hulk_VaRi-0002_east.png // Naked_Hulk_north.png // Naked_Hulk_VaRi-0001_north.png // Naked_Hulk_VaRi-0002_north.png // Naked_Hulk_south.png // Naked_Hulk_VaRi-0001_south.png // Naked_Hulk_VaRi-0002_south.png // and like // Naked_Hulk_Female_south.png // Naked_Hulk_Female_VaRi-0001_south.png // if ((__result == null) || (pawn.Drawer.renderer.CurRotDrawMode == RotDrawMode.Dessicated) || (ModsConfig.AnomalyActive && pawn.IsMutant && !pawn.mutant.Def.bodyTypeGraphicPaths.NullOrEmpty()) || (ModsConfig.AnomalyActive && pawn.IsCreepJoiner && pawn.story.bodyType != null && !pawn.creepjoiner.form.bodyTypeGraphicPaths.NullOrEmpty()) || (pawn.story?.bodyType?.bodyNakedGraphicPath == null) ) { return; } bool enact = false; string oldGraphicPath = pawn.story.bodyType.bodyNakedGraphicPath; //Log.Message("### GraphicExists(" + graphicPath + "): " + GraphicExists(graphicPath)); // False //Log.Message("### GraphicExists(" + graphicPath + "_south): " + GraphicExists(graphicPath + "_south")); // True //Log.Message("### GraphicExists(" + graphicPath + "_south.png): " + GraphicExists(graphicPath + "_south.png")); // False // (paranoid?) if (oldGraphicPath != null) { string graphicPath = oldGraphicPath; if (pawn.gender == Gender.Female && !graphicPath.Contains("_Female") && (graphicPath.Contains("_Thin") || graphicPath.Contains("_Fat") || graphicPath.Contains("_Hulk"))) { graphicPath += "_Female"; enact = true; } graphicPath = StripSubvariantFromPath(graphicPath); graphicPath = GetSubvariantGraphicPath(graphicPath, ref pawn); if (oldGraphicPath != graphicPath) { enact = true; } //Log.Message("MultiBodyVariants.Postfix().2: enact=" + enact + ", path=" + graphicPath); if (enact) { Shader shader = __instance.ShaderFor(pawn); __result = GraphicDatabase.Get<Graphic_Multi>(graphicPath, shader, Vector2.one, __instance.ColorFor(pawn)); } } }//func------------------------ }//class=============================================== }//namespace################################################################### ¯\_(ツ)_/¯
< >
전체 댓글 1개 중 1~1개 표시 중
tiagocc0  [개발자] 2025년 11월 1일 오전 9시 40분 
very interesting, i don't know when i will have time to check it and add
if you feel like making a mod with this modification, you can go ahead
< >
전체 댓글 1개 중 1~1개 표시 중
페이지당 표시 개수: 1530 50