/* monster.c */

#include <sys.h>
#include <weapon.h>
#include <armor.h>
#include <combat_def.h>
#include <flags.h>

#define TP(X) tell_player(this_object(),(X))
#define TPE(X) tell_room_except(location(this_object()),this_object(),(X))
#define TPE2(X) tell_room_except2(location(this_object()),this_object(), \
                                  is_attacking,(X))

object me;
object is_attacking;
string name,short,long;
int alive,hp,max_hp,xp,level,flags,special,hit_bonus,dam_num_die,dam_die_size,
    dam_bonus,hitreduce,damreduce,armor_hit,armor_dam,is_healing;
object weapon,possessor,last_location,owner;
string msg_array1[MAX_INTENSITY];
string msg_array2[MAX_INTENSITY];
string msg_array3[MAX_INTENSITY];
string msg_array4[MAX_INTENSITY];
string msg_array5[MAX_INTENSITY];
object armor_worn[NUM_PROT];

self_destruct() { destruct(this_object()); return 1; }

static autoheal() {
  hp+=HEAL_AMOUNT;
  if (hp>=max_hp) {
    is_healing=0;
    hp=max_hp;
  } else
    alarm(HEAL_DELAY,"autoheal");
}

set_hitreduce(arg) { hitreduce=arg; return 1; }
set_damreduce(arg) { damreduce=arg; return 1; }
get_hitreduce() { return hitreduce+armor_hit; }
get_damreduce() { return damreduce+armor_dam; }

replicate() {
  object o;
  int loop;

  o=clone_object("/obj/monster");
  call_other(o,"set_name",get_name());
  call_other(o,"set_short",short);
  call_other(o,"set_long",long);
  call_other(o,"set_special",special);
  call_other(o,"set_flags",flags);
  call_other(o,"set_owner",owner);
  call_other(o,"set_level",level);
  call_other(o,"set_damage_bonus",dam_num_die,dam_die_size,dam_bonus);
  call_other(o,"set_hit_bonus",hit_bonus);
  call_other(o,"set_hp",max_hp);
  call_other(o,"set_xp",xp);
  call_other(o,"set_hitreduce",hitreduce);
  call_other(o,"set_damreduce",damreduce);
  loop=0;
  while (loop<MAX_INTENSITY) {
    call_other(o,"set_hit_msg",loop,msg_array1[loop],msg_array2[loop],
               msg_array3[loop],msg_array4[loop],msg_array5[loop]);
    loop++;
  }
  move_object(o,location(this_object()));
  return o;
}

static do_exits(arg) {
  object curr;
  int count,pos;
  string ename;

  if (location(this_object())) curr=contents(location(this_object()));
  while (curr) {
    if (call_other(curr,"get_type")==TYPE_EXIT)
      if (!(call_other(curr,"get_flags") & F_DARK)) {
        if (!count) write("Obvious exits:");
        ename=call_other(curr,"get_name");
        pos=instr(ename,1,";");
        if (pos)
          write(" "+leftstr(ename,pos-1));
        else
          write(" "+ename);
        count++;
      }
    curr=next_object(curr);
  }
  if (!count)
    write("There are no obvious exits.\n");
  else
    write("\n");
  return 1;
}

static do_wear(arg) {
  string w_name;

  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,this_object());
  if (!arg) {
    write("You don't have that.\n");
    return 1;
  }
  if (call_other(arg,"get_type")!=TYPE_ARMOR) {
    write("You can't wear that.\n");
    return 1;
  }
  if (armor_worn[call_other(arg,"get_protection")]) {
    write("You're already wearing something on your "+
          make_prot(call_other(arg,"get_protection"))+".\n");
    return 1;
  }
  w_name=call_other(arg,"get_short");
  if (!call_other(arg,"wear")) {
    write("You can't wear that.\n");
    return 1;
  }
  write("You wear "+w_name+".\n");
  TPE(get_short()+" wears "+w_name+".\n");
  armor_worn[call_other(arg,"get_protection")]=arg;
  recalc_armor();
  return 1;
}

