Difference between revisions of "Complete roguelike tutorial using C++ and libtcod - extra 5: more generic items"
m (bullet points) |
(pasted →A generic target selector) |
||
Line 22: | Line 22: | ||
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 ? | 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"); | |||
} | |||
} | |||
[[Category:Developing]] | [[Category:Developing]] |
Revision as of 13:21, 23 October 2015
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"); } }