/
Crimson2/alias/
Crimson2/area.tmp/
Crimson2/area.tmp/AnomalySpaceDock/
Crimson2/area.tmp/AnomalyStation/
Crimson2/area.tmp/AntHill/
Crimson2/area.tmp/ArcticTerrarium/
Crimson2/area.tmp/BuilderCity/
Crimson2/area.tmp/Dungeon/
Crimson2/area.tmp/MiningDock/
Crimson2/area.tmp/PipeSystem/
Crimson2/area.tmp/RattArea/
Crimson2/area.tmp/RobotFactory/
Crimson2/area.tmp/SilverDale/
Crimson2/area.tmp/StarshipFearless/
Crimson2/area.tmp/StationConduits/
Crimson2/area.tmp/TerrariumAlpha/
Crimson2/area.tmp/TerrariumBeta/
Crimson2/area.tmp/TestArea/
Crimson2/area.tmp/Void/
Crimson2/area/
Crimson2/area/AnomalySpaceDock/
Crimson2/area/AnomalyStation/
Crimson2/area/MiningDock/
Crimson2/area/PipeSystem/
Crimson2/area/SilverDale/
Crimson2/area/StationConduits/
Crimson2/area/Void/
Crimson2/board/
Crimson2/clone/
Crimson2/lib/
Crimson2/mole/
Crimson2/mole/mole_src/HELP/
Crimson2/player/
Crimson2/util/
Crimson2/wldedit/
Crimson2/wldedit/res/
/* Crimson2 Mud Server
 * All source written/copyright Ryan Haksi 1995 *
 * This source code is proprietary. Use in whole or in part without
 * explicity permission by the author is strictly prohibited
 *
 * Current email address(es): cryogen@infoserve.net
 * Phone number: (604) 591-5295
 *
 * C4 Script Language written/copyright Cam Lesiuk 1995
 * Email: clesiuk@engr.uvic.ca
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "crimson2.h"
#include "macro.h"
#include "log.h"
#include "mem.h"
#include "str.h"
#include "property.h"
#include "file.h"
#include "queue.h"
#include "send.h"
#include "thing.h"
#include "index.h"
#include "edit.h"
#include "history.h"
#include "socket.h"
#include "code.h"
#include "exit.h"
#include "world.h"
#include "base.h"
#include "char.h"
#include "group.h"
#include "mobile.h"
#include "player.h"
#include "area.h"
#include "skill.h"
#include "object.h"
#include "affect.h"
#include "effect.h"
#include "cmd_move.h"
#include "cmd_inv.h"
#include "cmd_cbt.h"
#include "fight.h"
#include "group.h"

#define FIGHT_INDEX_SIZE     2048
#define FIGHT_MSG_INDEX_SIZE 1024
#define FIGHT_MSG_SIZE        256 /* block alloc size */

/* How combat is going to work
   chance to hit is determined by:
   skill + familyskill + HitBonus with any given weapon, where skill with ONE weapon type is
   reasonably easy to learn but a general skill with all weapons in that family
   should be (MUCH) harder to learn.
*/

/* should damage flags be determined by Ammo?, at the very least they can be overridden! */
/* List is more or less sorted according to damage potential, and in increasing difficulty
   to learn ie Plasma Cannons should rock, but practicing the skill up should be a bitch
  */
struct WeaponListType weaponList[] = {

  /* Name,         strbonus   damflags               skill                  fam. skill */

  /* Melee Weapons */
  {"MELEE-KNIFE",         1,  FD_PUNCTURE,           &SKILL_MELEE_KNIFE,     &SKILL_MELEE},
  {"MELEE-BLADE",         1,  FD_SLASH,              &SKILL_MELEE_BLADE,     &SKILL_MELEE},
  {"MELEE-BLUDGEON",      1,  FD_CONCUSSIVE,         &SKILL_MELEE_BLUDGEON,  &SKILL_MELEE},
  {"MELEE-AXE",           1,  FD_SLASH,              &SKILL_MELEE_AXE,       &SKILL_MELEE},

  /* Laser Weapons */
  {"LASER-PISTOL",        0,  FD_LASER,              &SKILL_LASER_PISTOL,    &SKILL_PISTOL},
  {"LASER-RIFLE",         0,  FD_LASER,              &SKILL_LASER_RIFLE,     &SKILL_RIFLE},

  /* Slugthrower Weapons */
  {"SLUG-PISTOL",         0,  FD_PUNCTURE,           &SKILL_SLUG_PISTOL,     &SKILL_PISTOL},
  {"SLUG-RIFLE",          0,  FD_PUNCTURE,           &SKILL_SLUG_RIFLE,      &SKILL_RIFLE},
  {"SLUG-MACHINEPISTOL",  0,  FD_PUNCTURE,           &SKILL_SLUG_MACHINE,    &SKILL_PISTOL},

  /* Blaster Weapons */
  {"BLASTER-PISTOL",      0,  FD_HEAT,               &SKILL_BLASTER_PISTOL,  &SKILL_PISTOL},
  {"BLASTER-RIFLE",       0,  FD_HEAT,               &SKILL_BLASTER_RIFLE,   &SKILL_RIFLE},
  {"BLASTER-CANNON",      0,  FD_HEAT,               &SKILL_BLASTER_CANNON,  &SKILL_CANNON},

  /* Ion Weapons */
  {"ION-RIFLE",           0,  FD_EMR,                &SKILL_ION_RIFLE,       &SKILL_RIFLE},
  {"ION-CANNON",          0,  FD_EMR,                &SKILL_ION_CANNON,      &SKILL_CANNON},

  /* Support Weapons */
  {"GRENADE-LAUNCHER",    0,  FD_CONCUSSIVE,         &SKILL_GRENADE_RIFLE,   &SKILL_RIFLE},
  {"ROCKET-LAUNCHER",     0,  FD_CONCUSSIVE,         &SKILL_MISSILE_CANNON,  &SKILL_CANNON},

