Help overriding the food system by mod.

Started by 1000101, May 06, 2015, 12:19:31 AM

Previous topic - Next topic

1000101

I'm trying to extend Building_NutrientPasteDispenser to support additional modded features (different hoppers, different meals, more than one type of dispenser).

What works:  The new NPDs accept the new hoppers (buildings with CompHopper), the new WorkGiver_CookFillHoppers fill the hoppers, the alert is surpressed as long as the buildings which need hoppers have hoppers.

What fails:  The new methods are successfully injected to replace the private/hard-coded methods in Building_NutrientPasteDispenser but they don't seem to be called.  No errors are thrown, cave-man debugging logs nothing (and I have plenty of log messages just trying to trace this).  My example project to show that the code injection works, works and everything about the methods I'm injecting meet the criterion for code injection (call type, return type, method flags, parameter list, code target (IL, not x86), etc, etc) except...it doesn't?

Hopefully someone here is familiar with code injection and can help me here, I'm right stumped.  This is the only thing holding up the completion of the mod and being able to add some modding features to the food system in RimWorld for a variety of purposes.


[attachment deleted due to age]
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

Acruid

The injection library you are using was made for the MS CLR, IIRC Unity games use a modified version of the mono runtime.

soltysek

#2
Here you have hook bridge that i use for multistructure machines

Here is a controler :
public class Mach : Building
    {
        private IntVec3 suppPos;
        private support supLink = (support)null;
        private support checkSupp
        {
            get
            {
               return this.supLink = (support)Find.ThingGrid.ThingAt(this.suppPos, ThingDef.Named("Custom_Hopper"));
            }
        }
        private bool checkSuppCK
        {
            get
            {
                return this.checkSupp != null;
            }
        }
        public override void SpawnSetup()
        {
            base.SpawnSetup();
            this.suppPos = this.Position + new IntVec3(0, 0, 1);
        }
        public override void Tick()
        {
            base.Tick();
            if (this.checkSuppCK)
            {
                Log.Warning(this.supLink.counter.ToString());
            }
          }
    }


and here is code for code for hopper :
class support : Building_Storage
    {
        public int counter = 0;
        private int ticker = 0;
        public override void Tick()
        {           
            base.Tick();
            if(this.ticker >= 120 )
            {
                ++this.counter;
                this.ticker = 0;
            }
            else
            {
                ++this.ticker;
            }
        }
    }


this way you can hard hook to any other custom class building and call there public method and acces public var.

But if you cant inject in dispencer class (but whot for if you can override it just) make work around .

soltysek

#3
More over you can modify hopper setings strait from code :

here is sneak from my loader code :

public override void SpawnSetup()
        {
            base.SpawnSetup();
            this.def.building.fixedStorageSettings.allowances.DisallowAll();
            this.settings.allowances.DisallowAll();
            this.CargoMax = this.CargoDefault + this.CargoUpgrade;
            this.MachList = null;
            this.SetFactionDirect(Faction.OfColony);
        }

        public void SendCFG( List<LoaderDB> List , IntVec3 Pos )
        {
            this.MachPos = Pos;
            this.MachList = List;
        }

        private void setAllCargo()
        {
            foreach(LoaderDB T in this.MachList)
            {
                ThingDef D = DefDatabase<ThingDef>.GetNamed(T.ListDefName);
                this.def.building.fixedStorageSettings.allowances.thingDefs.Add(D);
                this.def.building.fixedStorageSettings.allowances.SetAllow(D, true);
                this.settings.allowances.SetAllow(D, true);
            }
            this.SetDone = true;
        }


As you see ther is one public class that wait for call from other building
and here you have call itself :

private void Fuelrecip()
        {
            List<SteamFuel> A = new List<SteamFuel>();
            A.Add(new SteamFuel().add("WoodLog",250,30));
            A.Add(new SteamFuel().add("WoodPlank", 180, 10));
            A.Add(new SteamFuel().add("Charcoal", 300, 120));
            A.Add(new SteamFuel().add("Coal", 400, 180));
            this.FuelList = A;
        }