recalc_armor() {
  int loop;

  armor_hit=0;
  armor_dam=0;
  loop=0;
  while (loop<NUM_PROT) {
    if (armor_worn[loop]) {
      armor_hit+=call_other(armor_worn[loop],"get_hitreduce");
      armor_dam+=call_other(armor_worn[loop],"get_damreduce");
    }
    loop++;
  }
}

force_unwear(p) {
  if (armor_worn[p]!=caller_object()) return;
  armor_worn[p]=0;
  recalc_armor();
}

static do_unwear(arg) {
  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,this_object());
  if (call_other(arg,"get_type")!=TYPE_ARMOR) {
    write("You're not wearing that.\n");
    return 1;
  }
  if (armor_worn[call_other(arg,"get_protection")]!=arg) {
    write("You're not wearing that.\n");
    return 1;
  }
  if (call_other(arg,"unwear")) {
    write("You can't unwear that.\n");
    return 1;
  }
  armor_worn[call_other(arg,"get_protection")]=0;
  recalc_armor();
  write("You unwear "+call_other(arg,"get_short")+".\n");
  TPE(get_short()+" unwears "+call_other(arg,"get_short")+".\n");
  return 1;
}

set_owner(x) { owner=x; return 1; }
get_owner() { return owner; }
set_hp(x) { hp=x; max_hp=x; return 1; }
set_xp(x) { xp=x; return 1; }
set_level(x) {
  int loop;

  if (x<1) x=1;
  if (x>15) x=15;
  loop=x;
  xp=1;
  while (--loop) {
    xp*=2;
  }
  xp*=5;
  level=x;
  hitreduce=2*level-2;
  hit_bonus=2*level-2;
  max_hp=10+4*level;
  hp=10+4*level;
  return 1;
}

set_damage_bonus(x,y,z) {
  dam_num_die=x;
  dam_die_size=y;
  dam_bonus=z;
  return 1;
}

set_hit_bonus(x) { hit_bonus=x; return 1; }

get_hp() { return hp; }
get_xp() { return xp; }
get_level() { return level; }

stat() {
  int loop;

  tell_player(this_player(),"Object Type: MONSTER\n");
  if (flags) tell_player(this_player(),"Flags: "+make_flags(flags)+"\n");
  if (special) tell_player(this_player(),"Special: "+itoa(special)+"\n");
  if (name)
    tell_player(this_player(),"Name: "+get_name()+"\n");
  if (short)
    tell_player(this_player(),"Short: "+short+"\n");
  if (long)
    tell_player(this_player(),"Long: "+long+"\n");
  if (alive)
    tell_player(this_player(),"HP: "+itoa(hp)+"/"+itoa(max_hp)+
                "   XP: "+itoa(xp)+"   Level: "+itoa(level)+"\n");
  else
    tell_player(this_player(),"HP: DEAD/"+itoa(max_hp)+"   XP: "+
                itoa(xp)+"   Level: "+itoa(level)+"\n");
  tell_player(this_player(),"HitBonus: "+itoa(hit_bonus)+" (With Weapon: "+
              itoa(hit_bonus+(weapon?call_other(weapon,"get_hit_bonus"):0))+
              ")\n");
  tell_player(this_player(),"Damage: "+itoa(dam_num_die)+"d"+
                            itoa(dam_die_size)+"+"+itoa(dam_bonus)+
              " (With Weapon: "+(weapon?call_other(weapon,"get_damage"):
              (itoa(dam_num_die)+"d"+itoa(dam_die_size)+"+"+itoa(dam_bonus)))
              +")\n");
  tell_player(this_player(),"HitReduce: "+itoa(hitreduce)+
              " (With Armor: "+itoa(get_hitreduce())+")\n");
  tell_player(this_player(),"DamReduce: "+itoa(damreduce)+
              " (With Armor: "+itoa(get_damreduce())+")\n");
  if (weapon)
    tell_player(this_player(),"Wielding: "+make_num(weapon)+"\n");
  loop=0;
  if (is_attacking)
    tell_player(this_player(),"Is Attacking: "+make_num(is_attacking)+"\n");
  if (owner) tell_player(this_player(),"Owner: "+make_num(owner)+"\n");
  if (possessor && priv(this_player()))
    tell_player(this_player(),"Possessed By: "+make_num(possessor)+"\n");
  loop=0;
  while (loop<MAX_INTENSITY) {
    if (msg_array1[loop] || msg_array2[loop] || msg_array3[loop] ||
        msg_array4[loop] || msg_array5[loop])
      tell_player(this_player(),call_other(atoo("/obj/weapon"),
                  "intensity_string",loop)+": "+
                  msg_array1[loop]+"*"+msg_array2[loop]+";"+
                  msg_array3[loop]+";"+
                  msg_array4[loop]+"*"+msg_array5[loop]+"\n");
    loop++;
  }
  return 1;
}

