Errors when using PatchOperationInsert to add <li> links to buildings

Started by 5thHorseman, August 01, 2018, 11:47:58 PM

Previous topic - Next topic

5thHorseman

For my mod Toolboxifier, I have found that I have to sometimes add and sometimes insert into the buildings the code I need. In some cases, I do something like this to add references to items that have a "comps" section, but no "CompProperties_AffectedByFacilities" within it. This works fine.

<Operation Class="PatchOperationAdd">
<xpath>*/ThingDef[defName = "ElectricStove"]/comps</xpath>
<value>
<li Class="CompProperties_AffectedByFacilities">
<linkableFacilities>
<li>Stool</li>
<li>DiningChair</li>
<li>Armchair</li>
<li>PowerConduit</li>
<li>WaterproofConduit</li>
<li>PowerSwitch</li>
</linkableFacilities>
</li>
</value>
</Operation>


Sometimes though the buildings have no comps section, so I have to insert it. I do this and it works fine as well:

<Operation Class="PatchOperationInsert">
<xpath>*/ThingDef[defName = "Brewery"]/description</xpath>
<value>
<comps>
<li Class="CompProperties_AffectedByFacilities">
<linkableFacilities>
<li>Stool</li>
<li>DiningChair</li>
<li>Armchair</li>
<li>FermentingBarrel</li>
</linkableFacilities>
</li>
</comps>
</value>
</Operation>


Where I have trouble is for those buildings that have a comps section, and within that comps section already have a "CompProperties_AffectedByFacilities" li with a linkableFacilities section. I do this currently:

<Operation Class="PatchOperationInsert">
<xpath>*/ThingDef[defName = "CraftingSpot"]/comps/li/linkableFacilities/li</xpath>
<value>
<li>Armchair</li>
<li>DiningChair</li>
<li>Stool</li>
<li>ButcherSpot</li>
</value>
</Operation>


This actually works. It does what I want. However, I get tons of errors, one per building that exists in the world, upon loading a save. Also, whenever one of the buildings is created (either by the pawns building it or by my creating one in god mode) I get another error.

Here's an error on save load:

Exception spawning loaded thing CraftingSpot54573: System.NullReferenceException: Object reference not set to an instance of an object
  at RimWorld.CompAffectedByFacilities.CanPotentiallyLinkTo_Static (Verse.ThingDef facilityDef, IntVec3 facilityPos, Rot4 facilityRot, Verse.ThingDef myDef, IntVec3 myPos, Rot4 myRot) [0x00008] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:113
  at RimWorld.CompAffectedByFacilities.CanPotentiallyLinkTo_Static (Verse.Thing facility, Verse.ThingDef myDef, IntVec3 myPos, Rot4 myRot) [0x00016] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:56
  at RimWorld.CompAffectedByFacilities+<PotentialThingsToLinkTo>c__Iterator1.MoveNext () [0x001b3] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:384
  at RimWorld.CompAffectedByFacilities+<>c__Iterator0.MoveNext () [0x00100] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:346
  at RimWorld.CompAffectedByFacilities.LinkToNearbyFacilities () [0x00051] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:580
  at RimWorld.CompAffectedByFacilities.PostSpawnSetup (Boolean respawningAfterLoad) [0x00002] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:291
  at Verse.ThingWithComps.SpawnSetup (Verse.Map map, Boolean respawningAfterLoad) [0x0002a] in C:\Dev\RimWorld\Assets\Scripts\Verse\Thing\ThingWithComps.cs:231
  at Verse.Building.SpawnSetup (Verse.Map map, Boolean respawningAfterLoad) [0x00020] in C:\Dev\RimWorld\Assets\Scripts\Verse\Thing\Building.cs:55
  at RimWorld.Building_WorkTable.SpawnSetup (Verse.Map map, Boolean respawningAfterLoad) [0x00004] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\Thing\Building\WorkTable\Building_WorkTable.cs:49
  at Verse.GenSpawn.Spawn (Verse.Thing newThing, IntVec3 loc, Verse.Map map, Rot4 rot, WipeMode wipeMode, Boolean respawningAfterLoad) [0x0027d] in C:\Dev\RimWorld\Assets\Scripts\Verse\Utility\Gen\Placement\GenSpawn.cs:105
  at Verse.GenSpawn.SpawnBuildingAsPossible (Verse.Building building, Verse.Map map, Boolean respawningAfterLoad) [0x0013c] in C:\Dev\RimWorld\Assets\Scripts\Verse\Utility\Gen\Placement\GenSpawn.cs:184
  at Verse.Map.FinalizeLoading () [0x00181] in C:\Dev\RimWorld\Assets\Scripts\Verse\Map\Map.cs:443