private void SpawnLoaders()
        {
            Thing Wood = ThingMaker.MakeThing(ThingDef.Named(NameDB.LoaderDefName),(ThingDef)null);
            Thing Water = ThingMaker.MakeThing(ThingDef.Named(NameDB.LoaderDefName), (ThingDef)null);
            List<LoaderDB> WoodList = new List<LoaderDB>();
            List<LoaderDB> WaterList = new List<LoaderDB>();
            foreach (SteamFuel A in FuelList)
            {
                WoodList.Add(new LoaderDB().add(A.ListDefName));
            }
            WaterList.Add(new LoaderDB().add("Water"));

            if(this.Rotation == Rot4.North)
            {
                this.WoodLoaderPos = this.Position + new IntVec3(2, 0, 1);
                this.WaterLoaderPos = this.Position + new IntVec3(2, 0, 0);
                GenSpawn.Spawn(Wood, this.WoodLoaderPos, Rot4.East);
                this.WoodLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WoodLoaderPos,ThingDef.Named(NameDB.LoaderDefName));
                this.WoodLoaderHand.SendCFG(WoodList, this.Position);
                GenSpawn.Spawn(Water, this.WaterLoaderPos, Rot4.East);
                this.WaterLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WaterLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WaterLoaderHand.SendCFG(WaterList, this.Position);
            }
            if (this.Rotation == Rot4.South)
            {
                this.WoodLoaderPos = this.Position + new IntVec3(-2, 0, -1);
                this.WaterLoaderPos = this.Position + new IntVec3(-2, 0, 0);
                GenSpawn.Spawn(Wood, this.WoodLoaderPos, Rot4.West);
                this.WoodLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WoodLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WoodLoaderHand.SendCFG(WoodList, this.Position);
                GenSpawn.Spawn(Water, this.WaterLoaderPos, Rot4.West);
                this.WaterLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WaterLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WaterLoaderHand.SendCFG(WaterList, this.Position);
            }
            if (this.Rotation == Rot4.West)
            {
                this.WoodLoaderPos = this.Position + new IntVec3(-1, 0, 2);
                this.WaterLoaderPos = this.Position + new IntVec3(0, 0, 2);
                GenSpawn.Spawn(Wood, this.WoodLoaderPos, Rot4.North);
                this.WoodLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WoodLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WoodLoaderHand.SendCFG(WoodList, this.Position);
                GenSpawn.Spawn(Water, this.WaterLoaderPos, Rot4.North);
                this.WaterLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WaterLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WaterLoaderHand.SendCFG(WaterList, this.Position);
            }
            if (this.Rotation == Rot4.East)
            {
                this.WoodLoaderPos = this.Position + new IntVec3(1, 0, -2);
                this.WaterLoaderPos = this.Position + new IntVec3(0, 0,-2);
                GenSpawn.Spawn(Wood, this.WoodLoaderPos, Rot4.South);
                this.WoodLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WoodLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WoodLoaderHand.SendCFG(WoodList, this.Position);
                GenSpawn.Spawn(Water, this.WaterLoaderPos, Rot4.South);
                this.WaterLoaderHand = (Building_SteamLoader)Find.ThingGrid.ThingAt(this.WaterLoaderPos, ThingDef.Named(NameDB.LoaderDefName));
                this.WaterLoaderHand.SendCFG(WaterList, this.Position);
            }
        }


In this code you have hoppers at fix position but its easy to scan around for one i also use just one ThingDef declarng "steamloader" hopper and after spawn i modify their texture , allowence , hitpoints everything from code (From controler machine not hopper ) depend of purpes differend for water and differend for fuel . My mod is base on multistructure construction i test some posibilities so if you need some help write :)

soltysek

