untermud/DOC/
untermud/DOC/U/
untermud/DOC/U/U-examples/
untermud/DOC/internals/
untermud/DOC/wizard/
untermud/MISC/
untermud/MISC/dbchk/
untermud/RWHO/
untermud/RWHO/rwhod/
/*
    Copyright (C)1991, Marcus J. Ranum. All rights reserved.
*/

#include    "config.h"
#include    "mud.h"
#include    "vars.h"
#include    "sbuf.h"
#include    "match.h"
#include    "combat.h"

#ifndef COMBAT
edit combat.o out of the makefile, you klunch.
#endif
static char *stat2att (char *nam, int m);
static char *att2stat (char *nam, int m);
static void update_time (char *u, time_t * tim);
static time_t get_time (char *u);
static void regenerate (char *u, time_t elapsed, int nowval, int maxval,
			char *nowatt);
static void update_stats (char *u);
static int do_attack (char *att, char *def, char *attrib, int arisk, int abet,
		      int dhas, int ahas, int wmod, int amod, char *smes,
		      char *fmes);
static int modify_weapon (char *who, char *att);
static int modify_armor (char *who, char *att);
static int stop_wielding (char *who, char *ud);

static struct smap
{
  char *lnam;
  char *mval;
  char *cval;
} map[] =
{
  {
  "strength", var_Strength, var_strength},
  {
  "endurance", var_Endurance, var_endurance},
  {
  "willpower", var_Willpower, var_willpower},
  {
  "agility", var_Agility, var_agility},
  {
  "magic", var_Magic, var_magic},
  {
  "action", var_Action, var_action},
  {
  0, 0, 0}
};



/* map stat name to attribute name */
static char *
stat2att (char *nam, int m)
{
  struct smap *mp;
  size_t len = strlen (nam);

  for (mp = map; mp->lnam != (char *) 0; mp++)
    if (!strncmp (nam, mp->lnam, len))
      return (m ? mp->mval : mp->cval);
  return NULL;
}



/* map attribute name to stat name */
static char *
att2stat (char *nam, int m)
{
  struct smap *mp;

  for (mp = map; mp->lnam != (char *) 0; mp++)
    {
      if (m && !strcmp (nam, mp->mval))
	return (mp->lnam);
      if (!m && !strcmp (nam, mp->cval))
	return (mp->lnam);
    }
  return ((char *) 0);
}



/* this is a function just to ease porting if needed */
static void
update_time (u, tim)
     char *u;
     time_t *tim;
{
  ut_setnum (u, u, var_lastupd, *tim);
}



static time_t
get_time (u)
     char *u;
{
  char *lupt;

  lupt = ut_getatt (u, 0, typ_int, var_lastupd, (char *) 0);
  if (lupt == (char *) 0)
    return ((time_t) - 1);
  return ((time_t) atol (lupt));
}



static void
regenerate (char *u, time_t elapsed, int nowval, int maxval, char *nowatt)
{
  long i;
  int d;
  int mc = maxval * 100;
  int nc = nowval * 100;

  /*
     basically what we do here is a quick form of compounding
     interest, with values shifted to make them slip into a pleasant
     bracket with respect to rounding errors in integer division.
   */
  for (i = elapsed / TIMEUNIT; i > 0; i--)
    {
      d = ((nc * PERCENT) / 100) > 1 ? ((nc * PERCENT) / 100) : 10;
      if (nc >= mc - d)
	{
	  nc = mc;
	  break;
	}
      nc += d;
    }
  nowval = (nc / 100);
  (void) ut_setnum (u, u, nowatt, nowval);
}




