[Solved] How to override ThoughtUtility?

Started by JaxelT, August 16, 2016, 05:05:48 PM

Previous topic - Next topic

JaxelT

Hello all, this is my first mod, and I'm trying to add a new thought. It's supposed to trigger when a colonist kills a humanlike pawn who's hostile to the colony. However, I can't get the game to actually run the code that triggers it. I tried making my own ThoughtUtility, and it quickly became clear that it wasn't paying any attention to it. So then I tried overriding ThoughtUtility, and that didn't seem to do anything either. Here's my code. Anyone have any advice?


public static void GiveThoughtsForPawnDied(Pawn victim, DamageInfo? dinfo, Hediff hediff)
        {
            if (PawnGenerator.IsBeingGenerated(victim) || Current.ProgramState != ProgramState.MapPlaying)
                return;
            bool flag1 = dinfo.HasValue && dinfo.Value.Def == DamageDefOf.ExecutionCut || hediff != null && (hediff.def == HediffDefOf.Euthanasia || hediff.def == HediffDefOf.ShutDown);
            bool flag2 = victim.IsPrisonerOfColony && !PrisonBreakUtility.IsPrisonBreaking(victim) && !victim.InAggroMentalState;
            if (!victim.RaceProps.Humanlike)
                return;
            if (dinfo.HasValue && dinfo.Value.Def.externalViolence && dinfo.Value.Instigator != null)
            {
                Pawn pawn = dinfo.Value.Instigator as Pawn;
                if (pawn != null && !pawn.Dead && (pawn.needs.mood != null && pawn.story != null))
                {
                    pawn.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KilledHumanlikeBloodlust, (Pawn)null);
                    if (victim.Faction.HostileTo(pawn.Faction) && !flag1 && !flag2)
                        pawn.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOfAdrenaline.KilledHumanlikeEnemy, (Pawn)null);
                }
            }
            List<Pawn> allPawnsSpawned = Find.MapPawns.AllPawnsSpawned;
            for (int index = 0; index < allPawnsSpawned.Count; ++index)
            {
                Pawn p = allPawnsSpawned[index];
                if (p.needs.mood != null && !flag1 && (p.MentalStateDef != MentalStateDefOf.SocialFighting || ((MentalState_SocialFighting)p.MentalState).otherPawn != victim))
                {
                    if (victim.Spawned && (p.Position.InHorDistOf(victim.Position, 12f) && GenSight.LineOfSight(victim.Position, p.Position, false) && (p.Awake() && p.health.capacities.CapableOf(PawnCapacityDefOf.Sight))))
                    {
                        if (p.Faction == victim.Faction)
                            p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathAlly, (Pawn)null);
                        //else
                        //    p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathNonAlly, (Pawn)null);
                        if (p.relations.FamilyByBlood.Contains<Pawn>(victim))
                            p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathFamily, (Pawn)null);
                        p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathBloodlust, (Pawn)null);
                    }
                    else if (victim.Faction == Faction.OfPlayer && victim.Faction == p.Faction && victim.HostFaction != p.Faction)
                        p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KnowColonistDied, (Pawn)null);
                    if (flag2 && p.Faction == Faction.OfPlayer && !p.IsPrisoner)
                        p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KnowPrisonerDiedInnocent, (Pawn)null);
                }
            }
        }



namespace AdrenalineRush
{
    [DefOf]
    public static class ThoughtDefOfAdrenaline
    {
        public static ThoughtDef KilledHumanlikeEnemy;
    }
}

RawCode

In C# you may override only methods with "override" modifier, static methods cannot have such modifier.
You may need to read MSDN article about method modifiers to understand system fully.

Only way is using hooks, most "novice" friendly implementation is shipped with CCL modification, that can be found in "released mods" branch of forum.

JaxelT

#2
Quote from: RawCode on August 16, 2016, 09:56:28 PM
In C# you may override only methods with "override" modifier, static methods cannot have such modifier.
You may need to read MSDN article about method modifiers to understand system fully.

Only way is using hooks, most "novice" friendly implementation is shipped with CCL modification, that can be found in "released mods" branch of forum.

I know how to override and that you cannot do it on static classes. Visual Studio said as much. When I said I overrode ThoughtUtility, I meant I replaced it with my own version in the DLL, but RimWorld just seemed to ignore it.

So I have to use CCL for my purposes? Well, that's dismaying. A new Thought seems like it should be a very simple and basic thing to implement. Still, thanks for your help! I'll look into it.

e: Ah, I see, you're simply pointing me to CCL as an example of using hooks.

