Skip to content
Snippets Groups Projects
character.cpp 13.4 KiB
Newer Older
Jake Feddersen's avatar
Jake Feddersen committed
#include <ncurses.h>
#include <cstdint>
#include <iostream>
#include <algorithm>
Jake Feddersen's avatar
Jake Feddersen committed
#include <string>
Jake Feddersen's avatar
Jake Feddersen committed
#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"
};

Jake Feddersen's avatar
Jake Feddersen committed
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();
}

Jake Feddersen's avatar
Jake Feddersen committed
std::string item::get_name() {
  return desc->get_name() + " - Speed:" + std::to_string(speed) + " Damage:" + damage->to_string();
}

Jake Feddersen's avatar
Jake Feddersen committed
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();
}

Jake Feddersen's avatar
Jake Feddersen committed
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;
}

Jake Feddersen's avatar
Jake Feddersen committed
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;
}

Jake Feddersen's avatar
Jake Feddersen committed
player_character::player_character(uint8_t x, uint8_t y) : character(10, '@', 100) {
Jake Feddersen's avatar
Jake Feddersen committed
  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;
  }
Jake Feddersen's avatar
Jake Feddersen committed
  
  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];
  }
Jake Feddersen's avatar
Jake Feddersen committed
}

bool player_character::is_player() {
  return true;
}

int player_character::has_characteristic(int bit) {
  return 0;
}

int player_character::next_color() {
  return COLOR_WHITE;
}

Jake Feddersen's avatar
Jake Feddersen committed
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;
}

Jake Feddersen's avatar
Jake Feddersen committed
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;
Jake Feddersen's avatar
Jake Feddersen committed
      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) {
Jake Feddersen's avatar
Jake Feddersen committed
          mvprintw(row, 4, "*");
Jake Feddersen's avatar
Jake Feddersen committed
        }
        
Jake Feddersen's avatar
Jake Feddersen committed
        move(row, 6);
Jake Feddersen's avatar
Jake Feddersen committed
        print_char(floor[i]->get_symbol(), floor[i]->get_color());
        
Jake Feddersen's avatar
Jake Feddersen committed
        mvprintw(row, 8, floor[i]->get_name().c_str());
    int c = getch();
Jake Feddersen's avatar
Jake Feddersen committed
    
    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;
    }
Jake Feddersen's avatar
Jake Feddersen committed
  }
  
  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]) {
Jake Feddersen's avatar
Jake Feddersen committed
      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]) {
Jake Feddersen's avatar
Jake Feddersen committed
      mvprintw(4+i, 4, "%c) %s\t%s", (char)('a'+i), equipment[i]->desc->get_type_name(), equipment[i]->get_name().c_str());
Jake Feddersen's avatar
Jake Feddersen committed
      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() {
Jake Feddersen's avatar
Jake Feddersen committed
  int slot = print_inventory("Select an item to drop, or ESC to cancel:");
  if (!(slot >= '0' && slot <= '9')) return 0;
  
Jake Feddersen's avatar
Jake Feddersen committed
  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;
  
Jake Feddersen's avatar
Jake Feddersen committed
  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() {
Jake Feddersen's avatar
Jake Feddersen committed
  int i, j;
  
  int slot = print_inventory("Select an item to inspect, or ESC to cancel:");
  if (!(slot >= '0' && slot <= '9')) return;
  
Jake Feddersen's avatar
Jake Feddersen committed
  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));
Jake Feddersen's avatar
Jake Feddersen committed

void init_character_turn_heap(heap_t *h) {
  heap_init(h, character_turn_cmp, NULL);
}