id(arg) { return instr(name,1,";"+arg+";"); }

set_possessor(arg) {
  if (!arg && possessor==caller_object()) {
    possessor=0;
    return 1;
  }
  if (arg!=caller_object()) return 0;
  if (possessor && arg) return 0;
  possessor=arg;
  return 1;
}

force(arg) {
  command(arg);
  return 1;
}

set_name(arg) {
  int pos;

  name=";"+arg+";";
  pos=instr(arg,1,";");
  if (!short)
    if (pos)
      short="a "+leftstr(arg,pos-1);
    else
      short="a "+arg;
  return 1;
}

set_flags(f) { flags=f; return 1; }
set_special(s) { special=s; return 1; }
get_flags() { return flags; }
get_special() { return special; }

do_say(arg) {
  write("You say \""+arg+"\"\n");
  TPE(short+" says \""+arg+"\"\n");
  return 1;
}

do_pose(arg) {
  write(short+" "+arg+"\n");
  TPE(short+" "+arg+"\n");
  return 1;
}

impending_attack() {
  if (is_attacking) return xp;
  is_attacking=caller_object();
  attack(0);
  return xp;
}

get_name() {
  if (name)
    return midstr(name,2,strlen(name)-2);
}

get_hit_msg1(intensity) {
  string s;

  if (weapon) s=call_other(weapon,"get_hit_msg1",intensity);
  else s=msg_array1[intensity];
  if (!s) s=get_def_hit_msg1(intensity);
  return s;
}

get_hit_msg2(intensity) {
  string s;

  if (weapon) s=call_other(weapon,"get_hit_msg2",intensity);
  else s=msg_array2[intensity];
  if (!s) s=get_def_hit_msg2(intensity);
  return s;
}

get_hit_msg3(intensity) {
  string s;

  if (weapon) s=call_other(weapon,"get_hit_msg3",intensity);
  else s=msg_array3[intensity];
  if (!s) s=get_def_hit_msg3(intensity);
  return s;
}

get_hit_msg4(intensity) {
  string s;

  if (weapon) s=call_other(weapon,"get_hit_msg4",intensity);
  else s=msg_array4[intensity];
  if (!s) s=get_def_hit_msg4(intensity);
  return s;
}

set_hit_msg(intensity,msg1,msg2,msg3,msg4,msg5) {
  if (intensity<0 || intensity>=MAX_INTENSITY) return 0;
  msg_array1[intensity]=msg1;
  msg_array2[intensity]=msg2;
  msg_array3[intensity]=msg3;
  msg_array4[intensity]=msg4;
  msg_array5[intensity]=msg5;
  return 1;
}

get_hit_msg5(intensity) {
  string s;

  if (weapon) s=call_other(weapon,"get_hit_msg5",intensity);
  else s=msg_array5[intensity];
  if (!s) s=get_def_hit_msg5(intensity);
  return s;
}

set_long(arg) { long=arg; return 1; }

get_long() {
  if (long) return long;
  else return "Little. Yellow. Different.";
}

