Chapter 13 · ~31 min read

Equipment and Derived Stats

Everything in the pack so far gets used up: you drink a potion, and it's gone. This chapter adds things you keep. A weapon in your hand decides how hard you hit, and armor on your back decides how much of each blow you feel. From now on, the numbers of every fight come from what you've found.

That changes how the player's damage works. It stops being a number stored in the player. Instead, it's worked out whenever it's needed, from the weapon in hand, or from bare fists if there isn't one. A value worked out like that is a derived stat, and it can't fall out of step with the equipment, since nothing stores it. The next image shows the end of the chapter: the pack, with what the player wields and wears listed above it.

The end of Chapter 13
Figure 13.1 — The end of Chapter 13

You've put on a suit of plate armor, which takes five off every blow you suffer. And you've kept the short sword you started with, since the dagger you found does less. The side panel shows the result: the damage of your blows, your defense, and what you have in hand and on your back. The plate has a price, which you'll meet at the third checkpoint.

Project folder: Chapter 13 in the book's repository holds this chapter's finished project. The chapter carries on in your own project from Chapter 12, or from the book's Chapter 12 folder.

In this chapter, we will:

  • Add five weapons and three suits of armor to the table of kinds, each with a picture of its own
  • Keep weapons and armor out of the identification game, and out of stacks
  • Give the player a hand that may hold a weapon, and work out the damage of a blow from it
  • Wield weapons and wear armor, with the old ones going back into the pack
  • Make plate armor noisy, so that walking in it wakes things up
  • Show what the player wields and wears, in the side panel and in the pack

There are four checkpoints along the way. Let's start with the plan.

Planning the Chapter

This chapter adds no files. It changes ten, along with the tile atlas:

File Status What it's for
src/Item.h Changed Weapons and armor in the table of kinds, a picture for every kind, and which kinds stack
src/Player.h Changed The weapon in the player's hand, the armor they wear, and the damage of their blows
src/View.h, src/TileView.cpp Changed Pictures that keep their own colors
src/Common.h Changed The colors of weapons and armor in the glyph view
src/Inventory.h, src/Inventory.cpp Changed A slot of its own for each weapon and suit of armor
src/Identification.cpp Changed No disguises for weapons and armor
src/Game.h, src/Game.cpp Changed Wielding, wearing, noisy plate, and the equipment in the panel and the pack
assets/tiles.png, assets/tiles.txt Changed Pictures of the weapons and armor
The other files Unchanged

The next image shows how the files fit together at the end of the chapter. Nothing is new, and there are no new arrows. The player already reaches the items through the pack, so a weapon in the hand is just one more item the player holds.

The files at the end of Chapter 13
Figure 13.2 — The files at the end of Chapter 13

Weapons and armor join the table of kinds, so the table's rows now differ. A potion or a scroll has an effect, a weapon has damage, and a suit of armor has a defense. Every row gives the fields its kind needs, and leaves the others at zero.

The player gets two places to put things that aren't in the pack: a hand, and a back. Each holds one item, or none, which is exactly what std::optional is for. Using a weapon from the pack moves it into the hand, and the one that was there goes back into the pack. Armor works the same way, on the back.

This chapter uses seed 2 again. The table of kinds is longer now, so the dice that choose a kind for each item land differently, and the first room holds new things in the same two cells. We'll build the chapter in four steps: weapons on the floor, a weapon in hand, armor, and the equipment on show.

Weapons

The first step brings five kinds of weapon into the dungeon. They lie on the floor, in both views, and you can pick them up, carry them and drop them. Wielding one comes in the next step.

Five Weapons

Open Item.h. The comment above ItemClass said that Chapter 13 adds weapons and armor, and here they are. Replace the comment above enum class ItemClass with this:

// The kinds of thing an item can be

Then add the new class at the end of ItemClass, below Scroll,:

Weapon,

A weapon isn't drunk or read, so it has no effect to play. Every kind needs some value in its effect field, though, so add one that means nothing, at the top of ItemEffect, above Heal:

None,         // isn't drunk or read

Each kind now carries more than a name and an effect. Replace the comment above struct ItemDef with this:

// What every item of one kind has in common. Chapter 16 moves these into
// a text file. A potion or a scroll has an effect; a weapon has damage

Then add three fields below ItemEffect effect;:

const char* tile;   // its picture's name in tiles.txt
int minDamage = 0;
int maxDamage = 0;

A kind's tile names its picture in tiles.txt, just as each kind of monster's tile has since Chapter 8. Until now, itemLook chose a potion's or a scroll's picture by its class, and with weapons there are too many pictures for that. The damage is a range, as a monster's is: each blow rolls a number from the first to the second.

The two damage fields have values written in place, as count in Item does. A row of the table can stop before its last fields, and any it leaves out take those values. So a potion's row can stop at its tile, and its damage is zero, which suits a potion. The tile has no such value, since every kind needs a picture, so every row must give one.

