#include #include "libtcod.hpp" #include "Ai.h" #include "Actor.h" #include "Engine.h" #include "Map.h" #include "Destructible.h" #include "Attacker.h" #include "Gui.h" #include "Pickable.h" #include "Container.h" 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) { SDL_Event event; int dx = 0, dy = 0; int levelUpXp = getNextLevelXp(); if (owner->destructible->xp >= levelUpXp) { xpLevel++; owner->destructible->xp -= levelUpXp; engine->gui->message(engine->gui->lightYellow, "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(engine->context, engine->console, 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()) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: engine->gameStatus = Engine::QUIT; break; case SDL_EVENT_KEY_DOWN: if (event.key.key == SDLK_ESCAPE) { engine->gameStatus = Engine::PAUSE; } break; default: break; } } return; } while (SDL_PollEvent(&event)) { engine->context->convert_event_coordinates(event); switch (event.type) { case SDL_EVENT_MOUSE_MOTION: engine->mouse.cx = event.motion.x; engine->mouse.cy = event.motion.y; break; case SDL_EVENT_KEY_UP: switch (event.key.key) { case SDLK_UP: dy = -1; break; case SDLK_DOWN: dy = 1; break; case SDLK_LEFT: dx = -1; break; case SDLK_RIGHT: dx = 1; break; case SDLK_ESCAPE: engine->gameStatus = Engine::PAUSE; break; default: handleActionKey(owner, event.key.key, event.key.mod); break; } break; case SDL_EVENT_QUIT: engine->gameStatus = Engine::QUIT; 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(); } } } } void PlayerAi::handleActionKey(Actor* owner, int sdlkey, int mod) { switch (sdlkey) { case SDLK_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(engine->gui->lightGrey, "You pick up the %s.", actor->name.c_str()); break; } else if (!found) { found = true; engine->gui->message(engine->gui->red, "Your inventory is full."); } } } if (!found) { engine->gui->message(engine->gui->lightGrey, "There's nothing here that you can pick up."); } if (engine->gameStatus != Engine::QUIT) engine->gameStatus = Engine::NEW_TURN; } break; case SDLK_I: { Actor* actor = choseFromInventory(owner); if (actor) { actor->pickable->use(actor, owner); if (engine->gameStatus != Engine::QUIT) engine->gameStatus = Engine::NEW_TURN; } } break; case SDLK_D: // drop item { Actor* actor = choseFromInventory(owner); if (actor) { actor->pickable->drop(actor, owner); if (engine->gameStatus != Engine::QUIT) engine->gameStatus = Engine::NEW_TURN; } } break; case SDLK_PERIOD: if (mod & SDL_KMOD_SHIFT) { if (engine->stairs->x == owner->x && engine->stairs->y == owner->y) { engine->nextLevel(); } else { engine->gui->message(engine->gui->lightGrey, "There are no stairs here."); } } break; } } 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; } } 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(engine->gui->lightGrey, "There's a %s here.", actor->name.c_str()); } } owner->x = targetx; owner->y = targety; return true; } Actor* PlayerAi::choseFromInventory(Actor* owner) { static const int INVENTORY_WIDTH = 50; static const int INVENTORY_HEIGHT = 28; tcod::Console con = engine->context->new_console(INVENTORY_WIDTH, INVENTORY_HEIGHT); TCOD_ColorRGB fg(200, 180, 50); TCOD_ColorRGB bg(0, 0, 0); tcod::print_frame(con, { 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT }, "Inventory", &fg, &bg); char shortcut = 'a'; int y = 1; for (Actor** it = owner->container->inventory.begin(); it != owner->container->inventory.end(); it++) { Actor* actor = *it; tcod::print(con, { 1, y }, std::string("(") + shortcut + ") " + actor->name, TCOD_ColorRGB(255, 255, 255), std::nullopt, TCOD_LEFT); y++; shortcut++; } tcod::blit(*engine->console, con, { engine->screenWidth / 2 - INVENTORY_WIDTH / 2, engine->screenHeight / 2 - INVENTORY_HEIGHT / 2 }, { 0, 0, INVENTORY_WIDTH, INVENTORY_HEIGHT }); engine->context->present(*engine->console); SDL_Event event; while (1) { SDL_WaitEvent(&event); if (event.type == SDL_EVENT_KEY_UP) { if (event.key.key >= SDLK_A && event.key.key < SDLK_A + owner->container->inventory.size()) { return owner->container->inventory.get(event.key.key - SDLK_A); } return nullptr; } if (event.type == SDL_EVENT_QUIT) { engine->gameStatus = Engine::QUIT; return nullptr; } } } static const int TRACKING_TURNS = 3; 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) { // we can see the player. move towards him 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((float)dx * (float)dx + (float)dy * (float)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); } } 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; } } 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, NULL); break; } ai->load(zip); return ai; } void MonsterAi::load(TCODZip& zip) { moveCount = zip.getInt(); } void MonsterAi::save(TCODZip& zip) { zip.putInt(MONSTER); zip.putInt(moveCount); } 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); } void PlayerAi::load(TCODZip& zip) { } void PlayerAi::save(TCODZip& zip) { zip.putInt(PLAYER); }