/**************************************************************
 * FFTacticsMUD : ch.cpp                                      *
 **************************************************************
 * (c) 2002 Damien Dailidenas (Trenton). All rights reserved. *
 **************************************************************/

#include "main.h"
#include <strstream>
#include <iomanip>
#include <set>
#include "mysql/mysql.h"
#include <cstdio>

void CH::printf(const string str, const bool sleeping = false) {
  if(npc)
    return;

  string new_str = str;
  string::size_type pos;

  if(!desc)
    return;

  while((pos = new_str.find('{')) != string::npos)
    new_str.replace(pos, 2, colour(new_str[pos + 1]));

  desc->printf(new_str);
  return;
}

void CH::printf(char *txt, ...) {
  char buf[MSL], string[MSL];
  va_list args;  

  if(!desc)
    return;
        
  va_start(args, txt);
  vsprintf(string, txt, args);
  va_end(args);
  buf[0] = '\0';
      
  for(unsigned short x = 0; x < strlen(string); ++x) {
    if(string[x] == '{') {
      ++x;
      strcat(buf, colour(string[x]).c_str());
    }
    else
      sprintf(buf + strlen(buf), "%c", string[x]);
  }

  desc->printf(buf);
  return;
}

CH *get_ch(const string name) {
  string arg;
  short number = number_arg(name, arg), count = 0;
  
  for(CH *ch = ch_list; ch; ch = ch->next) {
    if(!find(arg, ch->name))
      continue;
    
    if(++count == number)
      return ch;
  }
  
  return NULL;
} 

CH::CH() {
  next=NULL;
  next_in_room=NULL;
  next_in_battle=NULL;
  next_in_group=NULL;
  following=NULL;
  desc=NULL;
  npc=confirm_delete=snooping=admin=disabled=false;
  act=wiznet=0;      
  mins_total=0;
  pks=pdeaths=mins_login=mins_ave=0;
  PK=false;
  area=NULL;
  room=NULL;
  battle_room=orig_room=to_room=NULL;
  objects=NULL;

  for(short x = 0; x < 5; ++x)
    eq[x] = NULL;

  status_list=NULL;
  battle=NULL;
  group=NULL;
  acted = moved = false;

  for(short x = 0; x < MAX_SKILLS; ++x)
    skill[x] = false;

  HPP = MPP = SpP = MAP = PAP = 0;
  money = strengthen = weakness = imm = absorb = half = 0;
  timer = 0;
  sex = cjob = CT = wait = HP[0] = HP[1] = MP[0] = MP[1] = PA = MA = Sp = Br = Mv = Ju = Ev = Fa = lvl = exp = moves = 0;

  for(short x = 0; x < MAX_JOBS; ++x)
    job[x].lvl = job[x].JP = job[x].exp = job[x].subjob = job[x].reaction = job[x].support = job[x].move = 0;

  action.target = NULL;
  action.ctr = action.id = 0;
  action.action = NULL;
  
  temp_action.action = NULL;
  temp_action.range = 0;

  for(short x = 0; x < MAX_BATTLES; ++x)
    battles[x] = false;

  birthday[0]=birthday[1]=0;
  return;
}

CH::~CH() {
  OBJ *obj;
  leave_group();
  save_objects();

  while((obj = objects))
    zap(obj);

  save();
  clear_status(true);
  from_room();
  from_battle_room();

  if(this == ch_list)
    ch_list = next;
  else {
    for(CH *ch = ch_list; ch; ch = ch->next) {
      if(ch->next == this) {
	ch->next = next;
	break;
      }
    }
  }
  
  if(desc)
    desc->ch = NULL;

  return;
}