The table needs a tile in every row, and five new rows. First, replace the comment above ITEM_DEFS with this:

// Every kind of item there is, so far. Chapter 14 adds the potions whose
// effects last (poison, regeneration and confusion) and the scroll of
// fear, and Chapter 21 bows, arrows, darts, and the scrolls of lightning
// and fireball

Then find the table's rows:

{ "healing", ItemClass::Potion, ItemEffect::Heal },
{ "greater healing", ItemClass::Potion, ItemEffect::HealFully },
{ "strength", ItemClass::Potion, ItemEffect::Strengthen },
{ "teleportation", ItemClass::Scroll, ItemEffect::Teleport },
{ "magic mapping", ItemClass::Scroll, ItemEffect::MapFloor },
{ "identify", ItemClass::Scroll, ItemEffect::Identify },

Replace them with these rows, which give every kind its tile, and add the five weapons:

// name, class, effect, tile, then a weapon's damage from and to
{ "healing", ItemClass::Potion, ItemEffect::Heal, "potion" },
{ "greater healing", ItemClass::Potion, ItemEffect::HealFully,
  "potion" },
{ "strength", ItemClass::Potion, ItemEffect::Strengthen, "potion" },
{ "teleportation", ItemClass::Scroll, ItemEffect::Teleport, "scroll" },
{ "magic mapping", ItemClass::Scroll, ItemEffect::MapFloor, "scroll" },
{ "identify", ItemClass::Scroll, ItemEffect::Identify, "scroll" },
{ "dagger", ItemClass::Weapon, ItemEffect::None, "dagger", 1, 4 },
{ "short sword", ItemClass::Weapon, ItemEffect::None, "sword", 2, 6 },
{ "spear", ItemClass::Weapon, ItemEffect::None, "spear", 2, 8 },
{ "mace", ItemClass::Weapon, ItemEffect::None, "mace", 3, 7 },
{ "long sword", ItemClass::Weapon, ItemEffect::None, "sword", 3, 9 },

The potions and scrolls stop at their tiles. Greater healing's row is too long for one line, so it carries on below, as the monsters' rows do. Each weapon gives its damage, from the dagger's one to four, up to the long sword's three to nine. The short sword's two to six is the damage the player has done since Chapter 6.

Notice that the spear and the mace do the same five on average, and differ only in how far they stray from it. A spear can do two, or eight; a mace never does less than three, or more than seven. The two swords share a picture, since at this size a sword is a sword, and so the tile view draws both the same. The next image shows three rows of the table, and the fields each one uses.

Three kinds of row
Figure 13.3 — Three kinds of row

Every row has the same fields, since every row is an ItemDef, but each kind reads only the ones it needs. Armor's two fields join the table later in the chapter.

Pictures That Keep Their Colors

The tile view tints a potion's picture, a pale gray flask, with the potion's color. That way, one picture serves every color of potion. A weapon's picture is already colored, with a wooden handle and a steel blade, and tinting it would spoil it. So the look of an item must say whether its tile wants tinting. Open View.h, and replace the comment above struct ItemLook with this:

// How an item looks: a character and its color in the glyph view, and a
// tile in the tile view. A potion's or a scroll's tile is tinted with the
// same color; a weapon's keeps its own colors

Add a field to say which, below const char* tile;:

bool tinted;

The tile view acts on it. Open TileView.cpp, and replace the comment above drawItem with this:

// An item's tile is tinted with its color, if it takes one. A remembered
// one is tinted by the memory's tint too: the two are multiplied, as color
// mods multiply

Then, in drawItem, change SDL_Color tint = look.color; to SDL_Color tint = look.tinted ? look.color : NO_TINT;. The tint NO_TINT is white, which changes nothing, since it multiplies every color by one. An untinted picture keeps its own colors. A remembered weapon is still darkened by the memory's tint, in the lines below, as every remembered item is.

In the glyph view, a weapon is a ), as it was in Rogue, in a color of its own. Open Common.h, and add the color at the end of Palette, below SCROLL:

// Weapons, in the glyph view
constexpr SDL_Color WEAPON = { 190, 200, 215, 255 };

Now the game can say how each kind looks. Open Game.cpp, and replace the whole of itemLook with this:

// How an item looks: a potion is a ! in its color, a scroll a ?, and a
// weapon a ). A weapon's tile keeps its own colors
ItemLook Game::itemLook(const Item& item) const
{
    const ItemDef& def = *item.def;
    if (def.itemClass == ItemClass::Weapon)
    {
        return { ')', Palette::WEAPON, def.tile, false };
    }
    SDL_Color color = identification_.appearanceOf(def).color;
    char glyph = def.itemClass == ItemClass::Potion ? '!' : '?';
    return { glyph, color, def.tile, true };
}

Each weapon is a ) in the weapons' color, with its own picture, untinted. A potion or a scroll is drawn as before, but its picture's name comes from its kind, and its look asks for tinting. The weapon is dealt with first, and returns early, since it has no look in the identification game for appearanceOf to find.