  /* Plasma Weapons */
  {"PLASMA-RIFLE",        0,  FD_HEAT|FD_CONCUSSIVE, &SKILL_PLASMA_RIFLE,    &SKILL_RIFLE},
  {"PLASMA-CANNON",       0,  FD_HEAT|FD_CONCUSSIVE, &SKILL_PLASMA_CANNON,   &SKILL_CANNON},

  {"FIREBLADE",           0,  FD_HEAT,               &SKILL_FIREBLADE,       &SKILL_FIRE},
  /* End of the List */
  {"",                    0,  0,                     0,                     0}
};



/* Heat & Concussive would be the most common I would think... */
BYTE *resistList[] = {
  "PUNCTURE",
  "SLASH",
  "CONCUSSIVE",
  "HEAT",
  "EMR", /* ie electrical */
  "LASER",
  "PSYCHIC", /* ie mind attack */
  "ACID",
  "POISON",
  "NONE",
  ""
};

INDEX fightIndex;
INDEX missMsgIndex;
INDEX singleMsgIndex;
INDEX multipleMsgIndex;
INDEX deathMsgIndex;

/* returns same values as strcmp */
INDEXPROC(FightMsgCompareProc) { /* BYTE IndexProc(void *index1, void *index2) */
  if ( FightMsg(index1)->fmType == FightMsg(index2)->fmType )
    return 0;
  else if ( FightMsg(index1)->fmType < FightMsg(index2)->fmType )
    return -1;
  else
    return 1;
}

/* returns same values as strcmp */
INDEXFINDPROC(FightMsgFindProc) { /* BYTE IFindProc(void *key, void *index) */
  if ( (LWORD)key == FightMsg(index)->fmType )
    return 0;
  else if ( (LWORD)key < FightMsg(index)->fmType )
    return -1;
  else
    return 1;
}


void FightMessageAdd(FIGHTMSG *msg, BYTE *typeBuf, BYTE indexType) {
  LWORD i=0;
  BYTE  word[256];
  LWORD found;
  
  while (typeBuf[i]) {
    sscanf(typeBuf+i, " %s", word);
    found = TYPEFIND(word, weaponList);
    if (found == -1) {
      found = TYPEFIND(word, effectList);
      if (found != -1)
        found += FM_EFFECT;
    } else
      found += FM_WEAPON;
    if (found != -1) {
      msg->fmType = found;
      switch (indexType) {
      case '0':
        IndexInsert(&missMsgIndex, (THING*)msg, FightMsgCompareProc);
        break;
      case 'S':
        IndexInsert(&singleMsgIndex, (THING*)msg, FightMsgCompareProc);
        break;
      case 'M':
        IndexInsert(&multipleMsgIndex, (THING*)msg, FightMsgCompareProc);
        break;
      case 'D':
        IndexInsert(&deathMsgIndex, (THING*)msg, FightMsgCompareProc);
        break;
      }
    } else {
      Log(LOG_ERROR, "FightMsgAdd: Unknown Message Type");
      LogPrintf(LOG_ERROR, word);
      LogPrintf(LOG_ERROR, "\n");
    }
    for(i++; typeBuf[i]&&typeBuf[i]!=' '; i++);
  }
}

void FightMessageRead(void) {
  BYTE            buf[256];
  BYTE            typeBuf[256] = "";
  FILE           *msgFile;
  FIGHTMSG       *msg;

  msgFile = fopen("msg/damage.msg", "rb");
  if (!msgFile) {
    Log(LOG_ERROR, "Unable to open msg/damage.msg file\n");
    return;
  }
  fgets(buf, 256, msgFile);
  while (!feof(msgFile)) {
    switch (buf[0]) {
      case '#':
        strcpy(typeBuf, buf+1);
        break;

      case '0':
      case 'S':
      case 'M':
      case 'D':
        if (!*typeBuf) break; /* must have read this first */
        MEMALLOC(msg, FIGHTMSG, FIGHT_MSG_SIZE);
        memset(msg, 0, sizeof(FIGHTMSG));
        msg->fmSrc  = FileStrRead(msgFile);
        msg->fmDst  = FileStrRead(msgFile);
        msg->fmRoom = FileStrRead(msgFile);
        FightMessageAdd(msg, typeBuf, buf[0]);
        break;

      case ';': /* its a comment */
      case '\0': /* its nothing */
      case '\r': /* its nothing */
      case '\n': /* its nothing */
        break;

      default: /* corrupt crap for example */
        Log(LOG_ERROR, "in [msg/damage.msg] not sure what to make of:\n");
        Log(LOG_ERROR, buf);
        break; /* its a comment */
    }
    fgets(buf, 256, msgFile);
  }
}

void FightInit(void) {
  IndexInit(&fightIndex,       FIGHT_INDEX_SIZE,     "fightIndex",       0                 );
  IndexInit(&missMsgIndex,     FIGHT_MSG_INDEX_SIZE, "missMsgIndex",     IF_ALLOW_DUPLICATE);
  IndexInit(&singleMsgIndex,   FIGHT_MSG_INDEX_SIZE, "singleMsgIndex",   IF_ALLOW_DUPLICATE);
  IndexInit(&multipleMsgIndex, FIGHT_MSG_INDEX_SIZE, "multipleMsgIndex", IF_ALLOW_DUPLICATE);
  IndexInit(&deathMsgIndex,    FIGHT_MSG_INDEX_SIZE, "deathMsgIndex",    IF_ALLOW_DUPLICATE);

  FightMessageRead();
}

/* Just moved through this exit take appropriate action */
void FightMove(THING *thing, EXIT *exit) {
  FightStop(thing);
}