move_player(dest,direction) {
  if (location(this_object()))
    TPE(short+" goes "+direction+".\n");
  move_object(this_object(),dest);
  look(0);
  if (dest)
    TPE(short+" has arrived.\n");
  if (flags & F_HOSTILE) attack();
  return 1;
}

look(arg) {
  int contents_printed,curr;
  string oname,t;

  contents_printed=0;
  t=arg;
  if (arg) {
    arg=present(arg,location(this_object()));
    if (!arg) arg=present(t,this_object());
    if (!arg && t=="me") arg=this_object();
    if (!arg && t=="here") arg=location(this_object());
  } else
    if (!location(this_object())) {
      write("You are in the void.\n");
      return 1;
    } else
      arg=location(this_object());
  if (!arg) {
    write("You don't see that here.\n");
    return 1;
  }
  if (arg==location(this_object())) {
    oname=call_other(arg,"get_short");
    if (oname) write(oname+"\n");
  }
  oname=call_other(arg,"get_long");
  if (oname) write(oname+"\n");
  curr=contents(arg);
  if (!(call_other(arg,"get_flags") & F_DARK))
    while (curr) {
      if (curr!=this_object()) {
        oname=call_other(curr,"get_short");
        if (oname) {
          if (!contents_printed) {
            write("Contents:\n");
            contents_printed=1;
          }
          write(oname+"\n");
        }
      }
      curr=next_object(curr);
    }
  return 1;
}

get_type() { return TYPE_MONSTER; }

find_enemy() {
  object curr;

  curr=location(this_object());
  if (!(flags & F_HOSTILE)) {
    is_attacking=0;
    return;
  }
  if (curr) {
    curr=contents(curr);
    while (curr) {
      if (call_other(curr,"is_player") && call_other(curr,"is_living")) {
        is_attacking=curr;
        tell_player(curr,short+" starts attacking you.\n");
        tell_room_except2(location(this_object()),curr,this_object(),
                          short+" starts attacking "+call_other(curr,
                          "get_short")+".\n");
        listen("You start attacking "+call_other(curr,"get_short"));
        return;
      }
      curr=next_object(curr);
    }
  }
  is_attacking=0;
}

is_living() { return alive; }

get_short() { return short; }

set_short(arg) { short=arg; return 1; }

to_hit_bonus() {
  int bonus;

  if (weapon)
    bonus=call_other(weapon,"get_hit_bonus",is_attacking);
  else
    bonus=0;
  return bonus+hit_bonus;
}

damage_bonus() {
  int bonus,loop;

  if (weapon)
    bonus=call_other(weapon,"get_damage_bonus",is_attacking);
  else {
    loop=0;
    bonus=0;
    while (loop++<dam_num_die) bonus+=random(dam_die_size)+1;
    bonus+=dam_bonus;
  }
  return bonus;
}

static do_kill(arg) {
  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,location(this_object()));
  if (!arg) {
    write("You don't see that here.\n");
    return 1;
  }
  if (!call_other(arg,"is_living")) {
     write("You can't kill that, you bonehead.\n");
     return 1;
  }
  if (arg==this_object()) {
    write("PLEASE try to control your suicidal impulses.\n");
    return 1;
  }
  if (arg==is_attacking) {
    write("You're already attacking that. Chill.\n");
    return 1;
  }
  write("You start attacking "+call_other(arg,"get_short")+".\n");
  tell_player(arg,short+" starts attacking you.\n");
  tell_room_except2(location(this_object()),arg,this_object(),short+
                    " starts attacking "+call_other(arg,"get_short")+".\n");
  attack(arg);
  return 1;
}

