/*
Copyright (C)1991, Marcus J. Ranum. All rights reserved.
*/
#ifndef lint
static char RCSid[] = "$Header: /home/mjr/hacks/umud/RCS/combat.c,v 1.10 92/05/17 23:32:37 mjr Exp $";
#endif
#include "config.h"
#ifdef NOSYSTYPES_H
#include <types.h>
#else
#include <sys/types.h>
#endif
#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 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(nam,m)
char *nam;
int m;
{
int len;
struct smap *mp;
len = strlen(nam);
for(mp = map; mp->lnam != (char *)0; mp++)
if(!strncmp(nam,mp->lnam,len))
return(m ? mp->mval : mp->cval);
return((char *)0);
}
/* map attribute name to stat name */
static char *
att2stat(nam,m)
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(u,elapsed,nowval,maxval,nowatt)
char *u;
time_t elapsed;
int nowval;
int maxval;
char *nowatt;
{
int 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(u)
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);
}
cmd__combat(ac,av,who,aswho)
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),(char *)0);
say(who,"/",itoa(maxstr,xbuf)," ",(char *)0);
say(who,"End:",itoa(nowend,xbuf),(char *)0);
say(who,"/",itoa(maxend,xbuf)," ",(char *)0);
say(who,"Will:",itoa(nowwil,xbuf),(char *)0);
say(who,"/",itoa(maxwil,xbuf)," ",(char *)0);
say(who,"Agil:",itoa(nowagi,xbuf),(char *)0);
say(who,"/",itoa(maxagi,xbuf)," ",(char *)0);
say(who,"Magic:",itoa(nowmag,xbuf),(char *)0);
say(who,"/",itoa(maxmag,xbuf)," ",(char *)0);
say(who,"Action Points:",itoa(nowact,xbuf),(char *)0);
say(who,"/",itoa(maxact,xbuf),"\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(att,def,attrib,arisk,abet,dhas,ahas,wmod,amod,smes,fmes)
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),
" 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),
" 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(who,att)
char *who;
char *att;
{
char *weap;
char *ap;
int iac;
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(who,att)
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 */
cmd__attack(ac,av,who,aswho)
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,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(who,ud)
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 */
cmd_wield(ac,av,who,aswho)
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);
}