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

#include "main.h"
#include <strstream>
#include <cstdio>

BATTLE::BATTLE() {
  area = NULL;
  next = battle_list;
  battle_list = this;
  return;
}

BATTLE::~BATTLE() {
  short prize = 0;

  if(!winner)
    return;

  for(CH *ch = people, *ch_next; ch; ch = ch_next) {
    ch_next = ch->next_in_battle;

    if(ch->npc) {
      ch->leave_battle();
      continue;
    }

    if(winner != ch && (!ch->group || !winner->group || ch->group != winner->group)) {
      ch->printf("You Lose! The battle is over.\n\r");
      ch->die();
      continue;
    }

    if(!prize)
      prize = (gil / ch->get_group_cnt());

    ch->printf("Congratulations! The battle is complete.\n\r");
    ch->money += prize;
    ch->printf("You receive %d Gil!\n\r", prize);
    ch->leave_battle();

    if(ch->group && ch->group->leader != ch)
      ch->to(ch->group->leader->room);
  }

  if(this == battle_list)
    battle_list = next;
  else {
    for(BATTLE *prev = battle_list; prev; prev = prev->next) {
      if(prev->next == this) {
        prev->next = next;
        break;
      }
    }
  }
  
  return;
}

void CH::leave_battle() {
  if(!battle)
    return;     
  
  if(battle->turn == this)
    battle->turn = false;

  if(this == battle->people)
    battle->people = next_in_battle;
  else {
    for(CH *prev = battle->people; prev; prev = prev->next_in_battle) {
      if(prev->next_in_battle == this) {
        prev->next_in_battle = next_in_battle;
        break;
      }
    }
  }
  
  if(npc) {
    CH *ch_this = this;
    zap(ch_this);
    return;
  }

  battle = NULL;
  action.action = NULL;
  action.target = NULL;
  action.ctr = 0;
  moved = acted = false;
  temp_action.action = NULL;  
  CT = 0;
  commands = "";
  clear_status(false);
  restore();
  return;
}

bool twoswords_attack(CH *ch, CH *victim) {
  bool found = false;
 
  if(ch->physical_attack(victim, ch->calculate_dam(0, LOC_RHAND)))
    found = true;
  if(ch->physical_attack(victim, ch->calculate_dam(0, LOC_LHAND)))
    found = true;
  
  return found;
} 

short CH::get_weap_loc() {
  return (eq[LOC_RHAND] && obj_table[eq[LOC_RHAND]->id].obj_type == OBJ_WEAPON ? LOC_RHAND : LOC_LHAND);
}

bool do_attack(CH *ch, const bool check) {
  CH *victim;
 
  if(!(victim = ch->action.target->people))
    return false;
  
  if(ch->has_skill("Two Swords"))
    return twoswords_attack(ch, victim);

  return ch->physical_attack(victim, ch->calculate_dam(0, ch->get_weap_loc()));
}
    
bool CH::check_evade() {
  if(status(status_charging))
    return false;
  
  if(num_percent() > get_hitpercent())
    return true;
   
  return false;
}

bool CH::physical_attack(CH *victim, short dam) {
  bool crit = false;
    
  if(!victim)
    return false;
    
  if(victim->check_reaction(this, TRIG_ATTACK))
    return false;
    
  if(victim->check_evade()) {
    battle->echo(victim->name + " evades " + name + "'s attack!\n\r");
    return false;
  }

  if(num_percent() <= 5) {
    crit = true;
    dam += num_range(1, dam);
  }
  
  if(victim->status(status_charging))
    dam = (short)(dam * 1.5);
    
  damage(victim, dam);
  
  if(!victim || !victim->battle)
    return true;
  
  if(crit && num_percent() < 50)
    if(victim->move(rev_dir[dir(victim)]))
      battle->echo(victim->name + " is pushed back!\n\r");
  
  return true;
} 