One thing that i found out in test if you are not one 100% sure if given class exist at given Pos you need to use get method
as eg here are a scan around for my steam pipe :
public override void SpawnSetup()
        {
            base.SpawnSetup();
            this.PosSetup();
        }

        private void PosSetup()
        {
            this.PosNorth = this.Position + new IntVec3(1, 0, 0);
            this.PosSouth = this.Position + new IntVec3(-1, 0, 0);
            this.PosEast = this.Position + new IntVec3(0, 0, 1);
            this.PosWest = this.Position + new IntVec3(0, 0, -1);
        }

        private Steam NorthCk
        {
            get
            {
                if (Find.ThingGrid.ThingAt(this.PosNorth, ThingDef.Named("Steam_Pipe")) != null)
                {
                    return this.SteamNorth = (Steam)Find.ThingGrid.ThingAt(this.PosNorth, ThingDef.Named("Steam_Pipe"));
                }
                else if (Find.ThingGrid.ThingAt(this.PosNorth, ThingDef.Named("Steam_Boiler")) != null)
                {
                    return this.SteamNorth = (Steam)Find.ThingGrid.ThingAt(this.PosNorth, ThingDef.Named("Steam_Boiler"));
                }
                else
                {
                    return (Steam)null;
                }
            }
        }
        private bool NorthCkBool
        {
            get
            {
                return this.NorthCk != null;
            }
        }
        private Steam SouthCk
        {
            get
            {
                if (Find.ThingGrid.ThingAt(this.PosSouth, ThingDef.Named("Steam_Pipe")) != null)
                {
                    return this.SteamSouth = (Steam)Find.ThingGrid.ThingAt(this.PosSouth, ThingDef.Named("Steam_Pipe"));
                }
                else if (Find.ThingGrid.ThingAt(this.PosSouth, ThingDef.Named("Steam_Boiler")) != null)
                {
                    return this.SteamSouth = (Steam)Find.ThingGrid.ThingAt(this.PosSouth, ThingDef.Named("Steam_Boiler"));
                }
                else
                {
                    return (Steam)null;
                }
            }
        }
        private bool SouthCkBool
        {
            get
            {
                return this.SouthCk != null;
            }
        }
        private Steam EastCk
        {
            get
            {
                if (Find.ThingGrid.ThingAt(this.PosEast, ThingDef.Named("Steam_Pipe")) != null)
                {
                    return this.SteamEast = (Steam)Find.ThingGrid.ThingAt(this.PosEast, ThingDef.Named("Steam_Pipe"));
                }
                else if (Find.ThingGrid.ThingAt(this.PosEast, ThingDef.Named("Steam_Boiler")) != null)
                {
                    return this.SteamEast = (Steam)Find.ThingGrid.ThingAt(this.PosEast, ThingDef.Named("Steam_Boiler"));
                }
                else
                {
                    return (Steam)null;
                }
            }
        }
        private bool EastCkBool
        {
            get
            {
                return this.EastCk != null;
            }
        }
        private Steam WestCk
        {
            get
            {
                if (Find.ThingGrid.ThingAt(this.PosWest, ThingDef.Named("Steam_Pipe")) != null)
                {
                    return this.SteamWest = (Steam)Find.ThingGrid.ThingAt(this.PosWest, ThingDef.Named("Steam_Pipe"));
                }
                else if(Find.ThingGrid.ThingAt(this.PosWest, ThingDef.Named("Steam_Boiler")) != null)
                {
                    return this.SteamWest = (Steam)Find.ThingGrid.ThingAt(this.PosWest, ThingDef.Named("Steam_Boiler"));
                }
                else
                {
                    return (Steam)null;
                }

            }
        }
        private bool WestCkBool
        {
            get
            {
                return this.WestCk != null;
            }
        }


then you use it like :

