Plants which produce two seperate items possible?

Started by Qwynn, February 10, 2017, 01:43:19 PM

Previous topic - Next topic

Qwynn

Hello there!

I'm having some difficulty on a project I'm working on. The short of it is that I'm trying to get a plant to drop two separate items on harvest: both the fruit and the fibre. But -- I don't actually know if that's possible to do as I can't seem to quite figure it out. Adding in a second harvestedThing def doesn't do anything, and I can't seem to find any examples of what I'm looking for in the core files to base my work off.

I was hoping someone knew if such a thing were actually possible, and if so how I could go about doing it?

Thanks!  ;)

Thirite

Well, pretty much anything is possible. Whether something can be done neatly without overwriting base game code is the real question. I took a look at the source and for whatever bizarre reason the method for creating the products of harvesting is coded into the JobDriver_PlantWork class rather than the Plant class itself. You could still do it in a shitty hacky way though without detouring anything:

using System;
using RimWorld;
using UnityEngine;
using Verse;

namespace MySpecialPlant
{
public class SpecialPlant : Plant
{
public override int YieldNow ()
{
if (!this.HarvestableNow) {
return 0;
}
if (this.def.plant.harvestYield <= 0) {
return 0;
}
float num = this.def.plant.harvestYield;
float num2 = Mathf.InverseLerp (this.def.plant.harvestMinGrowth, 1, this.growthInt);
num2 = 0.5f + num2 * 0.5f;
num *= num2;
num *= Mathf.Lerp (0.5f, 1, (float)this.HitPoints / (float)base.MaxHitPoints);
num *= Find.Storyteller.difficulty.cropYieldFactor;


int amount = GenMath.RoundRandom (num);

ThingDef second_thing_def = ThingDef.Named ("HarvestedThing2"); // replace with the defName of whatever it is that should also appear beside the normal harvested item
Thing second_thing = ThingMaker.MakeThing (second_thing_def, null);
second_thing.stackCount = amount; // you could modify this to "amount * 0.5f" if you wanted half as much of the second thing created as the amount of the normal harvested item, for example
GenPlace.TryPlaceThing (second_thing, this.Position, this.Map, ThingPlaceMode.Near, null);

return amount;
}
}
}


Compile that as a dll and it would probably work. You'd also have to add this to the top of your fancy plant's xml ThingDef:
<thingClass>MySpecialPlant.SpecialPlant</thingClass>

Qwynn

#2
That's very helpful, thank you!

I've never compiled a DLL before, but I'm sure I'll figure it out!

dnks

Take a look at SeedsPlease! mod, when you harvest a plant there's a chance it will produce a seed, it is possible

Qwynn

#4
Quote from: Thirite on February 10, 2017, 09:35:50 PM
Well, pretty much anything is possible. Whether something can be done neatly without overwriting base game code is the real question. I took a look at the source and for whatever bizarre reason the method for creating the products of harvesting is coded into the JobDriver_PlantWork class rather than the Plant class itself. You could still do it in a shitty hacky way though without detouring anything:

using System;
using RimWorld;
using UnityEngine;
using Verse;

namespace MySpecialPlant
{
public class SpecialPlant : Plant
{
public override int YieldNow ()
{
if (!this.HarvestableNow) {
return 0;
}
if (this.def.plant.harvestYield <= 0) {
return 0;
}
float num = this.def.plant.harvestYield;
float num2 = Mathf.InverseLerp (this.def.plant.harvestMinGrowth, 1, this.growthInt);
num2 = 0.5f + num2 * 0.5f;
num *= num2;
num *= Mathf.Lerp (0.5f, 1, (float)this.HitPoints / (float)base.MaxHitPoints);
num *= Find.Storyteller.difficulty.cropYieldFactor;


int amount = GenMath.RoundRandom (num);

ThingDef second_thing_def = ThingDef.Named ("HarvestedThing2"); // replace with the defName of whatever it is that should also appear beside the normal harvested item
Thing second_thing = ThingMaker.MakeThing (second_thing_def, null);
second_thing.stackCount = amount; // you could modify this to "amount * 0.5f" if you wanted half as much of the second thing created as the amount of the normal harvested item, for example
GenPlace.TryPlaceThing (second_thing, this.Position, this.Map, ThingPlaceMode.Near, null);

return amount;
}
}
}


Compile that as a dll and it would probably work. You'd also have to add this to the top of your fancy plant's xml ThingDef:
<thingClass>MySpecialPlant.SpecialPlant</thingClass>

So I've been working on this for a few days and am having a little bit of an issue. After a lot of trial, error, and google I managed to successfully compile the DLL and have it work. I won't tell you how long it took me to realize my original issue was due to using .NET 4.5 instead of 3.5, but it's a bit embarrassing.

