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