private void method()
        {
            //Some code
                if(this.NorthCkBool)
               {
                 //some code
               }
               if(this.SouthCkBool)
               {
               // and so .................


i dont say that best way to do this but as you say once its work without bugs :P

1000101

Quote from: soltysek on May 07, 2015, 12:05:47 AM
Here you have hook bridge that i use for multistructure machines

...

and here is code for code for hopper :

...

this way you can hard hook to any other custom class building and call there public method and acces public var.

But if you cant inject in dispencer class (but whot for if you can override it just) make work around .
Everything hopper related works fine actually.

It's the dispenser which doesn't because it's using a hard-coded reference which I can't dynamically change as it may break underlying logic which may be between states and are assuming the reference to be otherwise a constant.  So, I tried to replace the methods to the new methods which can handle the additional functionality and are injected in place before the IL JIT happens.

The problem is that it doesn't inject properly it seems.

@Acruid:  I've tested the injection example under mono building against .NET 3.5 and that works.  But if Unity is using a modified version I will have to explore what changes it's made and maybe making the necessary adjustments will work.  Hopefully it's just a matter of binding to the proper CLR and that it supports the hacks I'm attempting.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

soltysek

i'm not home right now but i as remember dispencer class was pretty small  no faster/easier will be to rewrite manualy ... but as i say im away from RW source .

soltysek

i thing this will be usefull for you its a method replace in memory :
public static IntPtr GetMethodAddress(MethodBase method)
{
    if ((method is DynamicMethod))
    {
        unsafe
        {
            byte* ptr = (byte*)GetDynamicMethodRuntimeHandle(method).ToPointer();
            if (IntPtr.Size == 8)
            {
                ulong* address = (ulong*)ptr;
                address += 6;
                return new IntPtr(address);
            }
            else
            {
                uint* address = (uint*)ptr;
                address += 6;
                return new IntPtr(address);
            }
        }
    }

    RuntimeHelpers.PrepareMethod(method.MethodHandle);

    unsafe
    {
        // Some dwords in the met
        int skip = 10;

        // Read the method index.
        UInt64* location = (UInt64*)(method.MethodHandle.Value.ToPointer());
        int index = (int)(((*location) >> 32) & 0xFF);

        if (IntPtr.Size == 8)
        {
            // Get the method table
            ulong* classStart = (ulong*)method.DeclaringType.TypeHandle.Value.ToPointer();
            ulong* address = classStart + index + skip;
            return new IntPtr(address);
        }
        else
        {
            // Get the method table
            uint* classStart = (uint*)method.DeclaringType.TypeHandle.Value.ToPointer();
            uint* address = classStart + index + skip;
            return new IntPtr(address);
        }
    }
}

private static IntPtr GetDynamicMethodRuntimeHandle(MethodBase method)
{
    if (method is DynamicMethod)
    {
        FieldInfo fieldInfo = typeof(DynamicMethod).GetField("m_method",
                              BindingFlags.NonPublic|BindingFlags.Instance);
        return ((RuntimeMethodHandle)fieldInfo.GetValue(method)).Value;
    }
    return method.MethodHandle.Value;
}


public static void ReplaceMethod(IntPtr srcAdr, MethodBase dest)
{
    IntPtr destAdr = GetMethodAddress(dest);
    unsafe
    {
        if (IntPtr.Size == 8)
        {
            ulong* d = (ulong*)destAdr.ToPointer();
            *d = *((ulong*)srcAdr.ToPointer());
        }
        else
        {
            uint* d = (uint*)destAdr.ToPointer();
            *d = *((uint*)srcAdr.ToPointer());
        }
    }
}
public static void ReplaceMethod(MethodBase source, MethodBase dest)
{
    if (!MethodSignaturesEqual(source, dest))
    {
        throw new ArgumentException("The method signatures are not the same.",
                                    "source");
    }
    ReplaceMethod(GetMethodAddress(source), dest);
}



and here you have JIT Output of CTL Injection :
Enabling JIT debugging.
        JIT :   0x10720a8 Program.StaticTests
        JIT :   0x107217b Program.TestStaticReplaceJited
Replacing StaticClassA.A() with StaticClassB.A()
        JIT :   0x71ac205c MethodUtil.ReplaceMethod
        JIT :   0x71ac2270 MethodUtil.MethodSignaturesEqual
        JIT :   0x71ac231c MethodUtil.GetMethodReturnType
        JIT :   0x71ac20e4 MethodUtil.GetMethodAddress
        JIT :   0x10723e6 StaticClassB.A
        JIT :   0x71ac2094 MethodUtil.ReplaceMethod
        JIT :   0x1072426 StaticClassA.A
Call StaticClassA.A() from a  method that has already been jited
StaticClassA.A
Call StaticClassA.A() from a  method that has not been jited
        JIT :   0x1072172 Program.TestStaticReplace
StaticClassB.A
        JIT :   0x1072190 Program.InstanceTests
        JIT :   0x1072284 Program.TestInstanceReplaceJited
Replacing InstanceClassA.A() with InstanceClassB.A()
        JIT :   0x10723c2 InstanceClassB.A
        JIT :   0x1072402 InstanceClassA.A
Call InstanceClassA.A() from a  method that has already been jited
        JIT :   0x107241e InstanceClassA..ctor
InstanceClassA.A
Call InstanceClassA.A() from a  method that has not been jited
        JIT :   0x1072268 Program.TestInstanceReplace
InstanceClassB.A
        JIT :   0x10722a0 Program.DynamicTests
        JIT :   0x1072344 Program.CreateTestMethod
Created new dynamic metbod StaticClassA.C
        JIT :   0x107232e Program.TestDynamicReplaceJited
Replacing StaticClassA.B() with dynamic StaticClassA.C()
        JIT :   0x71ac2210 MethodUtil.GetDynamicMethodRuntimeHandle
        JIT :   0x1072434 StaticClassA.B
Call StaticClassA.B() from a  method that has already been jited
StaticClassA.B
Call StaticClassA.B() from a  method that has not been jited
        JIT :   0x1072325 Program.TestDynamicReplace
        JIT :   0x10c318 DynamicClass.C
StaticClassA.C


i hope it will be helpfull .

1000101

#8
I've done all that, actually. See the OP attachments.

The injection example is the same code you found that I did it seems, it's been slightly modified (Intel parameter ordering instead of AT&T parameter ordering) and I added an additional test case (sealed classes).

Testing is Community Core Library with the injection code in a sub-project (compiles to Community Core Injection) along side the main project (Community Core Library) injecting the new dispenser methods.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

soltysek

#9
I reach home ok i see whot you mean and i found a crack for you mayby yeah i definitivy break it try this way .

dont worry about building_nutriet class leve it make a new custom one which you like whotever you like no mather then make a neu class JobGiver_GetFood say mayby JobGiver_GetFood_custom whot importent you can overide this protectet method :


protected override Verse.AI.Job TryGiveTerminalJob(Verse.Pawn pawn)

inside you will finde :
Building_NutrientPasteDispenser nutrientPasteDispenser = thing2 as Building_NutrientPasteDispenser;
      if (nutrientPasteDispenser != null && !nutrientPasteDispenser.HasEnoughFoodInHoppers())
      {
        Building building = nutrientPasteDispenser.AdjacentReachableHopper(pawn);
        if (building == null)
        {
          thing2 = FoodUtility.BestFoodInWorldFor(pawn, pawn);
          if (thing2 == null)
            return (Job) null;
        }
        else
        {
          SlotGroupParent hopperSgp = building as SlotGroupParent;
          Job job = WorkGiver_CookFillHopper.HopperFillFoodJob(pawn, hopperSgp);
          if (job != null)
            return job;
          thing2 = FoodUtility.BestFoodInWorldFor(pawn, pawn);
          if (thing2 == null)
            return (Job) null;
          foodDef = thing2.def;
        }
      }


in your custom jobdrive class change pointer to dispencer class IMPORTENT delete base method or return it in last case !!!!! and last aproch is to overide xml : 8)
exacly this one :
<ThinkTreeDef>
<defName>Humanlike</defName>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>

<!-- Burning response -->
<li Class="ThinkNode_Subtree">
<treeDef>BurningResponse</treeDef>
</li>
       
        <!-- Mind broken - berserk -->
<li Class="ThinkNode_Subtree">
<treeDef>Berserk</treeDef>
</li>

<!-- Self-defense only if not drafted -->
<li Class="ThinkNode_ConditionalDrafted">
          <invert>true</invert>
<subNodes>
<li Class="ThinkNode_Subtree">
<treeDef>SelfDefense</treeDef>
</li>
</subNodes>
</li>


<!-- Mind broken - Give up and leave -->
<li Class="ThinkNode_ConditionalBrokenState">
          <state>GiveUpExit</state>
            <subNodes>
              <li Class="JobGiver_ExitMapWalkRandom" />
              <li Class="JobGiver_WanderAnywhere">
                <maxDanger>Deadly</maxDanger>
              </li>
            </subNodes>
          </li>

        <!-- Mind broken - Panic flee -->
        <li Class="ThinkNode_ConditionalBrokenState">
          <state>PanicFlee</state>
          <subNodes>
            <li Class="JobGiver_PanicFlee" />
          </subNodes>
        </li>

        <!-- Mind broken - Wander -->
        <li Class="ThinkNode_ConditionalBrokenState">
          <state>DazedWander</state>
          <subNodes>
            <li Class="JobGiver_WanderAnywhere">
              <maxDanger>Deadly</maxDanger>
            </li>
          </subNodes>
        </li>

        <!-- Mind broken - Binging -->
        <li Class="ThinkNode_ConditionalBrokenState">
          <state>BingingAlcohol</state>
          <subNodes>
            <li Class="ThinkNode_PrioritySorter">
              <minPriority>0.5</minPriority>
              <subNodes>
                <li Class="JobGiver_GetFood"/>
                <li Class="JobGiver_GetRest"/>
                <li Class="JobGiver_GetJoy"/>
              </subNodes>
              </li>
            <li Class="JobGiver_Binge"/>
            <li Class="JobGiver_WanderColony" />
          </subNodes>
        </li>

        <!-- Insertion hook for modders -->
        <li Class="ThinkNode_SubtreesByTag">
          <insertTag>Humanlike_PostBroken</insertTag>
        </li>
       
<!-- Prisoner -->
<li Class="ThinkNode_ConditionalPrisoner">
<subNodes>
<li Class="JobGiver_PrisonerEscape" />
            <li Class="JobGiver_PrisonerPatientGoToBed" />
            <li Class="JobGiver_PrisonerGetDressed" />
            <li Class="ThinkNode_PrioritySorter">
              <subNodes>
                <li Class="JobGiver_GetFood"/>
                <li Class="JobGiver_GetRest"/>
                <li Class="JobGiver_GetJoy"/>
              </subNodes>
            </li>
            <li Class="JobGiver_WanderCurrentRoom">
              <maxDanger>Deadly</maxDanger>
            </li>
<li Class="JobGiver_IdleError" />
</subNodes>
</li>

<!-- Squad brain directives -->
        <li Class="ThinkNode_Subtree">
          <treeDef>SquadBrainDuty</treeDef>
        </li>

        <!-- Insertion hook for modders -->
        <li Class="ThinkNode_SubtreesByTag">
          <insertTag>Humanlike_PostDuty</insertTag>
        </li>

        <!-- If on colonist team, do forced and emergency work -->
<li Class="ThinkNode_ConditionalColonist">
<subNodes>
<!-- Take direct orders when drafted -->
<li Class="JobGiver_Orders" />

<!-- Queue for forced work -->
<li Class="JobGiver_JobQueue" />

            <!-- Seek safe temperatures -->
            <li Class="JobGiver_SeekSafeTemperature" />
           
<!-- Do emergency work (supercedes satisfying needs, except starvation) -->
            <li Class="ThinkNode_ConditionalStarving">
              <invert>true</invert>
              <subNodes>
    <li Class="JobGiver_Work">
                  <emergency>true</emergency>
                </li>
              </subNodes>
            </li>
           
            <!-- Optimize apparel -->
            <li Class="JobGiver_OptimizeApparel" />
</subNodes>
</li>

        <!-- Behavior from traits -->
        <li Class="ThinkNode_TraitBehaviors" />

        <!-- Insertion hook for modders -->
        <li Class="ThinkNode_SubtreesByTag">
          <insertTag>Humanlike_PreMain</insertTag>
        </li>

        <!-- Main behavior core-->
        <li Class="ThinkNode_PrioritySorter">
          <subNodes>
            <li Class="JobGiver_GetFood"/>
            <li Class="JobGiver_GetRest"/>
            <li Class="JobGiver_GetJoy"/>
            <li Class="JobGiver_Work"/>
</subNodes>
</li>

        <!-- Insertion hook for modders -->
        <li Class="ThinkNode_SubtreesByTag">
          <insertTag>Humanlike_PostMain</insertTag>
        </li>

        <!-- Idle wander for colonists -->
        <li Class="ThinkNode_ConditionalColonist">
          <subNodes>
            <li Class="ThinkNode_Tagger">
              <tagToGive>Idle</tagToGive>
              <subNodes>
                <li Class="JobGiver_WanderColony">
                  <maxDanger>None</maxDanger>
                </li>
              </subNodes>
            </li>
          </subNodes>
        </li>
       
        <!-- If you're a neutral non-prisoner, exit the map -->
        <li Class="ThinkNode_ConditionalPrisoner">
          <invert>true</invert>
          <subNodes>
            <li Class="ThinkNode_ConditionalNeutralFaction">
              <subNodes>
                <li Class="JobGiver_ExitMapWalkNearest" />
              </subNodes>
            </li>
          </subNodes>
        </li>
       
        <li Class="JobGiver_WanderAnywhere">
          <maxDanger>None</maxDanger>
        </li>

        <li Class="JobGiver_WanderAnywhere">
          <maxDanger>Deadly</maxDanger>
        </li>

        <li Class="JobGiver_IdleError"/>

</subNodes>
</thinkRoot>
</ThinkTreeDef>


you replace all JobGiver_GetFood with your JobGiver_GetFood_custom or add them before JobGiver_GetFood depend if you wona leve vanila dispencer or not .
and you done you completly overide nutriet line :D with out injection and complicated code its more simple aproch :) have fun