Anyway! While it is working there seems to be a bit of a bug: Designating the plant for removal (cut) instead of harvest causes the second item to spontaneously drop without pawn interaction. I can then remove the cut order, wait a few ticks, issue a cut order again on the same plant and have the plant "shed" it's second item. Hello infinite resource issue.


using System;
using RimWorld;
using UnityEngine;
using Verse;

namespace FruitTreeWood
{
public class Apple : Plant
{
public override int YieldNow () // is this what makes the wood pop up instantly on cut?
{
if (!this.HarvestableNow) { // if this plant is harvestable now then...? removing this line doesn't seem to do anything?
return 0;
}
if (this.def.plant.harvestYield <= 0) { // if plant yield is greater or equal to 0 then...?
return 0;
}
float num = this.def.plant.harvestYield; // yield of first item?
float num2 = Mathf.InverseLerp (this.def.plant.harvestMinGrowth, 1, this.growthInt); // thought minimum growth for acceptable harvest but doesn't seem correct
num2 = 0.5f + num2 * 0.5f;
num *= num2;
num *= Mathf.Lerp (0.5f, 1, (float)this.HitPoints / (float)base.MaxHitPoints);
num *= Find.Storyteller.difficulty.cropYieldFactor; // looks for the yield number based on difficulty?


int amount = GenMath.RoundRandom (num);

ThingDef second_thing_def = ThingDef.Named ("WoodLog_Apple"); // replace with the defName of whatever it is that should also appear beside the normal harvested item
Thing second_thing = ThingMaker.MakeThing (second_thing_def, null); // creates the second item
second_thing.stackCount = amount/2; // changing to "amount * 0.5f" creates error on build about float and int, managed to change to amount/2 as a float
GenPlace.TryPlaceThing (second_thing, this.Position, this.Map, ThingPlaceMode.Near, null); // places the new item

return amount;
}
}
}


You can see here how I've slowly been trying to figure out the code. I did manage to fix another problem I was having with the second_thing.stackCount line, so at least I got that done.

Does anyone have some advice for the whole infinite resource thing? I've looked through the Rimworld source code a few times for ideas but... I'm not really sure what I'm looking for. I found the JobDriver_PlantCut, but I'm not sure if anything in there would help me.

My modding experience over the years has been limited to tweaking XML and LUA. This C# stuff is brand new.  :-[

Qwynn

#5
I'm proud of myself in saying that my guess was right! The public override int YieldNow () line was what was causing the issue. I fixed the problem by switching to PlantCollected. Of course, I then I had learn what the return function did and how to change it to return void!

But, I did.

Thank you so much Thirite. You've helped a tonne. Huzzah for compiling my first DLL. XD

Edit: Sadly I celebrated too soon. Now I have an issue of whenever the plant is designated to be harvested it doesn't lose it's growth rating.  :'(

Alas, back to figuring stuff out!

Thirite

#6
Hm, I guess I didn't foresee YieldNow being used anywhere but on the harvest of the plant, but looks like it is.

Bear in mind if you change the Method which is being overridden you'll need to rework the code to reflect that.
So maybe:

public class Apple : Plant
{
public override void PlantCollected ()
{
// all this math stuff in this block is just taken directly from the YieldNow method to figure out how much
// should be in the resulting stack
float num = this.def.plant.harvestYield;
float num2 = Mathf.InverseLerp (this.def.plant.harvestMinGrowth, 1, this.growthInt);
num2 = 0.5f + num2 * 0.5f;
num *= num2;
num *= Mathf.Lerp (0.5f, 1, (float)this.HitPoints / (float)base.MaxHitPoints);
num *= Find.Storyteller.difficulty.cropYieldFactor;
int crop_yield = GenMath.RoundRandom (num);

ThingDef second_thing_def = ThingDef.Named ("WoodLog_Apple");
Thing second_thing = ThingMaker.MakeThing (second_thing_def, null);
second_thing.stackCount = (int)(crop_yield * 0.5f); // It wants an int not a float, so you can make the result cast to int by doing this. But crop_yield/2 works fine as well if you just want exactly half
GenPlace.TryPlaceThing (second_thing, Position, Map, ThingPlaceMode.Near, null); // places the new item

// Executes the normal method as well
base.PlantCollected ();
}
}

Qwynn

Beautiful, Thririte. I just tested it against both the original problems and it all looks good. Thank you! I think I also see how it works now as well thanks to your additional commenting. That makes a lot of sense!

Hilariously, you've also given me an interest in C# sharp now, so I shall enjoy my floundering and I learn my way about it. Thank you for that as well!  ;D