Verse.Log:Error(String, Boolean) (at C:\Dev\RimWorld\Assets\Scripts\Verse\Utility\Debug\Log\Log.cs:78)
Verse.Map:FinalizeLoading() (at C:\Dev\RimWorld\Assets\Scripts\Verse\Map\Map.cs:447)
Verse.Game:LoadGame() (at C:\Dev\RimWorld\Assets\Scripts\Verse\Game\Game.cs:478)
Verse.SavedGameLoaderNow:LoadGameFromSaveFileNow(String) (at C:\Dev\RimWorld\Assets\Scripts\Verse\Map\MapIniter\SavedGameLoaderNow.cs:39)
Verse.Root_Play:<Start>m__0() (at C:\Dev\RimWorld\Assets\Scripts\Verse\Global\Root\Root_Play.cs:46)
Verse.LongEventHandler:RunEventFromAnotherThread(Action) (at C:\Dev\RimWorld\Assets\Scripts\Verse\Global\LongEventHandler.cs:455)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__1() (at C:\Dev\RimWorld\Assets\Scripts\Verse\Global\LongEventHandler.cs:367)


And here's an error on placing:

Exception in UIRootUpdate: System.NullReferenceException: Object reference not set to an instance of an object
  at RimWorld.CompAffectedByFacilities.CanPotentiallyLinkTo_Static (Verse.ThingDef facilityDef, IntVec3 facilityPos, Rot4 facilityRot, Verse.ThingDef myDef, IntVec3 myPos, Rot4 myRot) [0x00008] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:113
  at RimWorld.CompAffectedByFacilities.CanPotentiallyLinkTo_Static (Verse.Thing facility, Verse.ThingDef myDef, IntVec3 myPos, Rot4 myRot) [0x00016] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:56
  at RimWorld.CompAffectedByFacilities+<PotentialThingsToLinkTo>c__Iterator1.MoveNext () [0x001b3] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:384
  at RimWorld.CompAffectedByFacilities.DrawLinesToPotentialThingsToLinkTo (Verse.ThingDef myDef, IntVec3 myPos, Rot4 myRot, Verse.Map map) [0x00040] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\ThingComps\CompAffectedByFacilities.cs:408
  at RimWorld.PlaceWorker_ShowFacilitiesConnections.DrawGhost (Verse.ThingDef def, IntVec3 center, Rot4 rot, Color ghostCol) [0x00020] in C:\Dev\RimWorld\Assets\Scripts\Verse\Map\PlaceWorker\PlaceWorkers_Various.cs:374
  at Verse.GhostDrawer.DrawGhostThing (IntVec3 center, Rot4 rot, Verse.ThingDef thingDef, Verse.Graphic baseGraphic, Color ghostCol, AltitudeLayer drawAltitude) [0x00097] in C:\Dev\RimWorld\Assets\Scripts\Verse\UI\Utility\GhostDrawer.cs:31
  at RimWorld.Designator_Place.DrawGhost (Color ghostCol) [0x0001b] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\UI\Designators\Place\Designator_Place.cs:131
  at RimWorld.Designator_Place.SelectedUpdate () [0x00067] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\UI\Designators\Place\Designator_Place.cs:116
  at RimWorld.Designator_Build.SelectedUpdate () [0x00002] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\UI\Designators\Place\Designator_Build.cs:317
  at Verse.DesignatorManager.DesignatorManagerUpdate () [0x0001d] in C:\Dev\RimWorld\Assets\Scripts\Verse\UI\Designator\DesignatorManager.cs:125
  at RimWorld.MapInterface.MapInterfaceUpdate () [0x0006f] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\UI\MapInterface.cs:197
  at RimWorld.UIRoot_Play.UIRootUpdate () [0x00036] in C:\Dev\RimWorld\Assets\Scripts\RimWorld\UI\UIRoot_Play.cs:159
Verse.Log:Error(String, Boolean) (at C:\Dev\RimWorld\Assets\Scripts\Verse\Utility\Debug\Log\Log.cs:78)
RimWorld.UIRoot_Play:UIRootUpdate() (at C:\Dev\RimWorld\Assets\Scripts\RimWorld\UI\UIRoot_Play.cs:178)
Verse.Root:Update() (at C:\Dev\RimWorld\Assets\Scripts\Verse\Global\Root\Root.cs:122)
Verse.Root_Play:Update() (at C:\Dev\RimWorld\Assets\Scripts\Verse\Global\Root\Root_Play.cs:83)


