Sophie

Sophie

distrib > Mageia > 6 > armv5tl > by-pkgid > 64a7ad7b9bc18f479a4ecf5d3bf13eee > files > 41

crawl-common-data-0.20.1-1.mga6.noarch.rpm

Written by Cerol, 2017/02/09

Species are the biggest factor you choose when creating a character in Crawl, and the diversity of options helps make Crawl stand out from other classic roguelikes. On a quick, serious game design note: species aren't balanced against anything in particular, power-wise. In DCSS, it's more important to be unique and interesting than asymmetrically balanced correctly. Human, mummies, and minotaurs are vastly different and interesting in their own ways, but not on the same level.

Because there are so many options for species, theming a new species can be difficult to make them unique. In today's example, we're simply going to revise a classic, the Mountain Dwarf.

First, we go to species-type.h and update the species enums with a new value. Adding enums is going to be a common step in most any addition to the game.

        #if TAG_MAJOR_VERSION == 34
        SP_DJINNI,
        SP_LAVA_ORC,
        #endif
        SP_GARGOYLE,
        SP_FORMICID,
        SP_VINE_STALKER,
        SP_MD_REVISED, //In the right spot
        NUM_SPECIES,

        SP_UNKNOWN  = 100,
        SP_RANDOM   = 101,
        SP_VIABLE   = 102,
    };

Unlike many other species, particuarly Lava Orc and Djinni, Mountain Dwarves were entirely removed from the code base. LO and Dj still have everything present to play, but they've been removed from the species selection menu instead of completely stripped out of the whole game (and you should probably do the same if you remove a species). I will use the SP_MD_REVISED tag for this to make it clear that I'm working on my new version, not restoring the old one as it was.

Next up, we go to species-data.h to fill in the core entry for our species. Once again, the basic struct is reasonly well documented and clear.

    struct species_def
    {
        const char* abbrev; ///< Two-letter abbreviation
        const char* name; ///< Main name
        const char* adj_name; ///< Adjectival form of name; if null, use name
        const char* genus_name; ///< Genus name; if null, use name
        species_flags flags; ///< Miscellaneous flags
        // The following three need to be 2 lines after the name for gen-apt.pl:
        int xp_mod; ///< Experience level modifier
        int hp_mod; ///< HP modifier (in tenths)
        int mp_mod; ///< MP modifier
        int mr_mod; ///< MR modifier (multiplied by XL for base MR)
        monster_type monster_species; ///< Corresponding monster (for display)
        habitat_type habitat; ///< Where it can live; HT_WATER -> no penalties
        undead_state_type undeadness; ///< What kind of undead (if any)
        size_type size; ///< Size of body
        int s, i, d; ///< Starting stats contribution
        set<stat_type> level_stats; ///< Which stats to gain on level-up
        int how_often; ///< When to level-up stats
        vector<level_up_mutation> level_up_mutations; ///< Mutations on level-up
        vector<string> verbose_fake_mutations; ///< Additional information on 'A'
        vector<string> terse_fake_mutations; ///< Additional information on '%'
        vector<job_type> recommended_jobs; ///< Which jobs are "good" for it
        vector<skill_type> recommended_weapons; ///< Which weapons types are "good"
    };

Species has a much bigger range of potential changes compared to background. More of these options can be null compared to backgrounds, so you should double-check on a few species to see examples of how these can vary. Particularly odd is the stat_choice entry, which handles choosing which of 2 or 3 stats to gain, gaining random stats, gaining a fixed stat, or gaining double of the stat picked, and on vary ranges of levels.

