/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team * Copyright (©) 1992,2007 Frank Tore Johansen * * Deliantra is free software: you can redistribute it and/or modify it under * the terms of the Affero GNU General Public License as published by the * Free Software Foundation, either version 3 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 Affero GNU General Public License * and the GNU General Public License along with this program. If not, see * . * * The authors can be reached via e-mail to */ /* 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 , 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 #include #include #include #include #include #include /* 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. */ /* grants immunity to plagues we've seen before. */ static int 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 0; /* first, search for an immunity of the same name */ for (walk = disease->env->inv; walk; walk = walk->below) if (walk->type == 98 && disease->name == walk->name) { walk->level = disease->level; return 1; /* just update the existing immunity. */ } immunity = get_archetype ("immunity"); immunity->name = disease->name; immunity->level = disease->level; immunity->move_block = 0; insert_ob_in_ob (immunity, disease->env); return 1; } /* argument is a disease */ static object * find_symptom (object *disease) { /* check the inventory for symptoms */ for (object *walk = disease->env->inv; walk; walk = walk->below) if (walk->name == disease->name && walk->type == SYMPTOM) return walk; return NULL; } /* 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. */ static int remove_symptoms (object *disease) { object *symptom, *victim = NULL; while ((symptom = find_symptom (disease)) != NULL) { if (!victim) victim = symptom->env; symptom->destroy (); } if (victim) victim->update_stats (); return 0; } /* searches around for more victims to infect */ static int check_infection (object *disease) { int range = abs (disease->magic); object *op = disease->outer_env_or_self (); if (!op->is_on_map ()) return 0; unordered_mapwalk (op, -range, -range, range, range) { mapspace &ms = m->at (nx, ny); if (ms.flags () & P_IS_ALIVE) for (object *tmp = ms.bot; tmp; tmp = tmp->above) infect_object (tmp, disease, 0); } return 1; } /* check if victim is susceptible to disease. */ static int is_susceptible_to_disease (object *victim, object *disease) { if (!QUERY_FLAG (victim, FLAG_ALIVE)) return 0; if (victim->flag [FLAG_WIZ]) return 0; if (disease->race.contains ("*") && !QUERY_FLAG (victim, FLAG_UNDEAD)) return 1; if ((disease->race == shstr_undead) && QUERY_FLAG (victim, FLAG_UNDEAD)) return 1; if ((victim->race && disease->race.contains (victim->race)) || disease->race.contains (victim->name)) return 1; return 0; } /* this function monitors the symptoms caused by the disease (if any), causes symptoms, and modifies existing symptoms in the case of existing diseases. */ static int do_symptoms (object *disease) { object *symptom; object *victim; object *tmp; victim = disease->env; if (!victim) return 0; /* no-one to inflict symptoms on */ /* 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. */ //TODO: should no longer be the case, monitor, and remove if (victim == disease) { LOG (llevError | logBacktrace, "%s: disease->env points to itself", disease->debug_desc ()); return 0; } symptom = find_symptom (disease); if (!symptom) { /* no symptom? need to generate one! */ /* first check and see if the carrier of the disease is immune. If so, no symptoms! */ if (!is_susceptible_to_disease (victim, disease)) return 0; /* check for an actual immunity */ /* do an immunity check */ for (tmp = victim->head_ ()->inv; tmp; tmp = tmp->below) if (tmp->type == SIGN) /* possibly an immunity, or diseased */ if (tmp->name == disease->name && tmp->level >= disease->level) return 0; /* Immune! */ object *new_symptom = get_archetype ("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) { int dam = disease->stats.dam; /* reduce the damage, on average, 50%, and making things random. */ dam = random_roll (1, abs (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; new_symptom->name = new_symptom->name_pl = disease->name; new_symptom->level = disease->level; new_symptom->speed = disease->speed; new_symptom->value = 0; for (int i = 0; i < NUM_STATS; ++i) new_symptom->stats.stat (i) = disease->stats.stat (i); 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 = disease->msg; new_symptom->attacktype = disease->attacktype; new_symptom->other_arch = disease->other_arch; new_symptom->skill = disease->skill; new_symptom->move_block = 0; victim->head_ ()->insert (new_symptom); // set owner last, as insert clears owner new_symptom->set_owner (disease->owner); return 1; } /* now deal with progressing diseases: we increase the debility * caused by the symptoms. */ if (disease->stats.ac) { symptom->value = clamp (symptom->value + disease->stats.ac, 0, 100*100); float scale = 1.f + symptom->value / 100.f; /* now rescale all the debilities */ for (int i = 0; i < NUM_STATS; ++i) symptom->stats.stat (i) = clamp (int (scale * disease->stats.stat (i)), -128, 127); symptom->stats.dam = clamp (scale * disease->stats.dam , -1024, 1024); symptom->stats.food = clamp (scale * disease->last_eat , -1024, 1024); symptom->stats.maxsp = clamp (scale * disease->stats.maxsp, -1024, 1024); symptom->stats.sp = clamp (scale * disease->stats.sp , -1024, 1024); symptom->stats.hp = clamp (scale * disease->stats.hp , -1024, 1024); symptom->stats.exp = 0; symptom->last_sp = disease->last_sp ? clamp (disease->last_sp / scale, 1, 1024) : 0; symptom->msg = disease->msg; symptom->attacktype = disease->attacktype; symptom->other_arch = disease->other_arch; } SET_FLAG (symptom, FLAG_APPLIED); victim->update_stats (); return 1; } 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. */ if (!disease->env) { /* we're outside of someone */ if (disease->stats.maxhp > 0) disease->value--; if (!disease->value) { disease->destroy (); return 1; } } else { /* if we're inside a person, have the disease run its course */ /* negative/zero food denotes "perpetual" diseases. */ if (disease->stats.food > 0) { disease->stats.food--; if (!disease->stats.food) { remove_symptoms (disease); /* remove the symptoms of this disease */ grant_immunity (disease); disease->destroy (); 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; } /* check to see if an object is infectable: * 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. */ 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; /* 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 */ /* 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 = victim->head_ ()->inv; tmp; tmp = tmp->below) if (tmp->type == SIGN && tmp->name == disease->name && tmp->level >= disease->level) return 0; /* Immune! */ else if (tmp->type == DISEASE && tmp->name == disease->name) return 0; /* already diseased */ /* If we've gotten this far, go ahead and infect the victim. */ new_disease = disease->clone (); new_disease->stats.food = disease->stats.maxgrace; new_disease->value = disease->stats.maxhp; new_disease->stats.wc -= disease->last_grace; /* self-limiting factor */ /* This appears to be a horrible case of overloading 'NO_PASS' * for meaning in the diseases. */ new_disease->move_block = 0; // insert before setting the owner victim->head_ ()->insert (new_disease); if (disease->owner) new_disease->set_owner (disease->owner); else if (object *pl = disease->in_player ()) /* for diseases which are passed by hitting, set owner and skill */ new_disease->set_owner (pl); if (new_disease->owner && new_disease->owner->type == PLAYER) { const char *buf; /* 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) buf = format ("%s %s!!", &disease->title, &victim->name); else buf = format ("You infect %s with your disease, %s!", &victim->name, &new_disease->name); if (victim->type == PLAYER) new_draw_info (NDI_UNIQUE | NDI_RED, 0, new_disease->owner, buf); else new_draw_info (0, 4, new_disease->owner, buf); } if (victim->type == PLAYER) new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, "You suddenly feel ill."); return 1; } /* make the symptom do the nasty things it does */ int move_symptom (object *symptom) { object *victim = symptom->env; object *new_ob; int sp_reduce; if (!victim || !victim->map) { /* outside a monster/player, die immediately */ symptom->destroy (); return 0; } 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), symptom, symptom->attacktype, 1); if (symptom->stats.maxsp > 0) sp_reduce = symptom->stats.maxsp; else sp_reduce = max (1, victim->stats.maxsp * symptom->stats.maxsp / 100); 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 (victim->map) { victim->play_sound (symptom->sound); if (symptom->other_arch) for (object *tmp = victim->head_ (); tmp; tmp = tmp->more) { new_ob = arch_to_object (symptom->other_arch); 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); } } new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, symptom->msg); return 1; } /* possibly infect due to direct physical contact * i.e., AT_PHYSICAL-- called from "hit_player_attacktype" */ int check_physically_infect (object *victim, object *hitter) { /* search for diseases, give every disease a chance to infect */ for (object *disease = hitter->inv; disease; disease = disease->below) if (disease->type == DISEASE) infect_object (victim, disease, 0); return 1; } // find a disease in someone static object * find_disease (object *victim) { for (object *disease = victim->inv; disease; disease = disease->below) if (disease->type == DISEASE) return disease; return 0; } /* do the cure disease stuff, from the spell "cure disease" */ int cure_disease (object *sufferer, object *caster, object *spell) { object *disease, *next; int cure = 0; int casting_level = caster ? caster->level : 1000; /* if null caster, CURE all. */ for (disease = sufferer->inv; disease; disease = next) { next = disease->below; if (disease->type == DISEASE) { /* attempt to cure this disease */ /* If caster level 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); cure = 1; if (caster && spell) change_exp (caster, disease->stats.exp, spell->skill, SK_EXP_SKILL_ONLY); disease->destroy (); } } } if (cure) { /* Only draw these messages once */ if (caster) new_draw_info_format (NDI_UNIQUE, 0, caster, "You cure a disease!"); new_draw_info (NDI_UNIQUE, 0, sufferer, "You no longer feel diseased."); } return 1; } /* reduces disease progression: reduce_symptoms * return true if we actually reduce a disease. */ int reduce_symptoms (object *sufferer, int reduction) { object *walk; int success = 0; for (walk = sufferer->inv; walk; walk = walk->below) { if (walk->type == SYMPTOM) { if (walk->value > 0) { success = 1; walk->value = max (0, walk->value - 2 * reduction); /* give the disease time to modify this symptom, * and reduce its severity. */ walk->speed_left = 0; } } } if (success) new_draw_info (NDI_UNIQUE, 0, sufferer, "Your illness seems less severe."); return success; }