[Solved] Troubleshooting Error in my Mod

Started by LegendaryMinuteman, January 03, 2020, 04:04:58 AM

Previous topic - Next topic

LegendaryMinuteman

My Planetside Politics (and/or Religions) mod has been up for quite a while and I while receiving occasional reports of errors I initially attributed them to mod conflicts until I eventually encountered the error.  I've since narrowed down the issue to the harmony patch and specifically to the PawnKindDef type SpaceRefugee.

The harmony patch is used to check when a pawn is created whether or not it should get a specific political or religious trait and it works for tribes people, pirates, outlanders, ancient marines, in fact it works in every instance except for when generating Space Refugees.  I'm at a loss for understanding what is the cause of the error or parsing the language of the debugger.

The debug log error report looks like this:

RimWorld 1.0.2408 rev747
Verse.Log:Message(String, Boolean)
RimWorld.VersionControl:LogVersionNumber()
Verse.Root:CheckGlobalInit()
Verse.Root:Start()
Verse.Root_Entry:Start()

Loading game from file Autosave-2 with mods Core, PPR Backup
Verse.Log:Message(String, Boolean)
Verse.SavedGameLoaderNow:LoadGameFromSaveFileNow(String)
Verse.Root_Play:<Start>m__0()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__1()

Error while generating pawn. Rethrowing. Exception: System.NullReferenceException: Object reference not set to an instance of an object
at PoliticsandReligions.HarmonyPatches/Patch_GainTrait.Prefix (RimWorld.TraitSet,Verse.Pawn,RimWorld.Trait) <0x00166>
at (wrapper dynamic-method) RimWorld.TraitSet.GainTrait_Patch1 (object,RimWorld.Trait) <0x00026>
at Verse.PawnGenerator.GenerateTraits (Verse.Pawn,Verse.PawnGenerationRequest) <0x00a56>
at Verse.PawnGenerator.TryGenerateNewPawnInternal (Verse.PawnGenerationRequest&,string&,bool,bool) <0x0066d>
at Verse.PawnGenerator.GenerateNewPawnInternal (Verse.PawnGenerationRequest&) <0x003b0>
at Verse.PawnGenerator.GenerateOrRedressPawnInternal (Verse.PawnGenerationRequest) <0x007bb>
at Verse.PawnGenerator.GeneratePawn (Verse.PawnGenerationRequest) <0x0009b>

Verse.Log:Error(String, Boolean)
Verse.PawnGenerator:GeneratePawn(PawnGenerationRequest)
Verse.PawnGenerator:GeneratePawn(PawnKindDef, Faction)
Verse.<DoListingItems_MapTools>c__AnonStorey36:<>m__0()
Verse.DebugTool:DebugToolOnGUI()
Verse.DebugTools:DebugToolsOnGUI()
RimWorld.UIRoot_Play:UIRootOnGUI()
Verse.Root:OnGUI()

Root level exception in OnGUI(): System.NullReferenceException: Object reference not set to an instance of an object
at PoliticsandReligions.HarmonyPatches/Patch_GainTrait.Prefix (RimWorld.TraitSet,Verse.Pawn,RimWorld.Trait) <0x00166>
at (wrapper dynamic-method) RimWorld.TraitSet.GainTrait_Patch1 (object,RimWorld.Trait) <0x00026>
at Verse.PawnGenerator.GenerateTraits (Verse.Pawn,Verse.PawnGenerationRequest) <0x00a56>
at Verse.PawnGenerator.TryGenerateNewPawnInternal (Verse.PawnGenerationRequest&,string&,bool,bool) <0x0066d>
at Verse.PawnGenerator.GenerateNewPawnInternal (Verse.PawnGenerationRequest&) <0x003b0>
at Verse.PawnGenerator.GenerateOrRedressPawnInternal (Verse.PawnGenerationRequest) <0x007bb>
at Verse.PawnGenerator.GeneratePawn (Verse.PawnGenerationRequest) <0x0009b>

Verse.Log:Error(String, Boolean)
Verse.Root:OnGUI()


The harmony patch looks like this:
using Harmony;
using RimWorld;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Verse;

namespace PoliticsandReligions
{
    [StaticConstructorOnStartup]

    internal static class HarmonyPatches

    {
        private static HarmonyInstance harmony = HarmonyInstance.Create("PoliticsandReligionsMod");

        static HarmonyPatches()
        {
            HarmonyPatches.harmony.PatchAll(Assembly.GetExecutingAssembly());
        }