static void
update_stats (char *u)
{
  time_t now;
  time_t then;
  time_t delta;
  int pow;
  int nowstr;
  int maxstr;
  int nowend;
  int maxend;
  int nowwil;
  int maxwil;
  int nowagi;
  int maxagi;
  int nowmag;
  int maxmag;
  int nowact;
  int maxact;

  (void) time (&now);
  if ((then = get_time (u)) == (time_t) - 1)
    update_time (u, &now);

  /* noncombatant or recently updated */
  if (ut_getnum (u, var_power, &pow) < 0 || now - then < UPDATE_QUANT)
    return;
  delta = now - then;

  /* if these values aren't set for some reason, they default zero */
  (void) ut_getnum (u, var_strength, &nowstr);
  (void) ut_getnum (u, var_Strength, &maxstr);
  (void) ut_getnum (u, var_endurance, &nowend);
  (void) ut_getnum (u, var_Endurance, &maxend);
  (void) ut_getnum (u, var_willpower, &nowwil);
  (void) ut_getnum (u, var_Willpower, &maxwil);
  (void) ut_getnum (u, var_agility, &nowagi);
  (void) ut_getnum (u, var_Agility, &maxagi);
  (void) ut_getnum (u, var_magic, &nowmag);
  (void) ut_getnum (u, var_Magic, &maxmag);
  (void) ut_getnum (u, var_action, &nowact);
  (void) ut_getnum (u, var_Action, &maxact);

  regenerate (u, delta, nowstr, maxstr, var_strength);
  regenerate (u, delta, nowend, maxend, var_endurance);
  regenerate (u, delta, nowwil, maxwil, var_willpower);
  regenerate (u, delta, nowagi, maxagi, var_agility);
  regenerate (u, delta, nowmag, maxmag, var_magic);

  /* give more action points - by scaling delta */
  regenerate (u, delta * ACTIONSCALE, nowact, maxact, var_action);
  update_time (u, &now);

  /* if the guy's no longer incapacitated, unmark him */
  if (nowstr && nowend && nowwil && nowagi && nowmag)
    ut_unset (u, u, var_isdead);
}



