Difference between revisions of "Complete roguelike tutorial using C++ and libtcod - extra 5: more generic items"

From RogueBasin
Jump to navigation Jump to search
(pasted →‎Effects)
(Add syntaxhighlight.)
 
(5 intermediate revisions by one other user not shown)
Line 27: Line 27:
All target selection algorithms but the one selecting the wearer use a range. So we create a class with an enum for the algorithm and a range field.
All target selection algorithms but the one selecting the wearer use a range. So we create a class with an enum for the algorithm and a range field.


class TargetSelector {
<syntaxhighlight lang="C++">
public :
class TargetSelector {
public :
     enum SelectorType {
     enum SelectorType {
         CLOSEST_MONSTER,
         CLOSEST_MONSTER,
Line 37: Line 38:
     TargetSelector(SelectorType type, float range);
     TargetSelector(SelectorType type, float range);
     void selectTargets(Actor *wearer, TCODList<Actor *> & list);
     void selectTargets(Actor *wearer, TCODList<Actor *> & list);
protected :
protected :
     SelectorType type;
     SelectorType type;
     float range;
     float range;
};
};
</syntaxhighlight>


There's no WEARER selector type. Well we'll simply assume that if the pickable has no TargetSelector, the effect applies to the wearer.
There's no WEARER selector type. Well we'll simply assume that if the pickable has no TargetSelector, the effect applies to the wearer.
Line 46: Line 48:
As usual, the constructor is trivial :
As usual, the constructor is trivial :


TargetSelector::TargetSelector(SelectorType type, float range)  
<syntaxhighlight lang="C++">
TargetSelector::TargetSelector(SelectorType type, float range)  
     : type(type), range(range) {
     : type(type), range(range) {
}
}
</syntaxhighlight>


The selectTargets method populate the list with all selected actors, depending on the algorithm. CLOSEST_MONSTER grabs the monster closest to the wearer :
The selectTargets method populate the list with all selected actors, depending on the algorithm. CLOSEST_MONSTER grabs the monster closest to the wearer :


void TargetSelector::selectTargets(Actor *wearer, TCODList<Actor *> & list) {
<syntaxhighlight lang="C++">
void TargetSelector::selectTargets(Actor *wearer, TCODList<Actor *> & list) {
     switch(type) {
     switch(type) {
         case CLOSEST_MONSTER :
         case CLOSEST_MONSTER :
Line 62: Line 67:
         }
         }
         break;
         break;
</syntaxhighlight>


SELECTED_MONSTER asks the player to chose an actor (possibly the player himself) :
SELECTED_MONSTER asks the player to chose an actor (possibly the player himself) :


case SELECTED_MONSTER :
<syntaxhighlight lang="C++">
{  
case SELECTED_MONSTER :
{  
     int x,y;
     int x,y;
     engine.gui->message(TCODColor::cyan, "Left-click to select an enemy,\nor right-click to cancel.");
     engine.gui->message(TCODColor::cyan, "Left-click to select an enemy,\nor right-click to cancel.");
Line 75: Line 82:
         }
         }
     }
     }
}
}
break;
break;
</syntaxhighlight>


WEARER_RANGE picks all actors close enough from the wearer (excluding the wearer himself) :
WEARER_RANGE picks all actors close enough from the wearer (excluding the wearer himself) :


case WEARER_RANGE :
<syntaxhighlight lang="C++">
case WEARER_RANGE :
     for (Actor **iterator=engine.actors.begin();
     for (Actor **iterator=engine.actors.begin();
             iterator != engine.actors.end(); iterator++) {
             iterator != engine.actors.end(); iterator++) {
Line 89: Line 98:
         }
         }
     }
     }
break;
break;
</syntaxhighlight>


and SELECTED_RANGE asks the player to pick a tile, then selects all actors close enough from this tile (possibly including the player)  :
and SELECTED_RANGE asks the player to pick a tile, then selects all actors close enough from this tile (possibly including the player)  :


