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.

860 lines
17 KiB
C++

#include "Entity.hpp"
#include "RLGame.hpp"
#include "utils.hpp"
//#include "Room.hpp"
//#include <iostream>
//#include <cmath>
Entity* Entity::m_pPlayer{};
Entity::Entity(const std::string& name, SPECIES type, Position position)
: m_Species{ SPECIES::UNDEFINED }
, m_Pos{ position }
, m_PreviousPos{ position }
, m_Dead{ false }
, m_UpperChar{ true }
, m_MaxHealth{ 100 }
, m_Health{ 100 }
, m_AttrStrength{}
, m_AttrIntellect{}
, m_AttrDeftness{}
, m_HugCounter{}
, m_Faction{ ENTITY_FACTION::NEUTRAL }
, m_pWeapon{}
, m_pArmor{}
, m_Inventory{}
, m_Name{ name }
{
SetSpecies(type);
}
Entity::~Entity()
{
m_Inventory.clear();
}
void Entity::Draw() const
{
terminal_color(GetColor());
terminal_put(m_Pos.x + G_OFFSET_X, m_Pos.y + G_OFFSET_Y, GetSymbol());
terminal_color(G_COL_WHITE);
}
void Entity::Update()
{
}
void Entity::Update(const std::vector<Entity*>& entities)
{
}
bool Entity::Fight(Entity& other)
{
if((&other == m_pPlayer && G_CH_BITS[G_CH_CODES::GOD]) || other.m_Dead || GetDistanceFrom(other) > 1)
return false;
if(utils::RandomBetween(0, (100 + other.m_AttrDeftness - m_AttrDeftness)) < 75) // I don't know how to make balanced dice rolls
{
//PRINT_STATUS(m_Name + " hits ");
int damage{ m_AttrStrength * 3 / 2 };
damage += utils::RandomBetween(-damage / 5 - 1, damage / 5 + 1);
if(m_pWeapon)
damage += utils::RandomBetween(m_pWeapon->weaponMinDamage, m_pWeapon->weaponMaxDamage);
if(other.m_pArmor)
damage -= other.m_pArmor->armorRating;
other.AddHealth(-damage);
if(!other.m_Dead)
{
if(m_pWeapon)
G_PRINT_STATUS(m_Name + " (" + std::to_string(m_Health) + ") hits " + other.m_Name + " (" + std::to_string(other.m_Health) + ") with " + m_pWeapon->itemName);
else
G_PRINT_STATUS(m_Name + " (" + std::to_string(m_Health) + ") hits " + other.m_Name + " (" + std::to_string(other.m_Health) + ')');
if(this == m_pPlayer)
{
switch(other.m_Faction)
{
case ENTITY_FACTION::NEUTRAL:
other.m_Faction = ENTITY_FACTION::EVIL;
break;
case ENTITY_FACTION::FRIENDLY:
other.m_Faction = ENTITY_FACTION::NEUTRAL;
break;
case ENTITY_FACTION::EVIL:
case ENTITY_FACTION::DEVOTED:
default:
break;
}
}
}
return true;
}
else
{
G_PRINT_STATUS(m_Name + " misses");
return false;
}
}
bool Entity::Hug(Entity& other)
{
if(&other != m_pPlayer)
{
G_PRINT_STATUS("You hug " + other.GetFullName());
}
else
{
G_PRINT_STATUS(GetFullName() + " hugs you");
}
if(other.m_Species != SPECIES::HUMAN)
{
++other.m_HugCounter;
other.AddHealth(utils::RandomBetweenSimple(2, 6));
if(&other != m_pPlayer && other.m_Faction == ENTITY_FACTION::NEUTRAL)
other.m_Faction = ENTITY_FACTION::FRIENDLY;
}
return true;
}
bool Entity::UseItem(unsigned int index)
{
if(m_Inventory.begin() == m_Inventory.end() || index >= m_Inventory.size())
return false;
Item* item{ m_Inventory[index] };
bool removeAfterUse{ false };
switch(item->itemType)
{
case ITEM_TYPE::DEFAULT:
{
// stuff I guess
G_PRINT_STATUS(m_Name + " used Item " + item->itemName);
removeAfterUse = false;
break;
}
case ITEM_TYPE::CONSUMABLE:
{
Consumable* consumable{ static_cast<Consumable*>(item) };
AddHealth(consumable->consumableHeal);
G_PRINT_STATUS(m_Name + " used Consumable " + consumable->itemName + " (" + std::to_string(consumable->consumableHeal) + " HP)");
removeAfterUse = true;
break;
}
case ITEM_TYPE::WEAPON:
{
Weapon* weapon{ static_cast<Weapon*>(item) };
if(m_pWeapon != nullptr) // add current weapon to inventory
AddItem(m_pWeapon);
m_pWeapon = weapon;
G_PRINT_STATUS(m_Name + " equipped Weapon " + weapon->itemName);
removeAfterUse = true;
break;
}
case ITEM_TYPE::ARMOR:
{
Armor* armor{ static_cast<Armor*>(item) };
if(m_pArmor != nullptr) // add current armor to inventory
AddItem(m_pArmor);
m_pArmor = armor;
G_PRINT_STATUS(m_Name + " equipped Armor " + armor->itemName);
removeAfterUse = true;
break;
}
}
if(removeAfterUse)
RemoveItem(index);
return true;
}
std::string Entity::PrintSheet() const
{
std::string sheet{};
sheet += GetFullName() + '\n'
+ "--------------------------------" + '\n'
+ "HEALTH:\t\t" + std::to_string(m_Health) + '\n'
+ "RACE:\t\t" + GetSpeciesString() + '\n'
+ "STRENGTH:\t" + std::to_string(m_AttrStrength) + '\n'
+ "INTELLECT:\t" + std::to_string(m_AttrIntellect) + '\n'
+ "DEFTNESS:\t" + std::to_string(m_AttrDeftness) + '\n'
+ "FACTION:\t" + std::to_string(short(m_Faction)) + '\n'
+ '\n';
return sheet;
}
/// GETTERS ///
const std::string& Entity::GetName() const
{
return m_Name;
}
const std::string Entity::GetFullName() const
{
return (m_Name + " the " + GetSpeciesString());
}
char Entity::GetSymbol() const
{
char speciesChar{ GetSpeciesString()[0] };
if(!m_UpperChar)
speciesChar = char(tolower(speciesChar));
return speciesChar;
}
SPECIES Entity::GetSpecies() const
{
return m_Species;
}
const char* Entity::GetSpeciesString() const
{
// TODO: load strings from language file?
// will allow translations and stuff
switch(m_Species)
{
case SPECIES::LIZARD:
return "Lizard";
break;
case SPECIES::KOBOLD:
return "Kobold";
break;
case SPECIES::HUMAN:
return "Human";
break;
case SPECIES::DRAGON:
return "Dragon";
break;
case SPECIES::WYVERN:
return "Wyvern";
break;
case SPECIES::HYDRA:
return "Hydra";
break;
case SPECIES::EEL:
return "Eel";
break;
case SPECIES::SNAKE:
return "Snake";
break;
case SPECIES::CRAB:
return "Crab";
break;
case SPECIES::UNDEFINED:
return "Nothing";
break;
default: // should be impossible
return "N/A";
break;
}
}
const Position& Entity::GetPosition() const
{
return m_Pos;
}
const int& Entity::GetHealth() const
{
return m_Health;
}
const int& Entity::GetMaxHealth() const
{
return m_MaxHealth;
}
const int& Entity::GetAttribute(ATTRIBUTES attribute) const
{
switch(attribute)
{
case ATTRIBUTES::STRENGTH:
return m_AttrStrength;
break;
case ATTRIBUTES::INTELLECT:
return m_AttrIntellect;
break;
case ATTRIBUTES::DEFTNESS:
return m_AttrDeftness;
break;
// default isn't really needed, ATTRIBUTES is an enum class so it should be safer than a simple enum
}
}
bool Entity::IsDead() const
{
return m_Dead;
}
int Entity::GetDistanceFrom(const Position& pos) const
{
/*int dx{ m_Pos.x - pos.x }, dy{ m_Pos.y - pos.y };
//return int(sqrt(dx * dx + dy * dy));
return (dx * dx + dy * dy) - 1;*/
return std::max(std::abs(m_Pos.x - pos.x), std::abs(m_Pos.y - pos.y));
}
int Entity::GetDistanceFrom(const Entity& other) const
{
return GetDistanceFrom(other.m_Pos);
}
std::vector<Entity*> Entity::GetClosest(const std::vector<Entity*>& entities, int range)
{
std::vector<Entity*> closestEntities{};
for(auto& entity : entities)
{
if(entity != this && !entity->m_Dead && GetDistanceFrom(*entity) <= range)
{
closestEntities.push_back(entity);
}
}
// uncomment to also include player
/*
if(m_pPlayer != this && !m_pPlayer->m_Dead && GetDistanceFrom(*m_pPlayer) <= range)
{
closestEntities.push_back(m_pPlayer);
}
*/
return closestEntities;
}
std::vector<Entity*> Entity::GetClosestWithState(const std::vector<Entity*>& entities, ENTITY_FACTION state, int range)
{
std::vector<Entity*> closestEntities{};
for(auto& entity : entities)
{
if(entity != this && !entity->m_Dead && GetDistanceFrom(*entity) <= range && entity->GetFaction() == state)
{
closestEntities.push_back(entity);
}
}
// uncomment to also include player
/*
if(m_pPlayer != this && !m_pPlayer->m_Dead && GetDistanceFrom(*m_pPlayer) <= range && m_pPlayer->GetFaction() == state)
{
closestEntities.push_back(m_pPlayer);
}
*/
return closestEntities;
}
color_t Entity::GetColor() const
{
/*switch(m_Species)
{
{
case SPECIES::LIZARD:
return m_ColorLizard;
break;
case SPECIES::KOBOLD:
return m_ColorKobold;
break;
case SPECIES::HUMAN:
return m_ColorHuman;
break;
case SPECIES::DRAGON:
return m_ColorDragon;
break;
case SPECIES::WYVERN:
return m_ColorWyvern;
break;
case SPECIES::HYDRA:
return m_ColorHydra;
break;
case SPECIES::EEL:
return m_ColorEel;
break;
case SPECIES::UNDEFINED:
default:
return G_BLACK;
break;
}
}*/
/*switch(m_Species)
{
case SPECIES::LIZARD:
return color_from_name("lizard");
break;
case SPECIES::KOBOLD:
return color_from_name("kobold");
break;
case SPECIES::HUMAN:
return color_from_name("human");
break;
case SPECIES::DRAGON:
return color_from_name("dragon");
break;
case SPECIES::WYVERN:
return color_from_name("wyvern");
break;
case SPECIES::HYDRA:
return color_from_name("hydra");
break;
case SPECIES::EEL:
return color_from_name("eel");
break;
case SPECIES::SNAKE:
return color_from_name("snake");
break;
case SPECIES::UNDEFINED:
default:
return G_WHITE;
break;
}*/
// better
if(m_Species == SPECIES::UNDEFINED)
return G_COL_WHITE;
std::string species{ GetSpeciesString() };
species[0] = char(tolower(species[0]));
return color_from_name(species.c_str());
}
int Entity::GetHug() const
{
return m_HugCounter;
}
ENTITY_FACTION Entity::GetFaction() const
{
return m_Faction;
}
Weapon* Entity::GetWeapon() const
{
return m_pWeapon;
}
Armor* Entity::GetArmor() const
{
return m_pArmor;
}
/// SETTERS ///
void Entity::SetName(const std::string& newName)
{
m_Name = newName;
m_Name.resize(newName.size());
}
void Entity::SetSpecies(const SPECIES& type)
{
m_Species = type;
int strengthBonus{};
int deftnessBonus{};
int intellectBonus{};
switch(type)
{
case SPECIES::LIZARD:
strengthBonus = 6;
deftnessBonus = 10;
intellectBonus = 5;
break;
case SPECIES::KOBOLD:
strengthBonus = -1;
deftnessBonus = 15;
intellectBonus = 3;
m_UpperChar = false;
break;
case SPECIES::HUMAN:
strengthBonus = 2;
deftnessBonus = 1;
intellectBonus = -2;
m_Faction = ENTITY_FACTION::EVIL;
m_UpperChar = false;
break;
case SPECIES::DRAGON:
strengthBonus = 15;
deftnessBonus = 5;
intellectBonus = 10;
break;
case SPECIES::WYVERN:
strengthBonus = 10;
deftnessBonus = 7;
intellectBonus = 10;
break;
case SPECIES::HYDRA:
strengthBonus = 20;
deftnessBonus = 3;
intellectBonus = 9;
break;
case SPECIES::EEL:
strengthBonus = 20;
deftnessBonus = 20;
intellectBonus = 20;
break;
case SPECIES::SNAKE:
strengthBonus = 5;
deftnessBonus = 20;
intellectBonus = 10;
break;
case SPECIES::CRAB:
strengthBonus = 6;
deftnessBonus = 3;
intellectBonus = 2;
m_UpperChar = false;
break;
case SPECIES::UNDEFINED:
default:
strengthBonus = -74; // -69 haha
deftnessBonus = strengthBonus;
intellectBonus = strengthBonus;
break;
}
m_AttrStrength = 5 + strengthBonus;
m_AttrDeftness = 5 + deftnessBonus;
m_AttrIntellect = 5 + intellectBonus;
}
void Entity::SetPosition(int x, int y)
{
if(x >= G_MAP_WIDTH - 1|| x < 0 || y >= G_MAP_HEIGHT - 1 || y < 0)
return;
m_Pos.x = x;
m_Pos.y = y;
}
void Entity::SetPosition(Position position)
{
SetPosition(position.x, position.y);
}
void Entity::SetHealth(int health)
{
m_Health = health;
}
void Entity::SetMaxHealth(int health)
{
m_MaxHealth = health;
}
void Entity::SetAttribute(ATTRIBUTES ATTRIBUTE, int value)
{
switch(ATTRIBUTE)
{
case ATTRIBUTES::STRENGTH:
m_AttrStrength = value;
break;
case ATTRIBUTES::INTELLECT:
m_AttrIntellect = value;
break;
case ATTRIBUTES::DEFTNESS:
m_AttrDeftness = value;
break;
default:
break;
}
}
void Entity::SetHug(int value)
{
m_HugCounter = value;
}
void Entity::SetFaction(ENTITY_FACTION state)
{
m_Faction = state;
}
/// CHANGERS ///
void Entity::AddHealth(int health)
{
if(m_Health + health > m_MaxHealth)
m_Health = m_MaxHealth;
else
m_Health += health;
if(m_Health <= 0)
{
m_Health = 0;
Die();
}
}
// Returns true if moved
bool Entity::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;
}*/
/*bool canMove{ true };
bool isFinished{ false };
do
{
for(size_t id{}; id < rooms.size(); ++id)
{
if(rooms[id].GetAtPosition(Position{ m_Pos.x + x, m_Pos.y + y }))
{
canMove = false;
break;
}
else
canMove = true;
}
isFinished = true;
}
while(!isFinished);
if(canMove)
{
m_PreviousPos = m_Pos;
m_Pos.x += x;
m_Pos.y += y;
return true;
}
else
{
return false;
}*/
// from previous version in switch statement
/*CURRENT_MAP.CheckTile(m_Pos.x + x, m_Pos.y + y)*/
//switch(GET_AT_POS(m_Pos.x + G_OFFSET_PREV_X + x, m_Pos.y + G_OFFSET_PREV_Y + y))
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;
return true;
break;
}
}
bool Entity::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 Entity::MoveTo(Position position)
{
/*if(m_Dead) // shouldn't happen, maybe remove later
return;*/
///int currentDistance{ GetDistanceFrom(position) };
///if(currentDistance > 1)
///{
/*int newDistance{}; // old version
int randX{}, randY{};
bool moved{ true };
int counter{};
do
{
randX = rand() % 3 - 1;
randY = rand() % 3 - 1;
if(!Move(randX, randY))
moved = false;
newDistance = GetDistanceFrom(position);
if(currentDistance <= newDistance && moved)
{
Move(-randX, -randY);
--counter;
}
++counter;
} while(newDistance != currentDistance - 1 && counter == 0);*/
short x{};
short y{};
// x
if(m_Pos.x < position.x)
++x;
else if(m_Pos.x > position.x)
--x;
// y
if(m_Pos.y < position.y)
++y;
else if(m_Pos.y > position.y)
--y;
if(!Move(x, y))
{
//auto newPos{ utils::Pathfind(m_Pos, position.m_Pos).at(1) };
//auto newPos{ utils::BFS(m_Pos, position.m_Pos)[1] };
//Move(newPos.x - m_Pos.x, newPos.y - m_Pos.y);
// v1
bool randInt{ bool(utils::RandomBetweenSimple(0, 1)) };
if(!randInt)
{
if(x == 0)
{
if(utils::RandomBetweenSimple(0, 1))
++x;
else
--x;
}
if(m_Pos.x + x == m_PreviousPos.x || !Move(x, 0))
Move(-x, 0);
}
else
{
if(y == 0)
{
if(utils::RandomBetweenSimple(0, 1))
++y;
else
--y;
}
if(m_Pos.y + y == m_PreviousPos.y || !Move(0, y))
Move(0, -y);
}
// v2
/*if(m_Pos.x + x == m_PreviousPos.x || !Move(x, 0))
{
if(y == 0) ++y;
if(m_Pos.y - y == m_PreviousPos.y || !Move(0, -y))
{
if(m_Pos.y + y == m_PreviousPos.y || !Move(0, y))
Move(-x, 0);
}
}
else if(m_Pos.y + y == m_PreviousPos.y || !Move(0, y))
{
if(x == 0) ++x;
if(m_Pos.x + x == m_PreviousPos.x || !Move(0, x))
{
if(m_Pos.x - x == m_PreviousPos.x || !Move(-x, 0))
Move(0, -y);
}
}*/
}
}
void Entity::MoveTo(const Entity& other)
{
MoveTo(other.m_Pos);
}
void Entity::AddAttribute(ATTRIBUTES ATTRIBUTE, int amount)
{
switch(ATTRIBUTE)
{
case ATTRIBUTES::STRENGTH:
m_AttrStrength += amount;
break;
case ATTRIBUTES::INTELLECT:
m_AttrIntellect += amount;
break;
case ATTRIBUTES::DEFTNESS:
m_AttrDeftness += amount;
break;
default:
break;
}
}
void Entity::AddHug(int amount)
{
m_HugCounter += amount;
}
/// INVENTORY THINGS ///
std::vector<Item*>& Entity::GetInventory()
{
return m_Inventory;
}
void Entity::AddItem(Item* item)
{
m_Inventory.push_back(item);
G_PRINT_STATUS(GetFullName() + " got " + item->itemName);
}
void Entity::RemoveItem(unsigned int index)
{
std::vector<Item*>::iterator it{ m_Inventory.begin() + index };
m_Inventory.erase(it);
}
bool Entity::DropItem(std::vector<ItemTile>& items, unsigned int index)
{
if(index > m_Inventory.size())
return false;
ItemTile droppedItem{ m_Inventory[index], m_Pos };
items.push_back(droppedItem);
RemoveItem(index);
return true;
}
/// PRIVATE THINGS ///
void Entity::Die()
{
G_PRINT_STATUS(GetFullName() + " died!");
m_Dead = true;
}