The tile view needs the new pictures. From the book's repository, copy tiles.png and tiles.txt from the Chapter 13 folder's assets folder into your project's assets folder, in place of the old ones. They hold the armor's pictures too, ready for later in the chapter.

No Disguises

A weapon has no disguise: a sword looks like a sword, and the player knows one when they see it. So its name is simply its kind's name. In itemName, add this above the line bool potion = def.itemClass == ItemClass::Potion;, with a blank line in between:

if (def.itemClass == ItemClass::Weapon)
{
    // A weapon needs no identifying: one look says what it is
    return withArticle(def.name);
}

The function withArticle, from Chapter 6, puts "a" or "an" in front of the name, so the log says "You pick up a long sword." The rest of itemName, with its colors and titles, is only for potions and scrolls now.

The identification game must leave weapons alone, too. Open Identification.cpp. Its randomize gives the potions colors, and everything else a title. As it stands, every weapon would get a made-up title it never shows. In randomize, change else to else if (def.itemClass == ItemClass::Scroll).

That leaves a weapon with no look at all, which isKnown doesn't expect. A kind is known once it's learned. But a weapon is never drunk or read, so it would never be learned. Then a scroll of identify would offer to identify your sword, and the game would announce "You identify a long sword." as if that were news. Replace the whole of isKnown with this:

// A kind with no look, such as a weapon, isn't disguised: the player
// knows it at a glance
bool Identification::isKnown(const ItemDef& def) const
{
    return !appearances_.contains(&def) || known_.contains(&def);
}

A kind with nothing in appearances_ is known at a glance. The || stops at the first test that's true, so for a weapon, known_ is never asked. Potions and scrolls always have looks, and are known only once they've been learned, as before.

One to a Slot

Potions of one kind stack, since there's no telling them apart, and nothing to choose between. Weapons are different: you hold one at a time, so each takes a slot of its own, where you can choose it. Whether a kind stacks is a question about the kind, so it's a member function of ItemDef. In Item.h, add it below maxDamage:

// Potions and scrolls stack: any number of one kind share a slot in
// the pack, or a cell on the floor. Anything else is one of its own
bool stacks() const
{
    return itemClass == ItemClass::Potion ||
           itemClass == ItemClass::Scroll;
}

The function is const, since asking a kind whether it stacks changes nothing. That's more than good manners here. Every row of the table is constexpr, and so const. Only const member functions can be called on a const object.

The pack asks before it joins a stack. Open Inventory.cpp. In add, change if (carried.def == item.def) to if (item.def->stacks() && carried.def == item.def). A weapon then never joins anything, and takes a free slot of its own, or none if the pack is full.

Then open Inventory.h, and replace the comment above bool add(const Item& item); with this:

// Adds the items, onto a stack of the same kind if their kind stacks
// and there is one. Returns false, and adds nothing, if they need a
// slot and there's none free

The floor needs the same rule. A cell holds one stack, and a dropped stack may join one of its own kind there. So two long swords dropped in the same place would become "2 long swords", picked up together and wielded together. In Game.cpp, replace the comment above dropItem with this:

// Drops a whole stack from the pack where the player stands, which takes a
// turn. A cell holds one stack, so it can only join one of its own kind,
// and only if its kind stacks

Then, in dropItem, change here->item.def != kind to (here->item.def != kind || !kind->stacks()). Dropping a sword onto any item at all now says "There's already something here." The parentheses matter: without them, && would bind tighter than ||, and a weapon couldn't be dropped anywhere, even on bare floor.

Checkpoint: Press F5, with seed 2 in Command Arguments. The first room has changed. Where Chapter 12 had a potion, two steps up and to the right, there's a scroll. And where it had a scroll, three steps down and to the right, there's a long sword, ). Press F2, and the sword is a picture in its own colors, as the next image shows.

A long sword, in both views
Figure 13.4 — A long sword, in both views

Press F2 again, and walk onto the sword, with n three times: "You see a long sword here." Pick it up with g, and open the pack with i: "a) a long sword". Now press a. "You read a long sword.", and the sword is gone. Using an item drinks it or reads it, whatever it is, and a sword is neither. Teaching the pack to wield is the next step.

A Weapon in Hand

The player has done two to six with every blow since Chapter 6, from two fields of Player. From this step on, a blow does whatever the weapon in hand does. The weapon can change at any moment. So rather than store the damage and keep it in step, the player works it out from the weapon whenever it's asked.

Damage, Worked Out

Open Player.h. The player's hand might hold a weapon, or nothing, so it's a std::optional, as a monster's target is. Find this line at the top:

#include <string>   // std::string, for what killed the player

Replace it with these lines, which add <optional>:

#include <optional>   // std::optional, for an empty hand
#include <string>     // std::string, for what killed the player

