[MODULE DESIGN] Need Advice on Treasure Distribution

I’m using the XP1 Treasure System and am looking for some advice on how to break my treasure up into 3 categories: Low, Med, and High - as defined by the Module Wide Treasure Chests.

The module is designed for a single player of levels 2-5.

I’m thinking of the following values for determining which chest an item goes into:

For Magic Items (Except Potions/Scrolls)

  • Low Treasure = Up to 1,000gp
  • Med Treasure = 1,001 - 2,500gp
  • High Treasure = 2,501 - 5,000gp

For Potions

  • Low Treasure = Up to 100gp
  • Med Treasure = 101 - 299gp
  • High Treasure = 300gp potions
    Potion of Heal will be a unique item

For Scrolls

  • Low Treasure = Up to Lvl 1 spell scrolls
  • Med Treasure = Up to Lvl 2 spell scrolls
  • High Treasure = Up to Lvl 3 spell scrolls

Thoughts?

A lot depends on the economy in the module and whether its considered a high or low (or even medium) magic setting. IE, will 2-5 level PCs have access to enchanted goods? If so, how high? +1 caps to enchantments or can they find/buy better stuff. How inexpensive are healing kits, curative potions and buffing potions going to be? What if any rest restrictions are there? If few or no resting restrictions and folk can “drop and plop” to regain health and spells easily, then potions and healing are less of a factor.

On my own PW, I use a modified version of Bioware’s Treasure System. I set up Base Low, Medium and High loots as a “coverall” initially as I further refined it. Overtime, I converted all my loot drops to the “Monster” type using tags (X0_TREASURE_MONSTER_“insert tag of loot creator”). Even my placeables use it nowadays. IE, my goblins drop goblin themed loot. Their chest is tagged “X0_TREASURE_MONSTER_GOBLIN”. A placeable short sword for a random short sword is tagged “SSWORD” and its matching loot chest is “X0_TREASURE_MONSTER_SSWORD”. This allows a lot of diversity beyond just loot dropped by creatures or found in a chest. It also allows a builder to unleash endlessly unique items without inflating their palettes either (if done properly).

Here’s some tips …

  1. Create the base containers in a separate module. Fill them with all the goodies you can imagine based on the “type” of drop for each variant. Then copy the chest and open your actual module and you can now paste the chest into your treasure area without inflating that module’s palettes. You can bloat your “treasure” module as much as you wish to create and sort loot as you see fit, but you get to skip bloating your playing module’s palettes needlessly.

  2. Compare the base chests in the toolset to what you see IG. The base chests have a limit of 25 pages/screens and builders often over load them in the toolset. You can load these containers well past the 25 page limit the game has, at least when viewed from the toolset, but if you open them as DM later in the running module (or as an unlocked chest in a test area using a PC) you’ll note that the treasure peaks at 25 pages. Nor does the engine do a great job of loading the chest’s inventories when you fire the module up. IE, there’s often wasted space on pages especially when shields, armor and weapons are in the mix.

Just a suggestion about item rarity:

Depending on your mod’s power level, weapons with an enhancement bonus are one of the most vital loot drops due to things with +1/- damage reduction.

If max level is 5th, making a +1 club or +1 dagger or +1 sickle, kama, or kukri as a rare drop and more powerful +1 weapons like a longsword, battleaxe, or warhammer super rare has more impact on the narrative if you actually find a nice magic weapon.

Magic armor is less so, but still making +1 padded. +1 half plate and +1 chain mail drop more than +1 leather, studded leather, breastplates, and full plate is also similar in dramatic effect.

Ideally, you want the less min max items to drop more often to make the more efficient gear, potion, or scroll more “wow!”

So, like for scrolls - a cloud of bewilderment scroll has more oomph than say, a ultravision scroll. Identify has more bang for the buck than a charm person. And so on. A cure light wounds is certainly more “oh, thank the gods” than an aid potion, making the character be more strategic when they do get an aid potion.

Of course, this is relative to the mod’s power level, so if you go “low magic” you should also do the same with foes and monsters.

Appreciate the feedback so far.

