/*

 BabbleMud - A MUD server

 Author:  Nick Gammon 
          http://www.gammon.com.au/ 

(C) Copyright Nick Gammon 2004. Permission to copy, use, modify, sell and
distribute this software is granted provided this copyright notice appears
in all copies. This software is provided "as is" without express or implied
warranty, and with no claim as to its suitability for any purpose.
 
*/

#ifdef WIN32
#include <time.h>
#endif

#include "commands.h"

const string FIGHTING = "fighting";           // player is fighting flag
const string FIGHT_TARGET = "fight_target";   // who they are fighting
const string FIGHT_PHASE = "fight_phase";     // what phase are we in? - see below
const string FIGHT_HP_LOST_DURING_PREPARATION = "fight_hp_lost_during_preparation";

const string FIGHT_PHASE_SURPRISED  = "surprised";
const string FIGHT_PHASE_PREPARING  = "preparing";
const string FIGHT_PHASE_RECOVERING = "recovering";

typedef enum
  {
  NEUTRAL,
  GOOD, 
  BAD
  } debugTypes;

// debugging during fight development
void FightDebug (const string & msg, Character * p = NULL, const debugTypes type = NEUTRAL)
  {
  time_t t;
  struct tm *thetime;
  char sTimeBuff [80];
  time (&t);
  
  thetime = localtime (&t);
  strftime (sTimeBuff, sizeof sTimeBuff, "%H:%M:%S", thetime);
  
  string s;
  
  if (p && p->mob ())
    s = "  ";
  
  if (type == GOOD)
    s += "++ ";
  else if (type == BAD)
    s += "-- ";
  else
    s += "   ";
  
  SendToAll (string (sTimeBuff) + " :: " + s + msg, "fight_debug"); 
  }

// this character has stopped fighting
void StopFight (Character & p)
  {
  if (p.getb (FIGHTING))
    {
    p.setb (FIGHTING, false);  
    FightDebug (p.GetName () + " stops fighting.", &p);
    p.send ("not_fighting_now", messagemap ());
    }
  }

bool SetUpFightEvent (Event & e, Character * & player, 
                      Character * & target, const bool needtarget = true)
  {
  
  // find player who is fighting
  player = FindPlayerID (e.gets ("id"));
  
  if (player == NULL)
    {
    e.SetCount (0); // delete event
    FightDebug ("Player: " + e.gets ("id") + " does not exist");
    return true;  
    }
  
  // if they aren't fighting any more, note that they are no longer in phase X
  //  (this could be used to test whether another fight can start)
  if (!player->getb (FIGHTING))
    player->sets (FIGHT_PHASE, "");   // they are no longer doing whatever it was
  
  // get target of fight
  target = FindPlayerID (player->gets (FIGHT_TARGET));
  
  // must have a target
  if (target == NULL && needtarget)
    {
    e.SetCount (0); // delete event
    FightDebug ("Target of fight: " + player->gets (FIGHT_TARGET) + " does not exist", player);
    StopFight (*player); // may as well stop fighting
    return true;  
    }
  
  // better both be in the same room
  if (needtarget)
    {
    if (player->GetRoom () != target->GetRoom ())
      {
      FightDebug ("Target of fight: " + player->gets (FIGHT_TARGET) + " is not in the same room", player);
      StopFight (*player); // may as well stop fighting
      StopFight (*target); // ditto
      return true;        
      }
    
    } // end of needing a target
  
  return false; // all OK
  }

// new round
void CommenceNewAttackRound (Character * p, Character * t)
  {
  
  // if they have stopped fighting, don't bother
  if (!p->getb (FIGHTING))
    return;
  
  int prepareTime = 500 * roll (2, 6);
  
  Event ep ("EventFightAttackPrepared", prepareTime, 1);  // prepare to attack
  ep.sets ("id", p->gets ("id")); // which player
  eventQueue ().push (ep); // queue it
  FightDebug (MAKE_STRING (p->GetName () << " prepares to attack for " 
                                            << prepareTime << " ms"), p);
  p->sets (FIGHT_PHASE, FIGHT_PHASE_PREPARING);   // remember what we are doing
  p->seti (FIGHT_HP_LOST_DURING_PREPARATION, 0);   // nothing has happened to us - yet
  
  } // end of CommenceNewAttackRound