short CH::calculate_dam(const short K, const short loc) {
  short dam = 0;
  OBJ *weapon = eq[loc];

  if(!weapon)
    dam = (short)((float)(PA + K) * ((float)Br / 100) * (float)PA);
  else {
    switch(obj_table[weapon->id].type) {
    case TYPE_KNIGHT_SWORD:
      dam = Br / 100 * (PA + K) * obj_table[weapon->id].ATp;
      break;
    case TYPE_KNIFE:
    case TYPE_NINJA_SWORD:
    case TYPE_BOW:
      dam = (PA + K + Sp + K) / 2 * obj_table[weapon->id].ATp;
      break;
    case TYPE_KATANA:
      dam = Br / 100 * (PA + K) * obj_table[weapon->id].ATp;
      break; 
    case TYPE_STAFF:
    case TYPE_STICK:
      dam = (MA  + K) * obj_table[weapon->id].ATp;
      break;
    case TYPE_SWORD:
    case TYPE_ROD:
    case TYPE_CROSSBOW:
    case TYPE_SPEAR:
      dam = (PA + K) * obj_table[weapon->id].ATp;
      break;
    case TYPE_GUN:
      dam = (obj_table[weapon->id].ATp + K) * obj_table[weapon->id].ATp;
      break;
    case TYPE_FLAIL:
    case TYPE_AXE:
    case TYPE_BAG:
      dam = ((PA + K) / 2 + 1) * obj_table[weapon->id].ATp;
      break;
    case TYPE_INSTRUMENT:
    case TYPE_DICTIONARY:
    case TYPE_CLOTH:
      dam = (PA + K + MA + K) / 2 * obj_table[weapon->id].ATp;
      break;
    }
  }
      
  return dam;
}

bool CH::check_reaction(CH *victim, const short trigger) {
  BATTLE_ROOM *room;
  DO_ACT *action;
  short id;

  if(!job[cjob].reaction || !skill_table[job[cjob].reaction].trigger || skill_table[job[cjob].reaction].trigger != trigger || num_percent() < 50)
    return false;

//  switch(trigger) {
//  case TRIG_HPDAMAGE:
    room = this->action.target;
    action = this->action.action;
    id = this->action.id;
    this->action.target = victim->battle_room;
    this->action.id = job[cjob].reaction;
    this->action.action = skill_table[this->action.id].fun;
//    resolve_action(ch);
      
    this->action.action(this);
      
    if(battle) {
      this->action.target = room;
      this->action.action = action;
      this->action.id = id;
    }
    
    return true;
//  case TRIG_ATTACK:
//    room =
//  }
}

void CH::damage(CH *victim, const short dam) {
  ostrstream ost;
  ost << "{!" << name << " hits " << victim->name << "! " << dam << "{0\n\r" << ends;
  battle->echo(ost.str());
  victim->HP[0] -= dam;
      
  if(victim->HP[0] < 1) {
    if(victim->npc) {
      victim->battle->gil += 100;
      victim->die(this);
    }
    else {
      victim->die(this);
    
      if(!victim->get_group_cnt()) {
        victim->battle->winner = this;
        zap(victim->battle);
      }
    }
      
    return;
  } 

  victim->check_reaction(this, TRIG_HPDAMAGE);
  return;
}

void CH::die(CH *killer=NULL) {
  if(npc)
    battle->echo(name + " has been killed!\n\r");
  else {
    if(!status(status_dead)) {
      battle->echo(name + " has been killed!\n\r");
      add(status_dead, 0, 0, false);
      return;
    } 
  
    if(get_group_cnt())
      battle->echo(name + "'s spirit turned into crystal...\n\r");

    clear_status();
  
    if(!admin) {
      exp -= 50;
      job[cjob].JP -= 50;
      job[cjob].exp -= 50;
    }

    to(area->room[ROOM_OUTSIDE]);
     
    if(group)
      do_leave(this);
  }

  leave_battle();
  
  if(killer && !killer->get_nearest_target(false)) {
    killer->battle->winner = killer;
    zap(killer->battle);
  }
    
  return;
}

short CH::get_hitpercent() {
  float hitpercent = 100;
  hitpercent *= 1 - (float)Ev / 100;
  
  for(short x = 2; x < 5; ++x) {
    if(eq[x]) {
//      if(x <= 1 && !has_skill(ch, "Weapon Guard", TRUE))
//      continue;
      
      hitpercent *= 1 - ((float)obj_table[eq[x]->id].pEv / 100);
    }
  }
  
  if(status(status_defending))
    hitpercent /= 2;
    
  return (int)hitpercent;
}

bool CH::check_ok(const bool act=false) {
  if((orig_room && battle_room->distance(orig_room) > temp_action.range) || battle_room->occupied(this))
    return false;
  if(act && !skill_table[action.id].self && battle_room == orig_room)
    return false;

  return true;
}

