I apologize if this has already be answered before but I cannot seem to find anything on the topic.
The situation is this: I don't really like how campfires disappear when they run out of fuel. I understand realistically why it happens (no more wood = no more campfire) but having to redo their bills all the time because my pawns are busy with other things is a pain. So I came up with the mod idea of a firepit. Almost exactly the same as a campfire, it just requires a few stone/steel to build and doesn't disappear when the fuel runs out.
I know I can just copy the XML for the campfire, change what I want, and name it "firepit" to create a new building. But...there are other mods that add more recipes to a campfire that I'd really like to be able to use. I know I could just name my firepit as "campfire" and just override the vanilla campfire, but ideally I'd like to have the vanilla one available if someone wants to use that instead.
So is there any way to copy or inherit recipes from the vanilla campfire to my firepit without overriding it? It doesn't seem to be possible with straight XML but I thought maybe it would be with C#. If it can be done, I'd greatly appreciate it if someone could point me in the right direction.
Sooo, you already have your <Defs> in a xml, or did I misunderstand something?
You can add whatever mod you want by putting its folder under the path "Steam\steamapps\common\RimWorld\Mods".
Just be sure to maintain a proper folder structure, especially whatever <texPath> you've given to your texture.
For example, I have added a gun by putting its About.xml in the folder "About", its Defs.xml in the folder "Def", its texure in the folder "Textures" and the three said folders were put in the folder "TestTest".
The folder "TestTest" was then put in the path I have posted above and I can activate the mod under the register "Mods" on the main menu.
Er, that's not quite what I asked. Let me try explaining it another way...
I've already created a functioning mod that replaces the vanilla campfire with the "firepit" idea (changed destroyOnNoFuel to false, change the building materials, changed the texture, etc.). I have the defName set to Campfire so that it overwrites the vanilla one. I did this because I wanted it to also have any recipes that other mods would add (like fertilizer from Vegetable Garden, dried meat from Medieval Times, and so on). But ideally, I want the firepit to be a separate item from the campfire and not overwrite it. I'm just wondering if there's a way to do that AND still have the campfire recipes from other mods available on the firepit.
Nonono, this is exactly what you've asked!
For example:
Have you ever thought to yourself "Man, It'd be great to have a pistol with 150 range and the option to attack the ground with it! I want it to otherwise function exactly like a pistol and I don't care that I can't draw for ****!".
Fear not, download the zip I have attached and put the folder it contains under your path "Steam\steamapps\common\RimWorld\Mods".
The moment you have smacked together a mod similarily to this ^and you have "created" a gun / building / whatever with your own <defName> for it, it will be there as another gun / building / whatever. (This is also how you "install" mods manually)
[attachment deleted by admin due to age]
But if I change the <defName> from "Campfire" to "Firepit" (for example), then the firepit does not have the campfire recipes from mods.
Are you creating this mod for yourself, or to distribute?
The reason I ask is that if it's just something you're using yourself, you can do what you want with XML. Simply copy the relevant recipe defs into your new mod's folder, edit those defs to include both the basic campfire and your new firepit as "users," and then make sure your mod loads after all the other mods whose defs you've altered. But obviously, that won't work in a mod you want to distribute, as you won't know which particular mods the end user actually has installed.
If you're looking to create something for distribution, you'd need to include a bit of C# code to copy campfire recipes to the firepit. I don't know off the top of my head how to do it, as recipe database storage isn't something I've had reason to look for in the source code, but it should be possible, and I wouldn't expect it even to be especially complex.
Bloody hell forgive me, I have misunderstood you!
The problem is, you can only inherit things via ParentName="...", if said "..." has been defined with Name="..." in the <ThingDef>.
So if you add Name="Campfire" in the <Thingdef> of the campfire in the Building_Temperature.xml, so it ends up like:
<ThingDef ParentName="BuildingBase" Name="Campfire">
you should be able to define your firepit by using ParentName="Campfire". Which of course brings us to the point dburgdorf has raised. The reason that your game has no idea what you mean if you put "ParentName="Campfire"" is that "ParentName" doesn't take whatever <defName> has been given to an object, but it looks for the "Name" that has been given to an object in its <ThingDef>.
As far as I can tell, this is defined under the Verse.XmlInheritance in the Assembly-CSharp.dll. However I have no idea how you would be able to change that behaviour in order to also take a <defName> into account in terms of inheritance.
Quote from: dburgdorf on March 24, 2017, 09:42:30 PM
If you're looking to create something for distribution, you'd need to include a bit of C# code to copy campfire recipes to the firepit. I don't know off the top of my head how to do it, as recipe database storage isn't something I've had reason to look for in the source code, but it should be possible, and I wouldn't expect it even to be especially complex.
I would like to distribute it because I imagine there are other folks who might like the option for a non-disappearing campfire, especially if they like to begin as a tribe like I do. Besides, it would be a fun learning experience. :)
Do you happen to know if anyone has copied recipes using C# in their mod? I could probably figure it out if I had some examples to work with.
Actually, I missed the obvious, as what Apfel suggested should work. (Defining a name via defName and defining it via Name in the ThingDef tag are two different ways of doing the same thing.) Try defining your firepit with Campfire as a parent. If that doesn't work for some reason, we can investigate further.
OK, no, the inheritance doesn't seem to work, and even if it did, it probably wouldn't actually solve your problem, as it occurs to me that it would only give the firepit the traits of the campfire at the time the inheritance was processed, and not anything added later by mods.
After I posted my previous response about C# code, I took a quick look, and I think I spotted what I need. I can't guarantee getting to it tonight, but I should be able to get some usable code back to you sometime tomorrow morning.
Edit: Ah okay, just saw your edits, dburgdorf. I'll gladly take any code you can come up with, whenever you have a chance.
Ooooh. So if I'm understanding what you two are saying, I need to do something like this:
Create one ThingDef that will overwrite the vanilla campfire and allow it to be used as a parent object.
<ThingDef ParentName="BuildingBase" Name="Campfire">
<defName>Campfire</defName>
<!-- etc etc -->
</ThingDef>
And create another ThingDef for the firepit, using campfire as the parent.
<ThingDef ParentName="Campfire">
<defName>Firepit</defName>
<!-- etc etc -->
</ThingDef>
Is that about right? (I already tried doing the second set of code on its own and that didn't work.)
If you overwrite the default campfire definition as in your example, the inheritance will work. But as I said, I don't think it'll actually solve your problem, since anything added to the campfire by mods after the inheritance is processed as part of the firepit's initial creation, won't be added to the firepit.
(But by all means, double-check that. I've already demonstrated tonight the folly of saying how things work without testing first to make sure I'm right!)
I tested it out and you were right. The inheritance worked but the modded recipes weren't there, just the vanilla ones. I also managed to duplicate a whole bunch of attributes at first. :o The firepit had 2 listings for fuel, 2 bill tabs, the vanilla recipes were doubled, etc. So that was interesting but I at least managed to fix that by getting rid of all of the duplicate attributes.
Not sure if it is from another mod or something made exclusively for the pack, but the HardcoreSK mod pack has I believe just what you are aiming to make here.
They have a campfire ring that takes like 10 or 15 stone blocks to make. Then has bills that remain, because even when fuel runs out, the ring is still there. It also has a multiple fuel source menu, so not perfect for vanilla.
Maybe take a glance through there and see if you can find where it is in their pack. May be able to see how they did things to some idea of how to get yours up and running.
Quote from: BlackSmokeDMax on March 25, 2017, 06:56:27 AM
They have a campfire ring that takes like 10 or 15 stone blocks to make. Then has bills that remain, because even when fuel runs out, the ring is still there. It also has a multiple fuel source menu, so not perfect for vanilla.
I can create the firepit and make it not disappear/lose its bills just fine, the problem is I want to copy/inherit the recipes that other mods add to the campfire. dburgdorf PMed me some C# code to try so I'm giving that a whirl.
Quotedburgdorf PMed me some C# code
great community oriented behavior, PMing code instead sharing it with community will definely allow other users to find answers!*
*no, this is unacceptable actually.
static public void Main(string[] ignored)
{
Console.WriteLine("_yacil entry");
ThingDef dest = DefDatabase<ThingDef>.GetNamed("CraftingSpot",false);
ThingDef src = DefDatabase<ThingDef>.GetNamed("TableButcher",false);
//force game to cache data
object o = dest.AllRecipes;
o = src.AllRecipes;
if (o == null)
throw new Exception("to trick codeflow, in other case it will eliminate calls");
FieldInfo ff = typeof(ThingDef).GetField("allRecipesCached",(System.Reflection.BindingFlags)60);
object tmp = ff.GetValue(src);
ff.SetValue(dest,tmp);
Console.WriteLine("_yacil end");
}
run from [StaticConstructorOnStartup]
Quote from: RawCode on March 26, 2017, 03:34:10 AM
Quotedburgdorf PMed me some C# code
great community oriented behavior, PMing code instead sharing it with community will definely allow other users to find answers!*
*no, this is unacceptable actually.
static public void Main(string[] ignored)
{
Console.WriteLine("_yacil entry");
ThingDef dest = DefDatabase<ThingDef>.GetNamed("CraftingSpot",false);
ThingDef src = DefDatabase<ThingDef>.GetNamed("TableButcher",false);
//force game to cache data
object o = dest.AllRecipes;
o = src.AllRecipes;
if (o == null)
throw new Exception("to trick codeflow, in other case it will eliminate calls");
FieldInfo ff = typeof(ThingDef).GetField("allRecipesCached",(System.Reflection.BindingFlags)60);
object tmp = ff.GetValue(src);
ff.SetValue(dest,tmp);
Console.WriteLine("_yacil end");
}
run from [StaticConstructorOnStartup]
Says the guy who never spoonfeeds/gives usable C# code to beginners.
Nice double-standard you got there. ;)
Actually, Granite, RawCode has a valid point. I don't like to post code because I'm a rank amateur coder and my code is often inelegant, to say the least, but at the same time, if I'm going to answer questions, it makes sense to answer them publicly.
The code I provided to faeldray was as follows.
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
namespace Firepit_Code {
[StaticConstructorOnStartup]
internal static class Firepit {
static Firepit() {
List<RecipeDef> allRecipes = DefDatabase<RecipeDef>.AllDefsListForReading;
for (int i = 0; i < allRecipes.Count; i++) {
if (allRecipes[i].recipeUsers != null && allRecipes[i].recipeUsers.Contains(ThingDefOf.Campfire) && !allRecipes[i].recipeUsers.Contains(ThingDefOf.Firepit)) {
allRecipes[i].recipeUsers.Add(ThingDefOf.Firepit);
}
}
}
}
[DefOf]
public static class ThingDefOf {
public static ThingDef Campfire;
public static ThingDef Firepit;
}
}
It's not elegant, but it's complete and it does what faeldray wanted done, adding campfire recipes from mods to the fire pit.
Faeldray, you might actually want to consider using both RawCode's code and my own, as they do different and complementary things.
This stems from the fact that worktable items can have recipes attached to them, OR recipes can have worktable items attached to them. In other words, you can define a worktable and include with it a list of recipes that can be made there, OR you can define a recipe and include with it a list of worktables at which it can be made.
My code adds the fire pit to the list of worktables for any recipes that are defined such that they can be made at a campfire. That means that any recipe added by a mod which says, "this recipe can be made at a campfire," will also be available at the fire pit.
RawCode's code, on the other hand, copies every recipe from the campfire to the fire pit. That may not seem particularly useful, since you're already copying vanilla recipes from the campfire to the fire pit, anyway, but if another mod replaces the campfire and adds new recipes to it, his code would catch the additions, where mine wouldn't, just as my code catches recipes that his doesn't.
It took me a little while to figure out how to integrate RawCode's code and that I needed to include System.Reflection but this is the final code that I got to compile without errors and looks to be working after a glance at it in-game:
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Verse;
namespace Firepit_Code {
[StaticConstructorOnStartup]
internal static class Firepit {
static Firepit() {
List<RecipeDef> allRecipes = DefDatabase<RecipeDef>.AllDefsListForReading;
for (int i = 0; i < allRecipes.Count; i++) {
if (allRecipes[i].recipeUsers != null && allRecipes[i].recipeUsers.Contains(ThingDefOf.Campfire) && !allRecipes[i].recipeUsers.Contains(ThingDefOf.Firepit)) {
allRecipes[i].recipeUsers.Add(ThingDefOf.Firepit);
}
}
}
static public void Main(string[] ignored) {
Console.WriteLine("_yacil entry");
ThingDef dest = DefDatabase<ThingDef>.GetNamed("Firepit",false);
ThingDef src = DefDatabase<ThingDef>.GetNamed("Campfire",false);
//force game to cache data
object o = dest.AllRecipes;
o = src.AllRecipes;
if (o == null) {
throw new Exception("to trick codeflow, in other case it will eliminate calls");
}
FieldInfo ff = typeof(ThingDef).GetField("allRecipesCached",(System.Reflection.BindingFlags)60);
object tmp = ff.GetValue(src);
ff.SetValue(dest,tmp);
Console.WriteLine("_yacil end");
}
}
[DefOf]
public static class ThingDefOf {
public static ThingDef Campfire;
public static ThingDef Firepit;
}
}
I'm going to playtest it in one of my own games before releasing the mod on Steam Workshop but barring any issues, this is exactly what I wanted to do!
Huge thanks for dburgdorf for providing the first set of code and patiently helping me when I made some silly mistakes at first. Thank you also to RawCode for providing the second set, although I hope that next time you do so in a less harsh manner, therefore encouraging more great community oriented behaviour.
One small fix, and you should be good to go. Right now, the second method is never used, as you'll see if you remove some of the vanilla recipes from the fire pit definition. (The fire pit will then include any modded recipes, but not those vanilla recipes.) You'll want to make it all a single method, rather than two. If you just delete:
}
static public void Main(string[] ignored) {
You'll be set.
I deleted those lines as well as the vanilla recipes from the firepit thingDef (to test it) and all of the recipes, including the vanilla ones, are still showing at the firepit. Hooray! Now time for the playtesting.