The comment above the struct promised weapons in this chapter. Replace the comment above struct Player : Entity with this:

// The player

Then find the two fields that held the damage:

int minDamage = 2;
int maxDamage = 6;

Replace them with the damage of bare fists:

// The damage of a blow with bare fists
static constexpr int FIST_MIN_DAMAGE = 1;
static constexpr int FIST_MAX_DAMAGE = 2;

They're static constexpr, as the pack's SLOTS is: one value for the whole struct, fixed when the program is compiled. Next, the hand itself. Find the pack:

// What they carry
Inventory inventory;

Replace it with the pack and the hand:

// What they carry, and the weapon in their hand, if any
Inventory inventory;
std::optional<Item> weapon;

Last, add the two functions that work out the damage, below weapon:

// Each blow does minDamage() to maxDamage(), before strength and the
// target's defense: the weapon's damage, or the fists'
int minDamage() const
{
    return weapon ? weapon->def->minDamage : FIST_MIN_DAMAGE;
}

int maxDamage() const
{
    return weapon ? weapon->def->maxDamage : FIST_MAX_DAMAGE;
}

The test weapon ? asks whether the optional holds a weapon, as Chapter 11 asked whether a monster had a target. Then weapon->def reaches into it, as -> reaches through a pointer. With a weapon in hand, a blow does its kind's damage, and without one, the fists'. Both functions are const, since working out the damage changes nothing.

The functions have the old fields' names, so the code that read the fields needs only a pair of parentheses. Open Game.cpp. In playerAttacks, change player_.minDamage, player_.maxDamage to player_.minDamage(), player_.maxDamage().

The player should start with a short sword in hand, which does Chapter 6's two to six. That way, nothing about the first fights changes. To look up the short sword's row by its name, Item.h gets a small search. Open Item.h, and add an include above #include "Common.h":

#include <string>   // std::string, to find a kind by its name

Then add the search below the table:

// The kind with this name, or nullptr if there isn't one
inline const ItemDef* findItemDef(const std::string& name)
{
    for (const ItemDef& def : ITEM_DEFS)
    {
        if (name == def.name)
        {
            return &def;
        }
    }
    return nullptr;
}

Each kind's name is compared with the one asked for, and the first that matches gives its address. The comparison works because name is a std::string, which knows how to compare itself with a C string such as def.name. A name that isn't in the table gives nullptr. The function is inline, as a function written in a header must be, so that every file that includes Item.h shares one copy.

In Game.cpp, in the constructor, add these lines below identification_.randomize(rng_);:

// The player starts with a short sword in hand
player_.weapon = Item{ findItemDef("short sword") };

The braces make an Item on the spot: its first member is the kind, and its count keeps its value of one. Assigning it to the optional puts it in the hand. The name must match the table exactly. A typo would give nullptr, and the game would crash the first time it asked the weapon for its damage.

The side panel has shown the player's strength, as "Strength +0". Now that the damage can change, the damage itself is more useful, with strength already added. In drawSidePanel, find these lines:

glyphs_.drawText("Strength +" + std::to_string(player_.strength), x, y,
                 Palette::TEXT);

Replace them with these:

// The damage of a blow, with strength added
int low = player_.minDamage() + player_.strength;
int high = player_.maxDamage() + player_.strength;
glyphs_.drawText("Damage " + std::to_string(low) + "-" +
                 std::to_string(high), x, y, Palette::TEXT);

The panel now says "Damage 2-6". Its numbers move when the weapon changes, or when a potion of strength adds one to both. They don't count the defense of whatever you hit, which depends on the monster. The next image shows where a blow's damage comes from.

The damage of a blow
Figure 13.5 — The damage of a blow

Each blow rolls a number in the weapon's range, adds strength, and takes away the target's defense. Chapter 6's rollDamage does all three, and makes sure that every blow does at least one, however good the armor.

Wielding

Wielding a weapon from the pack takes a turn, and puts the one you held back into the pack. Add wield below pickUp:

// Wields a weapon from the pack, which takes a turn. The one in hand, if
// there is one, goes back into the pack
void Game::wield(int slot)
{
    state_ = GameState::Playing;
    log_.markRead();
    Item weapon = player_.inventory.removeStack(slot);
    if (player_.weapon)
    {
        player_.inventory.add(*player_.weapon);
        log_.add("You put away " + itemName(*player_.weapon) + ".");
    }
    player_.weapon = weapon;
    log_.add("You wield " + itemName(weapon) + ".");
    finishTurn();
}

The new weapon leaves the pack first, with removeStack, so there's always a free slot for the old one. Then if (player_.weapon) asks whether the hand holds anything. If it does, that weapon goes into the pack, and the log says so. The order matters. With a full pack, adding the old weapon first would fail, and it would be lost.

Add the declaration to Game.h, below useItem's:

void wield(int slot);

Choosing a weapon in the pack should wield it, rather than read it. In Game.cpp, replace the comment above useItem with this:

