263 lines
7.3 KiB
C++
263 lines
7.3 KiB
C++
#include "main.hpp"
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
// How many turns the monster chases the player
|
|
// After losing his sight
|
|
static const int TRACKING_TURNS = 3;
|
|
|
|
MonsterAi::MonsterAi() : moveCount(0) { }
|
|
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) { }
|
|
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) {
|
|
case TCODK_UP: dy=-1; break;
|
|
case TCODK_DOWN: dy=1; break;
|
|
case TCODK_LEFT: dx=-1; break;
|
|
case TCODK_RIGHT: dx=1; break;
|
|
case TCODK_CHAR: handleActionKey(owner, engine.lastKey.c); break;
|
|
default: break;
|
|
}
|
|
if(dx != 0 || dy != 0) {
|
|
engine.gameStatus = Engine::NEW_TURN;
|
|
if(moveOrAttack(owner, owner->x+dx, owner->y+dy)) {
|
|
engine.map->computeFov();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PlayerAi::moveOrAttack(Actor *owner, int targetx, int targety) {
|
|
if(engine.map->isWall(targetx, targety)) return false;
|
|
// look for living actors to attack
|
|
for(Actor **iterator=engine.actors.begin();
|
|
iterator != engine.actors.end(); iterator++) {
|
|
Actor *actor=*iterator;
|
|
if(actor->destructible && !actor->destructible->isDead()
|
|
&& actor->x == targetx && actor->y == targety) {
|
|
owner->attacker->attack(owner, actor);
|
|
return false;
|
|
}
|
|
}
|
|
// Look for corpses or items
|
|
for(Actor **iterator=engine.actors.begin();
|
|
iterator != engine.actors.end(); iterator++) {
|
|
Actor *actor = *iterator;
|
|
bool corpseOrItem=(actor->destructible && actor->destructible->isDead())
|
|
|| actor->pickable;
|
|
if(corpseOrItem && actor->x == targetx && actor->y == targety) {
|
|
engine.gui->message(TCODColor::lightGrey, "There's a %s here\n", actor->name);
|
|
}
|
|
}
|
|
owner->x = targetx;
|
|
owner->y = targety;
|
|
return true;
|
|
}
|
|
|
|
void PlayerAi::handleActionKey(Actor *owner, int 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
|
|
{
|
|
bool found=false;
|
|
for(Actor **iterator=engine.actors.begin();
|
|
iterator != engine.actors.end(); iterator++) {
|
|
Actor *actor = *iterator;
|
|
if(actor->pickable && actor->x == owner->x && actor->y == owner->y) {
|
|
if(actor->pickable->pick(actor, owner)) {
|
|
found=true;
|
|
engine.gui->message(TCODColor::lightGrey, "Picked up the %s.", actor->name);
|
|
break;
|
|
} else if(!found) {
|
|
found = true;
|
|
engine.gui->message(TCODColor::red, "Your inventory is full.");
|
|
}
|
|
}
|
|
}
|
|
if(!found) {
|
|
engine.gui->message(TCODColor::lightGrey, "There's nothing here.");
|
|
}
|
|
engine.gameStatus=Engine::NEW_TURN;
|
|
}
|
|
break;
|
|
case 'i': // Display inventory
|
|
{
|
|
Actor *actor=chooseFromInventory(owner);
|
|
if(actor) {
|
|
actor->pickable->use(actor, owner);
|
|
engine.gameStatus=Engine::NEW_TURN;
|
|
}
|
|
}
|
|
break;
|
|
case '>':
|
|
if(engine.stairs->x == owner->x && engine.stairs->y == owner->y) {
|
|
engine.nextLevel();
|
|
} else {
|
|
engine.gui->message(TCODColor::lightGrey,"There are no stairs here.");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Actor *PlayerAi::chooseFromInventory(Actor *owner) {
|
|
static const int INVENTORY_WIDTH=50;
|
|
static const int INVENTORY_HEIGHT=28;
|
|
static TCODConsole con(INVENTORY_WIDTH, INVENTORY_HEIGHT);
|
|
|
|
// Display the inventory frame
|
|
con.setDefaultForeground(TCODColor(200, 180, 50));
|
|
con.printFrame(0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT, true,
|
|
TCOD_BKGND_DEFAULT, "Inventory");
|
|
|
|
// Display the items with their keyboard shortcut
|
|
con.setDefaultForeground(TCODColor::white);
|
|
int shortcut='a';
|
|
int y=1;
|
|
for(Actor **it=owner->container->inventory.begin();
|
|
it != owner->container->inventory.end(); it++) {
|
|
Actor *actor = *it;
|
|
con.print(2, y, "(%c) %s", shortcut, actor->name);
|
|
y++;
|
|
shortcut++;
|
|
}
|
|
|
|
// blit the inventory console on the root console
|
|
TCODConsole::blit(&con, 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT,
|
|
TCODConsole::root, engine.screenWidth/2 - INVENTORY_WIDTH/2,
|
|
engine.screenHeight/2 - INVENTORY_HEIGHT/2);
|
|
TCODConsole::flush();
|
|
|
|
// wait for a key press
|
|
TCOD_key_t key;
|
|
TCODSystem::waitForEvent(TCOD_EVENT_KEY_PRESS, &key, NULL, true);
|
|
if(key.vk == TCODK_CHAR) {
|
|
int actorIndex = key.c - 'a';
|
|
if(actorIndex >= 0 && actorIndex < owner->container->inventory.size()) {
|
|
return owner->container->inventory.get(actorIndex);
|
|
}
|
|
}
|
|
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::save(TCODZip &zip) {
|
|
zip.putInt(PLAYER);
|
|
}
|