Generic Pickables
This commit is contained in:
parent
ae72140657
commit
d8a83e2768
291
src/Ai.cpp
291
src/Ai.cpp
@ -6,71 +6,108 @@
|
|||||||
// After losing his sight
|
// After losing his sight
|
||||||
static const int TRACKING_TURNS = 3;
|
static const int TRACKING_TURNS = 3;
|
||||||
|
|
||||||
Ai *Ai::create(TCODZip &zip) {
|
MonsterAi::MonsterAi() : moveCount(0) { }
|
||||||
AiType type=(AiType)zip.getInt();
|
void MonsterAi::update(Actor *owner) {
|
||||||
Ai *ai=NULL;
|
|
||||||
switch(type) {
|
|
||||||
case PLAYER: ai = new PlayerAi(); break;
|
|
||||||
case MONSTER: ai = new MonsterAi(); break;
|
|
||||||
case CONFUSED_MONSTER: ai = new ConfusedMonsterAi(0, NULL); break;
|
|
||||||
}
|
|
||||||
ai->load(zip);
|
|
||||||
return ai;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Player AI */
|
|
||||||
PlayerAi::PlayerAi() : xpLevel(1) { }
|
|
||||||
|
|
||||||
const int LEVEL_UP_BASE=200;
|
|
||||||
const int LEVEL_UP_FACTOR=150;
|
|
||||||
|
|
||||||
int PlayerAi::getNextLevelXp() {
|
|
||||||
return LEVEL_UP_BASE + xpLevel*LEVEL_UP_FACTOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayerAi::update(Actor *owner) {
|
|
||||||
int levelUpXp = getNextLevelXp();
|
|
||||||
if(owner->destructible->xp >= levelUpXp) {
|
|
||||||
xpLevel++;
|
|
||||||
owner->destructible->xp -= levelUpXp;
|
|
||||||
engine.gui->message(TCODColor::yellow, "Your battle skills grow stronger! You reached level %d", xpLevel);
|
|
||||||
engine.gui->menu.clear();
|
|
||||||
engine.gui->menu.addItem(Menu::CONSTITUTION, "Constitution (+20HP)");
|
|
||||||
engine.gui->menu.addItem(Menu::STRENGTH, "Strength (+1 Attack)");
|
|
||||||
engine.gui->menu.addItem(Menu::AGILITY, "Agility (+1 Defense)");
|
|
||||||
Menu::MenuItemCode menuItem=engine.gui->menu.pick(Menu::PAUSE);
|
|
||||||
switch(menuItem) {
|
|
||||||
case Menu::CONSTITUTION:
|
|
||||||
owner->destructible->maxHp+=20;
|
|
||||||
owner->destructible->hp+=20;
|
|
||||||
break;
|
|
||||||
case Menu::STRENGTH:
|
|
||||||
owner->attacker->power+=1;
|
|
||||||
break;
|
|
||||||
case Menu::AGILITY:
|
|
||||||
owner->destructible->defense+=1;
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(owner->destructible && owner->destructible->isDead()) {
|
if(owner->destructible && owner->destructible->isDead()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int dx=0, dy=0;
|
if(engine.map->isInFov(owner->x, owner->y)) {
|
||||||
|
// We can see the player, move towards him
|
||||||
|
moveCount=TRACKING_TURNS;
|
||||||
|
} else {
|
||||||
|
moveCount--;
|
||||||
|
}
|
||||||
|
if(moveCount > 0) {
|
||||||
|
moveOrAttack(owner, engine.player->x, engine.player->y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void MonsterAi::moveOrAttack(Actor *owner, int targetx, int targety) {
|
||||||
|
int dx = targetx - owner->x;
|
||||||
|
int dy = targety - owner->y;
|
||||||
|
int stepdx = (dx > 0 ? 1:-1);
|
||||||
|
int stepdy = (dy > 0 ? 1:-1);
|
||||||
|
float distance=sqrtf(dx*dx+dy*dy);
|
||||||
|
if(distance >= 2) {
|
||||||
|
dx = (int)(round(dx/distance));
|
||||||
|
dy = (int)(round(dy/distance));
|
||||||
|
if(engine.map->canWalk(owner->x+dx, owner->y+dy)) {
|
||||||
|
owner->x += dx;
|
||||||
|
owner->y += dy;
|
||||||
|
} else if(engine.map->canWalk(owner->x+stepdx, owner->y)) {
|
||||||
|
owner->x += stepdx;
|
||||||
|
} else if(engine.map->canWalk(owner->x, owner->y+stepdy)) {
|
||||||
|
owner->y += stepdy;
|
||||||
|
}
|
||||||
|
} else if(owner->attacker) {
|
||||||
|
owner->attacker->attack(owner, engine.player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void MonsterAi::load(TCODZip &zip) {
|
||||||
|
moveCount = zip.getInt();
|
||||||
|
}
|
||||||
|
void MonsterAi::save(TCODZip &zip) {
|
||||||
|
zip.putInt(MONSTER);
|
||||||
|
zip.putInt(moveCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
void TemporaryAi::load(TCODZip &zip) {
|
||||||
|
nbTurns=zip.getInt();
|
||||||
|
oldAi=Ai::create(zip);
|
||||||
|
}
|
||||||
|
void TemporaryAi::save(TCODZip &zip) {
|
||||||
|
zip.putInt(CONFUSED_MONSTER);
|
||||||
|
zip.putInt(nbTurns);
|
||||||
|
oldAi->save(zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerAi::update(Actor *owner) {
|
||||||
|
if(owner->destructible && owner->destructible->isDead()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int dx=0,dy=0;
|
||||||
switch(engine.lastKey.vk) {
|
switch(engine.lastKey.vk) {
|
||||||
case TCODK_UP: case TCODK_KP8: dy=-1; break;
|
case TCODK_UP: dy=-1; break;
|
||||||
case TCODK_DOWN: case TCODK_KP2: dy=1; break;
|
case TCODK_DOWN: dy=1; break;
|
||||||
case TCODK_LEFT: case TCODK_KP4: dx=-1; break;
|
case TCODK_LEFT: dx=-1; break;
|
||||||
case TCODK_RIGHT: case TCODK_KP6: dx=1; break;
|
case TCODK_RIGHT: dx=1; break;
|
||||||
case TCODK_KP7: dy=dx=-1; break;
|
|
||||||
case TCODK_KP9: dy=-1;dx=1; break;
|
|
||||||
case TCODK_KP1: dy=1;dx=-1; break;
|
|
||||||
case TCODK_KP3: dy=dx=1; break;
|
|
||||||
case TCODK_CHAR: handleActionKey(owner, engine.lastKey.c); break;
|
case TCODK_CHAR: handleActionKey(owner, engine.lastKey.c); break;
|
||||||
default:break;
|
default: break;
|
||||||
}
|
}
|
||||||
if(dx != 0 || dy != 0) {
|
if(dx != 0 || dy != 0) {
|
||||||
engine.gameStatus=Engine::NEW_TURN;
|
engine.gameStatus = Engine::NEW_TURN;
|
||||||
if(moveOrAttack(owner, owner->x+dx, owner->y+dy)) {
|
if(moveOrAttack(owner, owner->x+dx, owner->y+dy)) {
|
||||||
engine.map->computeFov();
|
engine.map->computeFov();
|
||||||
}
|
}
|
||||||
@ -106,6 +143,15 @@ bool PlayerAi::moveOrAttack(Actor *owner, int targetx, int targety) {
|
|||||||
|
|
||||||
void PlayerAi::handleActionKey(Actor *owner, int ascii) {
|
void PlayerAi::handleActionKey(Actor *owner, int ascii) {
|
||||||
switch(ascii) {
|
switch(ascii) {
|
||||||
|
case 'd': // Drop item
|
||||||
|
{
|
||||||
|
Actor *actor = chooseFromInventory(owner);
|
||||||
|
if(actor) {
|
||||||
|
actor->pickable->drop(actor, owner);
|
||||||
|
engine.gameStatus = Engine::NEW_TURN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'g': // pickup item
|
case 'g': // pickup item
|
||||||
{
|
{
|
||||||
bool found=false;
|
bool found=false;
|
||||||
@ -131,22 +177,13 @@ void PlayerAi::handleActionKey(Actor *owner, int ascii) {
|
|||||||
break;
|
break;
|
||||||
case 'i': // Display inventory
|
case 'i': // Display inventory
|
||||||
{
|
{
|
||||||
Actor *actor=choseFromInventory(owner);
|
Actor *actor=chooseFromInventory(owner);
|
||||||
if(actor) {
|
if(actor) {
|
||||||
actor->pickable->use(actor, owner);
|
actor->pickable->use(actor, owner);
|
||||||
engine.gameStatus=Engine::NEW_TURN;
|
engine.gameStatus=Engine::NEW_TURN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'd': // Drop item
|
|
||||||
{
|
|
||||||
Actor *actor = choseFromInventory(owner);
|
|
||||||
if(actor) {
|
|
||||||
actor->pickable->drop(actor, owner);
|
|
||||||
engine.gameStatus = Engine::NEW_TURN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '>':
|
case '>':
|
||||||
if(engine.stairs->x == owner->x && engine.stairs->y == owner->y) {
|
if(engine.stairs->x == owner->x && engine.stairs->y == owner->y) {
|
||||||
engine.nextLevel();
|
engine.nextLevel();
|
||||||
@ -157,13 +194,16 @@ void PlayerAi::handleActionKey(Actor *owner, int ascii) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Actor *PlayerAi::choseFromInventory(Actor *owner) {
|
Actor *PlayerAi::chooseFromInventory(Actor *owner) {
|
||||||
static const int INVENTORY_WIDTH=50;
|
static const int INVENTORY_WIDTH=50;
|
||||||
static const int INVENTORY_HEIGHT=28;
|
static const int INVENTORY_HEIGHT=28;
|
||||||
static TCODConsole con(INVENTORY_WIDTH, INVENTORY_HEIGHT);
|
static TCODConsole con(INVENTORY_WIDTH, INVENTORY_HEIGHT);
|
||||||
|
|
||||||
// Display the inventory frame
|
// Display the inventory frame
|
||||||
con.setDefaultForeground(TCODColor(200, 180, 50));
|
con.setDefaultForeground(TCODColor(200, 180, 50));
|
||||||
con.printFrame(0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT, true, TCOD_BKGND_DEFAULT, "inventory");
|
con.printFrame(0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT, true,
|
||||||
|
TCOD_BKGND_DEFAULT, "Inventory");
|
||||||
|
|
||||||
// Display the items with their keyboard shortcut
|
// Display the items with their keyboard shortcut
|
||||||
con.setDefaultForeground(TCODColor::white);
|
con.setDefaultForeground(TCODColor::white);
|
||||||
int shortcut='a';
|
int shortcut='a';
|
||||||
@ -175,11 +215,13 @@ Actor *PlayerAi::choseFromInventory(Actor *owner) {
|
|||||||
y++;
|
y++;
|
||||||
shortcut++;
|
shortcut++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// blit the inventory console on the root console
|
// blit the inventory console on the root console
|
||||||
TCODConsole::blit(&con, 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT,
|
TCODConsole::blit(&con, 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT,
|
||||||
TCODConsole::root, engine.screenWidth/2 - INVENTORY_WIDTH/2,
|
TCODConsole::root, engine.screenWidth/2 - INVENTORY_WIDTH/2,
|
||||||
engine.screenHeight/2 - INVENTORY_HEIGHT/2);
|
engine.screenHeight/2 - INVENTORY_HEIGHT/2);
|
||||||
TCODConsole::flush();
|
TCODConsole::flush();
|
||||||
|
|
||||||
// wait for a key press
|
// wait for a key press
|
||||||
TCOD_key_t key;
|
TCOD_key_t key;
|
||||||
TCODSystem::waitForEvent(TCOD_EVENT_KEY_PRESS, &key, NULL, true);
|
TCODSystem::waitForEvent(TCOD_EVENT_KEY_PRESS, &key, NULL, true);
|
||||||
@ -192,94 +234,29 @@ Actor *PlayerAi::choseFromInventory(Actor *owner) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ai *Ai::create(TCODZip &zip) {
|
||||||
|
AiType type=(AiType)zip.getInt();
|
||||||
|
Ai *ai=NULL;
|
||||||
|
switch(type) {
|
||||||
|
case PLAYER: ai = new PlayerAi(); break;
|
||||||
|
case MONSTER: ai = new MonsterAi(); break;
|
||||||
|
case CONFUSED_MONSTER: ai = new ConfusedMonsterAi(0); break;
|
||||||
|
}
|
||||||
|
ai->load(zip);
|
||||||
|
return ai;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Player AI */
|
||||||
|
PlayerAi::PlayerAi() : xpLevel(1) { }
|
||||||
|
|
||||||
|
const int LEVEL_UP_BASE=200;
|
||||||
|
const int LEVEL_UP_FACTOR=150;
|
||||||
|
|
||||||
|
int PlayerAi::getNextLevelXp() {
|
||||||
|
return LEVEL_UP_BASE + xpLevel*LEVEL_UP_FACTOR;
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerAi::load(TCODZip &zip) { }
|
void PlayerAi::load(TCODZip &zip) { }
|
||||||
void PlayerAi::save(TCODZip &zip) {
|
void PlayerAi::save(TCODZip &zip) {
|
||||||
zip.putInt(PLAYER);
|
zip.putInt(PLAYER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Monster AI */
|
|
||||||
void MonsterAi::update(Actor *owner) {
|
|
||||||
if(owner->destructible && owner->destructible->isDead()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(engine.map->isInFov(owner->x, owner->y)) {
|
|
||||||
// We can see the player. Move towards him.
|
|
||||||
moveCount=TRACKING_TURNS;
|
|
||||||
} else {
|
|
||||||
moveCount--;
|
|
||||||
}
|
|
||||||
if(moveCount > 0) {
|
|
||||||
moveOrAttack(owner, engine.player->x, engine.player->y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MonsterAi::moveOrAttack(Actor *owner, int targetx, int targety) {
|
|
||||||
int dx = targetx - owner->x;
|
|
||||||
int dy = targety - owner->y;
|
|
||||||
int stepdx = (dx > 0 ? 1:-1);
|
|
||||||
int stepdy = (dy > 0 ? 1:-1);
|
|
||||||
|
|
||||||
float distance=sqrtf(dx*dx+dy*dy);
|
|
||||||
|
|
||||||
if(distance >= 2) {
|
|
||||||
dx = (int)(round(dx/distance));
|
|
||||||
dy = (int)(round(dy/distance));
|
|
||||||
if(engine.map->canWalk(owner->x+dx, owner->y+dy)) {
|
|
||||||
owner->x += dx;
|
|
||||||
owner->y += dy;
|
|
||||||
} else if(engine.map->canWalk(owner->x + stepdx, owner->y)) {
|
|
||||||
owner->x += stepdx;
|
|
||||||
} else if(engine.map->canWalk(owner->x, owner->y + stepdy)) {
|
|
||||||
owner->y += stepdy;
|
|
||||||
}
|
|
||||||
} else if(owner->attacker) {
|
|
||||||
owner->attacker->attack(owner, engine.player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MonsterAi::load(TCODZip &zip) {
|
|
||||||
moveCount = zip.getInt();
|
|
||||||
}
|
|
||||||
void MonsterAi::save(TCODZip &zip) {
|
|
||||||
zip.putInt(MONSTER);
|
|
||||||
zip.putInt(moveCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
TemporaryAi::TemporaryAi(int nbTurns) : nbTurns(nbTurns) { }
|
|
||||||
/* RIGHT HERE */
|
|
||||||
|
|
||||||
ConfusedMonsterAi::ConfusedMonsterAi(int nbTurns, Ai *oldAi)
|
|
||||||
: nbTurns(nbTurns), oldAi(oldAi) { }
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nbTurns--;
|
|
||||||
if(nbTurns == 0) {
|
|
||||||
owner->ai = oldAi;
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void ConfusedMonsterAi::load(TCODZip &zip) {
|
|
||||||
nbTurns=zip.getInt();
|
|
||||||
oldAi=Ai::create(zip);
|
|
||||||
}
|
|
||||||
void ConfusedMonsterAi::save(TCODZip &zip) {
|
|
||||||
zip.putInt(CONFUSED_MONSTER);
|
|
||||||
zip.putInt(nbTurns);
|
|
||||||
oldAi->save(zip);
|
|
||||||
}
|
|
||||||
|
18
src/Ai.hpp
18
src/Ai.hpp
@ -20,11 +20,12 @@ public :
|
|||||||
protected :
|
protected :
|
||||||
bool moveOrAttack(Actor *owner, int targetx, int targety);
|
bool moveOrAttack(Actor *owner, int targetx, int targety);
|
||||||
void handleActionKey(Actor *owner, int ascii);
|
void handleActionKey(Actor *owner, int ascii);
|
||||||
Actor *choseFromInventory(Actor *owner);
|
Actor *chooseFromInventory(Actor *owner);
|
||||||
};
|
};
|
||||||
|
|
||||||
class MonsterAi : public Ai {
|
class MonsterAi : public Ai {
|
||||||
public :
|
public :
|
||||||
|
MonsterAi();
|
||||||
void update(Actor *owner);
|
void update(Actor *owner);
|
||||||
void load(TCODZip &zip);
|
void load(TCODZip &zip);
|
||||||
void save(TCODZip &zip);
|
void save(TCODZip &zip);
|
||||||
@ -38,18 +39,15 @@ public:
|
|||||||
TemporaryAi(int nbTurns);
|
TemporaryAi(int nbTurns);
|
||||||
void update(Actor *owner);
|
void update(Actor *owner);
|
||||||
void applyTo(Actor *actor);
|
void applyTo(Actor *actor);
|
||||||
protected:
|
|
||||||
int nbTurns;
|
|
||||||
Ai *oldAi;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ConfusedMonsterAi : public Ai {
|
|
||||||
public:
|
|
||||||
ConfusedMonsterAi(int nbTurns, Ai *oldAi);
|
|
||||||
void update(Actor *owner);
|
|
||||||
void load(TCODZip &zip);
|
void load(TCODZip &zip);
|
||||||
void save(TCODZip &zip);
|
void save(TCODZip &zip);
|
||||||
protected:
|
protected:
|
||||||
int nbTurns;
|
int nbTurns;
|
||||||
Ai *oldAi;
|
Ai *oldAi;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ConfusedMonsterAi : public TemporaryAi {
|
||||||
|
public:
|
||||||
|
ConfusedMonsterAi(int nbTurns);
|
||||||
|
void update(Actor *owner);
|
||||||
|
};
|
||||||
|
21
src/Map.cpp
21
src/Map.cpp
@ -178,28 +178,41 @@ void Map::addItem(int x, int y) {
|
|||||||
Actor *healthPotion = new Actor(x, y, '!', "Health Potion",
|
Actor *healthPotion = new Actor(x, y, '!', "Health Potion",
|
||||||
TCODColor::violet);
|
TCODColor::violet);
|
||||||
healthPotion->blocks=false;
|
healthPotion->blocks=false;
|
||||||
healthPotion->pickable=new Healer(4);
|
healthPotion->pickable=new Pickable(NULL, new HealthEffect(4, NULL));
|
||||||
engine.actors.push(healthPotion);
|
engine.actors.push(healthPotion);
|
||||||
} else if(dice < 70 + 10) {
|
} else if(dice < 70 + 10) {
|
||||||
// Create a scroll of lightning bolt
|
// Create a scroll of lightning bolt
|
||||||
Actor *scrollOfLightningBolt = new Actor(x, y, '#',
|
Actor *scrollOfLightningBolt = new Actor(x, y, '#',
|
||||||
"Scroll of lightning bolt", TCODColor::lightYellow);
|
"Scroll of lightning bolt", TCODColor::lightYellow);
|
||||||
scrollOfLightningBolt->blocks = false;
|
scrollOfLightningBolt->blocks = false;
|
||||||
scrollOfLightningBolt->pickable = new LightningBolt(5, 20);
|
scrollOfLightningBolt->pickable = new Pickable(
|
||||||
|
new TargetSelector(TargetSelector::CLOSEST_MONSTER, 5),
|
||||||
|
new HealthEffect(-20, "A lightning bolt strikes the %s with a loud CRACK!\n"
|
||||||
|
"The damage is %g hit points."
|
||||||
|
)
|
||||||
|
);
|
||||||
engine.actors.push(scrollOfLightningBolt);
|
engine.actors.push(scrollOfLightningBolt);
|
||||||
} else if(dice < 70 + 10 + 10) {
|
} else if(dice < 70 + 10 + 10) {
|
||||||
// Create a scroll of fireball
|
// Create a scroll of fireball
|
||||||
Actor *scrollOfFireball = new Actor(x, y, '#', "Scroll of fireball",
|
Actor *scrollOfFireball = new Actor(x, y, '#', "Scroll of fireball",
|
||||||
TCODColor::lightYellow);
|
TCODColor::lightYellow);
|
||||||
scrollOfFireball->blocks = false;
|
scrollOfFireball->blocks = false;
|
||||||
scrollOfFireball->pickable = new Fireball(3, 12);
|
scrollOfFireball->pickable = new Pickable(
|
||||||
|
new TargetSelector(TargetSelector::SELECTED_RANGE, 3),
|
||||||
|
new HealthEffect(-12, "The %s gets burned for %g points.")
|
||||||
|
);
|
||||||
engine.actors.push(scrollOfFireball);
|
engine.actors.push(scrollOfFireball);
|
||||||
} else {
|
} else {
|
||||||
// create a scroll of confusion
|
// create a scroll of confusion
|
||||||
Actor *scrollOfConfusion = new Actor(x, y, '#',
|
Actor *scrollOfConfusion = new Actor(x, y, '#',
|
||||||
"Scroll of confusion", TCODColor::lightYellow);
|
"Scroll of confusion", TCODColor::lightYellow);
|
||||||
scrollOfConfusion->blocks=false;
|
scrollOfConfusion->blocks=false;
|
||||||
scrollOfConfusion->pickable = new Confuser(10,8);
|
scrollOfConfusion->pickable = new Pickable(
|
||||||
|
new TargetSelector(TargetSelector::SELECTED_MONSTER, 5),
|
||||||
|
new AiChangeEffect(new ConfusedMonsterAi(10),
|
||||||
|
"The eyes of the %s look vacant,\nas it starts to stumble around."
|
||||||
|
)
|
||||||
|
);
|
||||||
engine.actors.push(scrollOfConfusion);
|
engine.actors.push(scrollOfConfusion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
256
src/Pickable.cpp
256
src/Pickable.cpp
@ -1,49 +1,6 @@
|
|||||||
#include "main.hpp"
|
#include "main.hpp"
|
||||||
|
|
||||||
bool Pickable::pick(Actor *owner, Actor *wearer) {
|
TargetSelector::TargetSelector(SelectorType type, float range) : type(type), range(range) { }
|
||||||
if(wearer->container && wearer->container->add(owner)) {
|
|
||||||
engine.actors.remove(owner);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Pickable::use(Actor *owner, Actor *wearer) {
|
|
||||||
if(wearer->container) {
|
|
||||||
wearer->container->remove(owner);
|
|
||||||
delete owner;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Pickable::drop(Actor *owner, Actor *wearer) {
|
|
||||||
if(wearer->container) {
|
|
||||||
wearer->container->remove(owner);
|
|
||||||
engine.actors.push(owner);
|
|
||||||
owner->x = wearer->x;
|
|
||||||
owner->y = wearer->y;
|
|
||||||
engine.gui->message(TCODColor::lightGrey, "%s drops a %s.",
|
|
||||||
wearer->name, owner->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pickable Factory */
|
|
||||||
Pickable *Pickable::create(TCODZip &zip) {
|
|
||||||
PickableType type=(PickableType)zip.getInt();
|
|
||||||
Pickable *pickable=NULL;
|
|
||||||
switch(type) {
|
|
||||||
case HEALER: pickable = new Healer(0); break;
|
|
||||||
case LIGHTNING_BOLT: pickable = new LightningBolt(0, 0); break;
|
|
||||||
case CONFUSER: pickable = new Confuser(0, 0); break;
|
|
||||||
case FIREBALL: pickable = new Fireball(0, 0); break;
|
|
||||||
}
|
|
||||||
pickable->load(zip);
|
|
||||||
return pickable;
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetSelector::TargetSelector(SelectorType type, float range) :
|
|
||||||
type(type), range(range) { }
|
|
||||||
void TargetSelector::selectTargets(Actor *wearer, TCODList<Actor *> & list) {
|
void TargetSelector::selectTargets(Actor *wearer, TCODList<Actor *> & list) {
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case CLOSEST_MONSTER: {
|
case CLOSEST_MONSTER: {
|
||||||
@ -95,114 +52,34 @@ void TargetSelector::selectTargets(Actor *wearer, TCODList<Actor *> & list) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sub-Pickables */
|
void TargetSelector::save(TCODZip &zip) {
|
||||||
Healer::Healer(float amount) : amount(amount) { }
|
int persistType = 0;
|
||||||
bool Healer::use(Actor *owner, Actor *wearer) {
|
switch(type) {
|
||||||
if(wearer->destructible) {
|
case CLOSEST_MONSTER: persistType = 1; break;
|
||||||
float amountHealed = wearer->destructible->heal(amount);
|
case SELECTED_MONSTER: persistType = 2; break;
|
||||||
if(amountHealed > 0) {
|
case WEARER_RANGE: persistType = 3; break;
|
||||||
return Pickable::use(owner, wearer);
|
case SELECTED_RANGE: persistType = 4; break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
zip.putInt(persistType);
|
||||||
}
|
|
||||||
void Healer::load(TCODZip &zip) {
|
|
||||||
amount=zip.getFloat();
|
|
||||||
}
|
|
||||||
void Healer::save(TCODZip &zip) {
|
|
||||||
zip.putInt(HEALER);
|
|
||||||
zip.putFloat(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
LightningBolt::LightningBolt(float range, float damage)
|
|
||||||
: range(range), damage(damage) { }
|
|
||||||
bool LightningBolt::use(Actor *owner, Actor *wearer) {
|
|
||||||
Actor *closestMonster = engine.getClosestMonster(
|
|
||||||
wearer->x, wearer->y, range);
|
|
||||||
if(!closestMonster) {
|
|
||||||
engine.gui->message(TCODColor::lightGrey, "No enemy is close enough to strike.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Hit closest monster for <damage> hit points
|
|
||||||
engine.gui->message(TCODColor::lightBlue,
|
|
||||||
"A lightning bolt strikes the %s with a loud thunder!\nThe damage is %g hit points.",
|
|
||||||
closestMonster->name, damage);
|
|
||||||
closestMonster->destructible->takeDamage(closestMonster, damage);
|
|
||||||
return Pickable::use(owner, wearer);
|
|
||||||
}
|
|
||||||
void LightningBolt::load(TCODZip &zip) {
|
|
||||||
range=zip.getFloat();
|
|
||||||
damage=zip.getFloat();
|
|
||||||
}
|
|
||||||
void LightningBolt::save(TCODZip &zip) {
|
|
||||||
zip.putInt(LIGHTNING_BOLT);
|
|
||||||
zip.putFloat(range);
|
|
||||||
zip.putFloat(damage);
|
|
||||||
}
|
|
||||||
|
|
||||||
Fireball::Fireball(float range, float damage)
|
|
||||||
: LightningBolt(range, damage) { }
|
|
||||||
bool Fireball::use(Actor *owner, Actor *wearer) {
|
|
||||||
engine.gui->message(TCODColor::cyan, "Left-click a target tile for the fireball,\nor right-click to cancel.");
|
|
||||||
int x, y;
|
|
||||||
if(!engine.pickATile(&x, &y)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Burn everything in <range> (including player)
|
|
||||||
engine.gui->message(TCODColor::orange, "The fireball explodes, burning everything within %g tiles!", range);
|
|
||||||
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) {
|
|
||||||
engine.gui->message(TCODColor::orange, "The %s gets burned for %g hit points.",
|
|
||||||
actor->name, damage);
|
|
||||||
actor->destructible->takeDamage(actor, damage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Pickable::use(owner, wearer);
|
|
||||||
}
|
|
||||||
void Fireball::save(TCODZip &zip) {
|
|
||||||
zip.putInt(FIREBALL);
|
|
||||||
zip.putFloat(range);
|
|
||||||
zip.putFloat(damage);
|
|
||||||
}
|
|
||||||
|
|
||||||
Confuser::Confuser(int nbTurns, float range)
|
|
||||||
: nbTurns(nbTurns), range(range) { }
|
|
||||||
bool Confuser::use(Actor *owner, Actor *wearer) {
|
|
||||||
engine.gui->message(TCODColor::cyan, "Left-click an enemy to confuse it,\nor right-click to cancel.");
|
|
||||||
int x, y;
|
|
||||||
if(!engine.pickATile(&x, &y, range)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Actor *actor = engine.getActor(x, y);
|
|
||||||
if(!actor) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Confuse the monster for <nbTurns> turns
|
|
||||||
Ai *confusedAi = new ConfusedMonsterAi(nbTurns, actor->ai);
|
|
||||||
actor->ai = confusedAi;
|
|
||||||
engine.gui->message(TCODColor::lightGreen, "The eyes of the %s look vacant,\nas he starts to stumble around!", actor->name);
|
|
||||||
return Pickable::use(owner, wearer);
|
|
||||||
}
|
|
||||||
void Confuser::load(TCODZip &zip) {
|
|
||||||
nbTurns=zip.getInt();
|
|
||||||
range=zip.getFloat();
|
|
||||||
}
|
|
||||||
void Confuser::save(TCODZip &zip) {
|
|
||||||
zip.putInt(CONFUSER);
|
|
||||||
zip.putInt(nbTurns);
|
|
||||||
zip.putFloat(range);
|
zip.putFloat(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
HealthEffect::HealthEffect(float amount, const char *message)
|
void TargetSelector::load(TCODZip &zip) {
|
||||||
: amount(amount), message(message) { }
|
int persistType = zip.getInt();
|
||||||
|
switch(persistType) {
|
||||||
|
case 1: type = CLOSEST_MONSTER; break;
|
||||||
|
case 2: type = SELECTED_MONSTER; break;
|
||||||
|
case 3: type = WEARER_RANGE; break;
|
||||||
|
case 4: type = SELECTED_RANGE; break;
|
||||||
|
}
|
||||||
|
range = zip.getFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
HealthEffect::HealthEffect(float amount, const char *message) : amount(amount), message(message) { }
|
||||||
bool HealthEffect::applyTo(Actor *actor) {
|
bool HealthEffect::applyTo(Actor *actor) {
|
||||||
if(!actor->destructible) return false;
|
if(!actor->destructible) return false;
|
||||||
if(amount > 0) {
|
if(amount > 0) {
|
||||||
float pointsHealed = actor->destructible->heal(amount);
|
float pointsHealed=actor->destructible->heal(amount);
|
||||||
if(pointsHealed > 0) {
|
if(pointsHealed > 0) {
|
||||||
if(message) {
|
if(message) {
|
||||||
engine.gui->message(TCODColor::lightGrey, message, actor->name, pointsHealed);
|
engine.gui->message(TCODColor::lightGrey, message, actor->name, pointsHealed);
|
||||||
@ -211,7 +88,7 @@ bool HealthEffect::applyTo(Actor *actor) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(message && -amount-actor->destructible->defense > 0) {
|
if(message && -amount-actor->destructible->defense > 0) {
|
||||||
engine.gui->message(TCODColor::lightGrey, message, actor->name,
|
engine.gui->message(TCODColor::lightGrey, message, actor->name,
|
||||||
-amount-actor->destructible->defense);
|
-amount-actor->destructible->defense);
|
||||||
}
|
}
|
||||||
if(actor->destructible->takeDamage(actor, -amount) > 0) {
|
if(actor->destructible->takeDamage(actor, -amount) > 0) {
|
||||||
@ -220,3 +97,92 @@ bool HealthEffect::applyTo(Actor *actor) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pickable::Pickable(TargetSelector *selector, Effect *effect)
|
||||||
|
: selector(selector), effect(effect) { }
|
||||||
|
Pickable::~Pickable() {
|
||||||
|
if(selector) delete selector;
|
||||||
|
if(effect) delete effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pickable::pick(Actor *owner, Actor *wearer) {
|
||||||
|
if(wearer->container && wearer->container->add(owner)) {
|
||||||
|
engine.actors.remove(owner);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pickable::drop(Actor *owner, Actor *wearer) {
|
||||||
|
if(wearer->container) {
|
||||||
|
wearer->container->remove(owner);
|
||||||
|
engine.actors.push(owner);
|
||||||
|
owner->x = wearer->x;
|
||||||
|
owner->y = wearer->y;
|
||||||
|
engine.gui->message(TCODColor::lightGrey, "%s drops a %s.",
|
||||||
|
wearer->name, owner->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pickable::use(Actor *owner, Actor *wearer) {
|
||||||
|
TCODList<Actor *> list;
|
||||||
|
if(selector) {
|
||||||
|
selector->selectTargets(wearer, list);
|
||||||
|
} else {
|
||||||
|
list.push(wearer);
|
||||||
|
}
|
||||||
|
bool succeed=false;
|
||||||
|
for(Actor **it=list.begin(); it!=list.end(); it++) {
|
||||||
|
if(effect->applyTo(*it)) {
|
||||||
|
succeed=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(succeed) {
|
||||||
|
if(wearer->container) {
|
||||||
|
wearer->container->remove(owner);
|
||||||
|
delete owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return succeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pickable::save(TCODZip &zip) {
|
||||||
|
zip.putInt(selector != NULL);
|
||||||
|
zip.putInt(effect != NULL);
|
||||||
|
if(selector) selector->save(zip);
|
||||||
|
//if(effect) effect->save(zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pickable::load(TCODZip &zip) {
|
||||||
|
bool hasSelector = zip.getInt();
|
||||||
|
bool hasEffect = zip.getInt();
|
||||||
|
if(hasSelector) {
|
||||||
|
selector = new TargetSelector(TargetSelector::CLOSEST_MONSTER, 0);
|
||||||
|
selector->load(zip);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if(hasEffect) {
|
||||||
|
effect = new Effect();
|
||||||
|
effect->load(zip);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Pickable Factory */
|
||||||
|
Pickable *Pickable::create(TCODZip &zip) {
|
||||||
|
Pickable *pickable=NULL;
|
||||||
|
pickable->load(zip);
|
||||||
|
return pickable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
class TargetSelector {
|
class TargetSelector : public Persistent {
|
||||||
public:
|
public:
|
||||||
enum SelectorType {
|
enum SelectorType {
|
||||||
CLOSEST_MONSTER,
|
CLOSEST_MONSTER,
|
||||||
@ -8,63 +8,35 @@ public:
|
|||||||
};
|
};
|
||||||
TargetSelector(SelectorType type, float range);
|
TargetSelector(SelectorType type, float range);
|
||||||
void selectTargets(Actor *wearer, TCODList<Actor *>& list);
|
void selectTargets(Actor *wearer, TCODList<Actor *>& list);
|
||||||
|
void save(TCODZip &zip);
|
||||||
|
void load(TCODZip &zip);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SelectorType type;
|
SelectorType type;
|
||||||
float range;
|
float range;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Effect {
|
class Effect /*: public Persistent */{
|
||||||
public:
|
public:
|
||||||
virtual bool applyTo(Actor *actor) = 0;
|
virtual bool applyTo(Actor *actor) = 0;
|
||||||
|
// void save(TCODZip &zip);
|
||||||
|
// void load(TCODZip &zip);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Pickable : public Persistent {
|
class Pickable : public Persistent {
|
||||||
public:
|
public:
|
||||||
|
Pickable(TargetSelector *selector, Effect *effect);
|
||||||
|
virtual ~Pickable();
|
||||||
bool pick(Actor *owner, Actor *wearer);
|
bool pick(Actor *owner, Actor *wearer);
|
||||||
void drop(Actor *owner, Actor *wearer);
|
void drop(Actor *owner, Actor *wearer);
|
||||||
virtual bool use(Actor *owner, Actor *wearer);
|
bool use(Actor *owner, Actor *wearer);
|
||||||
static Pickable *create(TCODZip &zip);
|
static Pickable *create(TCODZip &zip);
|
||||||
|
void save(TCODZip &zip);
|
||||||
|
void load(TCODZip &zip);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum PickableType {
|
TargetSelector *selector;
|
||||||
HEALER, LIGHTNING_BOLT, CONFUSER, FIREBALL
|
Effect *effect;
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Sub-Pickables */
|
|
||||||
class Healer : public Pickable {
|
|
||||||
public:
|
|
||||||
float amount; // how much?
|
|
||||||
Healer(float amount);
|
|
||||||
bool use(Actor *owner, Actor *wearer);
|
|
||||||
void load(TCODZip &zip);
|
|
||||||
void save(TCODZip &zip);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Aggro Spells */
|
|
||||||
class LightningBolt: public Pickable {
|
|
||||||
public:
|
|
||||||
float range, damage;
|
|
||||||
LightningBolt(float range, float damage);
|
|
||||||
bool use(Actor *owner, Actor *wearer);
|
|
||||||
void load(TCODZip &zip);
|
|
||||||
void save(TCODZip &zip);
|
|
||||||
};
|
|
||||||
|
|
||||||
class Fireball : public LightningBolt {
|
|
||||||
public:
|
|
||||||
Fireball(float range, float damage);
|
|
||||||
bool use(Actor *owner, Actor *wearer);
|
|
||||||
void save(TCODZip &zip);
|
|
||||||
};
|
|
||||||
|
|
||||||
class Confuser : public Pickable {
|
|
||||||
public:
|
|
||||||
int nbTurns;
|
|
||||||
float range;
|
|
||||||
Confuser(int nbTurns, float range);
|
|
||||||
bool use(Actor *owner, Actor *wearer);
|
|
||||||
void load(TCODZip &zip);
|
|
||||||
void save(TCODZip &zip);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Effects */
|
/* Effects */
|
||||||
@ -76,3 +48,11 @@ public:
|
|||||||
bool applyTo(Actor *actor);
|
bool applyTo(Actor *actor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AiChangeEffect : public Effect {
|
||||||
|
public:
|
||||||
|
TemporaryAi *newAi;
|
||||||
|
const char *message;
|
||||||
|
|
||||||
|
AiChangeEffect(TemporaryAi *newAi, const char *message);
|
||||||
|
bool applyTo(Actor *actor);
|
||||||
|
};
|
||||||
|
BIN
src/main.hpp.gch
BIN
src/main.hpp.gch
Binary file not shown.
Loading…
Reference in New Issue
Block a user