Atlas   Rules   Resources   Adventures   Stories       FAQ   Search   Links



Mystaran NPC Generator (BECMI) - guidelines

by Giampaolo Agosta

This post provides guidelines for people who might want to customise the generator, or help with the development of new regions and origins. It assumes no knowledge of computer programming.

How to add an ethnic group
In file config.py, add a section such as:

classes_by_origin = {
  'Alphatian' : {
      'Fighter' : 'common',
      'Cleric' : 'rare',
      'Thief'   : 'very rare',
      'Rake'   : 'very rare',
      'Magic User' : 'uncommon',
      'Mystic' : 'very rare',
      'Druid'   : 'rare',
  },

Basically, the structure specifies how common is each character class in each ethnic group. It uses prevalent, common, uncommon, rare, and very rare. Just omit any class that is absent.

Then, scroll down to the following section:

ethnic_by_region = {
  'Savage Coast' : {
      'Human' : {
        'Ispan' : 'prevalent',
        'Verdan' : 'common',
        'Traladaran' : 'uncommon',
        'Dunael' : 'uncommon',
        'Eusdrian' : 'uncommon',
      },
      'Dwarf' : { 'Montoya' : 'prevalent', },
      'Elf'   : { 'Belcadiz' : 'prevalent', },
      'Halfling' : { 'Shire' : 'prevalent', },
      'Lupin' : {
        'Foxfolk' : 'uncommon',
        'Renardois Folk' : 'prevalent' ,
        'Narvaezan Maremma' : 'uncommon',
        'Bouchon' : 'uncommon',
        'Carrasquito' : 'uncommon',
        'Papillon' : 'uncommon',
        'Slagovici Gonic' : 'uncommon',
        'Zvornikian Sentinel' : 'uncommon',
      },
      'Rakasta' : { 'Bellaynish' : 'prevalent', },
  },

and add the new ethnic group to the appropriate regions (here, the code for the Savage Coast is shown). Use the same labels to give a frequency to the ethnic group.

Then, scroll down to

align_by_origin = {
  'Human' : {
      'Lawful' : 'common',
      'Neutral' : 'common',
      'Chaotic' : 'uncommon',
  },

Here we define the frequency of each alignment. There are few combinations, so you may use a shortcut and just add a line such as:

align_by_origin['Thyatian']=align_by_origin['Human']

In case the new ethnic group as a 'standard' distribution.

You may also add a set of common weapons here:

common_weapons = {
  'any' : [ 'Dagger', 'Club', 'Staff', 'Spear', 'Mace', 'Short Bow' ],
  'Traladaran' : [ 'Short Sword', 'Hand Axe', 'Long Sword' ],
  'Thyatian' : [ 'Short Sword', 'Javelin', 'Long Sword', 'Trident', 'Net', 'Cestus', 'Rapier', 'Light Crossbow' ],

You can also provide a customization of the height generator in file physique.py, and add the name of the new ethnic group in the appropriate list. If necessary, you can also provide a weight divisor (similar to the Lupin and Rakasta weight breed divisors in Bruce's articles).


How to add a new Immortal or religion

You can also add Immortals in the immortals.py file.
The following code specifies which modifiers are applied to the behavioural traits of an NPC worshipper:

i_trait = {
'Al Kalim' : { 'Dogmatic/Open-Minded' : 1, 'Honest/Deceitful' : 1, 'Forgiving/Vengeful' : -1, 'Generous/Greedy' : 1 },

Then, scroll down to:

by_origin = {
  'Dwarf' : set([ 'Chernobog', 'Kagyar', 'Volund' ]),
  'Elf' : set([ 'Ilsundal', 'Lornasen', 'Eiryndul' ]),

and add the list of immortals worshipped by the specific ethnic group (or race).
You can also add a pantheon or temple, here:
pantheon = {
  'Norwold' : set(['Druidism', 'Heldann Sidhr', 'Mos Thyaticum']),
  'Thyatis' : set(['Mos Thyaticum']),
  'Ylaruam' : set(['Eternal Truth']),
  'Isle of Dawn' : set(['Druidism', 'Heldann Sidhr', 'Mos Thyaticum']),
  'Alphatia' : set(['Pantheon of the Seven']),
  'Karameikos' : set([ 'Church of Karameikos', 'Church of Traladara', 'Cult of Halav', 'Gens Caelenes', 'Dark Triad' ]),
  'Glantri' : set([ 'Temple of Rad' ]),
  'Savage Coast' : set([ 'Druidism', 'Iglesia de Narvaez' ]),
}

If you add a new temple or immortal, you need to specify the allowed alignments:

i_lawful = set([ 'Temple of Rad', 'Al Kalim', 'Alphatia', 'Rad', 'Asterius', 'Diulanna', 'Forsetta', 'Frey', 'Freyja', 'Hel', 'Ilsundal', 'Ixion', 'Kagyar', 'Night Hunter', 'Nyx', 'Odin', 'Razud', 'Skauf-Halli', 'Tarastia', 'Thor', 'Valerias', 'Vanya', 'Volund', 'Philosophy of Law', 'Shamanism', 'Mos Thyaticum', 'Heldann Sidhr', 'Hin High Heroes', 'Chardastes', 'Koryis', 'Eternal Truth', 'Jade Temple of Ochalea', 'Mystic Way of Order', 'Pantheon of the Seven', 'Church of Karameikos', 'Church of Traladara', 'Halav', 'Petra', 'Mealiden', 'Iglesia de Narvaez', 'Saimpt Malinois', 'Saimpt M\^atin', 'Saimpt Ralon', 'Saimpt Cl\\\'ebard' ])
i_neutral = set([ 'Temple of Rad', 'Alphatia', 'Asterius', 'Rad', 'Chardastes', 'Diulanna', 'Eyrindul', 'Frey', 'Freyja', 'Hel', 'Hymir', 'Ixion', 'Loki', 'Lornasen', 'Night Hunter', 'Odin', 'Palartarkan', 'Protius', 'Rathanos', 'Razud', 'Skauf-Halli', 'Skuld', 'Thor', 'Valerias', 'Vanya', 'Volos', 'Volund', 'Zirchev', 'Druidism', 'Shamanism', 'Heldann Sidhr', 'Hin High Heroes', 'Nyx', 'Baxian Shendao', 'Jade Temple of Ochalea', 'Order of the Shadow Court', 'Circle of Heaven and Earth', 'Eternal Truth', 'Church of Traladara', 'Church of Karameikos', 'Petra', 'Ordana', 'Saimpt Ralon', 'Saimpt M\^atin', 'Faunus', 'Calitha' ])
i_chaotic = set([ 'Temple of Rad', 'Asterius', 'Chernobog', 'Eyrindul', 'Hel', 'Idris', 'Ixion', 'Loki', 'Night Hunter', 'Church of Karameikos', 'Protius', 'Rathanos', 'Razud', 'Skauf-Halli', 'Valerias', 'Vanya', 'Volund', 'Philosophy of Chaos', 'Shamanism', 'Nyx', 'Order of the Shadow Court', 'Dark Triad', 'Cult of Halav', 'Gens Caelenes', 'Orcus', 'Demogorgon', 'Leptar', 'Alphaks', 'Masauwu', 'Saimpt Ralon', 'Faunus' ])

and add them to the regionally allowed cults lists:
by_region = {
  'Norwold' : set([ 'Alphatia', 'Asterius', 'Chernobog', 'Diulanna', 'Eyrindul', 'Forsetta', 'Frey', 'Freyja', 'Hel', 'Hymir', 'Idris', 'Ilsundal', 'Ixion', 'Kagyar', 'Loki', 'Lornasen', 'Night Hunter', 'Nyx', 'Odin', 'Palartarkan', 'Protius', 'Rathanos', 'Razud', 'Skauf-Halli', 'Skuld', 'Tarastia', 'Thor', 'Valerias', 'Volos', 'Volund', 'Zirchev', 'Heldann Sidhr', 'Alphaks', 'Mos Thyaticum', 'Hin High Heroes', 'Shamanism', 'Druidism', 'Philosophy of Law', 'Philosophy of Chaos', 'Philosophy of Neutrality' ]),
  'Thyatis' : set([ 'Asterius', 'Chernobog', 'Diulanna', 'Ilsundal', 'Ixion', 'Kagyar', 'Loki', 'Night Hunter', 'Nyx', 'Odin', 'Protius', 'Skauf-Halli', 'Tarastia', 'Thor', 'Valerias', 'Zirchev', 'Mos Thyaticum', 'Philosophy of Law', 'Philosophy of Chaos', 'Philosophy of Neutrality', 'Koryis', 'Chardastes', 'Baxian Shendao', 'Jade Temple of Ochalea', 'Alphaks' ]),

You can also specify local names:

replace_by_origin = {
  'Thyatian' : {
      'Chernobog' : 'Thanatos',
      'Ixion' : 'Solarios',
      'Tarastia' : 'Pax Bellanica',
      'Skauf-Halli' : 'Korotiku',
      'Thor' : 'Alcaeus',
  },

Classes are assigned randomly (using the usual "common", "uncommon", etc. scores, see below), then those who don't match the requirements are downgraded to Fighters. Otherwise, most high-score NPC would be Foresters (at the moment, Foresters are a "common" option for Thyatians, but not for other humans).
BTW, in my interpretation of the rules, Rakasta and Lupins can take any human class, including Forester, so this is a choice -- the rationale being that DotE comes before the extension of Lupin population to Thyatis, so when it says only humans can be Foresters, it mostly means that Dwarves (and Halflings, if you have them in Thyatis), cannot, since no other PC races are available in DotE. As a result, I put Foresters as a common option for some Lupin subraces (those that appear in Thyatis only).

Of course, demographics can be tuned -- for example, by changing the meaning of "common", "rare", etc. More specifically, I already have two definitions, although with the web version, only the variant one is used.

default_freq = {
  'prevalent' : 16,
  'common'    : 8,
  'uncommon'  : 4,
  'rare'      : 2,
  'very rare' : 1,
}

variant_freq = {
  'prevalent' : 6,
  'common'    : 4,
  'uncommon'  : 3,
  'rare'      : 2,
  'very rare' : 1,
}

freq = variant_freq

The meaning of this fragment is as follow: when choosing class, race or other elements, as many items are put in a (virtual) urn as there are in the corrisponding descriptor. So, if I have to choose a character class with Fighter : prevalent and Cleric : uncommon, I put (with the variant version) 6 Fighters and 3 Clerics in the urn, then I randomly extract one. Thus, the probability in this case is 6/9 = 66.67% Fighter and 3/9 = 33.33% Cleric. By using the "default" version, I'd put in 16 Fighters and 4 Clerics, thus resulting in a 75% Fighter 25% Cleric probability.

I'll put exposing this parameter in the worklist for the web version.

That said, customization is much easier on the offline version, since you can edit config.py to fine tune all of the parameters. In particular, it is possible to change all the frequencies by modifying the values as shown in the post above. This level of configuration is definitely not in the plans for the web version -- I don't have a way to easily allow the user to change a large data structure such as the one in config.py from a simple web interface.
With the offline version, you can easily change the setup to make Foresters prevalent instead of common for Thyatians and possibly for other humans that can appear in Thyatis, and lower the frequency of Lupins and Rakasta, or just remove them and add Haflings instead.

In a pinch, you can always generate more NPCs than you actually need, and drop those who don't fit your requirements.

The current distribution for Thyatis is:

  'Thyatis' : {
     'Human' : 'prevalent',
     'Dwarf' : 'uncommon',
     'Elf'   : 'uncommon',
     'Lupin' : 'common',
     'Rakasta' : 'rare',
  },

And for ethnic groups/breeds:

  'Thyatis' : {
     'Human' : {
        'Thyatian' : 'prevalent',
        'Heldannic' : 'common',
        'Dunael' : 'uncommon',
        'Heldanner' : 'rare',
        'Ochalean' : 'uncommon',
        'Alasiyan' : 'rare',
     },
     'Dwarf' : { 'Denwarf' : 'prevalent', },
     'Elf'   : { 'Vyalia' : 'prevalent', },
     'Lupin' : {
        'Mongrel Lupin' : 'prevalent',
        'Doggerman' : 'common',
        'Mastiff' : 'common',
        'Beagle' : 'common',
        'Ochalean Crested' : 'common',
        'Ochalean Houndling' : 'common',
        'Shar-Pei' : 'common' ,
        'Chow Chow': 'common' ,
        'Das Hund' : 'common' ,
     },
     'Rakasta' :  { 'Pardasta' : 'prevalent', },
  },

So, indeed Lupins are quite common in Thyatis... maybe I should downgrade them to "uncommon" (they would work well as 'common' with the default frequency interpretation, but with the variant they are almost as common as humans, indeed, which seems too much).
Note that I primarily use the Norwold region, so maybe the others are less than optimal -- for example, I had forgotten to include the Thothian name generator, so the Alphatia and Isle of Dawn did not work and I didn't notice it. Let me know if there are other issues with the various regions.

How to define a spell selection

At the moment, in order to change the spell selection for wizards, you need to edit the spellbooks folder. The following data structure defines the spell selection:

spells = {
       1 : set([ 'Analyze', 'Charm Person', 'Detect Magic', 'Floating Disc', 'Hold Portal', 'Light*', 'Magic Missile', 'Protection from Evil', 'Read Languages', 'Read Magic', 'Shield', 'Sleep', 'Ventriloquism' ]),
       2 : set([ 'Continual Light*', 'Detect Evil', 'Detect Invisible', 'Entangle', 'ESP*', 'Invisibility', 'Knock', 'Levitate', 'Locate Object', 'Mirror Image', 'Phantasmal Force', 'Web', 'Wizard Lock' ]),
       3 : set([ 'Clairvoyance', 'Create Air', 'Dispel Magic', 'Fireball', 'Fly', 'Haste*', 'Hold Person*', 'Infravision', 'Invisibility 10\' Radius', 'Lightning Bolt', 'Protection from Evil 10\' Radius', 'Protection from Normal Missiles', 'Water Breathing' ]),
       4 : set([ 'Charm Monster', 'Clothform', 'Confusion', 'Dimension Door', 'Growth of Plants*', 'Hallucinatory Terrain', 'Ice Storm/Wall of Ice', 'Massmorph', 'Polymorph Other', 'Polymorph Self', 'Remove Curse*', 'Wall of Fire', 'Wizard Eye', ]),
       5 : set([ 'Animate Dead', 'Cloudkill', 'Conjure Elemental', 'Contact Outer Plane', 'Dissolve*', 'Feeblemind', 'Hold Monster*', 'Magic Jar', 'Passwall', 'Telekinesis', 'Teleport', 'Wall of Stone', 'Woodform', ]),
       6 : set([ 'Anti-Magic Shell', 'Death Spell', 'Disintegrate', 'Geas*', 'Invisible Stalker', 'Lower Water', 'Move Earth', 'Projected Image', 'Reincarnation', 'Stone to Flesh*', 'Stoneform', 'Wall of Iron', 'Weather Control' ]),
}

There's no point in adding spells over level 6, as for very high level mages, the spellbook becomes too large and therefore I've disabled their generation.
Simply add or remove spells as needed from the appropriate line, and save the file with name origin.py (replace "origin" with the actual region or ethnic group you're creating a spellbook for, e.g., "karameikos.py").