/* see if we hit who we are fighting */
LWORD FightCheckHit(THING *thing, WORD *hitNum) {
  LWORD  hitAttempts = 0; /* Minset to 1 later */
  THING *weapon;
  LWORD  i;
  LWORD  roll;
  LWORD  result;
  THING *ammo     = NULL;
  LWORD  ammoType = 0;
  LWORD  ammoUse  = 0;
  LWORD  ammoLeft = 0;
  THING *target = Character(thing)->cFight;
  LWORD  ambush;
  LWORD  hidden = FALSE;

  /* determine # of hit attempts */
  *hitNum = 0;
  weapon = Character(thing)->cWeapon;
  if (!weapon || weapon->tType!=TTYPE_OBJ || Obj(weapon)->oTemplate->oType!=OTYPE_WEAPON)
    weapon = NULL; /* not a valid weapon */
  if (weapon) {
    ammo = ObjectGetAmmo(weapon, &ammoType, &ammoUse, &ammoLeft);
    if (ammoUse <= ammoLeft || thing->tType==TTYPE_MOB)
      hitAttempts = OBJECTGETFIELD(weapon, OF_WEAPON_FIRERATE);
  }
  MINSET(hitAttempts, 1);

  /* multiple hit attempts skill */
  roll = 100+Character(thing)->cSpeed;
  if (thing->tType == TTYPE_PLR) {
    if (weapon) {
      if (!ammoType || ammoUse <= ammoLeft) {
        i = OBJECTGETFIELD(weapon, OF_WEAPON_TYPE);
        TYPECHECK(i, weaponList);
        roll += Plr(thing)->pSkill[*weaponList[i].wSkill]/3;
        roll += Plr(thing)->pSkill[*weaponList[i].wFamily+2];
      } else {
        /* out of ammo use like a club */
        roll += Plr(thing)->pSkill[SKILL_MELEE_BLUDGEON+2]/3;
        roll += Plr(thing)->pSkill[SKILL_MELEE+2];
      }
    }
    roll += Plr(thing)->pSkill[SKILL_SPEED];
  } else if (thing->tType == TTYPE_MOB) {
    roll += PropertyGetLWord(thing, "%Speed", Mob(thing)->mTemplate->mLevel*3);
  }

  /* If they are hidden add in ambush skill to speed */
  if BIT(Character(thing)->cAffectFlag, AF_HIDDEN) hidden = TRUE;
  ambush = CharGetAmbush(thing);
  roll += ambush;
  /* check for the effects of haste and slow */
  if (AffectFind(thing,EFFECT_HASTE)) {
    roll*=2;
  }
  if (AffectFind(thing,EFFECT_SLOW)) {
    roll/=2;
  }

  MINSET(roll, 1);
  result = DiceOpenEnded(1, roll, 100);
  hitAttempts += result/100;
  if (result%100>50) hitAttempts++;
  if (weapon && ammoType && ammoUse && ammo)
    MAXSET(hitAttempts, ammoLeft/ammoUse);

  /* see how many times we hit the target */
  for (i=0; i<hitAttempts; i++) {
    result = DiceOpenEnded(1,100,100);
    
    /* adjust for armor  */
    result -= Character( target )->cArmor;

    /* adjust for dex if target is player */
    if (target->tType == TTYPE_PLR)
      result -= Plr( target )->pDex/10;

    /* adjust for hit bonus */
    result += CharGetHitBonus(thing, weapon);

    if (result>50) {
      *hitNum += 1;
    } else { /* deduct ammo if we missed, otherwise wait until after damage */
      ObjectUseAmmo(weapon);
    }

    /* Only the first attack gets the ambush bonuses */
    BITCLR(Character(thing)->cAffectFlag, AF_HIDDEN);
  }

  /* Set hidden bit again so that damage message mentions attacking
   * from the shadows */
  if (hidden) BITSET(Character(thing)->cAffectFlag, AF_HIDDEN);

  /* return results */
  if (*hitNum>0)
    return TRUE;
  else
    return FALSE;
}


void FightReload(THING *thing) {
  THING *ammo = NULL;
  THING *newAmmo = NULL;
  THING *weapon;
  LWORD  ammoType;
  LWORD  ammoUse;
  LWORD  ammoLeft;

  weapon = Character(thing)->cWeapon;
  ammo = ObjectGetAmmo(weapon, &ammoType, &ammoUse, &ammoLeft);
  if (!ammoType) return;

  if (!ammo || ammoUse > ammoLeft) {
    /* Look for replacement ammo */
    for (newAmmo = thing->tContain; newAmmo; newAmmo=newAmmo->tNext) {
      if (newAmmo->tType != TTYPE_OBJ) continue;
      if (Obj(newAmmo)->oTemplate->oType != OTYPE_AMMO) continue;
      if (OBJECTGETFIELD(newAmmo, OF_AMMO_AMMOTYPE) == ammoType) break;
    }
    if (!newAmmo) return;

    if (ammo) THINGFREE(ammo);
    ThingTo(newAmmo, weapon);
    /* let 'em know what happened */
    SendAction("^G$n reloads $N\n", 
      thing, weapon, SEND_ROOM|SEND_VISIBLE|SEND_AUDIBLE|SEND_CAPFIRST);
    SendAction("^wYou reload $N\n", 
      thing, weapon, SEND_SRC |SEND_VISIBLE|SEND_AUDIBLE|SEND_CAPFIRST);
  }
}