int
cmd__combat (int ac, char *av[], char *who, char *aswho)
{
  if (ac < 2)
    {
      say (who, "please provide an option (try ", av[0], " help).\n",
	   (char *) 0);
      return (UERR_ARGCNT);
    }

  /* update a player's stats */
  if (!strncmp (av[1], "update", strlen (av[1])))
    {
      update_stats (who);
      return (UERR_NONE);
    }


  /* register a player as a combatant - loads of checks... */
  if (!strcmp (av[1], "register"))
    {
      int pow;
      if (ut_getnum (who, var_power, &pow) == 0)
	{
	  say (who, "You are already registered as a combatant.\n",
	       (char *) 0);
	  return (UERR_PERM);
	}
      if (strcmp (who, aswho))
	{
	  say (who, "You cannot register another object.\n", (char *) 0);
	  return (UERR_PERM);
	}
      if (!ut_flagged (who, var_isplay))
	{
	  say (who, "Only player objects may fight.\n", (char *) 0);
	  return (UERR_PERM);
	}
      if (ut_setnum (who, aswho, var_power, INITIAL_POWER))
	{
	  say (who, "Cannot register combatant status.\n", (char *) 0);
	  return (UERR_FATAL);
	}
      update_stats (who);
      say (who, "Registered. You may now allocate power.\n", (char *) 0);
      return (UERR_NONE);
    }

  /* allocate power points. */
  if (!strcmp (av[1], "allocate"))
    {
      int pow;
      int pts;
      int aval;
      char *aptr;

      if (ac != 4)
	{
	  say (who, "usage: allocate attribute points\n", (char *) 0);
	  return (UERR_BADPARM);
	}
      if (strcmp (who, aswho))
	{
	  say (who, "You cannot allocate for another object.\n", (char *) 0);
	  return (UERR_PERM);
	}
      if (ut_getnum (who, var_power, &pow) < 0)
	{
	  say (who, "You must first register as a combatant.\n", (char *) 0);
	  return (UERR_PERM);
	}
      if ((pts = atoi (av[3])) <= 0 || pts > INITIAL_POWER)
	{
	  say (who, "Cannot allocate ", av[3], " points.\n", (char *) 0);
	  return (UERR_BADPARM);
	}
      if (pts > pow)
	{
	  say (who, "You don't have enough points.\n", (char *) 0);
	  return (UERR_BADPARM);
	}


      if ((aptr = stat2att (av[2], 1)) == (char *) 0)
	{
	  say (who, "Unknown combat attribute: ", av[2], ".\n", (char *) 0);
	  return (UERR_BADPARM);
	}

      /* is gut. */
      if (ut_getnum (who, aptr, &aval) < 0)
	aval = 0;
      aval += pts;
      if (ut_setnum (who, aswho, aptr, aval))
	{
	  say (who, "Cannot set attribute (internal error)\n", (char *) 0);
	  return (UERR_FATAL);
	}
      pow -= pts;
      /* f***it - if this doesn't work they get free points */
      (void) ut_setnum (who, aswho, var_power, pow);
      say (who, "Allocated points to ", aptr, ".\n", (char *) 0);

      /* give the guy some points */
      if ((aptr = stat2att (av[2], 0)) == (char *) 0)
	return (UERR_NONE);
      if (ut_getnum (who, aptr, &pow) < 0)
	pow = 0;
      (void) ut_setnum (who, aswho, aptr, (aval + pow) / 2);
      return (UERR_NONE);
    }

  /* stats */
  if (!strcmp (av[1], "stats"))
    {
      int nowstr;
      int maxstr;
      int nowend;
      int maxend;
      int nowwil;
      int maxwil;
      int nowagi;
      int maxagi;
      int nowmag;
      int maxmag;
      int nowact;
      int maxact;
      char xbuf[MAXOID];

      update_stats (who);
      (void) ut_getnum (who, var_strength, &nowstr);
      (void) ut_getnum (who, var_Strength, &maxstr);
      (void) ut_getnum (who, var_endurance, &nowend);
      (void) ut_getnum (who, var_Endurance, &maxend);
      (void) ut_getnum (who, var_willpower, &nowwil);
      (void) ut_getnum (who, var_Willpower, &maxwil);
      (void) ut_getnum (who, var_agility, &nowagi);
      (void) ut_getnum (who, var_Agility, &maxagi);
      (void) ut_getnum (who, var_magic, &nowmag);
      (void) ut_getnum (who, var_Magic, &maxmag);
      (void) ut_getnum (who, var_action, &nowact);
      (void) ut_getnum (who, var_Action, &maxact);
      say (who, "Str:", itoa (nowstr, xbuf, 10), (char *) 0);
      say (who, "/", itoa (maxstr, xbuf, 10), "  ", (char *) 0);
      say (who, "End:", itoa (nowend, xbuf, 10), (char *) 0);
      say (who, "/", itoa (maxend, xbuf, 10), "  ", (char *) 0);
      say (who, "Will:", itoa (nowwil, xbuf, 10), (char *) 0);
      say (who, "/", itoa (maxwil, xbuf, 10), "  ", (char *) 0);
      say (who, "Agil:", itoa (nowagi, xbuf, 10), (char *) 0);
      say (who, "/", itoa (maxagi, xbuf, 10), "  ", (char *) 0);
      say (who, "Magic:", itoa (nowmag, xbuf, 10), (char *) 0);
      say (who, "/", itoa (maxmag, xbuf, 10), "  ", (char *) 0);
      say (who, "Action Points:", itoa (nowact, xbuf, 10), (char *) 0);
      say (who, "/", itoa (maxact, xbuf, 10), "\n", (char *) 0);
      return (UERR_NONE);
    }

  /* help */
  if (!strcmp (av[1], "help"))
    {
      say (who, av[0], " update\n", (char *) 0);
      say (who, av[0], " register\n", (char *) 0);
      say (who, av[0], " stats\n", (char *) 0);
      say (who, av[0], " allocate combat-attribute #points\n", (char *) 0);
      return (UERR_NONE);
    }
  say (who, av[0], " Unknown option (try \"help\")\n", (char *) 0);
  return (UERR_BADPARM);
}




