/*
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);
}