void do_ok(CH *ch, string argument="") {
  if(ch->temp_action.action == do_move) {
    if(ch->check_ok()) {
      ch->printf("ok: ERROR: Select within moveable range.\n\r");
      return;
    }
        
    ch->temp_action.action = NULL;
    ch->orig_room = NULL;
    do_look(ch);
    ch->moved = true;
    ch->battle->echo("{B" + ch->name + " moved.{0\n\r");
    ch->cancel(status_charging, false);
      
    if(ch && !ch->acted) {
      ch->printf("{^ACT or WAIT?{0\n\r");
      ch->commands = "act wait";   
    }
  }
  else if(ch->temp_action.action == do_act) {
    if(ch->check_ok(true)) {
      ch->printf("ok: ERROR: Select within firing range.\n\r");
      return;
    }
    
    ch->temp_action.action = NULL;
    
    if(skill_table[ch->action.id].range) {
      if(!ch->npc) {
        ch->action.target = ch->battle_room;
        ch->to(ch->orig_room);
        ch->orig_room = NULL;
        do_look(ch);
      }
      
      ch->battle->echo(ch->name + " selected an action and target.\n\r");
    }
    
    ch->acted = true;
    
    if(skill_table[ch->action.id].job == JOB_ARCHER && skill_table[ch->action.id].type == SK_ACTION)
      ch->add(status_charging, 0, 0, 0);
    else if(ch->action.action == do_defend)
      ch->add(status_defending, 0, 0, 0);
  
    if(!ch->action.ctr)
      ch->resolve_action();
   
    if(!ch)
      return;
    
    if(!ch->moved && ch->battle) {
      ch->printf("{^MOVE or WAIT?{0\n\r");
      ch->commands = "move wait";
    }   
  }

  if(ch->moved && ch->acted)
    ch->end_turn();
    
  return;
}     

void do_wait(CH *ch, string argument="") {
  ch->battle->echo(ch->name + " waits.\n\r");
  ch->end_turn();
  return;
}

void do_cancel(CH *ch, string argument="") {
  ch->printf((string)(ch->temp_action.action == do_move ? "MOVE" : "ACT") + " cancelled.\n\r");
    
  if(ch->temp_action.action == do_move) {
    if(!ch->acted) {
      ch->printf("{^MOVE, ACT or WAIT?{0\n\r");
      ch->commands = "move act wait";
    }
    else {
      ch->printf("{^MOVE or WAIT?{0\n\r");
      ch->commands = "act wait";
    }
  } 
  else {
    ch->action.action = NULL;
      
    if(!ch->moved) {
      ch->printf("{^MOVE, ACT or WAIT?{0\n\r");
      ch->commands = "move act wait";
    }
    else {
      ch->printf("{^ACT or WAIT?{0\n\r");
      ch->commands = "act wait";
    }
  }
    
  ch->temp_action.action = NULL;
    
  if(ch->orig_room) {
    ch->to(ch->orig_room);
    ch->orig_room = NULL;
  }
      
  return;
}

short get_skill_id_by_function(DO_ACT *fun) {
  for(short x = 1; skill_table[x].name; ++x) 
    if(skill_table[x].fun == fun)
      return x;
 
  return 0;
}
  
void CH::npc_turn() {
  short id;

  for(short x = 0; job_table[cjob].skill[x]; ++x) {
    id = get_skill_id_by_function(job_table[cjob].skill[x]);

    temp_action.range = skill_table[id].range;

    if(job_table[cjob].skill[x](this, true)) {
      do_act(this);
      action.action = job_table[cjob].skill[x];
      action.id = id;
      action.ctr = skill_table[id].ctr;
      
      if(!skill_table[id].range) {
        resolve_action();
  
        if(!this)
          return;
    
        acted = true;
      }
     
      if(!moved) {
        do_ok(this);
      
        if(!this)
          return;
    
        do_move(this);
        retreat();
      }
    
      do_ok(this);
      return;
    }
  }   
  
  do_move(this);
  advance();
  do_ok(this);
  do_wait(this);
  return;
}

void BATTLE::echo(string str) {
  for(CH *pers = people; pers; pers = pers->next_in_battle)
    if(pers->desc)
      pers->printf(str);

  return;
}

void BATTLE::echo(CH *ch, char *txt, ...) {
  char string[MSL];
  va_list args;      
  va_start(args, txt);
  vsprintf(string, txt, args);
  va_end(args);

  for(CH *pers = ch->battle->people; pers; pers = pers->next_in_battle)
    if(pers->desc)   
      ch->printf(string);
    
  return;
}