static int
do_attack (char *att, char *def, char *attrib, int arisk, int abet, int dhas,
	   int ahas, int wmod, int amod, char *smes, char *fmes)
{
  int p;
  float risk;
  int rdam;
  char xuf[64];

  risk = arisk + wmod;
  p = (int) ((risk * risk * 1000.0) /
	     ((risk * risk * 10.0) + ((float) abet * (float) abet * 14.0)));
#ifdef  COMBAT_DEBUG
  printf ("prob of %s hitting %s %s: %d%\n", att, def, attrib, p);
#endif

  /* a HIT ! */
  if (p > get_random (100))
    {
      if (abet <= 0)
	{
	  ut_roombcast (ut_loc (att), (char *) 0, ut_name (att),
			" launches a futile attack at ", ut_name (def), "\n",
			(char *) 0);
	  return (0);
	}

      rdam = abet - amod;
      if (rdam <= 0)
	{
	  ut_roombcast (ut_loc (att), (char *) 0, ut_name (att),
			"'s attack glances off ", ut_name (def),
			"'s armor!\n", (char *) 0);
	  return (0);
	}

      if (smes != (char *) 0)
	ut_roombcast (ut_loc (att), (char *) 0, ut_name (att),
		      " ", smes, "\n", (char *) 0);

      /* a kill */
      if (dhas - rdam <= 0)
	{
	  ut_setnum (def, def, attrib, 0);
	  ut_roombcast (ut_loc (att), (char *) 0, ut_name (att),
			" killed ", ut_name (def), "!!\n", (char *) 0);
	  ut_home_player (def, def, ut_loc (att));
	  ut_set (def, def, typ_flag, var_isdead, "");
	  return (1);
	}
      say (att, "You hit ", ut_name (def), " for ", itoa (rdam, xuf, 10),
	   " point", rdam > 1 ? "s" : "", " of ",
	   att2stat (attrib, 0), "!\n", (char *) 0);
      say (def, ut_name (att), " hit you, for ", xuf, " point",
	   rdam > 1 ? "s" : "", " of ", att2stat (attrib, 0), "!\n",
	   (char *) 0);
      ut_setnum (def, def, attrib, dhas - rdam);
      return (0);
    }

  /* failure message */
  if (fmes != (char *) 0)
    ut_roombcast (ut_loc (att), (char *) 0, ut_name (att), " ",
		  fmes, "\n", (char *) 0);

  /* did the attacker wipe himself? */
  if (ahas - arisk <= 0)
    {
      ut_setnum (att, att, attrib, 0);
      ut_roombcast (ut_loc (att), (char *) 0, ut_name (def),
		    " killed ", ut_name (att), "!!\n", (char *) 0);
      ut_home_player (att, att, ut_loc (def));
      ut_set (att, def, typ_flag, var_isdead, "");
      return (1);
    }

  /* hurt him */
  ut_setnum (att, att, attrib, ahas - arisk);
  say (att, "You missed ", ut_name (def), " and lose ", itoa (arisk, xuf, 10),
       " point", arisk > 1 ? "s" : "", " of ",
       att2stat (attrib, 0), "!\n", (char *) 0);
  say (def, ut_name (att), " missed you, and lost ", xuf, " point",
       arisk > 1 ? "s" : "", " of ", att2stat (attrib, 0), "!\n", (char *) 0);
  return (0);
}



/* generate weapon modifier */
static int
modify_weapon (char *who, char *att)
{
  char *weap;
  char *ap;

  if ((weap =
       ut_getatt (who, 0, typ_obj, var_weapon, (char *) 0)) == (char *) 0)
    return (0);
  if ((ap = ut_getatt (weap, 0, typ_int, att, (char *) 0)) == (char *) 0)
    return (0);
#ifdef  COMBAT_DEBUG
  printf ("%s weapon modifier: %d%\n", who, atoi (ap));
#endif
  return (atoi (ap));
}




/* generate armor modifier */
static int
modify_armor (char *who, char *att)
{
  char *lp;
  char arm[MAXOID];
  char *ap;
  int ret = 0;

  if ((lp =
       ut_getatt (who, 0, typ_list, var_wearing, (char *) 0)) == (char *) 0)
    return (0);
  while ((lp = lstnext (lp, arm, sizeof (arm))) != (char *) 0)
    {
      if (!ut_flagged (arm, var_isarmor))
	continue;
      if ((ap = ut_getatt (arm, 0, typ_int, att, (char *) 0)) != (char *) 0)
	ret += atoi (ap);
    }
#ifdef  COMBAT_DEBUG
  printf ("%s total armor modifiers: %d%\n", who, ret);
#endif
  return (ret);
}