case SELECTED_RANGE :
<syntaxhighlight lang="C++">
case SELECTED_RANGE :
     int x,y;
     int x,y;
     engine.gui->message(TCODColor::cyan, "Left-click to select a tile,\nor right-click to cancel.");
     engine.gui->message(TCODColor::cyan, "Left-click to select a tile,\nor right-click to cancel.");
Line 106: Line 117:
         }
         }
     }
     }
break;
break;
</syntaxhighlight>


Finally, if no monster has been selected, we display a message :
Finally, if no monster has been selected, we display a message :


<syntaxhighlight lang="C++">
     }
     }
     if ( list.isEmpty() ) {
     if ( list.isEmpty() ) {
         engine.gui->message(TCODColor::lightGrey,"No enemy is close enough");
         engine.gui->message(TCODColor::lightGrey,"No enemy is close enough");
     }
     }
}
}
</syntaxhighlight>


==Effects==
==Effects==
Line 121: Line 135:
Now that we can pick targets using a generic class, let's see what happens to those targets when the item is used. First things first, let's define an abstract effect class :
Now that we can pick targets using a generic class, let's see what happens to those targets when the item is used. First things first, let's define an abstract effect class :


class Effect {
<syntaxhighlight lang="C++">
public :
class Effect {
public :
     virtual bool applyTo(Actor *actor) = 0;
     virtual bool applyTo(Actor *actor) = 0;
};
};
</syntaxhighlight>