soltysek

more over you can just copy past JobGiver_GetFood and change it as you like as long as you modify thinktree you do not need to overide it .

mrofa

Wouldnt it be easier and more valiable to make custom dispenser class and replace dispenser via xml with the modded one with that class ?
All i do is clutter all around.

1000101

#12
You're both on right track but this is where I hit the wall.

The first method I tried was simply replace the building with a new one, no problem there.

Test it, uh-oh, pawns won't take from the new dispensers.  Ok, where is it used?  eep, everywhere:
Alert_PasteDispenserNeedsHopper (already dealt with and works)
FoodUtility.BestFoodSourceFor
FoodUtility.NutritionAvailableFromFor
JobDriver_FoodDeliver
JobDriver_FoodFeedPatient
JobDriver_Injest
JobGiver_GetFood
Toils_Ingest.TakeMealFromDispenser

And because FoodUtility has methods which need to be updated, so do other classes including...
WorkGiver_FeedIncapped
WorkGiver_Warden
Map.MapUpdate (method referenced isn't reliant on npd it seems so it's ok to leave)
ThingDef.SpecialDisplayStatus (not fully investigated)

So, I would have to modify all those and I did with the exception of Toils_Ingest since there is a lot going on there and it's hidden from the debugger/decompiler.  I did re-create the code for all the other hard-coded classes though and updated the xml to reflect the changes.  When done all that, I just got null reference errors when anything looked for food, being it animal or human.

