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