The method returns false if the effect could not be applied (for example healing someone who is not wounded).
The method returns false if the effect could not be applied (for example healing someone who is not wounded).
==Health effect==
Now let's define the effect that modify the health points :
<syntaxhighlight lang="C++">
class HealthEffect : public Effect {
public :
    float amount;
    const char *message;
    HealthEffect(float amount, const char *message);
    bool applyTo(Actor *actor);
};
</syntaxhighlight>
The effect is healing the target if amount is positive or hurting him if amount is negative. The message makes it possible to display some custom message. To keep things simple, we suppose that the message string always contains a parameter for the target name (%s) and a parameter for the amount (%g). But you can use NULL if you don't want a message to be displayed.
The applyTo function first checks that we're dealing with a destructible actor :
<syntaxhighlight lang="C++">
HealthEffect::HealthEffect(float amount, const char *message)
    : amount(amount), message(message) {
}
bool HealthEffect::applyTo(Actor *actor) {
    if (!actor->destructible) return false;
</syntaxhighlight>
Then deals with the healing part :
<syntaxhighlight lang="C++">
if ( amount > 0 ) {
    float pointsHealed=actor->destructible->heal(amount);
    if (pointsHealed > 0) {
        if ( message ) {
            engine.gui->message(TCODColor::lightGrey,message,actor->name,pointsHealed);
        }
        return true;
    }
</syntaxhighlight>
We display the message only if some health points were actually restored. Now the hurting part :
<syntaxhighlight lang="C++">
    } else {
            if ( message && -amount-actor->destructible->defense > 0 ) {
                engine.gui->message(TCODColor::lightGrey,message,actor->name,
                    -amount-actor->destructible->defense);
            }
            if ( actor->destructible->takeDamage(actor,-amount) > 0 ) {
                return true;
            }
    }
</syntaxhighlight>
Here we have to display the message before calling takeDamage because after, the actor might already be dead. You wouldn't want to read "the dead orc get burnt for x hit points".
If the target could not be healed/wounded, we return false :
<syntaxhighlight lang="C++">
    return false;
}
</syntaxhighlight>
==Some class to officialize temporary Ai==
The second effect has to temporary replace the target's  Ai. It can only be replaced by an Ai class that is capable of saving the previous Ai to get back to it once the effect ends. Let's improve the Ai class hierarchy by defining a TemporaryAi abstract class :
<syntaxhighlight lang="C++">
class TemporaryAi : public Ai {
public :
    TemporaryAi(int nbTurns);
    void update(Actor *owner);
    void applyTo(Actor *actor);
protected :
    int nbTurns;
    Ai *oldAi;
};
</syntaxhighlight>
The TemporaryAi class stores the number of turns and the previous Ai. The implementation is trivial as it mostly reuses code from the ConfusedMonsterAi class :
<syntaxhighlight lang="C++">
TemporaryAi::TemporaryAi(int nbTurns) : nbTurns(nbTurns) {
}
void TemporaryAi::update(Actor *owner) {
    nbTurns--;
    if ( nbTurns == 0 ) {
        owner->ai = oldAi;
        delete this;
    }
}
void TemporaryAi::applyTo(Actor *actor) {
    oldAi=actor->ai;
    actor->ai=this;
}
</syntaxhighlight>
Now we can simplify the ConfusedMonsterAi class by making it inherit from TemporaryAi :
<syntaxhighlight lang="C++">
class ConfusedMonsterAi : public TemporaryAi {
public :
    ConfusedMonsterAi(int nbTurns);
    void update(Actor *owner);
};
</syntaxhighlight>
<syntaxhighlight lang="C++">
ConfusedMonsterAi::ConfusedMonsterAi(int nbTurns)
    : TemporaryAi(nbTurns) {
}
void ConfusedMonsterAi::update(Actor *owner) {
    TCODRandom *rng=TCODRandom::getInstance();
    int dx=rng->getInt(-1,1);
    int dy=rng->getInt(-1,1);
    if ( dx != 0 || dy != 0 ) {
        int destx=owner->x+dx;
        int desty=owner->y+dy;
        if ( engine.map->canWalk(destx, desty) ) {
            owner->x = destx;
            owner->y = desty;
        } else {
            Actor *actor=engine.getActor(destx, desty);
            if ( actor ) {
                owner->attacker->attack(owner, actor);
            }
        }
    }
    TemporaryAi::update(owner);
}
</syntaxhighlight>
==An effect that changes the Ai==
We can know define our second effect :
<syntaxhighlight lang="C++">
class AiChangeEffect : public Effect {
public :
    TemporaryAi *newAi;
    const char *message;
    AiChangeEffect(TemporaryAi *newAi, const char *message);
    bool applyTo(Actor *actor);
};
</syntaxhighlight>
Once again, it's a very simple class :
<syntaxhighlight lang="C++">
AiChangeEffect::AiChangeEffect(TemporaryAi *newAi, const char *message)
    : newAi(newAi), message(message) {
}
bool AiChangeEffect::applyTo(Actor *actor) {
    newAi->applyTo(actor);
    if ( message ) {
        engine.gui->message(TCODColor::lightGrey,message,actor->name);
    }
    return true;
}
</syntaxhighlight>
==A more generic Pickable class==
It's time to use our new TargetSelector and Effect classes to improve the Pickable class.
<syntaxhighlight lang="C++">
class Pickable {
public :
    Pickable(TargetSelector *selector, Effect *effect);
    virtual ~Pickable();
    bool pick(Actor *owner, Actor *wearer);
    void drop(Actor *owner, Actor *wearer);
    bool use(Actor *owner, Actor *wearer);
protected :
    TargetSelector *selector;
    Effect *effect;
};
</syntaxhighlight>
The class will store a TargetSelector (NULL if the target is the wearer) and an Effect. We add a destructor to delete those fields when the Pickable is destroyed.
<syntaxhighlight lang="C++">
Pickable::Pickable(TargetSelector *selector, Effect *effect) :
    selector(selector), effect(effect) {
}
Pickable::~Pickable() {
    if ( selector ) delete selector;
    if ( effect ) delete effect;
}
</syntaxhighlight>
Now the use function determines the list of targets using the selector :
<syntaxhighlight lang="C++">
bool Pickable::use(Actor *owner, Actor *wearer) {
    TCODList<Actor *> list;
    if ( selector ) {
        selector->selectTargets(wearer, list);
    } else {
        list.push(wearer);
    }
</syntaxhighlight>
Then tries to apply the effect to the targets. We consider that the action is a success if the effect succeeds on at least one of the targets.
<syntaxhighlight lang="C++">
bool succeed=false;
for (Actor **it=list.begin(); it!=list.end(); it++) {
    if ( effect->applyTo(*it) ) {
        succeed=true;
    }
}
</syntaxhighlight>
In case of a success, we destroy the item :
<syntaxhighlight lang="C++">
    if ( succeed ) {
        if ( wearer->container ) {
            wearer->container->remove(owner);
            delete owner;
        }
    }
    return succeed;
}
</syntaxhighlight>
With this new powerful Pickable class, you can now safely remove all the previous item classes from the code :
*Healer
*Confuser
*LightningBolt
*Fireball
==Updating the Map::addItem function==
All the items now rely on the Pickable class, using appropriate target selectors and effects.
The health potion :
<syntaxhighlight lang="C++">
// create a health potion
Actor *healthPotion=new Actor(x,y,'!',"health potion",
    TCODColor::violet);