// Uses an item from the pack, which takes a turn. A weapon is wielded. A
// potion is drunk, or a scroll read: its kind is known from then on, so
// the player learns what it was, and then sees what it does

Then add this at the top of useItem, above state_ = GameState::Playing;, with a blank line in between:

ItemClass itemClass = player_.inventory.items()[slot].def->itemClass;
if (itemClass == ItemClass::Weapon)
{
    wield(slot);
    return;
}

The item's class goes into a local, itemClass, since armor will ask it too, later in the chapter. A weapon goes to wield, and return leaves useItem before anything is drunk or read. The next image shows what wielding does to the hand and the pack.

Wielding a long sword
Figure 13.6 — Wielding a long sword

The swap is the whole of equipment, and armor will use it too. Nothing is ever in two places, and nothing is lost.

Checkpoint: Press F5, with seed 2. The side panel says "Damage 2-6", the short sword's. Walk onto the long sword, with n three times, and pick it up. Open the pack with i, and press a: "You put away a short sword." and "You wield a long sword." The panel says "Damage 3-9" now, and the pack holds the short sword, in slot a.

Wield the short sword again, and they swap back. Each wielding takes a turn, so changing weapons in the middle of a fight gives the monster a free blow.

Armor

The third step adds three suits of armor. Wearing one sets the player's defense. Every monster's blow has subtracted it since Chapter 6, when it was always zero. Plate armor is the best of them, and also the noisiest.

Three Suits

Armor is a class of item too. In Item.h, add it below Weapon,:

Armor,

Armor isn't drunk or read either, so its effect is None, as a weapon's is. Each suit has a defense, though, and may be noisy. Replace the comment above struct ItemDef with this:

// What every item of one kind has in common. Chapter 16 moves these into
// a text file. A potion or a scroll has an effect; a weapon has damage;
// armor has defense, and may be noisy

Add armor's two fields below maxDamage:

int defense = 0;
bool noisy = false;

Like the damage, both have values written in place: no defense, and no noise, for everything that isn't armor. Armor doesn't stack either, and stacks already says so, since only potions and scrolls do.

The table's own comment lists the fields, and needs the new ones. Replace the comment above { "healing" with this:

// name, class, effect, tile, then a weapon's damage from and to, and
// armor's defense and noise

Then add the three suits at the end of the table, below the long sword:

{ "leather armor", ItemClass::Armor, ItemEffect::None, "leather-armor",
  0, 0, 1 },
{ "chain mail", ItemClass::Armor, ItemEffect::None, "metal-armor",
  0, 0, 3 },
{ "plate armor", ItemClass::Armor, ItemEffect::None, "metal-armor",
  0, 0, 5, true },

A row gives its fields in order, so armor's rows fill in the two damage fields with zeros, to reach the defense after them. Leather armor takes one off every blow, chain mail three, and plate armor five. Plate's true makes it noisy. Chain mail and plate share a picture of metal armor.

In the glyph view, armor is a [, as it is in NetHack (Rogue used ]). Open Common.h, and replace the comment above constexpr SDL_Color WEAPON with this:

// Weapons and armor, in the glyph view

Then add the armor's color below it:

constexpr SDL_Color ARMOR = { 175, 150, 115, 255 };

In Game.cpp, in itemLook, add these lines below the weapon's if:

if (def.itemClass == ItemClass::Armor)
{
    return { '[', Palette::ARMOR, def.tile, false };
}

Armor is drawn as a weapon is, in its own colors, but with its own character. Replace the comment above itemLook with this:

// How an item looks: a potion is a ! in its color, a scroll a ?, a weapon
// a ), and armor a [. Weapons' and armor's tiles keep their own colors

Armor's name needs a small change too. In itemName, add this below the weapon's if:

if (def.itemClass == ItemClass::Armor)
{
    // Nor does armor, which takes no "a": "chain mail"
    return def.name;
}

You put on "chain mail", not "a chain mail", so armor's name goes out as it is, with no article. The log then says "You pick up plate armor." and the look panel "Plate armor."

Wearing It

The player wears armor as they hold a weapon, in an optional of its own. Open Player.h, and replace the comment above Inventory inventory; with this:

// What they carry, the weapon in their hand, and the armor they wear,
// if any. Armor sets the player's defense

Then add the armor below weapon:

std::optional<Item> armor;

Wearing is wielding again, with one more line. In Game.cpp, add wear below wield:

// Puts on armor from the pack, which takes a turn. Armor already worn, if
// there is any, goes back into the pack. The new armor's defense is the
// player's
void Game::wear(int slot)
{
    state_ = GameState::Playing;
    log_.markRead();
    Item armor = player_.inventory.removeStack(slot);
    if (player_.armor)
    {
        player_.inventory.add(*player_.armor);
        log_.add("You take off " + itemName(*player_.armor) + ".");
    }
    player_.armor = armor;
    player_.defense = armor.def->defense;
    log_.add("You put on " + itemName(armor) + ".");
    finishTurn();
}