// end of surprise phase in fight
void EventFightSurpriseOver (Event & e)
  {
Character * p;  // player
Character * t;  // target
  
  if (SetUpFightEvent (e, p, t))
    return;   // can't find player or target
  
  FightDebug (p->GetName () + " is no longer surprised", p);
  
  CommenceNewAttackRound (p, t);  // start a round
  } // end of EventFightSurpriseOver

// end of recovery phase in fight
void EventFightRecoveryComplete (Event & e)
  {
Character * p;  // player
Character * t;  // target
  
  if (SetUpFightEvent (e, p, t))
    return;   // can't find player or target
  
  FightDebug (p->GetName () + " has recovered", p);
  
  CommenceNewAttackRound (p, t);  // start a round
  } // end of EventFightRecoveryComplete

// attack is ready to launch
void EventFightAttackPrepared (Event & e)
  {
Character * p;  // player
Character * t;  // target
  
  if (SetUpFightEvent (e, p, t))
    return;   // can't find player or target
    
  FightDebug (p->GetName () + " has finished preparing his attack", p);
  
  // if they have stopped fighting, don't bother
  if (!p->getb (FIGHTING))
    return;
  
  // initially, recovery time for failed preparation
  int recoveryTime = 0;
  
  int lostConcentrationThreshold = 3;
  
  // an attack while we were preparing may make us fail
  if (p->geti (FIGHT_HP_LOST_DURING_PREPARATION) > lostConcentrationThreshold)
    {
    
    // here for failed preparation
    
    FightDebug (p->GetName () + " fails to prepare due to attack during preparation", p, BAD);    
    recoveryTime = 500 * roll (3, 6);
    }
  else
    {
    
    // here for successful preparation
    
    int intransitTime = 500 * roll (2, 6);
    
    Event et ("EventFightTransitComplete", intransitTime, 1);  // launch attack
    et.sets ("id", t->gets ("id")); // which target
    et.seti ("transit_time", intransitTime);  // how long
    eventQueue ().push (et); // queue it
    FightDebug (MAKE_STRING (p->GetName () << " launches his attack, in transit for " 
                                              << intransitTime << " ms"), p);
    
    FightDebug (p->GetName () + " has things deducted (eg. mana) for the attack", p);
    
    // recovery time for successful launch
    recoveryTime = 500 * roll (3, 6);   
    }
  
   
  Event ep ("EventFightRecoveryComplete", recoveryTime, 1);  // player recovers
  ep.sets ("id", p->gets ("id")); // which player
  eventQueue ().push (ep); // queue it
  FightDebug (MAKE_STRING (p->GetName () << " now recovers for " 
                                            << recoveryTime << " ms"), p);
  
  p->sets (FIGHT_PHASE, FIGHT_PHASE_RECOVERING);   // remember what we are doing
  
  } // end of EventFightAttackPrepared