healthPotion->blocks=false;
healthPotion->pickable=new Pickable(NULL,new HealthEffect(4,NULL));
engine.actors.push(healthPotion);
</syntaxhighlight>
The scroll of lightning bolt :
<syntaxhighlight lang="C++">
// create a scroll of lightning bolt
Actor *scrollOfLightningBolt=new Actor(x,y,'#',"scroll of lightning bolt",
    TCODColor::lightYellow);
scrollOfLightningBolt->blocks=false;
scrollOfLightningBolt->pickable=new Pickable(
    new TargetSelector(TargetSelector::CLOSEST_MONSTER,5),
    new HealthEffect(-20,"A lighting bolt strikes the %s with a loud thunder!\n"
        "The damage is %g hit points."));
engine.actors.push(scrollOfLightningBolt);
</syntaxhighlight>
The scroll of fireball :
<syntaxhighlight lang="C++">
// create a scroll of fireball
Actor *scrollOfFireball=new Actor(x,y,'#',"scroll of fireball",
    TCODColor::lightYellow);
scrollOfFireball->blocks=false;
scrollOfFireball->pickable=new Pickable(
    new TargetSelector(TargetSelector::SELECTED_RANGE,3),
    new HealthEffect(-12,"The %s gets burned for %g hit points."));
engine.actors.push(scrollOfFireball);
</syntaxhighlight>
The scroll of confusion :
<syntaxhighlight lang="C++">
// create a scroll of confusion
Actor *scrollOfConfusion=new Actor(x,y,'#',"scroll of confusion",
    TCODColor::lightYellow);
scrollOfConfusion->blocks=false;
scrollOfConfusion->pickable=new Pickable(
    new TargetSelector(TargetSelector::SELECTED_MONSTER,5),
    new AiChangeEffect(new ConfusedMonsterAi(10),
        "The eyes of the %s look vacant,\nas he starts to stumble around!"));
engine.actors.push(scrollOfConfusion);
</syntaxhighlight>
That's it. You can now compile and well... the game is almost exactly the same. We lost some details in the process (for example having specific colors for the messages but we could easily add a TCODColor parameter to the effects' constructors), but now, you can add a new category of item we only a few lines of code, by either mixing existing selectors and effect or just adding a new effect.




[[Category:Developing]]
[[Category:Developing]]

Latest revision as of 08:48, 20 July 2022

Complete roguelike tutorial using C++ and libtcod
-originally written by Jice
Text in this tutorial was released under the Creative Commons Attribution-ShareAlike 3.0 Unported and the GNU Free Documentation License (unversioned, with no invariant sections, front-cover texts, or back-cover texts) on 2015-09-21.


This article is an optional "extra" that implements more generic items, making it possible to create new items with less code. It can be applied on article 9 source code.

In article 9, we brought more variety to the items, but each new item required a new class inheriting from Pickable. Yet, we can see some pattern in the way pickable actors behave :

They all select one or several targets :

  • the wearer of the item (health potion)
  • the wearer's closest enemy (lightning bolt)
  • a selected actor (confusion)
  • all actors close to a selected tile (fireball)