void CH::save() {
  if(npc || !room || confirm_delete)
    return;

  MYSQL mysql;
  short rows, x;
  time_t now_t;
  struct tm now;
  ostrstream ost, ost_date;
  char query[256];
  mysql_init(&mysql);

  if(!mysql_real_connect(&mysql, DB_LOC, DB_USER, DB_PASS, DB_NAME, 0, NULL, 0))
    return;

  time(&now_t);
  now = *localtime(&now_t); 
  ost_date << (now.tm_year + 1900) << "/" << setfill('0') << setw(2) << (now.tm_mon + 1) << "/" << setw(2) << now.tm_mday << " " << str_time().substr(11, 8) << ends;
  ost << "SELECT * FROM players WHERE name='" << name << "'" << ends;
  mysql_query(&mysql, ost.str());
  rows = mysql_num_rows(mysql_store_result(&mysql));
  mysql_free_result(mysql_store_result(&mysql));
  ost.freeze(false);
  ost.seekp(0, ios::beg);

  if(rows) {
    ost << "UPDATE players SET name='" << name << "'";
    ost << ", pwd='" << pwd << "'";
    ost << ", email='" << email << "'";
    ost << ", llogoff='" << ost_date.str() << "'";
    ost << ", money=" << money;
    ost << ", job=" << cjob;
    ost << ", lvl=" << lvl;
    ost << ", exp=" << exp;
    ost << ", HP=" << HP[0];
    ost << ", HPmax=" << HP[1];
    ost << ", MP=" << MP[0];
    ost << ", MPmax=" << MP[1];
    ost << ", PA=" << PA;
    ost << ", MA=" << MA;
    ost << ", Spd=" << Sp;
    ost << ", Mv=" << Mv;
    ost << ", Ju=" << Ju;
    ost << ", Ev=" << Ev;
    ost << ", Br=" << Br;
    ost << ", Fa=" << Fa;
    ost << ", area=" << area->id;
    ost << ", mins_total=" << mins_total;
    ost << ", mins_ave=" << mins_ave;
    ost << ", mins_login=" << mins_login;
    ost << ", disabled=" << disabled << " WHERE name='" << name << "'" << ends;
  }
  else {
    ost << "INSERT INTO players(name, pwd, email, created, llogoff, bmonth, bday, HPP, MPP, SpP, PAP, MAP, money, sex, job, lvl, exp, HP, HPmax, MP, MPmax, PA, MA, Spd, Mv, Ju, Ev, Br, Fa, area, mins_total, mins_ave, mins_login, disabled) VALUES(";
    ost << "'" << name << "'";
    ost << ", '" << pwd << "'";
    ost << ", '" << email << "'";
    ost << ", '" << ost_date.str() << "'";
    ost << ", '" << ost_date.str() << "'";
    ost << ", " << birthday[0];
    ost << ", " << birthday[1];
    ost << ", " << HPP;
    ost << ", " << MPP;
    ost << ", " << SpP;
    ost << ", " << PAP;
    ost << ", " << MAP;
    ost << ", " << money;
    ost << ", " << sex;
    ost << ", " << cjob;
    ost << ", " << lvl;
    ost << ", " << exp;
    ost << ", " << HP[0];
    ost << ", " << HP[1];
    ost << ", " << MP[0];
    ost << ", " << MP[1];
    ost << ", " << PA;
    ost << ", " << MA;
    ost << ", " << Sp;
    ost << ", " << Mv;
    ost << ", " << Ju;
    ost << ", " << Ev;
    ost << ", " << Br;
    ost << ", " << Fa;
    ost << ", " << area->id << ", 0, 0, 0, 0)" << ends;
  }

  mysql_query(&mysql, ost.str());
    
  if(admin) {
    sprintf(query, "UPDATE admins SET wiz='%s' WHERE name='%s'", print_flags(wiznet).c_str(), name.c_str());
    mysql_query(&mysql, query);
  }
  
  sprintf(query, "DELETE FROM job_data WHERE player='%s'", name.c_str());
  mysql_query(&mysql, query);
    
  for(x = 1; x <= MAX_JOBS; ++x) {
    if(job[x].lvl) {
      sprintf(query, "INSERT INTO job_data(player, job, lvl, JP, exp, subjob, reaction, support, move) values('%s', %d, %d, %d, %d, %d, %d, %d, %d)", name.c_str(), x, job[x].lvl, job[x].JP, job[x].exp, job[x].subjob, job[x].reaction, job[x].support, job[x].move);
      mysql_query(&mysql, query);
    }
  }
    
  sprintf(query, "DELETE FROM skills WHERE player='%s'", name.c_str());
  mysql_query(&mysql, query);
  
  for(x = 1; x <= MAX_SKILLS; ++x) {
    if(skill[x]) {
      sprintf(query, "INSERT INTO skills(player, id) values('%s', %d)", name.c_str(), x);
      mysql_query(&mysql, query);
    }
  }

  mysql_close(&mysql);
  return;
}
 
string CH::idle() {
  int mins = timer % 60, hrs = timer / 60;
  ostrstream ost;
  ost << setfill('0') << setw(2) << hrs << ':' << setfill('0') << setw(2) << mins << 'm' << ends;
  return ost.str();
}

