RimWorld

RimWorld

RIMMSqol
This topic has been locked
Razuhl  [developer] Nov 20, 2024 @ 11:51am
corpse eating
The code requires the harmony reference. In e.g. visual studio you right click in the "solution explorer" on "references" and add a new one pointing at "...\Steam\steamapps\workshop\content\294100\2009463077\Current\Assemblies\0Harmony.dll" then select the new reference and make sure to set "local copy" to false(you do not wan't to include a copy of harmony in your mod). Referencing the steam version means it stays up to date automatically.

The code below can then be added to a new class file. Change the namespace name to your liking. Then the merhod "AllowCorpseEating" controls whether corpse eating will be allowed or not. Here is an example where the xenotype's id is checked. You can (probably) also use the tag lists in the pawn kind and add a tag "CorpseEater", that way any kind of unit can be allowed to eat corpses(e.g. all but the bug queen in your faction are ok with eating corpses).

The code itself is designed to leave as little impact as possible. The two base game methods "TryFindBestFoodSourceFor" and "BestFoodSourceOnMap" handle the corpse eating and already have a true/false parameter for it. The code simply injects instructions that replaces the original value with the one determined by "AllowCorpseEating". Then the original code is being executed.

using HarmonyLib; using RimWorld; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using System.Threading.Tasks; using Verse; namespace MyNamespace { [HarmonyPatch(typeof(FoodUtility))] [HarmonyPatch("TryFindBestFoodSourceFor")] static class FoodUtility_TryFindBestFoodSourceFor { static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr, ILGenerator gen) { //insert expression: allowCorpse = FoodUtility_TryFindBestFoodSourceFor.AllowCorpseEating(allowCorpse, eater); yield return new CodeInstruction(OpCodes.Ldarg_S, 9);//allowCorpse yield return new CodeInstruction(OpCodes.Ldarg_S, 1);//eater yield return new CodeInstruction(OpCodes.Call, typeof(FoodUtility_TryFindBestFoodSourceFor).GetMethod("AllowCorpseEating", BindingFlags.NonPublic | BindingFlags.Static)); yield return new CodeInstruction(OpCodes.Starg_S, 9);//allowCorpse //continue normally foreach (CodeInstruction ci in instr) { yield return ci; } } static bool AllowCorpseEating(bool allowCorpse, Pawn eater) { if (allowCorpse || eater.genes?.Xenotype?.defName == "MyXeno") return true; return false; } } [HarmonyPatch(typeof(FoodUtility))] [HarmonyPatch("BestFoodSourceOnMap")] static class FoodUtility_BestFoodSourceOnMap { static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr, ILGenerator gen) { //insert expression: allowCorpse = FoodUtility_TryFindBestFoodSourceFor.AllowCorpseEating(allowCorpse, eater); yield return new CodeInstruction(OpCodes.Ldarg_S, 7);//allowCorpse yield return new CodeInstruction(OpCodes.Ldarg_S, 1);//eater yield return new CodeInstruction(OpCodes.Call, typeof(FoodUtility_TryFindBestFoodSourceFor).GetMethod("AllowCorpseEating", BindingFlags.NonPublic | BindingFlags.Static)); yield return new CodeInstruction(OpCodes.Starg_S, 7);//allowCorpse //continue normally foreach (CodeInstruction ci in instr) { yield return ci; } } } }
< >
Showing 1-10 of 10 comments
Skaadi Nov 20, 2024 @ 12:59pm 
Oh this is fantastic! I can 100% do this. Thank you so SO much!
Skaadi Nov 21, 2024 @ 2:50am 
Welp. Excitement got the better of me. I got it to about 90% right, I think.
Did create a solution and then set the class file up. Visual studio showed me some errors at first but I realized, some references were missing. So I added Assembly-CSharp.dll and
UnityEngine.CoreModule.dll and that solved the errors. And of course harmony is also referenced. The game and Rimpy finds the mod just fine.

I changed the 'MyXeno' in the code to 'Troglodyte' but,it's not showing any effect in the game. Loaded up a troglodyte colony save I had to test it and they aren't going for corpses. Set up a new game to see if that might be required, sadly same result.

So now I am trying to figure out what I am missing. And I'm thinking I actually wouldn't mind if this applies regardless of xenotype provided this doesn't cause issues or makes this needlessly complicated.

I assume in-game food permission settings would be enough to avoid accidental corpse munching there. Where my understanding sadly ends right now is how to utilize the tags you mentioned. But I'll get there!
Razuhl  [developer] Nov 21, 2024 @ 11:22am 
Forgot to tell you that you also need to start harmony in your project. This is according to the harmony/rimworld wiki. Create a new class.

using RimWorld; using Verse; namespace MyNamespace { [StaticConstructorOnStartup] public static class HarmonyBootstrap { static HarmonyBootstrap() { var harmony = new Harmony("MyMod"); harmony.PatchAll(); } } }

You can add a line in "AllowCorpseEating" to see if the code get's executed.

Log.Message("Xenotype for corpse eating: "+eater.genes?.Xenotype?.defName);

In addition this turns corpses into regular food, their desireability might have to be modded. If you are using food rules than keep in mind that corpses are ingredients and will be forbidden in some rules.

The tags could be checked like this.

if (allowCorpse || eater.genes?.Xenotype?.defName == "MyXeno" || (myPawn.kindDef.weaponTags != null && myPawn.kindDef.weaponTags.Contains("CorpseEater"))) return true;

Then you can edit the weapon tags in the "pawn kind" category of rimmsqol(I think).
Skaadi Nov 22, 2024 @ 4:25am 
Okay, some progress! The harmony class made it execute the function and the Log line did work, however, it only showed 'Xenotype for corpse eating:' and 'Xenotype for corpse eating: Baseliner'

I am not sure why this mentions baseliner and returns null every other time the message popped up. I did name the xenotype 'Troglodyte' in the first part you gave me of the code.

I am running a custom xenotype in the game pieced together from various genes in the xenotype editor and named it Troglodyte (Like in the code). Is that causing issues in some way because it's not counting as a preset one?

I am also stumped on the last code part you sent there.

if (allowCorpse || eater.genes?.Xenotype?.defName == "MyXeno" || (myPawn.kindDef.weaponTags != null && myPawn.kindDef.weaponTags.Contains("CorpseEater"))) return true;

I think I am putting it in the right line in the code, but 'myPawn' has visual studio yell at me that 'The name 'myPawn' does not exist in the current context' I am not certain what it wants there.


On more familiar territory with rimmsqol, I am actually confused about weapon tags in 'pawn kind'. My first thought was to look up 'corpse' in the pawn kind category but as it happens corpse is not on that list. Tried a few other options such as 'dead' in the search bar but quickly realized I am not going to find corpses under pawn kind. And I am probably looking for the wrong thing to begin with. So what exactly would I be looking for there?


And I also really wanted to say thank you for bearing with me so far. I know dealing with beginners can be frustrating.
Razuhl  [developer] Nov 22, 2024 @ 12:56pm 
Custom xenotypes are no xenotypes. They are modeled by creating baseliners and then the modifications defined in the custom xenotype are applied. So if you do that it is the expected outcome. I think you would have to look at "eater.genes?.xenotypeName" instead of "eater.genes?.Xenotype?.defName".

Replace "myPawn" with "eater" that's the name of the pawn type variable you have in that method. It is defined as a parameter on the function.

Pawn kind are character classes, corpse has nothing to do with it. If you created a pirate faction you would define classes like captain, swabby or chef. Then the chef could be declared as a corpse eater and the others not. In the function you then check the "eater" not the corpse for whatever criteria defines them as a corpse eater(e.g. weapon tags).
Skaadi Nov 23, 2024 @ 2:43am 
Hm. I think I did everything according to what you wrote, But still no dice.
This is what the code looks like.

using HarmonyLib; using RimWorld; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using System.Threading.Tasks; using Verse; namespace CorpseEating { [HarmonyPatch(typeof(FoodUtility))] [HarmonyPatch("TryFindBestFoodSourceFor")] static class FoodUtility_TryFindBestFoodSourceFor { static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr, ILGenerator gen) { //insert expression: allowCorpse = FoodUtility_TryFindBestFoodSourceFor.AllowCorpseEating(allowCorpse, eater); yield return new CodeInstruction(OpCodes.Ldarg_S, 9);//allowCorpse yield return new CodeInstruction(OpCodes.Ldarg_S, 1);//eater yield return new CodeInstruction(OpCodes.Call, typeof(FoodUtility_TryFindBestFoodSourceFor).GetMethod("AllowCorpseEating", BindingFlags.NonPublic | BindingFlags.Static)); yield return new CodeInstruction(OpCodes.Starg_S, 9);//allowCorpse //continue normally foreach (CodeInstruction ci in instr) { yield return ci; } } static bool AllowCorpseEating(bool allowCorpse, Pawn eater) { if(allowCorpse || eater.genes?.xenotypeName == "Troglodyte" || (eater.kindDef.weaponTags != null && eater.kindDef.weaponTags.Contains("CorpseEater"))) return true; return false; } } [HarmonyPatch(typeof(FoodUtility))] [HarmonyPatch("BestFoodSourceOnMap")] static class FoodUtility_BestFoodSourceOnMap { static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr, ILGenerator gen) { //insert expression: allowCorpse = FoodUtility_TryFindBestFoodSourceFor.AllowCorpseEating(allowCorpse, eater); yield return new CodeInstruction(OpCodes.Ldarg_S, 7);//allowCorpse yield return new CodeInstruction(OpCodes.Ldarg_S, 1);//eater yield return new CodeInstruction(OpCodes.Call, typeof(FoodUtility_TryFindBestFoodSourceFor).GetMethod("AllowCorpseEating", BindingFlags.NonPublic | BindingFlags.Static)); yield return new CodeInstruction(OpCodes.Starg_S, 7);//allowCorpse //continue normally foreach (CodeInstruction ci in instr) { yield return ci; } } } }

Harmony is applied in a seperate class, as instructed. And I did check with character editor what class my colonists have. Turns out it's just 'colonist'. So I gave every single 'colonist' entry in the RIMMSQOL pawn kind category the 'CorpseEater' tag under weapon tags, just to be sure. The other entries are from mods such as Big & Small.

https://ibb.co/z7SCTsP

The only thing I can think of now, aside from me just generally having done something wrong in the code, is that the current colony having some modded faction is preventing this from working?

I'm going to check this real quick, make a default colony, slap the xenotype on a colonist and see how they behave.
Last edited by Skaadi; Nov 23, 2024 @ 3:39am
Skaadi Nov 23, 2024 @ 3:39am 
Negative. Default colony faction made no difference.
Razuhl  [developer] Nov 23, 2024 @ 8:10am 
Works fine, remember you need to edit the corpses food preferability(possibly food category too). By default it is undesireable. Most corpse types are seperat as well - like alpaca or muffalo are different objects that need to be edited.

You can either do that in rimmsqol or you will have to write another harmony patch that edit's corpses as they are generated. For example it could look at the fleshType of the corpse and make flesh based corpses more attractive while metal corpses are left alone.
Skaadi Nov 24, 2024 @ 12:12am 
Yes! That's it! That was the missing magic! Edited human corpses in RIMMSQOL and the troglodytes are nomming on dead raiders!

I am absolutely fine to edit the corpses individually in RIMMSQOL.
Holy cow, this is awesome! I could hug you. Thank you so much for the help!
Razuhl  [developer] Nov 26, 2024 @ 9:33pm 
Good job seeing it through, quite the acomplishment for a beginner. Hopefully it opens some doors for you with future ideas.
< >
Showing 1-10 of 10 comments
Per page: 1530 50