DICE PACKS BUNDLE
  1. #1

    Procedural Item Module Generation

    I have written a Python script which will procedurally generate Fantasy Grounds items based on a set of parameters.

    It reads in a JSON file which defines parameters for how the items should be generated, and outputs into a definition.xml and db.xml file suitable to be copied directly into your modules/ folder and loaded. If you wish to generate items to add to an existing module, you can pass the script an "--id" parameter and it will start generating items at that ID instead of at id-00001, so you can open the resulting db.xml file and copy/paste into your module's <item> section without conflict.

    The items it makes can have a name, description, nonidentified name, nonidentified description, cost, and weight. The script allows for essentially unlimited numbers of modifiers, which can be selectively applied to base items as desired and each of which may change any of the names, descriptions, cost, or weight of the base item. This means you can very quickly produce hundreds of different items based on a few dozen lines of definition, thanks to combinatorial math. Note that there is no randomness involved - it generates every single possible item within the constraints that have been defined for it.

    The items will import into the following rulesets correctly: Core RPG, MoreCore, D&D 2E, D&D 3.5E, D&D 4E, D&D 5E, Pathfinder 1E, Pathfinder 2E, Starfinder, Fate Core, Cypher System, Numenera, The Strange.

    As an example of a practical use for it, I have recreated the items from LordEntrails' Gemstone Items Module in it (although the "Documentation" library reference page / story entry is a simple "author + contributors" list and would need to be modified by hand or with another tool). The definition for this is 120 lines long and generates 324 items (and just over 4,200 lines of xml). More, adding a new type of gemstone (and therefore 6 new items) takes a single line of definition, and adding a new size (and therefore 54 new items) takes 4 lines of definition. You can see this example on the github repository - the definition file is "examples/lord_entrails_compact.json" and the output is "examples/lord_entrails_compact/"

    You can find the script on GitHub here: https://github.com/theoldestnoob/fg-item-generator with much more detailed documentation and several examples of the input file and what it outputs.

    Please let me know what you think, if you run into any issues, or if you have any ideas for improvements you would like to see.

  2. #2

  3. #3
    JohnD's Avatar
    Join Date
    Mar 2012
    Location
    Johnstown ON
    Posts
    5,315
    Blog Entries
    1
    Looks like a time saver. I feel somewhat befuddled though (not a new feeling)... how do we use it?
    "I am a Canadian, free to speak without fear, free to worship in my own way, free to stand for what I think right, free to oppose what I believe wrong, or free to choose those who shall govern my country. This heritage of freedom I pledge to uphold for myself and all mankind."

    - John Diefenbaker

    RIP Canada, February 21, 2022

  4. #4

    Usage Explanation

    Right, it isn't the most user friendly thing in the world, and I'm not exactly a qualified technical documentation writer. I'll work through an example and hopefully that helps.

    The first step is to install Python, if you don't have it. Then, download the script somewhere on your computer - I would put it in its own directory, just to keep things organized. Then create a text file named something like "testmodule.json" and open it up in your text editor of choice. This will be your "item definition" file. Then you type up the parameters for your item generation and save it (more on that in a bit). After you have that, open up a command prompt, change directory to the folder that the script and your .json file are in, and run the command `python fg-item-generator.py --input testmodule.json --output testmodule`. It will spit out a few lines and create a module for you in directory form, suitable to be dropped into your Fantasy Grounds modules/ directory and used without further alteration. I do recommend dropping an image named "thumbnail" in, to make it easier to find in the modules list and library. If you zip the contents and rename it to end in .mod it will be just a normal module.

    Code:
    > python fg-item-generator.py --input examples\candelabras.json --output examples\candelabras
    Generating items based on examples\candelabras.json and outputting to examples\candelabras...
    Generated 12 items!
    Module XML files written to examples\candelabras. Done!
    
    >dir examples\candelabras
    ...
    01/01/2021  15:12 PM    <DIR>          .
    01/01/2021  15:12 PM    <DIR>          ..
    01/01/2021  23:18 PM            15,496 db.xml
    01/01/2021  23:18 PM               202 definition.xml
                   2 File(s)         15,698 bytes
                   2 Dir(s)  1,121,787,736,064 bytes free

    The .json file is the complicated part. Attached to this post is an example I worked up for the documentation (candelabras.json, renamed as "candelabras.txt"). You can also find this on the github, in the examples/ directory. I'll go through it section by section (skipping some stuff that isn't critical).

    At the top is the schema, if you use an editor that knows about JSON it may examine that file to give you hints and warnings about the format of the file you're writing (stuff like autocompleting possible parameters, warning you of missing parameters, etc). You can safely ignore or remove that.

    Next is the "module" definition. The minimum required is the module name and author, which is what I have put.

    Code:
    "module" : {
        "name" : "Noob's Candelabras",
        "author" : "theoldestnoob"
    },
    The real meat of the configuration comes after that, in the "items" section. This has two parts: "modifiers" and "base". I put the modifiers section first, because the base definitions refer to it, but it doesn't matter what order you put them in.

    The "modifiers" section defines the modifiers you want to be applied to your base items. In this case, I've defined two modifiers: "size" and "material". These can be anything, the names do not matter. I have defined three sizes and three materials that items can be. I will take the materials section as an example, since it is more complex, but know that the only part that is actually required in any of them modifiers is the "key". It won't actually *do* anything if you only have the key, but the key must be present for it to do anything else.

    So let's look at the "material" modifier "iron":

    Code:
    {
    	"key" : "Fe",
    	"name" : "Iron",
    	"desc" : "iron",
    	"nonid_name" : "Black Metal",
    	"nonid_desc" : "a dull dark metal",
    	"mod_cost" : 0.5,
    	"mod_weight" : 0.8
    },
    The defines a "key", which we will use to tell a base item to apply this modification to itself, a handful of strings that get used to replace parts of the base item's names and descriptions (here I've called them "name", "desc", "nonid_name", and "nonid_desc", but you could use any alphanumeric string for this), a modifier to multiply the base item's cost by, and a modifier to multiply the base item's weight by.

    If we go down to the base item, there are a few sections that these get referred to.

    This part is what tells the base item to apply the modifiers:

    Code:
    "modifiers": {
    	"size" : [ "S", "L" ],
    	"material" : [ "Fe", "Au", "Ag" ]
    }
    It will apply the "S" and "L" keyed modifiers (small and large) from the array of "size" modifiers, but not the "M" keyed modifier (medium).
    It will applly all three material modifiers.

    These are the parts of the base item definition that the modifiers actually modify:

    Code:
    "base_name" : "Candelabra",
    "item_name" : "|size#name| |varieties#name| |material#name| |base_name|",
    "description" : "This is a |size#desc| |material#desc| |base_name|. |varieties#desc|",
    "nonid_name" : "|size#name| |material#nonid_name| |base_name|",
    "nonid_description" : "This |base_name| is made of |material#nonid_desc|. |varieties#desc|",
    "base_cost" : 100,
    "base_weight": 3,
    "item_name", "description", "nonid_name", and "nonid_description" are all special strings that the script will interpret. Anything inside the bar characters ||, the script will check to see if it can replace with something from either a base parameter or a modifier. So you can see for the "item_name" it will use the "base_name" from the base item definition, the "name" field from the "size" and "material" modifiers, and the "name" field from the "varieties" modifiers.

    The base item defines a number of other things. I've tried to keep the names fairly self-explanatory, but there is more detail for every parameter in the readme displayed on the github page.

    The other important section is the "varieties" section. This section is optional.

    Code:
    "varieties": [
    	{ 
    		"name": "Functional",
    		"desc" : "It is unornamented."
    	},
    	{
    		"name" : "Luxury",
    		"desc": "It is covered in intricate carvings.",
    		"base_cost" : 1000,
    		"cost_ceiling" : 10000,
    		"cost_floor" : 500
    	}
    ],
    This defines several variations on the base item. The "varieties" function much like a regular modifier, but they are capable of overriding the base item's parameters (except for the names and descriptions). You can see that here, where the "Luxury" variety has a higher base cost, cost ceiling, and cost floor.

    This definition file generates 12 items (2 varieties * 2 sizes * 3 materials), with the following names:
    Small Functional Iron Candelabra
    Small Functional Gold Candelabra
    Small Functional Silver Candelabra
    Large Functional Iron Candelabra
    Large Functional Gold Candelabra
    Large Functional Silver Candelabra
    Small Luxury Iron Candelabra
    Small Luxury Gold Candelabra
    Small Luxury Silver Candelabra
    Large Luxury Iron Candelabra
    Large Luxury Gold Candelabra
    Large Luxury Silver Candelabra

    Here is the full XML that gets generated for the Large Luxury Iron Candelabra item:
    Code:
    		<id-00010>
    			<name type="string">Large Luxury Iron Candelabra</name>
    			<description type="formattedtext">This is a large iron Candelabra. It is covered in intricate carvings.</description>
    			<flavor type="string">This is a large iron Candelabra. It is covered in intricate carvings.</flavor>
    			<notes type="formattedtext">This is a large iron Candelabra. It is covered in intricate carvings.</notes>
    			<text type="formattedtext">This is a large iron Candelabra. It is covered in intricate carvings.</text>
    			<nonid_name type="string">Large Black Metal Candelabra</nonid_name>
    			<nonidentified type="string">This Candelabra is made of a dull dark metal. It is covered in intricate carvings.</nonidentified>
    			<nonid_notes type="string">This Candelabra is made of a dull dark metal. It is covered in intricate carvings.</nonid_notes>
    			<type type="string">Treasure</type>
    			<subtype type="string">Art Objects</subtype>
    			<cost type="string">500 gp</cost>
    			<price type="string">500 gp</price>
    			<weight type="number">2.88</weight>
    			<isidentified type="number">0</isidentified>
    			<locked type="number">1</locked>
    		</id-00010>
    You can see how the names and descriptions were made from the base item and modifiers. The cost is 500 gp: 1000 (luxury base cost) * 0.5 (iron modifier). The weight is 2.88: 3 (base item weight) * 1.2 (large modifier) * 0.8 (iron modifier). It is locked and not identified, as per the base item definition. Some things are repeated for ruleset compatibility.

    Changing the base item definition's modifier section to read

    Code:
    "modifiers": {
    	"size" : [ "S", "M", "L" ],
    	"material" : [ "Fe", "Au", "Ag" ]
    }
    will make it generate 6 more items (18 in total, 2 varieties * 3 sizes * 3 materials).

    There are several different ways you can write these definition files, some much more efficient than others. I would probably keep "varieties" as broad as possible and use modifiers for more generic stuff - if I intended to actually use the definition above, I would have written the base item to be something like "art object", made the "varieties" different art objects with different base prices ("Candelabra", "Plate", "Brazier", "Statue", etc), and made a separate "modifier" called something like "style" for things like "Unornamented", "Elaborate Decorative Carvings", "Eldritch Runes", "Mystical Sigils", etc. If you look through the examples, I've tried to show some different ways to do things - you can see this especially in the two "lord_entrails" examples, they both produce the same output but the "compact" version is a much shorter and much more straightforward way to do it.

    Does that help at all? I don't really have the necessary distance from the code to be able to judge how well I'm explaining things (it all seems so straightforward and obvious to me at this point ).
    Attached Files Attached Files
    Last edited by theoldestnoob; January 2nd, 2021 at 17:08. Reason: additional detail

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
5E Character Create Playlist

Log in

Log in