void do_quit(CH *ch, string argument = "") {
  if(ch->battle) {
    ch->printf("quit: ERROR: Can't quit in battle.\n\r");
    return;
  }

  ch->mins_ave = (ch->mins_ave ? (ch->mins_ave + ch->mins_login) / 2 : ch->mins_login);
  ch->mins_login = 0;
  ch->printf("logout\n\r");
  wiz_echo(ch->name + " has quit.\n\r");
  log_string(ch->name + " has quit.");
  ch->room->echo(ch->name + " has left the game.\n\r", ch);
  DESC *d = ch->desc, *d_next;
  string name = ch->name;
  zap(ch);
  
  if(d)
    d->close_socket();
  
  for(d = desc_list; d; d = d_next) {
    CH *tch = d->ch;
    d_next = d->next;

    if(tch && tch->name == name) {
      zap(tch);
      d->close_socket();
    }
  }

  return;
}

void do_delete(CH *ch, string argument = "") {
  if(ch->confirm_delete) {
    if(!argument.empty() && argument != ch->pwd) {
      ch->printf("Delete cancelled.\n\r");
      ch->confirm_delete = false;
      return;
    }
    else if(argument == ch->pwd) {
      MYSQL mysql;
      mysql_init(&mysql);
      
      if(!mysql_real_connect(&mysql, DB_LOC, DB_USER, DB_PASS, DB_NAME, 0, NULL, 0))
	return;

      ostrstream ost;
      ost << "DELETE FROM players WHERE name='" + ch->name + "'" << ends;
      mysql_query(&mysql, ost.str());
      ost.freeze(false);
      ost.seekp(0, ios::beg);
      ost << "DELETE FROM job_data WHERE player='" + ch->name + "'" << ends;
      mysql_query(&mysql, ost.str());
      ost.freeze(false);
      ost.seekp(0, ios::beg);
      ost << "DELETE FROM skills WHERE player='" + ch->name + "'" << ends;
      mysql_query(&mysql, ost.str());
      ost.freeze(false);
      ost.seekp(0, ios::beg);
      ost << "DELETE FROM objects WHERE owner='" + ch->name + "'" << ends;
      mysql_query(&mysql, ost.str());
      mysql_close(&mysql);
      ost << "DELETE FROM ignore_data WHERE player='" + ch->name + "'" << ends;
      mysql_query(&mysql, ost.str());
      mysql_close(&mysql);
      wiz_echo(ch->name + " has deleted.\n\r");
      DESC *d = ch->desc;
      zap(ch);
      
      if(d)
	d->close_socket();
      
      mysql_close(&mysql);
      return;
    }
    else {
      ch->printf("Usage: delete [[password] | cancel]\n\r");
      return;
    }
  }
  
  if(argument.empty()) {
    ch->printf("Usage: delete [[password] | cancel]\n\r");
    return;
  }
  else if(argument != ch->pwd) {
    ch->printf("delete: ERROR: [password] incorrect.\n\r");
    return;
  }
  
  ch->printf("Type delete [password] again to confirm.\n\r{!WARNING: this command is irreversible.{0\n\rType delete cancel to abort.\n\r");
  ch->confirm_delete = true;
  wiz_echo(ch->name + " is contemplating deletion...\n\r");  
  return;
}

void do_setpwd(CH *ch, string argument="") {
  string arg;
  split(argument, arg);

  if(arg.empty() || argument.empty()) {
    ch->printf("Usage: setpwd [old] [new]\n\r");
    return;
  }

  if(arg != ch->pwd) {
    ch->printf("ERROR: [old] incorrect...waiting 10 seconds.\n\r");
    WAIT_STATE(ch, 10);
    return;
  }

  if(argument.size() < 5 || argument.size() > 12) {
    ch->printf("ERROR: Must be between 5 and 12 characters long.\n\r");
    return;
  }

  if(argument.find('/') != string::npos || argument.find('\\') != string::npos || argument.find("'") != string::npos) {
    ch->printf("ERROR: Contains illegal characters, try again.\n\r");
    return;
  }
  
  ch->pwd = argument;
  ch->save();
  ch->printf("Password set.\n\r");
  return;
}

void do_setemail(CH *ch, string argument="") {
  if(argument.empty()) {
    ch->printf("Usage: setemail [address]\n\r");
    return;
  }
 
  ch->email = argument;
  ch->save();
  ch->printf("E-Mail address set to " + ch->email + ".\n\r");
  return;
}

