Skip to content
Snippets Groups Projects
character.cpp 8.06 KiB
#include <ncurses.h>
#include <cstdint>
#include <iostream>
#include <algorithm>
#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";

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();
}

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::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;
  }
}

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::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, 28, "*");
        }
        
        move(row, 30);
        print_char(floor[i]->get_symbol(), floor[i]->get_color());
        
        mvprintw(row, 32, floor[i]->desc->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;
}      

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