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.
401 lines
9.7 KiB
C++
401 lines
9.7 KiB
C++
//#include "Game.hpp"
|
|
#include "Player.hpp"
|
|
#include "RLGame.hpp"
|
|
#include "utils.hpp"
|
|
#include <algorithm>
|
|
//#include <cstring>
|
|
//#include <stdexcept>
|
|
|
|
#ifdef _DEBUG
|
|
#include <iostream>
|
|
#endif // _DEBUG
|
|
|
|
//const wchar_t Player::m_CharPlayer{ L'@' };
|
|
|
|
Player::Player(const std::string& name)
|
|
: Player{ name, SPECIES::UNDEFINED }
|
|
{
|
|
}
|
|
|
|
Player::Player(const std::string& name, SPECIES species)
|
|
: Entity{ name, species, Position{ G_SCREEN_WIDTH / 2, G_SCREEN_HEIGHT / 2 } }
|
|
, m_Friends{}
|
|
{
|
|
// don't see why there'd be more than 1 player, but just putting this here in case something would happen
|
|
if(m_pPlayer == nullptr)
|
|
m_pPlayer = this;
|
|
}
|
|
|
|
bool Player::ChooseAction(const std::vector<Entity*>& npcs, INTERACT_CHOICE choice)
|
|
{
|
|
if(npcs.empty())
|
|
return false;
|
|
|
|
Position boxPos{ 1, 1 };
|
|
int extraHeight{ (int(npcs.size()) < 15) ? int(npcs.size()) : 16 };
|
|
int boxWidth{ 36 };
|
|
int boxHeight{ int(2 + extraHeight) };
|
|
color_t enemyColor{ color_from_name("red") };
|
|
color_t friendlyColor{ color_from_name("light green") };
|
|
color_t devotedColor{ color_from_name("lightest green") };
|
|
color_t neutralColor{ color_from_name("light gray") };
|
|
color_t printedColor{};
|
|
const char* printedString{};
|
|
const char* title{};
|
|
|
|
switch(choice)
|
|
{
|
|
case INTERACT_CHOICE::FIGHT:
|
|
title = "FIGHT";
|
|
break;
|
|
case INTERACT_CHOICE::HUG:
|
|
title = "HUG";
|
|
break;
|
|
case INTERACT_CHOICE::GIVE:
|
|
title = "GIVE";
|
|
break;
|
|
}
|
|
utils::DrawBox(boxPos.x, boxPos.y, boxWidth, boxHeight, G_LAYER_MENU, true, title);
|
|
terminal_layer(G_LAYER_MENU_TOP);
|
|
|
|
for(size_t id{}; id < npcs.size() && id <= 15; ++id)
|
|
{
|
|
//terminal_printf(boxPos.x + 1, boxPos.y + 1 + int(id), "[color=sea]%d[/color]. %s", int(id + 1), npcs[id]->GetName().c_str());
|
|
|
|
switch(npcs[id]->GetFaction())
|
|
{
|
|
case ENTITY_FACTION::NEUTRAL:
|
|
printedColor = neutralColor;
|
|
printedString = "NEUTRAL";
|
|
break;
|
|
case ENTITY_FACTION::EVIL:
|
|
printedColor = enemyColor;
|
|
printedString = "ENEMY";
|
|
break;
|
|
case ENTITY_FACTION::FRIENDLY:
|
|
printedColor = friendlyColor;
|
|
printedString = "FRIENDLY";
|
|
break;
|
|
case ENTITY_FACTION::DEVOTED:
|
|
printedColor = devotedColor;
|
|
printedString = "FRIENDLY";
|
|
break;
|
|
default:
|
|
printedColor = G_COL_WHITE;
|
|
printedString = "";
|
|
break;
|
|
}
|
|
|
|
int posY{ boxPos.y + 1 + int(id) };
|
|
terminal_printf(boxPos.x + 1, posY, "[color=sea]%c[/color]. %s", char('a' + id), npcs[id]->GetName().c_str());
|
|
terminal_printf(boxPos.x + 19, posY, "[color=%d]%s", npcs[id]->GetColor(), npcs[id]->GetSpeciesString());
|
|
terminal_printf(boxPos.x + 26, posY, "[color=%d]%s", printedColor, printedString);
|
|
}
|
|
|
|
terminal_refresh();
|
|
terminal_layer(G_LAYER_MAIN);
|
|
|
|
wchar_t key{ utils::GetWChar() };
|
|
unsigned int id{};
|
|
if(isupper(key))
|
|
id = int(key) + 58 - 'a'; // put upper letters after the small ones
|
|
else
|
|
id = int(key) - 'a'; // ascii a = 97
|
|
|
|
if(key == L'\0' || id >= npcs.size())
|
|
{
|
|
#ifdef _DEBUG
|
|
std::wcerr << L"\nERROR\n";
|
|
std::wcerr << L"Value: " << std::to_wstring(id) << L'\n';
|
|
std::wcerr << L"Amount of NPCs: " << std::to_wstring(npcs.size()) << L'\n';
|
|
std::wcerr << L"You entered a number higher than the amount of NPCs around you or interaction cancelled\n";
|
|
#endif // _DEBUG
|
|
//terminal_clear_area(0, 0, 30, 10);
|
|
|
|
// if cancelled or invalid input, no turn will be counted so Draw() in Game.cpp won't be called
|
|
// the box will stay drawn on screen so this clears it
|
|
utils::ClearBox(boxPos.x, boxPos.y, boxWidth, boxHeight, G_LAYER_MENU);
|
|
terminal_refresh();
|
|
return false;
|
|
}
|
|
|
|
switch(choice)
|
|
{
|
|
case INTERACT_CHOICE::FIGHT:
|
|
Fight(*npcs[id]);
|
|
break;
|
|
case INTERACT_CHOICE::HUG:
|
|
{
|
|
Hug(*npcs[id]);
|
|
if(std::find(m_Friends.begin(), m_Friends.end(), npcs[id]) == m_Friends.end())
|
|
m_Friends.push_back(npcs[id]);
|
|
break;
|
|
}
|
|
case INTERACT_CHOICE::GIVE:
|
|
{
|
|
utils::ClearBox(boxPos.x, boxPos.y, boxWidth, boxHeight, G_LAYER_MENU);
|
|
int itemId{ GetItemId("- GIVE") };
|
|
if(itemId == -1 || m_Inventory.size() < itemId)
|
|
return false;
|
|
|
|
Item* gift{ m_Inventory[itemId] };
|
|
if(gift && gift->itemType == ITEM_TYPE::CONSUMABLE)
|
|
{
|
|
npcs[id]->AddItem(gift);
|
|
RemoveItem(itemId);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Player::OpenInventory(std::vector<ItemTile>& items, INVENTORY_CHOICE choice)
|
|
{
|
|
std::string title{};
|
|
switch(choice)
|
|
{
|
|
case INVENTORY_CHOICE::USE_ITEM:
|
|
title += "- USE";
|
|
break;
|
|
case INVENTORY_CHOICE::DROP_ITEM:
|
|
title += "- DROP";
|
|
break;
|
|
}
|
|
int itemId{ GetItemId(title) };
|
|
if(itemId == -1)
|
|
return false; // cancelled
|
|
|
|
switch(choice)
|
|
{
|
|
case INVENTORY_CHOICE::USE_ITEM:
|
|
return UseItem(itemId);
|
|
break;
|
|
case INVENTORY_CHOICE::DROP_ITEM:
|
|
return DropItem(items, itemId);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Player::GetItemId(const std::string& title)
|
|
{
|
|
if(m_Inventory.empty())
|
|
{
|
|
G_PRINT_STATUS("You aren't carrying any items.");
|
|
return -1;
|
|
}
|
|
|
|
Position boxPos{ 1, 1 };
|
|
int boxWidth{ 20 };
|
|
int boxHeight{ int(2 + m_Inventory.size()) };
|
|
|
|
utils::DrawBox(boxPos.x, boxPos.y, boxWidth, boxHeight, G_LAYER_MENU, true, "INVENTORY " + title);
|
|
terminal_layer(G_LAYER_MENU_TOP);
|
|
|
|
for(size_t id{}; id < m_Inventory.size(); ++id)
|
|
{
|
|
Item* item{ m_Inventory[id] };
|
|
int tempY{ boxPos.y + 1 + int(id) };
|
|
terminal_printf(boxPos.x + 1, tempY, "[color=sea]%c[/color]. %s", char('a' + id), item->itemName.c_str());
|
|
}
|
|
terminal_refresh();
|
|
terminal_layer(G_LAYER_MAIN);
|
|
|
|
wchar_t key{ utils::GetWChar() };
|
|
int id{ int(key) - 97 }; // ascii a = 97
|
|
if(id > m_Inventory.size() -1 || id < 0)
|
|
{
|
|
id = -1;
|
|
}
|
|
|
|
utils::ClearBox(boxPos.x, boxPos.y, boxWidth, boxHeight, G_LAYER_MENU);
|
|
terminal_refresh();
|
|
return id;
|
|
}
|
|
|
|
std::vector<Entity*>& Player::GetFriends()
|
|
{
|
|
return m_Friends;
|
|
}
|
|
|
|
void Player::SetPosition(int x, int y)
|
|
{
|
|
// moving outside the map crashes the game iirc, so just don't allow it
|
|
if(x >= G_MAP_WIDTH - 1|| x < 0 || y >= G_MAP_HEIGHT - 1 || y < 0)
|
|
return;
|
|
|
|
m_Pos.x = x;
|
|
m_Pos.y = y;
|
|
G_OFFSET_PREV_X = G_OFFSET_X;
|
|
G_OFFSET_PREV_Y = G_OFFSET_Y;
|
|
G_OFFSET_X = G_SCREEN_WIDTH / 2 - x;
|
|
G_OFFSET_Y = G_SCREEN_HEIGHT / 2 - y;
|
|
}
|
|
|
|
void Player::SetPosition(Position position)
|
|
{
|
|
SetPosition(position.x, position.y);
|
|
}
|
|
|
|
/*
|
|
* Returns true if moved
|
|
*/
|
|
bool Player::Move(int x, int y)
|
|
{
|
|
// never move out of the window
|
|
if(m_Pos.x + x >= G_MAP_WIDTH || m_Pos.x + x < 0 || m_Pos.y + y >= G_MAP_HEIGHT || m_Pos.y + y < 0)
|
|
return false;
|
|
/*else if(utils::RandomBetweenSimple(0, 90 + m_AttrDeftness * 2) == 1)
|
|
{
|
|
PRINT_STATUS(GetFullName() + " stumbles.");
|
|
return false;
|
|
}*/
|
|
else if(G_CH_BITS[G_CH_CODES::GHOST])
|
|
{
|
|
m_PreviousPos = m_Pos;
|
|
m_Pos.x += x;
|
|
m_Pos.y += y;
|
|
G_OFFSET_PREV_X = G_OFFSET_X;
|
|
G_OFFSET_PREV_Y = G_OFFSET_Y;
|
|
G_OFFSET_X -= x;
|
|
G_OFFSET_Y -= y;
|
|
/*#ifdef _DEBUG
|
|
std::cout << "\nOffset x: " << G_OFFSET_X << "\nOffset y: " << G_OFFSET_Y;
|
|
#endif*/
|
|
return true;
|
|
}
|
|
|
|
switch(G_CURRENT_MAP->CheckTile(m_Pos.x + x, m_Pos.y + y))
|
|
{
|
|
case G_MAPTILE_WALL:
|
|
case Map::WALLS_ARRAY[0]:
|
|
case Map::WALLS_ARRAY[1]:
|
|
case Map::WALLS_ARRAY[2]:
|
|
case Map::WALLS_ARRAY[3]:
|
|
case Map::WALLS_ARRAY[4]:
|
|
case Map::WALLS_ARRAY[5]:
|
|
return false;
|
|
break;
|
|
case '.':
|
|
case ',':
|
|
case '\'':
|
|
case '`':
|
|
case G_MAPTILE_DOOR: // '+'
|
|
case '@':
|
|
default:
|
|
m_PreviousPos = m_Pos;
|
|
m_Pos.x += x;
|
|
m_Pos.y += y;
|
|
G_OFFSET_PREV_X = G_OFFSET_X;
|
|
G_OFFSET_PREV_Y = G_OFFSET_Y;
|
|
G_OFFSET_X -= x;
|
|
G_OFFSET_Y -= y;
|
|
// don't go over borders and don't move if the player isn't centered
|
|
/*if((G_OFFSET_X > 0 || G_OFFSET_X + SCREEN_WIDTH / 2 <= -SCREEN_WIDTH - 500))
|
|
{
|
|
if(m_Pos.x + G_OFFSET_X + x != SCREEN_WIDTH / 2)
|
|
G_OFFSET_X += x;
|
|
}
|
|
|
|
if((G_OFFSET_Y > 0 || G_OFFSET_Y + SCREEN_HEIGHT / 2 <= -SCREEN_HEIGHT - 500) && m_Pos.y + G_OFFSET_PREV_Y != SCREEN_HEIGHT / 2)
|
|
{
|
|
G_OFFSET_Y += y;
|
|
}
|
|
|
|
std::cout << "\nX: " << G_OFFSET_X << "\nY: " << G_OFFSET_Y;*/
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Player::Move(DIRECTION direction)
|
|
{
|
|
switch(direction)
|
|
{
|
|
case DIRECTION::NORTH:
|
|
return Move(0, -1);
|
|
break;
|
|
case DIRECTION::SOUTH:
|
|
return Move(0, 1);
|
|
break;
|
|
case DIRECTION::EAST:
|
|
return Move(1, 0);
|
|
break;
|
|
case DIRECTION::WEST:
|
|
return Move(-1, 0);
|
|
break;
|
|
case DIRECTION::NORTHEAST:
|
|
return Move(1, -1);
|
|
break;
|
|
case DIRECTION::SOUTHEAST:
|
|
return Move(1, 1);
|
|
break;
|
|
case DIRECTION::NORTHWEST:
|
|
return Move(-1, -1);
|
|
break;
|
|
case DIRECTION::SOUTHWEST:
|
|
return Move(-1, 1);
|
|
break;
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Player::Draw() const
|
|
{
|
|
//terminal_bkcolor("darker green");
|
|
terminal_color(GetColor());
|
|
terminal_put(m_Pos.x + G_OFFSET_X, m_Pos.y + G_OFFSET_Y, L'@');
|
|
terminal_color(G_COL_WHITE);
|
|
//terminal_bkcolor(G_BLACK);
|
|
}
|
|
|
|
// Resets position and drawing offsets
|
|
void Player::ResetPosition()
|
|
{
|
|
SetPosition(G_SCREEN_WIDTH / 2, G_SCREEN_HEIGHT / 2);
|
|
G_OFFSET_PREV_X = 0;
|
|
G_OFFSET_PREV_Y = 0;
|
|
G_OFFSET_X = 0;
|
|
G_OFFSET_Y = 0;
|
|
}
|
|
|
|
void Player::ResetDrawingOffset()
|
|
{
|
|
G_OFFSET_PREV_X = G_OFFSET_X;
|
|
G_OFFSET_PREV_Y = G_OFFSET_Y;
|
|
G_OFFSET_X = G_SCREEN_WIDTH / 2 - m_Pos.x;
|
|
G_OFFSET_Y = G_SCREEN_HEIGHT / 2 - m_Pos.y;
|
|
}
|
|
|
|
void Player::Die()
|
|
{
|
|
Entity::Die();
|
|
int width{ 22 };
|
|
int height{ 4 };
|
|
Position pos{ G_SCREEN_WIDTH / 2 - width / 2, G_SCREEN_HEIGHT / 2 - height / 2 };
|
|
utils::DrawBox(pos.x, pos.y, width, height, G_LAYER_MENU, true, "GAME OVER");
|
|
terminal_layer(G_LAYER_MENU_TOP);
|
|
terminal_print(pos.x + 1, pos.y + 1, "You have died.\nPress ENTER to quit.");
|
|
terminal_refresh();
|
|
//while(terminal_read() != TK_ENTER || terminal_read() != TK_KP_ENTER);
|
|
/*int key{};
|
|
do
|
|
{
|
|
key = terminal_read();
|
|
if(key == TK_ENTER || key == TK_KP_ENTER)
|
|
break;
|
|
}
|
|
while(true); // for some reason putting ORs in here causes it get stuck? so only check for ENTER
|
|
// but checking in the while loop works?*/
|
|
utils::WaitForEnter();
|
|
utils::ClearBox(pos.x, pos.y, width, height, G_LAYER_MENU);
|
|
G_QUIT = true;
|
|
}
|