void do_money(CH *ch, string argument = "") {
  ostrstream ost;
  ost << "Money: {2${0" << ch->money << ".\n\r" << ends;
  ch->printf(ost.str());
  return;
}

bool CH::has_skill(const string skill_name, const bool jobonly=true) {
  for(short x = 1; skill_table[x].name; ++x)
    if(find(skill_name, skill_table[x].name) && skill[x] && ((jobonly && (skill_table[x].job == cjob || job[cjob].reaction == x || job[cjob].support == x || job[cjob].move == x)) || (!jobonly)))
      return true;
  
  return false;
}     

void CH::gain_exp() {
  CH *victim = (action.target ? action.target->people : this);
  
  if(!victim)
    victim = this;

  short gain = MIN((10 + (victim->lvl - lvl) + (victim->HP[0] < 0 ? 10 : 0)) * (has_skill("Gained Exp-UP", true) ? 2 : 1), 1);
  exp += gain;
  ostrstream ost;
  ost << "{#[" << name << "] " << gain << " Exp.{0\n\r" << ends;
  battle->echo(ost.str());

  if(exp >= 100 && lvl < 99) {
    level_up();
    exp = 0;
  }
    
  return;
}

void CH::gain_jp() {
  short jp, gjp;
    
  if(npc)  
    return;
  
  jp = (short)((gjp = (8 + (job[cjob].lvl * 2) + job[cjob].lvl / 4)) * (has_skill("Gained Jp UP", true) ? 1.5 : 1));
  job[cjob].JP += jp;
  
  if(job[cjob].exp < 9999)
    job[cjob].exp = MAX(job[cjob].exp + jp, 9999);
  
  ostrstream ost;
  ost << "{#[" << name << "] " << jp << " Jp{0\n\r" << ends;
  battle->echo(ost.str());
  
  if(job[cjob].lvl < 8 && job[cjob].exp >= 250) {
    battle->echo("[" + name + "] Job level up!\n\r");
    ++job[cjob].lvl;
    job[cjob].exp = 0;
  }
    
  if(group)
    for(CH *pers = group->people; pers; pers = pers->next_in_group)
      pers->job[cjob].JP += gjp / 4;
      
  return;
}

void CH::restore() {
  HP[0] = HP[1];
  MP[0] = MP[1];
  return;
}

void CH::level_up(const bool quiet=false) {
  short x, locs[5], hp = HP[0], mp = MP[0];
  OBJ *obj, *objects[5]; 

  for(x = 0; x < 5; ++x)
    objects[x] = NULL;
  
  x = 0;

  for(obj = this->objects; obj; obj = obj->next) {
    if(obj->equipped) {
      objects[x] = obj;
      locs[x] = obj->loc;
      remove(obj);
      ++x;
    }
  }
  
  if(!quiet)
    battle->echo("[" + name + "] level up!\n\r");
  
  HP[1] += HP[1] / (job_table[cjob].HPP + lvl);
  MP[1] += MP[1] / (job_table[cjob].MPP + lvl);
  PA += PA / (job_table[cjob].PAP + lvl);
  MA += MA / (job_table[cjob].MAP + lvl);
  Sp += Sp / (job_table[cjob].SpP + lvl);
  ++lvl;
   
  for(x = 0; objects[x]; ++x)
    equip(objects[x], locs[x]);
    
  HP[0] = MAX(hp, HP[1]);
  MP[0] = MAX(mp, MP[1]);
  return;
}  
      
const struct statpoint_type statpoint_table[] = {
  { 98304,      81920,  81920,  65536,  65536,  491520, 524288, 229376, 245760  },
  { 98304,      65536,  65536,  81920,  81920,  458752, 491520, 245760, 262144  },
  { 81920,      81920,  98304,  81920,  98304,  589824, 622592, 131072, 147456  },
  { 0,  0,      0,      0,      0,      0,      0,      0,      0       }
};

void do_abilities(CH *ch, string argument="") {
  char buf_ctr[3], buf_range[2];
  ch->printf("%-14s  %-8s  %5s  %2s  %3s\n\r", "ABILITY", "TYPE", "RANGE", "CT", "JP");
    
  for(short x = 1; skill_table[x].name; ++x) {
    if(ch->skill[x] && skill_table[x].job == ch->cjob) {
      sprintf(buf_ctr, "%d", skill_table[x].ctr);
      sprintf(buf_range, "%d", skill_table[x].range);
      ch->printf("%-14s  %-8s  %5s  %2s  %3d\n\r", skill_table[x].name, get_skill_type(skill_table[x].type).c_str(), skill_table[x].range ? buf_range : "--", skill_table[x].ctr > 0 ? buf_ctr : "--", skill_table[x].JP);
    }
  }
 
  return;
}
  
