TombsOfHarc/src/Ai.cpp

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);
}