757 lines
26 KiB
C
757 lines
26 KiB
C
/*
|
|
* static char *rcsid_disease_c =
|
|
* "$Id: disease.c 11578 2009-02-23 22:02:27Z lalo $";
|
|
*/
|
|
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2006 Mark Wedel & Crossfire Development Team
|
|
Copyright (C) 1992 Frank Tore Johansen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
The authors can be reached via e-mail to crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* This file contains all the code implementing diseases,
|
|
* except for odds and ends in attack.c and in living.c
|
|
*/
|
|
|
|
/*
|
|
* For DISEASES:
|
|
* Stat Property Definition
|
|
*
|
|
* attacktype Attack effects Attacktype of the disease. usu. AT_GODPOWER.
|
|
* other_arch Creation object created and dropped when symptom moved.
|
|
* title Message When the "disease" "infects" something, it will
|
|
* print "title victim!!!" to the player who owns
|
|
* the "disease".
|
|
* wc+ Infectiousness How well the plague spreads person-to-person
|
|
* magic+ Range range of infection
|
|
* Stats* Disability What stats are reduced by the disease (str con...)
|
|
* maxhp+ Persistence How long the disease can last OUTSIDE the host.
|
|
* * value TimeLeft Counter for persistence
|
|
* dam^ Damage How much damage it does (%?).
|
|
* maxgrace+ Duration How long before the disease is naturally cured.
|
|
* food DurCount Counter for Duration
|
|
*
|
|
* speed Speed How often the disease moves.
|
|
* last_sp^ Lethargy Percentage of max speed--10 = 10% speed.
|
|
*
|
|
* maxsp^ Mana deplete Saps mana.
|
|
* ac^ Progressiveness How the diseases increases in severity.
|
|
* last_eat*^ Deplete food saps food if negative
|
|
* last_heal GrantImmunity If nonzero, disease does NOT grant immunity
|
|
* when it runs out
|
|
*
|
|
* exp experience experience awarded when plague cured
|
|
* hp*^ ReduceRegen reduces regeneration of disease-bearer
|
|
* sp*^ ReduceSpRegen reduces spellpoint regeneration
|
|
*
|
|
* name Name Name of the plague
|
|
* msg message What the plague says when it strikes.
|
|
* race those affected species/race the plague strikes (* = everything)
|
|
* level Plague Level General description of the plague's deadliness
|
|
* armour Attenuation reduction in wc per generation of disease.
|
|
* This builds in a self-limiting factor.
|
|
*
|
|
*
|
|
* Explanations:
|
|
* * means this # should be negative to cause adverse effect.
|
|
* + means that this effect is modulated in spells by ldur
|
|
* ^ means that this effect is modulated in spells by ldam
|
|
*
|
|
* attacktype is the attacktype used by the disease to smite "dam" damage with.
|
|
*
|
|
* wc/127 is the chance of someone in range catching it.
|
|
*
|
|
* magic is the range at which infection may occur. If negative, the range is
|
|
* NOT level dependent.
|
|
*
|
|
* Stats are stat modifications. These should typically be negative.
|
|
*
|
|
* maxhp is how long the disease will persist if the host dies and "drops" it,
|
|
* in "disease moves", i.e., moves of the disease. If negative, permanent.
|
|
*
|
|
* value is the counter for maxhp, it starts at maxhp and drops...
|
|
*
|
|
* dam if positive, it is straight damage. if negative, a %-age.
|
|
*
|
|
* maxgrace how long in "disease moves" the disease lasts in the host, if negative,
|
|
* permanent until cured.
|
|
*
|
|
* food if negative, disease is permanent. otherwise, decreases at <speed>,
|
|
* disease goes away at food=0, set to "maxgrace" on infection.
|
|
*
|
|
* speed is the speed of the disease, how fast "disease moves" occur.
|
|
*
|
|
* last_sp is the lethargy imposed on the player by the disease. A lethargy
|
|
* of "1" reduces the players speed to 1% of its normal value.
|
|
*
|
|
* maxsp how much mana is sapped per "disease move". if negative, a %-age is
|
|
* taken.
|
|
*
|
|
* ac every "disease move" the severity of the symptoms are increased by
|
|
* ac/100. (severity = 1 + (accumlated_progression)/100)
|
|
*
|
|
* last_eat increases food usage if negative.
|
|
*
|
|
*
|
|
*
|
|
* For SYMPTOMS:
|
|
*
|
|
* Stats modify stats
|
|
* hp modify regen
|
|
* value progression counter (multiplier = value/100)
|
|
* food modify food use (from last_eat in DISEASE)
|
|
* maxsp suck mana ( as noted for DISEASE)
|
|
* last_sp Lethargy
|
|
* msg What to say
|
|
* speed speed of movement, from DISEASE
|
|
*/
|
|
|
|
#include <global.h>
|
|
#include <object.h>
|
|
#include <living.h>
|
|
#ifndef __CEXTRACT__
|
|
#include <sproto.h>
|
|
#endif
|
|
#include <spells.h>
|
|
#include <sounds.h>
|
|
#include <skills.h>
|
|
#include <assert.h>
|
|
|
|
static void remove_symptoms(object *disease);
|
|
static object *find_symptom(object *disease);
|
|
static void check_infection(object *disease);
|
|
static void do_symptoms(object *disease);
|
|
static void grant_immunity(object *disease);
|
|
|
|
|
|
|
|
/* IMPLEMENTATION NOTES
|
|
|
|
Diseases may be contageous. They are objects which exist in a player's
|
|
inventory. They themselves do nothing, except modify Symptoms, or
|
|
spread to other live objects. Symptoms are what actually damage the player:
|
|
these are their own object. */
|
|
|
|
/**
|
|
* Check if victim is susceptible to disease. Does not check for immunity.
|
|
*
|
|
* @param victim
|
|
* potential victim.
|
|
* @param disease
|
|
* disease to check.
|
|
* @retval 1
|
|
* victim can be infected
|
|
* @retval 0
|
|
* victim doesn't care for the disease.
|
|
*/
|
|
static int is_susceptible_to_disease(object *victim, object *disease) {
|
|
/* Non living and DMs are immune. */
|
|
if (!QUERY_FLAG(victim, FLAG_ALIVE) || QUERY_FLAG(victim, FLAG_WIZ))
|
|
return 0;
|
|
|
|
if (strstr(disease->race, "*") && !QUERY_FLAG(victim, FLAG_UNDEAD))
|
|
return 1;
|
|
|
|
if ((disease->race == undead_name) && QUERY_FLAG(victim, FLAG_UNDEAD))
|
|
return 1;
|
|
|
|
if ((victim->race && strstr(disease->race, victim->race))
|
|
|| strstr(disease->race, victim->name))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Ticks the clock for disease: infect, aggravate symptoms, ...
|
|
* @param disease
|
|
* disease to move. Can be removed during processing.
|
|
* @retval 1
|
|
* if disease was removed.
|
|
* @retval 0
|
|
* disease just moved.
|
|
*/
|
|
int move_disease(object *disease) {
|
|
/* first task is to determine if the disease is inside or outside of someone.
|
|
* If outside, we decrement 'value' until we're gone.
|
|
*/
|
|
|
|
/* DMs don't infect, and don't suffer either. */
|
|
if (disease->env && QUERY_FLAG(disease->env, FLAG_WIZ))
|
|
return 0;
|
|
|
|
if (disease->env == NULL) { /* we're outside of someone */
|
|
if (disease->stats.maxhp > 0)
|
|
disease->value--;
|
|
if (disease->value == 0) {
|
|
remove_ob(disease);
|
|
free_object(disease);
|
|
return 1;
|
|
}
|
|
} else {
|
|
/* if we're inside a person, have the disease run its course */
|
|
/* negative foods denote "perpetual" diseases. */
|
|
if (disease->stats.food > 0) {
|
|
disease->stats.food--;
|
|
if (disease->stats.food == 0) {
|
|
remove_symptoms(disease); /* remove the symptoms of this disease */
|
|
grant_immunity(disease);
|
|
remove_ob(disease);
|
|
free_object(disease);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
/* check to see if we infect others */
|
|
check_infection(disease);
|
|
|
|
/* impose or modify the symptoms of the disease */
|
|
if (disease->env && is_susceptible_to_disease(disease->env, disease))
|
|
do_symptoms(disease);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Remove any symptoms of disease.
|
|
*
|
|
* Modified by MSW 2003-03-28 do try to find all the symptom the
|
|
* player may have - I think through some odd interactoins with
|
|
* disease level and player level and whatnot, a player could get
|
|
* more than one symtpom to a disease.
|
|
*
|
|
* @param disease
|
|
* disease to remove. Must be in a living object.
|
|
*/
|
|
static void remove_symptoms(object *disease) {
|
|
object *symptom, *victim = NULL;
|
|
|
|
assert(disease != NULL);
|
|
|
|
while ((symptom = find_symptom(disease)) != NULL) {
|
|
if (!victim)
|
|
victim = symptom->env;
|
|
remove_ob(symptom);
|
|
free_object(symptom);
|
|
}
|
|
if (victim)
|
|
fix_object(victim);
|
|
}
|
|
|
|
/**
|
|
* Find a symptom for a disease in disease's env.
|
|
*
|
|
* @param disease
|
|
* disease to search symptom of. Must be in another object.
|
|
* @return
|
|
* matching symptom object, NULL if none found.
|
|
*/
|
|
static object *find_symptom(object *disease) {
|
|
object *walk;
|
|
|
|
assert(disease->env != NULL);
|
|
|
|
/* check the inventory for symptoms */
|
|
for (walk = disease->env->inv; walk; walk = walk->below)
|
|
if (!strcmp(walk->name, disease->name) && walk->type == SYMPTOM)
|
|
return walk;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Searches around for more victims to infect.
|
|
*
|
|
* @param disease
|
|
* disease infecting. Can be either on a map or inside another object.
|
|
*/
|
|
static void check_infection(object *disease) {
|
|
int x, y, range, mflags;
|
|
mapstruct *map, *map2;
|
|
object *tmp;
|
|
sint16 i, j, i2, j2;
|
|
|
|
range = abs(disease->magic);
|
|
if (disease->env) {
|
|
x = disease->env->x;
|
|
y = disease->env->y;
|
|
map = disease->env->map;
|
|
} else {
|
|
x = disease->x;
|
|
y = disease->y;
|
|
map = disease->map;
|
|
}
|
|
|
|
if (map == NULL)
|
|
return;
|
|
for (i = x-range; i <= x+range; i++) {
|
|
for (j = y-range; j <= y+range; j++) {
|
|
mflags = get_map_flags(map, &map2, i, j, &i2, &j2);
|
|
if (!(mflags&P_OUT_OF_MAP) && (mflags&P_IS_ALIVE)) {
|
|
for (tmp = GET_MAP_OB(map2, i2, j2); tmp; tmp = tmp->above) {
|
|
infect_object(tmp, disease, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Try to infect something with a disease. Rules:
|
|
* - objects with immunity aren't infectable.
|
|
* - objects already infected aren't infectable.
|
|
* - dead objects aren't infectable.
|
|
* - undead objects are infectible only if specifically named.
|
|
*
|
|
* @param victim
|
|
* potential victim to infect.
|
|
* @param disease
|
|
* what could infect.
|
|
* @param force
|
|
* don't do a random check for infection. Other checks (susceptible to disease,
|
|
* not immune, and so on) are still done.
|
|
* @retval 0
|
|
* victim wasn't infected.
|
|
* @retval 1
|
|
* victim was infected.
|
|
*/
|
|
int infect_object(object *victim, object *disease, int force) {
|
|
object *tmp;
|
|
object *new_disease;
|
|
|
|
/* don't infect inanimate objects */
|
|
if (!QUERY_FLAG(victim, FLAG_MONSTER) && !(victim->type == PLAYER))
|
|
return 0;
|
|
|
|
/* check and see if victim can catch disease: diseases
|
|
* are specific
|
|
*/
|
|
if (!is_susceptible_to_disease(victim, disease))
|
|
return 0;
|
|
|
|
/* If disease is on battleground, only infect other victims on battleground.
|
|
Not checking results in spectators being infected, which could lead to PK. */
|
|
if ((disease->map && op_on_battleground(disease, NULL, NULL, NULL))
|
|
|| (disease->env && op_on_battleground(disease->env, NULL, NULL, NULL)))
|
|
if (!op_on_battleground(victim, NULL, NULL, NULL))
|
|
return 0;
|
|
|
|
/* roll the dice on infection before doing the inventory check! */
|
|
if (!force && (random_roll(0, 126, victim, PREFER_HIGH) >= disease->stats.wc))
|
|
return 0;
|
|
|
|
/* do an immunity check */
|
|
if (victim->head)
|
|
tmp = victim->head->inv;
|
|
else
|
|
tmp = victim->inv;
|
|
|
|
/* There used to (IMO) be a flaw in the below - it used to be the case
|
|
* that if level check was done for both immunity and disease. This could
|
|
* result in a person with multiple afflictions of the same disease
|
|
* (eg, level 1 cold, level 2 cold, level 3 cold, etc), as long as
|
|
* they were cast in that same order. Instead, change it so that
|
|
* if you diseased, you can't get diseased more.
|
|
*/
|
|
|
|
for (/* tmp initialized in if, above */; tmp; tmp = tmp->below) {
|
|
if (tmp->type == SIGN && !strcmp(tmp->name, disease->name) && tmp->level >= disease->level)
|
|
return 0; /*Immune! */
|
|
else if (tmp->type == DISEASE && !strcmp(tmp->name, disease->name))
|
|
return 0; /* already diseased */
|
|
}
|
|
|
|
/* If we've gotten this far, go ahead and infect the victim. */
|
|
new_disease = get_object();
|
|
copy_object(disease, new_disease);
|
|
new_disease->stats.food = disease->stats.maxgrace;
|
|
new_disease->value = disease->stats.maxhp;
|
|
new_disease->stats.wc -= disease->last_grace; /* self-limiting factor */
|
|
|
|
/* Unfortunately, set_owner does the wrong thing to the skills pointers
|
|
* resulting in exp going into the owners *current *chosen skill.
|
|
*/
|
|
|
|
if (get_owner(disease)) {
|
|
set_owner(new_disease, disease->owner);
|
|
|
|
/* Only need to update skill if different */
|
|
if (new_disease->skill != disease->skill) {
|
|
if (new_disease->skill)
|
|
free_string(new_disease->skill);
|
|
if (disease->skill)
|
|
new_disease->skill = add_refcount(disease->skill);
|
|
}
|
|
} else { /* for diseases which are passed by hitting, set owner and praying skill*/
|
|
if (disease->env && disease->env->type == PLAYER) {
|
|
object *player = disease->env;
|
|
|
|
set_owner(new_disease, player);
|
|
/* the skill pointer for these diseases should already be set up -
|
|
* hardcoding in 'praying' is not the right approach.
|
|
*/
|
|
}
|
|
}
|
|
|
|
insert_ob_in_ob(new_disease, victim);
|
|
/* This appears to be a horrible case of overloading 'NO_PASS'
|
|
* for meaning in the diseases.
|
|
*/
|
|
new_disease->move_block = 0;
|
|
if (new_disease->owner && new_disease->owner->type == PLAYER) {
|
|
char buf[128];
|
|
|
|
/* if the disease has a title, it has a special infection message
|
|
* This messages is printed in the form MESSAGE victim
|
|
*/
|
|
if (new_disease->title)
|
|
snprintf(buf, sizeof(buf), "%s %s!!", disease->title, victim->name);
|
|
else
|
|
snprintf(buf, sizeof(buf), "You infect %s with your disease, %s!", victim->name, new_disease->name);
|
|
|
|
if (victim->type == PLAYER)
|
|
draw_ext_info(NDI_UNIQUE|NDI_RED, 0, new_disease->owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_HIT,
|
|
buf, buf);
|
|
else
|
|
draw_ext_info(0, 4, new_disease->owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_HIT,
|
|
buf, buf);
|
|
}
|
|
if (victim->type == PLAYER)
|
|
draw_ext_info(NDI_UNIQUE|NDI_RED, 0, victim, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START,
|
|
"You suddenly feel ill.", NULL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* This function monitors the symptoms caused by the disease (if any),
|
|
* causes symptoms, and modifies existing symptoms in the case of
|
|
* existing diseases.
|
|
*
|
|
* @param disease
|
|
* disease acting. Should be in a living object.
|
|
*/
|
|
static void do_symptoms(object *disease) {
|
|
object *symptom;
|
|
object *victim;
|
|
object *tmp;
|
|
victim = disease->env;
|
|
|
|
/* This is a quick hack - for whatever reason, disease->env will point
|
|
* back to disease, causing endless loops. Why this happens really needs
|
|
* to be found, but this should at least prevent the infinite loops.
|
|
*/
|
|
|
|
if (victim == NULL || victim == disease)
|
|
return;/* no-one to inflict symptoms on */
|
|
|
|
/* DMs don't suffer from diseases. */
|
|
if (QUERY_FLAG(victim, FLAG_WIZ))
|
|
return;
|
|
|
|
symptom = find_symptom(disease);
|
|
if (symptom == NULL) {
|
|
/* no symptom? need to generate one! */
|
|
object *new_symptom;
|
|
|
|
/* first check and see if the carrier of the disease is immune. If so, no symptoms! */
|
|
if (!is_susceptible_to_disease(victim, disease))
|
|
return;
|
|
|
|
/* check for an actual immunity */
|
|
/* do an immunity check */
|
|
if (victim->head)
|
|
tmp = victim->head->inv;
|
|
else
|
|
tmp = victim->inv;
|
|
|
|
for (/* tmp initialized in if, above */; tmp; tmp = tmp->below) {
|
|
if (tmp->type == SIGN) /* possibly an immunity, or diseased*/
|
|
if (!strcmp(tmp->name, disease->name) && tmp->level >= disease->level)
|
|
return; /*Immune! */
|
|
}
|
|
|
|
new_symptom = create_archetype(ARCH_SYMPTOM);
|
|
|
|
/* Something special done with dam. We want diseases to be more
|
|
* random in what they'll kill, so we'll make the damage they
|
|
* do random, note, this has a weird effect with progressive diseases.
|
|
*/
|
|
if (disease->stats.dam != 0) {
|
|
int dam = disease->stats.dam;
|
|
|
|
/* reduce the damage, on average, 50%, and making things random. */
|
|
|
|
dam = random_roll(1, FABS(dam), victim, PREFER_LOW);
|
|
if (disease->stats.dam < 0)
|
|
dam = -dam;
|
|
new_symptom->stats.dam = dam;
|
|
}
|
|
|
|
new_symptom->stats.maxsp = disease->stats.maxsp;
|
|
new_symptom->stats.food = new_symptom->stats.maxgrace;
|
|
|
|
FREE_AND_COPY(new_symptom->name, disease->name);
|
|
FREE_AND_COPY(new_symptom->name_pl, disease->name);
|
|
new_symptom->level = disease->level;
|
|
new_symptom->speed = disease->speed;
|
|
new_symptom->value = 0;
|
|
new_symptom->stats.Str = disease->stats.Str;
|
|
new_symptom->stats.Dex = disease->stats.Dex;
|
|
new_symptom->stats.Con = disease->stats.Con;
|
|
new_symptom->stats.Wis = disease->stats.Wis;
|
|
new_symptom->stats.Int = disease->stats.Int;
|
|
new_symptom->stats.Pow = disease->stats.Pow;
|
|
new_symptom->stats.Cha = disease->stats.Cha;
|
|
new_symptom->stats.sp = disease->stats.sp;
|
|
new_symptom->stats.food = disease->last_eat;
|
|
new_symptom->stats.maxsp = disease->stats.maxsp;
|
|
new_symptom->last_sp = disease->last_sp;
|
|
new_symptom->stats.exp = 0;
|
|
new_symptom->stats.hp = disease->stats.hp;
|
|
new_symptom->msg = add_string(disease->msg);
|
|
new_symptom->attacktype = disease->attacktype;
|
|
new_symptom->other_arch = disease->other_arch;
|
|
|
|
set_owner(new_symptom, disease->owner);
|
|
if (new_symptom->skill != disease->skill) {
|
|
if (new_symptom->skill)
|
|
free_string(new_symptom->skill);
|
|
if (disease->skill)
|
|
new_symptom->skill = add_refcount(disease->skill);
|
|
}
|
|
new_symptom->move_block = 0;
|
|
insert_ob_in_ob(new_symptom, victim);
|
|
return;
|
|
}
|
|
|
|
/* now deal with progressing diseases: we increase the debility
|
|
* caused by the symptoms.
|
|
*/
|
|
|
|
if (disease->stats.ac != 0) {
|
|
float scale;
|
|
|
|
symptom->value += disease->stats.ac;
|
|
scale = 1.0+symptom->value/100.0;
|
|
/* now rescale all the debilities */
|
|
symptom->stats.Str = (int)(scale*disease->stats.Str);
|
|
symptom->stats.Dex = (int)(scale*disease->stats.Dex);
|
|
symptom->stats.Con = (int)(scale*disease->stats.Con);
|
|
symptom->stats.Wis = (int)(scale*disease->stats.Wis);
|
|
symptom->stats.Int = (int)(scale*disease->stats.Int);
|
|
symptom->stats.Pow = (int)(scale*disease->stats.Pow);
|
|
symptom->stats.Cha = (int)(scale*disease->stats.Cha);
|
|
symptom->stats.dam = (int)(scale*disease->stats.dam);
|
|
symptom->stats.sp = (int)(scale*disease->stats.sp);
|
|
symptom->stats.food = (int)(scale*disease->last_eat);
|
|
symptom->stats.maxsp = (int)(scale*disease->stats.maxsp);
|
|
symptom->last_sp = (int)(scale*disease->last_sp);
|
|
symptom->stats.exp = 0;
|
|
symptom->stats.hp = (int)(scale*disease->stats.hp);
|
|
symptom->msg = add_string(disease->msg);
|
|
symptom->attacktype = disease->attacktype;
|
|
symptom->other_arch = disease->other_arch;
|
|
}
|
|
SET_FLAG(symptom, FLAG_APPLIED);
|
|
fix_object(victim);
|
|
}
|
|
|
|
/**
|
|
* Grants immunity to a disease.
|
|
*
|
|
* @param disease
|
|
* disease to grant immunity to. Must be in another object.
|
|
*/
|
|
static void grant_immunity(object *disease) {
|
|
object *immunity;
|
|
object *walk;
|
|
|
|
/* Don't give immunity to this disease if last_heal is set. */
|
|
if (disease->last_heal)
|
|
return;
|
|
|
|
assert(disease->env != NULL);
|
|
|
|
/* first, search for an immunity of the same name */
|
|
for (walk = disease->env->inv; walk; walk = walk->below) {
|
|
if (walk->type == SIGN && !strcmp(disease->name, walk->name)) {
|
|
walk->level = disease->level;
|
|
return; /* just update the existing immunity. */
|
|
}
|
|
}
|
|
immunity = create_archetype("immunity");
|
|
immunity->name = add_string(disease->name);
|
|
immunity->level = disease->level;
|
|
immunity->move_block = 0;
|
|
insert_ob_in_ob(immunity, disease->env);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Make the symptom do the nasty things it does.
|
|
*
|
|
* @param symptom
|
|
* symptom to move.
|
|
*/
|
|
void move_symptom(object *symptom) {
|
|
object *victim = symptom->env;
|
|
object *new_ob;
|
|
int sp_reduce;
|
|
tag_t tag = symptom->count;
|
|
|
|
if (victim == NULL || victim->map == NULL) { /* outside a monster/player, die immediately */
|
|
remove_ob(symptom);
|
|
free_object(symptom);
|
|
return;
|
|
}
|
|
|
|
if (symptom->stats.dam > 0)
|
|
hit_player(victim, symptom->stats.dam, symptom, symptom->attacktype, 1);
|
|
else
|
|
hit_player(victim, MAX(1, -victim->stats.maxhp*symptom->stats.dam/100.0), symptom, symptom->attacktype, 1);
|
|
|
|
/* In most cases, if the victim has been freed, the logic that
|
|
* does that will also free the symptom, so check for that.
|
|
*/
|
|
if (QUERY_FLAG(victim, FLAG_FREED)) {
|
|
if (!was_destroyed(symptom, tag)) {
|
|
remove_ob(symptom);
|
|
free_object(symptom);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (symptom->stats.maxsp > 0)
|
|
sp_reduce = symptom->stats.maxsp;
|
|
else
|
|
sp_reduce = MAX(1, victim->stats.maxsp*symptom->stats.maxsp/100.0);
|
|
victim->stats.sp = MAX(0, victim->stats.sp-sp_reduce);
|
|
|
|
/* create the symptom "other arch" object and drop it here
|
|
* under every part of the monster
|
|
* The victim may well have died.
|
|
*/
|
|
|
|
if (symptom->other_arch) {
|
|
object *tmp;
|
|
|
|
tmp = victim;
|
|
if (tmp->head != NULL)
|
|
tmp = tmp->head;
|
|
for (/*tmp initialized above */; tmp != NULL; tmp = tmp->more) {
|
|
char name[MAX_BUF];
|
|
|
|
new_ob = arch_to_object(symptom->other_arch);
|
|
snprintf(name, sizeof(name), "%s's %s", victim->name, new_ob->name);
|
|
FREE_AND_COPY(new_ob->name, name);
|
|
if (new_ob->name_pl != NULL) {
|
|
snprintf(name, sizeof(name), "%s's %s", victim->name, new_ob->name_pl);
|
|
FREE_AND_COPY(new_ob->name_pl, name);
|
|
}
|
|
new_ob->x = tmp->x;
|
|
new_ob->y = tmp->y;
|
|
new_ob->map = victim->map;
|
|
insert_ob_in_map(new_ob, victim->map, victim, 0);
|
|
}
|
|
}
|
|
if (!symptom->msg) {
|
|
LOG(llevError, "BUG: move_symptom(): symptom %d (%s) without message!\n", symptom->count, symptom->name);
|
|
return;
|
|
}
|
|
draw_ext_info(NDI_UNIQUE|NDI_RED, 0, victim, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START,
|
|
symptom->msg, symptom->msg);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Possibly infect due to direct physical contact i.e., AT_PHYSICAL.
|
|
*
|
|
* @param victim
|
|
* potential victim.
|
|
* @param hitter
|
|
* who is hitting.
|
|
*/
|
|
|
|
void check_physically_infect(object *victim, object *hitter) {
|
|
object *walk;
|
|
|
|
/* search for diseases, give every disease a chance to infect */
|
|
for (walk = hitter->inv; walk != NULL; walk = walk->below)
|
|
if (walk->type == DISEASE)
|
|
infect_object(victim, walk, 0);
|
|
}
|
|
|
|
/**
|
|
* Do the cure disease stuff, from the spell "cure disease".
|
|
* @param sufferer
|
|
* who is getting cured.
|
|
* @param caster
|
|
* spell object used for curing. If NULL all diseases are removed, else only those of lower level than
|
|
* caster or randomly chosen.
|
|
* @retval 0
|
|
* no disease was cured.
|
|
* @retval 1
|
|
* at least one disease was cured.
|
|
*/
|
|
int cure_disease(object *sufferer, object *caster) {
|
|
object *disease, *next;
|
|
int casting_level;
|
|
int cure = 0;
|
|
|
|
if (caster)
|
|
casting_level = caster->level;
|
|
else
|
|
casting_level = 1000; /* if null caster, CURE all. */
|
|
|
|
for (disease = sufferer->inv; disease; disease = next) {
|
|
next = disease->below;
|
|
|
|
if (disease->type == DISEASE && !QUERY_FLAG(disease, FLAG_STARTEQUIP)) {
|
|
/* attempt to cure this disease. God-given diseases are given by the god, so don't remove them */
|
|
/* If caster lvel is higher than disease level, cure chance
|
|
* is automatic. If lower, then the chance is basically
|
|
* 1 in level_diff - if there is a 5 level difference, chance
|
|
* is 1 in 5.
|
|
*/
|
|
if ((casting_level >= disease->level)
|
|
|| (!(random_roll(0, (disease->level-casting_level-1), caster, PREFER_LOW)))) {
|
|
remove_symptoms(disease);
|
|
remove_ob(disease);
|
|
cure = 1;
|
|
if (caster)
|
|
change_exp(caster, disease->stats.exp, caster->chosen_skill ? caster->chosen_skill->skill : NULL, 0);
|
|
free_object(disease);
|
|
}
|
|
}
|
|
}
|
|
if (cure) {
|
|
/* Only draw these messages once */
|
|
if (caster)
|
|
draw_ext_info_format(NDI_UNIQUE, 0, caster, MSG_TYPE_SPELL, MSG_TYPE_SPELL_HEAL,
|
|
"You cure a disease!", NULL);
|
|
|
|
draw_ext_info(NDI_UNIQUE, 0, sufferer, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_END,
|
|
"You no longer feel diseased.", NULL);
|
|
}
|
|
return cure;
|
|
}
|