Ludeon Forums

RimWorld => Mods => Help => Topic started by: 1000101 on May 06, 2015, 12:19:31 AM

Title: Help overriding the food system by mod.
Post by: 1000101 on May 06, 2015, 12:19:31 AM
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]
Title: Re: Help overriding the food system by mod.
Post by: Acruid on May 06, 2015, 09:15:56 PM
The injection library you are using was made for the MS CLR, IIRC Unity games use a modified version of the mono runtime.
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 12:05:47 AM
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 .
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 01:04:56 AM
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 :)
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 01:26:02 AM
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
Title: Re: Help overriding the food system by mod.
Post by: 1000101 on May 07, 2015, 04:00:13 AM
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.
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 04:23:17 AM
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 .
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 04:54:34 AM
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 .
Title: Re: Help overriding the food system by mod.
Post by: 1000101 on May 07, 2015, 10:45:40 AM
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.
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 02:12:05 PM
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
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 02:16:43 PM
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 .
Title: Re: Help overriding the food system by mod.
Post by: mrofa on May 07, 2015, 02:22:17 PM
Wouldnt it be easier and more valiable to make custom dispenser class and replace dispenser via xml with the modded one with that class ?
Title: Re: Help overriding the food system by mod.
Post by: 1000101 on May 07, 2015, 02:53:15 PM
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.
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 02:56:15 PM
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
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 03:03:00 PM
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 :)   
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 03:05:19 PM
but realy i do not understand all shit about dispencer personaly i did not use it in a game even once i think i buil it once and only becouse i did not know yet whots that :)
Title: Re: Help overriding the food system by mod.
Post by: 1000101 on May 07, 2015, 03:16:09 PM
The idea here is that the dispenser can be upgraded to better versions and more than one version of a dispenser can exist.  So you could have small dispensers which only serve small meals, for example, and/or another dispenser which can do various meals (including sythahol), etc.

To do this, the dispenser has to accurately report what it can make at a given time, ie, can it do fine meals but only has enough ingredients available for nutrient sludge?  Either way it has to report the meal it will make so the decision tree can properly choose between the dispenser which can do fine meals, the dispenser which can only do paste right now and the simple meal on the floor.  There are other things it needs to report/do as well in this process but for illustration purposes I think the above suffices.

If I can get this to work one way or the other, the dispenser will actually be a useful alternative to large kitchens and pawns cooking.  I built a dispenser in one colony and the only thing it saved was time cooking, the mood debuff and it's enormous size made it useless though.
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 03:25:43 PM
unfortunetly i did not play with toils yet but do you realy need to touch it ?? is just beaking in think tree with custom jobdrivers befor normal dispencer round will not be enought ?? i never look in ther becous i did not need so i do not know anything about toils beside that i know that ther is something like it ,but nothing whot for and so one.
Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 07, 2015, 03:45:53 PM
there is also one more way becouse a game is mod frendly ask TYNAN to add a hook class betwin building_nutriet and building class i use this trick in my steam part of mod this class is a kind of dummy without any method  but if i need to hook to something i just hook to this class its holding basic information i need and i dont need to wory about whot exacly is standing here . or extend posibilites to mod nutrietdispencer ...
SO .. O great TYNAN pls add suport for moders for your great nutrietpaste becouse we cannot modify it properly . :) Amen
Title: Re: Help overriding the food system by mod.
Post by: mrofa on May 07, 2015, 04:01:27 PM
So apart from new dispenser, you can do rest of the things i think
My basic workaround is that replace the paste meal class with custom one, which would do all the thing you want.
On spawn the meal would look for nearest dispenser. After that it would look for hoppers around that dispenser.
Custom hoppers would have a bool variable if its active or not. If it would be active, then meal would check the hopper contents and to the stuff with them, and dependable on all that stuff, meal would destroy itself and spawn a proper meal.
This is my theory :D

Title: Re: Help overriding the food system by mod.
Post by: soltysek on May 08, 2015, 01:25:58 AM
i thing i get whot you mean BUT this way system will still see nasty nutrietpaste in getbestfood as foodutility round so is useless this type of mod will req rewrite whole food menagment system and as long as we will not get proper tool for it or hole to hook up we need to inject class replace or rewrite whole AI system with food menagment 

To binary i will look at it after weekend i'm preety busy this weekend

To TYNAN .... o GOD please make a hooking spot so we can hook our custom class in getbestfood loop .
Title: Re: Help overriding the food system by mod.
Post by: 1000101 on May 08, 2015, 02:25:04 PM
Yeah, I've been monkeying around and I found you're supposed to use MethodBase.MethodHandle.GetFunctionPointer() instead of RuntimeHelpers.PrepareMethod( MethodBase.MethodHandle ) except...it doesn't work either.  I've done self-modifying code and code injection with other development platforms (c, asm, x86, arm) and other tool chains (gnu) but I don't know enough about the C# lexer and emitter to know what my code is being translated into.  If I knew what it's producing, I could figure this out but my usual tools for debugging and disassembling just produce huge amount of wtf garbage.  RimWorld disassembled to 57M of crappy x86 assembly which was of no help.

It would be really nice if Tynan moved the code from the classes referencing the NPD directly into the NPD and used functions (public virtual please) in the NPD so we can mod the NPD and hopper system without potential unsafe code changing references the game assumes are otherwise static or using dirty work-around-hacks like I'm attempting.
Title: Re: Help overriding the food system by mod.
Post by: mrofa on May 08, 2015, 03:00:44 PM
I think it would good if we look thrugh the code and write to tynan what stuff need tag specific hooks.
Simply becouse he dont get engouh spam mail ^^
Title: Re: Help overriding the food system by mod.
Post by: 1000101 on May 08, 2015, 04:34:09 PM
Yeah, I'll look through the code and see where/what changes would need to be made to accomplish the desired effect.  The changes are minimal the problem is from the modders side it's not practicable.
Title: Re: Help overriding the food system by mod.
Post by: isistoy on May 12, 2015, 08:32:47 AM
I would possibly ask for Factory classes to instanciate some of the classes in the object tree, so that the game engine would allow external types to replace existing ones (at our own risks, of course) with just a few xmls and without injecting anything...
Now the complete instantiation of these would have to be rewritten! Quite the request!