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++

//#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;
}