In EE, how do I query a 2da for "****"?

I’m working on a custom treasure generating system and I’ve created custom 2da files for it. Some entries have **** just like all other 2da files.

Whenever I try to do a lookup on a 2da (e.g. grym_treasure.2da), if the result is **** then I want to re-roll. However, whenever I check to see if sString == “****”, it fails. If I use “Grym” as the dummy entries, the script works as intended.

In EE, how do I check for a string with a value of “****”? Do I add extra quotes or some other symbol, or what?

Get2DAString returns empty string for cells containing ****. MWE:

void main()
    // "CastSpell"    
    SpeakString("'" + Get2DAString("actions", "label", 4) + "'"); 

    // "****"    
    SpeakString("'" + Get2DAString("actions", "iconresref", 4) + "'");

Exactly. This hasn’t changed in EE.

An empty string is also returned if you try to read past end of file.

1 Like

Or a column that doesn’t exist (BTW column names are case-insensitive).

This is probably why BW usually adds dummy “label” columns to their tables so you can tell EOF and empty/removed rows apart (i.e. to avoid rows in the middle having all “****”).

EDIT: out of curiosity I made a small experiment and can confirm that:

  • only **** evaluates to empty string; *, **, ***, *****, etc are passed through verbatim
  • "****" (double-quoted) also yields an empty string
  • so does ""
  • '****' (single-quoted) yields '****' - don’t use single quotes for quoting in 2DA (even though the script editor parses things like '2' as valid integer literals)

In that case, how can I test to see if a string is empty?

IIRC it’s just if(sAString == "") or alternatively if(GetStringLength(sAString) == 0)



one way, which assumes 0 is actually a valid value for treasure [and thus would be indistinguishable from an invalid ‘****’ value using conversion on the initial return] :

int nCritter, nAmount;


// nCritter assigned the value of your creature


string sAmount = Get2DAString("grym_treasure", "amount", nCritter);

if  (sAmount == "")
    nAmount = StringToInt(sAmount);

if all creatures in the 2da will always have treasure, and thus 0 is also an invalid value, the code is much simpler :

int nAmount = StringToInt(Get2DAString("grym_treasure", "amount", nCritter));
if (nAmount == 0)

There are also issues with sparse 2DAs (i.e. with high null-to-content ratio). To avoid a lot of missed rolls that may eventually TMI the script, you can roll until i rather than until last row. This makes the generator non-uniform (promoting earlier rows) but gives you finer control over what is returned:

// returns s2DA[sColumn,row] where row in [0, iMaxRow]
// returns "" if unsuccessful iMaxIter times (TMI sentinel)
// ** UNTESTED **
string RollFrom2DA(string s2DA, string sColumn, int iMaxRow, int iMaxIter=100)
    string sCell;
    int iRow;

    while(iMaxIter-- > 0)
        iRow = Random(iMaxRow) + 1;
        sCell = Get2DAString(s2DA, sColumn, iRow);
        if(sCell != "" || iMaxRow == 0)
            iMaxRow = iRow;

    return sCell;

This way you can put common items at the beginning and rare treasure at the end. Also instead of replacing iMaxRow with iRow you can halve it (less aggressive cut-off) or use any other strategy.

1 Like

@Tarot_Redhand - You are correct sir! Thanks for that.

@NWShacker - That’s an interesting concept. My current design calls for a 2da for each treasure type (amulets, rings, armor, weapons, etc.) and a column for each ItemLevel. Each row will have a unique ResRef of the appropriate item for that item level. The challenge is that there is an inconsistent number of items for each ItemLevel of each TreasureType. For example, there are twelve 1st level gems, two 2nd level gems, and three 3rd level gems. Given your point about timeouts from excessive rolling, I suppose instead of rolling a d12 to generate gems, I could check to see which ItemLevel is being used and then roll a d12, d2, or d3 respectively.

I suggest the following approach:

  1. Make one table for each base item type with following structure:
  ResRef Level Weight
0 item1  1     1
1 item2  1     1.5
2 item3  1     1.5
3 item4  1     2
4 item5  2     3
5 item6  2     3.5
6 item7  2     3.5
7 item8  3     5
8 item9  3     7
  1. Make master table with following structure:
  BaseItem Table       Count Level1 Level2  ...etc
0 52       treas_ring  9     0      4

If you need a ring level X, get row number LevelX + Random(LevelX+1 - LevelX). EDIT: made this less redundant.

Maintenance of such tables is pretty rudimentary. You can easily add duplicate rows to affect the random variable, or if you want to draw items from whole table, adapt the roll paradigm from my previous post. Or you could merge all item tables together, leaving only “master” and “slave”.

The (optional) weight column is there if you want to tie the random generator to some upper limit to prevent too many powerful items from being created (i.e. here item6 + item7 = item9 cost-wise).


@NWShacker - Thanks for the suggestions. Definitely a way to add more randomness to the the treasure. Food for thought.

Other benefit is that it compacts the table - no need to deal with empty cells.

BTW, it is actually similar to the vanilla treasure generator (see des_treas*2da), but I’d like to see a good treasure generator.