Anybody have any ideas what I'm doing wrong? I assume it's me because:

  • This is my first Rimworld mod and all of this stuff is pretty new to me.
  • It's always me
Toolboxifier - Soil Clarifier
I never got how pawns in the game could have such insanely bad reactions to such mundane things.
Then I came to the forums.

Roolo

I took a look at the c# code. Apparently, when you add linkables to the CraftingSpot, it is assumed that they are so called "facilities", and have comps with class "CompProperties_Facility". There are two ways to solve this:

1. You make all things you add in the linkableFacilities list actual facilities, so you patch the comps of Stool, DiningChair, Armchair, and ButcherSpot. This will very likely have unwanted side-effects but you could try it at least.
2. You add a harmony patch that alleviates the requirement of the linkables to be actual facilities. I think this is the cleanest and best way to solve it. The following patch would do:


    //CanPotentiallyLinkTo_Static is the method throwing the error.
    [HarmonyPatch(typeof(CompAffectedByFacilities), "CanPotentiallyLinkTo_Static")]
    //Because multiple definitions of CanPotentiallyLinkTo_Static exist, we need to provide the types of the parameters:
    [HarmonyPatch(new Type[] {typeof(ThingDef), typeof(IntVec3), typeof(Rot4), typeof(ThingDef), typeof(IntVec3), typeof(Rot4)})]
    class CompAffectedByFacilities_CanPotentiallyLinkTo_Static
    {
        //We use a prefix that checks if a linked def is actually a facility. It returns false when it's not, indicating that the original method shouldn't be called.
        static bool Prefix(ThingDef facilityDef, ThingDef myDef)
        {
            CompProperties_Facility compProperties = facilityDef.GetCompProperties<CompProperties_Facility>();
            if(compProperties == null)
            {
                return false;
            }
            return true;
        }
    }


A sidenote, while your patch is functional, I think the following patch (doing exactly the same) is more precise and a bit more robust. You don't really need to insert since you don't care about the order of the inner list. Moreover, specifying which list element you want to patch (with: [@Class="CompProperties_AffectedByFacilities"]) is a good practice IMO.


<Operation Class="PatchOperationAdd">
<xpath>*/ThingDef[defName = "CraftingSpot"]/comps/li[@Class="CompProperties_AffectedByFacilities"]/linkableFacilities</xpath>
<value>
<li>Armchair</li>
<li>DiningChair</li>
<li>Stool</li>
<li>ButcherSpot</li>
</value>
</Operation>

5thHorseman

Quote from: Roolo on August 03, 2018, 07:27:35 AM
I took a look at the c# code. Apparently, when you add linkables to the CraftingSpot, it is assumed that they are so called "facilities", and have comps with class "CompProperties_Facility". There are two ways to solve this:

1. You make all things you add in the linkableFacilities list actual facilities, so you patch the comps of Stool, DiningChair, Armchair, and ButcherSpot. This will very likely have unwanted side-effects but you could try it at least.
If I understand what you're saying, that's exactly what I did last night. The only side-effect I've seen is that it draws lines to the items but I think I can alleviate that by setting the effect range to 0 or (if 0 means "Infinity") 1, or 0.1 or something like that.

Quote
2. You add a harmony patch that alleviates the requirement of the linkables to be actual facilities. I think this is the cleanest and best way to solve it.
I hate to say it but I really don't want to go down that road. I don't even have a compilation environment set up on this machine. I know it's not the hardest thing in the world but ... meh. :D

Quote
A sidenote, while your patch is functional, I think the following patch (doing exactly the same) is more precise and a bit more robust. You don't really need to insert since you don't care about the order of the inner list.
Thanks for that snippet. I'm still learning all this stuff so I basically kept fiddling until I got it working. I'll look into incorporating it into the mod instead of my hacks. Also, I was hoping to use that conditional PatchOperation (switch?) to kind of make a general use case one, so I wouldn't have 3 cases to deal with whenever I want to add or change one. Or the game changes. Or a mod changes something. Or, or, or.

Quote
Moreover, specifying which list element you want to patch (with: [@Class="CompProperties_AffectedByFacilities"]) is a good practice IMO.
I *knew* that was possible. I just didn't know the syntax and the 2 or 3 things I could think of to try didn't work. I'll add that, thanks!
Toolboxifier - Soil Clarifier
I never got how pawns in the game could have such insanely bad reactions to such mundane things.
Then I came to the forums.