/
umud/DOC/
umud/DOC/U/
umud/DOC/U/U-examples/
umud/DOC/internals/
umud/DOC/wizard/
umud/MISC/
umud/MISC/dbchk/
umud/RWHO/rwhod/
/*
	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);
}