void CH::init_stats() {
  HPP = (float)num_range(statpoint_table[sex].HPPlow, statpoint_table[sex].HPPhigh) / 65536;
  MPP = (float)num_range(statpoint_table[sex].MPPlow, statpoint_table[sex].MPPhigh) / 65536;
  SpP = (float)statpoint_table[sex].SpP / 65536;
  PAP = (float)statpoint_table[sex].PAPhigh / 65536;
  MAP = (float)statpoint_table[sex].MAPhigh / 65536;
  Br = num_range(40, 80);
  Fa = num_range(40, 80);
  return;
}

string CH::his_her() {
  if(sex)
    return "her";
  else
    return "his";
}

string CH::he_she() {
  if(sex)
    return "she";
  else
    return "he";
}

CH *CH::get_ch_room(const string name) {
  for(CH *ch = room->people; ch; ch = ch->next_in_room)
    if(ch != this && find(name, ch->name))
      return ch;
 
  return NULL;
}

void CH::interpret(string argument) {
  string logline, command;
  int x;
  bool found = false;

  lcom = name + ": " + argument + " @ " + str_time().substr(11, 8);
  argument.erase(0, argument.find_first_not_of(" ")); 

  if(argument.empty())
    return;

  if(IS_SET(act, PLR_FROZEN)) {
    printf(argument + ": ERROR: You can't do anything, you're frozen!\n\r");
    return;
  }

  logline = argument;

  if(desc && desc->snoop_by)
    desc->snoop_by->printf("% " + logline + ENDL);

  if(battle && battle->turn && battle->turn == this && temp_action.action == do_act && !action.action) {
    for(x = 1; skill_table[x].name; ++x) {
      if(find(argument, skill_table[x].name) && ((!skill_table[x].JP && !skill_table[x].job) || (skill[x] && (skill_table[x].job == cjob || skill_table[x].job == job[cjob].subjob))) && (skill_table[x].type == SK_ACTION || skill_table[x].type == SK_SUPPORT) && skill_table[x].fun) {
        action.action = skill_table[x].fun;
        action.ctr = skill_table[x].ctr;
        action.id = x;

        if(skill_table[x].range) {
          printf("Action selected.\n\r");
          do_move(this);
        }
        else
          do_ok(this);

        return;
      }
    }
    
    printf(argument + ": ERROR: Invalid action.\n\r");
    return;
  } 

  split(argument, command);

  if(!commands.empty() && !find(command, commands)) {
    printf(command + ": ERROR: Acceptable commands are '" + commands + "'\n\r");
    return;
  }

  for(x = 0; cmd_table[x].name; ++x) {
    if(find(command, cmd_table[x].name)) {
      if(!cmd_table[x].admin || (cmd_table[x].admin && admin))
        found = true;
 
      break;
    }
  }

  if(IS_SET(act, PLR_LOG)) {
    wiz_echo("wiznet> log> " + name + ": " + logline + "\n\r", WIZ_SECURE);
    log_string("log: " + name + ": " + logline);
  }

  if(!found) {
    for(x = 1; social_table[x].social; ++x) {
      if(find(command, social_table[x].social)) {
	ostrstream ost;
	ost << "{6" << social_table[x].to_ch << "{0\n\r" << ends;
        printf(ost.str());
	ost.freeze(false);
	ost.seekp(0, ios::beg);
	ost << "{6" << name << " " << social_table[x].to_room << "{0\n\r" << ends;
	room->echo(ost.str(), this);
        return;
      }
    }

    printf(command + ": ERROR: Command not found. Type 'c' for a list of commands.\n\r");
    return;
  }
  else {
    if(battle) {
      if((cmd_table[x].loc == 2 || cmd_table[x].loc == 12) && (!battle->turn || battle->turn != this)) {
        printf((string)cmd_table[x].name + ": ERROR: Not your turn.\n\r");
        return;
      }
      else if(cmd_table[x].loc == 1) {
        printf((string)cmd_table[x].name + ": ERROR: Can't be used in battle.\n\r");
        return;
      }
    }
    else {
      if(cmd_table[x].loc == 2) {
        printf((string)cmd_table[x].name + ": ERROR: Can only be used in battle.\n\r");
        return;
      }
    }
  }  

  (*cmd_table[x].doFun)(this, argument);
  return;
}