This is where I switched tracks to doing a minimal code replacement and just inject the new methods.  This means I would only have to recreate the building (done) and alert (done), then inject the code (done but fails).

So, I have two paths which lead to results which only partially work.  The first attempt to recode it all but got snagged when dissecting Toils_Ingest, the second where the code injection reports success but nothing happens.

Trust me, I didn't wake up and say, "code injection!"  This was more of a last resort since some methods/classes are private/sealed/internal.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

soltysek

mrofa sortly NO becouse JobGiver_GetFood has a hardcoded referance to a dispencer class name if he do not find it he will skip it its exacly this part :

Building_NutrientPasteDispenser nutrientPasteDispenser = thing2 as Building_NutrientPasteDispenser;
      if (nutrientPasteDispenser != null && !nutrientPasteDispenser.HasEnoughFoodInHoppers())

but if you overide think node in xml you can make your own JobGiver_GetFood_custom and add it before vanila one or replace it
then you make
public class JobGiver_GetFood_custom : JobGiver_GetFood
and make method :
protected override Job TryGiveTerminalJob(Pawn pawn)
then if you decide to add you create small additon and delete base. from overadie or if you replace then you make additon and leve base. at end of overide

soltysek

yes ofc but if you wona havestandard dispencer and custom dispenser injecton is inpasible ... morover injection is unsafe and ustable in along scope from wht i know about it ... as some jobdriver class are easy to replace overaid ... toils are hidde but we all know someone that know whot inside and we can gently ask for share :)