LWORD FightWeaponDamage(THING *thing, FLAG *damFlag, WORD *damType) {
  LWORD  damage;
  LWORD  wtype;
  LWORD  dieNum;
  LWORD  dieSize;
  THING *ammo = NULL;
  LWORD  ammoType;
  LWORD  ammoUse;
  LWORD  ammoLeft;

  switch(thing->tType) {
  case TTYPE_PLR:
    damage = CharGetDamBonus(thing, Character(thing)->cWeapon);
    if (Character(thing)->cWeapon)
      return  (damage + FightWeaponDamage(Character(thing)->cWeapon, damFlag, damType));
    *damFlag = FD_CONCUSSIVE;
    *damType = FM_DEFAULT;
    damage += ( Dice(1,4) );
    break;
    
  case TTYPE_MOB:
    damage = CharGetDamBonus(thing, Character(thing)->cWeapon);
    if (Character(thing)->cWeapon)
      return (damage + FightWeaponDamage(Character(thing)->cWeapon, damFlag, damType));
    *damFlag = FD_CONCUSSIVE;
    *damType = FM_DEFAULT; 
    damage += Dice(Mob(thing)->mTemplate->mDamDiceNum, Mob(thing)->mTemplate->mDamDiceSize);
    break;
    
  case TTYPE_OBJ:
    if (Obj(thing)->oTemplate->oType == OTYPE_WEAPON) {
      wtype    = OBJECTGETFIELD(thing, OF_WEAPON_TYPE);
      dieNum   = OBJECTGETFIELD(thing, OF_WEAPON_DIENUM);
      dieSize  = OBJECTGETFIELD(thing, OF_WEAPON_DIESIZE);
      ammo = ObjectGetAmmo(thing, &ammoType, &ammoUse, &ammoLeft);
      if (!ammoType || ammoUse <= ammoLeft) {
        *damFlag = weaponList[wtype].wDamage;
        *damType = FM_WEAPON+wtype;
        damage = Dice(dieNum, dieSize);
        if (ammoType) {
          ObjectUseAmmo(thing);
        }
      } else {
        /* mobs do weapon damage even without ammo */
        if (Base(thing)->bInside->tType == TTYPE_MOB) {
          *damFlag = weaponList[wtype].wDamage;
          *damType = FM_WEAPON+wtype;
          damage = Dice(dieNum, dieSize);
        } else {
          *damFlag = FD_CONCUSSIVE;
          *damType = FM_DEFAULT; 
          damage = Dice(1, Obj(thing)->oTemplate->oWeight/25+1);
        }
      }
    } else {
      *damFlag = FD_CONCUSSIVE;
      *damType = FM_DEFAULT; 
      damage = Dice(1, Obj(thing)->oTemplate->oWeight/25+1);
    }
    break;
    
  default:
    *damFlag = FD_CONCUSSIVE;
    *damType = FM_DEFAULT; 
    damage = 1;
    break;
  }
  
  return damage;
}


/* for the grammer impaired, a gerund verb form is anything that ends with "ing" */
#define FSS_NORMAL 0
#define FSS_GERUND 1
void FightSprintfSeverity(BYTE *str, THING *victim, LWORD damage, WORD field) {
  typedef struct severityStrType {
    BYTE *sName;
    BYTE *sGerund;
    WORD  sPercent;
  } SEVERITYSTR;

  SEVERITYSTR severityStr[] = {
    {"tickle",        "tickling",           2},
    {"lightly wound", "lightly wounding",   5},
    {"hurt",          "hurting",           10},
    {"badly hurt",    "badly hurting",     20},
    {"mangle",        "mangling",          40},
    {"obliterate",    "obliterating",      99},
    {"kill",          "killing",           -1}
  };

  LWORD percent;
  LWORD i;

  percent = (damage * 100) / ( MAXV(Character(victim)->cHitP,1) );
  for(i=0; percent > severityStr[i].sPercent && severityStr[i].sPercent != -1; i++);
  if (field == FSS_NORMAL)
    strcpy(str, severityStr[i].sName);
  else
    strcpy(str, severityStr[i].sGerund);
}

BYTE *FightSprintf(BYTE *str, THING *victim, BYTE *messageStr, LWORD damage, WORD hitNum, BYTE *weaponName) {
  LWORD i;
  LWORD j=0;
  LWORD sLen;

  sLen = strlen(messageStr);
  for (i=0; i<=sLen; i++) {
    if (messageStr[i]=='#') {
      i++; /* get past # sign */
      switch (messageStr[i]) {
      case 'h': /* # of hits */
        sprintf(str+j, "%hd", hitNum);
        break;
      case 'd': /* relative wound severity */
        FightSprintfSeverity(str+j, victim, damage, FSS_NORMAL);
        break;
      case 'g': /* wound severity (gerund form) */
        FightSprintfSeverity(str+j, victim, damage, FSS_GERUND);
        break;
      case 'p': /* points of damage */
        sprintf(str+j, "%ld", damage);
        break;
      case 'n': /* weapon name */
        sprintf(str+j, "%s", weaponName);
        break;
      }
      j+=strlen(str+j); /* correct for added length */
    } else {
      str[j] = messageStr[i];
      j++;
    }
  }

  return str;
}

/* Thing is optional, its for crediting experience in case of a kill */
LWORD FightDamagePrimitive(THING *thing, THING *target, LWORD damage) {
  THING *code;

  if (!target) return TRUE;
  /* Make sure we cant hurt Gods */
  if (target->tType==TTYPE_PLR) {
    if (Character(target)->cLevel >= LEVEL_GOD)
      damage = 0;
    if ((Base(target)->bInside) &&
        (AreaIsEditor(AreaOf(Wld(Base(target)->bInside)->wVirtual),target)==2)) {
      damage = 0;
    }
  }

  /* do damage */
  Character(target)->cHitP -= damage;

  /* check for death */
  if (Character(target)->cHitP < 0) {
    if (thing) {
      /* see if special handler intercepts */
      code = Base(target)->bInside;
      if ((CodeParseDeath(thing, code, target)))
        return TRUE;
      for (code=code->tContain; code; code=code->tNext)
        if ((CodeParseDeath(thing, code, target))) 
          return TRUE;
      if (Base(thing)->bInside->tType == TTYPE_WLD)
        if (CodeParseDeath(thing, &areaList[Wld(Base(thing)->bInside)->wArea].aResetThing, target))
          return TRUE;
    }

    /* they're history */
    FightKill(thing, target); /* thing will kill target, target is dangling after this */
    target = NULL; /* so we dont forget and use by accident */
    return TRUE;
  } 
  return FALSE;
}