// end of attack in transit
void EventFightTransitComplete (Event & e)
  {
Character * p;  // player
Character * t;  // target
  
  if (SetUpFightEvent (e, p, t, false))
    return;   // can't find player or target
  
  FightDebug ("Attack on " + p->GetName () + " has reached its destination", p);
  
  // target gets a chance to dodge - probably based on the transit time
  const int chanceToDodge = 30;
  
  int intransitTime = e.geti ("transit_time");  // this is how much time they had

  FightDebug (p->GetName () + " is in phase: " + p->gets (FIGHT_PHASE) , p);
  
  // cannot dodge if busy drawing a bow or whatever
  if (p->gets (FIGHT_PHASE) == FIGHT_PHASE_PREPARING)
    {
    FightDebug (p->GetName () + " is preparing his attack and cannot dodge.", p, BAD);
    }
  else
    { // not preparing
    if (percent (chanceToDodge))
      {
      FightDebug (p->GetName () + " dodges the attack!", p, GOOD);
      return;
      }
    } // not preparing phase
  
  // there is a chance the attack will miss ...
  const int chanceToHit = 70;
   if (!percent (chanceToHit))
    {
    FightDebug ("The attack misses " + p->GetName (), p, GOOD);
    return;
    }
 
  // the defender may parry by a certain amount
  int parryAmount = roll (3, 10);
  
  // --- reflection might occur here ----
  
  // calculate damage amount
  int damage = roll (2, 6); 

  // susceptible/immune/resistant/absorption
  
  double multiplier = 1.1;  
  
  int deduct = int ((double) damage * multiplier);
  
  // what happened to them while they were preparing to fight
  if (deduct > 0)
      p->seti (FIGHT_HP_LOST_DURING_PREPARATION, p->geti (FIGHT_HP_LOST_DURING_PREPARATION) + deduct);
  
  p->seti ("hp", p->geti ("hp") - deduct);
  
  int hp = p->geti ("hp");
  
  FightDebug (MAKE_STRING (p->GetName () << " takes " 
              << deduct << " damage, HP now = " << hp), p, BAD);
    
  // apply other effects here (eg. blindness)
  
  // check for death
  
  if (hp <= 0)
    { // he's dead, Jim!
    FightDebug (p->GetName () + " has been killed!", p, BAD);
    StopFight (*p); // may as well stop fighting
    
    if (t &&
        t->getb (FIGHTING) &&
        t->gets (FIGHT_TARGET) == p->gets ("id"))
      StopFight (*t); // the attacker may as well stop too
    
    }
  
  } // end of EventFightTransitComplete

static int LoadEventsMap ()
  {
static EventHandlerMap & eventmap = getEventHandlerMap ();

  cout << "Loading event handlers in " __FILE__ << endl;
    
  EVENT (EventFightSurpriseOver);       // we are no longer surprised
  EVENT (EventFightAttackPrepared);     // attack is prepared
  EVENT (EventFightRecoveryComplete);   // attacker has recovered
  EVENT (EventFightTransitComplete);    // attack has reached target

  return 0;
  } // end of LoadEventsMap

static int loadEvents = LoadEventsMap ();


void DoFight (CommandInfo & info)
  {
Character & p (info.player ());
Character & t = info.target ();
 
  // a few checks for whether we can start fighting
  if (p.getb (FIGHTING))
    throw msg ("you_already_fighting");
  
  if (t.getb (FIGHTING))
    throw msg ("target_already_fighting");
  
  // other checks here, eg. no-fight rooms etc.
  
  p.setb (FIGHTING, true);    // both are now fighting
  t.setb (FIGHTING, true);
  
  p.sets (FIGHT_TARGET, t.gets ("id"));   // we are  fighting them
  t.sets (FIGHT_TARGET, p.gets ("id"));   // and they are fighting us
  
  const int playerSurprise = 2000;
  const int targetSurprise = 3000;
  
  Event ep ("EventFightSurpriseOver", playerSurprise, 1);  // element of surprise
  ep.sets ("id", p.gets ("id")); // which player
  eventQueue ().push (ep); // queue it
  FightDebug (MAKE_STRING (p.GetName () << " is surprised for " 
                                          << playerSurprise << " ms"), &p);
  p.sets (FIGHT_PHASE, FIGHT_PHASE_SURPRISED);   // remember what we are doing
  
  Event et ("EventFightSurpriseOver", targetSurprise, 1);  // element of surprise
  et.sets ("id", t.gets ("id")); // which player
  eventQueue ().push (et); // queue it
  FightDebug (MAKE_STRING (t.GetName () << " is surprised for " 
                                          << targetSurprise << " ms"), &p);
  t.sets (FIGHT_PHASE, FIGHT_PHASE_SURPRISED);    // remember what they are doing
  } // end of DoFight


static int LoadCommandsMap ()
  {
static CommandHandlerMap & commandmap = getCommandHandlerMap ();

  cout << "Loading command handlers in " __FILE__ << endl;
    
  COMMAND (DoFight);      // start fighting

  return 0;
  } // end of LoadCommands

static int loadCommands = LoadCommandsMap ();