Module’s set in the Realms, so I’m looking at a medium magic setting. I figured on limiting PCs to +1 weapons with low damage being “high” tier and higher damage weapons such as long swords, greatswords, etc. being “unique” tier. Armor will follow a similar pattern, but start at 1 tier lower than weapons.

I’m not looking to change the prices on anything - no need to reinvent the wheel. I’ve figured on Cure Light potions being “low” tier with the other cure potions (up to Cure Critical) filling out the higher tiers. Healer Kits will start at “medium” and work their way up through tiers, limiting them to the +1, +3, and +6 versions.Thieves’ Tools will be limited to +1, +3, and +6 but start at the “low” tier.

Scrolls will be limited to 4th level spells, with those being in the “unique” tier. The rest of the magic items will be fleshed out using low-mid powered items (e.g. +1 to +2 max. stat bonus).

I’ve decided to use a single script to pull from the containers, using the following percentages for each tier:

20% - Tier 1 (Low or “junk” items) - greenstones, torches, lvl 0 scrolls, etc.
60% - Tier 2 (Med or “common” items) +1 shields, lvl 1-2 scrolls, etc.
15% - Tier 3 (High or “uncommon” items) +1 armor, lvl 3 scrolls, etc.
5% - Tier 4 (Unique or “rare” items) diamonds, emeralds, +1 weapons, etc.

The OnDeath/OnUsed script looks like this:

#include "x0_i0_treasure"
void main()
{
    int nRoll = d100();
    int nType;

    if (nRoll > 0)   nType = TREASURE_TYPE_LOW;
    if (nRoll > 20) nType = TREASURE_TYPE_MED;
    if (nRoll > 80) nType = TREASURE_TYPE_HIGH;
    if (nRoll > 95) nType = TREASURE_TYPE_UNIQUE;

    CTG_CreateTreasure(nType, GetLastOpener(), OBJECT_SELF);
}

While that will work it does seem mildly inefficient in that, depending on the value of nRoll, the value of nType could be set up to 4 times in one pass through that function. If efficiency is important in your module you could use this slightly modified version instead.

#include "x0_i0_treasure"

void main()
{
    int nRoll = d100();
    int nType;

    if(nRoll > 95)
        nType = TREASURE_TYPE_UNIQUE;
    else if(nRoll > 80)
        nType = TREASURE_TYPE_HIGH;
    else if(nRoll > 20)
        nType = TREASURE_TYPE_MED;
    else
        nType = TREASURE_TYPE_LOW;
        
    CTG_CreateTreasure(nType, GetLastOpener(), OBJECT_SELF);
}

Although the efficiency gain will be minimal. Hope that helps.

TR

1 Like

Heh - sometimes I’m oblivious to the obvious. Thanks, Tarot.

What about +skill items? Personally, I prefer to hand out a plus/level; i.e. +1 at 1st, +2, at 2nd, etc. So this works out to +1 for low treasure, +2 at medium, +3/+4 at high, and +5 at unique.

Lots of progress on the treasure distribution. I’ve tweaked the XP1 Treasure system so it only needs module-wide containers and pulls from four rarities: Common, Uncommon, Rare, and Very Rare.

The rarities I’ve pulled from a 5th Edition PDF on the WotC website that lists rarities for magical items. This document gives a good baseline to work from. Its available from https://media.wizards.com/2014/downloads/dnd/MagicItemsRarity.pdf

The rarities I’ve defined in the library include correspond directly to the XP1 Type Values as follows:

  • Common = Low
  • Uncommon = Medium
  • Rare = High
  • Very Rare = Unique

I could have used the original constants but I wanted the variable names to directly correlate to the rarity of the item.

I can also use the original XP1 Treasure System if I so desire as all the changes I’ve made are self-contained into their own library include.

The tweaked system uses one script in the OnOpen/OnDeath event of the container:

#include "tr_i0_treasure"

