nexus/attack.c

3562 lines
94 KiB
C

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ai.h"
#include "attack.h"
#include "defs.h"
#include "flag.h"
#include "god.h"
#include "io.h"
#include "lf.h"
#include "map.h"
#include "move.h"
#include "nexus.h"
#include "objects.h"
#include "spell.h"
#include "text.h"
extern lifeform_t *player;
extern lifeform_t *godlf[];
extern prompt_t prompt;
extern int needredraw;
extern int statdirty;
extern enum ERROR reason;
int applyarmourdamage(lifeform_t *lf, object_t *wep, int dam, enum DAMTYPE damtype, lifeform_t *attacker) {
object_t *armour = NULL;
int damtaken = 0;
// first of all, only apply some of the damage
dam /= 2;
if (dam == 0) {
return 0;
}
// special case - missiles always hit flak jacket
if (damtype == DT_PROJECTILE) {
object_t *o;
o = hasequippedobid(lf->pack, OT_FLAKJACKET);
if (o) {
armour = o;
}
}
// if attacker is directly behind us, tortoiseshell is what was hit.
/*
if (attacker) {
if (getcellindir(lf->cell, diropposite(lf->facing)) == attacker->cell) {
object_t *o;
o = hasequippedob(lf->pack, OT_TORTOISESHELL);
if (o) {
armour = o;
}
}
}
*/
// figure out what bit of armour was hit
if (!armour) {
// pick a random piece of armour
armour = getrandomarmour(lf, attacker);
}
if (armour) {
int actualdam;
int ar = 0;
flag_t *rust, *f;
f = hasflag(armour->flags, F_ARMOURRATING);
if (f) {
ar = f->val[0];
}
rust = hasflag(armour->flags, F_RUSTED);
actualdam = dam;
/*
// adjust how much damage to do to armour
if ( ((armour->type->id == OT_FLAKJACKET) && (damtype == DT_PROJECTILE)) ||
(damtype == DT_ACID) ||
rust ) {
// ALL of damage reduction goes towards armour
} else {
// SOME of the damage reduction goes towards the armour
// damage taken by armour is reduced by _UP TO_ half its armour rating
if (ar) {
int maxreduction;
maxreduction = ar/2;
if (maxreduction >= 1) {
actualdam -= rnd(0,maxreduction);
limit(&actualdam, 0, NA);
}
}
}
*/
// modify for rust
if (rust) {
int multiplier = 1;
switch (rust->val[0]) {
case R_RUSTY:
multiplier = 2;
case R_VRUSTY:
multiplier = 3;
case R_TRUSTY:
multiplier = 4;
}
actualdam *= multiplier;
}
if (actualdam > 0) {
// actually apply the damage to the armour
damtaken = takedamage(armour,actualdam, damtype,attacker);
}
}
return damtaken;
}
void applyarmourdamreduction(lifeform_t *lf, object_t *wep, int reduceamt, int *dam, enum DAMTYPE damtype) {
int db = B_FALSE;
int newdam = 0;
// figure out reduced damage value
if (dam) {
int ar;
newdam = *dam;
ar = getarmourrating(lf, NULL, NULL, NULL, NULL);
// if you did at least one damage...
if ((*dam >= 1) && (reduceamt >= 0)) {
int lowerlimit = 0,divideby;
// reduce it.
newdam -= reduceamt;
// you will always take at least 1 hp of damage for every (ar/2) damage.
// ie. if you took ar/2 damage, you take at least 1.
// ie. if you took ar damage, you take at least 2.
// ie. if you took ar*2 damage, you take at least 3.
// stop at 3.
divideby = ar/2;
if (divideby <= 0) divideby = 1;
lowerlimit = (*dam / divideby);
limit(&lowerlimit, 1, NA);
limit(&newdam, lowerlimit, NA); // don't reduce too far.
}
if (db) {
if ((*dam >= 1) && (reduceamt > 0)) {
dblog("Armour reduces dam=%d by %d to %d.",*dam,reduceamt,newdam);
} else {
dblog("No armour dam reduction.");
}
}
*dam = newdam;
}
}
int attackcell(lifeform_t *lf, cell_t *c, int force) {
int validwep[MAXCANDIDATES];
object_t *wep[MAXCANDIDATES];
flag_t *damflag[MAXCANDIDATES];
obpile_t *op = NULL;
enum {
AT_NONE = 0,
AT_LF = 1,
AT_OB = 2,
AT_WALL = 3,
} attacktype = AT_NONE;
void *attacktarget;
int attacklfid = -1;
int nweps = 0;
int innateattacks = 0;
int i;
int attacktime;
int gotweapon = B_FALSE;
int maxattacks = ALL;
int attacksdone = 0;
int lastweaponidx = -1;
int saysorry = B_FALSE;
int attackedhelpless = B_FALSE;
int attackedfriend = B_FALSE;
int attackedpeaceful = B_FALSE;
enum SKILLLEVEL slev;
int dostamloss = B_TRUE;
// warn if attacking will cause injury
if (!force && isplayer(lf) && haslos(lf, c)) {
if (!confirm_injury_action(BP_HANDS, DT_SLASH, "attack")) {
return B_TRUE;
}
}
stoprunning(lf);
stoppathfinding(lf);
// anyone there? if so just attack.
if (c->lf) {
// warnings
if (!force && isplayer(lf)) {
int h,m,s;
splittime(&h,&m,&s);
char warnbuf[BUFLEN];
char ch;
char victimname[BUFLEN];
getlfname(c->lf, victimname);
strcpy(warnbuf, "");
if (godprayedto(R_GODLIFE) && (h == 6) && !lfhasflag(lf, F_STRIKETOKO)) {
if (!warnabout("Really attack during Glorana's Peace?")) {
return B_TRUE;
}
}
if (isprone(lf)) {
if (!warnabout("Really attack while prone (-4 accuracy)?")) {
return B_TRUE;
}
}
if (!areenemies(lf,c->lf) && (getraceclass(c->lf) != RC_PLANT) &&
cansee(lf, c->lf) &&
!lfhasflag(lf, F_RAGE)
) {
switch (getallegiance(c->lf)) {
case AL_PEACEFUL:
if (getlorelevel(lf, getraceclass(c->lf) >= PR_NOVICE) ||
getskill(lf, SK_SPEECH)) { // need this to KNOW whether they're peaceful
snprintf(warnbuf, BUFLEN, "Really attack the peaceful %s?",noprefix(victimname));
}
break;
case AL_FRIENDLY:
snprintf(warnbuf, BUFLEN, "Really attack the allied %s?",noprefix(victimname));
break;
default:
snprintf(warnbuf, BUFLEN, "Really attack the allied %s?",noprefix(victimname));
break;
}
if (strlen(warnbuf)) {
ch = askchar(warnbuf, "yn","n", B_TRUE, B_FALSE);
if (ch == 'n') {
// cancel.
return B_TRUE;
}
}
attackedpeaceful = B_TRUE;
// non-evil players get no xp for attacking peaceful lfs
if ((isplayer(lf) || areallies(player, lf)) && (getalignment(player) != AL_EVIL)) {
killflagsofid(c->lf->flags, F_XPVAL);
addflag(c->lf->flags, F_XPVAL, 0, NA, NA, NULL);
real_warnabout(TEXT_WARN_NOXP_GOODVSPEACEFUL, PERMENANT, B_FALSE);
}
} else if ((c->lf->race->raceclass->id == RC_PLANT) &&
(c->map->region->rtype->id == BH_WOODS)) {
int willwarn = B_FALSE;
if (lfhasflag(lf, F_SYLVANWARN)) {
willwarn = B_TRUE;
} else if (getattrbracket(getattr(lf, A_WIS), A_WIS, NULL) >= AT_GTAVERAGE) {
willwarn = B_TRUE;
}
if (willwarn) {
snprintf(warnbuf, BUFLEN, "Really attack %s while in the Sylvan Woods?",victimname);
ch = askchar(warnbuf, "yn","n", B_TRUE, B_FALSE);
if (ch == 'n') {
// cancel.
return B_TRUE;
}
}
}
// average wisdom will prevent you from annoying your god
if (getattrbracket(getattr(lf, A_WIS), A_WIS, NULL) >= AT_AVERAGE) {
enum HELPLESSTYPE how;
if (ishelplessvictim(c->lf, lf, &how)) {
int dowarning = B_FALSE;
if (godprayedto(R_GODPURITY) && (getalignment(c->lf) != AL_EVIL)) {
dowarning = B_TRUE;
} else if (godprayedto(R_GODMERCY)) {
dowarning = B_TRUE;
}
if (dowarning) {
char victimname[BUFLEN],buf[BUFLEN];
getlfname(c->lf, victimname);
snprintf(buf, BUFLEN, "Really attack the %s %s?",
(how == HL_FLEEING) ? "fleeing" : "helpless",
noprefix(victimname));
if (!warnabout(buf)) {
return B_TRUE;
}
}
}
if ((getraceclass(c->lf) == RC_PLANT) && godprayedto(R_GODNATURE)) {
char victimname[BUFLEN],buf[BUFLEN];
getlfname(c->lf, victimname);
snprintf(buf, BUFLEN, "Really attack %s?",victimname);
if (!warnabout(buf)) {
return B_TRUE;
}
}
}
// above average wisdom will prevent you from starting fires, or rusting your weapon
if (getattrbracket(getattr(lf, A_WIS), A_WIS, NULL) >= AT_GTAVERAGE) {
if (hasflag(c->type->material->flags, F_FLAMMABLE)) {
if (getlorelevel(player, c->lf->race->raceclass->id) >= PR_BEGINNER) {
if (c->lf->race->id == R_FIREBUG) {
char victimname[BUFLEN],buf[BUFLEN];
getlfname(c->lf, victimname);
snprintf(buf, BUFLEN, "Attacking %s might start a fire - proceed anyway?",victimname);
if (!warnabout(buf)) {
return B_TRUE;
}
}
}
}
if (c->lf->material->id == MT_WATER) {
object_t *priwep;
priwep = getweapon(lf);
if (priwep && willrust(priwep)) {
char victimname[BUFLEN],wepname[BUFLEN],buf[BUFLEN];
getlfname(c->lf, victimname);
real_getobname(priwep, wepname, priwep->amt, B_NOPREMODS,
B_NOCONDITION, B_BLINDADJUST,
B_NOBLESSINGS, B_NOUSED, B_NOSHOWALL);
snprintf(buf, BUFLEN, "Attacking %s might rust your %s - proceed anyway?",victimname, noprefix(wepname));
if (!warnabout(buf)) {
return B_TRUE;
}
}
}
}
if (lfhasflag(lf, F_HASNEWLEVEL)) {
if (!warnabout(TEXT_WARN_ATTACK_NOXP)) {
return B_TRUE;
}
}
// player walked into someone who was feigning death?
if (lfhasflag(c->lf, F_FEIGNINGDEATH)) {
char vicname[BUFLEN];
killflagsofid(c->lf->flags, F_FEIGNINGDEATH);
getlfname(c->lf, vicname);
capitalise(vicname);
if (cansee(lf, c->lf)) {
msg("Hey! %s was just feigning death!", vicname);
} else {
msg("You bump into someone!");
}
killflagsofid(c->lf->flags, F_PRONE);
// still counts as a move!
addflagifneeded(lf->flags, F_TOOKACTION, B_TRUE, NA, NA, NULL);
taketime(lf, getmovespeed(lf));
return B_FALSE;
}
}
attacktype = AT_LF;
attacktarget = c->lf;
attacklfid = c->lf->id; // remember for later
if (areallies(lf, attacktarget)) attackedfriend = B_TRUE;
attackedhelpless = ishelplessvictim(attacktarget, lf, NULL);
} else {
object_t *o;
// has an impassable object?
o = hasobwithflag(c->obpile, F_IMPASSABLE);
if (o) {
object_t *priwep;
attacktype = AT_OB;
attacktarget = o;
priwep = getweapon(lf);
// confirm ?
if (!force && isplayer(lf)) {
if (wepdullable(priwep) &&
(getattrbracket(getattr(player, A_IQ), A_IQ, NULL) >= AT_GTAVERAGE) &&
!lfhasflag(lf, F_RAGE)) {
if (!hasflagknown(priwep->flags, F_IMMUTABLE)) {
char obname[BUFLEN],wepname[BUFLEN],buf[BUFLEN];
char ch;
real_getobname(o, obname, o->amt, B_NOPREMODS, B_NOCONDITION, B_BLINDADJUST, B_NOBLESSINGS, B_NOUSED, B_NOSHOWALL);
getobname(priwep, wepname, priwep->amt);
snprintf(buf, BUFLEN, "Attacking %s might damage your %s. Proceed?", obname, noprefix(wepname));
ch = askchar(buf, "yn","n", B_TRUE, B_FALSE);
if (ch == 'n') {
// cancel.
return B_TRUE;
}
}
} else if ((o->type->obclass->id == OC_FLORA) &&
(c->map->region->rtype->id == BH_WOODS)) {
int willwarn = B_FALSE;
char obname[BUFLEN];
real_getobname(o, obname, o->amt, B_NOPREMODS, B_NOCONDITION,
B_BLINDADJUST, B_NOBLESSINGS, B_NOUSED, B_NOSHOWALL);
if (lfhasflag(lf, F_SYLVANWARN)) {
willwarn = B_TRUE;
} else if (getattrbracket(getattr(lf, A_WIS), A_WIS, NULL) >= AT_GTAVERAGE) {
willwarn = B_TRUE;
}
if (willwarn) {
char ch;
char buf[BUFLEN];
snprintf(buf, BUFLEN, "Really attack %s while in the Sylvan Woods?",obname);
ch = askchar(buf, "yn","n", B_TRUE, B_FALSE);
if (ch == 'n') {
// cancel.
return B_TRUE;
}
}
}
}
} else { // no impassable objects here
object_t *poss[MAXPILEOBS];
int nposs = 0;
for (o = c->obpile->first ; o ; o = o->next) {
if (hasflag(o->flags, F_ATTACKABLE)) {
poss[nposs++] = o;
}
}
if (nposs == 1) {
attacktype = AT_OB;
attacktarget = poss[0];
} else if (nposs) {
o = NULL;
if (isplayer(lf) && !lfhasflag(lf, F_HURRICANESTRIKE)) {
// ask which one to attack
char ch = 'a';
initprompt(&prompt, "What will you attack?");
for (i = 0; i < nposs; i++) {
char obname[BUFLEN];
getobname(poss[i], obname, poss[i]->amt);
addchoice(&prompt, ch, obname, obname, o, NULL);
if (ch == 'z') ch = 'A';
else ch++;
}
addchoice(&prompt, '-', "(nothing)", "(nothing)", NULL, NULL);
prompt.maycancel = B_TRUE;
ch = getchoice(&prompt);
if (ch != '\0') o = (object_t *)prompt.result;
} else {
// pick one randomly
o = poss[rnd(0,nposs-1)];
}
if (o) {
attacktype = AT_OB;
attacktarget = o;
} else {
if (isplayer(lf)) msg("Cancelled.");
return B_TRUE;
}
} else { // no objects here to attack
if (!lfhasflag(lf, F_HURRICANESTRIKE)) {
if (c->type->solid) {
// attacking a wall
attacktype = AT_WALL;
attacktarget = c;
if ((c->type->id == CT_WALLTREE) &&
(c->map->region->rtype->id == BH_WOODS)) {
int willwarn = B_FALSE;
if (lfhasflag(lf, F_SYLVANWARN)) {
willwarn = B_TRUE;
} else if (getattrbracket(getattr(lf, A_WIS), A_WIS, NULL) >= AT_GTAVERAGE) {
willwarn = B_TRUE;
}
if (willwarn) {
char ch;
char buf[BUFLEN];
snprintf(buf, BUFLEN, "Really attack %s while in the Sylvan Woods?",c->type->name);
ch = askchar(buf, "yn","n", B_TRUE, B_FALSE);
if (ch == 'n') {
// cancel.
return B_TRUE;
}
}
}
} else {
if (isplayer(lf)) {
msg("There is nothing there to attack!");
}
return B_TRUE;
}
} // end if !hurricanestrike
}
}
}
// can you actually attack?
if (!canattack(lf)) {
if (isplayer(lf)) {
switch (reason) {
//case E_NOSTAM: msg("You are too tired to fight at the moment."); break;
case E_STUNNED: msg("You are too stunned to fight at the moment."); break;
case E_IMPOSSIBLE: msg("You have no way of attacking!"); break;
default: msg("For some reason, you cannot attack."); break;
}
}
return B_TRUE;
}
// ai code...
if (lfhasflag(lf, F_DEMANDSBRIBE)) {
if (!isplayer(lf) && (attacktype == AT_LF) && isplayer((lifeform_t *)attacktarget)) {
if (demandbribe(lf)) {
// ie. player paid.
taketime(lf, getactspeed(lf));
return B_FALSE;
}
}
}
// monsters won't attack with non-melee weapons like bows
gotweapon = getweapons(lf, isplayer(lf) ? B_FALSE : B_MELEEONLY, wep, damflag, &lastweaponidx, &op, &nweps);
for (i = 0; i < nweps; i++) {
validwep[i] = B_TRUE;
}
innateattacks = countinnateattacks(lf);
attacktime = getattackspeed(lf);
if (nweps <= 0) {
if (isplayer(lf)) {
msg("You cannot attack!");
} else if (lfhasflag(lf, F_DEBUG)) {
msg("DB: %s cannot attack!",lf->race->name);
}
if (op) killobpile(op);
if (!isplayer(lf)) {
// avoid infinite loops
taketime(lf, getactspeed(lf));
}
return B_TRUE;
}
//maxattacks = nweps; // ie. all
maxattacks = getattacks(lf, NULL, NULL);
// cope with special monsters with NO innate attacks, but
// which still use weapons (eg. dancing weapon).
limit(&maxattacks, 1, NA);
/*
// if we have a weapon, this takes the place of one of our
// attacks.
// - for monsters, pick which one to replace randomly.
// - for players, never pick the weapon to replace randomly.
*/
// # valid attacks higher than our allowed attacks?
if (nweps > maxattacks) {
int nvalid;
int first;
// player never invalidates their equipped weapons
//if (isplayer(lf) && gotweapon) {
if (gotweapon) {
first = lastweaponidx+1;
} else {
first = 0;
}
nvalid = 0;
for (i = 0; i < nweps; i++) {
if (validwep[i]) nvalid++;
}
while (nvalid > maxattacks) {
int sel;
// mark a random one as invalid
sel = rnd(first,nweps-1);
while (!validwep[sel]) {
sel = rnd(first,nweps-1);
}
validwep[sel] = B_FALSE;
// re-count...
nvalid = 0;
for (i = 0; i < nweps; i++) {
if (validwep[i]) nvalid++;
}
}
}
// lore about this race will tell you if you will do no damage.
if ((attacktype == AT_LF) && isplayer(lf) && wep[0]) {
lifeform_t *victim;
victim = (lifeform_t *)attacktarget;
if (getlorelevel(player, victim->race->raceclass->id) >= PR_BEGINNER) {
enum DAMTYPE dt;
char buf[BUFLEN],victimname[BUFLEN];
getlfname(victim, victimname);
dt = getdamtype(wep[0]);
if (isimmuneto(victim->flags, dt, B_FALSE)) {
snprintf(buf, BUFLEN, "%s is immune to %s damage. Really attack?",victimname,
getdamname(dt));
if (!warnabout(buf)) {
return B_TRUE;
}
} else if (isresistantto(victim->flags, dt, B_FALSE)) {
snprintf(buf, BUFLEN, "%s is resistant to %s damage. Really attack?",victimname,
getdamname(dt));
if (!warnabout(buf)) {
return B_TRUE;
}
}
}
}
if (maxattacks) {
addflagifneeded(lf->flags, F_TOOKACTION, B_TRUE, NA, NA, NULL);
if (!lfhasflag(lf, F_COMBOSTRIKE)) {
taketime(lf, attacktime);
}
}
attacksdone = 0;
while (attacksdone < maxattacks) {
for (i = 0; (i < nweps) && (attacksdone < maxattacks); i++) {
if (validwep[i]) {
if (attacktype == AT_LF) {
if (!isdead((lifeform_t *)attacktarget)) {
lifeform_t *victim;
flag_t *fleeflag;
victim = (lifeform_t *)attacktarget;
fleeflag = isfleeingfrom(victim, lf);
if (i == 0) {
// did we just attack someone by accident?
if (!isplayer(lf) && !areenemies(lf, victim) && (lf->race->raceclass->id == RC_HUMANOID) &&
(getattrbracket(getattr(lf, A_IQ), A_IQ, NULL) >= A_LOW) ) {
if (lf != victim) {
saysorry = B_TRUE;
}
}
// announce attacks from behind which aren't backstabs.
if (isplayer(lf) && attackedhelpless && !willbackstab(lf, victim, wep[i])) {
char vname[BUFLEN];
getlfname(victim, vname);
if (isbehind(lf, victim)) {
msg("You attack %s from behind!", vname);
}
}
}
if (attacklf(lf, victim, wep[i], damflag[i])) {
// failed, or victim died/dodged
attacksdone = maxattacks;
break;
}
// no longer adjacent?
if (!isadjacent(lf->cell, victim->cell) || isdead(victim)) {
break;
}
// vicitm started to flee?
if (isplayer(lf)) {
if (!fleeflag && isfleeingfrom(victim, lf)) {
if ( (i+1 < nweps) && (attacksdone+1 < maxattacks)) {
char ch,ques[BUFLEN];
char vname[BUFLEN];
getlfname(victim, vname);
sprintf(ques, "Continue attacking the fleeing %s?",
noprefix(vname));
ch = askchar(ques, "yn","y", B_TRUE, B_FALSE);
if (ch == 'n') {
break;
}
}
}
}
}
} else if (attacktype == AT_OB) {
if (attackob(lf, (object_t *)attacktarget, wep[i], damflag[i])) {
// failed
attacksdone = maxattacks;
break;
}
} else if (attacktype == AT_WALL) {
if (attackwall(lf, (cell_t *)attacktarget, wep[i], damflag[i])) {
// failed
attacksdone = maxattacks;
break;
}
}
attacksdone++;
// stop attacking if they somehow got out of range
// (eg. dodging)
if (attacktype == AT_LF) {
if (getcelldist(lf->cell, ((lifeform_t *)attacktarget)->cell) > 1) {
attacksdone = maxattacks;
break;
}
}
}
}
}
// now kill all temp obs
if (op) {
killobpile(op);
}
if (attacktype == AT_LF) {
// in case the lf disappered....
attacktarget = findlf(lf->cell->map, attacklfid);
}
// now stop hiding
killflagsofid(lf->flags, F_HIDING);
if (saysorry && attacktarget) {
sayphrase(lf, SP_SORRY, -1, NA, NULL, attacktarget);
}
if (hasbleedinginjury(lf, BP_HANDS)) {
if (!bleedfrom(lf, BP_HANDS, B_FALSE)) {
if (isplayer(lf)) msg("^BYou bleed!");
losehp(lf, 1, DT_DIRECT, NULL, "blood loss");
}
}
// god or map effects...
if (isplayer(lf) && attacktarget) {
int angered = B_FALSE;
if (attacktype == AT_LF) {
if (!isgod(attacktarget)) {
int h,m,s;
splittime(&h,&m,&s);
if (attackedfriend) {
if (angergodmaybe(R_GODMERCY, 25, GA_ATTACKALLY)) angered = B_TRUE;
if (angergodmaybe(R_GODPURITY, 100, GA_ATTACKALLY)) angered = B_TRUE;
switch (getalignment(attacktarget)) {
case AL_GOOD:
if (angergodmaybe(R_GODPURITY, 20, GA_ATTACKALLY)) angered = B_TRUE;
break;
default:
break;
}
} else if (attackedpeaceful) {
if (getraceclass(attacktarget) == RC_ANIMAL) {
// attacking helpless animals is fine
} else {
if (angergodmaybe(R_GODMERCY, 15, GA_ASSAULT)) angered = B_TRUE;
if (angergodmaybe(R_GODLIFE, 15, GA_ASSAULT)) angered = B_TRUE;
if (getalignment(attacktarget) == AL_GOOD ) {
if (angergodmaybe(R_GODPURITY, 70, GA_ASSAULT)) angered = B_TRUE; // even more
} else {
if (angergodmaybe(R_GODPURITY, 50, GA_ASSAULT)) angered = B_TRUE;
}
}
} else if (attackedhelpless) {
if (getraceclass(attacktarget) == RC_ANIMAL) {
// attacking helpless animals is fine
} else {
if (angergodmaybe(R_GODMERCY, 15, GA_ATTACKHELPLESS)) angered = B_TRUE;
}
if (getalignment(attacktarget) != AL_EVIL) {
if (angergodmaybe(R_GODPURITY, 50, GA_ATTACKHELPLESS)) angered = B_TRUE;
pleasegodmaybe(R_GODTHIEVES, 5);
pleasegodmaybe(R_GODDEATH, 10);
}
}
if ( ((lifeform_t *)attacktarget)->race->raceclass->id == RC_PLANT) {
lifeform_t *ll;
ll = (lifeform_t *)attacktarget;
if (angergodmaybe(R_GODNATURE, 25, GA_ATTACKOBJECT)) angered = B_TRUE;
if ((ll->race->raceclass->id == RC_PLANT) &&
!isdeaf(ll) &&
(lf->cell->map->region->rtype->id == BH_WOODS)) {
magicwoods_warn(lf);
}
}
if (lfhasflag(lf, F_USEDPOISON)) {
killflagsofid(lf->flags, F_USEDPOISON);
if (isplayer(lf)) god_usepoison_response();
}
if (godprayedto(R_GODLIFE) && (h == 6) && !lfhasflag(lf, F_STRIKETOKO)) {
if (angergodmaybe(R_GODLIFE, 30, GA_PEACEHOUR)) angered = B_TRUE;
}
}
} else if (attacktype == AT_OB) {
object_t *oo;
if (angergodmaybe(R_GODNATURE, 10, GA_ATTACKOBJECT)) angered = B_TRUE;
oo = (object_t *)attacktarget;
if (oo && !hasflag(oo->flags, F_DEAD) && (oo->type->obclass->id == OC_FLORA)) {
if (lf->cell->map->region->rtype->id == BH_WOODS) {
magicwoods_warn(lf);
}
}
}
}
dostamloss = B_TRUE; // default
slev = getskill(lf, SK_COMBAT);
if (slev == PR_MASTER) {
dostamloss = B_FALSE;
} else if (lfhasflagval(lf, F_LASTATTACKHIT, B_FALSE, NA, NA, NULL) &&
(getskill(lf, SK_ATHLETICS) >= PR_BEGINNER)) {
// missed, and we have balance via athletics skill
dostamloss = B_FALSE;
} else if (pctchance(slev * 10)) {
dostamloss = B_FALSE;
}
if (dostamloss) {
// lose a bit of stamina
modstamina(lf, -getattackstamloss(lf));
}
//}
// stop sprinting
stopsprinting(lf);
return B_FALSE;
}
int attacklf(lifeform_t *lf, lifeform_t *victim, object_t *wep, flag_t *damflag) {
int dam[MAX_HITS];
enum DAMTYPE damtype[MAX_HITS];
int ndam = 0;
char buf[BUFLEN];
char attackername[BUFLEN];
char victimname[BUFLEN],victimbpname[BUFLEN];
int fatal = B_FALSE;
int waskod = B_FALSE;
int feigneddeath = B_FALSE;
int deflected = B_FALSE;
int weppassthrough = B_FALSE;
int firstisbackstab = B_FALSE;
int blocked = B_FALSE,dodged = B_FALSE;
flag_t *magicarm = NULL;
int hit = B_FALSE;
int critical = 0;
int fumble = B_FALSE;
char wepname[BUFLEN];
//int acc;
//int ev;
int i;
int willheal = B_FALSE;
int isunarmed = B_FALSE;
skill_t *wepsk = NULL;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
int aidb = B_FALSE;
flag_t *f;
enum BODYPART critpos = BP_NONE;
int missby = 0;
// init
for (i = 0; i < MAX_HITS; i++) {
dam[i] = 0;
damtype[i] = DT_NONE;
}
if (wep) {
wepsk = getobskill(wep->flags);
}
if (lfhasflag(lf, F_DEBUG)) {
aidb = B_TRUE;
}
if (hasflag(wep->flags, F_UNARMEDWEP)) {
isunarmed = B_TRUE;
}
moveeffects(lf, B_FALSE);
if (isdead(lf)) {
return B_TRUE;
}
// if you have somehow attacked someone who was
// hiding (bump into them?) then you have now
// spotted them.
if (ishidingfrom(victim, lf)) {
spot_hiding_lf(lf, victim);
}
// get names
getlfname(lf, attackername);
if (lf == victim) {
if (isplayer(lf)) {
strcpy(victimname, "yourself");
} else {
strcpy(victimname, "itself");
}
} else {
getlfname(victim, victimname);
}
if (aidb) dblog(".oO { trying to attack %s }", victimname);
getobname(wep, wepname, 1);
if (aidb) dblog(".oO { my weapon is %s }", wepname);
if (lf->race->raceclass->id == RC_INSECT) {
if (hasactivespell(victim, OT_S_REPELINSECTS)) {
if (isplayer(lf)) {
msg("^wSomething prevents you from attacking %s!", victimname);
} else if (cansee(player, lf)) {
msg("^wSomething prevents %s from attacking %s!", attackername, victimname);
}
//taketime(lf, getattackspeed(lf));
return B_FALSE;
}
}
getflags(lf->flags, retflag, &nretflags, F_PROTALIGN, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if (f->val[1] == getalignment(lf)) {
int lftr,victr;
int protected = B_FALSE;
lftr = gettr(lf);
victr = gettr(victim);
if (lftr > victr) {
protected = B_TRUE;
} else {
// same level = 50% chance of protection.
// for each level that victim is higher, -10%.
if (pctchance(50 - (victr - lftr))) protected = B_TRUE;
}
if (protected) {
if (isplayer(lf)) {
msg("^wA %s force prevents you from attacking %s!",
(f->val[1] == AL_GOOD) ? "demonic" : "holy",
victimname);
} else if (cansee(player, lf)) {
msg("^wA %s force prevents %s from attacking %s!",
(f->val[1] == AL_GOOD) ? "demonic" : "holy",
attackername, victimname);
}
if (f->val[0] != PERMENANT) {
f->val[0]--;
if (f->val[0] <= 0) {
killflag(f);
}
}
return B_FALSE;
}
}
}
if (wep && lfhasflagval(victim, F_ASLEEP, NA, ST_KO, NA, NULL)) {
f = hasflag(wep->flags, F_MERCIFUL);
if (f) {
if (isplayer(lf)) {
msg("^wYour %s refuses to attack %s!", noprefix(wepname), victimname);
if (!f->known) f->known = B_TRUE;
} else if (cansee(player, lf)) {
msg("^w%s%s %s refuses to attack %s!", attackername, getpossessive(attackername),
noprefix(wepname), victimname);
if (!f->known) f->known = B_TRUE;
}
//taketime(lf, getattackspeed(lf));
return B_FALSE;
}
}
getflags(lf->flags, retflag, &nretflags, F_NONCORPOREAL, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
// ie. you have been made noncorporeal
if ((f->id == F_NONCORPOREAL) && (f->lifetime != FROMRACE)) {
if (isplayer(lf)) {
msg("^wYour attack passes straight through %s.", victimname);
} else if (cansee(player, lf)) {
msg("^w%s%s attack passes straight through %s!", attackername, getpossessive(attackername), victimname);
}
//taketime(lf, getattackspeed(lf));
return B_FALSE;
}
}
// long weapon in an enclosed space?
if (wep && hasflag(wep->flags, F_NEEDSSPACE) && (getdamtype(wep) != DT_PIERCE)) {
if (countcellexits(lf->cell, DT_COMPASS) < 3) {
if (pctchance(75)) {
if (isplayer(lf)) {
msg("^wYour %s glances off a nearby wall.", noprefix(wepname));
} else if (cansee(player, lf)) {
msg("^w%s%s %s glances off a nearby wall.", attackername, getpossessive(attackername), noprefix(wepname));
}
//taketime(lf, getattackspeed(lf));
return B_FALSE;
}
}
}
// did you hit?
ndam = 0;
hit = rolltohit(lf, victim, wep, &critical, &fumble, &missby);
if (lfhasflag(victim, F_HEAVENARM)) {
critical = B_FALSE;
} else if (isexhausted(lf)) {
critical = B_FALSE;
}
if (critical && !lfhasflag(lf, F_PHANTASM)) {
object_t *armour;
char noun[BUFLEN];
if (lfhasflag(victim, F_CANSEVER) && wep && (getdamtype(wep) == DT_SLASH)) {
flag_t *retflag[MAXCANDIDATES],*poss[MAXCANDIDATES],*f;
int nretflags = 0,nposs = 0;
// select a random sever-able body part
getflags(victim->flags, retflag, &nretflags, F_CANSEVER, F_NONE);
for (i = 0; i < nretflags; i++) {
if (hasbp(victim, retflag[i]->val[0])) {
poss[nposs++] = retflag[i];
}
}
f = poss[rnd(0,nposs-1)];
critpos = f->val[0];
} else {
critpos = getrandomcorebp(victim, lf);
}
if ((int)critpos == BP_NONE) {
strcpy(victimbpname, victimname);
} else {
armour = getouterequippedob(victim, critpos);
if (armour) {
char armname[BUFLEN];
real_getobname(armour, armname, 1, B_NOPREMODS, B_NOCONDITION, B_BLINDADJUST, B_NOBLESSINGS, B_NOUSED, B_NOSHOWALL);
sprintf(noun, "%s", noprefix(armname));
} else {
sprintf(noun, "%s", getbodypartname(victim, critpos));
}
// replace victicname to include body part
if ((lf == victim) && !isplayer(lf)) {
snprintf(victimbpname, BUFLEN, "its %s", noun);
} else {
getlfname(victim, buf);
snprintf(victimbpname, BUFLEN, "%s%s %s", buf, getpossessive(buf), noun);
}
}
} else {
strcpy(victimbpname, "");
}
if (lf == victim) {
if (isplayer(lf)) {
strcpy(victimname, "yourself");
} else {
strcpy(victimname, "itself");
}
} else {
getlfname(victim, victimname);
}
// weapon passing through ghosts etc?
if (hit) {
if (lfhasflag(victim, F_NONCORPOREAL) &&
!lfhasflag(lf, F_NONCORPOREAL) ) {
// using a magical or blessed weapon? if so you're ok.
if (wep && (ismagical(wep) || isblessed(wep) || (wep->material->id == MT_SILVER)) ) {
} else {
weppassthrough = B_TRUE;
hit = B_FALSE;
ndam = 0;
}
}
}
// deflection?
if (hit) {
object_t *dwep;
dwep = isdualweilding(victim);
if (dwep && (getskill(victim, SK_TWOWEAPON) >= PR_MASTER)) {
if (onein(4)) {
deflected = B_TRUE;
hit = B_FALSE;
}
}
}
if (hit) {
if (aidb) dblog(".oO { i hit! }");
if (!cansee(victim, lf)) {
addflag(lf->flags, F_UNSEENATTACKER, victim->id, NA, NA, NULL);
if (isplayer(victim) && !isplayer(lf)) needredraw = B_TRUE;
}
// special case
if (isplayer(lf) && (victim->race->id == R_GLOWBUG)) {
if ((wep->type->id == OT_EMPTYFLASK) || (wep->type->id == OT_EMPTYVIAL)) {
object_t *o;
// catch the glowbug!
msg("You catch %s in your %s.", victimname, noprefix(wepname));
removeob(wep, 1);
killlf(victim); // don't leave a corpse
o = addob(lf->pack, "glowing flask");
if (o) {
getobname(o, buf, o->amt);
msgnocap("%c - %s.",o->letter, buf);
} else {
// add to the ground
o = addob(lf->cell->obpile, "glowing flask");
if (o) {
getobname(o, buf, o->amt);
msg("%s drops to the ground.", buf);
}
}
// stop all further attacks
return B_TRUE;
}
}
// determine base damage
// determine damage
dam[0] = getdamroll(wep, victim, damflag);
if (isunarmed) {
object_t *gloves;
gloves = getouterequippedob(lf, BP_HANDS);
if (gloves && hasflag(gloves->flags, F_HARDNESS)) {
dam[0]++;
}
}
if (critical) {
// critical hit means an extra damage roll.
dam[0] += getdamroll(wep, victim, damflag);
}
if (aidb) dblog("rolled dam[%d] = %d",0,dam[0]);
// half damage if exhausted
if (isexhausted(lf)) {
dam[0] = pctof(75, dam[0]);
}
if (dam[0] < 0) {
willheal = B_TRUE;
}
// damtype?
damtype[0] = getdamtype(wep);
if (!willheal) {
enum SKILLLEVEL slev;
float loreadd = 0;
// blessed vs undead
adjustdamforblessings(lf, &(dam[0]), victim, wep->blessed);
// modify for weapon skill, strength, rings of wounding etc
applylfdammod(&dam[0], lf, wep);
// modify for size
modifyforsize(&dam[0], lf, victim, 10, M_PCT);
// hecta worshippers get bonus for bone weapons
if (wep && isplayer(lf)) {
int matbonus = 0;
if ((wep->material->id == MT_BONE) && godprayedto(R_GODDEATH)) {
matbonus = rnd(1,2);
} else if ((wep->material->id == MT_WOOD) && godprayedto(R_GODNATURE)) {
matbonus = rnd(1,2);
}
dam[0] += matbonus;
}
// backstab?
if (willbackstab(lf, victim, wep)) {
//addflag(victim->flags, F_STABBEDBY, lf->id, NA, NA, NULL);
dam[0] *= (getskill(lf, SK_BACKSTAB));
firstisbackstab = B_TRUE;
} else if (lfhasflag(victim, F_ASLEEP)) {
// target asleep?
dam[0] *= 2;
}
// bonus for knowledge about the other lf's race? applied LAST.
slev = getlorelevel(lf, victim->race->raceclass->id);
if (slev == PR_INEPT) {
loreadd = 0;
} else {
loreadd = slev;
}
dam[0] = (int) ( (float)dam[0] + loreadd );
// extra damage to fire-based lifeforms if they are cold.
if (lf->material->id == MT_FIRE) {
enum TEMPERATURE temp;
temp = getlftemp(lf);
switch (temp) {
case T_CHILLY: dam[0] = pctof(125, dam[0]); break;
case T_COLD: dam[0] = pctof(160, dam[0]); break;
case T_VCOLD: dam[0] = pctof(200, dam[0]); break;
default: break;
}
}
}
if (lfhasflag(lf, F_PHANTASM)) {
dam[0] = 0;
if (aidb) dblog(".oO { adjusting phantasm dam to 0 }");
}
if (aidb) dblog(".oO { dealing %d %s damage }", dam[0], getdamname(damtype[0]));
ndam = 1;
// determine extra damage for flaming etc.
// getextradam from USER too
if (!willheal) {
getextradamwep(wep, &dam[0], &damtype[0], &ndam, B_FALSE);
getextradamlf(lf, &dam[0], &damtype[0], &ndam, B_FALSE);
}
} else {
hit = B_FALSE;
ndam = 0;
}
if (ndam > 0) {
flag_t *f;
// hit!
killflagsofid(lf->flags, F_LASTATTACKHIT);
addflag(lf->flags, F_LASTATTACKHIT, B_TRUE, NA, NA, NULL);
for (i = 0; i < ndam; i++) {
int damreducedbyarmour = 0;
int backstab = B_FALSE;
int prebleed = B_FALSE;
int stopnow = B_FALSE;
if (firstisbackstab && (i == 0)) backstab = B_TRUE;
// slightly more damage for heavy blows
if (lfhasflag(lf, F_HEAVYBLOW) || hasflag(wep->flags, F_HEAVYBLOW)) {
dam[i] = (int)pctof(110,dam[i]);
}
// modify for rusted weapon.
switch (damtype[i]) {
case DT_PIERCE:
case DT_SLASH:
case DT_CHOP:
dam[i] = pctof(getrustdampct(wep), dam[i]);
break;
default:
break;
}
// blocked by defender's shield?
if (i == 0) {
int difficulty;
char attackname[BUFLEN];
if (lfhasflag(lf, F_HOLYAURA) && isvulnto(victim->flags, DT_HOLY, B_FALSE)) {
damtype[i] = DT_HOLY;
}
sprintf(attackname, "%s%s attack", attackername, getpossessive(attackername));
difficulty = 100 + (gettr(victim)*5) - (gettr(lf)*5);
if (check_for_block(lf, victim, dam[i], damtype[i], difficulty, attackname, isadjacent(lf->cell,victim->cell))) {
blocked = B_TRUE;
break; // stop processing damage now.
}
}
// modify damage based on defender's resistances
adjustdamlf(victim, &dam[i], damtype[i]);
//dblog("adjusted for lf to dam[%d] = %d",i,dam[i]);
// can't do damage to phantasms
if (lfhasflag(lf, F_PHANTASM)) dam[i] = 0;
// check for protective spells like heavenly armour
if (dam[i] > 0) {
if (isphysicaldam(damtype[i])) {
getflags(victim->flags, retflag, &nretflags, F_HEAVENARM, F_MAGICARMOUR, F_NONE);
if (nretflags) {
magicarm = retflag[0];
}
}
}
if (!magicarm) {
// armour doesn't reduce damage for backstabs or critical hits.
// BUT in the case of a critical hit, the armour might get
// damaged during the call to criticalhit() later on.
//
// normally armour would be handled by losehp() & losehpeffects(), but
// in this case we need to know whether the armour was hit beforehand,
// in order to construct the "you hit xxx" string.
if ((dam[i] > 0) && !backstab && !critical) {
damreducedbyarmour = handlearmour(victim, wep, &dam[i], damtype[i]);
}
// if damage has been reduced zero, it's not a critical hit anymore.
if (dam[i] <= 0) {
critical = B_FALSE;
critpos = BP_NONE;
}
// make sure quivering palm damage isn't fatal
// becquse we want them to explode
if (lfhasflag(lf, F_QUIVERINGPALM)) {
if (dam[i] >= victim->hp) {
dam[i] = victim->hp - 1;
}
}
// at this point, is the damage enough to be fatal? we need to know so we
// can determine whether monsters will use feign death or dodge abilities.
// NOTE: whether or not the attack is fatal is re-calculated again
// later on after damage is applied to ensure that the correct
// attack string is displayed ("you hit xxx" vs "you kill xxx").
if (dam[i] >= victim->hp) {
fatal = B_TRUE;
}
// another check for phantasms
if (lfhasflag(lf, F_PHANTASM)) dam[0] = 0;
// is the victim feigning death? if so, stop now.
if (lfhasflag(victim, F_FEIGNINGDEATH)) {
killflagsofid(victim->flags, F_FEIGNINGDEATH);
} else if (!fatal && !isplayer(victim) && cancast(victim, OT_A_FEIGNDEATH, NULL)) {
// monsters might pretend to be dead.
if (onein(2) || islowhp(victim)) {
// do it!
useability(victim, OT_A_FEIGNDEATH, lf, lf->cell);
feigneddeath = B_TRUE;
}
}
// did the defender use the Reflexive Dodging skill to dodge the attack?
if (fatal && !feigneddeath && lfhasflag(victim, F_DODGES) && cansee(victim, lf) && hasfreeaction(victim)) {
cell_t *adj;
int candodge = B_FALSE;
if (getstamina(victim)) {
if (isplayer(victim)) {
candodge = B_TRUE;
} else if (onein(3)) {
candodge = B_TRUE;
}
}
if (candodge) {
adj = getdodgecell(victim);
if (adj) {
flag_t *f;
if (isplayer(victim) || cansee(player, victim)) {
if (cansee(player, lf)) {
msg("^w%s dodge%s %s%s attack!",victimname,isplayer(victim) ? "" : "s",
attackername, getpossessive(attackername));
} else {
msg("^w%s dodge%s an attack!",victimname,isplayer(victim) ? "" : "s");
}
} else if (isplayer(lf)) {
msg("You attack something, but it dodges!");
} else if (cansee(player, lf)) {
msg("%s attacks something, but it dodges!", attackername);
}
f = addflag(victim->flags, F_NOTIME, B_TRUE, NA, NA, NULL);
moveto(victim, adj, B_FALSE, B_FALSE);
killflag(f);
if (isplayer(victim)) {
setstamina(victim, 0);
}
// remember that we dodged, to avoid otehr attack effects like
// heavy blow, etc.
dodged = B_TRUE;
// stop processing now.
break;
}
}
}
} // end if !magicarm
prebleed = isbleeding(victim);
// Now handle the actual hit.
if (magicarm) {
// if you have a forcefield/magic armour, you can never be killed until
// it vanishes.
fatal = B_FALSE;
} else if (willheal) {
// some magical weapons will heal instead of doing damage
if (cansee(player, victim)) {
flag_t *f;
if (areallies(player, victim)) {
msg("^g%s %s healed!",victimname, isplayer(victim) ? "are" : "is");
} else {
msg("^w%s %s healed!",victimname, isplayer(victim) ? "are" : "is");
}
f = hasflag(wep->flags, F_BALANCE);
if (f) {
f->known = B_TRUE;
}
}
gainhp(victim, dam[i]);
damreducedbyarmour = 0;
} else if (lfhasflag(lf, F_QUIVERINGPALM)) {
// victim explodes!
losehp_real(victim, victim->hp, DT_EXPLOSIVE, lf, "a quivering palm strike", B_FALSE, NULL, B_FALSE, NULL, B_FALSE, BP_NONE, B_NOCRIT);
damreducedbyarmour = 0;
} else {
// actually deal the melee damage!
char attackername2[BUFLEN];
real_getlfname(lf, attackername2, NULL, B_SHOWALL, B_REALRACE);
if (lf->race->raceclass->id == RC_GOD) {
flag_t *gf;
gf = lfhasflag(lf, F_GODOF);
if (getgender(lf) == G_FEMALE) {
strcat(attackername2, " the Goddess of ");
} else {
strcat(attackername2, " the God of ");
}
strcat(attackername2, gf->text);
}
// get name of weapon/attacker, for "killedby" text
if (wep && !isunarmed) {
char wepname[BUFLEN];
real_getobname(wep, wepname, 1, B_PREMODS, B_NOCONDITION, B_NOBLINDADJUST, B_BLESSINGS, B_NOUSED, B_NOSHOWALL);
/*
snprintf(buf, BUFLEN, "%s^%s %s",attackername2,
(lf == victim) ? "using" : "weilding",
wepname);
*/
// ie. killed by "an orc's dagger"
snprintf(buf, BUFLEN, "%s%s %s",attackername2, getpossessive(attackername2), noprefix(wepname));
} else {
strcpy(buf, attackername2);
}
// apply damage to victim
// note: we delay extra effects from the damage so that we get a chance to first annoiunce
// the hit.
//
// otherwise the messages are in the wrong order, eg:
// "the fire sets you alight!"
// "xxx hits you with a flaming sword."
// don't adjust damage for resistences - we've already done that
losehp_real(victim, dam[i], damtype[i], lf, buf, B_NODAMADJUST, wep, B_NORETALIATE,
&waskod, B_NODAMEFFECTS, critpos, critical);
}
// was it fatal ? override previously calculated value.
if ((victim->hp <= 0) && !waskod) {
fatal = B_TRUE;
} else {
fatal = B_FALSE;
}
// announce the hit
if (!feigneddeath) {
if (isplayer(lf) || isplayer(victim) || cansee(player, lf) || cansee(player, victim)) {
construct_hit_string(lf, victim, attackername, victimname, victimbpname, wep, damtype[i], dam[i], victim->maxhp, i, backstab, critical, lfhasflag(victim, F_NOFATALTEXT) ? B_FALSE : fatal, isunarmed, buf);
if (strlen(buf)) {
warn("%s", buf);
}
}
if (fatal) {
if (strstr(buf, "behead")) {
// we'll need to place the severed head object
addflag(victim->flags, F_BEHEADED, B_TRUE, NA, NA, NULL);
addflag(victim->flags, F_MUTILATED, B_TRUE, NA, NA, NULL);
if (damtypecausesbleed(damtype[i], wep)) {
bleed(victim, B_SPLATTER);
}
} else if (strstr(buf, "bisect") || strstr(buf, "dismember")) {
if (victim->race->id != R_EARTHWYRM) {
addflag(victim->flags, F_MUTILATED, B_TRUE, NA, NA, NULL);
}
if (damtypecausesbleed(damtype[i], wep)) {
bleed(victim, B_SPLATTER);
}
} else {
if (damtypecausesbleed(damtype[i], wep)) {
int bloodamt = 0,n;
switch (getlfsize(victim)) {
case SZ_MINI:
if (onein(3)) bloodamt = 1;
break;
case SZ_TINY: bloodamt = rnd(0,1); break;
case SZ_SMALL: bloodamt = rnd(0,2); break;
case SZ_MEDIUM: bloodamt = rnd(1,3); break;
case SZ_HUMAN: bloodamt = rnd(3,5); break;
case SZ_LARGE: bloodamt = rnd(5,10); break;
case SZ_HUGE: bloodamt = rnd(10,15); break;
case SZ_ENORMOUS:
case SZ_MAX:
bloodamt = rnd(15,20); break;
default: bloodamt = 0; break;
}
for (n = 0; n < bloodamt; n++) {
bleed(victim, B_NOSPLATTER);
}
}
}
if (isplayer(lf) && cansee(player, victim) && !hasflag(victim->flags, F_NODEATHANNOUNCE)) {
if (!hasflag(victim->flags, F_PHANTASM)) {
// don't also say "the xx dies"
addflag(victim->flags, F_NODEATHANNOUNCE, B_TRUE, NA, NA, NULL);
}
}
}
} // end if !feigneddeath
// special case
if (!isdead(victim)) {
f = lfhasflag(victim, F_ABSORBKINETIC);
if (f) {
char damstr[BUFLEN];
sprintf(damstr, "%d", f->val[0]);
addtempflag(victim->flags, F_EXTRADAM, f->val[1], NA, NA, damstr,f->val[2]);
addtempflag(victim->flags, F_HEAVYBLOW, B_TRUE, NA, NA, NULL, 1);
if (cansee(player, victim)) {
msg("^%c%s looks stronger!", getlfcol(victim, CC_GOOD), victimname);
}
}
}
/// ... used to apply armour damage here...
// make noise
// UNLESS this fighting involved the player.
// This is a hack - should really move this check into noise(), and
// implement some way to tell whether a lf is currently fighting the player.
if (!isplayer(lf) && !isplayer(victim)) {
noise(lf->cell, lf, NC_FIGHTING, SV_SHOUT, "fighting.", NULL);
}
if (backstab) {
practice(lf, SK_BACKSTAB, 1);
pleasegodmaybe(R_GODTHIEVES, 8);
} else if (lfhasflagval(lf, F_UNSEENATTACKER, victim->id, NA, NA, NULL)) {
pleasegodmaybe(R_GODTHIEVES, 4);
}
// now handle the extra hp loss effects which we postponed above.
losehpeffects(victim, dam[i], damtype[i], lf, wep, B_NORETALIATE, waskod,
&waskod, prebleed, BP_NONE, damreducedbyarmour, critical);
if (fatal || waskod || dodged || stopnow) break; // stop now, don't process further damtypes!
} // end foreach damtype
if (waskod) {
loseconsciousness(victim, waskod, lf);
}
// other effects
if ((victim->material->id == MT_WATER) && wep && !isunarmed) {
makewet(wep, 1);
}
// special weapon effects, as long as you're not doing a heavy blow
if (!lfhasflag(lf, F_HEAVYBLOW) && dam[0]) {
// confer flags from weapon
wepeffects(wep->flags, victim->cell, damflag, dam[0], isunarmed);
// confer flags from attacker themselves
wepeffects(lf->flags, victim->cell, damflag, dam[0], isunarmed);
}
if (!isdead(victim) && !blocked && !dodged) {
f = lfhasflag(lf, F_FREEZINGTOUCH);
if (f) {
int diff;
diff = f->val[2]*20;
if (isimmuneto(victim->flags, DT_COLD, B_FALSE) || skillcheck(victim, SC_RESISTMAG, diff, 0)) {
if (isplayer(victim)) {
msg("You feel mildly chilly.");
}
} else {
// victim turns to ice for a while!
freezelf(victim, lf, f->val[1]);
}
killflag(f);
}
// critical hit effects
if (critical && damtypecausescriteffects(damtype[0])) {
criticalhit(lf, victim, critpos, wep, dam[0], damtype[0]);
}
// special lifeform-based effects
if ((lf->race->id == R_COCKATRICE) && dam[0]) {
// first saving throw...
if (skillcheck(victim, SC_CON, 75, 0)) {
// slowed
addtempflag(victim->flags, F_SLOWACTMOVE, 15, NA, NA, NULL, 2);
} else {
// second saving throw...
if (skillcheck(victim, SC_CON, 75, 0)) {
// paralyzed
addtempflag(victim->flags, F_PARALYZED, B_TRUE, NA, NA, NULL, 5);
} else {
if (!lfhasflag(lf, F_BEINGSTONED)) {
// stoned!
addflag(victim->flags, F_BEINGSTONED, 2, NA, NA, NULL);
}
}
}
} else if ((lf->race->id == R_STIRGE) || (lf->race->baseid == R_LEECH)) {
if (getexposedlimbs(victim)) {
// automatically latch on
if (!lfhasflag(victim, F_NONCORPOREAL) && !hasflag(lf->flags, F_ATTACHEDTO)) {
addflag(lf->flags, F_ATTACHEDTO, victim->id, NA, NA, NULL);
}
}
}
// if victim was flying and took >= 40% of its hit points, it drops to the ground.
if (isphysicaldam(damtype[i]) && (dam[i] >= pctof(40, victim->maxhp))) {
fall_from_air(victim);
}
// if victim can still move...
if (hasfreeaction(victim)) {
fightback(victim, lf);
}
} // end if !isdead(victim)
if (!blocked && !dodged) {
// adhesion?
f = lfhasflag(victim, F_ADHESIVE);
if (f && wep && !isunarmed && !skillcheck(lf, SC_STR, f->val[0], 0)) {
// attacker's weapon sticks to it!
if (cansee(player, lf) || cansee(player, victim)) {
char wepname[BUFLEN];
real_getobname(wep, wepname, 1, B_PREMODS, B_NOCONDITION, B_NOBLINDADJUST, B_BLESSINGS, B_NOUSED, B_NOSHOWALL);
msg("^%c%s%s %s %s to %s!", getlfcol(lf, CC_BAD),
attackername, getpossessive(attackername), noprefix(wepname),
(wep->amt == 1) ? "sticks" : "stick", victimname);
}
moveob(wep, victim->pack, wep->amt);
}
// retaliation happens even if victim died
getflags(victim->flags, retflag, &nretflags, F_RETALIATE, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if ((f->id == F_RETALIATE) && (getcelldist(victim->cell, lf->cell) == 1)) {
int rdam;
char damstring[BUFLEN],dicetext[BUFLEN],obname[BUFLEN];
char *loctext,*p;
loctext = strdup(f->text);
p = readuntil(dicetext, loctext, '^');
readuntil(obname, p, '^');
rdam = roll(dicetext);
if (cansee(player, victim)) {
msg("^%c%s%s %s %s %s!", getlfcol(lf, CC_BAD), victimname, getpossessive(victimname),
noprefix(obname),
getattackverb(victim, NULL, f->val[0], rdam, lf->maxhp),
attackername);
}
snprintf(damstring, BUFLEN, "%s%s %s", victimname, getpossessive(victimname), noprefix(obname));
losehp_real(lf, rdam, f->val[0], victim, damstring, B_TRUE, NULL, B_TRUE, NULL, B_TRUE, critpos, critical);
free(loctext);
}
}
}
} else { // miss!
if (aidb) dblog(".oO { i missed! }");
killflagsofid(lf->flags, F_LASTATTACKHIT);
addflag(lf->flags, F_LASTATTACKHIT, B_FALSE, NA, NA, NULL);
// announce it
if (weppassthrough) {
if (cansee(player, lf)) {
msg("^w%s%s attack passes straight through %s!", attackername, getpossessive(attackername), victimname);
}
} else if (deflected) {
if (cansee(player, lf)) {
msg("^w%s deflect%s %s%s attack.", victimname, isplayer(victim) ? "" : "s",attackername, getpossessive(attackername));
}
} else if (lfhasflag(victim, F_MAGSHIELD) && ismetal(wep->material->id)) {
if (isplayer(lf) || cansee(player, lf)) {
snprintf(buf, BUFLEN, "%s",attackername);
msg("^w%s%s magnetic shield repels %s%s attack.", victimname, getpossessive(victimname),
buf, getpossessive(buf));
}
} else {
char missamt[BUFLEN];
flag_t *anticipated = NULL;
anticipated = lfhasflagval(victim, F_ANTICIPATE, lf->id, NA, NA, NULL);
if (anticipated || (missby >= 50)) {
strcpy(missamt, "wildly ");
} else if (missby <= 10) {
strcpy(missamt, "narrowly ");
} else {
strcpy(missamt, "");
}
if (isplayer(lf)) {
msg("You %smiss %s.", missamt, victimname);
} else {
if (cansee(player, lf)) {
// capitalise first letter
snprintf(buf, BUFLEN, "%s",attackername);
msg("%s %smisses %s.", buf, missamt, victimname);
}
}
if (canevade(victim, lf)) {
// victim trains evasion
practice(victim, SK_EVASION, 1);
}
// chance to fumble attack.
if (fumble) {
if (wep && !isunarmed) {
if (isplayer(lf)) {
msg("^%cYou fumble your attack!", getlfcol(lf, CC_BAD));
} else if (cansee(player, lf)) {
msg("^%c%s fumbles its attack!", getlfcol(lf, CC_BAD), attackername);
}
drop(wep, ALL);
wep = NULL;
}
}
// high chance that ai will give up if we can't reach the victim
if (!isplayer(lf) && !canreach(lf, victim, NULL)) {
if (pctchance(80)) {
loseaitargets(lf);
if (isplayer(victim) && cansee(player, lf)) {
//msg("%s seems to have lost interest in you.", attackername);
msg("%s can't reach you!", attackername);
}
}
}
// anticipated spells lose power
if (anticipated) {
if (--anticipated->val[1] <= 0) {
killflag(anticipated);
anticipated = NULL;
}
}
}
}
// practice?
if (hit) {
if (wepsk) {
practice(lf, wepsk->id, 1);
}
if (isdualweilding(lf)) {
practice(lf, SK_TWOWEAPON, 1);
}
}
// induction of fear?
if (!isdead(victim)) {
if (lfhasflag(victim, F_INDUCEFEAR)) {
if (cansee(lf, victim)) {
scare(lf, victim, rnd(2,3), 0);
}
}
}
// twoweapon?
if (hit && !blocked) {
enum SKILLLEVEL slev;
slev = getskill(lf, SK_TWOWEAPON);
if (slev >= PR_SKILLED) {
object_t *secwep;
secwep = getsecmeleeweapon(lf);
// ie. if we are using two weapons, and the current one
// is the first...
if (secwep && (secwep != wep)) {
int bonus = 0;
// next hit will have enhanced accuracy
if (slev == PR_SKILLED) {
bonus = 10;
} else if (slev == PR_EXPERT) {
bonus = 25;
} else if (slev == PR_MASTER) {
bonus = 40;
}
if (bonus) {
addtempflag(lf->flags, F_ACCURACYMOD, bonus, NA, NA, NULL, 1);
}
}
}
}
if (fatal || waskod || dodged || fumble) {
// don't keep attacking if the victim is dead, or moved!
// also don't keep attacking if we fumbled.
return B_TRUE;
}
if (aidb) dblog(".oO { doattack about to return B_FALSE }");
return B_FALSE;
}
int attackob(lifeform_t *lf, object_t *o, object_t *wep, flag_t *damflag) {
int dam[100];
enum DAMTYPE damtype[100];
int ndam = 0;
char attackername[BUFLEN];
char obname[BUFLEN];
flag_t *f;
int isunarmed = B_FALSE;
cell_t *obloc = NULL;
char wepname[BUFLEN],buf[BUFLEN];
int i;
//int aidb = B_TRUE;
int maxhp;
moveeffects(lf, B_FALSE);
if (isdead(lf)) return B_TRUE;
// get names
getlfname(lf, attackername);
getobname(o, obname, o->amt);
// get target object details
obloc = o->pile->where;
f = hasflag(o->flags, F_OBHP);
if (f) {
maxhp = f->val[1];
} else {
maxhp = 1;
}
if (hasflag(wep->flags, F_UNARMEDWEP)) {
isunarmed = B_TRUE;
}
getobname(wep, wepname, 1);
// don't need to figure out accuracy - we always hit.
// determine damage
ndam = 0;
//if (unarmedflag && (unarmedflag->val[0] != NA)) {
dam[ndam] = getdamroll(wep, NULL, damflag);
// modify for strength
if (!hasflag(wep->flags, F_NOSTRDAMMOD) && !lfhasflag(lf, F_NOSTRDAMMOD)) {
dam[ndam] += getstrdammod(lf);
}
// damtype?
damtype[ndam] = getdamtype(wep);
ndam++;
// don't need to check for blessed vs mosnters
// determine extra damage
getextradamwep(wep, &dam[0], &damtype[0], &ndam, B_FALSE);
getextradamlf(lf, &dam[0], &damtype[0], &ndam, B_FALSE);
if (isdoor(o, NULL)) {
// extra damage for engineering skill
dam[0] += getengineeringwallmod(lf);
}
for (i = 0; i < ndam; i++) {
// announce the hit
construct_hit_string(lf, NULL, attackername, obname, NULL, wep, damtype[i], dam[i], maxhp, i, B_FALSE, B_FALSE, B_FALSE, isunarmed, buf);
if (strlen(buf)) {
msg("%s", buf);
}
if (!isplayer(lf) && !cansee(player, lf)) {
char noisebuf[BUFLEN];
int vol;
switch (o->material->id) {
case MT_METAL:
strcpy(noisebuf, "a metallic clanging.");
vol = 4;
break;
case MT_GLASS:
strcpy(noisebuf, "cracking glass.");
vol = 4;
break;
case MT_WOOD:
case MT_DRAGONWOOD:
strcpy(noisebuf, "splintering wood.");
vol = 4;
break;
case MT_BONE:
case MT_STONE:
strcpy(noisebuf, "a dull thumping.");
vol = 3;
break;
case MT_GOLD:
case MT_SILVER:
case MT_LEATHER:
strcpy(noisebuf, "a dull thumping.");
vol = 2;
break;
case MT_PAPER:
case MT_WETPAPER:
case MT_RUBBER:
strcpy(noisebuf, "a dull thumping.");
vol = 1;
break;
default:
strcpy(noisebuf, "something being hit.");
vol = 3;
break;
}
noise(obloc, NULL, NC_OTHER, vol, noisebuf, NULL);
}
if ((i == 0) && (wep->type->id == OT_FISTS) && hasflag(o->flags, F_HARDNESS)) {
object_t *gloves;
gloves = getouterequippedob(lf, BP_HANDS);
if (gloves && hasflag(gloves->flags, F_HARDNESS)) {
// ok
} else if ((o->material->id == MT_WOOD) && (getskill(lf, SK_UNARMED) >= PR_ADEPT)) {
// ok
} else {
char buf[BUFLEN];
snprintf(buf, BUFLEN, "punching %s", obname);
if ( losehp(lf, 1, DT_BASH, lf, buf)) {
if (isplayer(lf)) {
msg("^bOw!");
}
}
}
}
// smash wood bonus
if ((wep->type->id == OT_FISTS) &&
(o->material->id == MT_WOOD) &&
(getskill(lf, SK_UNARMED) >= PR_ADEPT)) {
dam[i] += rnd(1,6);
}
// object loses hp
takedamage(o, dam[i], damtype[i], lf);
if (isplayer(lf) && hasflag(o->flags, F_LOCKED)) {
angergodmaybe(R_GODTHIEVES, 25, GA_MONEY);
}
if (isdeadob(o)) {
break;
}
} // end foreach damtype
if (!isdeadob(o)) {
// special weapon effects, as long as you're not doing a heavy blow
if (!lfhasflag(lf, F_HEAVYBLOW) && dam[0]) {
wepeffects(wep->flags, obloc, damflag, dam[0], isunarmed);
}
}
if (isunarmed) {
// touch effects
touch(lf, o);
} else if (hasflag(o->flags, F_IMPASSABLE)) {
// weapon gets damaged ?
if (wep && (ndam > 0)) {
if (wepdullable(wep)) {
// weapon gets duller
makedullermaybe(wep, 1);
}
}
}
// still not dead? more checks
if (!isdeadob(o)) {
object_t *oo;
f = hasflag(o->flags, F_TRAPPED);
if (f && pctchance(75)) {
doobtraps(o, lf);
} else {
// if a trap didn't go off, you might break the lock
f = hasflag(o->flags, F_LOCKED);
if (f && (damtype[0] == DT_BASH)) {
int difficulty;
int unlockit = B_FALSE;
difficulty = f->val[1];
if (rnd(0,getattr(lf, A_STR)) + dam[0] >= difficulty) {
// hit it hard enough
unlockit = B_TRUE;
} else if ( pctchance(dam[0]*3)) {
// did enough damage
unlockit = B_TRUE;
}
if (unlockit) {
// lock breaks!
if (isplayer(lf)) {
msg("You break the lock!");
}
killflagsofid(o->flags, F_LOCKED);
}
}
}
// objects inside might smash
if (damtype[0] == DT_BASH) {
for (oo = o->contents->first ;oo ; oo = oo->next) {
if (willshatter(oo->material->id) && onein(2)) {
if (isplayer(lf)) {
// since the sound won't work.
msg("You hear shattering glass from inside %s.", obname);
}
// damstring should never be used...
shatter(oo, B_FALSE, "shattering damage", lf);
} else {
int obdam;
obdam = dam[0] / 2;
limit(&obdam, 1, NA);
takedamage(oo, obdam, DT_BASH, NULL);
}
}
}
}
return B_FALSE;
}
int attackwall(lifeform_t *lf, cell_t *c, object_t *wep, flag_t *damflag) {
int dam[100];
enum DAMTYPE damtype[100];
int ndam = 0;
char attackername[BUFLEN];
int isunarmed = B_FALSE;
char buf[BUFLEN];
int i;
int maxhp;
moveeffects(lf, B_FALSE);
if (isdead(lf)) return B_TRUE;
maxhp = c->type->hp;
// get names
getlfname(lf, attackername);
// don't need to figure out accuracy - we always hit.
// determine damage
ndam = 0;
//if (unarmedflag && (unarmedflag->val[0] != NA)) {
dam[ndam] = getdamroll(wep, NULL, damflag);
// modify for strength
if (!hasflag(wep->flags, F_NOSTRDAMMOD) && !lfhasflag(lf, F_NOSTRDAMMOD)) {
dam[ndam] += getstrdammod(lf);
}
// damtype?
damtype[ndam] = getdamtype(wep);
ndam++;
// don't need to check for blessed vs mosnters
// determine extra damage
getextradamwep(wep, &dam[0], &damtype[0], &ndam, B_FALSE);
getextradamlf(lf, &dam[0], &damtype[0], &ndam, B_FALSE);
// extra damage for engineering skill
dam[0] += getengineeringwallmod(lf);
for (i = 0; i < ndam; i++) {
char cellname[BUFLEN];
sprintf(cellname, "%s %s", needan(c->type->name) ? "an" : "a", c->type->name);
// announce the hit
construct_hit_string(lf, NULL, attackername, cellname, NULL, wep, damtype[i], dam[i], maxhp, i, B_FALSE, B_FALSE, B_FALSE, isunarmed, buf);
if (strlen(buf)) {
msg("%s", buf);
}
if (!isplayer(lf) && !cansee(player, lf)) {
char noisebuf[BUFLEN];
int vol;
switch (c->type->material->id) {
case MT_METAL:
strcpy(noisebuf, "a metallic clanging.");
vol = 4;
break;
case MT_GLASS:
strcpy(noisebuf, "cracking glass.");
vol = 4;
break;
case MT_WOOD:
case MT_DRAGONWOOD:
strcpy(noisebuf, "splintering wood.");
vol = 4;
break;
case MT_BONE:
case MT_STONE:
strcpy(noisebuf, "a dull thumping.");
vol = 3;
break;
case MT_GOLD:
case MT_SILVER:
case MT_LEATHER:
strcpy(noisebuf, "a dull thumping.");
vol = 2;
break;
case MT_PAPER:
case MT_WETPAPER:
case MT_RUBBER:
strcpy(noisebuf, "a dull thumping.");
vol = 1;
break;
default:
strcpy(noisebuf, "something being hit.");
vol = 3;
break;
}
noise(c, NULL, NC_OTHER, vol, noisebuf, NULL);
}
if ((i == 0) && (wep->type->id == OT_FISTS) && hasflag(c->type->material->flags, F_HARDNESS)) {
object_t *gloves;
gloves = getouterequippedob(lf, BP_HANDS);
if (gloves && hasflag(gloves->flags, F_HARDNESS)) {
// ok
} else if ((c->type->material->id == MT_WOOD) && (getskill(lf, SK_UNARMED) >= PR_ADEPT)) {
// ok
} else {
char buf[BUFLEN];
snprintf(buf, BUFLEN, "punching %s", cellname);
if ( losehp(lf, 1, DT_BASH, lf, buf)) {
if (isplayer(lf)) {
msg("^bOw!");
}
}
}
}
// smash wood bonus
if ((wep->type->id == OT_FISTS) &&
(c->type->material->id == MT_WOOD) &&
(getskill(lf, SK_UNARMED) >= PR_ADEPT)) {
dam[i] += rnd(1,6);
}
if (dam[i] > 0) {
damagecell(c, dam[i], damtype[i], lf);
// don't deal any more damage types
break;
}
} // end foreach damtype
// no special weapon effects on cells.
// weapon gets damaged ?
if (wep && (ndam > 0)) {
if (wepdullable(wep)) {
// weapon gets duller
makedullermaybe(wep, 1);
}
}
return B_FALSE;
}
enum DAMTYPE basedamagetype(enum DAMTYPE dt) {
switch (dt) {
case DT_HEAT:
dt = DT_FIRE; break;
default:
break;
}
return dt;
}
// returns B_TRUE if victim blocked lf's attack
//
// Note: sometimes we'll call this function with a check difficulty of IMPOSSIBLE.
// This means that it'sonly possible to block the attack if you are in
// 'fullshield' mode.
int check_for_block(lifeform_t *lf, lifeform_t *victim, int dam, enum DAMTYPE damtype, int difficulty, char *attackname, int ranged) {
object_t *shield[MAXPILEOBS];
int checkmod[MAXPILEOBS];
int nshields,i;
flag_t *fflag;
long shid;
fflag = lfhasflag(victim, F_FULLSHIELD);
if (fflag) {
shid = atol(fflag->text);
} else {
shid = -1;
}
if (lf && !cansee(victim, lf)) {
// in fullblock mode, you can block even if you can't see
// your attacker.
if (!fflag) {
return B_FALSE;
}
}
if (lfhasflag(victim, F_STUNNED) || !hasfreeaction(victim)) {
return B_FALSE;
}
// need stamina to block
if (!getstamina(victim)) return B_FALSE;
// get all usable shields for this damtype
getallshields(victim, damtype, shield, checkmod, &nshields);
for (i = 0; i < nshields; i++) {
int blocked = B_FALSE;
// did f_fullblock skill work?
if (fflag && ranged && (shield[i]->id == shid)) {
int fullchance;
fullchance = 40 + (getobsize(shield[i])*5);
if (pctchance(fullchance)) {
blocked = B_TRUE;
}
}
// did we block with this object?
if (!blocked && skillcheck(victim, SC_SHIELDBLOCK, difficulty, checkmod[i])) {
blocked = B_TRUE;
}
if (blocked) {
char shname[BUFLEN];
char victimname[BUFLEN];
getlfname(victim, victimname);
// announce
real_getobname(shield[i], shname, 1, B_PREMODS, B_NOCONDITION, B_BLINDADJUST, B_NOBLESSINGS, B_NOUSED, B_NOSHOWALL);
if (lf && isplayer(lf)) { // player is atatcking
msg("%s blocks %s with %s.", victimname, attackname, shname);
} else if ((lf && cansee(player, lf)) || cansee(player, victim)) { // monster is attacking
msg("%s block%s %s with %s.", victimname, isplayer(victim) ? "" : "s",
attackname, shname);
}
if (isshield(shield[i])) {
// apply all damage to shield.
// (blocking with weapons won't damage them)
takedamage(shield[i], dam, damtype, lf);
practice(victim, SK_SHIELDS, 1);
}
if (!isdeadob(shield[i])) {
if (lf) {
flag_t *f;
f = hasflag(lf->flags, F_ADHESIVE);
if (f && !skillcheck(victim, SC_STR, f->val[0], 0)) {
if (cansee(player, lf) || cansee(player, victim)) {
char attname[BUFLEN];
getlfname(lf, attname);
msg("^%c%s%s %s %s to %s!", getlfcol(victim, CC_BAD),
victimname, getpossessive(victimname), noprefix(shname),
(shield[i]->amt == 1) ? "sticks" : "stick", attname);
}
moveob(shield[i], lf->pack, shield[i]->amt);
}
}
}
// stop checking.
return B_TRUE;
}
}
return B_FALSE;
}
void criticalhit(lifeform_t *lf, lifeform_t *victim, enum BODYPART hitpos, object_t *wep, int dam, enum DAMTYPE damtype) {
object_t *o,*armour;
int protected = B_FALSE;
char lfname[BUFLEN],victimname[BUFLEN];
if ((int)hitpos == BP_NONE) return;
// replace some dam types
if (damtype == DT_UNARMED) damtype = DT_BASH;
if (damtype == DT_BITE) damtype = DT_SLASH;
if (damtype == DT_PIERCE) damtype = DT_SLASH;
if (damtype == DT_CHOP) damtype = DT_SLASH;
// special case - beheading multiheaded monsters
if (lf && victim && (damtype == DT_SLASH)) {
flag_t *selflag;
selflag = lfhasflagval(victim, F_CANSEVER, hitpos, NA, NA, NULL);
if (selflag) {
int i,nretflags,num;
char dambuf[BUFLEN];
char bpname[BUFLEN];
flag_t *retflag[MAXCANDIDATES];
strcpy(bpname, getbodypartname(victim, hitpos));
// special case
if (!victim->race->id == R_HYDRA) {
// remove it
addflag(victim->flags, F_NOBODYPART, hitpos, NA, NA, NULL);
// remove hasattack flags
getflags(victim->flags, retflag, &nretflags, F_CANCAST, F_HASATTACK, F_NONE);
for (i = 0; i < nretflags; i++) {
if (retflag[i]->id == F_CANCAST) {
if (retflag[i]->val[0] == selflag->val[2]) {
killflag(retflag[i]);
continue;
}
} else if (retflag[i]->id == F_HASATTACK) {
if (retflag[i]->val[2] == selflag->val[1]) {
killflag(retflag[i]);
continue;
}
}
}
}
if (cansee(player, victim)) {
char lfname[BUFLEN],vname[BUFLEN];
getlfname(victim, vname);
if (lf && cansee(player, lf)) { // can see who did it
getlfname(lf, lfname);
} else {
strcpy(lfname, "Something");
}
msg("^%c%s slice%s off %s%s %s!", getlfcol(victim, CC_VBAD),
lfname, isplayer(lf) ? "" : "s", vname, getpossessive(vname), bpname);
}
// take extra damage based on number of severable limbs
num = countflagsofid(victim->race->flags, F_CANSEVER);
if (victim->race->id == R_HYDRA) {
int regrow = B_TRUE;
if (wep) {
if (hasflag(wep->flags, F_ONFIRE) || (wep->material->id == MT_SILVER)) {
regrow = B_FALSE;
}
}
if (regrow) {
growhydrahead(victim, B_TRUE);
} else {
// lose a head
losehydrahead(victim);
}
} else {
sprintf(dambuf, "a severed %s", bpname);
losehp(victim, pctof(100/num, victim->maxhp), DT_DIRECT, lf, dambuf);
}
// drop the head
if (strlen(selflag->text)) {
addob(victim->cell->obpile, selflag->text);
}
return;
}
}
if (damtype == DT_BASH) {
switch (hitpos) {
default:
case BP_BODY:
if (pctchance(40)) {
// some kind of non-injury effect
switch (rnd(1,2)) {
case 1: fall(victim, lf, B_TRUE); break;
case 2:
if (lf) {
if (cansee(player, lf) || cansee(player, victim)) {
getlfname(lf, lfname);
getlfname(victim, victimname);
setfacing(victim, getrandomdirexcept(DT_COMPASS, victim->facing));
msg("%s%s blow spins %s around!", lfname, getpossessive(lfname),victimname);
}
} else {
if (isplayer(victim) || cansee(player, victim)) {
getlfname(victim, victimname);
setfacing(victim, getrandomdirexcept(DT_COMPASS, victim->facing));
msg("%s is spun around!", victimname);
}
}
break;
}
}
if ((armour = getarmour(victim, BP_BODY)) != NULL) {
protected = checkcritprotection(victim,armour);
takedamage(armour, dam, damtype, lf);
}
if (!protected) injure(victim, BP_BODY, damtype, IJ_NONE);
break;
case BP_HEAD:
if (pctchance(80)) fall(victim, lf, B_TRUE);
stun(victim, 2);
// chance of your helmet falling off
o = getarmour(victim, BP_HEAD);
if (o) {
if (onein(2)) {
if (isplayer(victim)) {
char buf[BUFLEN];
getobname(o, buf, o->amt);
msg("Your %s falls off!", noprefix(buf));
} else if (cansee(player, victim)) {
char buf[BUFLEN], lfname[BUFLEN];
getobname(o, buf, o->amt);
getlfname(victim, lfname);
msg("%s%s %s falls off!", lfname, getpossessive(lfname), noprefix(buf));
}
moveob(o, victim->cell->obpile, o->amt);
} else {
/*
if (isplayer(victim)) {
msg("Your %s protects you.", noprefix(obname));
} else if (cansee(player, victim)) {
getlfname(victim, victimname);
msg("%s%s %s protects it.", victimname, getpossessive(victimname), noprefix(obname));
}
*/
takedamage(o, dam, damtype, lf);
}
} else {
injure(victim, BP_HEAD, damtype, IJ_NONE);
}
break;
case BP_HANDS:
if ((armour = getarmour(victim, BP_HANDS)) != NULL) {
protected = checkcritprotection(victim,armour);
takedamage(armour, dam, damtype, lf);
}
if (!protected) injure(victim, BP_HANDS, damtype, IJ_NONE);
if (onein(2)) {
// drop your weapon!
o = getweapon(victim);
if (o) {
drop(o, ALL);
}
break;
}
case BP_LEGS:
if (pctchance(70)) fall(victim, lf, B_TRUE);
if ((armour = getarmour(victim, BP_LEGS)) != NULL) {
/*
getobname(armour, obname, armour->amt);
if (isplayer(victim)) {
msg("Your %s protects you.", noprefix(obname));
} else if (cansee(player, victim)) {
getlfname(victim, victimname);
msg("%s%s %s protects it.", victimname, getpossessive(victimname), noprefix(obname));
}
*/
takedamage(armour, dam, damtype, lf);
} else {
injure(victim, BP_LEGS, damtype, IJ_NONE);
}
break;
}
} else if (damtype == DT_SLASH) {
if ((armour = getarmour(victim, hitpos)) != NULL) {
protected = checkcritprotection(victim,armour);
takedamage(armour, dam, damtype, lf);
}
if (!protected) injure(victim, hitpos, damtype, IJ_NONE);
} else if (damtype == DT_EXPLOSIVE) {
if ((armour = getarmour(victim, hitpos)) != NULL) {
int min,max;
protected = checkcritprotection(victim,armour);
max = getobmaxhp(armour);
min = max / 2;
limit(&min, 1, NA);
takedamage(armour, rnd(min,max), DT_EXPLOSIVE, lf);
}
if (!protected) injure(victim, hitpos, damtype, IJ_NONE);
}
if (lf) {
if (lfhasflag(lf, F_CRITKNOCKDOWN) && !isprone(victim)) {
fall(victim, lf, B_TRUE);
}
}
}
int damtypecausesbleed(enum DAMTYPE dt, object_t *fromob) {
switch (dt) {
case DT_PIERCE:
case DT_SLASH:
case DT_BITE:
case DT_CHOP:
case DT_EXPLOSIVE:
return B_TRUE;
case DT_PROJECTILE:
if (fromob && hasflag(fromob->flags, F_MISSILEDAM)) return B_TRUE;
break;
default:
break;
}
return B_FALSE;
}
int damtypecausescriteffects(enum DAMTYPE dt) {
switch (dt) {
case DT_BASH: case DT_UNARMED:
case DT_SLASH: case DT_PIERCE: case DT_BITE: case DT_CHOP:
/*
case DT_EXPLOSIVE:
case DT_FALL:
*/
return B_TRUE;
default:
break;
}
return B_FALSE;
}
// returns the amount of damage the armour blocked...
int getarmourdamreduction(lifeform_t *lf, object_t *wep, int dam, enum DAMTYPE damtype) {
int reduceamt = 0;
int ar,min,max;
//int pctrange;
object_t *o;
if (wep && hasflag(wep->flags, F_ARMOURIGNORE)) {
return 0;
}
ar = getarmourrating(lf, NULL, NULL, NULL, NULL);
// between 25% and 75% of AR.
// ie. with AR of 20, all damage is reduced by 5-15.
//pctrange = rnd(25,75);
//reduceamt = pctof(pctrange, ar);
getarrange(ar, &min, &max);
reduceamt = rnd(min, max);
// special case
if (damtype == DT_PROJECTILE) {
o = hasequippedobid(lf->pack, OT_FLAKJACKET);
if (o) {
// stop ALL missile damage
reduceamt = dam;
}
}
if (wep && reduceamt) {
flag_t *pierce = NULL;
// and if weapon is armour piercing, you always take at least its
// armourpiercing valut.
pierce = hasflag(wep->flags, F_ARMOURPIERCE);
if (pierce) {
if (pierce->val[0] < 0) reduceamt = 0;
else reduceamt -= pierce->val[0];
}
}
limit(&reduceamt, 0, dam);
return reduceamt;
}
// for a given ArmourRating (AR), return range of damage which it might reduce
void getarrange(int arating, int *min, int *max) {
*min = pctof(50,arating);
*max = pctof(80, arating);
}
/*
object_t *getattackwep(lifeform_t *lf, obpile_t **unarmedpile, flag_t **unarmedflag) {
object_t *wep;
wep = getweapon(lf);
if (!wep) {
// ie. unarmed
*unarmedpile = getunarmedweapon(lf, unarmedflag);
if ((*unarmedpile)->first) {
wep = (*unarmedpile)->first;
} else {
wep = NULL;
}
}
return wep;
}
*/
enum DAMTYPE getdamtype(object_t *wep) {
flag_t *f;
enum DAMTYPE dt = DT_NONE;
f = hasflag(wep->flags, F_DAM);
if (f) {
dt = f->val[0];
} else {
// default - you are just bashing with whatever
// you weilded.
dt = DT_BASH;
}
return dt;
}
int getextradamlf(lifeform_t *lf, int *dam, enum DAMTYPE *damtype, int *ndam, int wantmax) {
flag_t *f;
int i;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
if (lf && lfhasflag(lf, F_PHANTASM)) {
return *dam;
}
// special case - EXTRADAM of same damtype goes onto INITIAL dam[], rather than
// adding a new one.
getflags(lf->flags, retflag, &nretflags, F_EXTRADAM, F_NONE);
for (i = 0; i < nretflags; i++) {
int *damwhere;
int *damtypewhere;
int doinc = B_FALSE;
int amtextra = 0;
f = retflag[i];
if (f->val[2] == NA) {
amtextra = real_roll(f->text, wantmax); // addition
} else {
amtextra = f->val[2];
}
if ((f->val[0] == NA) || (f->val[0] == *damtype)) {
// addition to the first one
damwhere = dam;
damtypewhere = damtype;
*(damwhere) += amtextra; // addition
} else {
// add a new damtype
damwhere = (dam + *ndam);
damtypewhere = (damtype + *ndam);
doinc = B_TRUE;
*(damwhere) = amtextra; // set
*(damtypewhere) = f->val[0];
}
if ((f->lifetime == FROMOBEQUIP) ||
(f->lifetime == FROMOBHOLD) ||
(f->lifetime == FROMOBACTIVATE) ) {
object_t *obfrom;
obfrom = findobbyid(lf->pack, f->obfrom);
if (obfrom) {
int bonusdam = 0;
sumflags(obfrom->flags, F_BONUS, &bonusdam, NULL, NULL);
*(damwhere) += bonusdam;
}
}
if (doinc) {
(*ndam)++;
}
}
return *dam;
}
int getextradamwep(object_t *wep, int *dam, enum DAMTYPE *damtype, int *ndam, int fordisplay) {
flag_t *f;
int i;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
lifeform_t *owner;
owner = wep->pile->owner;
if (owner && lfhasflag(owner, F_PHANTASM)) {
return *dam;
}
// weapons like cattle prod deal extra damage if they have charges left
f = hasflag(wep->flags, F_EXTRADAMWITHCHARGES);
if (f) {
int charges;
charges = getcharges(wep);
if (fordisplay && !chargesknown(wep)) {
// ie don't display the extra damage
charges = 0;
}
if ((f->val[2] == IFACTIVE) && !isactivated(wep) ) {
charges = 0;
}
if (charges > 0) {
if (!fordisplay) {
// use up a charge
usecharge(wep);
}
// deal extra damage of the given type
*(dam + *ndam) = real_roll(f->text, fordisplay);
*(damtype + *ndam) = f->val[0];
(*ndam)++;
}
}
// enchanted weapons only deal magic damage if the user has remaining mp.
if (owner && owner->mp) {
f = hasflag(wep->flags, F_ENCHANTED);
if (f) {
if (strlen(f->text)) {
*(dam + *ndam) = real_roll(f->text, fordisplay);
} else {
*(dam + *ndam) = real_roll("1d2", fordisplay); // default: 1d2 extra damage
}
*(damtype + *ndam) = DT_MAGIC;
(*ndam)++;
}
}
getflags(wep->flags, retflag, &nretflags, F_EXTRADAM, F_FROZEN, F_ONFIRE, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if (f->id == F_EXTRADAM) {
int *damwhere;
int *damtypewhere;
int doinc = B_FALSE;
if ((f->val[0] == NA) || (f->val[0] == *damtype)) {
// addition to the first one
damwhere = dam;
damtypewhere = damtype;
*(damwhere) += real_roll(f->text, fordisplay); // addition
} else {
// add a new damtype
damwhere = (dam + *ndam);
damtypewhere = (damtype + *ndam);
doinc = B_TRUE;
*(damwhere) = real_roll(f->text, fordisplay); // set
*(damtypewhere) = f->val[0];
}
if (doinc) {
(*ndam)++;
}
}
if (f->id == F_ONFIRE) {
if (strlen(f->text)) {
*(dam + *ndam) = real_roll(f->text, fordisplay);
} else {
*(dam + *ndam) = real_roll("1d4", fordisplay);
}
*(damtype + *ndam) = DT_FIRE;
(*ndam)++;
} else if (f->id == F_HOT) {
if (strlen(f->text)) {
*(dam + *ndam) = real_roll(f->text, fordisplay);
} else {
*(dam + *ndam) = real_roll("1d2", fordisplay);
}
*(damtype + *ndam) = DT_HEAT;
(*ndam)++;
} else if (f->id == F_FROZEN) {
*(dam + *ndam) = real_roll("1d4", fordisplay);
*(damtype + *ndam) = DT_COLD;
(*ndam)++;
}
}
return *dam;
}
// if damflag isn't passed in, it will be taken from the object
void getdamrange(object_t *o, flag_t *f, int *min, int *max) {
int mindam,maxdam;
if (!f) {
f = hasflag(o->flags, F_DAM);
}
if (f) {
if (hasflag(o->flags, F_MASTERWORK)) {
// 85%-100%
mindam = pctof(85,f->val[1]);
maxdam = f->val[1];
} else if (hasflag(o->flags, F_SHODDY)) {
// 25% - 75%
mindam = pctof(25, f->val[1]);
maxdam = pctof(75, f->val[1]);
} else {
// 50%-100%
mindam = f->val[1] / 2;
maxdam = f->val[1];
}
} else {
// TODO wepaon does damage based on weight
mindam = 0;
maxdam = 0;
}
limit(&mindam, 0, NA);
limit(&maxdam, mindam, NA);
if (min) *min = mindam;
if (max) *max = maxdam;
}
// roll for damage
int getdamroll(object_t *o, lifeform_t *victim, flag_t *damflag) {
int dam;
int bonusdam = 0;
flag_t *f;
lifeform_t *owner;
owner = o->pile->owner;
if (owner && lfhasflag(owner, F_PHANTASM)) {
return 0;
}
if (damflag) {
int min,max;
getdamrange(o, damflag, &min, &max);
dam = rnd(min,max);
if (isblessed(o)) {
int dam2;
// blessed weapons get two rolls, and take the best
dam2 = rnd(min,max);
if (dam2 > dam) dam = dam2;
} else if (iscursed(o)) {
int dam2;
// cursed weapons get two rolls, and take the worst
dam2 = rnd(min,max);
if (dam2 < dam) dam = dam2;
}
} else {
// TODO weapon does bashing damage based on weight
dam = rnd(1,2);
}
// modify for bonus
sumflags(o->flags, F_BONUS, &bonusdam, NULL, NULL);
dam += bonusdam;
if (dam < 0) dam = 0;
// special effects ?
f = hasflag(o->flags, F_BALANCE);
if (f) {
if (owner && victim) {
float ratio;
ratio = (float)owner->maxhp / (float)victim->maxhp;
if (ratio >= 1.25) {
// heals instead!
dam = -dam;
} else if (ratio <= 0.75) {
// extra dam!
dam = (int) ((float)dam * ratio);
}
}
}
if (victim) {
if (hasbleedinginjury(victim, BP_BODY)) {
if (willbleedfrom(victim, BP_BODY)) {
// extra damage
dam += rnd(1,2);
}
}
}
if (o->pile->owner) {
int pct;
sumflags(o->pile->owner->flags, F_MELEEDAMPCT, &pct, NULL, NULL);
if (pct != 0) {
dam = pctof(pct, dam);
}
}
return dam;
}
// returns amt of hp to modify damage by. (in range -3 to 3)
int getstrdammod(lifeform_t *lf) {
int mod = 0;
int base;
base = getattr(lf, A_STR);
if ((base >= 45) && (base <= 60)) {
mod = 0;
} else if (base > 60) {
base -= 60; // ie. 0 - 40
mod = base / 13;
} else { // ie. 0 through 44
base = 45 - base;
mod = -(base / 12);
}
return mod;
}
int armourcanstopdam(enum DAMTYPE damtype) {
switch (damtype) {
case DT_PIERCE:
case DT_SLASH:
case DT_BASH:
case DT_BITE:
case DT_CHOP:
case DT_PROJECTILE:
case DT_UNARMED:
case DT_ACID:
case DT_EXPLOSIVE:
case DT_MAGIC:
case DT_CRUSH:
return B_TRUE;
default:
break;
}
return B_FALSE;
}
// ie. caused by hitting something with a melee weapon
int ismeleedam(enum DAMTYPE damtype) {
switch (damtype) {
case DT_PIERCE:
case DT_SLASH:
case DT_BASH:
case DT_BITE:
case DT_CHOP:
case DT_PROJECTILE:
case DT_UNARMED:
case DT_CRUSH:
return B_TRUE;
default:
break;
}
return B_FALSE;
}
int iskineticdam(enum DAMTYPE damtype) {
switch (damtype) {
case DT_BASH:
case DT_BITE:
case DT_CHOP:
case DT_CRUSH:
case DT_EXPLOSIVE:
case DT_FALL:
case DT_PIERCE:
case DT_PROJECTILE:
case DT_SLASH:
case DT_UNARMED:
return B_TRUE;
default:
break;
}
return B_FALSE;
}
int isphysicaldam(enum DAMTYPE damtype) {
switch (damtype) {
case DT_BASH:
case DT_BITE:
case DT_CHOP:
case DT_COLD:
case DT_CRUSH:
case DT_ELECTRIC:
case DT_EXPLOSIVE:
case DT_FALL:
case DT_FIRE:
case DT_MAGIC:
case DT_PIERCE:
case DT_PROJECTILE:
case DT_SLASH:
case DT_UNARMED:
return B_TRUE;
default:
break;
}
return B_FALSE;
}
// 'howmuch' is the amount to adjust 'val' by for every size bracket
// difference.
//
// how can be M_PCT (adjust by this val% per size bracket)
// how can be M_VAL (adjust by this number per size bracket)
//
// if lf is bigger than victim, ADD howmuch.
// if lf is smaller than victim, SUBTRACT howmuch.
void modifyforsize(int *val, lifeform_t *lf, lifeform_t *victim, int howmuch, enum MODTYPE how) {
enum LFSIZE szlf,szvictim;
assert(val);
szlf = getlfsize(lf);
szvictim = getlfsize(victim);
if (szvictim < szlf) {
// if defender is smaller...
if (how == M_VAL) {
// +howmuch per size difference
*val += (howmuch * (szlf - szvictim));
} else {
// +(howmuch*sizediff)% of original value
*val += (pctof(howmuch * (szlf - szvictim), *val));
}
} else if (szvictim > szlf) {
// if defender is bigger...
if (how == M_VAL) {
// -howmuch per size difference
*val -= (howmuch * (szvictim - szlf));
} else {
// +(howmuch*sizediff)% of original value
*val -= (pctof(howmuch * (szlf - szvictim), *val));
}
}
}
// returns true if we hit. also sets 'critical' if passed
int rolltohit(lifeform_t *lf, lifeform_t *victim, object_t *wep, int *critical, int *fumble, int *missby) {
int acc,baseacc,ev;
int gothit = B_FALSE;
enum SKILLLEVEL lorelev = PR_INEPT;
flag_t *f;
int db = B_FALSE;
char lfname[BUFLEN];
char vicname[BUFLEN];
int critpossible = B_TRUE;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
real_getlfname(lf, lfname, NULL, B_SHOWALL, B_CURRACE);
real_getlfname(victim, vicname, NULL, B_SHOWALL, B_CURRACE);
if (db) dblog("%s: rolling to hit %s", lfname, vicname);
// default
if (critical) *critical = 0;
if (fumble) *fumble = B_FALSE;
if (missby) *missby = 0;
if (lf && lfhasflag(lf, F_NOGIVECRITS)) {
critpossible = B_FALSE;
} else if (victim && lfhasflag(victim, F_NOTAKECRITS)) {
critpossible = B_FALSE;
}
// anticipate action spell?
if (lfhasflagval(victim, F_ANTICIPATE, lf->id, NA, NA, NULL)) {
if (db) dblog("%s: victim has anticipate action - MISS.", lfname);
return B_FALSE;
}
// remember lore about victim...
lorelev = getlorelevel(lf, victim->race->raceclass->id);
f = lfhasflag(lf, F_TRUESTRIKE);
if (f) {
if (f->val[0] > 1) {
f->val[0]--;
} else {
killflag(f);
}
gothit = B_TRUE;
if (db) dblog("%s: we have truestrike - HIT.", lfname);
} else if (critical && *critical && critpossible) {
// critical already set = forced critical
gothit = B_TRUE;
if (db) dblog("%s: pre-determined critical - HIT.", lfname);
} else {
int myroll;
int reachpenalty = 0;
int vicheight = 0;
// actually roll...
baseacc = getlfaccuracy(lf, wep);
acc = baseacc;
if (db) dblog("%s: base accuracy: %d", lfname, baseacc);
// size difference (penalty for attacking smaller ones)
modifyforsize(&acc, lf, victim, -5, M_VAL);
if (db) dblog("%s: modified for victim size -> %d", lfname, acc);
// easier to hit victims who are prone.
if (isprone(victim)) {
acc += 30;
if (db) dblog("%s: +30 for prone victim -> %d", lfname, acc);
}
if (lfhasflag(lf, F_AIMEDSTRIKE)) {
acc -= 20;
if (db) dblog("%s: -20 for aimed strike -> %d", lfname, acc);
}
// easier to hit things which have grabbed you
if (lfhasflagval(lf, F_GRABBEDBY, victim->id, NA, NA, NULL)) {
acc += 30;
if (db) dblog("%s: +30 as victim is holding us -> %d", lfname, acc);
}
// MUCH easier to hit things which you have grabbed
if (lfhasflagval(lf, F_GRABBING, victim->id, NA, NA, NULL)) {
acc += 50;
if (db) dblog("%s: +50 as we are holding victim -> %d", lfname, acc);
}
if (wep && (getcelldist(lf->cell, victim->cell) <= 1)) {
f = hasflag(wep->flags, F_ADJACCMOD);
if (f) {
acc += f->val[0];
if (db) dblog("%s: %s%d for weapon adjacency modifier -> %d", lfname,
(f->val[0] < 0) ? "" : "+", f->val[0], acc);
}
}
if (!canreach(lf, victim, &reachpenalty)) {
acc -= (10*reachpenalty);
if (db) dblog("%s: -%d reach penalty -> %d", lfname, (10*reachpenalty), acc);
}
// modify for defender's evasion
if (canevade(victim, lf)) {
ev = getevasion(victim);
} else {
ev = 0;
}
acc -= ev;
if (db) dblog("%s: minus victim's modified evasion (%d) -> %d", lfname, ev, acc);
// modify if victim is flying and we're not
if (isairborne(victim,&vicheight)) {
if (!isairborne(lf, NULL)) {
acc -= (5 * vicheight);
}
}
// modify if we can't see the victim
if (!cansee(lf, victim)) {
acc -= 30;
if (db) dblog("%s: -30 for not being able to see victim -> %d", lfname, acc);
}
// metal weapon versus magnetic shield?
if (lfhasflag(victim, F_MAGSHIELD) && ismetal(wep->material->id)) {
acc -= 45;
if (db) dblog("%s: -45 for metal weapon vs magshield -> %d", lfname, acc);
}
// victim immobile or asleep?
if (isimmobile(victim) || lfhasflag(victim, F_EATING)) {
acc += 50;
if (db) dblog("%s: +50 for immobile victim -> %d", lfname, acc);
}
// modify for lore level
if (lorelev != PR_INEPT) {
acc += (lorelev*10);
if (db) dblog("%s: +%d for knowledge about victim's race -> %d", lfname, lorelev*10, acc);
}
// modify for attacking while climbing
if (isclimbing(lf) && !lfhasflag(lf, F_SPIDERCLIMB)) {
switch (getskill(lf, SK_CLIMBING)) {
case PR_INEPT: acc -= 50; break;
case PR_NOVICE: acc -= 50; break;
case PR_BEGINNER: acc -= 30; break;
case PR_ADEPT: acc -= 20; break;
case PR_SKILLED: acc -= 10; break;
case PR_EXPERT: acc -= 5; break;
default:
case PR_MASTER: break;
}
if (db) dblog("%s: modified for attacking while climbing -> %d", lfname, acc);
}
limit(&acc, 0, 100);
if (db) dblog("%s: FINAL ACCURACY: %d%%", lfname, acc);
if (db) {
msg("%s vs. %s - %d%% hit chance", lfname, vicname, acc);
}
//if (aidb) dblog(".oO { my modified chance to hit is %d %% }", acc);
myroll = rnd(1,100);
if (myroll <= acc) {
gothit = B_TRUE;
} else {
gothit = B_FALSE;
if (missby) *missby = myroll - acc;
}
}
// critical chance
if (critical && gothit && critpossible) {
if (lfhasflag(lf, F_AIMEDSTRIKE)) {
*critical = 1;
} else {
int critroll;
int minroll = 1;
critroll = rnd(1,100);
// modify for lore level > pr_novice
if (lorelev > PR_NOVICE) {
int lorebonus;
lorebonus = ((lorelev-1)*5); // ie. up to 25% bonus
critroll -= lorebonus;
}
if (isplayer(lf)) {
f = lfhasflag(lf, F_MINCRITCHANCE);
if (f) {
minroll = f->val[0];
}
}
limit(&critroll, minroll, 100);
if (critroll <= getcritchance(lf, wep,victim)) *critical = 1;
}
}
if (fumble && !gothit) {
if (isexhausted(lf) || (baseacc <= 60) || !getweaponskill(lf, wep)) {
int nfailed = 0, i;
int nrolls = 1;
int newacc;
if (getskill(lf, SK_COMBAT) >= PR_BEGINNER) {
newacc = pctof(150, baseacc);
} else {
newacc = baseacc;
}
// chance to fumble
// if you miss, make more attack rolls. if you fail
// all of them, you fumble..
for (i = 0; i < nrolls; i++) {
if (!pctchance(newacc)) nfailed++;
}
if (nfailed >= nrolls) {
*fumble = B_TRUE;
if (missby) *missby = 100;
}
}
}
return gothit;
}
void wepeffects(flagpile_t *fp, cell_t *where, flag_t *damflag, int dam, int isunarmed) {
flag_t *f;
lifeform_t *victim;
lifeform_t *owner = NULL;
object_t *wep;
int i;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
char frombuf[BUFLEN];
if (!where) return;
wep = fp->ob;
if (wep && isdeadob(wep)) wep = NULL;
if (wep) {
cell_t *c;
c = getoblocation(wep);
if (c && c->lf) {
owner = c->lf;
}
} else {
owner = fp->owner;
}
victim = where->lf;
// calculate 'frombuf' string
strcpy(frombuf, "something unknown");
if (wep) {
if (owner) {
char lfname[BUFLEN];
char wepname[BUFLEN];
getlfnamea(owner, lfname);
getobname(wep, wepname, 1);
// ie. "a goblin's poisoned short sword"
snprintf(frombuf, BUFLEN, "%s%s %s",lfname,getpossessive(lfname), wepname);
} else {
char wepname[BUFLEN];
getobname(wep, wepname, 1);
// ie "a poisoned short sword"
snprintf(frombuf, BUFLEN, "%s", wepname);
}
} else {
if (owner) {
real_getlfname(owner, frombuf, NULL, B_SHOWALL, B_REALRACE);
} else {
strcpy(frombuf, "something unknown");
}
}
getflags(fp, retflag, &nretflags, F_AUTOTANGLE, F_DRAINONHIT, F_DRAINATTONHIT, F_FLAMESTRIKE, F_HEAVYBLOW, F_HITCONFER,
F_RACESLAY, F_REVENGE, F_RUSTED, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if ((f->id == F_AUTOTANGLE) && victim && !isdead(victim)) {
if (pctchance(f->val[0]) && !hasob(where->obpile, OT_VINE)) {
dospelleffects(owner, OT_S_ENTANGLE, f->val[1], victim, NULL, where, B_UNCURSED,
NULL, B_FALSE, NULL);
f->known = B_KNOWN;
}
} else if ((f->id == F_DRAINONHIT) && victim && !isdead(victim)) {
if (!leveldrain(victim, f->val[0], f->val[1], f->val[2], owner)) {
if (strlen(f->text)) {
gainhp(owner, roll(f->text));
}
}
} else if ((f->id == F_DRAINATTONHIT) && victim && !isdead(victim)) {
int amt;
amt = roll(f->text);
statdrain(victim, f->val[0], amt, f->val[1], f->val[2], owner);
} else if (f->id == F_FLAMESTRIKE) {
if (!hasob(where->obpile, OT_FIRESMALL)) {
// ignite
addobfast(where->obpile, OT_FIRESMALL);
// announce
if (haslos(player, where)) {
msg("^wA burst of fire erupts from the ground!");
f->known = B_KNOWN;
}
}
} else if ((f->id == F_RACESLAY) && victim && !isdead(victim) && (getraceclass(victim) == f->val[0])) {
char ownername[BUFLEN];
char wepname[BUFLEN];
char damstring[BUFLEN];
if (haslos(player, where)) {
char vname[BUFLEN];
getlfname(victim, vname);
msg("^wA pulse of lethal power blasts %s!", vname);
f->known = B_KNOWN;
}
real_getlfname(owner, ownername, NULL, B_SHOWALL, B_REALRACE);
getobname(wep, wepname, 1);
snprintf(damstring, BUFLEN, "%s%s %s",ownername, getpossessive(ownername), wepname);
losehp(victim, dam*3, DT_DIRECT, owner, damstring);
} else if ((f->id == F_REVENGE) && victim && !isdead(victim)) {
if (dam) { // only works if we did damage
lifeform_t *owner;
owner = wep->pile->owner;
if (owner && victim) {
float ratio;
float dampct;
int maxdam;
int extradam;
// figure out hp percentage
ratio = 1.0 - ((float)owner->hp / (float)owner->maxhp);
dampct = (ratio * 100); // ie. lower hp% = higher dampct
if (dampct >= 50) {
getdamrange(wep, NULL, NULL, &maxdam);
extradam = (int)((dampct/100) * (float)maxdam);
if (extradam > 0) {
char buf[BUFLEN];
char buf2[BUFLEN];
char obname[BUFLEN];
char damstring[BUFLEN];
char victimname[BUFLEN];
getlfname(owner, buf);
real_getlfname(owner, buf2, NULL, B_SHOWALL, B_REALRACE);
getlfname(victim, victimname);
getobname(wep, obname, 1);
// announce
if (isplayer(owner)) {
msg("^wYour %s blasts %s!",noprefix(obname),victimname);
f->known = B_TRUE;
} else if (cansee(player, owner)) {
msg("^w%s%s %s blasts %s!",buf, getpossessive(buf),noprefix(obname),victimname);
f->known = B_TRUE;
}
snprintf(damstring, BUFLEN, "%s%s blast of revenge",buf2, getpossessive(buf2));
losehp(victim, extradam, DT_DIRECT, owner, damstring);
}
} // end if dampct > 50
}
}
} else if ((f->id == F_HEAVYBLOW) && victim && owner) {
int dir;
int chance;
// lifeform flag works all the time. object flag only works sometimes.
if (wep) chance = 33;
else chance = 100;
if (pctchance(chance)) {
// knock back victim
dir = getdirtowards(owner->cell, victim->cell, victim, B_FALSE, DT_COMPASS);
knockback(victim, dir , 2, owner, 110, B_DOANNOUNCE, B_DODAM);
if (cansee(player, owner)) {
f->known = B_TRUE;
}
}
} else if ((f->id == F_HITCONFER) && victim ) {
// only works if we did damage
enum FLAG fid;
int howlong;
char *ftext;
flag_t *valflag = NULL;
fid = f->val[0];
ftext = f->text;
// hitconfer from owner can't be conferred if this was a weapon attack
// ie. if something has poisonous claws and hits you with a weapon, you
// don't get poisoned.
if (fp->owner && !isunarmed) {
continue;
}
// sometimes we can only hitconfer against certain races
if (hasflag(fp, F_HITCONFERRC)) {
if (!hasflagval(fp, F_HITCONFERRC, getraceclass(victim), NA, NA, NULL)) {
continue;
}
}
if (hasflag(fp, F_HITCONFERDEADONLY)) {
if (!isdead(victim)) {
continue;
}
}
// the f_poisoned flag stacks, others don't.
if (!lfhasflag(victim, fid) || (fid == F_POISONED)) {
int passedcheck = B_FALSE;
// do they get a saving throw?
if (f->val[1] != NA) {
int scdiff;
if (f->val[2] == NA) {
scdiff = 100; // default
} else {
scdiff = f->val[2];
}
if (skillcheck(victim, f->val[1], scdiff, 0)) {
passedcheck = B_TRUE;
}
}
if (!passedcheck) {
howlong = gethitconferlifetime(f->text, NULL, NULL);
// get conferred flag values
valflag = hasflag(f->pile, F_HITCONFERVALS);
if (fid == F_POISONED) {
// defaults - need to fill in the name of what poisoned us
enum POISONTYPE ptype = P_VENOM;
int ppower = 1;
assert(valflag);
ptype = valflag->val[0];
if (valflag->val[1] == NA) {
ppower = 1;
} else {
ppower = valflag->val[1];
}
if (!wep && strlen(ftext)) {
strcpy(frombuf, ftext);
} else if (ptype == P_LYCANTHROPY) {
char *p;
// special case - we need to remember what kind of
// creature to change into. since lycanthropy isn't fatal
// as such we can use the 'what caused you damage' field for
// this information
p = readuntil(frombuf, valflag->text, '^');
readuntil(frombuf, p, '^');
}
poison(victim, howlong, ptype, ppower, frombuf, owner ? owner->race->id : R_NONE, B_FALSE);
} else if (fid == F_ASLEEP) {
fallasleep(victim, ST_ASLEEP, howlong);
} else {
// flag values
if (valflag) {
addtempflag(victim->flags, fid, valflag->val[0], valflag->val[1], valflag->val[2], valflag->text, howlong);
} else {
addtempflag(victim->flags, fid, NA, NA, NA, NULL, howlong);
}
}
} // end if passedcheck
} // end (if victim doesn't already have the flag)
// was this from a poisoned weapon? if so the poison vanishes
if ((f->val[0] == F_POISONED) && (f->lifetime == FROMOBMOD)) {
killflag(f);
if (owner && isplayer(owner)) {
addflag(owner->flags, F_USEDPOISON, B_TRUE, NA, NA, NULL);
}
}
} else if ((f->id == F_RUSTED) && victim && !isdead(victim) ) {
int pct;
pct = f->val[0] * 10;
if (pctchance(pct)) {
poison(victim, PERMENANT, P_TETANUS, 1, frombuf, owner ? owner->race->id : R_NONE, B_FALSE);
}
} // end if (fid == xxx)
}
if (wep && owner && victim) {
enum DRAINTYPE draintype = DR_NONE;
flag_t *vampflag;
vampflag = lfhasflag(owner, F_VAMPIRIC);
if ((wep->type->id == OT_TEETH) && vampflag &&
((vampflag->val[0] == B_TRUE) || islowhp(victim))
) {
draintype = DR_FROMBITE;
} else if (hasflag(wep->flags, F_VAMPIRIC)) {
draintype = DR_FROMWEP;
}
if (draintype && !isimmuneto(victim->flags, DT_NECROTIC, B_FALSE)) {
int hpgain;
// drain life!
if (isresistantto(victim->flags, DT_NECROTIC, B_FALSE)) {
hpgain = dam;
} else {
hpgain = (dam/2);
}
if (hpgain && (owner->hp < owner->maxhp)) {
gainhp(owner, hpgain);
if (draintype == DR_FROMBITE) {
if (isplayer(owner)) {
char lfname[BUFLEN];
char victimname[BUFLEN];
getlfname(owner,lfname);
getlfname(victim, victimname);
msg("You suck %s%s blood!", victimname, getpossessive(victimname));
} else if (cansee(player, owner)) {
char lfname[BUFLEN];
char victimname[BUFLEN];
getlfname(owner,lfname);
getlfname(victim, victimname);
msg("%s sucks %s%s blood!", lfname, victimname, getpossessive(victimname));
}
} else {
if (isplayer(owner)) {
msg("Life force surges into you!");
}
}
}
}
}
}