The most complex part of this block is the mutation section. This is how your innate abilties and properties are handled most of the time. If you want to add in an existing mutation and fake-mutations (descriptor text for effects that aren't mutations proper), here is where they go. If you want to add new abilities, you'll also have to add new mutations.

Again, it's often easiest to pick an existing entry and copy/paste it to minimize mistakes in getting the structure right.

    { SP_MD_REVISED, {
        "MD",
        "Mountain Dwarf", "Dwarven", "Dwarf",
        SPF_NONE,
        -2, 3, 1, 8, //XP level mod, HP mod, MP mod, MR mod
        MONS_DEEP_DWARF, //Creating a new monster is out of scope for this tutorial
        HT_LAND, US_UNDEAD, SIZE_MEDIUM,
        11, 8, 8, // 27 total, STR - INT - DEX
        { STAT_STR, STAT_DEX }, 4, //Choose to upgrade STR or DEX every 4 levels.
        { { MUT_PREHENSILE_BEARD, 1, 1 }, { MUT_PASSIVE_MAPPING, 1, 1 },
          { MUT_PASSIVE_MAPPING, 1, 9 }, { MUT_PASSIVE_MAPPING, 1, 18 },
          { MUT_LASER_EYES, 1, 1 }, },
        { "You are the best type of dwarf",},
        { "dwarfy greatness",},
        { JOB_FIGHTER, JOB_HUNTER, JOB_BERSERKER, JOB_NECROMANCER,
          JOB_EARTH_ELEMENTALIST },
        { SK_UNARMED_COMBAT, SK_AXES, SK_SHORT_BLADES, SK_THROWING, SK_SLINGS },
    } },

Here, we've made MD undead, since the devs killed them long ago and they've finally made their return from the afterlife. Passive mapping is kept to share a common point with the deep dwarves for theme. We're also adding in our 2 new mutations from last time as innate racial mutations, and added a fake mutation to remind everyone we're awesome. There's no actual code behind the fake mutation entry here, but many species use it to explain out gameplay elements that are species-dependent without being a proper mutation (like nagas not being able to wear boots, and draconian breath weapons).

Next we need to add entries for our species in aptitudes.h. This is a big list of skills and the modifier the species gets to it. This file isn't sorted in any particular order, and uses a macro shortcut for assigning value. Another place to copy, paste, and modify for minimal mistakes:

    // SP_MD_REVISED,
        APT(SP_MD_REVISED,      SK_FIGHTING,        1),
        APT(SP_MD_REVISED,      SK_SHORT_BLADES,    2),
        APT(SP_MD_REVISED,      SK_LONG_BLADES,    -2),
        APT(SP_MD_REVISED,      SK_AXES,            4),
        APT(SP_MD_REVISED,      SK_MACES_FLAILS,   -2),
        APT(SP_MD_REVISED,      SK_POLEARMS,       -3),
        APT(SP_MD_REVISED,      SK_STAVES,         -4),
        APT(SP_MD_REVISED,      SK_SLINGS,          4),
        APT(SP_MD_REVISED,      SK_BOWS,           -3),
        APT(SP_MD_REVISED,      SK_CROSSBOWS,      -1),
        APT(SP_MD_REVISED,      SK_THROWING,        4),
        APT(SP_MD_REVISED,      SK_ARMOUR,          3),
        APT(SP_MD_REVISED,      SK_DODGING,        -3),
        APT(SP_MD_REVISED,      SK_STEALTH,         1),
        APT(SP_MD_REVISED,      SK_STABBING,      UNUSABLE_SKILL),
        APT(SP_MD_REVISED,      SK_SHIELDS,         3),
        APT(SP_MD_REVISED,      SK_TRAPS,         UNUSABLE_SKILL),
        APT(SP_MD_REVISED,      SK_UNARMED_COMBAT,  4),
        APT(SP_MD_REVISED,      SK_SPELLCASTING,   -1),
        APT(SP_MD_REVISED,      SK_CONJURATIONS,   -4),
        APT(SP_MD_REVISED,      SK_HEXES,          -1),
        APT(SP_MD_REVISED,      SK_CHARMS,         -4),
        APT(SP_MD_REVISED,      SK_SUMMONINGS,      2),
        APT(SP_MD_REVISED,      SK_NECROMANCY,      3),
        APT(SP_MD_REVISED,      SK_TRANSLOCATIONS,  1),
        APT(SP_MD_REVISED,      SK_TRANSMUTATIONS,  1),
        APT(SP_MD_REVISED,      SK_FIRE_MAGIC,      0),
        APT(SP_MD_REVISED,      SK_ICE_MAGIC,      -1),
        APT(SP_MD_REVISED,      SK_AIR_MAGIC,      -3),
        APT(SP_MD_REVISED,      SK_EARTH_MAGIC,     3),
        APT(SP_MD_REVISED,      SK_POISON_MAGIC,   -4),
        APT(SP_MD_REVISED,      SK_INVOCATIONS,     2),
        APT(SP_MD_REVISED,      SK_EVOCATIONS,      4),

While I've removed the version checks, we still have to have the UNUSABLE_SKILL tag on stabbing and traps to avoid ASSERT checks failing later. Interestingly, there's no actual check on what your aptitude value is. Everything in crawl is currently between -4 and 4, to make the skill train at half or double the base rate respectively. The math for the modifier is (return 1 / exp(log(2) * apt / APT_DOUBLE)), so every 4 points in an aptitude would double the growth rate again (aptitude 8 would require 1/4th the XP of the baseline, aptitude 12 would require 1/8th, etc), and negative values scale the same in the opposite direction.

The last step for this race will be to add them to the menu on startup in newgame.cc:

    static const species_type species_order[] =
    {
        // comparatively human-like looks
        SP_HUMAN,          SP_HIGH_ELF,
        SP_DEEP_ELF,       SP_DEEP_DWARF,
        SP_HILL_ORC,       SP_MD_REVISED,
        //More species trimmed

That's all it takes to create a basic species in Crawl. Simpler races might not have any mutations at all, just aptitudes and stat mods. More complex races have checks all over the code for special cases, like felids. Searching for a species by its enum name will show you every place that species gets special consideration. Humans get none, felids and vampires get tons.

Summary:
    - species-type.h to update species_type
    - species-data.h to add in core information
    - aptitudes.h to fill in skill aptitudes.
    - newgame.cc to update the species_order array.
    - use "you.species == SP_NAME_HERE" elsewhere in code for more complicated species-specific behaviors.