void CH::prompt() {
/*
  if(!queue[0].empty()) {
    printf("...");
    return;
  }
*/
  ostrstream ost;
  ost << "[" << name << "@Hp {^" << str_comma(HP[0]) << "{0.Mp {#" << str_comma(MP[0]) << "{0.CT {@" << CT << "{0]" << ends;
  printf(ost.str());
  printf("$ ");
  return;
}

void CH::print_MOTD() {
  printf(f_str_box("Typing 'commands' will show you a list of all the commands in the game. 'help' followed by a command will tell you what the command is for and how it is used.  Take the time to learn them if you haven't already."));
  printf(f_str_box("{!DO NOT FORGET OR SHARE YOUR PASSWORD WITH ANYONE, PASSWORD PROBLEMS ARE YOUR RESPONSIBILITY!{0", false));
  printf(ENDL);
  return;
}

bool CH::can_see(const CH *ch) {
  if(!battle)
    return true;
  if(battle != ch->battle)
    return false;

  return true;
}

short CH::dir(const CH *ch) {
  bool north = false, south = false, east = false, west = false;
 
  if(ch->battle_room->x > battle_room->x)
    east = true;
  else if(ch->battle_room->x < battle_room->x)
    west = true;
 
  if(ch->battle_room->y > battle_room->y)
    south = true;
  else if(ch->battle_room->y < battle_room->y)
    north = true;
 
  return (north && east ? DIR_NE : north && west ? DIR_NW : south && east ? DIR_SE : south && west ? DIR_SW : north ? DIR_N : south ? DIR_S : east ? DIR_E : DIR_W);
}
 
short CH::dir(const BATTLE_ROOM *room) {
  bool north = false, south = false, east = false, west = false;
 
  if(room->x > this->battle_room->x)
    east = true;
  else if(room->x < this->battle_room->x)
    west = true;

  if(room->y > this->battle_room->y)
    south = true;
  else if(room->y < this->battle_room->y)
    north = true;
 
  return (north && east ? DIR_NE : north && west ? DIR_NW : south && east ? DIR_SE : south && west ? DIR_SW : north ? DIR_N : south ? DIR_S : east ? DIR_E : DIR_W);
}

short CH::distance(const CH *ch) {
  return (abs)(ch->battle_room->x - battle_room->x) + (abs)(ch->battle_room->y - battle_room->y);
}

void CH::print_status(CH *ch) {
  if(status_list) {
    ch->printf("{3+--{0Status{3---------------------------------------------+\n\r");
    ch->printf("+-----------------------------------------------------+{0\n\r");
  
    for(STATUS *status = status_list; status; status = status->next)
      ch->printf("   " + status->name() + "\n\r");
  }
  
  return;
}