Here, the new armor's defense becomes the player's. The monsters' blows already subtract player_.defense, in monsterAttacks, so armor works at once, with no change to the fighting. Add the declaration to Game.h, below wield's:

void wear(int slot);

The damage is worked out from the weapon each time, but the defense is stored when the armor goes on. That's because defense belongs to Entity, and every monster has one too, read by the same rule. A stored value can fall out of step, so the places that set it must be few, and for now wear is the only one. Chapter 17's loading of a saved game adds a second, and Chapter 22 gathers the two into one function, updateDefense.

Now useItem has two classes to send elsewhere. Replace the comment above useItem with this:

// Uses an item from the pack, which takes a turn. A weapon is wielded, and
// armor worn. A potion is drunk, or a scroll read: its kind is known from
// then on, so the player learns what it was, and then sees what it does

Then add armor's test below the weapon's, at the top of useItem, to send armor to wear:

if (itemClass == ItemClass::Armor)
{
    wear(slot);
    return;
}

The side panel should show the defense. In drawSidePanel, add these lines below the damage's:

glyphs_.drawText("Defense " + std::to_string(player_.defense), x, y,
                 Palette::TEXT);
y += row;

The panel says "Defense 0" until you put something on. The next image shows what each suit does to the blows of each monster you've met so far.

What armor does to each blow
Figure 13.7 — What armor does to each blow

Every blow does at least one, so against these monsters, plate does little that chain mail doesn't. Only the goblin hits hard enough to tell them apart. Chain mail already makes a giant white rat's bite a single point, every time. Deeper floors, from Chapter 15, have monsters that hit hard enough for plate to matter.

Design note: Armor in Rogue. Rogue had eight kinds of armor, from leather armor to plate mail. Each gave an armor class, counted down from 10, for none, to 3, for plate. Armor class made you harder to hit, and that was all it did: a blow that landed did its full damage. NetHack kept the idea, and added a little. Once your armor class goes below zero, it also takes a random amount off each blow, though never below 1. Our armor only softens blows, since none miss, but it softens every one, by its whole defense. You can see what it's worth in every line of the log.

Plate is the best armor in the table, so the next section gives it a price.

Noisy Plate

Plate armor clanks. Each step in it makes a noise, as a fight does, and Chapter 11's noise wakes what hears it. In Game.cpp, add the noise of a step to the unnamed namespace, below SHRIEK_LOUDNESS:

constexpr int FOOTSTEP_LOUDNESS = 6;   // walking in noisy armor

A loudness of six reaches six steps. Any sleeper wakes if at least three of it is left when it arrives. So plate wakes anything asleep within three steps of where you walk. Wanderers hear it anywhere within six, and come to see what it was. In playerStep, find these lines:

// Arriving on an item, the player sees what it is
bool moved = step.x != 0 || step.y != 0;

Replace them with these:

// Walking in noisy armor makes a noise. Arriving on an item, the
// player sees what it is
bool moved = step.x != 0 || step.y != 0;
if (moved && player_.armor && player_.armor->def->noisy)
{
    makeNoise(player_.pos, FOOTSTEP_LOUDNESS);
}

A noise is made only when the player really moved. The period key's step of nowhere passes through playerStep too. Standing still in plate, waiting, should make no sound. Notice that player_.armor && player_.armor->def->noisy asks first whether there's any armor, and only then whether it's noisy, since && stops at the first test that's false.

Design note: The price of plate. If the best armor cost nothing, nobody would wear anything else, so roguelikes often charge for heavy armor. In NetHack, for example, a suit of metal armor makes spells more likely to fail. Ours charges in noise, since Chapter 11 made noise matter. In leather, a sleeping goblin wakes only if it happens to notice you, so you can choose your fights. Plate lets it hear you three steps away, and you fight whatever you pass. On this one floor, though, every blow against plate does just one, so its price is small until Chapter 15's deeper floors.

