[Example] Using a DLL to unlock researchable recipes all defined in XML

Started by 1000101, April 06, 2015, 05:50:29 PM

Previous topic - Next topic

1000101

After many hours examining code, trying to learn how RimWorld does what and what others have done to do things, I figured out my problem.  The method Saularain proposed below to remove and then re-add recipes from tables is the way to allow recipes to be unfinished.

I present my humble efforts at some sort of basic tutorial for those trying the same thing.  Not everyone will find this useful, but here you go to those that do.

Functional info:
AdvancedRecipeDefs define what recipes are linked with what research projects.  When a new colony is started, the MapComponent (DLL) loads all the AdvancedRecipeDefs, creates a list of the research to monitor for completion and then sits idle, periodically checking for research completion and unlocking recipes (re-adding the tables to the recipeDef) as they become available.


Notes:
In the XML for your recipes and tables, link the recipes to the tables and not the other way around.  That is, use <recipeUsers> in the recipeDef, not <recipes> in the building ThingDef.  The MapComponent is working on the recipe and not the table, the table itself is untouched except to have it's recipe cache flag cleared.



Quote from: OPThis is related to this post --> https://ludeon.com/forums/index.php?topic=11993.0

I have been trying to add recipies which may be unfinished dynamically to a table after research via DLL.  Everything works in that regard.  What does not, is that if a recipe can be unfinished (has an unfinishedThingDef), the job won't continue and exceptions are thrown.  Recipes without the tag work exactly as they should, it's only the unfinished ones.

Is there another table/list which needs to be updated for unfinished recipes to complete?  If so, then I would guess this is automagic in the xml loading and thus far hasn't become an issue.

Attached is a bare bones example of what's happening.

Tested against Core only on a new world and a new colony.