void CH::stat(CH *ch) {
  char buf[MIL];
  ch->printf("                              {3+-----------------------+{0\n\r");
  ch->printf("           Lv.%02d Exp.%04d     {3| {^*{0 %-19s {3|{0\n\r", lvl, exp, name.c_str());
  ch->printf("       Hp %s %03d/%03d  {3|{0   %-19s {3|{0\n\r", str_bar(HP[0], HP[1], "{^").c_str(), HP[0], HP[1], job_table[cjob].name);
  ch->printf("       Mp %s %03d/%03d  {3|{0   Brave %d  Faith %d  {3|{0\n\r", str_bar(MP[0], MP[1], "{#").c_str(), MP[0], MP[1], Br, Fa);
  sprintf(buf, "%03d", CT);
  ch->printf("       CT %s %s/%s  {3+-----------------------+\n\r", str_bar(CT, 100, "{@").c_str(), battle ? buf : "---", battle ? "100" : "---");
  ch->printf("+-----------------------------------------------------+\n\r");
  ostrstream ost;
  ost << "|{0  Move..." << get_move_range() << "    Weap.Power       AT   C-EV  S-EV  A-EV {3|\n\r" << ends;
  ch->printf(ost.str());
  ch->printf("|{0  Jump...%d   R...%03d / %02d%%  Ph %02d / %02d%% / %02d%% / %02d%%  {3|\n\r", Ju, eq[LOC_RHAND] ? obj_table[eq[LOC_RHAND]->id].ATp : 0, eq[LOC_RHAND] ? obj_table[eq[LOC_RHAND]->id].pEv : 0, PA, Ev, eq[LOC_RHAND] && obj_table[eq[LOC_RHAND]->id].type == TYPE_SHIELD ? obj_table[eq[LOC_RHAND]->id].pEv : eq[LOC_LHAND] && obj_table[eq[LOC_LHAND]->id].type == TYPE_SHIELD ? obj_table[eq[LOC_LHAND]->id].pEv : 0, eq[LOC_ACC] ? obj_table[eq[LOC_ACC]->id].pEv : 0);
  ch->printf("|{0 Speed...%02d  L...%03d / %02d%%  Ma %02d / 00%% / %02d%% / %02d%%  {3|\n\r", Sp, eq[LOC_LHAND] ? obj_table[eq[LOC_LHAND]->id].ATp : 0, eq[LOC_LHAND] ? obj_table[eq[LOC_LHAND]->id].pEv : 0, MA, eq[LOC_RHAND] && obj_table[eq[LOC_RHAND]->id].type == TYPE_SHIELD ? obj_table[eq[LOC_RHAND]->id].mEv : eq[LOC_LHAND] && obj_table[eq[LOC_LHAND]->id].type == TYPE_SHIELD ? obj_table[eq[LOC_LHAND]->id].mEv : 0, eq[LOC_ACC] ? obj_table[eq[LOC_ACC]->id].mEv : 0);
  ch->printf("+-----------------------------------------------------+\n\r");
  ch->printf("+----{0Eqp.{3----------------------{0Ability{3----------------+\n\r");
  ch->printf("|{0 RH {3|{0%c{3|{0 %-22s{3| {#Act1{3|{0 %-15s{3|\n\r", eq[LOC_RHAND] ? '*' : ' ', eq[LOC_RHAND] ? eq[LOC_RHAND]->name().c_str() : " ", job_table[cjob].act_name);
  ch->printf("|{0 LH {3|{0%c{3|{0 %-22s{3| {#Act2{3|{0 %-15s{3|\n\r", eq[LOC_LHAND] ? '*' : ' ', eq[LOC_LHAND] ? eq[LOC_LHAND]->name().c_str() : " ", (job[cjob].subjob ? job_table[job[cjob].subjob].act_name : " "));
  ch->printf("|{0 He {3|{0%c{3|{0 %-22s{3| {#Rea.{3|{0 %-15s{3|\n\r", eq[LOC_HEAD] ? '*' : ' ', eq[LOC_HEAD] ? eq[LOC_HEAD]->name().c_str() : " ", job[cjob].reaction ? skill_table[job[cjob].reaction].name : " ");
  ch->printf("|{0 Bo {3|{0%c{3|{0 %-22s{3| {#Sup.{3|{0 %-15s{3|\n\r", eq[LOC_BODY] ? '*' : ' ', eq[LOC_BODY] ? eq[LOC_BODY]->name().c_str() : " ", job[cjob].support ? skill_table[job[cjob].support].name : " ");
  ch->printf("|{0 Ac {3|{0%c{3|{0 %-22s{3| {#Move{3|{0 %-15s{3|\n\r", eq[LOC_ACC] ? '*' : ' ', eq[LOC_ACC] ? eq[LOC_ACC]->name().c_str() : " ", job[cjob].move ? skill_table[job[cjob].move].name : " ");
  ch->printf("+-----------------------------------------------------+{0\n\r");
  print_status(ch);
  return;
}

void do_set(CH *ch, string argument="") {
  string arg;
  split(argument, arg);
  
  if(arg.empty() || !find(arg, "job subaction reaction support move")) {
    ch->printf("Usage: set [job | subaction | reaction | support | move]\n\r");
    return;
  }
  
  if(find(arg, "job"))
    do_setjob(ch, argument);
  else if(find(arg, "subaction"))
    do_setsubaction(ch, argument);
  else if(find(arg, "reaction"))
    do_setreaction(ch, argument);
  else if(find(arg, "support"))
    do_setsupport(ch, argument);
  else
    do_setmove(ch, argument);
  
  return;
}
  
short get_subaction_id(string argument) {
  for(short x = 1; !job_table[x].skill[0]; ++x)
    if(find(argument, job_table[x].act_name))
      return x;

  return 0;
}