A fifth way that is not yet used but might be useful would be :

  • all actors close to the wearer of the item

They all apply some effect to the selected targets :

  • increase or decrease health (all but confusion)
  • temporary replace the target's Ai (confusion)

So instead of implementing the target selection and the effect in a class inheriting from Pickable, why not add a TargetSelector and an Effect directly to the Pickable class ?

A generic target selector

All target selection algorithms but the one selecting the wearer use a range. So we create a class with an enum for the algorithm and a range field.

class TargetSelector {
public :
    enum SelectorType {
        CLOSEST_MONSTER,
        SELECTED_MONSTER,
        WEARER_RANGE,
        SELECTED_RANGE      
    };
    TargetSelector(SelectorType type, float range);
    void selectTargets(Actor *wearer, TCODList<Actor *> & list);
protected :
    SelectorType type;
    float range;
};

There's no WEARER selector type. Well we'll simply assume that if the pickable has no TargetSelector, the effect applies to the wearer.

As usual, the constructor is trivial :

TargetSelector::TargetSelector(SelectorType type, float range) 
    : type(type), range(range) {
}

The selectTargets method populate the list with all selected actors, depending on the algorithm. CLOSEST_MONSTER grabs the monster closest to the wearer :

void TargetSelector::selectTargets(Actor *wearer, TCODList<Actor *> & list) {
    switch(type) {
        case CLOSEST_MONSTER :
        {
            Actor *closestMonster=engine.getClosestMonster(wearer->x,wearer->y,range);
            if ( closestMonster ) {
                list.push(closestMonster);
            }
        }
        break;

SELECTED_MONSTER asks the player to chose an actor (possibly the player himself) :

case SELECTED_MONSTER :
{ 
    int x,y;
    engine.gui->message(TCODColor::cyan, "Left-click to select an enemy,\nor right-click to cancel.");
    if ( engine.pickATile(&x,&y,range)) {
        Actor *actor=engine.getActor(x,y);
        if ( actor ) {
            list.push(actor);
        }
    }
}
break;

WEARER_RANGE picks all actors close enough from the wearer (excluding the wearer himself) :

case WEARER_RANGE :
    for (Actor **iterator=engine.actors.begin();
            iterator != engine.actors.end(); iterator++) {
        Actor *actor=*iterator;
        if ( actor != wearer && actor->destructible && !actor->destructible->isDead()
            && actor->getDistance(wearer->x,wearer->y) <= range) {              
            list.push(actor);
        }
    }
break;

and SELECTED_RANGE asks the player to pick a tile, then selects all actors close enough from this tile (possibly including the player)  :

case SELECTED_RANGE :
    int x,y;
    engine.gui->message(TCODColor::cyan, "Left-click to select a tile,\nor right-click to cancel.");
    if ( engine.pickATile(&x,&y)) {
        for (Actor **iterator=engine.actors.begin();
                iterator != engine.actors.end(); iterator++) {
            Actor *actor=*iterator;
            if ( actor->destructible && !actor->destructible->isDead()
                && actor->getDistance(x,y) <= range) {                
                list.push(actor);
            }
        }
    }
break;

Finally, if no monster has been selected, we display a message :

    }
    if ( list.isEmpty() ) {
        engine.gui->message(TCODColor::lightGrey,"No enemy is close enough");
    }
}

Effects

Now that we can pick targets using a generic class, let's see what happens to those targets when the item is used. First things first, let's define an abstract effect class :

class Effect {
public :
    virtual bool applyTo(Actor *actor) = 0;
};

The method returns false if the effect could not be applied (for example healing someone who is not wounded).

Health effect

Now let's define the effect that modify the health points :

class HealthEffect : public Effect {
public :
    float amount;
    const char *message;

    HealthEffect(float amount, const char *message);
    bool applyTo(Actor *actor); 
};