        [HarmonyPatch(typeof(TraitSet), "GainTrait")]
        private static class Patch_GainTrait
        {
            [HarmonyPriority(Priority.First)]
            private static bool Prefix(TraitSet __instance, Pawn ___pawn, Trait trait)
            {

                if (trait.def is TraitDef_PoliticsTrait)
                {
                    TraitDef_PoliticsTrait traitPoliticsTrait = trait.def as TraitDef_PoliticsTrait;
                    if (traitPoliticsTrait.isTribalIdeology == false)
                    {
                        if (___pawn.Faction.def.techLevel == RimWorld.TechLevel.Neolithic)
                        {
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else if (traitPoliticsTrait.isPirateIdeology == false)
                    {
                         if (___pawn.Faction.def.defName == "Pirate")
                         {
                            return false;
                         }
                         else
                         {
                            return true;
                         }
                    }
                    else
                    {
                        return true;
                    }
                }
                else if (trait.def is TraitDef_ReligionsTrait)
                {
                    TraitDef_ReligionsTrait traitReligionsTrait = trait.def as TraitDef_ReligionsTrait;
                    if (traitReligionsTrait.isExotheology == true)
                    {
                        if (___pawn.Faction.def.techLevel == RimWorld.TechLevel.Neolithic)
                        {
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else
                    {
                        if (___pawn.Faction.def.defName == "PlayerColony")
                        {
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    }
                }
                else
                {
                    return true;
                }

            }

        }

    }

}


I can deduce from the "object reference not set to an instance of an object" that that is the crux of the error but I don't understand why it only occurs in this specific reference and what can be done about it when it is otherwise functioning correctly.

Any help resolving this issue would be greatly appreciated.

K

Just from reading your code I'd guess that the error stems from the various "___pawn.Faction" accesses you make. The only other thing that could cause null refs would be the traitPoliticsTrait, but pattern matching should assure that isn't null before anything is done with it. To defend against such errors ensure that the pawn has a faction that isn't null before trying to do something with that faction.

Also, totally unrelated to your error but the is operator can be used to make your code a little more streamlined if you replace the code in your check with this:
if(trait.def is TraitDef_PoliticsTrait traitPoliticsTrait)
This way the "is" does both the check and the cast, removing the need for another line after which does the cast.

LegendaryMinuteman

Thanks for taking a look at the code and your advice on how to stream line it which worked nicely.  Can you elaborate on what you said about the traitPoliticsTrait possibly causing a null ref?  I took your advice and added a conditional statement to wrap the entire code.  You can see I even tried to brute force around the SpaceRefugee PawnKindDef by excluding them from the entire process but it doesn't seem to work.  This leads me to believe the faction was not the issue unless I'm still missing something in the code.
[HarmonyPriority(Priority.First)]
            private static bool Prefix(TraitSet __instance, Pawn ___pawn, Trait trait)
            {
                if ((trait.def != null) && (___pawn.Faction != null) && (___pawn.kindDef.defName != "SpaceRefugee"))
                {
                    if (trait.def is TraitDef_PoliticsTrait traitPoliticsTrait)
                    {
                        if (traitPoliticsTrait.isTribalIdeology == false)
                        {
                            if (___pawn.Faction.def.techLevel == RimWorld.TechLevel.Neolithic)
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else if (traitPoliticsTrait.isPirateIdeology == false)
                        {
                            if (___pawn.Faction.def.defName == "Pirate")
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else if (trait.def is TraitDef_ReligionsTrait traitReligionsTrait)
                    {
                        if (traitReligionsTrait.isExotheology == true)
                        {
                            if (___pawn.Faction.def.techLevel == RimWorld.TechLevel.Neolithic)
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else
                        {
                            if (___pawn.Faction.def.defName == "PlayerColony")
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                    }
                    else
                    {
                        return true;
                    }

                }
                else
                {
                    return true;
                }

            }

K

If the faction isn't null then the only other thing that could be null is the tech level I guess. Try checking if it's null before accessing it.

Unless some of the fields of your politics & religion traits can be null, but I assume you've checked for that already?

LegendaryMinuteman

I'm really at a loss.  I've added null checks as conditional statements before everything and it still fails.  Visual Studio tells me that it is impossible for the tech level to be null and even leaving in the null check regardless it still comes up empty.  I'm not sure how any of my traits could produce a null as every one has the requisite fields.  It's a strange case.

Thank you for your help in any event.

LWM

Another thing you can do is add LOTS of Log.Message/Warning/Error trace to your prefix: like, before every line ;)  It will show up in the debug log right up until the null reference exception is thrown.

Also, are you sure that all the things passed in are non-null?  __pawn, trait, etc?  __instance probably is, right?  (not a static function, etc)  You can have debug trace print out their values too...

LegendaryMinuteman

#6
Thank you for this, I thought about doing constant debug traces in C# but wasn't sure about the syntax.

I really did try to put a != null if condition before every single call on a variable but I'll go over it again with debug traces and see if I can't find the issue.

LWM

\
[HarmonyPriority(Priority.First)]
            private static bool Prefix(TraitSet __instance, Pawn ___pawn, Trait trait)
            {
                if (__pawn != null) Log.Error("Prefix called for Pawn "+__pawn+" with TraitSet "+(__instance!=null?__instance:"null traitset")+"  and trait of "+(trait!=null?trait:" null trait"));
                if ((trait.def != null) && (___pawn.Faction != null) && (___pawn.kindDef.defName != "SpaceRefugee"))
                {
                    if (trait.def is TraitDef_PoliticsTrait traitPoliticsTrait)
                    {
                       Log.Message("Trait is traitPoliticsTrait");  //etc ad nauseum.  But it works.
                        if (traitPoliticsTrait.isTribalIdeology == false)
                        {
                            if (___pawn.Faction.def.techLevel == RimWorld.TechLevel.Neolithic)
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else if (traitPoliticsTrait.isPirateIdeology == false)
                        {
                            if (___pawn.Faction.def.defName == "Pirate")
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else if (trait.def is TraitDef_ReligionsTrait traitReligionsTrait)
                    {
                        if (traitReligionsTrait.isExotheology == true)
                        {
                            if (___pawn.Faction.def.techLevel == RimWorld.TechLevel.Neolithic)
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else
                        {
                            if (___pawn.Faction.def.defName == "PlayerColony")
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                    }
                    else
                    {
                        return true;
                    }

                }
                else
                {
                    return true;
                }

            }

LegendaryMinuteman

I was updating the mod for 1.1 and decided on a whim to take one more crack at solving this issue despite being unsuccessful in all my prior efforts.

I reverted to the original code and modified the following bit:
if (trait.def is TraitDef_PoliticsTrait traitPoliticsTrait)
to the new version:
  if (trait.def is TraitDef_PoliticsTrait traitPoliticsTrait && ___pawn.kindDef.defName != "SpaceRefugee")

That apparently did the trick.  Just wanted to update the thread to show that the issue was solved and say thanks again for the suggestions I received in the past.  Thanks again.