Checkpoint: Press F5, with seed 2. The first room has changed again, since the table has three more kinds. Two steps up and to the right lies a dagger, and three steps down and to the right, plate armor, [. Walk onto the armor, pick it up, and wear it, with i and a: "You put on plate armor." The panel says "Defense 5".

Now press F4, to see the monsters' minds, and walk to the room below, where Chapter 11's shrieker sleeps. Go one step left, with h, and one down and to the left, with b, into the doorway. Then walk down the corridor and into the room with j. On your seventh step down, "The shrieker wakes up!", while you're still three steps from it. The next image shows that moment, beside the same walk with the plate only carried.

The same walk, with the plate carried and worn
Figure 13.8 — The same walk, with the plate carried and worn

Wait a few turns with the period key, and it shrieks. Everything that hears it heads for the shriek, a few steps from you. Then press F5, and try the walk with the plate in your pack instead, as on the left of the image. The shrieker sleeps on, since armor in a pack makes no noise.

Showing the Equipment

The last step shows what the player wields and wears, in words, in two places: the side panel, and the top of the pack. Both say the same, so one function makes each line. In Game.cpp, add them above drawInventory:

// What the player is wielding and wearing, in words, for the panel and the
// inventory
std::string Game::wieldingText() const
{
    if (player_.weapon)
    {
        return "Wielding " + itemName(*player_.weapon);
    }
    return "Fighting with bare fists";
}

std::string Game::wearingText() const
{
    if (player_.armor)
    {
        return "Wearing " + itemName(*player_.armor);
    }
    return "Wearing no armor";
}

An empty hand is "Fighting with bare fists", and an empty back "Wearing no armor". The names come from itemName, as everywhere else, so plate armor is "Wearing plate armor", with no article. Declare the two functions in Game.h, below drawInventory's:

std::string wieldingText() const;
std::string wearingText() const;

In Game.cpp, in drawSidePanel, add these lines below the defense's:

// What the player has in hand and on their back
glyphs_.drawText(wieldingText(), x, y, Palette::DIM_TEXT);
y += row;
glyphs_.drawText(wearingText(), x, y, Palette::DIM_TEXT);
y += row;

The lines are dim, as the seed is, since they describe rather than count. The pack's dialog needs three more rows for them, two for the lines and one for a gap. In drawInventory, change (Inventory::SLOTS + 5) to (Inventory::SLOTS + 8).

Then add the equipment above const std::vector<Item>& items = player_.inventory.items();, leaving a blank line after it:

// The equipment first, which isn't in the pack, then the pack
glyphs_.drawText(wieldingText(), x, y, Palette::DIM_TEXT);
y += row;
glyphs_.drawText(wearingText(), x, y, Palette::DIM_TEXT);
y += 2 * row;

The equipment comes first, above the pack's lettered slots. It isn't in the pack, and has no letter to choose it by. An empty pack still says "Your pack is empty.", below what you wield and wear.

Checkpoint: Press F5, with seed 2. The panel says "Wielding a short sword" and "Wearing no armor". Pick up the dagger, two steps up and to the right, and then the plate armor, four steps down and one down and to the right from there. Open the pack with i, put the plate on with b, and open the pack again. The equipment is listed above the dagger, as Figure 13.1 showed, and the panel says the same.

Playing the Game

Every weapon and suit you find is a choice now. A long sword is better than a short sword at both ends: 3 to 9, against 2 to 6. But the spear and the mace are a matter of taste: the same average, with the spear's blows varying more. The dagger is the worst of the five, though Chapter 21 lets you throw it.

For now, plate is simply the best armor. It makes this floor's monsters almost harmless, so the fights its noise starts cost you little. The choice gets real in Chapter 15, when the dungeon goes deeper, its monsters hit hard enough to get through plate, and plate itself turns up rarely. Then waking everything you pass means more fighting than you'd like, and leather lets you choose which monsters to wake. Carrying both, and changing for a fight, costs a turn each way, so it's best done before the fight starts.

Understanding the Code

Weapons and armor are rows of ITEM_DEFS, like potions and scrolls, with fields the others leave at zero: a weapon's damage, and armor's defense and noise. Every kind names its own picture, and an ItemLook says whether to tint it. That lets weapons and armor keep their colors. Only potions and scrolls stack, as ItemDef::stacks says, and both the pack and the floor ask it. A kind with no look in the identification game is known at a glance.

The player holds a weapon and wears armor, each a std::optional<Item>. The damage of a blow is derived: minDamage and maxDamage work it out from the weapon, or the fists, every time they're asked. The defense is stored, by wear, since it belongs to Entity, and the monsters read it there. Using a weapon or armor from the pack swaps it with what was there, in wield and wear. Plate armor makes a noise with every step.

Experimenting

Try each of these, and put each back afterward:

  • Bare fists. Delete the line in the constructor that puts a short sword in the player's hand. The panel says "Damage 1-2" and "Fighting with bare fists", and every fight takes much longer.
  • A giant's sword. In the table, change the short sword's 2, 6 to 20, 60. You start with one, so the panel says "Damage 20-60", and nothing you meet survives a blow.
  • Quiet plate. Set FOOTSTEP_LOUDNESS to 0. Walk to the shrieker in plate, and it sleeps on.
  • Loud plate. Set FOOTSTEP_LOUDNESS to 20. Every step wakes anything asleep within seventeen steps, and you'll soon have company.

The last two show how much a single number can change the way a game plays. A tuning constant is worth getting right, which is what Chapter 27's test bot is for.

Common Errors and Fixes

If the game didn't build, or doesn't behave as described, the problem is probably one of these.

C2440: 'initializing': cannot convert from 'const char [7]' to 'ItemEffect', in Item.h, followed by C2131. A weapon's row has left out its effect, as in { "dagger", ItemClass::Weapon, "dagger", 1, 4 }, so the tile's name lands where the effect should be. The values fill the fields in order, so the row needs ItemEffect::None before its tile.

Potions and scrolls are gray in the tile view, though weapons look right. The last line of itemLook is missing its true, as in return { glyph, color, def.tile };. A field left out of the braces is set to zero, which for a bool means false, so no item asks to be tinted.

A weapon or a suit of armor is a pink square in the tile view. The old tiles.txt has no pictures called dagger, sword, mace, spear, leather-armor and metal-armor. Copy tiles.png and tiles.txt from the Chapter 13 folder.

A scroll of identify offers to identify a weapon, and says "You identify a long sword." The function isKnown is still Chapter 12's, with return known_.contains(&def);. Weapons have no look, so they're never learned, and so they're never known. Replace the whole of isKnown, as in No Disguises.

C2662: 'bool ItemDef::stacks(void)': cannot convert 'this' pointer from 'const ItemDef' to 'ItemDef &', in Inventory.cpp and Game.cpp. The function stacks is missing its const. The kinds are reached through pointers to const, and only const member functions can be called through them.

Dropping a weapon always says "There's already something here.", even on bare floor. The test in dropItem is missing its parentheses, as in here->item.def != kind || !kind->stacks(). Since && binds tighter than ||, the test is true whenever the kind doesn't stack, whatever is on the floor.

C2039: 'def': is not a member of 'std::optional<Item>', in Player.h. The weapon is reached with a dot, as in weapon.def, which asks the optional itself for a def. Use weapon->def, which reaches the item inside it.

C3867: 'Player::minDamage': non-standard syntax; use '&' to create a pointer to member, and the same for maxDamage. A call to one of them is missing its parentheses, as in player_.minDamage in playerAttacks. They're functions now, so they need ().

C2039: 'string': is not a member of 'std', in Item.h, followed by several more. The include for <string> is missing from the top of Item.h, and findItemDef needs it.

Wielding a weapon also reads one: "You read a short sword." The return below wield(slot); in useItem is missing. After wielding, useItem carries on, and uses whatever is in that slot now. Here, that's the short sword, which has just gone back into the pack.

FAQ

Q) Why does the player start with a short sword?