[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

Saularian

Problem could probably be due to wrong encoding of the advancedrecipedef... (it's not UTF8 but UTF8 w/o BOM) as this usually screwed up my own mods, but that doesn't seem to be the case here after I altered the defs...

It looks as if the unfinished object isn't "defined" as it can't be touched after someone started on it, but yet it can be canceled.

Although I'm curious as to why you want to inject a recipe via dll as it can be done in the defs, (research pre-requisition).

Havent looked at how another mod does it though, but maybe you should look at the gun crafting mod. Don't know if it has it's recipes injected by dll, but if it does, your solution could be in there.

1000101

As to the XML - I didn't even notice the byte-encoding was wrong (notepad++ defaults).  I did notice that I had to change the encoding to load RimWorld xml's into some specialized code of my own (xml library I use only supports utf-8) and I had to convert.  But if the encoding was at fault then the game engine would fail long before it does.

It uses DLL injection because recipes can't be unlocked and added to tables using XML (and lots of mods use this technique, M&Co, MD2, Clutter, etc).  In fact, I "borrowed" MD2's research injection method due to the nice way it uses xml defs to link everything (AdvancedRecipeDefs is the same as MD2's ResearchRecipeDefs used for it's Droid Assembly table).

I don't want to just add new tables since that just adds to the clutter of the UI and it's a logically broken method anyway - I don't need a new stove because I now know how to make lasagna.

What I need is a recipe which can yield an unfinished step to actually complete on injection.  Failing that I would have to break the recipe down into discrete steps instead of the generalized recipes.  eg, produce gun barrels, rifled barrels, rifle stocks, pistol grips, etc, *then* turn a rifle barrel + rifle stock + iron sights + short time = survival rifle or rifle barrel + rifle stock + scope + longer time = sniper rifle.

But that means more things to just lie around your base with limited use.  While I like the idea of discrete steps, it's the item creep I worry about.

If I have to, I have to, but...
(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

Saularian

Yeah it took me a while before I got tired of the notepad++ default and changed those

but i havent looked at MD2's source, but did have a look at Ykara's craftable guns source, which in my opinion does a good job on the recipe injection part, and even for me easy to read and understand.namespace CraftableGuns
{
    public class UnlockResearch : MapComponent
    {
        public UnlockResearch()
        {
            LockAllRecipes();
        }

        private static void LockAllRecipes()
        {
            LockRecipe("CreateGun_PDW");
            LockRecipe("CreateGun_HeavySMG");
            LockRecipe("CreateGun_LMG");
            LockRecipe("CreateGun_AssaultRifle");
            LockRecipe("CreateGun_SniperRifle");
        }

        private static void LockRecipe(string defName)
        {
            var recipeDef = DefDatabase<RecipeDef>.GetNamed(defName);
            recipeDef.recipeUsers = new List<ThingDef>();
        }

        private static void UnlockRecipe(string tableDefName, string defName)
        {
            var tableDef = DefDatabase<ThingDef>.GetNamed(tableDefName);

            var recipeDef = DefDatabase<RecipeDef>.GetNamed(defName);
            recipeDef.recipeUsers = new List<ThingDef> {tableDef};

            // Clear cache to update existing objects
            typeof (ThingDef).GetField("allRecipesCached", BindingFlags.NonPublic | BindingFlags.Instance)
                .SetValue(tableDef, null);
        }

        public static void Weapons1()
        {
            UnlockRecipe("TableSmithing", "CreateGun_PDW");
            UnlockRecipe("TableSmithing", "CreateGun_HeavySMG");
            UnlockRecipe("TableSmithing", "CreateGun_LMG");
        }

        public static void Weapons2()
        {
            UnlockRecipe("TableSmithing", "CreateGun_AssaultRifle");
            UnlockRecipe("TableSmithing", "CreateGun_SniperRifle");
        }

        }
    }


Allthough I havent used that mod in a while now so I'm not sure if this one gave me any exceptions...



1000101

hrm, that may be the solution then and probably why he did it that way.  Do the process in reverse.  I'll give it a shot and see what happens.


Edit:  It worked, OP updated to reflect that this is now closed and I provide an example for those who are trying the same/similar thing.
(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

mrofa

Im interested in your results on this code, i did try something similar but it had a problem with other mods that use similar method.
All i do is clutter all around.

skullywag

Mrofa wasnt that to do with everyone using researchmodspecial and not unique names?
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

mrofa

Im not sure maybe, i dont understand how it works so i cant really say :D
All i do is clutter all around.

skullywag

Its simply tying a research def to a class. Nothing more. So whatever class you call from the research def as long as it extends researchmod your good. Thats how i see it anyway.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

mrofa

Oh i didnt mean the research itself, i meant the recipes, i did try it in one of the realese, but it seemed to have problems with mechanical defence.
That only one mod was able to add recipes and other did not.
All i do is clutter all around.

skullywag

recipes a list right? If modders always take the current list and append to it, it should work. Hmm might have to investigate this further when im home.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

mrofa

All i do is clutter all around.

skullywag

This shouldnt need a map listener and stuff like that...ill see what i can find out. 2 heads and all that...
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

1000101

Yes, recipeUsers in the recipeDef and recipes in the ThingDef are lists and the code I provide is bad in that it actually recreates the list and may break game compatibility.  My local copy I've actually changed the code a bit to add/remove recipes/tables from the lists instead of using New List< type >();

I can upload a copy of the source if you are interested.  The code in the OP is more a "quick, let's get it working," and less of, "you guys play nice!"  My local copy is more "play nice" and doesn't assume it's the only one using it.  It also removes the restriction on how the recipe is originally linked the the tables as it removes the recipe from the table and the table from the recipe then adds the table to the recipe (creating a table list for the recipe if needed).

I've also changed it to allow multiple recipes, multiple research and multiple tables as a single def (see source) for research/recipe groups.

As to a map listener, it needs some way of either polling or using a research mod on a base research to trigger the same thing - The research scan loop.  The easiest way (from the xml perspective) was a map component so you don't need to worry about it triggering (forgot the add the research mod def or something). The source provided here is much more "mod friendly."

[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

mrofa

Thanks alot i will study it, btw do you know maybe any sane way to change stuff in statbase from code ?
Im currently using mapcomponent on it and additional failsafe on things themself since map component sometimes fails on it.
All i do is clutter all around.