void main()
{
    //prevent duplicate treasure generation
    if (CTG_GetIsTreasureGenerated(OBJECT_SELF))
        return;

    object oPC = GetLastOpener();

    //set the items in the container
    int i;
    int iMax = tr_GetNumItems(oPC);
    int nRoll;
    int nHasRare = 0;
    int nType;

    for (i = 0; i < iMax; i++)
    {
        nRoll = d100();
        if (nHasRare == 1 && nRoll > 95)
        {
            nRoll = 90;
        }

        if(nRoll > 95)
        {
            nType = TREASURE_TYPE_VERYRARE;
            nHasRare = 1;
        }
        else if(nRoll > 85)
            nType = TREASURE_TYPE_RARE;
        else if(nRoll > 60)
            nType = TREASURE_TYPE_UNCOMMON;
        else
            nType = TREASURE_TYPE_COMMON;

        tr_CreateTreasure(nType, GetLastOpener(), OBJECT_SELF);
    }
    //mark to prevent duplicate treasure generation
    CTG_SetIsTreasureGenerated(OBJECT_SELF);
}

The modified library functions are:

/*
    Pstemarie: 11-30-2019

    Treasure Include Library

*/

#include "x0_i0_treasure"

//------------------------------------------------------------------------------
// DEFINE CONSTANTS

//constants for treasure categories - use in place of those defined in x0_i0_treasure
const int TREASURE_TYPE_COMMON     = 1;
const int TREASURE_TYPE_UNCOMMON   = 2;
const int TREASURE_TYPE_RARE       = 3;
const int TREASURE_TYPE_VERYRARE   = 4;
//const int TREASURE_TYPE_MONSTER    = 5;  //defined in x0_i0_treasure

//------------------------------------------------------------------------------
// FUNCTION DEFINITIONS

// * Gets the number of items in the treasure container
int tr_GetNumItems(object oPC);

// * Create random treasure item of the appropriate type in the specified
// * container.
// *
// * Possible values for nTreasureType:
// *    TREASURE_TYPE_COMMON
// *    TREASURE_TYPE_UNCOMMON
// *    TREASURE_TYPE_RARE
// *    TREASURE_TYPE_VERYRARE
// *    TREASURE_TYPE_MONSTER
void tr_CreateTreasure(int nType, object oPC, object oCont = OBJECT_SELF);


//------------------------------------------------------------------------------
// FUNCTION IMPLEMENTATION

int tr_GetNumItems(object oPC)
{
    int nNum = d3();
    int nLevel = GetHitDice(oPC);

    switch (nLevel)
    {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            //use the default value of d3()
        break;

        case 6:
        case 7:
        case 8:
            nNum = nNum+1; //from 2-4 items
        break;

        case 9:
        case 10:
            nNum = d4()+1; //from 2-5 items
        break;

        case 11:
        case 12:
        case 13:
            nNum = nNum+d3(); //from 2-6 items
        break;

        case 14:
        case 15:
        case 16:
            nNum = nNum+d4(); //from 2-7 items
        break;

        default:
            nNum = nNum+d4()+1; //from 3-8 items
        break;
    }

    return nNum;
}


void tr_CreateSpecificBaseTypeTreasure(int nTreasureType, object oPC, object oCont, int nBaseType1=BASE_ITEM_INVALID, int nBaseType2=BASE_ITEM_INVALID, int nBaseType3=BASE_ITEM_INVALID)
{
    // Locate the base container
    object oBaseCont = CTG_GetNearestBaseContainer(nTreasureType, oCont);

    // Make sure we have a valid base container
    if (!GetIsObjectValid(oBaseCont))
    {
        // if not, generate treasure using default method
        if (nBaseType1 == BASE_ITEM_BOOK || nBaseType1 == BASE_ITEM_SPELLSCROLL)
        {
            // Make book treasure
            GenerateBookTreasure(oPC, oCont);
        }
        else
        {
            // Generate default treasure
            CTG_CreateDefaultTreasure(nTreasureType, oPC, oCont);
        }
        return;
    }

    int nItemsCreated = 0;
    object oItem = OBJECT_INVALID;

    // Keep track of items handed out to avoid dupes
    object oItem1 = OBJECT_INVALID;
    object oItem2 = OBJECT_INVALID;

    // Get the number of available items
    int nItemsInBaseCont = CTG_GetNumItemsInBaseContainer(oBaseCont);

    // Random number -- position of item to hand out
    int nRandom = 0;

    if (nBaseType1 == BASE_ITEM_INVALID &&  CTG_IsItemGold() )
    {
        CTG_CreateGoldTreasure(nTreasureType, oPC, oCont);
    }
    else
    {
        nRandom = Random(nItemsInBaseCont);

        if (nBaseType1 == BASE_ITEM_INVALID)
        {
            // we're not checking base types
            oItem = CTG_GetTreasureItem(oBaseCont, nRandom);
        }
        else
        {
            oItem = CTG_GetSpecificBaseTypeTreasureItem(oBaseCont, nRandom, nBaseType1, nBaseType2, nBaseType3);
        }

        if (!GetIsObjectValid(oItem))
        {
            CTG_CreateDefaultTreasure(nTreasureType, oPC, oCont);
        }
        else if ( nItemsCreated > 1 && ( GetTag(oItem) == GetTag(oItem1) || GetTag(oItem) == GetTag(oItem2) ) )
        {
            //Make gold instead
            CTG_CreateGoldTreasure(nTreasureType, oPC, oCont);
        }
        else
        {
            // Make the item
            CopyItem(oItem, oCont);
            if (nItemsCreated == 1)
            {
                oItem1 = oItem;
            }
            else
            {
                // if this is the third item, it doesn't matter
                // anyway, so we might as well save the conditional.
                oItem2 = oItem;
            }
        }
    }
}