/* basic combat driver */
int
cmd__attack (int ac, char *av[], char *who, char *aswho)
{
  char vict[MAXOID];
  char *aptr;
  int abet;
  int arisk;
  char *smes = (char *) 0;
  char *fmes = (char *) 0;
  int aval;
  int dval;
  int wmod = 0;
  int amod = 0;
  char *cp;
  int c;
  char cbuf[512];
  char *cav[12];
  Sbuf suf;

  if (matchplayers (who, av[1], ut_loc (who), MTCH_UNIQ | MTCH_MEOK, vict))
    return (UERR_NOMATCH);

  if (!strcmp (who, vict))
    {
      say (who, "Don't kill yourself over a game.\n", (char *) 0);
      return (UERR_PERM);
    }

  if (strcmp (who, aswho))
    {
      char *weap;
      weap = ut_getatt (who, 0, typ_obj, var_weapon, (char *) 0);
      if (weap == (char *) 0 || strcmp (aswho, weap))
	{
	  say (who, "Only players and weapons can attack.\n", (char *) 0);
	  return (UERR_PERM);
	}
    }

  if (ut_getnum (who, var_power, &aval) < 0)
    {
      say (who, "Sorry. You're a noncombatant.\n", (char *) 0);
      return (UERR_PERM);
    }

  if (ut_getnum (vict, var_power, &aval) < 0)
    {
      say (who, "Sorry. ", ut_name (vict), " is a noncombatant.\n",
	   (char *) 0);
      return (UERR_PERM);
    }

  if ((aptr = stat2att (av[2], 0)) == (char *) 0)
    {
      say (who, "Unknown combat attribute: ", av[2], ".\n", (char *) 0);
      return (UERR_BADPARM);
    }

  if ((abet = atoi (av[3])) <= 0)
    {
      say (who, "Cannot attack ", av[3], " points.\n", (char *) 0);
      return (UERR_BADPARM);
    }
  if ((arisk = atoi (av[4])) <= 0)
    {
      say (who, "Cannot gamble ", av[3], " points in attack.\n", (char *) 0);
      return (UERR_BADPARM);
    }

  /* messages? */
  if (ac > 5)
    smes = av[5];
  if (ac > 6)
    fmes = av[6];

  /* see if the guy has the cojones to attack with */
  update_stats (who);

  if (ut_flagged (who, var_isdead))
    {
      say (who, "You're too wounded to fight!\n", (char *) 0);
      return (UERR_PERM);
    }

  /* now see if they have the wherewithal to attack */
  if (ut_getnum (who, aptr, &aval) < 0 || aval == 0)
    {
      say (who, "You have no points with which to attack.\n", (char *) 0);
      return (UERR_BADPARM);
    }

  /* now charge the guy action points */
  if (ut_getnum (who, var_action, &dval) < 0 || dval < ATTACK_COST)
    {
      say (who, "You have insufficient action points.\n", (char *) 0);
      return (UERR_BADPARM);
    }
  dval -= ATTACK_COST;
  ut_setnum (who, who, var_action, dval);


  /* get victim's attribute values */
  update_stats (vict);
  if (ut_getnum (vict, aptr, &dval) < 0)
    dval = 0;

  /* add weapons modifiers for attacker */
  wmod = modify_weapon (who, aptr);
  /* add armor modifiers for defender */
  amod = modify_armor (vict, aptr);

  /* adjust values as best we can */
  if (arisk > aval)
    arisk = aval - 1;
  if (arisk < 0)
    arisk = 0;
  if (abet > dval + amod)
    abet = (dval + amod) > 0 ? (dval + amod) : 1;

  /* set last attacker */
  (void) ut_set (who, vict, typ_obj, var_lastatt, who);

  /* if someone wins in initial attack, stop the action. */
  if (do_attack (who, vict, aptr, arisk, abet, dval, aval, wmod, amod, smes,
		 fmes))
    {
      /* set last killer */
      (void) ut_set (who, vict, typ_obj, var_lastatt, who);
      return (UERR_NONE);
    }

  if (ut_flagged (vict, var_isdead))
    {
      say (vict, "You're too wounded to counterattack!\n", (char *) 0);
      return (UERR_PERM);
    }

  /* initial attack wasn't a knockout, so the victim gets to hit back */
  cp = ut_getatt (vict, 0, typ_list, var_counters, (char *) 0);
  if (cp == (char *) 0 || (c = lstcnt (cp)) == 0)
    return (UERR_NONE);

  /* pick one at random from list of counterattacks */
  c = get_random (c);
  sbuf_initstatic (&suf);
  cp = lstnextsbuf (cp, &suf);
  while (c > 0)
    {
      cp = lstnextsbuf (cp, &suf);
      c--;
    }

  /* tokenize it */
  c =
    enargv (sbuf_buf (&suf), cav, 12, cbuf, (int) sizeof (cbuf), (char **) 0,
	    who, aswho, 0, (char **) 0);
  sbuf_freestatic (&suf);
  if (c < 3)
    {
      say (vict, "Counterattack is missing parameters!\n", (char *) 0);
      return (UERR_NONE);
    }

  /* messages */
  if (c > 3)
    smes = cav[3];
  if (c > 4)
    fmes = cav[4];

  /* lookup aptr */
  if ((aptr = stat2att (cav[0], 0)) == (char *) 0)
    {
      say (vict, "Counterattack attribute ", cav[0], " is unknown!\n",
	   (char *) 0);
      return (UERR_NONE);
    }
  if ((abet = atoi (cav[1])) <= 0)
    {
      say (vict, "Cannot counterattack ", cav[1], " points.\n", (char *) 0);
      return (UERR_BADPARM);
    }
  if ((arisk = atoi (cav[2])) <= 0)
    {
      say (vict, "Cannot gamble ", cav[2], " points in counterattack.\n",
	   (char *) 0);
      return (UERR_BADPARM);
    }
  if (ut_getnum (vict, aptr, &aval) < 0 || aval == 0)
    {
      say (vict, "You have no points with which to counterattack.\n",
	   (char *) 0);
      return (UERR_BADPARM);
    }

  /* now charge the guy action points */
  if (ut_getnum (vict, var_action, &dval) < 0 || dval < ATTACK_COST)
    {
      say (vict, "You have no action points to counterattack.\n", (char *) 0);
      return (UERR_BADPARM);
    }
  dval -= ATTACK_COST;
  ut_setnum (vict, vict, var_action, dval);
  if (ut_getnum (who, aptr, &dval) < 0)
    dval = 0;
  wmod = modify_weapon (vict, aptr);
  amod = modify_armor (who, aptr);
  if (arisk > aval)
    arisk = aval;
  if (abet > dval + amod)
    abet = dval + amod;
  (void) ut_set (vict, who, typ_obj, var_lastatt, vict);
  if (do_attack (vict, who, aptr, arisk, abet, dval, aval, wmod, amod, smes,
		 fmes))
    {
      (void) ut_set (vict, who, typ_obj, var_lastatt, vict);
      return (UERR_NONE);
    }
  return (UERR_NONE);
}