JaxelT

#3
OK, I have looked into the code for detour injecting in CCL and tried to adapt it to my own needs. It's a very confusing mess of C# and I have no idea what's actually necessary and what's not to inject something and where the spiderweb of dependencies ends. I've almost wholesale copied more than 3x as many classes as were in my mod from CCL to try and get it to inject my classes. Detours, DetourInjector, IInjector, SpecialInjector, MHD_SpecialInjectors, Data, ModHelperDef... Can someone who's already done this please lay out what specifically I have to do to get this to work? Because sifting through this undocumented, uncommented maze of programming definitely feels like more trouble than it's worth to try to get one simple Thought to trigger in the game.

e: After looking around the forums a bit and finding an example in another mod, I figured out how to use CCL to hook detours into the game. However, when I run it I get an error (a step in the right direction) and it tells me 'Cannot cast from source type to destination type.' Here's the code:


namespace AdrenalineRush.Detour
{
    internal static class _ThoughtUtility
    {
        internal static void _GiveThoughtsForPawnDied(Pawn victim, DamageInfo? dinfo, Hediff hediff)
        {
            if (PawnGenerator.IsBeingGenerated(victim) || Current.ProgramState != ProgramState.MapPlaying)
                return;
            bool flag1 = dinfo.HasValue && dinfo.Value.Def == DamageDefOf.ExecutionCut || hediff != null && (hediff.def == HediffDefOf.Euthanasia || hediff.def == HediffDefOf.ShutDown);
            bool flag2 = victim.IsPrisonerOfColony && !PrisonBreakUtility.IsPrisonBreaking(victim) && !victim.InAggroMentalState;
            if (!victim.RaceProps.Humanlike)
                return;
            if (dinfo.HasValue && dinfo.Value.Def.externalViolence && dinfo.Value.Instigator != null)
            {
                Pawn pawn = dinfo.Value.Instigator as Pawn;
                if (pawn != null && !pawn.Dead && (pawn.needs.mood != null && pawn.story != null))
                {
                    pawn.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KilledHumanlikeBloodlust, (Pawn)null);
                    if (victim.Faction.HostileTo(pawn.Faction) && !flag1 && !flag2)
                        pawn.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOfAdrenaline.KilledHumanlikeEnemy, (Pawn)null);
                }
            }
            List<Pawn> allPawnsSpawned = Find.MapPawns.AllPawnsSpawned;
            for (int index = 0; index < allPawnsSpawned.Count; ++index)
            {
                Pawn p = allPawnsSpawned[index];
                if (p.needs.mood != null && !flag1 && (p.MentalStateDef != MentalStateDefOf.SocialFighting || ((MentalState_SocialFighting)p.MentalState).otherPawn != victim))
                {
                    if (victim.Spawned && (p.Position.InHorDistOf(victim.Position, 12f) && GenSight.LineOfSight(victim.Position, p.Position, false) && (p.Awake() && p.health.capacities.CapableOf(PawnCapacityDefOf.Sight))))
                    {
                        if (p.Faction == victim.Faction)
                            p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathAlly, (Pawn)null);
                        //else
                        //    p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathNonAlly, (Pawn)null);
                        if (p.relations.FamilyByBlood.Contains<Pawn>(victim))
                            p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathFamily, (Pawn)null);
                        p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.WitnessedDeathBloodlust, (Pawn)null);
                    }
                    else if (victim.Faction == Faction.OfPlayer && victim.Faction == p.Faction && victim.HostFaction != p.Faction)
                        p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KnowColonistDied, (Pawn)null);
                    if (flag2 && p.Faction == Faction.OfPlayer && !p.IsPrisoner)
                        p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KnowPrisonerDiedInnocent, (Pawn)null);
                }
            }
        }
    }
}



using CommunityCoreLibrary;
using CommunityCoreLibrary.Controller;
using CommunityCoreLibrary.Detour;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Verse;

namespace AdrenalineRush
{
    public class DetourInjector : SpecialInjector
    {
        public override bool Inject()
        {
            MethodInfo method1 = typeof(ThoughtUtility).GetMethod("GiveThoughtsForPawnDied", BindingFlags.Static | BindingFlags.Public);
            MethodInfo method2 = typeof(Detour._ThoughtUtility).GetMethod("_GiveThoughtsForPawnDied", BindingFlags.Static | BindingFlags.NonPublic);
            if (!Detours.TryDetourFromTo(method1, method2))
            {
                return false;
            }
            return true;
        }
    }
}


e2: I did it! It works! Thanks, RawCode & skullywag.