void tr_CreateTreasure(int nType, object oPC, object oCont = OBJECT_SELF)
{
    tr_CreateSpecificBaseTypeTreasure(nType, oPC, oCont);
}

What I’ve done with tr_CreateSpecificBaseTypeTreasure() is eliminate the random number of pulls BioWare used in the original function CTG_CreateSpecificBaseTypeTreasure(). This allows me to have a chest pull more than the maximum of 3 items - as defined in BioWare’s system - and allows for each item to possibly be pulled from a different rarity. Thus a chest could contain 1 common, 1 uncommon, and 1 very rare item as opposed to 3 items of all the same rarity.

I’ve got the standard items all sorted between the various containers. The only placement I’m uncertain of is scrolls. I’ve got Cantrip and 1st level spell scrolls dropping as “common” treasure, and 2nd and 3rd level spell scrolls dropping as “uncommon” treasure. I’m leaning towards moving the 3rd level spell scrolls up to “rare” treasure drops, as this is the highest level spell a 5th level character can cast - 5th level being the expected maximum level a PC can achieve during the adventure.

Does that sound reasonable or should I keep the 3rd level scrolls as “uncommon” drops?

Yes, that sound perfectly reasonable, in my book at least… I am for a treasure/scroll scarcity model more than abundance of riches. I think the reward is much more satisfying when it comes in limited numbers. That is why, like in my old p&p module days: every treasure i make, is custom hand tailored and if script selection is to be used, don’t go with standard Bioware system.

2 Likes

OK, moved the 3rd level scrolls to Very Rare and the treasure drops are looking good. Better than vanilla drops, but not so good as to give an unbalanced advantage to the PC.

I’ve also added a function that creates a base amount of gold in a container. The treasure script check for a flag to see if the gold should be created. Anyway, here’s the function:

void tr_CreateGold(object oPC, object oCont = OBJECT_SELF)
{
    int nLevel = GetHitDice(oPC);
    int nAmount;

    switch (nLevel)
    {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            nAmount = d10();
        break;

        case 6:
        case 7:
        case 8:
            nAmount = d20();
        break;

        case 9:
        case 10:
            nAmount = d20(2);
        break;

        case 11:
        case 12:
        case 13:
            nAmount = d20(5);
        break;

        case 14:
        case 15:
        case 16:
            nAmount = d20(8);
        break;

        default:
            nAmount = d20(10);
        break;
    }

    //determine modifier if any
    int nRoll = d100();
    if (nRoll > 95)
    {
        nAmount = nAmount *4;
    }
    else if (nRoll > 85)
    {
        nAmount = nAmount *3;
    }
    else if (nRoll > 55)
    {
        nAmount = (nAmount *3) /2;
    }
    else
    {
        //use base amount of gold
    }
    CreateItemOnObject("nw_it_gold001", oCont, nAmount);
}