static int
stop_wielding (char *who, char *ud)
{
  if (ut_listadd (who, who, var_cont, ud))
    return (UERR_FATAL);
  if (ut_unset (who, who, var_weapon))
    return (UERR_FATAL);
  say (who, "You are now empty-handed.\n", (char *) 0);
  return (UERR_NONE);
}




/* ARGSUSED */
int
cmd_wield (int ac, char *av[], char *who, char *aswho)
{
  char *ud;

  ud = ut_getatt (who, 0, typ_obj, var_weapon, (char *) 0);
  if (ac <= 1)
    {
      if (ud == (char *) 0)
	{
	  say (who, "You are empty-handed.\n", (char *) 0);
	  return (UERR_NONE);
	}
      return (stop_wielding (who, ud));
    }
  else if (ac == 2)
    {
      char ob[MAXOID];
      int xx;

      if (ud != (char *) 0 && (xx = stop_wielding (who, ud)) != UERR_NONE)
	return (xx);
      if (matchinv (who, av[1], 0, MTCH_UNIQ | MTCH_QUIET, ob))
	return (UERR_NOMATCH);
      if (!ut_flagged (ob, var_isweapon))
	{
	  say (who, ut_name (ob), " is not a weapon.\n", (char *) 0);
	  return (UERR_PERM);
	}
      if (ut_listdel (who, who, var_cont, ob))
	return (UERR_FATAL);
      if (ut_set (who, who, typ_obj, var_weapon, ob))
	return (UERR_FATAL);
      say (who, "You are now wielding ", ut_name (ob), ".\n", (char *) 0);
      return (UERR_NONE);
    }
  say (who, "You can only use one weapon at a time.\n", (char *) 0);
  return (UERR_NONE);
}