/* do some damage, guess we hit 'em - return TRUE if they croak */
LWORD FightDamage(THING *thing, LWORD damage, WORD hitNum, FLAG damFlag, WORD damType, BYTE *weaponName) {
  /* default miss messages */
  BYTE  defaultMissSrc[] =  "^7You miss $N with #n\n";
  BYTE  defaultMissDst[] =  "^8$n misses you with #n\n";
  BYTE  defaultMissRoom[] = "^9$n misses $N with #n\n";
  /* default single hit messages */
  BYTE  defaultSingleSrc[] =  "^7You #d $N with #n (#p pts)\n";
  BYTE  defaultSingleDst[] =  "^8$n #ds you with #n (#p pts)\n";
  BYTE  defaultSingleRoom[] = "^9$n #ds $N with #n (#p pts)\n";
  /* default multiple hit messages */
  BYTE  defaultMultipleSrc[] =  "^7You #d $N #h times with #n (#p pts)\n";
  BYTE  defaultMultipleDst[] =  "^8$n #ds you #h times with #n (#p pts)\n";
  BYTE  defaultMultipleRoom[] = "^9$n #ds $N #h times with #n (#p pts)\n";
  /* default death message */
  BYTE  defaultDeathSrc[] =  "^7You brutally slay $N with #n (#p pts)\n";
  BYTE  defaultDeathDst[] =  "^8$n brutally slays you with #n (#p pts)\n";
  BYTE  defaultDeathRoom[] = "^9$n brutally slay $N with #n (#p pts)\n";

  BYTE  buf[256];
  BYTE  *srcMessageStr;
  BYTE  *dstMessageStr;
  BYTE  *roomMessageStr;
  THING *target;

  target = Character(thing)->cFight;
  if (!target) return FALSE;

  /* compensate for damage flags, resist checks here */
  damage -= CharGetResist(target, damFlag)/10*hitNum;
  if (hitNum>0) MINSET(damage, 1);

  /* check if we are ambushing */
  if (BIT(Character(thing)->cAffectFlag, AF_HIDDEN)) {
    SendAction("^YYou step out of the shadows\n", 
      thing, NULL, SEND_SRC|SEND_AUDIBLE|SEND_CAPFIRST);
    SendAction("^Y$n steps out of the shadows\n",
      thing, NULL, SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    /* No longer hidden */
    BITCLR(Character(thing)->cAffectFlag, AF_HIDDEN);
    thing->tWait+=3; /* commit to 3 rounds */
  }

  /* Determine which message to send */
  if (hitNum==0 && damage==0) {
    srcMessageStr  = defaultMissSrc;
    dstMessageStr  = defaultMissDst;
    roomMessageStr = defaultMissRoom;
  } else if (damage >= Character(target)->cHitP) {
    srcMessageStr  = defaultDeathSrc;
    dstMessageStr  = defaultDeathDst;
    roomMessageStr = defaultDeathRoom;
  } else if (hitNum > 1) {
    srcMessageStr  = defaultMultipleSrc;
    dstMessageStr  = defaultMultipleDst;
    roomMessageStr = defaultMultipleRoom;
  } else {
    srcMessageStr  = defaultSingleSrc;
    dstMessageStr  = defaultSingleDst;
    roomMessageStr = defaultSingleRoom;
  }
  
  /* Send the message */
  FightSprintf(buf, target, srcMessageStr, damage, hitNum, weaponName); /* fill in the blanks */
  SendAction(buf, thing, target, SEND_SRC|SEND_AUDIBLE|SEND_CAPFIRST);

  FightSprintf(buf, target, dstMessageStr, damage, hitNum, weaponName); /* fill in the blanks */
  SendAction(buf, thing, target, SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);

  FightSprintf(buf, target, roomMessageStr, damage, hitNum, weaponName); /* fill in the blanks */
  SendAction(buf, thing, target, SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);

  /* start fighting back as necessary */

/* 
 * Whoops the exit logic here is a little screwy 
 */
  if (!Character(target)->cFight) FightStart(target, thing, Character(thing)->cFightRange, Character(thing)->cFightExit);
/*
 * Fix exit stuff here for ranged combat
 */

  return FightDamagePrimitive(thing, target, damage);
}

/* Try to hit current target with active weapon */
void FightAttack(THING *thing) {
  WORD      hitNum;
  WORD      damType=0;
  FLAG      damFlag=0;
  LWORD     damage=0;
  LWORD     i;
  LWORD     cost;
  BYTE      weaponName[256];
  AFFECT   *affect;

  /* normal invisibility will be expired by this */
  affect = AffectFind(thing, EFFECT_INVISIBILITY);
  if (affect) AffectRemove(thing, affect);

  if (thing->tType==TTYPE_PLR 
   && Character(thing)->cLevel<LEVEL_GOD 
   && Character(thing)->cLevel>=LEVEL_FIGHTTIRING) {
    cost = FIGHT_MOVECOST;
    cost = CharMoveCostAdjust(thing, cost);

    if (cost > Character(thing)->cMoveP) {
      SendThing("^wYOU'RE TOO EXHAUSTED TO FIGHT!\n", thing);
      Character(thing)->cMoveP += 1; /* be nice - recover a little bit */
      return;
    } else {
      Character(thing)->cMoveP -= cost;
    }
    if (cost*5 > Character(thing)->cMoveP)
      SendHint("^;HINT: You're getting tired - ^<FLEE^; NOW!\n", thing);
  }

  /*
   * For multiple weapons simply pass a weapon parm to
   * FightCheckHit and FightWeaponDamage
   *
   */
  if(FightCheckHit(thing, &hitNum)) {
    damage=0;
    for (i=0; i<hitNum; i++)
      damage += FightWeaponDamage(thing, &damFlag, &damType);
  }
  /* this is lame, do something better later */
  if (Character(thing)->cWeapon) {
    strcpy(weaponName, Character(thing)->cWeapon->tSDesc->sText);
  } else {
    PROPERTY *property;
    property = PropertyFind(thing->tProperty, "%WEAPONSDESC");
    if (!property && thing->tType==TTYPE_MOB)
      property = PropertyFind(Mob(thing)->mTemplate->mProperty, "%WEAPONSDESC");
    if (!property)
      strcpy(weaponName, "a flurry of punches");
    else
      strcpy(weaponName, property->pDesc->sText);
  }

  FightDamage(thing, damage, hitNum, damFlag, 0, weaponName);
  FightReload(thing);

  /* After Attack Event goes here */
  if (Character(thing)->cFight) {
    LWORD block;

    ThingSetEvent(thing);
    block = CodeParseAfterAttack(thing, thing, Character(thing)->cFight);
    if (block && ThingIsEvent(thing)) FightStop(thing);
    ThingDeleteEvent(thing);
  }
}

/* kill target */
void FightKill(THING *thing, THING *target) {
  THING *corpse;
  THING *money;
  THING *code;
  THING *dest;
  BYTE   buf[256];
  SOCK  *sock;
  LWORD  gain;
  FLAG   cFlag = OCF_CORPSE;
  LWORD  relLevel; /* relative level */
  LWORD  bank;
  LWORD  infamy;
  LWORD  fightStart=FALSE;

  /* FightStop removes the FIGHTSTART bit */
  if ( thing && BIT(Character(thing)->cAffectFlag, AF_FIGHTSTART) )
    fightStart = TRUE;

  /* Stop everybody from attacking this pointer */
  FightStop(target);
  CharKillFollow(target);

  /* Manufacture a "corpse" */
  if (target->tType!=TTYPE_MOB || Mob(target)->mTemplate!=spiritTemplate) {
    corpse = ObjectCreate(corpseTemplate, Base(target)->bInside);
    sprintf(buf, FIGHT_CORPSE_KEY, Base(target)->bKey->sText);
    STRFREE(Base(corpse)->bKey);
    Base(corpse)->bKey = STRCREATE(buf);
    sprintf(buf, "The remnants of %s", target->tSDesc->sText);
    STRFREE(corpse->tSDesc);
    corpse->tSDesc = STRCREATE(buf);
    sprintf(buf, "The remnants of %s are lying here", target->tSDesc->sText);
    STRFREE(Base(corpse)->bLDesc);
    Base(corpse)->bLDesc = STRCREATE(buf);
    OBJECTSETFIELD(corpse, OF_CONTAINER_ROT, 4);
    if (target->tType == TTYPE_MOB
      &&BIT(mTypeList[Mob(target)->mTemplate->mType].mTFlag, MT_ROBOT))
      cFlag |= OCF_ELECTRONIC;
    OBJECTSETFIELD(corpse, OF_CONTAINER_CFLAG, cFlag);

    /* expire affects in case they are carrying psi-items */
    while(Character(target)->cAffect) 
      AffectFree(target, Character(target)->cAffect);
    /* Move all the stuff into the corpse */
    while (target->tContain) ThingTo(target->tContain, corpse);
  
    /* if they carry money then create a money object inside the corpse */
    if (Character(target)->cMoney>0)
      money = ObjectCreateMoney(Character(target)->cMoney, corpse);
    
    /* If their corpse is worth money set its value */
    if (target->tType==TTYPE_MOB 
    && !BIT(mTypeList[Mob(target)->mTemplate->mType].mTFlag, MT_HASMONEY)) {
      OBJECTSETFIELD(corpse, OF_CONTAINER_SVALUE, Mob(target)->mTemplate->mMoney);
    }
  }

  /* See if we are killing a player */
  if (target->tType == TTYPE_PLR) {
    cFlag |= OCF_PLAYERCORPSE;
    OBJECTSETFIELD(corpse, OF_CONTAINER_CFLAG, cFlag);
    OBJECTSETFIELD(corpse, OF_CONTAINER_ROT, 10);
    Log(LOG_USAGE, target->tSDesc->sText);
    if (thing) {
      LogPrintf(LOG_USAGE, " just kicked the bucket... (killed by ");
      LogPrintf(LOG_USAGE, thing->tSDesc->sText);
      LogPrintf(LOG_USAGE, ")\n");
    } else {
      LogPrintf(LOG_USAGE, " just kicked the bucket...\n");
    }
    SendThing(fileList[FILE_DEATH].fileStr->sText, target);
    sprintf(buf, "%s", target->tSDesc->sText);

    /* Set infamy */
    if (thing && thing->tType==TTYPE_PLR && fightStart && !PlayerIsInfamous(target)) {
      LWORD gain = 2;
      BYTE  buf[256];

      if (Character(thing)->cLevel - Character(target)->cLevel > 5)
        gain = 3;
      sprintf(buf, "^yFor murdering another player, you gain %ld points of ^wINFAMY^w!!\n", gain);
      PlayerGainInfamy(thing, gain, buf);
    }

    /* execute afterdeath code scripts (if any) */
    code = Base(target)->bInside;
    CodeParseAfterDeath(thing, code, target);
    for (code=code->tContain; code; code=code->tNext)
      CodeParseAfterDeath(thing, code, target); 
    if (thing && Base(thing)->bInside->tType == TTYPE_WLD)
      CodeParseAfterDeath(thing, &areaList[Wld(Base(thing)->bInside)->wArea].aResetThing, target);

    /* Find players socket */
    sock = BaseControlFind(target);
    /* hmm, guess we'll search the socket list for an out of body player */
    if (!sock) {
      sock = sockList;
      while (sock && sock->sControlThing!=target) sock = sock->sNext;
      if (sock) {
        /* return to their real body */
        BaseControlFree(target, sock);
      }
    }
    /* Okay now make 'em dead */
    if (sock) {
      bank = Plr(target)->pBank;
      infamy = Plr(target)->pInfamy;
      sock->sHomeThing = Thing( PlayerRead(buf, PREAD_CLONE) );
      /* some things dont reset */
      Plr(sock->sHomeThing)->pBank     = Plr(target)->pBank;
      Plr(sock->sHomeThing)->pInfamy   = Plr(target)->pInfamy;
      Plr(sock->sHomeThing)->pAuto     = Plr(target)->pAuto;
      Plr(sock->sHomeThing)->pSockPref = Plr(target)->pSockPref;
      Plr(sock->sHomeThing)->pSystem   = Plr(target)->pSystem;
      THINGFREE(target); /* will stop fighting automaticly */
      BaseControlAlloc(sock->sHomeThing, sock); /* make new return pointer */
      target = sock->sHomeThing;
      dest = NULL;
      if (Plr(target)->pDeathRoom>0)
        dest = WorldOf(Plr(target)->pDeathRoom);
      if (!dest) dest = WorldOf(playerStartRoom);
      ThingTo(sock->sHomeThing, dest);
    } else {
      /* they're dead and not connected sign them off */ 
      /* have to read in clone copy stats, then update playerfile */
      THINGFREE(target); /* not good because they arent saved as having lost all */
    }
  } else {
    /* If we are an out of body player return to home body */
    sock = BaseControlFind(target);
    if (sock) {
      BaseControlFree(target, sock);
      BaseControlAlloc(sock->sHomeThing, sock);
    }
  
    /* If they are a mob and we're a player, gain exp/aura */
    if (target->tType==TTYPE_MOB && thing) {
      /* Gain experience */
      gain = Mob(target)->mTemplate->mExp;
      relLevel = Character(GroupGetHighestLevel(thing))->cLevel - Character(target)->cLevel;
      BOUNDSET(-8, relLevel, 8);
      gain -= ( (gain>>3) * relLevel );
      MINSET(gain, 1);
      if (Character(thing)->cLevel>=LEVEL_GOD-1)
        gain = 0;
      CharGainExpFollow(thing, gain);
      
      if (thing->tType == TTYPE_PLR) {
        /* Affect Aura */
        gain = abs(Character(target)->cAura);
        gain = gain / MAXV(50, abs(Character(thing)->cAura));
        if (Character(target)->cAura > 0) {
          Character(thing)->cAura -= gain;
        } else {
          Character(thing)->cAura += gain;
        }
        Character(thing)->cAura = BOUNDV(-1000, Character(thing)->cAura, 1000);
      }
    }
    /* execute afterdeath code scripts (if any) */
    code = Base(target)->bInside;
    CodeParseAfterDeath(thing, code, target);
    for (code=code->tContain; code; code=code->tNext)
      CodeParseAfterDeath(thing, code, target); 
    if (thing && Base(thing)->bInside->tType == TTYPE_WLD)
      CodeParseAfterDeath(thing, &areaList[Wld(Base(thing)->bInside)->wArea].aResetThing, target);

    /* Remove the target from play */
    ThingFree(target); /* will stop fighting automaticly */
  }

  /* Look around */
  if (sock) CmdLook(sock->sHomeThing, "");

  /* autoloot corpse if we are set to do so */
  if (thing && thing->tType==TTYPE_PLR && BIT(Plr(thing)->pAuto, PA_AUTOLOOT))
    CmdGet(thing, "get all remnants");
}

/* Nothing should ever delete items from this fightIndex except for us */
void FightIdle(void) {
  LWORD  block;
  LWORD  i;
  THING *thing;

  /* Check for fighting */
  for (i=0; i<fightIndex.iNum; i++) {
    /* attack opponent */
    if (fightIndex.iThing[i]) {
      /* check for an attached script */
      block=0;
      if (BIT(fightIndex.iThing[i]->tFlag,TF_FIGHTINGCODE))
        block=CodeParseFighting(Character(fightIndex.iThing[i])->cFight,fightIndex.iThing[i]);
      if (!block)
        FightAttack(fightIndex.iThing[i]);
    }
  }

  /* Check for fleeing */
  for (i=0; i<fightIndex.iNum; i++) {
    thing = fightIndex.iThing[i];
    if (thing) {
      if ( (thing->tType==TTYPE_PLR&&BIT(Plr(thing)->pAuto, PA_AUTOFLEE))
         ||(thing->tType==TTYPE_MOB&&BIT(Mob(thing)->mTemplate->mAct, MACT_WIMPY))) {
        if (Character(thing)->cHitP < CharGetHitPMax(thing)/20) {
          CmdFlee(thing, "");
        }
      }
    }
  }
  
  /* turf NULL entries from fightIndex 
   * Note the funny loop which occurs because indexdelete will swap in a fresh
   * entry into the same numeric position as the deleted entry when it is called
   * against an unsorted index
   */
  i=0;
  thing = (void*)0xffffffff;
  if (fightIndex.iNum==0) return;
  while(1) {
    if (thing == fightIndex.iThing[i]) i++;
    if (i>=fightIndex.iNum) break;
    thing = fightIndex.iThing[i];
    if (!thing)
      IndexDelete(&fightIndex, NULL, NULL);
  }

}

/* can the target hit us back */
LWORD FightCanHitBack(THING *thing, THING *target, EXIT *exit, EXIT **reverse) {
  LWORD weaponRange = 0;
  LWORD range = 1; /* 1 is inside the same room,
                      thus a weapon with a range
                      of 0 isnt very usefull */

  if (Base(thing)->bInside == Base(target)->bInside)
    return range;
  if (!exit)
    return 0;

  if (Character(target)->cWeapon)
    OBJECTGETFIELD(Character(target)->cWeapon, OF_WEAPON_RANGE);
  else if (target->tType==TTYPE_MOB)
    weaponRange = PropertyGetLWord(target, "%FightRange", 1);

  while (exit) {
    range++;
    if (range > weaponRange)
      break;
    *reverse = ExitReverse(Base(thing)->bInside, exit);
    if (!*reverse)
      return range;
    if (exit->eWorld == Base(target)->bInside)
      return range;
    exit = ExitDir(Wld(exit->eWorld)->wExit, exit->eDir);
  }
  return 0;
}

void FightStart(THING *thing, THING *target, BYTE range, EXIT *exit) {
  EXIT  *reverse;
  THING *i;
  BYTE   rescueBlock;

  if (!thing || !target)
    return;

  if (thing->tType<TTYPE_CHARACTER || target->tType<TTYPE_CHARACTER)
    return;

  /* no point in attacking ourselves */
  if (thing == target) {
    return;
  }

  /* stop following the thing we just attacked */
  if (Character(thing)->cLead == target)
    CharRemoveFollow(thing);

  /* If we are allready fighting dont re-add to index */
  if (!Character(thing)->cFight)
    IndexAppend(&fightIndex, thing);

  BITSET(Character(thing)->cAffectFlag, AF_FIGHTSTART);
  Character(thing)->cFight = target;
  Character(thing)->cFightExit = exit;
  Character(thing)->cFightRange = range;
  Character(thing)->cPos = POS_FIGHTING;

  /* Have victim attack back */
  if (!Character(target)->cFight 
      && FightCanHitBack(thing, target, exit, &reverse)) {
    Character(target)->cFight = thing;
    Character(target)->cFightExit = reverse;
    Character(target)->cFightRange = range;
    Character(target)->cPos = POS_FIGHTING;
    IndexAppend(&fightIndex, target);
    
    /* if the target has friends that will autoassist, have them join in */
    /*if (BIT(Character(target)->cAffectFlag, AF_GROUP)) {*/
    if (Character(target)->cLead) {
      for (i = GroupGetHighestLeader(target); i; i = Character(i)->cFollow) {
        /* if we are not allready fighting someone else & are nearby, possibly join in */
        if (!Character(i)->cFight && Base(i)->bInside==Base(target)->bInside) {
          /* Players AutoAssist as preferred */
          if (i->tType==TTYPE_PLR && BIT(Plr(i)->pAuto, PA_AUTOASSIST)) {
            FightStart(i, thing, range, reverse);

          /* Mobs allways assist */
          /* } else if (i->tType == TTYPE_MOB && BIT(Character(i)->cAffectFlag, AF_GROUP)) {*/
          } else if (i->tType == TTYPE_MOB && GroupIsGroupedMember(i,target)) {
            FightStart(i, thing, range, reverse);
          }
        }
      }
    }
   
  }

  /* if we have friends that will autoassist, have them join in */
  /*if (BIT(Character(thing)->cAffectFlag, AF_GROUP)) {*/
  if (Character(thing)->cLead) {
    for (i = GroupGetHighestLeader(thing); i; i = Character(i)->cFollow) {
      /* if we are not allready fighting someone else & are nearby, possibly join in */
      if (!Character(i)->cFight && Base(i)->bInside==Base(thing)->bInside) {
        /* Players AutoAssist as preferred */
        if (i->tType==TTYPE_PLR && BIT(Plr(i)->pAuto, PA_AUTOASSIST)) {
          FightStart(i, target, range, reverse);

        /* Mobs allways assist */
        } else if (i->tType == TTYPE_MOB && GroupIsGroupedMember(i,thing)) {
          FightStart(i, target, range, reverse);
        }
      }
    }
  }

  /* If your targets friends are really nice they will Autorescue */
  rescueBlock = FALSE;
  if ((target->tType==TTYPE_PLR && !BIT(Plr(target)->pAuto, PA_AUTORESCUE))
    ||(target->tType==TTYPE_MOB && !BIT(Mob(target)->mTemplate->mAct, MACT_RESCUE)))
  {
    /*if (BIT(Character(target)->cAffectFlag, AF_GROUP)) {*/
    if (Character(target)->cLead) {
      for (i = GroupGetHighestLeader(target); i; i = Character(i)->cFollow) {
        if (!rescueBlock && Base(i)->bInside==Base(target)->bInside) {
          /* Autorescue as preferred */
          if (i->tType==TTYPE_PLR && BIT(Plr(i)->pAuto, PA_AUTORESCUE)) {
            rescueBlock = CbtRescue(i, target);
          } else if (i->tType==TTYPE_MOB && BIT(Mob(i)->mTemplate->mAct, MACT_RESCUE) && GroupIsGroupedMember(i,target)) {
            rescueBlock = CbtRescue(i, target);
          }
        }
      }
    }
  }

}

/* 
 * Cant delete here just change "deleted" pointers to NULL
 * let FightIdle delete it so that we dont accidentally 
 * delete when its parsing through the loop
 */
void FightStop(THING *thing) {
  LWORD  i;

  if (!thing) return;
  for (i=0; i<fightIndex.iNum; i++) {
    if ( (fightIndex.iThing[i] == thing)
       ||(fightIndex.iThing[i] && Character(fightIndex.iThing[i])->cFight == thing)
    ) {
      Character(fightIndex.iThing[i])->cFight = NULL;
      Character(fightIndex.iThing[i])->cFightExit = NULL;
      Character(fightIndex.iThing[i])->cFightRange = 0;
      Character(fightIndex.iThing[i])->cPos = POS_STANDING;
      BITCLR(Character(fightIndex.iThing[i])->cAffectFlag, AF_FIGHTSTART);
      fightIndex.iThing[i] = NULL;
    }
  }
}