You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

915 lines
23 KiB
C++

#include <algorithm>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include "Game.hpp"
#include "ItemList.hpp"
#include "NPC.hpp"
#include "RLGame.hpp"
#include "utils.hpp"
using namespace std; // only cpp with this
unsigned int Game::m_RandomMapCount{ 0U }; // map
/*
* Main game functions
*/
Game::Game()
: m_NoUpMsg{ "You can't go up here." }
, m_NoDownMsg{ "You can't go down here." }
, m_NextTurn{ true }
, m_Player{ "" }
, m_NPCs{}
, m_Items{}
, m_CurrentLevel{}
, m_MapNumber{ static_cast<unsigned int>(utils::RandomBetween(0, 1000)) }
{
Init();
}
Game::~Game()
{
ShutDown();
}
void Game::Init()
{
G_CURRENT_MAP = new Map{ to_string(m_MapNumber) + "-" + to_string(m_RandomMapCount), false };
CreateItems(6);
SetupPlayer();
CreateNPCs(5);
size_t id{ m_NPCs.size() };
if((utils::RandomBetween(0, 100) < 1))
{
m_NPCs.push_back(new NPC{ "Regyr", SPECIES::LIZARD, m_Player.GetPosition() });
m_NPCs[id]->AddAttribute(ATTRIBUTES::DEFTNESS, 5);
m_NPCs[id]->SetFaction(ENTITY_FACTION::DEVOTED);
m_NPCs[id]->AddItem(G_ITEM_LIST->GetRandomItem());
m_Player.GetFriends().push_back(m_NPCs[id]);
#ifdef _DEBUG
//std::cout << "dev pos: " << to_string(position.x) << ", " << to_string(position.y) << '\n';
#endif // _DEBUG
}
else
{
const char* title{ "ENTER YOUR BFF'S NAME" };
Position boxPos{ G_SCREEN_WIDTH / 2 - 8, G_SCREEN_HEIGHT / 2 - 2 };
Position boxDimensions{ 2 + int(strlen(title)), 3 };
utils::DrawBox(boxPos.x, boxPos.y, boxDimensions.x, boxDimensions.y, G_LAYER_MENU, false, title);
std::string friendName{ AskString(true) };
utils::ClearBox(boxPos.x, boxPos.y, boxDimensions.x, boxDimensions.y, G_LAYER_MENU);
SPECIES friendSpecies{ AskSpecies(false) };
m_NPCs.push_back(new NPC{ friendName, friendSpecies, m_Player.GetPosition() });
m_NPCs[id]->SetFaction(ENTITY_FACTION::DEVOTED);
m_Player.GetFriends().push_back(m_NPCs[id]);
}
terminal_clear();
const string welcome{ GenerateWelcome(id) };
// TODO: fix the welcome message's position
terminal_print(G_SCREEN_WIDTH / 2 - 40/*- int(welcome.length()) / 2*/, G_SCREEN_HEIGHT / 2 - 1, welcome.c_str());
terminal_refresh();
GetKey(); // wait until key press to show welcome screen
Draw(); // clears the screen and draws everything for the first time before going to Run() and waiting for input
}
void Game::ShutDown()
{
delete G_CURRENT_MAP;
ClearNPCsExit();
ClearItems();
const int maxGoodbyes{ 5 };
const char* goodbyes[maxGoodbyes]{ "Bye", "See you later", "Adios", "Goodbye", "Auf Wiedersehen" };
cout << '\n' << goodbyes[utils::RandomBetweenSimple(0, maxGoodbyes - 1)] << ' ' << m_Player.GetName() << '!' << endl;
}
// TODO: add Saving and Loading
void Game::Load()
{
ifstream saveFile{ "Saves/" + m_Player.GetFullName() + '-' + to_string(m_MapNumber) };
}
void Game::Save()
{
}
// if true, wait until inputs
int Game::GetKey(bool wait) const
{
/*if(terminal_has_input())
return terminal_read();
else
return 0;*/
if(wait) // wait until terminal_read() returns
return terminal_read();
else if(terminal_has_input()) // check if terminal_read() will return
return terminal_read();
else
return -1;
}
void Game::Run()
{
int input{};
//std::chrono::steady_clock::time_point t1{ std::chrono::steady_clock::now() }, t2{};
while(!G_QUIT)
{
input = GetKey(true); // GetKey() waits until a key is pressed EDIT 2020-05-some day: not anymore
if(input != -1)
{
m_NextTurn = true; // if no valid key is pressed, NextTurn = false in HandleInput() so just skip Update() and Draw()
HandleInput(input);
if(!G_QUIT)
{
// each TURN
if(m_NextTurn)
{
//++G_TURNS;
Update();
Draw();
}
}
}
// each FRAME
//t2 = std::chrono::steady_clock::now();
//float elapsedSec{ std::chrono::duration<float>(t2 - t1).count() };
//t1 = t2;
//UpdateRealTime(elapsedSec);
//DrawRealTime();
}
G_QUIT_GAME();
}
// Gets called each TURN
void Game::Update()
{
for(auto& npc : m_NPCs)
{
if(!npc->IsDead())
{
npc->Update(m_NPCs);
}
else // delete dead NPCs, will have to be changed if someday there's a way to resurrect one
{
vector<Entity*>::iterator it{ find(m_NPCs.begin(), m_NPCs.end(), npc) };
if(it != m_NPCs.end())
{
delete npc;
m_NPCs.erase(it);
}
}
}
}
// Gets called after Update()
// terminal_put() and terminal_print() put chars and strings into a buffer which gets drawn when terminal_refresh() is called
// terminal_clear() clears the buffer and screen
void Game::Draw() const
{
#ifdef BEARLIBTERMINAL_H
terminal_clear(); // clear screen
terminal_layer(G_LAYER_MAIN);
#endif // BEARLIBTERMINAL_H
// level
G_CURRENT_MAP->Draw(m_Player.GetPosition());
// items
terminal_color("yellow");
for(auto& itemTile : m_Items)
terminal_put(itemTile.position.x + G_OFFSET_X, itemTile.position.y + G_OFFSET_Y, itemTile.item->itemSymbol);
// player +
for(auto& npc : m_NPCs)
npc->Draw();
m_Player.Draw();
// menu stuff, name + position
DrawMenu();
/* // effect sucks
if(G_CH_FUNNY_MODE)
terminal_bkcolor(color_from_argb(uint8_t(63u), uint8_t(utils::RandomBetween(0, UINT8_MAX)), uint8_t(utils::RandomBetween(0, UINT8_MAX)), uint8_t(utils::RandomBetween(0, UINT8_MAX))));
*/
#ifdef BEARLIBTERMINAL_H
terminal_refresh(); // actually draw it to the screen
#endif // BEARLIBTERMINAL_H
}
// Gets called each FRAME
void Game::UpdateRealTime(float elapsedTime)
{
// test if this works
//#define _TEST_REALTIME_
#ifdef _TEST_REALTIME_
cout << "FPS: " << to_string(1.f / elapsedTime) << '\n';
#endif // _TEST_REALTIME_
}
// Gets called after UpdateRealTime()
void Game::DrawRealTime() const
{
}
void Game::HandleInput(int key)
{
if(terminal_state(TK_SHIFT))
{
switch(key)
{
case TK_COMMA:
case TK_PAGEUP:
{
if((G_CURRENT_MAP->CheckTile(m_Player.GetPosition()) == L'<')) // go up
{
ChangeLevel(false);
}
else
{
G_PRINT_STATUS(m_NoUpMsg);
m_NextTurn = false;
}
break;
}
case TK_PERIOD:
case TK_PAGEDOWN:
{
if((G_CURRENT_MAP->CheckTile(m_Player.GetPosition()) == L'>')) // > , DOWN
{
ChangeLevel(true);
}
else
{
G_PRINT_STATUS(m_NoDownMsg);
m_NextTurn = false;
}
break;
}
/// NPC interaction ///
case TK_G: // give item
m_NextTurn = m_Player.ChooseAction(m_Player.GetClosest(m_NPCs), INTERACT_CHOICE::GIVE);
break;
case TK_F:
{
vector<Entity*> closeNPCs{ m_Player.GetClosest(m_NPCs) };
m_NextTurn = m_Player.ChooseAction(closeNPCs, INTERACT_CHOICE::FIGHT);
break;
}
case TK_H:
{
vector<Entity*> closeNPCs{ m_Player.GetClosest(m_NPCs) };
m_NextTurn = m_Player.ChooseAction(closeNPCs, INTERACT_CHOICE::HUG);
break;
}
/// Terminal stuff ///
case TK_C:
{
m_NextTurn = false;
if(terminal_state(TK_CONTROL))
{
string m_Cheat{};
G_PRINT_STATUS("ENTER CHEAT");
//getline(cin, m_Cheat);
m_Cheat = AskString();
Cheat(m_Cheat);
Draw();
}
break;
}
case TK_E:
{
Weapon* weapon{ m_Player.GetWeapon() };
Armor* armor{ m_Player.GetArmor() };
cout << m_Player.GetFullName() << '\n' << "--------------------------------" << '\n';
cout << "Weapon: ";
if(weapon)
cout << weapon->itemName << " (" << to_string(weapon->weaponMinDamage) << '-' << to_string(weapon->weaponMaxDamage) << ')' << '\n';
else
cout << "none\n";
cout << "Armor: ";
if(armor)
cout << armor->itemName << " (" << to_string(armor->armorRating) << ')' << '\n';
else
cout << "none\n";
break;
}
case TK_N:
{
std::vector<Entity*> closestNPCs{ m_Player.GetClosest(m_NPCs) };
for(auto& npc : closestNPCs)
{
/*cout << '\n' << npc->GetName() << "\nHealth: " << npc->GetHealth(); /*<< "\nWeapon: ";
if(npc->GetWeapon())
cout << npc->GetWeapon()->itemName;
else
cout << "none";
cout << "\nArmor: ";
if(npc->GetArmor())
cout << npc->GetArmor()->itemName;
else
cout << "none";*/
cout << npc->PrintSheet();
for(auto& item : npc->GetInventory())
cout << '\n' << item->itemName;
}
break;
}
default:
m_NextTurn = false;
break;
}
}
else
{
switch(key)
{
case TK_KP_5: // wait
break;
case TK_ESCAPE:
case TK_CLOSE:
{
m_NextTurn = false;
G_QUIT = true;
break;
}
/// Movement ///
case TK_KP_1: // lower left
m_Player.Move(DIRECTION::SOUTHWEST);
break;
case TK_DOWN:
case TK_KP_2:
m_Player.Move(DIRECTION::SOUTH);
break;
case TK_KP_3: // lower right
m_Player.Move(DIRECTION::SOUTHEAST);
break;
case TK_LEFT:
case TK_KP_4:
m_Player.Move(DIRECTION::WEST);
break;
case TK_RIGHT:
case TK_KP_6:
m_Player.Move(DIRECTION::EAST);
break;
case TK_KP_7: // upper left
m_Player.Move(DIRECTION::NORTHWEST);
break;
case TK_UP:
case TK_KP_8:
m_Player.Move(DIRECTION::NORTH);
break;
case TK_KP_9: // upper right
m_Player.Move(DIRECTION::NORTHEAST);
break;
/// Items ///
case TK_I: // use item
m_NextTurn = m_Player.OpenInventory(m_Items, INVENTORY_CHOICE::USE_ITEM);
break;
case TK_G: // get item
{
// TODO: show list of items instead of picking up everything
const Position& playerPos{ m_Player.GetPosition() };
for(size_t tileIndex{}; tileIndex < m_Items.size(); ++tileIndex)
{
if(m_Items[tileIndex].position == playerPos)
{
m_Player.AddItem(m_Items[tileIndex].item);
//PRINT_STATUS(m_Player.GetName() + " picked up " + m_Items[tileIndex].item->itemName);
std::vector<ItemTile>::iterator it{ m_Items.begin() + tileIndex };
m_Items.erase(it);
}
}
break;
}
case TK_D: // drop item
m_NextTurn = m_Player.OpenInventory(m_Items, INVENTORY_CHOICE::DROP_ITEM);
break;
/// Terminal stuff ///
case TK_B:
{
cout << G_FULL_TEXT << '\n';
G_FULL_TEXT.clear();
m_NextTurn = false;
break;
}
case TK_P:
{
cout << m_Player.PrintSheet(); // string
m_NextTurn = false;
break;
}
#ifdef _DEBUG // put debug stuff here
/*case TK_Y:
if(m_Player.GetInventory().size() >= 1)
m_Player.GetInventory().erase(m_Player.GetInventory().begin() + m_Player.GetInventory().size() - 1);
break;*/
case TK_F1:
{
CreateNPCs(1);
m_NPCs[m_NPCs.size() - 1]->SetFaction(ENTITY_FACTION::FRIENDLY);
m_Player.GetFriends().push_back(m_NPCs[m_NPCs.size() - 1]);
break;
}
case TK_F2:
{
size_t random{ size_t(utils::RandomBetween(0, static_cast<int>(m_NPCs.size()) - 1)) };
const Position& playerPos{ m_Player.GetPosition() };
const Position& enemyPos{ m_NPCs[random]->GetPosition() };
utils::Bresenham(playerPos.x, playerPos.y, enemyPos.x, enemyPos.y);
m_NextTurn = false;
break;
}
case TK_F3: // add random item
m_Player.AddItem(G_ITEM_LIST->GetRandomItem());
break;
case TK_F4: // force save map
G_CURRENT_MAP->SaveMap();
break;
case TK_F5: // instant death
{
G_CH_BITS[G_CH_CODES::GOD] = false;
m_Player.AddHealth(-m_Player.GetMaxHealth());
break;
}
case TK_F6:
m_NextTurn = m_Player.ChooseAction(m_Player.GetClosest(m_NPCs), INTERACT_CHOICE::GIVE);
break;
case TK_J:
{
++G_OFFSET_X;
Draw();
m_NextTurn = false;
break;
}
case TK_K:
{
--G_OFFSET_X;
Draw();
m_NextTurn = false;
break;
}
case TK_O:
{
++G_OFFSET_Y;
Draw();
m_NextTurn = false;
break;
}
case TK_L:
{
--G_OFFSET_Y;
Draw();
m_NextTurn = false;
break;
}
#endif // _DEBUG
default:
m_NextTurn = false;
break;
}
}
}
void Game::ChangeLevel(bool down)
{
int level{};
if(down)
level = m_CurrentLevel + 1;
else if(!down && m_CurrentLevel > 0)
level = m_CurrentLevel - 1;
else
{
G_PRINT_STATUS(m_NoUpMsg);
m_NextTurn = false;
return;
}
ClearNPCs();
ClearItems();
m_CurrentLevel = level;
delete G_CURRENT_MAP;
//m_Player.ResetPosition();
m_Player.SetPosition(G_MAP_WIDTH / 2, G_MAP_HEIGHT / 2);
G_CURRENT_MAP = new Map{ to_string(m_MapNumber) + "-" + to_string(level), static_cast<bool>(utils::RandomBetween(0, 1)), m_Player.GetPosition() };
CreateNPCs(utils::RandomBetweenSimple(5, 25));
CreateItems(utils::RandomBetweenSimple(5, 20));
// fix for spawning at wrong stairs
if(!down)
m_Player.SetPosition(G_CURRENT_MAP->m_DownStairsPos);
else
m_Player.SetPosition(G_MAP_WIDTH / 2, G_MAP_HEIGHT / 2);
std::vector<Entity*>& friends{ m_Player.GetFriends() };
for(auto& npc : friends)
{
// Not needed I think, the NPC would already get deleted in Update() and ClearNPCs() if dead
// Friend NPCs don't get deleted in the player's list
if(npc->IsDead())
{
// if dead, delete npc
vector<Entity*>::iterator it{ find(friends.begin(), friends.end(), npc) };
if(it != friends.end())
{
/*if(npc)
delete npc;*/ // dead NPCs already get deleted
friends.erase(it);
}
// If the NPC is dead, it would already get deleted from m_NPCs in Update()
vector<Entity*>::iterator itt{ find(m_NPCs.begin(), m_NPCs.end(), npc) };
if(it != friends.end())
{
m_NPCs.erase(itt);
}
continue;
}
// Friends get removed from m_NPCs in ClearNPCs(), so add them back
npc->SetPosition(m_Player.GetPosition());
m_NPCs.push_back(npc);
}
//ClearNPCs();
// Redraw the screen
Draw();
#ifdef _DEBUG
Position pos{ m_Player.GetPosition() };
cout << "Player position: " << to_string(pos.x) << ", " << to_string(pos.y) << '\n';
#endif // _DEBUG
}
void Game::SetupPlayer()
{
Position boxPos{ G_SCREEN_WIDTH / 2 - 8, G_SCREEN_HEIGHT / 2 - 2 };
Position boxDimensions{ 20, 3 };
utils::DrawBox(boxPos.x, boxPos.y, boxDimensions.x, boxDimensions.y, G_LAYER_MENU, false, "ENTER YOUR NAME");
m_Player.SetName(AskString(true));
utils::ClearBox(boxPos.x, boxPos.y, boxDimensions.x, boxDimensions.y, G_LAYER_MENU);
m_Player.SetSpecies(AskSpecies());
m_Player.ResetPosition();
}
// if isName is true, put first character in uppercase
string Game::AskString(bool isName) const
{
terminal_layer(G_LAYER_MENU_TOP);
//terminal_clear();
const int nameLength{ 16 };
char nameBuffer[nameLength] = "";
/*if(isName)
strcpy_s(nameBuffer, "The Traveler");*/
const char* mesg{ "> " };
const int mesgLength{ int(strlen(mesg)) };
Position mesgPos{ G_SCREEN_WIDTH / 2 - mesgLength - 5, G_SCREEN_HEIGHT / 2 - 1 };
terminal_print(mesgPos.x, mesgPos.y, mesg);
string temp{};
bool done{ false };
const int illegalCharsLength{ 2 };
char illegalChars[illegalCharsLength] = { '[', ']' };
// keep going until no brackets are found, they mess up printing with BearLibTerminal
while(!done)
{
if(terminal_read_str(mesgPos.x + mesgLength, mesgPos.y, nameBuffer, nameLength - 1) >= 0)
{
temp = nameBuffer;
}
//done = temp.find('[') == string::npos && temp.find(']') == string::npos;
for(int i{ 0 }; i < illegalCharsLength; ++i)
{
done = temp.find(illegalChars[i]) == string::npos;
if(!done)
break;
}
}
if(isName)
temp[0] = char(toupper(temp[0]));
terminal_clear();
terminal_layer(G_LAYER_MAIN);
return temp;
}
SPECIES Game::AskSpecies(bool forPlayer)
{
// who's going to choose human
// 2021-06-03: Don't remember why I added crabs
const string_view choices[] = { "lizard", "kobold", "dragon", "wyvern", "hydra", "eel", "snake", "crab", "human"/*, "UNDEFINED"*/ };
const unsigned char boxWidth{ 15 };
const unsigned char boxHeight{ 13 };
const Position mesgPos{ G_SCREEN_WIDTH / 2 - boxWidth / 2, G_SCREEN_HEIGHT / 2 };
char choice{};
char highlight{};
SPECIES chosenSpecies{ SPECIES::UNDEFINED };
string title{ "RACE" };
bool finished{ true };
if(!forPlayer)
title += " BFF";
utils::DrawBox(mesgPos.x, mesgPos.y, boxWidth, boxHeight, G_LAYER_MENU, false, title.c_str());
terminal_layer(G_LAYER_MENU_TOP);
while(finished)
{
terminal_clear_area(mesgPos.x + 1, mesgPos.y + 1, boxWidth - 2, boxHeight - 2);
//terminal_print(mesgPos.x + 2, mesgPos.y + 1, "[color=black][bkcolor=white]RACE:");
terminal_color(G_COL_WHITE);
terminal_put(mesgPos.x + 3, mesgPos.y + 2 + highlight, '>');
terminal_put(mesgPos.x + 10, mesgPos.y + 2 + highlight, '<');
for(unsigned char i{ 0 }; i < 9; ++i)
{
terminal_color(color_from_name(choices[i].data()));
char name[64]; // just in case
#ifdef _MSC_BUILD
strcpy_s(name, choices[i].data());
#else
strcpy(name, choices[i].data());
#endif
name[0] = char(toupper(name[0]));
terminal_print(mesgPos.x + 4, mesgPos.y + 2 + i, name);
}
/*terminal_print(mesgPos.x + 2, mesgPos.y + 3, "<- ");
terminal_print(mesgPos.x + 5, mesgPos.y + 3, choices[highlight]);
terminal_print(mesgPos.x + 11, mesgPos.y + 3, " ->");*/
terminal_refresh();
choice = GetKey();
switch(choice)
{
case TK_UP:
case TK_KP_8:
--highlight;
if(highlight == -1)
highlight = 8;
break;
case TK_DOWN:
case TK_KP_2:
++highlight;
if(highlight == 9)
highlight = 0;
break;
case TK_ENTER:
case TK_KP_ENTER:
if(highlight == 9)
chosenSpecies = SPECIES::UNDEFINED;
else
chosenSpecies = static_cast<SPECIES>(highlight);
finished = false;
break;
default:
break;
}
}
utils::ClearBox(mesgPos.x, mesgPos.y, boxWidth, boxHeight, G_LAYER_MENU);
terminal_color(G_COL_WHITE);
terminal_bkcolor(G_COL_BLACK);
terminal_layer(G_LAYER_MAIN);
return chosenSpecies;
}
/*
* Menu things
*/
void Game::DrawMenu() const
{
terminal_layer(G_LAYER_STATUS);
terminal_color(G_COL_BLACK);
terminal_composition(TK_ON);
const string level{ "Level: " + to_string(m_CurrentLevel) };
// background
for(int y{ G_SCREEN_HEIGHT - G_SCREEN_BOTTOM }; y < G_SCREEN_HEIGHT; ++y)
{
for(int x{}; x < G_SCREEN_WIDTH; ++x)
{
terminal_put(x, y, L'\x2588');
}
}
//terminal_layer(G_LAYER_STATUS + 1);
terminal_color(G_COL_WHITE);
auto GetHealthColor{ [this]()
{
const int health{ m_Player.GetHealth() };
const int max{ m_Player.GetMaxHealth() };
if(health >= .64f * max)
return "green";
else if(health >= .44f * max)
return "yellow";
else
return "red";
} };
terminal_print(0, G_SCREEN_HEIGHT - 3, m_Player.GetFullName().c_str());
terminal_printf(30, G_SCREEN_HEIGHT - 3, "HP: [color=%s]%d[/color](%d)\tStrength: %d\tDeftness: %d",
GetHealthColor(), m_Player.GetHealth(), m_Player.GetMaxHealth(), m_Player.GetAttribute(ATTRIBUTES::STRENGTH), m_Player.GetAttribute(ATTRIBUTES::DEFTNESS));
terminal_print(0, G_SCREEN_HEIGHT - 2, level.c_str());
G_PRINT_STATUS();
terminal_composition(TK_OFF);
}
string Game::GenerateWelcome(int friendId)
{
string fullstring{};
const string& playerName{ m_Player.GetName() };
unsigned int nameNumber{};
for(size_t i{}; i < playerName.size(); ++i)
nameNumber += playerName[i];
//cout << nameNumber;
if(nameNumber == 286 || nameNumber == 521)
{
fullstring = "It's the dev.";
dev = true;
}
else if(nameNumber == 111)
{
fullstring = "seks hue";
}
else
{
const Entity* friendNPC{ m_NPCs[friendId] };
string friendSpecies{ friendNPC->GetSpeciesString() };
friendSpecies[0] = char(tolower(friendSpecies[0]));
string species{ m_Player.GetSpeciesString() };
species[0] = char(tolower(species[0]));
fullstring = "You, [color=" + species + "]" + m_Player.GetFullName()
+ "[/color], \nhave arrived at the dark and foreboding Tundra with your friend, [color="
+ friendSpecies + "]" + friendNPC->GetFullName() + "[/color].";
}
return fullstring;
}
void Game::CreateNPCs(int amount)
{
unsigned int numberNPC{};
string name{};
Position position{};
for(int i{}; i < amount; ++i)
{
// a lambda to check if the random position is the same as another NPC already in the vector
auto isNPC { [this](const Position& pos)
{
for(auto& npc : m_NPCs)
{
if(pos == npc->GetPosition())
{
return true;
break;
}
}
return false;
}};
do
{
position = utils::RandomPositionBetween(0, G_MAP_WIDTH - 1, 0, G_MAP_HEIGHT - 1);
}
while(find(Map::WALLS_ARRAY.begin(), Map::WALLS_ARRAY.end(), G_CURRENT_MAP->CheckTile(position)) != Map::WALLS_ARRAY.end() || G_CURRENT_MAP->CheckTile(position) == '+'
|| G_CURRENT_MAP->CheckTile(position) == G_MAPTILE_WALL || G_CURRENT_MAP->CheckTile(position) == '<' || G_CURRENT_MAP->CheckTile(position) == '>' || isNPC(position));
name = "Random NPC " + to_string(numberNPC);
++numberNPC;
if(amount != 1 && i == amount - 1)
{
m_NPCs.push_back(new NPC{ name, SPECIES::HUMAN, position });
//m_NPCs[size_t(i)]->SetFaction(ENTITY_FACTION::EVIL);
}
else
m_NPCs.push_back(new NPC{ name, SPECIES(utils::RandomBetween(0, short(SPECIES::MAX) - 1)), position }); // - 1 because humans
}
}
void Game::CreateItems(int amount)
{
Position position;
for(int i{}; i < amount; ++i)
{
// a lambda to check if the random position is the same as another NPC already in the vector
auto isItem { [this](const Position& pos)
{
for(auto& tile : m_Items)
{
if(pos == tile.position)
{
return true;
break;
}
}
return false;
}};
do
{
position = utils::RandomPositionBetween(0, G_MAP_WIDTH - 1, 0, G_MAP_HEIGHT - 1);
}
while(find(Map::WALLS_ARRAY.begin(), Map::WALLS_ARRAY.end(), G_CURRENT_MAP->CheckTile(position)) != Map::WALLS_ARRAY.end() || G_CURRENT_MAP->CheckTile(position) == G_MAPTILE_DOOR
|| G_CURRENT_MAP->CheckTile(position) == G_MAPTILE_WALL || G_CURRENT_MAP->CheckTile(position) == '<' || G_CURRENT_MAP->CheckTile(position) == '>' || isItem(position));
ItemTile item{ G_ITEM_LIST->GetRandomItem(), position };
m_Items.push_back(item);
}
}
// Cheats
void Game::Cheat(const string& cheat) const
{
if(cheat.empty())
return;
string message{ "toggled " };
bool toggledCheat;
if(cheat == "gosh")
{
G_CH_BITS[G_CH_CODES::GOD] = !G_CH_BITS[G_CH_CODES::GOD];
toggledCheat = G_CH_BITS[G_CH_CODES::GOD];
message += "Goshmode ";
}
else if(cheat == "funnymode")
{
G_CH_BITS[G_CH_CODES::FUNNYMODE] = !G_CH_BITS[G_CH_CODES::FUNNYMODE];
toggledCheat = G_CH_BITS[G_CH_CODES::FUNNYMODE];
message += "Funny Mode ";
}
else if(cheat == "ghost")
{
G_CH_BITS[G_CH_CODES::GHOST] = !G_CH_BITS[G_CH_CODES::GHOST];
toggledCheat = G_CH_BITS[G_CH_CODES::GHOST];
message += "Ghost Mode ";
}
else
return;
if(toggledCheat)
message += "on\n";
else
message += "off\n";
G_PRINT_STATUS(message);
}
/*
* Clearing things
*/
void Game::ClearNPCs()
{
std::vector<Entity*>& friends{ m_Player.GetFriends() };
for(auto& npc : m_NPCs)
{
vector<Entity*>::iterator it{ find(friends.begin(), friends.end(), npc) };
if(it == friends.end())
delete npc;
}
m_NPCs.clear();
}
// ClearNPCs got called in ShutDown() but didn't clean up friend NPCs
void Game::ClearNPCsExit()
{
for(auto& npc : m_NPCs)
{
delete npc;
}
m_NPCs.clear();
}
void Game::ClearItems()
{
// ItemTiles aren't dynamically allocated but just putting this here in case I change it later
/*
for(auto& itemTile : m_Items)
{
delete itemTile
}
*/
m_Items.clear();
}