HALTED: major overhaul for character

This commit is contained in:
2024-07-13 22:05:17 -04:00
parent 39d3447593
commit f0aa564e08
10 changed files with 298 additions and 172 deletions

View File

@ -54,10 +54,6 @@ int character::get_room_num() const {
return room_num; return room_num;
} }
bool character::is_hostile() const {
return hostile;
}
void character::set_room_num(const int room) { void character::set_room_num(const int room) {
room_num = room; room_num = room;
} }
@ -86,36 +82,6 @@ void character::set_hitrate(const fraction nhitrate) {
base_hit_rate = nhitrate; base_hit_rate = nhitrate;
} }
void character::set_hostile(const bool is_hostile) {
hostile = is_hostile;
}
void character::apply_buff(const stat_name statn, const int amount) {
// TODO: add checks for bounds
switch (statn) {
case stat_name::HP:
HP += amount;
break;
case stat_name::ATK:
ATK += amount;
break;
case stat_name::DEF:
DEF += amount;
break;
case stat_name::hostile: {
if (amount > 0)
hostile = true;
else
hostile = false;
break;
}
}
}
direction_list character::moveable(const position_list &available_positions) direction_list character::moveable(const position_list &available_positions)
const { const {
direction_list result; direction_list result;
@ -128,13 +94,17 @@ const {
return result; return result;
} }
void character::apply(direction &dir, potion_list &plist) { character::apply_result character::apply(const direction &dir,
potion_list &plist) {
for (size_t i = 0; i < plist.size(); ++i) for (size_t i = 0; i < plist.size(); ++i)
if (pos + MOVE[dir] == plist[i]->get_pos()) { if (pos + MOVE[dir] == plist[i]->get_pos()) {
apply_result res{applied, plist[i]};
insert_potion(plist[i]); insert_potion(plist[i]);
plist.erase(plist.begin() + i); plist.erase(plist.begin() + i);
return; return res;
} }
return {applied_nothing, nullptr};
} }
void character::insert_potion(potion *p) { void character::insert_potion(potion *p) {
@ -191,30 +161,7 @@ result character::move(const direction dir,
return result::fine; return result::fine;
} }
result character::move_or_attack(const direction dir,
const position_list &available_positions,
character_list &chlist) {
auto res = this->move(dir, available_positions);
if (res != result::fine)
return res;
return this->attack(dir, chlist);
}
result character::move_or_attack(const direction dir,
const position_list &available_positions,
character *ch) {
auto res = this->move(dir, available_positions);
if (res != result::fine)
return res;
return this->attack(dir, ch);
}
int calc_dmg(const int ATK, const int DEF) { int calc_dmg(const int ATK, const int DEF) {
return ceil((100 / (100 + DEF)) * ATK); return ceil((100.0f / (100.0f + DEF)) * ATK);
} }

View File

@ -29,21 +29,33 @@ public:
direction_list moveable(const position_list &available_positions) const; direction_list moveable(const position_list &available_positions) const;
virtual result move(const direction dir, virtual result move(const direction dir,
const position_list &available_positions); const position_list &available_positions);
virtual result attack(const direction dir,
character_list &chlist) = 0;
virtual result move_or_attack(const direction dir,
const position_list &available_positions,
character_list &chlist);
virtual result attack(const direction dir, character *ch) = 0; struct attack_result {
virtual result move_or_attack(const direction dir, result res;
const position_list &available_positions, int dmg_dealt;
character *ch); int remaining_HP;
character *which; // hit whom
};
virtual void apply(direction &dir, struct apply_result {
potion_list &potions); result res;
virtual result get_hit(const enum race &race, const int atk, potion *which; // applied which potion
const fraction hitrate) = 0; };
struct hit_result {
result res;
int dmg_dealt;
int remaining_HP;
};
virtual attack_result attack(const direction dir,
character_list &chlist) = 0;
virtual attack_result attack(const direction dir, character *ch) = 0;
virtual apply_result apply(const direction &dir,
potion_list &potions);
virtual hit_result get_hit(const enum race &race, const int atk,
const fraction hitrate) = 0;
// overload for different races // overload for different races
virtual void print(display *out, bool player = false); virtual void print(display *out, bool player = false);
@ -60,7 +72,6 @@ public:
int get_gold() const; int get_gold() const;
float get_hitrate_real() const; float get_hitrate_real() const;
fraction get_hitrate() const; fraction get_hitrate() const;
bool is_hostile() const;
int get_room_num() const; int get_room_num() const;
void set_position(const position &npos); void set_position(const position &npos);
@ -69,14 +80,8 @@ public:
void set_DEF(const int nDEF); void set_DEF(const int nDEF);
void set_gold(const int ngold); void set_gold(const int ngold);
void set_hitrate(const fraction nhitrate); void set_hitrate(const fraction nhitrate);
virtual void set_hostile(const bool is_hostile);
void set_room_num(const int room); void set_room_num(const int room);
// if stat is hostile, positive to set it to hostile,
// negative to set it to peaceful
void apply_buff(const stat_name statn, const int amount);
// void apply_buff(const stat_name statn, const float mul);
// reserved for later
protected: protected:
RNG *rng; RNG *rng;
const enum race race; const enum race race;
@ -94,7 +99,6 @@ protected:
potion_list potions; // inventory potion_list potions; // inventory
potion_list effects; // applied potions potion_list effects; // applied potions
bool hostile;
int room_num; int room_num;
private: private:
void insert_potion(potion *p); void insert_potion(potion *p);

View File

@ -9,7 +9,11 @@ const int INF = 0x3F3F3F3F;
enum error {none}; enum error {none};
// TODO: update result to include subject // TODO: update result to include subject
enum result {fine, died, go_down, go_up, hit, moved, miss, terminate}; // fine will waste a turn
enum result {fine, died, go_down, go_up, hit, moved,
miss, terminate, applied, applied_nothing,
toggle_the_world, restart_game, unknown
};
enum game_status {terminated, main_menu, in_game, options, enum game_status {terminated, main_menu, in_game, options,
dead, won, escaped, restart dead, won, escaped, restart
@ -22,7 +26,7 @@ enum game_command {game_command_terminate = 0,
apply_north, apply_south, apply_east, apply_west, apply_north, apply_south, apply_east, apply_west,
apply_northeast, apply_northwest, apply_northeast, apply_northwest,
apply_southeast, apply_southwest, apply_southeast, apply_southwest,
apply_prompt, apply_panic, // for curses_input specifically apply_panic, // for curses_input specifically
attack_north, attack_south, attack_east, attack_west, attack_north, attack_south, attack_east, attack_west,
attack_northeast, attack_northwest, attack_northeast, attack_northwest,
attack_southeast, attack_southwest, attack_southeast, attack_southwest,
@ -33,22 +37,28 @@ enum game_command {game_command_terminate = 0,
enum stat_name {HP, ATK, DEF, hostile}; enum stat_name {HP, ATK, DEF, hostile};
const int RACE_CNT = 5; // TODO: update as you go const int RACE_CNT = 6; // TODO: update as you go
enum race {rshade = 0, rvampire, rgoblin, rdrow, rdragon /* TODO: fill out the other races (including enemies) */}; enum race {rshade = 0, rvampire, rgoblin, rdrow, rdragon, rmerchant /* TODO: fill out the other races (including enemies) */};
// TODO: fill out the other races (including enemies) // TODO: fill out the other races (including enemies)
const int MAX_HP[RACE_CNT] = {125, INF, 110, 150, 150}; const int MAX_HP[RACE_CNT] = {125, INF, 110, 150, 150, 30};
const int STARTING_HP[RACE_CNT] = {125, 50, 110, 150, 150}; const int STARTING_HP[RACE_CNT] = {125, 50, 110, 150, 150, 30};
const int STARTING_ATK[RACE_CNT] = {25, 25, 15, 25, 20}; const int STARTING_ATK[RACE_CNT] = {25, 25, 15, 25, 20, 70};
const int STARTING_DEF[RACE_CNT] = {25, 25, 20, 15, 20}; const int STARTING_DEF[RACE_CNT] = {25, 25, 20, 15, 20, 5};
const char CHARACTER_REP[RACE_CNT] = {'S', 'V', 'G', 'd', 'D'}; const char CHARACTER_REP[RACE_CNT] = {'S', 'V', 'G', 'd', 'D', 'M'};
const char *RACE_NAME[RACE_CNT] = {
"Shade", "Vampire", "Goblin", "Drow", "Dragon"
};
const int POTION_TYPE_CNT = 6; const int POTION_TYPE_CNT = 6;
const int DEFAULT_POTION_TYPE_CNT = 6; const int DEFAULT_POTION_TYPE_CNT = 6;
enum potion_type {restore_health = 0, boost_atk, boost_def, enum potion_type {restore_health = 0, boost_atk, boost_def,
poison_health, wound_atk, wound_def poison_health, wound_atk, wound_def
}; };
const char *POTION_REAL_NAME[POTION_TYPE_CNT] = {
"RH", "BA", "BD", "PH", "WA", "WD"
};
// Calculation priorities // Calculation priorities
const int CALC_ADD_BASE = 0; const int CALC_ADD_BASE = 0;

View File

@ -9,7 +9,8 @@ game::game(const enum race starting_race,
logger *new_log, logger *new_log,
RNG *new_rng): RNG *new_rng):
enabled_features{enabled_features}, enabled_features{enabled_features},
in{new_in}, out{new_out}, log{new_log}, rng{new_rng} { in{new_in}, out{new_out}, log{new_log}, rng{new_rng},
curr_turn{0} {
const position nil{0, 0}; const position nil{0, 0};
the_world = false; the_world = false;
@ -28,7 +29,11 @@ game::game(const enum race starting_race,
levels.reserve(max_level); levels.reserve(max_level);
curr_level = 0; curr_level = 0;
hostile_merchants = false;
new_level(); new_level();
msg += "Player character has spawned. ";
print();
} }
void game::new_level() { void game::new_level() {
@ -41,84 +46,116 @@ void game::new_level() {
} }
result game::player_moves(game_command cmd) { result game::player_moves(game_command cmd) {
switch (cmd) { if (cmd == game_command_terminate) {
case game_command_terminate:
return result::terminate; return result::terminate;
} else if (cmd >= move_north && cmd <= move_southwest) { // Tried to move
case move_east: {
if (enabled_features & FEATURE_NCURSES) if (enabled_features & FEATURE_NCURSES)
return player_move_or_attack(east); return player_move_or_attack((direction)(cmd - move_north));
return player->move(east, levels[curr_level]->get_available_around( return player->move((direction)(cmd - move_north),
player->get_position()));
}
case move_west: {
if (enabled_features & FEATURE_NCURSES)
return player_move_or_attack(west);
return player->move(west, levels[curr_level]->get_available_around(
player->get_position()));
}
case move_north: {
if (enabled_features & FEATURE_NCURSES)
return player_move_or_attack(north);
return player->move(north, levels[curr_level]->get_available_around(
player->get_position()));
}
case move_south: {
if (enabled_features & FEATURE_NCURSES)
return player_move_or_attack(south);
return player->move(south, levels[curr_level]->get_available_around(
player->get_position()));
}
case move_northeast: {
if (enabled_features & FEATURE_NCURSES)
return player_move_or_attack(northeast);
return player->move(northeast,
levels[curr_level]->get_available_around( levels[curr_level]->get_available_around(
player->get_position())); player->get_position()));
} else if (cmd >= apply_north && cmd <= apply_southwest) {
auto res = player->apply((direction)(cmd - apply_north),
levels[curr_level]->get_plist());
if (res.res == applied) {
msg += "PC used potion ";
msg += POTION_REAL_NAME[res.which->get_type()];
msg += ". ";
} else {
msg += "PC tried to drink thin air. ";
}
return res.res;
} else if (cmd == apply_panic) {
msg += "PC tried to use in some non-existent direction. ";
return fine;
} else if (cmd >= attack_north && cmd <= attack_southwest) {
auto res = player->attack((direction)(cmd - attack_north),
levels[curr_level]->get_chlist());
if (res.res == hit) {
msg += "PC deals " + std::to_string(res.dmg_dealt) +
" damage to " +
CHARACTER_REP[res.which->get_race()] +
" (" + std::to_string(res.remaining_HP) +
" HP). ";
} else if (res.res == miss) {
msg += "PC missed ";
msg += CHARACTER_REP[res.which->get_race()];
msg += ". ";
} else {
msg += "PC tried to attack thin air. ";
}
if (res.which->get_race() == rmerchant)
hostile_merchants = true;
return res.res;
} else if (cmd == up_stairs) {
if (player->get_position() ==
levels[curr_level]->get_up_stairs())
return go_up;
msg += "PC tried to fly through the ceiling. ";
return fine;
} else if (cmd == down_stairs) {
if (player->get_position() ==
levels[curr_level]->get_down_stairs())
return go_down;
msg += "PC tried to dig through the floor. ";
return fine;
} else if (cmd == the_world) {
msg += "PC toggled Stand: The World! ";
return toggle_the_world;
} else if (cmd == game_restart) {
return restart_game;
} else if (cmd == game_command_pass) {
return fine;
} else if (cmd == game_command_panic) {
msg += "PC tried to produce some undefined behaviour. ";
return unknown;
} }
case move_northwest: { msg += "PC tried to produce some undefined behaviour. ";
if (enabled_features & FEATURE_NCURSES) return unknown;
return player_move_or_attack(northwest); }
return player->move(northwest, #include <algorithm>
levels[curr_level]->get_available_around( bool compare_characters(character *&a, character *&b) {
player->get_position())); return a->get_position() < b->get_position();
} }
case move_southeast: { void game::move_enemies() {
if (enabled_features & FEATURE_NCURSES) if (the_world)
return player_move_or_attack(southeast); return;
return player->move(southeast, character_list enemies = levels[curr_level]->get_chlist();
levels[curr_level]->get_available_around( std::sort(enemies.begin(), enemies.end(), &game::compare_characters);
player->get_position()));
}
case move_southwest: { for (auto ch : enemies) {
if (enabled_features & FEATURE_NCURSES)
return player_move_or_attack(southwest);
return player->move(southwest,
levels[curr_level]->get_available_around(
player->get_position()));
}
} }
} }
game_status game::run() { game_status game::run() {
player->start_turn(); msg = "";
auto res = player_moves(in->get_command()); auto res = player_moves(in->get_command());
if (!(enabled_features & FEATURE_NCURSES) &&
res == result::moved &&
(player->get_position() ==
levels[curr_level]->get_down_stairs()))
res = go_down;
if (!(enabled_features & FEATURE_NCURSES) &&
res == result::moved &&
(player->get_position() ==
levels[curr_level]->get_up_stairs()))
res = go_up;
switch (res) { switch (res) {
case result::terminate: case result::terminate:
return terminated; return terminated;
@ -133,6 +170,7 @@ game_status game::run() {
player->discard_level_effects(); player->discard_level_effects();
++curr_level; ++curr_level;
new_level(); new_level();
msg += "PC went down a floor. ";
break; break;
} }
@ -140,19 +178,120 @@ game_status game::run() {
if (curr_level == 0) if (curr_level == 0)
return game_status::escaped; return game_status::escaped;
player->discard_level_effects();
--curr_level; --curr_level;
player->set_position(levels[curr_level]->get_down_stairs()); player->set_position(levels[curr_level]->get_down_stairs());
msg += "PC went up a floor. ";
break; break;
} }
case toggle_the_world:
the_world = !the_world;
break;
case restart_game:
return restart;
case unknown:
case fine:
case applied_nothing:
++curr_turn;
return game_status::in_game;
default: default:
break; break;
} }
player->start_turn();
player->apply_effects();
if (player->get_HP() <= 0)
return game_status::dead;
move_enemies(); move_enemies();
if (player->get_HP() <= 0) if (player->get_HP() <= 0)
return game_status::dead; return game_status::dead;
++curr_turn;
return game_status::in_game; return game_status::in_game;
} }
const position STATUS_RACE{0, 25};
const position STATUS_FLOOR{69, 25};
const position STATUS_HP{0, 26};
const position STATUS_ATK{0, 27};
const position STATUS_DEF{0, 28};
const position STATUS_ACTION{0, 29};
const char *ASK_ATK_MERCHANT =
"Action: Do you really want to attack the peaceful merchant? [Y/N]";
size_t game::get_curr_turn() const {
return curr_turn;
}
void game::print() {
if (msg.length() > MAX_MSG_LENGTH) {
msg.resize(MAX_MSG_LENGTH);
msg += "...";
}
levels[curr_level]->print(out);
player->print(out, true);
std::string tmp = "Race: ";
tmp += RACE_NAME[player->get_race()];
tmp += " Gold: " + std::to_string(player->get_gold());
out->print_str(STATUS_RACE, tmp);
tmp = "Floor " + std::to_string(curr_level + 1);
out->print_str(STATUS_FLOOR, tmp);
tmp = "Atk: " + std::to_string(player->get_ATK());
out->print_str(STATUS_ATK, tmp);
tmp = "Def: " + std::to_string(player->get_DEF());
out->print_str(STATUS_DEF, tmp);
tmp = "Action: " + msg;
out->print_str(STATUS_ACTION, tmp);
}
result game::player_move_or_attack(const direction &dir) {
auto avlbl = levels[curr_level]->get_available_around(
player->get_position());
if (find(avlbl, player->get_position() + MOVE[dir])
!= avlbl.size()) {
player->set_position(player->get_position() + MOVE[dir]);
return result::moved;
}
auto tmp = player->get_position() + MOVE[dir];
character *target;
for (auto ch : levels[curr_level]->get_chlist())
if (tmp == ch->get_position()) {
target = ch;
}
auto res = player->attack(dir, target);
if (target->get_race() == rmerchant)
hostile_merchants = true;
if (res.res == hit) {
msg += "PC deals " +
std::to_string(res.dmg_dealt) + " damage to " +
CHARACTER_REP[res.which->get_race()] + " (" +
std::to_string(res.remaining_HP) + " HP). ";
} else if (res.res == miss) {
msg += "PC missed ";
msg += CHARACTER_REP[res.which->get_race()];
msg += ". ";
} else {
msg += "PC tried to attack thin air. ";
}
return res.res;
}

View File

@ -15,6 +15,7 @@ private:
static const size_t MAX_LEVEL_CNT = 50; static const size_t MAX_LEVEL_CNT = 50;
static const size_t MIN_LEVEL_CNT = 30; static const size_t MIN_LEVEL_CNT = 30;
static const size_t DEFAULT_MAX_LEVEL = 5; static const size_t DEFAULT_MAX_LEVEL = 5;
static const size_t MAX_MSG_LENGTH = 300;
const feature enabled_features; const feature enabled_features;
@ -23,6 +24,8 @@ private:
logger *log; logger *log;
RNG *rng; RNG *rng;
unsigned int curr_turn;
std::unique_ptr<character> player; std::unique_ptr<character> player;
std::vector<std::unique_ptr<level>> levels; std::vector<std::unique_ptr<level>> levels;
@ -30,6 +33,10 @@ private:
size_t curr_level; size_t curr_level;
bool the_world; bool the_world;
bool hostile_merchants;
// shrink if over 300
std::string msg;
public: public:
game(const enum race starting_race, game(const enum race starting_race,
const feature enabled_features, const feature enabled_features,
@ -39,13 +46,15 @@ public:
RNG *new_rng); RNG *new_rng);
game_status run(); game_status run();
size_t get_curr_level() const; size_t get_curr_level() const;
size_t get_curr_turn() const;
const character *get_player() const; const character *get_player() const;
void print();
private: private:
result player_moves(game_command cmd); result player_moves(game_command cmd);
void new_level(); void new_level();
void move_enemies(); void move_enemies();
result player_move_or_attack(const direction &dir); result player_move_or_attack(const direction &dir);
bool compare_characters(const character *&a, const character *&b);
}; };
#endif #endif

View File

@ -102,7 +102,6 @@ void level::gen_potions(RNG *rng, std::vector<position_list> &tiles) {
void level::print(display *out) const { void level::print(display *out) const {
map.print(out); map.print(out);
player->print(out, true);
for (auto ch : chlist) for (auto ch : chlist)
ch->print(out); ch->print(out);

View File

@ -84,3 +84,8 @@ float distance(const position &a, const position &b) {
int distance_sqr(const position &a, const position &b) { int distance_sqr(const position &a, const position &b) {
return pow(a.x - b.x, 2) + pow(a.y - b.y, 2); return pow(a.x - b.x, 2) + pow(a.y - b.y, 2);
} }
bool is_adjacent(const position &a, const position &b) {
return (a.x - b.x >= -1 && a.x - b.x <= 1) &&
(a.y - b.y >= -1 && a.y - b.y <= 1);
}

View File

@ -29,5 +29,6 @@ void remove_from_list(std::vector<position> &sorted_positions,
float distance(const position &a, const position &b); float distance(const position &a, const position &b);
int distance_sqr(const position &a, const position &b); int distance_sqr(const position &a, const position &b);
bool is_adjacent(const position &a, const position &b);
#endif #endif

View File

@ -9,34 +9,46 @@ shade::shade(RNG *rng, const position &pos):
hostile = true; hostile = true;
} }
result shade::attack(const direction dir, character_list &chlist) { character::attack_result shade::attack(const direction dir,
character_list &chlist) {
position tmp{pos + MOVE[dir]}; position tmp{pos + MOVE[dir]};
for (auto &ch : chlist) for (auto &ch : chlist)
if (tmp == ch->get_position()) { if (tmp == ch->get_position()) {
return ch->get_hit(race, ATK, base_hit_rate); auto res = ch->get_hit(race, ATK, base_hit_rate);
return {res.res, res.dmg_dealt, res.remaining_HP, ch};
} }
return result::fine; return {fine, 0, 0, nullptr};
} }
result shade::attack(const direction dir, character *ch) { character::attack_result shade::attack(const direction dir, character *ch) {
position tmp{pos + MOVE[dir]}; position tmp{pos + MOVE[dir]};
if (tmp == ch->get_position()) if (tmp == ch->get_position()) {
return ch->get_hit(race, ATK, base_hit_rate); auto res = ch->get_hit(race, ATK, base_hit_rate);
return {res.res, res.dmg_dealt, res.remaining_HP, ch};
}
return result::fine; return {fine, 0, 0, nullptr};
} }
result shade::get_hit(const enum race &race, const int atk, character::hit_result shade::get_hit(const enum race &race, const int atk,
const fraction hitrate) { const fraction hitrate) {
if (rng->trial(hitrate)) // This is a hit! if (rng->trial(hitrate)) {
HP = std::max(HP - calc_dmg(atk, DEF), 0); int tmp = calc_dmg(atk, DEF);
if (HP == 0) if (tmp > HP)
return result::died; tmp = HP;
return result::hit; HP -= tmp;
if (HP == 0)
return {result::died, tmp, HP};
return {result::hit, tmp, HP};
}
return {result::miss, 0, HP};
} }

View File

@ -6,11 +6,11 @@
class shade final: public character { class shade final: public character {
public: public:
shade(RNG *rng, const position &pos); // spawn at a random place shade(RNG *rng, const position &pos); // spawn at a random place
virtual result attack(const direction dir, virtual attack_result attack(const direction dir,
character_list &chlist) override; character_list &chlist) override;
virtual result get_hit(const enum race &race, const int atk, virtual hit_result get_hit(const enum race &race, const int atk,
const fraction hit_rate) override; const fraction hit_rate) override;
virtual result attack(const direction dir, character *ch) override; virtual attack_result attack(const direction dir, character *ch) override;
}; };
#endif #endif