Harmony Help

Started by Ziehn, May 25, 2018, 11:14:08 PM

Previous topic - Next topic

Ziehn

I have a fairly poor working knowledge of C# just enough to read and edit existing code, so learning how to deal with Harmony is being a bit challenging. I've looked at many examples and they all seem to use different methods so what i got is probably a hot mess. After many hours of doing my best impression of a monkey on a typewriter this is what i got  :-[
using Harmony;
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Verse;
using RimWorld;

namespace More_Slaves
{
    [StaticConstructorOnStartup]
    public class Main
    {
        static Main()
        {
            var harmony = HarmonyInstance.Create("com.More_Slaves");
            harmony.PatchAll(Assembly.GetExecutingAssembly());
        }

        [HarmonyPatch(typeof(StockGenerator), "More_Slaves", null)]
        [HarmonyPatch("More_Slaves")]
        [HarmonyPatch(new Type[] { typeof(StockGenerator_Slaves) })]
        public class StockGenerator_Slaves_Patch
        {
            public class StockGenerator_Slaves : StockGenerator
            {
                public override IEnumerable<Thing> GenerateThings(int forTile)
                {   //
                    //Trying to Change this so traders will ignore population restrictions.
                    //
                    if (Rand.Value <= Find.Storyteller.intenderPopulation.PopulationIntent)
                    //
                    //
                    //
                    {
                        yield break;
                    }
                    int count = this.countRange.RandomInRange;
                    for (int i = 0; i < count; i++)
                    {
                        Faction slaveFaction;
                        if (!(from fac in Find.FactionManager.AllFactionsVisible
                              where fac != Faction.OfPlayer && fac.def.humanlikeFaction
                              select fac).TryRandomElement(out slaveFaction))
                        {
                            yield break;
                        }
                        PawnKindDef slave = PawnKindDefOf.Slave;
                        Faction faction = slaveFaction;
                        PawnGenerationRequest request = new PawnGenerationRequest(slave, faction, PawnGenerationContext.NonPlayer, forTile, false, false, false, false, true, false, 1f, !this.trader.orbital, true, true, false, false, false, false, null, null, null, null, null, null, null);
                        yield return PawnGenerator.GeneratePawn(request);
                    }
                    yield break;
                }

                public override bool HandlesThingDef(ThingDef thingDef)
                {
                    return thingDef.category == ThingCategory.Pawn && thingDef.race.Humanlike && thingDef.tradeability != Tradeability.Never;
                }
            }
        }

    }
}








jamaicancastle

Harmony is designed to replace methods of classes, not entire classes. In this case, StockGenerator_Slaves is the class, GenerateThings is the method.

With that in mind:
- You have a bunch of contradictory annotations (the things in [] brackets). For most purposes, you need only a single annotation that runs like so:
[HarmonyPatch(typeof(ClassToPatch), "NameOfMethodToPatch")]
(There are other annotations for a few specific cases, like when there are multiple methods with the same name - they're not relevant here, though.)
- Inside your patch class, it will look for Prefix(), Postfix(), and/or Transpile() methods by name. These denote how the patch is applied to the method. (Again, there are some other options here but you won't normally need them.)
- Prefix(), as the name implies, makes a patch that happens before the target method. Prefix can be either void or bool. If it's bool and returns false, the patch stops the original method and any subsequent patches from firing.
- Postfix() is always void. It happens after the original method finishes, but before whatever called the method gets any return value.
- Both Prefix() and Postfix() can take the following parameters (all optional):
-- Any of the original method's parameters, by their type and name.
-- The object the original method acted on, if it's not static, by its type and the name __instance.
-- The return value of the original method, if it's not void, by its type and the name __result. If you change this value, the function that called the method will get the new result.
-- Both __instance and __result should be passed by reference, e.g. "ref string __result" for a method that returns a string.
- Transpile() is more complicated, but it essentially lets you target a specific instruction inside a method and change it.

Essentially, there are three approaches you can take here:
- The Right Thing is to transpile StockGenerator_Slaves.GenerateThings() to take that specific "if" instruction and skip it. However, this does require some more theoretical knowledge of C# so I don't know if you want to get into it right now.
- The easy thing is probably to avoid Harmony altogether: write a new stockgenerator that's basically like the old one without the pop intent check. Then change the trader definitions in XML to use the new generator instead of the old one. (This could be done fairly easily with xpath patching; in fact, you could probably write a single patch that would find all instances of StockGenerator_Slaves, wherever they are, and change them at once.)
- The third method is to use a prefix. In this case, your prefix will be basically the whole original method, minus the if statement, and will return false so that the original doesn't run. (Detouring like this is disfavored for a number of reasons, but for your own personal use it's fine, and it's easier to learn. Just know that it can cause problems down the line.)

Ziehn

Thank you so much for your help, didn't think to just use xpathing with a new stockgenerator. Works great now  ;D