void do_setsubaction(CH *ch, string argument="") {
  short id;
      
  if(argument.empty() || !ch->can_be_job((id = get_subaction_id(argument)))) {
    ch->printf("Usage: set subaction [ability]\n\r");
    ch->printf("{3+--{0Ability{3------------------------+\n\r");
    ch->printf("{3+---------------------------------+{0\n\r");
  
    for(short x = 1; !job_table[x].skill[0]; ++x)
      if(x != ch->cjob && ch->can_be_job(x))
        ch->printf("   " + (string)job_table[x].act_name + "\n\r");
 
    return;
  }
  
  ch->printf("Secondary action ability set to " + (string)job_table[id].act_name + ".\n\r");
  ch->job[ch->cjob].subjob = id;
  return;
}

void do_setreaction(CH *ch, string argument="") {
  short id;
      
  if(argument.empty() || !ch->has_skill(argument, false) || skill_table[(id = ch->get_skill_id(argument, true))].type != SK_REACTION) {
    ch->printf("Usage: set reaction [ability]\n\r");
    ch->printf("{3+--{0Ability{3------------------------+\n\r");
    ch->printf("{3+---------------------------------+{0\n\r");
    
    for(short x = 1; skill_table[x].name; ++x)
      if(skill_table[x].type == SK_REACTION && ch->skill[x])
        ch->printf("   %s\n\r", skill_table[x].name);
 
    return;
  }
  
  ch->printf("Reaction ability set to %s.\n\r", skill_table[id].name);
  ch->job[ch->cjob].reaction = id;
  return;
}

void do_setsupport(CH *ch, string argument="") {
  short id;
      
  if(argument.empty() || !ch->has_skill(argument, false) || skill_table[(id = ch->get_skill_id(argument, true))].type != SK_SUPPORT) {
    ch->printf("Usage: set support [ability]\n\r");
    ch->printf("{3+--{0Ability{3------------------------+\n\r");
    ch->printf("{3+---------------------------------+{0\n\r");
    
    for(short x = 1; skill_table[x].name; ++x)
      if(skill_table[x].type == SK_SUPPORT && ch->skill[x])
        ch->printf("   %s\n\r", skill_table[x].name);
 
    return;
  }     
 
  ch->printf("Support ability set to %s.\n\r", skill_table[id].name);
  ch->job[ch->cjob].support = id;
  return;
} 

void do_setmove(CH *ch, string argument="") {
  short id;
    
  if(argument.empty() || !ch->has_skill(argument, false) || skill_table[(id = ch->get_skill_id(argument, true))].type != SK_MOVE) {
    ch->printf("Usage: set move [ability]\n\r");
    ch->printf("{3+--{0Ability{3------------------------+\n\r");
    ch->printf("{3+---------------------------------+{0\n\r");
    
    for(short x = 1; skill_table[x].name; ++x)
      if(skill_table[x].type == SK_MOVE && ch->skill[x])
        ch->printf("   %s\n\r", skill_table[x].name);
    
    return;
  }     
 
  ch->printf("Move ability set to %s.\n\r", skill_table[id].name);
  ch->job[ch->cjob].move = id;
  return;
} 

string CH::male_female() {
  if(sex == 1)
    return "female";
  else
    return "male";
}

void CH::look(CH *ch) {
  if(this == ch) {
    printf("You look at yourself.\n\r");
    room->echo(name + " looks at " + his_her() + "self.\n\r", this);
  }
  else {
    ch->printf(name + " looks at you.\n\r", true);
    room->echo(name + " looks at " + ch->name + ".\n\r", this, ch);
  }

  ch->stat(this);
  return;
}

bool CH::can_see(const OBJ *obj) {
  return true;
}

CH *CH::get_nearest_target(const bool ally) {
  CH *pers, *target = NULL;

  for(pers = battle->people; pers; pers = pers->next_in_battle) {
    if(pers == this || pers->status(status_dead) || (!ally && npc && pers->npc) || (ally && npc && !pers->npc) || (!ally && group && pers->group && group == pers->group))
      continue;
    
    if(!target || pers->distance(this) < target->distance(this))
      target = pers;
  }
  
  return target;
}

void CH::load_battle() {
  return;
}

void CH::update_queue() {
  if(queue[0].find("phase", 0) != string::npos) {
    string str = queue[0], str2;
    split(str, str2);
    room->area->battle()(this, atoi(str.c_str()));
  }
  else
    printf(queue[0]);

  for(short x = 0; !queue[x].empty(); ++x)
    queue[x] = queue[x + 1];

  return;
}