2316 lines
58 KiB
C
2316 lines
58 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 "text.h"
|
|
|
|
extern lifeform_t *player;
|
|
|
|
extern enum ERROR reason;
|
|
|
|
int applyarmourdamage(lifeform_t *lf, object_t *wep, int dam, enum DAMTYPE damtype) {
|
|
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 = getequippedob(lf->pack, BP_BODY);
|
|
if (o && (o->type->id == OT_FLAKJACKET)) {
|
|
armour = o;
|
|
}
|
|
}
|
|
|
|
// figure out what bit of armour was hit
|
|
if (!armour) {
|
|
// pick a random piece of armour
|
|
armour = getrandomarmour(lf);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
return damtaken;
|
|
}
|
|
|
|
void applyarmourdamreduction(lifeform_t *lf, object_t *wep, int reduceamt, int *dam, enum DAMTYPE damtype) {
|
|
int db = B_FALSE;
|
|
int newdam = 0;
|
|
|
|
// armour can't stop armour-piercing weapons
|
|
if (hasflag(wep->flags, F_ARMOURPIERCE)) {
|
|
reduceamt = 0;
|
|
}
|
|
|
|
// figure out reduced damage value
|
|
if (dam) {
|
|
int ar;
|
|
|
|
newdam = *dam;
|
|
ar = getarmourrating(lf, 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,
|
|
} attacktype = AT_NONE;
|
|
void *attacktarget;
|
|
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;
|
|
|
|
// 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);
|
|
|
|
// anyone there? if so just attack.
|
|
if (c->lf) {
|
|
if (!force && isplayer(lf) && isprone(lf)) {
|
|
if (!warnabout("Really attack while prone (-4 accuracy)?")) {
|
|
return B_TRUE;
|
|
}
|
|
}
|
|
|
|
if (!force && isplayer(lf) && !areenemies(lf,c->lf) && (getraceclass(c->lf) != RC_PLANT) && cansee(lf, c->lf)
|
|
&& !lfhasflag(lf, F_RAGE)) {
|
|
char ch;
|
|
char victimname[BUFLEN];
|
|
char buf[BUFLEN];
|
|
getlfname(c->lf, victimname);
|
|
switch (getallegiance(c->lf)) {
|
|
case AL_PEACEFUL:
|
|
snprintf(buf, BUFLEN, "Really attack the peaceful %s?",noprefix(victimname));
|
|
break;
|
|
case AL_FRIENDLY:
|
|
snprintf(buf, BUFLEN, "Really attack the allied %s?",noprefix(victimname));
|
|
break;
|
|
default:
|
|
snprintf(buf, BUFLEN, "Really attack the allied %s?",noprefix(victimname));
|
|
break;
|
|
}
|
|
ch = askchar(buf, "yn","n", B_TRUE, B_FALSE);
|
|
if (ch == 'n') {
|
|
// cancel.
|
|
return B_TRUE;
|
|
}
|
|
attackedpeaceful = B_TRUE;
|
|
}
|
|
|
|
if (!force && isplayer(lf) && lfhasflag(lf, F_HASNEWLEVEL)) {
|
|
if (!warnabout(TEXT_WARN_ATTACK_NOXP)) {
|
|
return B_TRUE;
|
|
}
|
|
}
|
|
|
|
// player walked into someone who was feigning death?
|
|
if (isplayer(lf) && lfhasflag(c->lf, F_FEIGNINGDEATH) && !force) {
|
|
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!
|
|
if (isplayer(lf)) addflag(lf->flags, F_MOVED, B_TRUE, NA, NA, NULL);
|
|
taketime(lf, getmovespeed(lf));
|
|
return B_FALSE;
|
|
}
|
|
|
|
attacktype = AT_LF;
|
|
attacktarget = c->lf;
|
|
|
|
if (areallies(lf, attacktarget)) attackedfriend = B_TRUE;
|
|
if (!cansee(attacktarget, lf) || isfleeing(attacktarget)) attackedhelpless = B_TRUE;
|
|
} 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) && wepdullable(priwep)
|
|
&& (getattrbracket(getattr(player, A_IQ), A_IQ, NULL) >= AT_GTAVERAGE) &&
|
|
!lfhasflag(lf, F_RAGE)) {
|
|
char obname[BUFLEN],wepname[BUFLEN],buf[BUFLEN];
|
|
char ch;
|
|
real_getobname(o, obname, o->amt, B_FALSE, B_FALSE, B_TRUE, B_FALSE, B_FALSE);
|
|
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 {
|
|
// otehr attackable ob here?
|
|
o = hasobwithflag(c->obpile, F_ATTACKABLE);
|
|
|
|
if (o) {
|
|
attacktype = AT_OB;
|
|
attacktarget = o;
|
|
} else {
|
|
// TODO: attack wall?
|
|
if (isplayer(lf) && !lfhasflag(lf, F_HURRICANESTRIKE)) {
|
|
msg("There is nothing there to attack!");
|
|
}
|
|
return B_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
gotweapon = getweapons(lf, wep, damflag, &lastweaponidx, &op, &nweps);
|
|
for (i = 0; i < nweps; i++) {
|
|
validwep[i] = B_TRUE;
|
|
}
|
|
|
|
innateattacks = countinnateattacks(lf);
|
|
|
|
// take time
|
|
attacktime = getattackspeed(lf);
|
|
|
|
if (!lfhasflag(lf, F_COMBOSTRIKE)) {
|
|
taketime(lf, attacktime);
|
|
}
|
|
|
|
if (nweps <= 0) {
|
|
if (isplayer(lf)) {
|
|
msg("You cannot attack!");
|
|
}
|
|
return B_TRUE;
|
|
}
|
|
|
|
//maxattacks = nweps; // ie. all
|
|
|
|
maxattacks = getattacks(lf, NULL, NULL);
|
|
|
|
/*
|
|
// 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) {
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maxattacks) {
|
|
if (isplayer(lf)) addflag(lf->flags, F_MOVED, B_TRUE, NA, NA, NULL);
|
|
}
|
|
|
|
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;
|
|
victim = (lifeform_t *)attacktarget;
|
|
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) ) {
|
|
saysorry = B_TRUE;
|
|
}
|
|
|
|
// announce helpless attcaks
|
|
if (isplayer(lf) && attackedhelpless) {
|
|
char vname[BUFLEN];
|
|
getlfname(victim, vname);
|
|
if (isbehind(lf, victim)) {
|
|
msg("You attack %s from behind!", vname);
|
|
} else {
|
|
char *vn;
|
|
// strip "the" from "the xxx"
|
|
vn = strdup(vname);
|
|
vn = strrep(vn, "the ", "", NULL);
|
|
msg("You attack the helpless %s!", vn);
|
|
free(vn);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (attacklf(lf, victim, wep[i], damflag[i])) {
|
|
// failed
|
|
attacksdone = maxattacks;
|
|
break;
|
|
}
|
|
}
|
|
} else if (attacktype == AT_OB) {
|
|
if (attackob(lf, (object_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);
|
|
}
|
|
|
|
// now stop hiding
|
|
killflagsofid(lf->flags, F_HIDING);
|
|
|
|
if (saysorry) {
|
|
sayphrase(lf, SP_SORRY, -1, NA, NULL);
|
|
}
|
|
|
|
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 effects...
|
|
if ((attacktype == AT_LF) && isplayer(lf)) {
|
|
if (attackedfriend) {
|
|
angergodmaybe(R_GODMERCY, 100, GA_ATTACKALLY);
|
|
angergodmaybe(R_GODPURITY, 100, GA_ATTACKALLY);
|
|
switch (getalignment(attacktarget)) {
|
|
case AL_EVIL:
|
|
angergodmaybe(R_GODDEATH, 20, GA_ATTACKALLY); // even more
|
|
break;
|
|
case AL_GOOD:
|
|
angergodmaybe(R_GODPURITY, 20, GA_ATTACKALLY); // even more
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (attackedpeaceful) {
|
|
angergodmaybe(R_GODMERCY, 50, GA_ASSAULT);
|
|
angergodmaybe(R_GODPURITY, 50, GA_ASSAULT);
|
|
switch (getalignment(attacktarget)) {
|
|
case AL_EVIL:
|
|
angergodmaybe(R_GODDEATH, 20, GA_ASSAULT); // even more
|
|
break;
|
|
case AL_GOOD:
|
|
angergodmaybe(R_GODPURITY, 20, GA_ASSAULT); // even more
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (attackedhelpless) {
|
|
angergodmaybe(R_GODMERCY, 50, GA_ATTACKHELPLESS);
|
|
angergodmaybe(R_GODPURITY, 50, GA_ATTACKHELPLESS);
|
|
if (getalignment(attacktarget) != AL_EVIL) {
|
|
pleasegodmaybe(R_GODTHIEVES, 5);
|
|
pleasegodmaybe(R_GODDEATH, 10);
|
|
}
|
|
}
|
|
if (lfhasflag(lf, F_USEDPOISON)) {
|
|
killflagsofid(lf->flags, F_USEDPOISON);
|
|
angergodmaybe(R_GODPURITY, 100, GA_POISON);
|
|
angergodmaybe(R_GODMERCY, 25, GA_POISON);
|
|
pleasegodmaybe(R_GODDEATH, 3);
|
|
}
|
|
}
|
|
|
|
if (isplayer(lf)) {
|
|
// 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[100];
|
|
enum DAMTYPE damtype[100];
|
|
int ndam = 0;
|
|
char buf[BUFLEN];
|
|
char attackername[BUFLEN];
|
|
char victimname[BUFLEN],victimbpname[BUFLEN];
|
|
int fatal = B_FALSE;
|
|
int feigneddeath = B_FALSE;
|
|
int deflected = B_FALSE;
|
|
int weppassthrough = B_FALSE;
|
|
int firstisbackstab = B_FALSE;
|
|
int blocked = B_FALSE;
|
|
int hit = B_FALSE;
|
|
int critical = 0;
|
|
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;
|
|
|
|
if (wep) {
|
|
wepsk = getobskill(wep);
|
|
}
|
|
|
|
if (lfhasflag(lf, F_DEBUG)) {
|
|
aidb = B_TRUE;
|
|
}
|
|
|
|
if (hasflag(wep->flags, F_UNARMEDWEP)) {
|
|
isunarmed = B_TRUE;
|
|
}
|
|
|
|
moveeffects(lf);
|
|
|
|
if (isdead(lf)) {
|
|
return B_TRUE;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
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);
|
|
|
|
if (critical) {
|
|
object_t *armour;
|
|
char noun[BUFLEN];
|
|
critpos = getrandomcorebp(victim);
|
|
if (critpos != BP_NONE) {
|
|
armour = getequippedob(victim->pack, critpos);
|
|
if (armour) {
|
|
char armname[BUFLEN];
|
|
real_getobname(armour, armname, 1, B_FALSE, B_FALSE, B_TRUE, B_FALSE, B_FALSE);
|
|
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)) ) {
|
|
} 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! }");
|
|
|
|
// 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 (critical) {
|
|
// critical hit means an extra damage roll.
|
|
dam[0] += getdamroll(wep, victim, damflag);
|
|
}
|
|
if (aidb) dblog("rolled dam[%d] = %d",0,dam[0]);
|
|
|
|
if (dam[0] < 0) {
|
|
willheal = B_TRUE;
|
|
}
|
|
|
|
// damtype?
|
|
damtype[0] = getdamtype(wep);
|
|
|
|
if (!willheal) {
|
|
enum SKILLLEVEL slev;
|
|
float loremult;
|
|
// blessed vs undead
|
|
if (isblessed(wep) && lfhasflagval(victim, F_DTVULN, DT_HOLY, NA, NA, NULL)) {
|
|
// a little extra damage
|
|
dam[0] = (int) ( (float)dam[0] * 1.25 );
|
|
}
|
|
|
|
// modify for weapon skill, strength etc
|
|
applylfdammod(&dam[0], lf, wep);
|
|
|
|
// modify for size
|
|
modifyforsize(&dam[0], lf, victim, 5, M_PCT);
|
|
|
|
// backstab?
|
|
if ((damtype[0] == DT_PIERCE) && // using a stabbing weapon
|
|
getskill(lf, SK_BACKSTAB)) { // able to backstab
|
|
if (!lfhasflagval(victim, F_STABBEDBY, lf->id, NA, NA, NULL) ) { // haven't stabbed them before
|
|
// && !lfhasflagval(victim, F_TARGETLF, lf->id, NA, NA, NULL)) { // victim isnt attacking us
|
|
|
|
if (!cansee(victim, lf) || // victim can't see us
|
|
isfleeing(victim)) {
|
|
addflag(victim->flags, F_STABBEDBY, lf->id, NA, NA, NULL);
|
|
dam[0] *= (getskill(lf, SK_BACKSTAB));
|
|
firstisbackstab = B_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// target asleep?
|
|
if (lfhasflag(victim, F_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) {
|
|
loremult = 1;
|
|
} else {
|
|
loremult = 1 + (slev * 0.1);
|
|
}
|
|
dam[0] = (int) ( (float)dam[0] * loremult );
|
|
}
|
|
|
|
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);
|
|
getextradamlf(lf, &dam[0], &damtype[0], &ndam);
|
|
}
|
|
|
|
} else {
|
|
hit = B_FALSE;
|
|
ndam = 0;
|
|
}
|
|
|
|
if (ndam > 0) {
|
|
object_t *shield = NULL;
|
|
flag_t *f;
|
|
for (i = 0; i < ndam; i++) {
|
|
int reduceamt = 0;
|
|
int backstab = B_FALSE;
|
|
flag_t *rust;
|
|
|
|
if (firstisbackstab && (i == 0)) backstab = B_TRUE;
|
|
//dblog("initial dam[%d] = %d",i,dam[i]);
|
|
|
|
// slightly more damage for heavy blows
|
|
if (lfhasflag(lf, F_HEAVYBLOW) || hasflag(wep->flags, F_HEAVYBLOW)) {
|
|
dam[i] = (int)pctof(110,dam[i]);
|
|
//dblog("heavy blow makes dam[%d] = %d",i,dam[i]);
|
|
}
|
|
|
|
// modify for rust
|
|
rust = hasflag(wep->flags, F_RUSTED);
|
|
if (rust) {
|
|
switch (damtype[i]) {
|
|
case DT_PIERCE:
|
|
case DT_SLASH:
|
|
if (rust->val[0] >= R_TRUSTY) {
|
|
dam[i] -= (pctof(50, dam[i]));
|
|
} else if (rust->val[0] >= R_VRUSTY) {
|
|
dam[i] -= (pctof(25, dam[i]));
|
|
} else {
|
|
dam[i] -= (pctof(10, dam[i]));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == 0) {
|
|
if (lfhasflag(lf, F_HOLYAURA) && isvulnto(victim->flags, DT_HOLY)) {
|
|
damtype[i] = DT_HOLY;
|
|
}
|
|
// blocked by defender's shield?
|
|
shield = getshield(victim);
|
|
if (shield && cansee(victim, lf)) {
|
|
int difficulty;
|
|
difficulty = 25 + ((gethitdice(lf) - gethitdice(victim))*3 );
|
|
if (skillcheck(victim, SC_SHIELDBLOCK, difficulty, 0)) {
|
|
char shname[BUFLEN];
|
|
// announce
|
|
real_getobname(shield, shname, 1, B_TRUE, B_FALSE, B_TRUE, B_FALSE, B_FALSE);
|
|
if (isplayer(lf)) { // player is atatcking
|
|
msg("%s blocks your attack with %s.", victimname, shname);
|
|
} else if (cansee(player, lf) || cansee(player, victim)) { // monster is attacking
|
|
msg("%s block%s %s%s attack with %s.", victimname, isplayer(victim) ? "" : "s",
|
|
attackername, getpossessive(attackername),
|
|
shname);
|
|
}
|
|
// apply all damage to shield
|
|
takedamage(shield, dam[i], damtype[i]);
|
|
practice(victim, SK_SHIELDS, 1);
|
|
blocked = B_TRUE;
|
|
break; // stop processing damage now.
|
|
}
|
|
}
|
|
}
|
|
|
|
// modify based on resistances
|
|
adjustdamlf(victim, &dam[i], damtype[i]);
|
|
//dblog("adjusted for lf to dam[%d] = %d",i,dam[i]);
|
|
|
|
// armour doesn't reduce damage for backstabs or critical hits.
|
|
// BUT in the case of a critical hit, the armour might get
|
|
// damaged during criticalhit()
|
|
if (!backstab && !critical && ismeleedam(damtype[i])) {
|
|
// modify for defender's armour
|
|
reduceamt = getarmourdamreduction(victim, wep, dam[i], damtype[i]);
|
|
|
|
applyarmourdamreduction(victim, wep, reduceamt, &dam[i], damtype[i]);
|
|
|
|
//dblog("reduced by armour to dam[%d] = %d",i,dam[i]);
|
|
}
|
|
|
|
// if damage has dropped to zero, it's not a critical hit anymore.
|
|
if (dam[i] <= 0) {
|
|
critical = B_FALSE;
|
|
critpos = BP_NONE;
|
|
}
|
|
|
|
if (lfhasflag(lf, F_QUIVERINGPALM)) {
|
|
// make sure damage isn't fatal
|
|
if (dam[i] >= victim->hp) {
|
|
dam[i] = victim->hp - 1;
|
|
}
|
|
}
|
|
|
|
// will this hit be fatal?
|
|
if (dam[i] >= victim->hp) {
|
|
fatal = B_TRUE;
|
|
}
|
|
|
|
|
|
// monsters feigning death?
|
|
if (lfhasflag(victim, F_FEIGNINGDEATH)) {
|
|
killflagsofid(victim->flags, F_FEIGNINGDEATH);
|
|
} else if (!fatal && !isplayer(victim) && cancast(victim, OT_A_FEIGNDEATH, NULL)) {
|
|
if (onein(2) || isbleeding(victim)) {
|
|
// do it!
|
|
useability(victim, OT_A_FEIGNDEATH, lf, lf->cell);
|
|
feigneddeath = B_TRUE;
|
|
}
|
|
}
|
|
|
|
// announce it
|
|
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, fatal, isunarmed, buf);
|
|
if (strlen(buf)) {
|
|
warn("%s", buf);
|
|
}
|
|
}
|
|
if (!isplayer(lf) && !isplayer(victim)) {
|
|
noise(lf->cell, lf, NC_OTHER, 3, "fighting.", NULL);
|
|
}
|
|
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);
|
|
}
|
|
if (!hasflag(victim->flags, F_NODEATHANNOUNCE)) {
|
|
// don't also say "the xx dies"
|
|
addflag(victim->flags, F_NODEATHANNOUNCE, B_TRUE, NA, NA, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (willheal) {
|
|
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]);
|
|
}
|
|
} 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);
|
|
} else {
|
|
char attackername2[BUFLEN];
|
|
real_getlfname(lf, attackername2, B_FALSE);
|
|
if (lf->race->raceclass->id == RC_GOD) {
|
|
flag_t *gf;
|
|
gf = lfhasflag(lf, F_GODOF);
|
|
if (gf->val[0] == B_FEMALE) {
|
|
strcat(attackername2, " the Goddess of ");
|
|
} else {
|
|
strcat(attackername2, " the God of ");
|
|
}
|
|
strcat(attackername2, gf->text);
|
|
}
|
|
// victim loses hp
|
|
// don't adjust damage - we've already done that
|
|
if (wep && !isunarmed) {
|
|
char wepname[BUFLEN];
|
|
getobname(wep, wepname, 1);
|
|
/*
|
|
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);
|
|
}
|
|
losehp_real(victim, dam[i], damtype[i], lf, buf, B_FALSE, wep, B_FALSE);
|
|
|
|
// victim's armour loses hp
|
|
if (reduceamt && !critical) {
|
|
applyarmourdamage(victim, wep, dam[i], damtype[i]);
|
|
// train armour
|
|
practice(victim, SK_ARMOUR, 1);
|
|
}
|
|
if (backstab) {
|
|
practice(lf, SK_BACKSTAB, 1);
|
|
}
|
|
}
|
|
} // end foreach damtype
|
|
|
|
|
|
// other effects
|
|
if (!isdead(victim) && !blocked) {
|
|
// special weapon effects, as long as you're not doing a heavy blow
|
|
if (!lfhasflag(lf, F_HEAVYBLOW) && dam[0]) {
|
|
wepeffects(wep->flags, victim->cell, damflag, dam[0]);
|
|
}
|
|
if (isunarmed) {
|
|
f = lfhasflag(lf, F_FREEZINGTOUCH);
|
|
if (f) {
|
|
int diff;
|
|
diff = f->val[2];
|
|
if (isimmuneto(victim->flags, DT_COLD) || 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);
|
|
}
|
|
}
|
|
|
|
f = lfhasflag(lf, F_QUICKBITE);
|
|
if (f) {
|
|
if (isbleeding(victim)) {
|
|
int dam;
|
|
char lfname[BUFLEN];
|
|
dam = rolldie(f->val[0], f->val[1]) + f->val[2];
|
|
real_getlfname(lf, lfname, B_FALSE);
|
|
losehp_real(victim, dam, DT_BITE, lf, lfname, B_FALSE, NULL, B_FALSE);
|
|
if (isplayer(victim) || cansee(player, victim)) {
|
|
msg("^%c%s bites %s!", isplayer(victim) ? 'b' : 'n', lfname, victimname);
|
|
}
|
|
}
|
|
}
|
|
|
|
f = lfhasflag(lf, F_PACKATTACK);
|
|
if (f) {
|
|
int dir;
|
|
cell_t *c;
|
|
int nmatched = 0;
|
|
char lfname[BUFLEN];
|
|
getlfname(lf, lfname);
|
|
// count adjacent allies of name xx
|
|
for (dir = DC_N; dir <= DC_NW; dir++) {
|
|
c = getcellindir(victim->cell, dir);
|
|
if (c && c->lf) {
|
|
if (c->lf->race->baseid == lf->race->baseid) {
|
|
nmatched++;
|
|
}
|
|
}
|
|
}
|
|
if (nmatched >= f->val[2]) {
|
|
char damstring[BUFLEN];
|
|
snprintf(damstring, BUFLEN, "%s pack", lfname);
|
|
losehp_real(victim, f->val[0], f->val[1], lf, damstring, B_TRUE, NULL, B_FALSE);
|
|
if (isplayer(victim) || cansee(player, victim)) {
|
|
msg("%c%s pack attacks %s!", isplayer(victim) ? 'b' : 'c', lfname, victimname);
|
|
}
|
|
}
|
|
}
|
|
|
|
// critical hit effects
|
|
if (critical && damtypecausescriteffects(damtype[0])) {
|
|
criticalhit(lf, victim, critpos, dam[0], damtype[0]);
|
|
}
|
|
|
|
// confer flags from attacker?
|
|
if (dam[0]) {
|
|
wepeffects(lf->flags, victim->cell, damflag, dam[0]);
|
|
}
|
|
|
|
// special lifeform-based effects
|
|
if ((lf->race->id == R_COCKATRICE) && dam[0]) {
|
|
// first saving throw...
|
|
if (skillcheck(victim, SC_CON, 25, 0)) {
|
|
// slowed
|
|
addtempflag(victim->flags, F_SLOWACTMOVE, 15, NA, NA, NULL, 2);
|
|
} else {
|
|
// second saving throw...
|
|
if (skillcheck(victim, SC_CON, 25, 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->id == 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, lf->maxhp))) {
|
|
int willfall = B_FALSE,willinjure = B_FALSE,n;
|
|
getflags(victim->flags, retflag, &nretflags, F_FLYING, F_LEVITATING, F_NONE);
|
|
for (n = 0; n < nretflags; n++) {
|
|
if (!istransitoryflag(retflag[n]) || (retflag[n]->lifetime == FROMRACE)) {
|
|
if (retflag[n]->id == F_FLYING) willinjure = B_TRUE;
|
|
willfall = B_TRUE;
|
|
killflag(retflag[n]);
|
|
}
|
|
}
|
|
if (willfall) fall(victim, NULL, B_TRUE);
|
|
if (willinjure) injure(victim, getrandomcorebp(victim), DT_BASH);
|
|
}
|
|
// if victim can still move...
|
|
if (hasfreeaction(victim)) {
|
|
fightback(victim, lf);
|
|
}
|
|
} // end if !isdead(victim)
|
|
|
|
// retaliation happens even if victim died
|
|
if (!blocked) {
|
|
getflags(victim->flags, retflag, &nretflags, F_RETALIATE, F_NONE);
|
|
for (i = 0; i < nretflags; i++) {
|
|
f = retflag[i];
|
|
if (f->id == F_RETALIATE) {
|
|
int rdam;
|
|
char damstring[BUFLEN];
|
|
rdam = rolldie(f->val[0], f->val[1]);
|
|
if (cansee(player, victim)) {
|
|
msg("^%c%s%s %s %s %s!", isplayer(victim) ? 'b' : 'n', victimname, getpossessive(victimname),
|
|
noprefix(f->text),
|
|
getattackverb(victim, NULL, f->val[2], rdam, lf->maxhp),
|
|
attackername);
|
|
}
|
|
snprintf(damstring, BUFLEN, "%s%s %s", victimname, getpossessive(victimname), noprefix(f->text));
|
|
losehp_real(lf, rdam, f->val[2], victim, damstring, B_TRUE, NULL, B_TRUE);
|
|
}
|
|
}
|
|
}
|
|
} else { // miss!
|
|
if (aidb) dblog(".oO { i missed! }");
|
|
// 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 {
|
|
if (isplayer(lf)) {
|
|
msg("You miss %s.", victimname);
|
|
} else {
|
|
if (cansee(player, lf)) {
|
|
// capitalise first letter
|
|
snprintf(buf, BUFLEN, "%s",attackername);
|
|
|
|
msg("%s misses %s.", buf, victimname);
|
|
}
|
|
}
|
|
|
|
// train evasion
|
|
practice(victim, SK_EVASION, 1);
|
|
|
|
if (lfhasflag(victim, F_DODGES)) {
|
|
cell_t *adj;
|
|
|
|
adj = getrandomadjcell(victim->cell, WE_WALKABLE, B_NOEXPAND);
|
|
if (adj) {
|
|
flag_t *f;
|
|
msg("^w%s dodge%s!",victimname,isplayer(victim) ? "" : "s");
|
|
f = addflag(victim->flags, F_NOTIME, B_TRUE, NA, NA, NULL);
|
|
moveto(victim, adj, B_FALSE, B_FALSE);
|
|
killflag(f);
|
|
}
|
|
|
|
}
|
|
|
|
// chance that ai will give up if we can't reach the victim
|
|
if (!isplayer(lf) && !canreach(lf, victim, NULL)) {
|
|
if (pctchance(90)) {
|
|
loseaitargets(lf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 (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);
|
|
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;
|
|
}
|
|
|
|
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] = (int)((float)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);
|
|
getextradamlf(lf, &dam[0], &damtype[0], &ndam);
|
|
|
|
|
|
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 = getequippedob(lf->pack, 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]);
|
|
if (isplayer(lf) && hasflag(o->flags, F_LOCKED)) {
|
|
angergodmaybe(R_GODTHIEVES, 25, GA_MONEY);
|
|
}
|
|
|
|
} // end foreach damtype
|
|
|
|
// 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]);
|
|
}
|
|
|
|
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
|
|
if (rnd(1,2)) makeduller(wep, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return B_FALSE;
|
|
}
|
|
|
|
enum DAMTYPE basedamagetype(enum DAMTYPE dt) {
|
|
switch (dt) {
|
|
case DT_HEAT:
|
|
dt = DT_FIRE; break;
|
|
default:
|
|
break;
|
|
}
|
|
return dt;
|
|
}
|
|
|
|
void criticalhit(lifeform_t *lf, lifeform_t *victim, enum BODYPART hitpos, int dam, enum DAMTYPE damtype) {
|
|
object_t *o,*armour;
|
|
int protected = B_FALSE;
|
|
char lfname[BUFLEN],victimname[BUFLEN];
|
|
|
|
if (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;
|
|
|
|
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);
|
|
}
|
|
if (!protected) injure(victim, BP_BODY, damtype);
|
|
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(armour, dam, damtype);
|
|
}
|
|
} else {
|
|
injure(victim, BP_HEAD, damtype);
|
|
}
|
|
break;
|
|
case BP_HANDS:
|
|
if ((armour = getarmour(victim, BP_HANDS)) != NULL) {
|
|
protected = checkcritprotection(victim,armour);
|
|
takedamage(armour, dam, damtype);
|
|
}
|
|
if (!protected) injure(victim, BP_HANDS, damtype);
|
|
|
|
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);
|
|
} else {
|
|
injure(victim, BP_LEGS, damtype);
|
|
}
|
|
break;
|
|
}
|
|
} else if (damtype == DT_SLASH) {
|
|
if ((armour = getarmour(victim, hitpos)) != NULL) {
|
|
protected = checkcritprotection(victim,armour);
|
|
takedamage(armour, dam, damtype);
|
|
}
|
|
if (!protected) injure(victim, hitpos, damtype);
|
|
} else if (damtype == DT_EXPLOSIVE) {
|
|
if ((armour = getarmour(victim, hitpos)) != NULL) {
|
|
int min,max;
|
|
protected = checkcritprotection(victim,armour);
|
|
max = getobmaxhp(o);
|
|
min = max / 2;
|
|
limit(&min, 1, NA);
|
|
takedamage(armour, rnd(min,max), DT_EXPLOSIVE);
|
|
}
|
|
if (!protected) injure(victim, hitpos, damtype);
|
|
}
|
|
|
|
if (lf) {
|
|
if (lfhasflag(lf, F_CRITKNOCKDOWN) && !isprone(victim)) {
|
|
fall(victim, lf, B_TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
int damtypecausesbleed(enum DAMTYPE dt) {
|
|
switch (dt) {
|
|
case DT_PIERCE:
|
|
case DT_SLASH:
|
|
case DT_BASH:
|
|
case DT_BITE:
|
|
case DT_CHOP:
|
|
case DT_PROJECTILE:
|
|
case DT_EXPLOSIVE:
|
|
case DT_UNARMED:
|
|
case DT_FALL:
|
|
return B_TRUE;
|
|
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 (hasflag(wep->flags, F_ARMOURPIERCE)) {
|
|
reduceamt = 0;
|
|
}
|
|
|
|
ar = getarmourrating(lf, 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 = getequippedob(lf->pack, BP_BODY);
|
|
if (o && (o->type->id == OT_FLAKJACKET)) {
|
|
// stop ALL missile damage
|
|
reduceamt = dam;
|
|
}
|
|
}
|
|
|
|
if (reduceamt < 0) reduceamt = 0;
|
|
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(20,arating);
|
|
*max = pctof(60, 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) {
|
|
flag_t *f;
|
|
int i;
|
|
flag_t *retflag[MAXCANDIDATES];
|
|
int nretflags = 0;
|
|
// special case - EXTRADAM goes onto INITIAL dam[] if the same type, rather than
|
|
// adding a new one.
|
|
getflags(lf->flags, retflag, &nretflags, F_EXTRADAM, 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)) {
|
|
damwhere = dam;
|
|
damtypewhere = damtype;
|
|
*(damwhere) += roll(f->text); // addition
|
|
} else {
|
|
damwhere = (dam + *ndam);
|
|
damtypewhere = (damtype + *ndam);
|
|
*(damwhere) = roll(f->text); // set
|
|
doinc = B_TRUE;
|
|
}
|
|
|
|
*(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) {
|
|
flag_t *f;
|
|
int i;
|
|
flag_t *retflag[MAXCANDIDATES];
|
|
int nretflags = 0;
|
|
lifeform_t *owner;
|
|
owner = wep->pile->owner;
|
|
|
|
// 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) = roll(f->text);
|
|
} else {
|
|
*(dam + *ndam) = roll("1d2"); // default: 1d2 extra damage
|
|
}
|
|
*(damtype + *ndam) = DT_MAGIC;
|
|
(*ndam)++;
|
|
}
|
|
}
|
|
|
|
getflags(wep->flags, retflag, &nretflags, F_FROZEN, F_ONFIRE, F_NONE);
|
|
for (i = 0; i < nretflags; i++) {
|
|
f = retflag[i];
|
|
if (f->id == F_ONFIRE) {
|
|
if (strlen(f->text)) {
|
|
*(dam + *ndam) = roll(f->text);
|
|
} else {
|
|
*(dam + *ndam) = rolldie(1,4);
|
|
}
|
|
*(damtype + *ndam) = DT_FIRE;
|
|
(*ndam)++;
|
|
} else if (f->id == F_FROZEN) {
|
|
*(dam + *ndam) = rolldie(1,4);
|
|
*(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)) {
|
|
// 75%-100%
|
|
mindam = pctof(75,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;
|
|
|
|
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) {
|
|
lifeform_t *owner;
|
|
owner = o->pile->owner;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dam;
|
|
}
|
|
|
|
// returns a multiplier
|
|
float getstrdammod(lifeform_t *lf) {
|
|
float mod = 0;
|
|
float base;
|
|
// <9 = penalty
|
|
// 9,10,11,12 = average
|
|
// >12 = bonus
|
|
|
|
base = getattr(lf, A_STR);
|
|
|
|
if ((base >= 9) && (base <= 12)) {
|
|
mod = 1;
|
|
} else if (base > 12) {
|
|
base -= 12; // ie. 1 - 6
|
|
|
|
mod = 1 + (base / 20.0); // ie. up to 1.3 / +30%
|
|
} else { // ie. 0 through 8
|
|
// 0 = 0.1
|
|
// 1 = 0.2
|
|
// 2 = 0.3
|
|
// 3 = 0.4
|
|
// 4 = 0.5
|
|
// 5 = 0.6
|
|
// 6 = 0.7
|
|
// 7 = 0.8
|
|
// 8 = 0.9
|
|
|
|
mod = (base * 0.1); // ie. 8 -> 0.8 or 4 -> 0.4
|
|
mod += 0.1; // ie. 8 -> 0.9 or 4 -> 0.5
|
|
}
|
|
return mod;
|
|
}
|
|
|
|
|
|
// 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 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 acc,ev;
|
|
int gothit = B_FALSE;
|
|
enum SKILLLEVEL lorelev = PR_INEPT;
|
|
flag_t *f;
|
|
|
|
// 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;
|
|
} else if (critical && *critical) {
|
|
gothit = B_TRUE;
|
|
} else {
|
|
int reachpenalty = 0;
|
|
// actually roll...
|
|
acc = getlfaccuracy(lf, wep);
|
|
|
|
// size difference (penalty for attacking smaller ones)
|
|
modifyforsize(&acc, lf, victim, -5, M_VAL);
|
|
|
|
// easier to hit victims who are prone.
|
|
if (isprone(victim)) {
|
|
acc += 30;
|
|
}
|
|
|
|
if (lfhasflag(lf, F_AIMEDSTRIKE)) {
|
|
acc -= 40;
|
|
}
|
|
|
|
// easier to hit things which have grabbed you
|
|
if (lfhasflagval(lf, F_GRABBEDBY, victim->id, NA, NA, NULL)) {
|
|
acc += 30;
|
|
}
|
|
|
|
if (!canreach(lf, victim, &reachpenalty)) {
|
|
acc -= (15*reachpenalty);
|
|
}
|
|
|
|
// modify for defender's evasion
|
|
if (isprone(victim) || !cansee(victim, lf)) {
|
|
ev = 0;
|
|
} else {
|
|
ev = getevasion(victim);
|
|
}
|
|
|
|
acc -= ev;
|
|
|
|
// modify if we can't see the victim
|
|
if (!cansee(lf, victim)) acc -= 50;
|
|
// metal weapon versus magnetic shield?
|
|
if (lfhasflag(victim, F_MAGSHIELD) && ismetal(wep->material->id)) acc -= 45;
|
|
// victim immobile or asleep?
|
|
if (isimmobile(victim) || lfhasflag(victim, F_EATING)) acc += 50;
|
|
// modify for lore level
|
|
if (lorelev != PR_INEPT) acc += (lorelev*10);
|
|
|
|
// modify for attacking while climbing
|
|
if (isclimbing(lf)) {
|
|
switch (getskill(lf, SK_CLIMBING)) {
|
|
case PR_INEPT: acc -= 100; break;
|
|
case PR_NOVICE: acc -= 100; break;
|
|
case PR_BEGINNER: acc -= 75; break;
|
|
case PR_ADEPT: acc -= 50; break;
|
|
case PR_SKILLED: acc -= 25; break;
|
|
case PR_EXPERT: acc -= 10; break;
|
|
default:
|
|
case PR_MASTER: break;
|
|
}
|
|
}
|
|
|
|
limit(&acc, 0, 100);
|
|
|
|
//if (aidb) dblog(".oO { my modified chance to hit is %d %% }", acc);
|
|
if (pctchance(acc)) gothit = B_TRUE;
|
|
}
|
|
|
|
// critical chance
|
|
if (critical) {
|
|
// default
|
|
*critical = 0;
|
|
|
|
if (gothit) {
|
|
if (lfhasflag(lf, F_AIMEDSTRIKE)) {
|
|
*critical = 1;
|
|
} else {
|
|
int critroll;
|
|
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;
|
|
}
|
|
limit(&critroll, 1, 100);
|
|
|
|
if (critroll <= getcritchance(lf, wep,victim)) *critical = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return gothit;
|
|
}
|
|
|
|
void wepeffects(flagpile_t *fp, cell_t *where, flag_t *damflag, int dam) {
|
|
flag_t *f;
|
|
lifeform_t *victim;
|
|
lifeform_t *owner = NULL;
|
|
object_t *wep;
|
|
int i;
|
|
flag_t *retflag[MAXCANDIDATES];
|
|
int nretflags = 0;
|
|
|
|
if (!where) return;
|
|
|
|
wep = fp->ob;
|
|
if (wep) {
|
|
cell_t *c;
|
|
c = getoblocation(wep);
|
|
if (c && c->lf) {
|
|
owner = c->lf;
|
|
}
|
|
} else {
|
|
owner = fp->owner;
|
|
}
|
|
victim = where->lf;
|
|
|
|
getflags(fp, retflag, &nretflags, F_FLAMESTRIKE, F_HEAVYBLOW, F_HITCONFER, F_RACESLAY, F_REVENGE, F_NONE);
|
|
for (i = 0; i < nretflags; i++) {
|
|
f = retflag[i];
|
|
if (f->id == F_FLAMESTRIKE) {
|
|
if (!hasob(where->obpile, OT_FIRESMALL)) {
|
|
// ignite!
|
|
addobfast(where->obpile, OT_FIRESMALL);
|
|
// announce
|
|
if (haslos(player, where)) {
|
|
msg("^wA column 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, B_FALSE);
|
|
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, B_FALSE);
|
|
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;
|
|
// knock back victim
|
|
dir = getdirtowards(owner->cell, victim->cell, victim, B_FALSE, DT_COMPASS);
|
|
knockback(victim, dir , 2, owner, 30, B_TRUE);
|
|
f->known = B_TRUE;
|
|
} else if ((f->id == F_HITCONFER) && victim ) {
|
|
// only works if we did damage
|
|
enum FLAG fid;
|
|
int howlong;
|
|
flag_t *valflag = NULL;
|
|
|
|
fid = f->val[0];
|
|
// 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 = 20; // 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) {
|
|
// need to fill in the name of what poisoned us
|
|
char frombuf[BUFLEN];
|
|
enum POISONTYPE ptype;
|
|
int ppower;
|
|
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 {
|
|
strcpy(frombuf, "something unknown");
|
|
}
|
|
|
|
|
|
if (valflag) {
|
|
ptype = valflag->val[0];
|
|
if (valflag->val[1] == NA) {
|
|
ppower = 1;
|
|
} else {
|
|
ppower = valflag->val[1];
|
|
}
|
|
} else {
|
|
// should never happen.
|
|
ptype = P_VENOM;
|
|
ppower = 1;
|
|
}
|
|
|
|
poison(victim, howlong, ptype, ppower, frombuf);
|
|
} 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);
|
|
}
|
|
}
|
|
} // end if (fid == hitconfer)
|
|
}
|
|
|
|
if (wep && owner && victim) {
|
|
enum DRAINTYPE draintype = DR_NONE;
|
|
if ((wep->type->id == OT_TEETH) && lfhasflag(owner, F_VAMPIRIC) && isbleeding(victim)) {
|
|
draintype = DR_FROMBITE;
|
|
} else if (hasflag(wep->flags, F_VAMPIRIC)) {
|
|
draintype = DR_FROMWEP;
|
|
}
|
|
|
|
if (draintype && !isimmuneto(victim->flags, DT_NECROTIC)) {
|
|
int hpgain;
|
|
// drain life!
|
|
if (isresistantto(victim->flags, DT_NECROTIC)) {
|
|
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!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|