A) The game's first fights were set up in Chapter 6, around blows of two to six. A short sword does exactly that, so this chapter changes how the damage is worked out without changing the damage. Rogue's hero started armed too, with a mace, a bow and arrows, and a suit of ring mail.

Q) Why can't I take my armor off?

A) You can swap it for other armor, but not take it off and wear nothing. There's little reason to go without armor, except to take off noisy plate and creep. A key just for that would be one more key for a rare need. The AI exercise below adds one, if you'd like it.

Q) Why is the damage worked out by a function, but the defense stored in a field?

A) The defense belongs to Entity, so the monsters have one too. And the code that resolves a blow reads it the same way for both. The player's changes only when armor goes on, so storing it is safe, as long as everything that sets it agrees: Chapter 22 makes sure of that, with one function, updateDefense. The damage is the player's alone, and it depends on a weapon that can be swapped, or missing. So working it out each time is simpler than keeping a copy in step.

AI Exercise (Optional)

Taking armor off would be a small command, in the shape of wear. If you'd like to add it with an AI's help, open your AI chatbot of choice, and try a prompt like this:

"I'm writing a roguelike in C++ with SDL 3. A struct, Player, has an Inventory inventory, with bool add(const Item& item), and a std::optional<Item> armor, and an int defense from its base struct, Entity. My Game class has void wear(int slot), which takes armor from the pack, puts any armor already worn back in the pack, sets player_.defense to the new armor's defense, logs what happened with log_.add, and ends the turn with finishTurn(). The keys pressed while playing are handled in void handlePlayingKey(const SDL_KeyboardEvent& key). I'd like a W key that takes off the armor being worn: it goes back into the pack, the defense goes back to 0, and it takes a turn. If nothing is worn, or the pack is full, it should say so, and take no turn. Show me the code, with each curly brace on its own line, and explain it."

Notice what the preceding prompt does. It names the functions and members the answer must use, and says exactly what should happen in each case, including the two that should fail. An answer that forgets one of them is easy to spot. The key is W, since it's one of the few letters the game never uses. It won't clash with a key that a later chapter adds.

When the answer comes back, check that the armor goes into the pack before the defense changes, and that a full pack leaves the armor where it is. Then test it with seed 2: wear the plate, take it off with W, and the panel should go from "Defense 5" to "Defense 0".

Summary

The player now holds a weapon and wears armor, each an optional item of its own. The damage of a blow is derived from the weapon every time it's needed. Armor sets the defense that monsters' blows already respected, and plate armor's noise makes the best defense a loud one. In Chapter 14, some potions work over time: poison that hurts every turn, regeneration that heals, and confusion that sends your steps astray, along with a scroll that sends monsters running.