void CH::resolve_action() {
  short x;
  announce_action();

  if(!skill_table[action.id].range && !skill_table[action.id].self) {
    bool found = false;
  
    for(x = 1; battle->area->room[x]; ++x) {
      if(battle->area->room[x] != battle_room && battle_room->distance(battle->area->room[x]) <= skill_table[action.id].effect - 1) {
        action.target = battle->area->room[x];
    
        if(action.action(this, false))
          found = true;
      }
    }  

    if(found) {
      gain_exp();
      gain_jp();
    }
  
    action.action = NULL;
    action.target = NULL;
    return;
  }
  
  if(action.action(this, false)) {
    gain_exp();
    gain_jp();
  }
  
  if(!this || !battle)
    return;

  if(skill_table[action.id].effect > 1) {
    BATTLE_ROOM *orig_target = action.target;
       
    for(x = 1; battle->area->room[x]; ++x) {
      if(battle->area->room[x] != orig_target && orig_target->distance(battle->area->room[x]) <= skill_table[action.id].effect - 1) {
        action.target = battle->area->room[x];
        action.action(this, false);
      }
    }
  }

  action.action = NULL;
  action.target = NULL;
  return;
} 

void CH::end_turn() {
  battle->echo(name + "'s turn has ended.\n\r");
  acted = moved = false;
  battle->turn = NULL;
  CT = (moved || acted ? MAX(CT - 80, 60) : MAX(CT - 60, 60));
  status_update();
  commands = "";
  return;
}

void CH::add_queue(const string str) {
  for(short x = 0; x < MAX_QUEUES; ++x) {
    if(queue[x].empty()) {
      queue[x] = str;
      return;
    }
  }

  return;
}

/**************
 * BATTLE_FUN *
 **************/
bool battle_orbonne_monastery(CH *ch, const short phase=1) {
  if(phase == 1 && ch->battles[BATTLE_ORBONNE_MONASTERY])
    return false;

  switch(phase) {
  case 1:
    ch->add_queue(dialogue_box("Female Knight", "Princess Ovelia, let's go.", true));
    ch->add_queue(dialogue_box("Princess Ovelia", "Just a moment, Agrias...", true));
    ch->add_queue(dialogue_box("Agrias", "The guards have already arrived.", true));
    ch->add_queue(dialogue_box("Priest", "Princess, don't give Agrias trouble. Please hurry...", true));
    ch->add_queue("A knight and two squires come in from outside.\n\r" + dialogue_box("Black Knight", "What's going on? It's been nearly an hour!", true));
    ch->add_queue(dialogue_box("Agrias", "Don't be rude to the Princess, Gafgarion.", true));
    ch->add_queue("The two squires behind the Black Knight kneel down while the Black Knight bows his head.\n\r" + dialogue_box("Gafgarion", "Is this going to be alright, Agrias? This is an urgent issue for us.", true));
    ch->add_queue(dialogue_box("Agrias", "So there are rude knaves even among the Hokuten?", true));
    ch->add_queue(dialogue_box("Gafgarion", "I'm being more than kind to the guard captains here. Besides, we're mercenaries hired by the Hokuten. I'm not obliged to show respect to you.", true));
    ch->add_queue(dialogue_box("Agrias", "What? How dare you!", true));
    ch->add_queue("The princess stands up.\n\r" + dialogue_box("Ovelia", "Enough. Let's go.", true));
    ch->add_queue("The two squires get up on their feet. The princess walks over to the priest.\n\r" + dialogue_box("Priest", "Go with God.", true));
    ch->add_queue(dialogue_box("Ovelia", "You too, Simon.", true));
    ch->add_queue("A female knight walks in with a nasty wound. The priest helps the knight.\n\r" + dialogue_box("Female Knight", "Lady Agrias!... The enemy!", true));
    ch->add_queue(dialogue_box("Simon", "Prince Goltana's men!?", true));
    ch->add_queue("Agrias quickly rushes outside!\n\r" + dialogue_box("Gafgarion", "What one must do to make money. What, " + ch->name + "? You have a problem, too?", true));
    ch->add_queue(dialogue_box(ch->name, "...I'm no longer a Knight. Just a mercenary like you.", true));
    ch->add_queue(dialogue_box("Gafgarion", "...That's right. Well then. Let's go!", true));
    ch->add_queue("The Knight and the two squires go outside.\n\r" + dialogue_box("Ovelia", "Oh God...", false));
    ch->add_queue("phase 2");
    break;
  case 2:
    ch->to(get_battle_area(AREA_ORBONNE_MONASTERY)->room[57]);
    do_look(ch);
    break;
  }

  return true;
}