do_damage(amount) {
  int retval,intensity;
  object curr,next,corpse;

  amount-=get_damreduce();
  if (amount<0) amount=0;
  retval=(amount>hp)?hp:amount;
  hp-=amount;
  if (!is_healing) {
    is_healing=1;
    alarm(HEAL_DELAY,"autoheal");
  }
  if (amount<I_PUNY_AMT)
    intensity=I_PUNY;
  else if (amount<I_WEAK_AMT)
    intensity=I_WEAK;
  else if (amount<I_POOR_AMT)
    intensity=I_POOR;
  else if (amount<I_AVERAGE_AMT)
    intensity=I_AVERAGE;
  else if (amount<I_GOOD_AMT)
    intensity=I_GOOD;
  else if (amount<I_VERYGOOD_AMT)
    intensity=I_VERYGOOD;
  else intensity=I_MASSACRE;
  write(call_other(caller_object(),"get_short")+" "+
        call_other(caller_object(),"get_hit_msg3",intensity)+"\n");
  write("You have "+itoa((hp>0)?hp:0)+" hit points left.\n");
  if (hp<1) {
    write("You have died. That's okay, you're just a monster, anyways.\n");
    corpse=clone_object("/obj/corpse");
    call_other(corpse,"set_name",short);
    move_object(corpse,location(this_object()));
    hp=0;
    alive=0;
    is_attacking=0;
    curr=contents(this_object());
    if (flags & F_PERMANENT) {
      last_location=location(this_object());
      move_object(this_object(),0);
      alarm(REINCARNATE_DELAY,"reincarnate");
      while (curr) {
        next=call_other(curr,"replicate");
        if (next) move_object(next,last_location);
        curr=next_object(curr);
      }
    } else {
      while (curr) {
        next=next_object(curr);
        if (call_other(curr,"drop")) {
          if (!call_other(curr,"dead_drop"))
            move_object(curr,location(this_object()));
        } else
          move_object(curr,location(this_object()));
        curr=next;
      }
      destruct(this_object());
    }
  }
  return retval;
}

static reincarnate() {
   hp=max_hp;
   alive=1;
   move_player(last_location,0);
}

attempt_hit(chance) {
  return chance+50-call_other(caller_object(),"get_hitreduce")>random(100);
}

attack(enemy) {
  int enemy_xp,intensity,dmg,tmp;
  string ename;

  if (enemy==this_object()) return;
  if (is_attacking==this_object()) {
    is_attacking=0;
    return;
  }
  if (!alive) {
    is_attacking=0;
    return;
  }
  if (enemy)
    if (is_attacking) {
      is_attacking=enemy;
      return;
    } else
      is_attacking=enemy;
  if (!is_attacking)
    find_enemy();
  if (!is_attacking) return;
  if (!call_other(is_attacking,"is_living")) find_enemy();
  if (!is_attacking) return;
  if (location(is_attacking)!=location(this_object()))
    find_enemy();
  if (!is_attacking) return;
  ename=call_other(is_attacking,"get_short");
  enemy_xp=call_other(is_attacking,"impending_attack");
  if (!alive) return;
  if (call_other(is_attacking,"attempt_hit",to_hit_bonus())) {
    dmg=damage_bonus();
    tmp=call_other(is_attacking,"do_damage",dmg);
    if (dmg<I_PUNY_AMT) {
      intensity=I_PUNY;
    } else if (dmg<I_WEAK_AMT) {
      intensity=I_WEAK;
    } else if (dmg<I_POOR_AMT) {
      intensity=I_POOR;
    } else if (dmg<I_AVERAGE_AMT) {
      intensity=I_AVERAGE;
    } else if (dmg<I_GOOD_AMT) {
      intensity=I_GOOD;
    } else if (dmg<I_VERYGOOD_AMT) {
      intensity=I_VERYGOOD;
    } else {
      intensity=I_MASSACRE;
    }
    TP(get_hit_msg1(intensity)+ename+get_hit_msg2(intensity)+"\n");
    TPE2(short+" "+get_hit_msg4(intensity)+ename+get_hit_msg5(intensity)+"\n");
    if (!call_other(is_attacking,"is_living")) {
      TP("You have killed "+ename+"!\n");
      TPE2(short+" has killed "+ename+"!\n");
      find_enemy();
    }
  } else {
    TP("You missed "+ename+".\n");
    tell_player(is_attacking,short+" misses you.\n");
    TPE2(short+" misses "+ename+".\n");
  }
  alarm(3,"attack");
}