The effect is healing the target if amount is positive or hurting him if amount is negative. The message makes it possible to display some custom message. To keep things simple, we suppose that the message string always contains a parameter for the target name (%s) and a parameter for the amount (%g). But you can use NULL if you don't want a message to be displayed.

The applyTo function first checks that we're dealing with a destructible actor :

HealthEffect::HealthEffect(float amount, const char *message) 
    : amount(amount), message(message) {
}

bool HealthEffect::applyTo(Actor *actor) {
    if (!actor->destructible) return false;

Then deals with the healing part :

if ( amount > 0 ) {
    float pointsHealed=actor->destructible->heal(amount);
    if (pointsHealed > 0) {
        if ( message ) {
            engine.gui->message(TCODColor::lightGrey,message,actor->name,pointsHealed);
        }
        return true;
    }

We display the message only if some health points were actually restored. Now the hurting part :

    } else {
            if ( message && -amount-actor->destructible->defense > 0 ) {
                engine.gui->message(TCODColor::lightGrey,message,actor->name,
                    -amount-actor->destructible->defense);
            }
            if ( actor->destructible->takeDamage(actor,-amount) > 0 ) {
                return true;
            }
    }

Here we have to display the message before calling takeDamage because after, the actor might already be dead. You wouldn't want to read "the dead orc get burnt for x hit points".

If the target could not be healed/wounded, we return false :

    return false;
}

Some class to officialize temporary Ai

The second effect has to temporary replace the target's Ai. It can only be replaced by an Ai class that is capable of saving the previous Ai to get back to it once the effect ends. Let's improve the Ai class hierarchy by defining a TemporaryAi abstract class :

class TemporaryAi : public Ai {
public :
    TemporaryAi(int nbTurns);
    void update(Actor *owner);
    void applyTo(Actor *actor);
protected :
    int nbTurns;
    Ai *oldAi;
};

The TemporaryAi class stores the number of turns and the previous Ai. The implementation is trivial as it mostly reuses code from the ConfusedMonsterAi class :

TemporaryAi::TemporaryAi(int nbTurns) : nbTurns(nbTurns) {
}

void TemporaryAi::update(Actor *owner) {
    nbTurns--;
    if ( nbTurns == 0 ) {
        owner->ai = oldAi;
        delete this;
    }
}

void TemporaryAi::applyTo(Actor *actor) {
    oldAi=actor->ai;
    actor->ai=this;
}

Now we can simplify the ConfusedMonsterAi class by making it inherit from TemporaryAi :

class ConfusedMonsterAi : public TemporaryAi {
public :
    ConfusedMonsterAi(int nbTurns);
    void update(Actor *owner);
};
ConfusedMonsterAi::ConfusedMonsterAi(int nbTurns) 
    : TemporaryAi(nbTurns) {
}
 
void ConfusedMonsterAi::update(Actor *owner) {
    TCODRandom *rng=TCODRandom::getInstance();
    int dx=rng->getInt(-1,1);
    int dy=rng->getInt(-1,1);
    if ( dx != 0 || dy != 0 ) {
        int destx=owner->x+dx;
        int desty=owner->y+dy;
        if ( engine.map->canWalk(destx, desty) ) {
            owner->x = destx;
            owner->y = desty;
        } else {
            Actor *actor=engine.getActor(destx, desty);
            if ( actor ) {
                owner->attacker->attack(owner, actor);
            }
        }
    }
    TemporaryAi::update(owner);
}

An effect that changes the Ai

We can know define our second effect :

class AiChangeEffect : public Effect {
public :
    TemporaryAi *newAi;
    const char *message;

    AiChangeEffect(TemporaryAi *newAi, const char *message);
    bool applyTo(Actor *actor);
};

Once again, it's a very simple class :

AiChangeEffect::AiChangeEffect(TemporaryAi *newAi, const char *message) 
    : newAi(newAi), message(message) {
}

bool AiChangeEffect::applyTo(Actor *actor) {
    newAi->applyTo(actor);
    if ( message ) {
        engine.gui->message(TCODColor::lightGrey,message,actor->name);
    }
    return true;
}

