Permalink
Switch branches/tags
abyssal-overworld abyssrun adamant adrenaline_rush all_bits altarstats aphorism146 ashtier aspirants avg_hp_extended avg_hp_numeric balefularc basajaun bearkin bonerider boriscaling bosch boulder catlobe chaoscrawl charms-reform cia-name-fullemail city-connection cloud-overlays cloud_gen console-polearm container contrib-updates-in-progress contribguide councilgod crawl-map-inheritance crypt-tiles cup-of-charity cyc datafiles dazzling_ghosts ddredheal debian-trunk demigods des_load destruction devtest diffusion distortionbolt door_items_solidity door_items dpegs_dynamic_monsters ds_trade_offs earthspells eatingprompt effect-timer elf-vaults enslaved-monster-parsing evoker-playstyle evokers faith_and_reason faithful fancyhell farmer fedhas_decay firewalk flags flatweap floodkiller food-time food-unification forest_wyrm forest forge_dwarves frenzy frogs fsim_acgdrev gammafunk-patch-1 gauntlet gcc_cxx_assert gdr ghost_fixes gitorious-merge-requests/2 gitorious-merge-requests/3 gitorious-merge-requests/4 gitorious-merge-requests/5 gitorious-merge-requests/6 gitorious-merge-requests/7 gitorious-merge-requests/8 gitorious-merge-requests/9 gitorious-merge-requests/10 gitorious-merge-requests/11 gitorious-merge-requests/12 gitorious-merge-requests/13 gitorious-merge-requests/14 gitorious-merge-requests/16 gitorious-merge-requests/17 gitorious-merge-requests/18 gitorious-merge-requests/20 gitorious-merge-requests/21 gitorious-merge-requests/22 gitorious-merge-requests/23 gitorious-merge-requests/24 gitorious-merge-requests/25 gitorious-merge-requests/26 gitorious-merge-requests/27 gitorious-merge-requests/28 gitorious-merge-requests/29 gitorious-merge-requests/30 gitorious-merge-requests/31 gitorious-merge-requests/32 gnoll goldify-books graceful_shutdown halftone harpoon header_simplification hell-effect-end hexcrawl hohoho hotspot imp instapickupdrop insulation koboldcultist labrework laplace layout lighting_form lobster-trap marksperson master melee_linear_damage meleemoves merc-shops mons-gods monsterspelldesc more-xv-resists move-to-trap movement-behaviours movespeed msvc2012 mulch_ado_about_nothing multisprint mutation_cleanup myrmec myrmecia new-layouts new-tree-tiles new_nemelex new_squarelos newdolls newgame-menus nfm-bondage-compat nfm-cast-light nimble-hulk no-crawl-ref no_backtracking_god no_item_transit nomes nospawn nostalgia notzombiehands numpad-fix oubliette panlord-info parareform plutonians polypotion pr-599 pr554 pretty_shiny_sparkles props pubby_swamp race_reordering randbook-rare-spells randeffects_malmutate randfood random-choose-weighted-double randon-choose-weighted-lambda ranged_reform rc-control reaver remove_chance_breaks require_rename resize ruin_everything safer-bitfield salamander sanitize_inscripts savefile sdl-2.0.7 seen-test segfault-debug sewer-water-tiles shard shrapnel simple_yesno simpleclouds simplesif sixtytwo slowokhle small_dwarves smithgod_rebased smithgod snake-enemies spell-sources spellres spiderstuff sponcor spooky_cleanup squarelos statlockgnolls steamblast stone_soup-0.1.3 stone_soup-0.1.4 stone_soup-0.1.5 stone_soup-0.1.6 stone_soup-0.1.7 stone_soup-0.2 stone_soup-0.3 stone_soup-0.4 stone_soup-0.5 stone_soup-0.6 stone_soup-0.7 stone_soup-0.8 stone_soup-0.9 stone_soup-0.10 stone_soup-0.11 stone_soup-0.12 stone_soup-0.13 stone_soup-0.14 stone_soup-0.15 stone_soup-0.16 stone_soup-0.17 stone_soup-0.18 stone_soup-0.19 stone_soup-0.20 stone_soup-0.21 stone_soup-0.22 stone_soup stream-drakes taunt thaw thinskin thorn_god tileschat-mute tombhatch tornado4 transporter travis-cc travis-test travis tutorial_kiss ugliness ungendered uniq.seen vehumet_spells wand_merging wand_stacking wandcharges water-beetle webtiles-changes-bsr webtiles-changes-score webtiles-changes webtiles-mouse-control webtiles-options webtiles-unicode willitblend win32-backtrace wip-coloured-FAQ wip-corpse_name wip-ring-jiyva wip-simulacra-tiles wip-splash-logo wizlab_changes zin-mutation zombieform
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
320 lines (270 sloc) 13 KB
Written by Cerol, 2017/02/09
Before we get to modding species in my set of tutorials, we need to
cover mutations and abilities first. All of a species innate abilities
are handled by mutations. Some grant stat changes, others grant
abilities, but they all use the same framework.
We start off by editing mutation-type.h to add the entry for our new
mutation. This time, we're updating mutation_data.h in the same way,
putting our new entry right above the NUM_MUTATIONS magic entry. Note
that if you're adding multiple mutations (or anything, really) it
doesn't matter which order they get added in, as long as they don't
replace the spot any existing mutation had.
#if TAG_MAJOR_VERSION == 34
MUT_STURDY_FRAME,
MUT_SANGUINE_ARMOUR,
#endif
MUT_LASER_EYES, //first new entry
MUT_PREHENSILE_BEARD, //second new entry
NUM_MUTATIONS,
RANDOM_MUTATION,
There's more special cases for mutations below RANDOM_MUTATION than we
saw for jobs, but those are for code that adds or removes mutations in
play. We aren't concerned with those now.
The second place we'll handle code is mutation-data.h. Much less happens
here than in other structs of similar purpose, but it's similarly nicely
documented. Most of the actual work for mutations is checked in the
relevant part of the code elsewhere in the game. This mostly handles how
many levels your mutation has and the text for them.
struct mutation_def
{
mutation_type mutation;
short weight; ///< Commonality of the mutation;
/// bigger = appears more often.
short levels; ///< The number of levels of the mutation.
mutflags uses; ///< Bitfield holding types of effects that
/// grant this mutation (mutflag::*)
bool form_based; ///< Mutation is suppressed when shapechanged.
const char* short_desc; ///< What appears on the '%' screen.
const char* have[3]; ///< What appears on the 'A' screen.
const char* gain[3]; ///< Message when you gain the mutation.
const char* lose[3]; ///< Message when you lose the mutation.
};
Again, copy and paste an existing one to minimize mistakes in structure:
{ MUT_LASER_EYES, 2, 2, mutflag::GOOD, false,
"laser eyes",
{"You can shoot laser beams from your eyes.",
"You can shoot BIGGER laser beams from your eyes.",
""},
{"Your eyes burn with power.",
"Your burning eyes intensify."
""},
{"Your eyes feel refreshed and cease burning.",
"Your eyes relax and burn less.",
""},
},
{ MUT_PREHENSILE_BEARD, 1, 1, mutflag::GOOD, false,
"prehensile beard",
{"Your beard can move around on its own.",
"",
""},
{"Your beard gains a will of its own.",
""
""},
{"Your beard shaves itself off in shame.",
"",
""},
},
We're going to have the laser eyes mutation be an activateable ability,
and prehensile beard will be yet another off-hand attack passive power.
These aren't fantastically clever mutations, but they'll illustrate how
to implement your own.
We can look at the naga's spit poison ability as the closest thing in
place to our laser eyes power. We'll search for that mutation's name
[MUT_SPIT_POISON] and we see that it's checked in ability.cc,
mutation.cc, and tags.cc outside of the mutation and species definition
blocks. Lucky for us, tsgs.cc is for save file compatibility across
versions, so we can ignore it for our new mutations. Were we editing
levels of an existing mutation, we might want to update tags.cc as well
to handle saves to keep them in line with the current range of levels.
For the laser eyes ability, we need to add an entry in ability-type.h
for an activated ability. This list is nicely organized right now, but
we should add our entry to the end right before NUM_ABILITIES to
minimize any possible save compatibility issue. I'll skip cutting and
pasting yet another block of code, but our name is ABIL_LASER_EYES.
Next, in ability.cc, we find that there's an array called Ability_List[].
The entries in it aren't sorted the same as the enum, but that's ok.
We'll still add a new entry to the bottom. The actual entries follow
this struct:
// Structure for representing an ability:
struct ability_def
{
ability_type ability;
const char * name;
unsigned int mp_cost; // magic cost of ability
scaling_cost hp_cost; // hit point cost of ability
unsigned int food_cost; // + rand2avg(food_cost, 2)
generic_cost piety_cost; // + random2((piety_cost + 1) / 2 + 1)
failure_info failure; // calculator for failure odds
ability_flags flags; // used for additional cost notices
};
Here we see there's 2 different random rolls involving different costs,
2 static costs that won't change, and flags for other costs. Let's add
our own, similar to ABIL_SPIT_POISON:
{ ABIL_LASER_EYES, "Shoot Eye Lasers",
1, 0, 50, 0, {FAIL_XL, 20, 1}, abflag::NONE },
This ability will cost us about 50 food and 1 MP to use, and the success
chance is only attached to our character level, not any skill. Farther
down, in, the _do_ability function, we see the master switch() statement
that handles ability use. We'll add ours in here, and order does not
matter for this one, so we'll place it by similiar abilities instead of
at the end:
case ABIL_LASER_EYES: // eye lasers
{
int power = 25 + (you.experience_level * 2 * you.get_mutation_level(MUT_LASER_EYES);
beam.range = _calc_breath_ability_range(abil.ability);
if (you.get_mutation_level(MUT_MISSING_EYE) //Ru - Sacrifice an Eye
power = power / 2; //only get half power if you only have half as many eyes.
if (you.get_mutation_level(MUT_EYEBALLS)) // Jiyva mutation.
power = power * 4; //if you're all eyes, you're also all eye-lasers!
if (!spell_direction(abild, beam)
|| !player_tracer(ZAP_DISINTEGRATE, power, beam))
{
return SPRET_ABORT;
}
else
{
fail_check();
zapping(ZAP_DISINTEGRATE, power, beam);
}
break;
}
In this case, I'm being lazy and using an existing spell for my laser
eyes' effect, and the casting power gets slightly stronger every level,
and doubles if I have 2 levels instead of 1. I could create a new spell,
and a new zap_type entry to match it, but that's another post.
You may also want to update the your_talents() or find_ability_slot()
blocks of ability.cc to set the letter for your ability if it should
have its own static letter. We won't this time, so laser eyes will
simply go in the next open letter slot for the character. In addition,
we also need to add an entry to _calc_breath_ability_range() for our
ranged ability.
case ABIL_BREATHE_STEAM: return 6;
case ABIL_SPIT_POISON: return 5;
case ABIL_BREATHE_POISON: return 6;
case ABIL_LASER_EYES: return 7; //7 is max for Line of
//Sight, so that's how far
//our eyes can work.
If you have an ability that might not always be possible to activate
(like a breath weapon, or stopping flight over deep water), you need to
go to _check_ability_possible() and add a check there. Laser eyes
shouldn't have any such conditions. We may also need to add the ability
as a talent (yet another term, this one means 'activateable ability' as
I've been using it here) in the your_talents function like so:
if (you.get_mutation_level(MUT_LASER_EYES) > 0)
_add_talent(talents, ABIL_LASER_EYES, check_confused);
You can do all sorts of complicated checks here, and the vampire part
should be a good example of how complex you might want to get at worst.
Note that deity-granted talents will be added automatically, and don't
appear in this block of code. We could also edit find_ability_slot() if
we wanted to assign a set letter to our ability, but I don't currently
want to do that for a randomly acquired mutation.
That should be all of the required steps for an active ability.
Now, let's look at our passive power, prehensile beard. Again, this is a
1-level copy of the horns/hooves/beak/etc mutations for demonstration
purposes, so we can search for MUT_HORNS and see it gets checked in
itemuse.cc, melee_attack.cc, mutation.cc, tiledoll.cc, and transform.cc
in addition to the files we handled at the beginning.
transform.cc handles gaining MUT_HORNS via the Beastly Appendage spell,
so we won't need to copy that part. All of the mutation.cc entries
involve conflicting mutations and gear slots, and the item-use.cc
entires stop you from equipping gear that won't fit over horns. This is
worth taking a look at, especially if you want to do similar body mods
like new scale mutations, or blade-hands, or something else that should
replace a piece of gear. We'll assume that beards can be tucked through
the facemask or bottom of a helmet and won't conflict with equipment.
tiledoll.cc handles the player character's tile icon in tiles mode. We
should update that to be a good citizen, but I don't want to worry about
the complexity of displaying a layered icon correctly in this tutorial.
That leaves melee_attack.cc as where the meat of the mutation goes. The
good news is that we can make a copy of already frequently implemented
code. First let's copy this auxilliary attack class and modify it, then
make an instance of our new class, and then check for our new attack
along with the other auxilliary attacks:
//CREATE THIS:
class AuxBeard: public AuxAttackType
{
public:
AuxBeard()
: AuxAttackType(9, "beard-slap") { }; //base damage, then display name.
int get_damage() const override
{
//dwarven beards are just as strong as their dwarf in many cases.
return damage + you.species == SP_DEEP_DWARF ? 20 : 1;
}
};
//ALSO CREATE THIS
static const AuxBeard AUX_BEARD = AuxBeard();
//UPDATE BOTTOM OF THIS LIST
static const AuxAttackType* const aux_attack_types[] =
{
&AUX_CONSTRICT,
&AUX_KICK,
&AUX_HEADBUTT,
&AUX_PECK,
&AUX_TAILSLAP,
&AUX_PUNCH,
&AUX_BITE,
&AUX_PSEUDOPODS,
&AUX_TENTACLES,
&AUX_BEARD,
};
//ADD A CASE TO THIS FUNCTION:
bool melee_attack::_extra_aux_attack(unarmed_attack_type atk)
{
if (atk != UNAT_CONSTRICT
&& you.strength() + you.dex() <= random2(50))
{
return false;
}
switch (atk)
{
case UNAT_CONSTRICT:
return you.get_mutation_level(MUT_CONSTRICTING_TAIL)
|| you.species == SP_OCTOPODE && you.has_usable_tentacle();
case UNAT_BEARD:
return you.get_mutation_level(MUT_PREHENSILE_BEARD)
//REST OF THE FUNCTION TRIMMED FOR SPACE
Now we have to go back to melee_attack.h and add in the new UNAT_BEARD
enum entry to unarmed_attack_type, since we didn't see that until getting
to the code above:
enum unarmed_attack_type
{
UNAT_NO_ATTACK, // 0
UNAT_CONSTRICT, // put constriction first so octopodes will use it
UNAT_KICK,
UNAT_HEADBUTT,
UNAT_PECK,
UNAT_TAILSLAP,
UNAT_PUNCH,
UNAT_BITE,
UNAT_PSEUDOPODS,
UNAT_TENTACLES,
UNAT_BEARD,
UNAT_FIRST_ATTACK = UNAT_CONSTRICT,
UNAT_LAST_ATTACK = UNAT_BEARD, //we had to update this now because
//we replaced the last attack. Still
//stay before NUM_UNARMED_ATTACKS
NUM_UNARMED_ATTACKS,
};
...and that should do it. Compile, and you should have a small chance
for a character to gain laser eyes or an off-hand attack with a beard
when mutating. Other flags can mark your mutation as being usable
exclusively by a deity.
Your mutations can do pretty much anything, so your own new mutations
might be much more complicated than these examples are. There are enough
current mutations that you most likely will have a good starting place
by looking at an existing mutation and making small changes to it. The
most complexity comes from when a mutation gets referenced on something
else, like how our beard ended up needed a new class to work correctly.
Summary Notes:
- mutation-type.h for mutation_type enum entry
- mutation-data.h to fill in the basics of the mutation
- ability-type.h for ability_type enum entry
- mutation.cc for any conflicting mutations or mutations sharing the
same 'slot'.
- ability.cc for mutations that have an activatable component (talents).
- mutations can affect stuff in almost any other file, depending on
what your mutation changes. Our example changed melee_attack.cc.
- check for your mutation using
"you.get_mutation_level(MUT_NAME_HERE) > 0"
as a simple check when getting creative with your code.