static do_inventory(arg) {
  int count;
  object curr;
  string oname;

  count=0;
  curr=contents(this_object());
  while (curr) {
    oname=call_other(curr,"get_short");
    if (oname) {
      if (!count) {
        write("You are carrying:\n");
      }
      count++;
      write(oname+"\n");
    }
    curr=next_object(curr);
  }
  if (!count)
    write("You are empty-handed.\n");
  return 1;
}

static do_score(arg) {
  if (alive) {
    write("HP: "+itoa(hp)+"/"+itoa(max_hp)+"       Level: "+itoa(level)+
          "       XP: "+itoa(xp)+"\n");
  } else
    write("You are dead. This implies your score kinda SUCKS.\n");
  return 1;
}

static do_huh(arg) {
  write(HUH_STRING);
}

static do_get(arg) {
  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,location(this_object()));
  if (!arg) {
    write("You don't see that here.\n");
    return 1;
  }
  if (!call_other(arg,"get")) {
    write("You can't get "+call_other(arg,"get_short")+".\n");
    return 1;
  }
  move_object(arg,this_object());
  write("You get "+call_other(arg,"get_short")+".\n");
  TPE(short+" gets "+call_other(arg,"get_short")+"\n");
  move_object(arg,this_object());
  return 1;
}

static do_drop(arg) {
  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,(this_object()));
  if (!arg) {
    write("You don't have that.\n");
    return 1;
  }
  if (call_other(arg,"drop")) {
    write("You can't drop "+call_other(arg,"get_short")+".\n");
    return 1;
  }
  write("You drop "+call_other(arg,"get_short")+".\n");
  TPE(short+" drops "+call_other(arg,"get_short")+"\n");
  move_object(arg,location(this_object()));
  return 1;
}

static do_wield(arg) {
  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,this_object());
  if (!arg) {
    write("You don't have that.\n");
    return 1;
  }
  if (weapon) {
    write("You are already wielding a weapon.\n");
    return 1;
  }
  if (!call_other(arg,"wield")) {
    write("You can't wield that.\n");
    return 1;
  }
  write("You wield "+call_other(arg,"get_short")+".\n");
  TPE(short+" wields "+call_other(arg,"get_short")+".\n");
  weapon=arg;
  return 1;
}

force_unwield() {
  if (weapon!=caller_object()) return;
  weapon=0;
}

static do_unwield(arg) {
  if (!alive) {
    write("You're too dead.\n");
    return 1;
  }
  arg=present(arg,this_object());
  if (arg!=weapon) {
    write("You aren't wielding that.\n");
    return 1;
  }
  if (call_other(arg,"unwield")) {
    write("You can't unwield "+call_other(arg,"get_short")+".\n");
    return 1;
  }
  weapon=0;
  write("You unwield "+call_other(arg,"get_short")+".\n");
  TPE(short+" unwields "+call_other(arg,"get_short")+".\n");
  return 1;
}

static init() {
  alive=1;
  set_interactive();
  max_hp=14;
  hp=max_hp;
  xp=5;
  level=1;
  dam_num_die=1;
  dam_die_size=2;
  me=this_object();
  if (!prototype(me)) return;
  add_xverb("","do_huh");
  add_verb("say","do_say");
  add_verb("pose","do_pose");
  add_xverb(":","do_pose");
  add_xverb("\"","do_say");
  add_verb("i","do_inventory");
  add_verb("inventory","do_inventory");
  add_verb("l","look");
  add_verb("look","look");
  add_verb("drop","do_drop");
  add_verb("get","do_get");
  add_verb("take","do_take");
  add_verb("wield","do_wield");
  add_verb("unwield","do_unwield");
  add_verb("kill","do_kill");
  add_verb("score","do_score");
  add_verb("exits","do_exits");
  add_verb("wear","do_wear");
  add_verb("unwear","do_unwear");
}

listen(arg) {
  if (!is_attacking && (flags & F_HOSTILE)) attack();
  if (possessor) call_other(possessor,"possession_report",arg);
}