A more generic Pickable class

It's time to use our new TargetSelector and Effect classes to improve the Pickable class.

class Pickable {
public :
    Pickable(TargetSelector *selector, Effect *effect);
    virtual ~Pickable();
    bool pick(Actor *owner, Actor *wearer);
    void drop(Actor *owner, Actor *wearer);
    bool use(Actor *owner, Actor *wearer);
protected :
    TargetSelector *selector;
    Effect *effect;
};

The class will store a TargetSelector (NULL if the target is the wearer) and an Effect. We add a destructor to delete those fields when the Pickable is destroyed.

Pickable::Pickable(TargetSelector *selector, Effect *effect) :
    selector(selector), effect(effect) {
}

Pickable::~Pickable() {
    if ( selector ) delete selector;
    if ( effect ) delete effect;
}

Now the use function determines the list of targets using the selector :

bool Pickable::use(Actor *owner, Actor *wearer) {
    TCODList<Actor *> list;
    if ( selector ) {
        selector->selectTargets(wearer, list);
    } else {
        list.push(wearer);
    }

Then tries to apply the effect to the targets. We consider that the action is a success if the effect succeeds on at least one of the targets.

bool succeed=false;
for (Actor **it=list.begin(); it!=list.end(); it++) {
    if ( effect->applyTo(*it) ) {
        succeed=true;
    }
}

In case of a success, we destroy the item :

    if ( succeed ) {
        if ( wearer->container ) {
            wearer->container->remove(owner);
            delete owner;
        }
    }
    return succeed; 
}

With this new powerful Pickable class, you can now safely remove all the previous item classes from the code :

  • Healer
  • Confuser
  • LightningBolt
  • Fireball

Updating the Map::addItem function

All the items now rely on the Pickable class, using appropriate target selectors and effects.

The health potion :

// create a health potion
Actor *healthPotion=new Actor(x,y,'!',"health potion",
    TCODColor::violet);
healthPotion->blocks=false;
healthPotion->pickable=new Pickable(NULL,new HealthEffect(4,NULL));
engine.actors.push(healthPotion);

The scroll of lightning bolt :

// create a scroll of lightning bolt 
Actor *scrollOfLightningBolt=new Actor(x,y,'#',"scroll of lightning bolt",
    TCODColor::lightYellow);
scrollOfLightningBolt->blocks=false;
scrollOfLightningBolt->pickable=new Pickable(
    new TargetSelector(TargetSelector::CLOSEST_MONSTER,5),
    new HealthEffect(-20,"A lighting bolt strikes the %s with a loud thunder!\n"
        "The damage is %g hit points."));
engine.actors.push(scrollOfLightningBolt);

The scroll of fireball :

// create a scroll of fireball
Actor *scrollOfFireball=new Actor(x,y,'#',"scroll of fireball",
    TCODColor::lightYellow);
scrollOfFireball->blocks=false;
scrollOfFireball->pickable=new Pickable(
    new TargetSelector(TargetSelector::SELECTED_RANGE,3),
    new HealthEffect(-12,"The %s gets burned for %g hit points."));
engine.actors.push(scrollOfFireball);

The scroll of confusion :

// create a scroll of confusion
Actor *scrollOfConfusion=new Actor(x,y,'#',"scroll of confusion",
    TCODColor::lightYellow);
scrollOfConfusion->blocks=false;
scrollOfConfusion->pickable=new Pickable(
    new TargetSelector(TargetSelector::SELECTED_MONSTER,5),
    new AiChangeEffect(new ConfusedMonsterAi(10),
        "The eyes of the %s look vacant,\nas he starts to stumble around!"));
engine.actors.push(scrollOfConfusion);

That's it. You can now compile and well... the game is almost exactly the same. We lost some details in the process (for example having specific colors for the messages but we could easily add a TCODColor parameter to the effects' constructors), but now, you can add a new category of item we only a few lines of code, by either mixing existing selectors and effect or just adding a new effect.