Something went wrong on our end
-
Jake Feddersen authoredJake Feddersen authored
character.cpp 13.43 KiB
#include <ncurses.h>
#include <cstdint>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>
#include "character.h"
#include "distance_map.h"
#include "draw_dungeon.h"
#include "heap.h"
#include "util.h"
const char *pickup_header_top_bottom = "************************************";
const char *pickup_header_middle = "* Pick up item (',' to select) *";
const char *pickup_no_items = "No items on the floor here.";
static int32_t character_turn_cmp(const void *key, const void *with) {
if (((character *) key)->next_turn == ((character *) with)->next_turn) {
return ((character *) key)->seq_num - ((character *) with)->seq_num;
} else {
return ((character *) key)->next_turn - ((character *) with)->next_turn;
}
}
char characteristics_symbols[] = "0123456789abcdef";
const char *equipment_slot_names[] = {
"WEAPON",
"OFFHAND",
"RANGED",
"ARMOR",
"HELMET",
"CLOAK",
"GLOVES",
"BOOTS",
"AMULET",
"LIGHT",
"RING",
"RING"
};
character::character(int speed, char symbol, int hitpoints) {
pos.x = 0;
pos.y = 0;
this->speed = speed;
seq_num = 0;
next_turn = 0;
alive = 1;
this->symbol = symbol;
this->hitpoints = hitpoints;
}
character::~character() { }
item::item(object_description *desc, dice *damage, int hit, int dodge, int defence, int weight, int speed, int attribute, int value) :
desc(desc), damage(damage), hit(hit), dodge(dodge), defence(defence), weight(weight), speed(speed), attribute(attribute), value(value) {
desc->instances++;
}
item::~item() {
desc->instances--;
}
int item::get_color() {
return (int)desc->get_color();
}
char item::get_symbol() {
return desc->get_symbol();
}
std::string item::get_name() {
return desc->get_name() + " - Speed:" + std::to_string(speed) + " Damage:" + damage->to_string();
}
void item::pick_up() {
if (desc->get_artifact()) desc->invalidate();
}
monster::monster(uint8_t x, uint8_t y, monster_description *desc, int speed, uint32_t abilities, int hitpoints, dice *damage, char symbol) : character(speed, symbol, hitpoints) {
this->desc = desc;
desc->instances++;
static int next_seq = 1;
this->characteristics = abilities;
seq_num = next_seq++;
symbol = characteristics_symbols[characteristics];
this->damage = damage;
color_index = 0;
pos.x = x;
pos.y = y;
last_seen.x = x;
last_seen.y = y;
}
monster::~monster() {
desc->instances--;
}
void monster::die() {
desc->invalidate();
}
int monster::get_speed() {
return speed;
}
int monster::get_damage() {
return damage->roll();
}
std::string monster::get_name() {
return desc->name + " - Speed:" + std::to_string(speed) + " Damage:" + damage->to_string() + " Hitpoints:" + std::to_string(hitpoints);
}
monster_description *monster::get_description() {
return desc;
}
int monster::next_color() {
color_index++;
color_index = (color_index % desc->color.size());
return desc->color[color_index];
}
bool monster::is_player() {
return false;
}
int monster::has_characteristic(int bit) {
return characteristics & bit;
}
// Erratic tunneling monsters don't care about anything but the border
position monster::next_monster_move_tunnel_erratic(dungeon &d) {
position next_move;
next_move = pos;
while(next_move.x == pos.x && next_move.y == pos.y) {
next_move.x = randrange(std::max(1, pos.x - 1), std::min(MAP_WIDTH-2, pos.x + 1));
next_move.y = randrange(std::max(1, pos.y - 1), std::min(MAP_HEIGHT-2, pos.y + 1));
}
return next_move;
}
// Erratic non-tunneling monsters have to pick a random open space
position monster::next_monster_move_erratic(dungeon &d) {
position next_move;
next_move = pos;
while(next_move.x == pos.x && next_move.y == pos.y && !d.hardness[next_move.y][next_move.x]) {
next_move.x = randrange(std::max(1, pos.x - 1), std::min(MAP_WIDTH-2, pos.x + 1));
next_move.y = randrange(std::max(1, pos.y - 1), std::min(MAP_HEIGHT-2, pos.y + 1));
}
return next_move;
}
// Smart tunneling monsters move toward the last known position of the player following
// the tunneling distance map. Smart non-tunneling monsters do the same but use the non-tunneling
// distance map
position monster::next_monster_move_smart(dungeon &d) {
int i, j;
uint32_t dist_to_target[MAP_HEIGHT][MAP_WIDTH];
dijkstra_from_pos(d, dist_to_target, (has_characteristic(NPC_TUNNEL)) ? 255 : 1, last_seen.x, last_seen.y);
position best = {pos.x, pos.y};
uint32_t best_dist = dist_to_target[pos.y][pos.x];
for (i = std::max(0, pos.y-1); i < std::min(MAP_WIDTH, pos.y+2); i++) {
for (j = std::max(0, pos.x-1); j < std::min(MAP_WIDTH, pos.x+2); j++) {
if (dist_to_target[i][j] < best_dist || (dist_to_target[i][j] == best_dist && i == pos.y)) {
best_dist = dist_to_target[i][j];
best.x = j;
best.y = i;
}
}
}
return best;
}
// Dumb tunneling monsters move directly toward the player position if they know it, and
// move erratically otherwise
// Dumb non-tunneling monsters do the same, but can't tunnel
position monster::next_monster_move_dumb(dungeon &d) {
if (last_seen.x == pos.x && last_seen.y == pos.y) {
if (has_characteristic(NPC_TUNNEL)) {
return next_monster_move_tunnel_erratic(d);
} else {
return next_monster_move_erratic(d);
}
} else {
position next_move;
next_move.x = pos.x + sign(last_seen.x - pos.x);
next_move.y = pos.y + sign(last_seen.y - pos.y);
return next_move;
}
}
position monster::monster_move(dungeon &d) {
if (is_player()) {
return pos;
}
// Handle telepathic monsters
// If the monster is telepathic, update the position of the PC
// Otherwise if it can currently see the PC then update the position
// Otherwise if it is not smart then forget the pc position
if (has_characteristic(NPC_TELE)) {
last_seen = d.player_pos;
} else if (d.pc_sight[pos.y][pos.x]) {
last_seen = d.player_pos;
} else if (!(has_characteristic(NPC_SMART))) {
last_seen = pos;
}
// Handle erratic monsters
// If the monster is erratic, then 50% chance to move randomly
// Movement based on whether it can tunnel or not
if (has_characteristic(NPC_ERRATIC) && randchance(0.5)) {
if (has_characteristic(NPC_TUNNEL)) {
return next_monster_move_tunnel_erratic(d);
} else {
return next_monster_move_erratic(d);
}
}
// Handle smart/dumb monsters
if (has_characteristic(NPC_SMART)) {
return next_monster_move_smart(d);
} else {
return next_monster_move_dumb(d);
}
return pos;
}
player_character::player_character(uint8_t x, uint8_t y) : character(10, '@', 100) {
pos.x = x;
pos.y = y;
int i;
for (i = 0; i < 12; i++) {
equipment[i] = 0;
}
for (i = 0; i < 10; i++) {
inventory[i] = 0;
}
base_attack = new dice(0, 1, 4);
}
player_character::~player_character() {
delete base_attack;
for (int i = 0; i < 10; i++) {
if (inventory[i]) delete inventory[i];
}
for (int i = 0; i < 12; i++) {
if (equipment[i]) delete equipment[i];
}
}
bool player_character::is_player() {
return true;
}
int player_character::has_characteristic(int bit) {
return 0;
}
int player_character::next_color() {
return COLOR_WHITE;
}
int player_character::get_speed() {
int result = speed;
for (int i = 0; i < 12; i++) {
if (equipment[i]) result += equipment[i]->speed;
}
return std::max(result, 1);
}
int player_character::get_damage() {
int result = base_attack->roll();
if (equipment[0]) result = equipment[0]->damage->roll();
for (int i = 1; i < 12; i++) {
if (equipment[i]) result += equipment[i]->damage->roll();
}
return result;
}
int player_character::pick_up(std::vector<item *> &floor) {
int i, j, k;
int shouldExit = 0;
k = 0;
while(1) {
for (i = 2; i < 21; i++) {
for (j = 1; j < 79; j++) {
mvaddch(i, j, ' ');
}
}
mvaddstr(3, (80 - strlen(pickup_header_top_bottom)) / 2, pickup_header_top_bottom);
mvaddstr(4, (80 - strlen(pickup_header_middle)) / 2, pickup_header_middle);
mvaddstr(5, (80 - strlen(pickup_header_top_bottom)) / 2, pickup_header_top_bottom);
if (floor.size() == 0) {
mvaddstr(7, (80 - strlen(pickup_no_items)) / 2, pickup_no_items);
} else {
int top = std::max(0, k-2);
top = std::min(top, std::max(0, (int)floor.size()-5));
int bottom = std::min((int)floor.size()-1, top + 4);
int missing_top = top;
if (missing_top > 0) {
mvprintw(7, 38, "(+%d)", missing_top);
}
int missing_bottom = floor.size() - 1 - bottom;
if (missing_bottom > 0) {
mvprintw(19, 38, "(+%d)", missing_bottom);
}
for (i = top; i <= bottom; i++) {
int row = 9 + ((i - top) * 2);
if (i == k) {
mvprintw(row, 4, "*");
}
move(row, 6);
print_char(floor[i]->get_symbol(), floor[i]->get_color());
mvprintw(row, 8, floor[i]->get_name().c_str());
}
}
int c = getch();
if (c == 'q' || c == 'Q') {
shouldExit = 1;
break;
}
if (c == 27) {
break;
}
if (floor.size() == 0) continue;
if (c == KEY_DOWN) {
k = std::max(0, std::min((int)floor.size()-1, k+1));
}
if (c == KEY_UP) {
k = std::max(0, k-1);
}
if (c == ',') {
for (i = 0; i < 10; i++) {
if (!inventory[i]) {
inventory[i] = floor[k];
floor.erase(floor.begin() + k);
break;
}
}
break;
}
}
return shouldExit;
}
int player_character::print_inventory(const char *prompt) {
int i, j;
for (i = 2; i < 21; i++) {
for (j = 1; j < 79; j++) {
mvaddch(i, j, ' ');
}
}
mvprintw(2, 2, prompt);
for (i = 0; i < 10; i++) {
if (inventory[i]) {
mvprintw(4+i, 4, "%c) %s\t%s", (char)('0'+i), inventory[i]->desc->get_type_name(), inventory[i]->get_name().c_str());
} else {
mvprintw(4+i, 4, "%c) <empty>", (char)('0'+i));
}
}
int key = -1;
while ((key = getch()) == -1);
return key;
}
int player_character::print_equipment(const char *prompt) {
int i, j;
for (i = 2; i < 21; i++) {
for (j = 1; j < 79; j++) {
mvaddch(i, j, ' ');
}
}
mvprintw(2, 2, prompt);
for (i = 0; i < 12; i++) {
if (equipment[i]) {
mvprintw(4+i, 4, "%c) %s\t%s", (char)('a'+i), equipment[i]->desc->get_type_name(), equipment[i]->get_name().c_str());
} else {
mvprintw(4+i, 4, "%c) %s\t<empty>", (char)('a'+i), equipment_slot_names[i]);
}
}
int key = -1;
while ((key = getch()) == -1);
return key;
}
void player_character::wear_item() {
int slot = print_inventory("Select an item to equip, or ESC to cancel:");
if (!(slot >= '0' && slot <= '9')) return;
item *item_to_equip = inventory[slot - '0'];
if (!item_to_equip) return;
inventory[slot-'0'] = 0;
item** slot_to_equip;
switch(item_to_equip->desc->get_type()) {
case objtype_WEAPON:
slot_to_equip = equipment + 0;
break;
case objtype_OFFHAND:
slot_to_equip = equipment + 1;
break;
case objtype_RANGED:
slot_to_equip = equipment + 2;
break;
case objtype_ARMOR:
slot_to_equip = equipment + 3;
break;
case objtype_HELMET:
slot_to_equip = equipment + 4;
break;
case objtype_CLOAK:
slot_to_equip = equipment + 5;
break;
case objtype_GLOVES:
slot_to_equip = equipment + 6;
break;
case objtype_BOOTS:
slot_to_equip = equipment + 7;
break;
case objtype_AMULET:
slot_to_equip = equipment + 8;
break;
case objtype_LIGHT:
slot_to_equip = equipment + 9;
break;
case objtype_RING:
slot_to_equip = (equipment[10]) ? equipment + 11 : equipment + 10;
break;
default:
break;
}
item *item_to_deequip = *slot_to_equip;
*slot_to_equip = item_to_equip;
inventory[slot-'0'] = item_to_deequip;
}
item *player_character::take_off() {
int slot = print_equipment("Select item to take off, or ESC to cancel:");
if (!(slot >= 'a' && slot <= 'l')) return 0;
item *item_to_take_off = equipment[slot-'a'];
equipment[slot-'a'] = 0;
for (int i = 0; i < 10; i++) {
if (!inventory[i]) {
inventory[i] = item_to_take_off;
item_to_take_off = 0;
break;
}
}
return item_to_take_off;
}
item *player_character::drop_item() {
int slot = print_inventory("Select an item to drop, or ESC to cancel:");
if (!(slot >= '0' && slot <= '9')) return 0;
item *item_to_drop = inventory[slot-'0'];
inventory[slot-'0'] = 0;
return item_to_drop;
}
void player_character::expunge() {
int slot = print_inventory("Select an item to expunge, or ESC to cancel:");
if (!(slot >= '0' && slot <= '9')) return;
item *item_to_destroy = inventory[slot-'0'];
inventory[slot-'0'] = 0;
delete item_to_destroy;
}
void player_character::list_inventory() {
print_inventory("Your current inventory:");
}
void player_character::list_equipment() {
print_equipment("Your current equipment:");
}
void player_character::inspect_item() {
int i, j;
int slot = print_inventory("Select an item to inspect, or ESC to cancel:");
if (!(slot >= '0' && slot <= '9')) return;
item *item_to_inspect = inventory[slot-'0'];
for (i = 2; i < 21; i++) {
for (j = 1; j < 79; j++) {
mvaddch(i, j, ' ');
}
}
mvprintw(2, 2, "%s", item_to_inspect->get_name().c_str());
mvprintw(4, 0, "%s", item_to_inspect->desc->get_description().c_str());
while((getch() == -1));
}
void init_character_turn_heap(heap_t *h) {
heap_init(h, character_turn_cmp, NULL);
}