ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/disease.C
Revision: 1.68
Committed: Sun May 1 16:58:17 2011 UTC (13 years ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.67: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 /*
2 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 *
4 * Copyright (©) 2005,2006,2007,2008,2009,2010,2011 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 * Copyright (©) 2002 Mark Wedel & Crossfire Development Team
6 * Copyright (©) 1992 Frank Tore Johansen
7 *
8 * Deliantra is free software: you can redistribute it and/or modify it under
9 * the terms of the Affero GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the Affero GNU General Public License
19 * and the GNU General Public License along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 *
22 * The authors can be reached via e-mail to <support@deliantra.net>
23 */
24
25 /* This file contains all the code implementing diseases,
26 * except for odds and ends in attack.c and in
27 * living.c
28 */
29
30 /*
31
32 For DISEASES:
33 Stat Property Definition
34
35 attacktype Attack effects Attacktype of the disease. usu. AT_GODPOWER.
36 other_arch Creation object created and dropped when symptom moved.
37 title Message When the "disease" "infects" something, it will
38 print "title victim!!!" to the player who owns
39 the "disease".
40 wc+ Infectiousness How well the plague spreads person-to-person
41 magic+ Range range of infection
42 Stats* Disability What stats are reduced by the disease (str con...)
43 maxhp+ Persistence How long the disease can last OUTSIDE the host.
44 value TimeLeft Counter for persistence
45 dam^ Damage How much damage it does (%?).
46 maxgrace+ Duration How long before the disease is naturally cured.
47 food DurCount Counter for Duration
48
49 speed Speed How often the disease moves.
50 last_sp^ Lethargy Percentage of max speed. 10 = 10% speed.
51
52 maxsp^ Mana deplete Saps mana.
53 ac^ Progressiveness How the diseases increases in severity.
54 last_eat*^ Deplete food saps food if negative
55 last_heal GrantImmunity If nonzero, disease does NOT grant immunity
56 when it runs out
57
58 exp experience experience awarded when plague cured
59 hp*^ ReduceRegen reduces regeneration of disease-bearer
60 sp*^ ReduceSpRegen reduces spellpoint regeneration
61
62 name Name Name of the plague
63 msg message What the plague says when it strikes.
64 race those affected species/race the plague strikes (* = everything)
65 level Plague Level General description of the plague's deadliness
66 armour Attenuation reduction in wc per generation of disease.
67 This builds in a self-limiting factor.
68
69
70 Explanations:
71 * means this # should be negative to cause adverse effect.
72 + means that this effect is modulated in spells by ldur
73 ^ means that this effect is modulated in spells by ldam
74
75 attacktype is the attacktype used by the disease to smite "dam" damage with.
76
77 wc/127 is the chance of someone in range catching it.
78
79 magic is the range at which infection may occur. If negative, the range is
80 NOT level dependent.
81
82 Stats are stat modifications. These should typically be negative.
83
84 maxhp is how long the disease will persist if the host dies and "drops" it,
85 in "disease moves", i.e., moves of the disease. If negative, permanent.
86
87
88 value is the counter for maxhp, it starts at maxhp and drops...
89
90 dam if positive, it is straight damage. if negative, a %-age.
91
92 maxgrace how long in "disease moves" the disease lasts in the host, if negative,
93 permanent until cured.
94
95 food if negative, disease is permanent. otherwise, decreases at <speed>,
96 disease goes away at food=0, set to "maxgrace" on infection.
97
98 speed is the speed of the disease, how fast "disease moves" occur.
99
100 last_sp is the lethargy imposed on the player by the disease. A lethargy
101 of "1" reduces the players speed to 1% of its normal value.
102
103 maxsp how much mana is sapped per "disease move". if negative, a %-age is
104 taken.
105
106 ac every "disease move" the severity of the symptoms are increased by
107 ac/100. (severity = 1 + (accumlated_progression)/100)
108
109 last_eat increases food usage if negative.
110
111
112
113 For SYMPTOMS:
114
115 Stats modify stats
116 hp modify regen
117 value progression counter (multiplier = value/100)
118 food modify food use (from last_eat in DISEASE)
119 maxsp suck mana ( as noted for DISEASE)
120 last_sp Lethargy
121 msg What to say
122 speed speed of movement, from DISEASE
123
124
125 */
126
127
128 #include <global.h>
129 #include <object.h>
130 #include <living.h>
131 #include <sproto.h>
132 #include <spells.h>
133 #include <sounds.h>
134 #include <skills.h>
135
136 /* IMPLEMENTATION NOTES
137
138 Diseases may be contageous. They are objects which exist in a player's
139 inventory. They themselves do nothing, except modify Symptoms, or
140 spread to other live objects. Symptoms are what actually damage the player:
141 these are their own object. */
142
143 /* grants immunity to plagues we've seen before. */
144 static int
145 grant_immunity (object *disease)
146 {
147 object *immunity;
148 object *walk;
149
150 /* Don't give immunity to this disease if last_heal is set. */
151 if (disease->last_heal)
152 return 0;
153
154 /* first, search for an immunity of the same name */
155 for (walk = disease->env->inv; walk; walk = walk->below)
156 if (disease->name == walk->name && walk->is_immunity ())
157 {
158 walk->level = disease->level;
159 return 1; /* just update the existing immunity. */
160 }
161
162 immunity = archetype::get (shstr_immunity);
163
164 immunity->name = disease->name;
165 immunity->level = disease->level;
166 immunity->move_block = 0;
167
168 insert_ob_in_ob (immunity, disease->env);
169
170 return 1;
171 }
172
173 /* argument is a disease */
174 static object *
175 find_symptom (object *disease)
176 {
177 /* check the inventory for symptoms */
178 for (object *walk = disease->env->inv; walk; walk = walk->below)
179 if (walk->name == disease->name && walk->type == SYMPTOM)
180 return walk;
181
182 return NULL;
183 }
184
185 /* remove any symptoms of disease
186 * Modified by MSW 2003-03-28 do try to find all the symptom the
187 * player may have - I think through some odd interactoins with
188 * disease level and player level and whatnot, a player could get
189 * more than one symtpom to a disease.
190 */
191 static int
192 remove_symptoms (object *disease)
193 {
194 object *symptom, *victim = NULL;
195
196 while ((symptom = find_symptom (disease)) != NULL)
197 {
198 if (!victim)
199 victim = symptom->env;
200
201 symptom->destroy ();
202 }
203
204 if (victim)
205 victim->update_stats ();
206
207 return 0;
208 }
209
210 /* searches around for more victims to infect */
211 static int
212 check_infection (object *disease)
213 {
214 int range = abs (disease->magic);
215
216 object *op = disease->outer_env_or_self ();
217
218 if (!op->is_on_map ())
219 return 0;
220
221 dynbuf buf;
222 unordered_mapwalk (buf, op, -range, -range, range, range)
223 {
224 mapspace &ms = m->at (nx, ny);
225
226 if (ms.flags () & P_IS_ALIVE)
227 for (object *tmp = ms.bot; tmp; tmp = tmp->above)
228 infect_object (tmp, disease, 0);
229 }
230
231 return 1;
232 }
233
234 /* check if victim is susceptible to disease. */
235 static int
236 is_susceptible_to_disease (object *victim, object *disease)
237 {
238 if (!victim->flag [FLAG_ALIVE])
239 return 0;
240
241 if (victim->flag [FLAG_WIZ])
242 return 0;
243
244 if (disease->race.contains ("*") && !victim->flag [FLAG_UNDEAD])
245 return 1;
246
247 if ((disease->race == shstr_undead) && victim->flag [FLAG_UNDEAD])
248 return 1;
249
250 if ((victim->race && disease->race.contains (victim->race)) || disease->race.contains (victim->name))
251 return 1;
252
253 return 0;
254 }
255
256 /* this function monitors the symptoms caused by the disease (if any),
257 causes symptoms, and modifies existing symptoms in the case of
258 existing diseases. */
259 static int
260 do_symptoms (object *disease)
261 {
262 object *symptom;
263 object *victim;
264 object *tmp;
265
266 victim = disease->env;
267
268 if (!victim)
269 return 0; /* no-one to inflict symptoms on */
270
271 /* This is a quick hack - for whatever reason, disease->env will point
272 * back to disease, causing endless loops. Why this happens really needs
273 * to be found, but this should at least prevent the infinite loops.
274 */
275 //TODO: should no longer be the case, monitor, and remove
276 if (victim == disease)
277 {
278 LOG (llevError | logBacktrace, "%s: disease->env points to itself", disease->debug_desc ());
279 return 0;
280 }
281
282 symptom = find_symptom (disease);
283 if (!symptom)
284 {
285 /* no symptom? need to generate one! */
286
287 /* first check and see if the carrier of the disease is immune. If so, no symptoms! */
288 if (!is_susceptible_to_disease (victim, disease))
289 return 0;
290
291 /* check for an actual immunity */
292 /* do an immunity check */
293 for (tmp = victim->head_ ()->inv; tmp; tmp = tmp->below)
294 if (tmp->name == disease->name && tmp->is_immunity ()) /* possibly an immunity, or diseased */
295 if (tmp->level >= disease->level)
296 return 0; /* Immune! */
297
298 object *new_symptom = archetype::get (shstr_symptom);
299
300 /* Something special done with dam. We want diseases to be more
301 * random in what they'll kill, so we'll make the damage they
302 * do random, note, this has a weird effect with progressive diseases.
303 */
304 if (disease->stats.dam)
305 {
306 int dam = disease->stats.dam;
307
308 /* reduce the damage, on average, 50%, and making things random. */
309
310 dam = random_roll (1, abs (dam), victim, PREFER_LOW);
311 if (disease->stats.dam < 0)
312 dam = -dam;
313
314 new_symptom->stats.dam = dam;
315 }
316
317 new_symptom->stats.maxsp = disease->stats.maxsp;
318 new_symptom->stats.food = new_symptom->stats.maxgrace;
319
320 new_symptom->name = new_symptom->name_pl = disease->name;
321
322 new_symptom->level = disease->level;
323 new_symptom->value = 0;
324
325 new_symptom->set_speed (disease->speed);
326
327 for (int i = 0; i < NUM_STATS; ++i)
328 new_symptom->stats.stat (i) = disease->stats.stat (i);
329
330 new_symptom->stats.sp = disease->stats.sp;
331 new_symptom->stats.food = disease->last_eat;
332 new_symptom->stats.maxsp = disease->stats.maxsp;
333 new_symptom->last_sp = disease->last_sp;
334 new_symptom->stats.exp = 0;
335 new_symptom->stats.hp = disease->stats.hp;
336 new_symptom->msg = disease->msg;
337 new_symptom->attacktype = disease->attacktype;
338 new_symptom->other_arch = disease->other_arch;
339 new_symptom->skill = disease->skill;
340
341 new_symptom->move_block = 0;
342
343 victim->head_ ()->insert (new_symptom);
344
345 // set owner last, as insert clears owner
346 new_symptom->set_owner (disease->owner);
347
348 return 1;
349 }
350
351 /* now deal with progressing diseases: we increase the debility
352 * caused by the symptoms.
353 */
354 if (disease->stats.ac)
355 {
356 symptom->value = clamp (symptom->value + disease->stats.ac, 0, 100*100);
357
358 float scale = 1.f + symptom->value / 100.f;
359
360 /* now rescale all the debilities */
361 for (int i = 0; i < NUM_STATS; ++i)
362 symptom->stats.stat (i) = clamp (int (scale * disease->stats.stat (i)), -128, 127);
363
364 symptom->stats.dam = clamp (scale * disease->stats.dam , -1024, 1024);
365 symptom->stats.food = clamp (scale * disease->last_eat , -1024, 1024);
366 symptom->stats.maxsp = clamp (scale * disease->stats.maxsp, -1024, 1024);
367 symptom->stats.sp = clamp (scale * disease->stats.sp , -1024, 1024);
368 symptom->stats.hp = clamp (scale * disease->stats.hp , -1024, 1024);
369 symptom->stats.exp = 0;
370 symptom->last_sp = disease->last_sp ? clamp (disease->last_sp / scale, 1, 1024) : 0;
371 symptom->msg = disease->msg;
372 symptom->attacktype = disease->attacktype;
373 symptom->other_arch = disease->other_arch;
374 }
375
376 symptom->set_flag (FLAG_APPLIED);
377 victim->update_stats ();
378
379 return 1;
380 }
381
382 int
383 move_disease (object *disease)
384 {
385 /* First task is to determine if the disease is inside or outside of someone.
386 * If outside, we decrement 'value' until we're gone.
387 */
388
389 if (!disease->env)
390 { /* we're outside of someone */
391 if (disease->stats.maxhp > 0)
392 disease->value--;
393
394 if (!disease->value)
395 {
396 disease->destroy ();
397 return 1;
398 }
399 }
400 else
401 {
402 /* if we're inside a person, have the disease run its course */
403 /* negative/zero food denotes "perpetual" diseases. */
404 if (disease->stats.food > 0)
405 {
406 disease->stats.food--;
407
408 if (!disease->stats.food)
409 {
410 remove_symptoms (disease); /* remove the symptoms of this disease */
411 grant_immunity (disease);
412 disease->destroy ();
413 return 1;
414 }
415 }
416 }
417
418 /* check to see if we infect others */
419 check_infection (disease);
420
421 /* impose or modify the symptoms of the disease */
422 if (disease->env && is_susceptible_to_disease (disease->env, disease))
423 do_symptoms (disease);
424
425 return 0;
426 }
427
428 /* check to see if an object is infectable:
429 * objects with immunity aren't infectable.
430 * objects already infected aren't infectable.
431 * dead objects aren't infectable.
432 * undead objects are infectible only if specifically named.
433 */
434 int
435 infect_object (object *victim, object *disease, int force)
436 {
437 object *tmp;
438 object *new_disease;
439
440 /* don't infect inanimate objects */
441 if (!victim->flag [FLAG_MONSTER] && !(victim->type == PLAYER))
442 return 0;
443
444 /* check and see if victim can catch disease: diseases
445 * are specific
446 */
447 if (!is_susceptible_to_disease (victim, disease))
448 return 0;
449
450 /* roll the dice on infection before doing the inventory check! */
451 if (!force && (random_roll (0, 126, victim, PREFER_HIGH) >= disease->stats.wc))
452 return 0;
453
454 /* do an immunity check */
455
456 /* There used to (IMO) be a flaw in the below - it used to be the case
457 * that if level check was done for both immunity and disease. This could
458 * result in a person with multiple afflictions of the same disease
459 * (eg, level 1 cold, level 2 cold, level 3 cold, etc), as long as
460 * they were cast in that same order. Instead, change it so that
461 * if you diseased, you can't get diseased more.
462 */
463
464 for (tmp = victim->head_ ()->inv; tmp; tmp = tmp->below)
465 if (tmp->name == disease->name && tmp->is_immunity () && tmp->level >= disease->level)
466 return 0; /* Immune! */
467 else if (tmp->type == DISEASE && tmp->name == disease->name)
468 return 0; /* already diseased */
469
470 /* If we've gotten this far, go ahead and infect the victim. */
471
472 new_disease = disease->clone ();
473
474 new_disease->stats.food = disease->stats.maxgrace;
475 new_disease->value = disease->stats.maxhp;
476 new_disease->stats.wc -= disease->last_grace; /* self-limiting factor */
477
478 /* This appears to be a horrible case of overloading 'NO_PASS'
479 * for meaning in the diseases.
480 */
481 new_disease->move_block = 0;
482
483 // insert before setting the owner
484 victim->head_ ()->insert (new_disease);
485
486 if (disease->owner)
487 new_disease->set_owner (disease->owner);
488 else if (object *pl = disease->in_player ())
489 /* for diseases which are passed by hitting, set owner and skill */
490 new_disease->set_owner (pl);
491
492 if (new_disease->owner && new_disease->owner->type == PLAYER)
493 {
494 const char *buf;
495
496 /* if the disease has a title, it has a special infection message
497 * This messages is printed in the form MESSAGE victim
498 */
499 if (new_disease->title)
500 buf = format ("%s %s!!", &disease->title, &victim->name);
501 else
502 buf = format ("You infect %s with your disease, %s!", &victim->name, &new_disease->name);
503
504 if (victim->type == PLAYER)
505 new_draw_info (NDI_UNIQUE | NDI_RED, 0, new_disease->owner, buf);
506 else
507 new_draw_info (0, 4, new_disease->owner, buf);
508 }
509
510 if (victim->type == PLAYER)
511 new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, "You suddenly feel ill.");
512
513 return 1;
514 }
515
516 /* make the symptom do the nasty things it does */
517 int
518 move_symptom (object *symptom)
519 {
520 object *victim = symptom->env;
521
522 if (!victim || !victim->map)
523 { /* outside a monster/player, die immediately */
524 symptom->destroy ();
525 return 0;
526 }
527
528 /* create the symptom "other arch" object and drop it here
529 * under every part of the monster
530 * The victim may well have died.
531 */
532 if (victim->map)
533 {
534 victim->play_sound (symptom->sound);
535
536 if (symptom->other_arch)
537 for (object *tmp = victim->head_ (); tmp; tmp = tmp->more)
538 {
539 object *new_ob = symptom->other_arch->instance ();
540 new_ob->x = tmp->x;
541 new_ob->y = tmp->y;
542 new_ob->map = victim->map;
543 insert_ob_in_map (new_ob, victim->map, victim, 0);
544 }
545 }
546
547 int damage =
548 symptom->stats.dam > 0
549 ? symptom->stats.dam
550 : max (1, victim->stats.maxhp * -symptom->stats.dam / 100);
551
552 hit_player (victim, damage, symptom, symptom->attacktype, 1);
553
554 int sp_reduce =
555 symptom->stats.maxsp > 0
556 ? symptom->stats.maxsp
557 : max (1, victim->stats.maxsp * -symptom->stats.maxsp / 100);
558
559 victim->stats.sp = max (0, victim->stats.sp - sp_reduce);
560
561 new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, symptom->msg);
562
563 return 1;
564 }
565
566 /* possibly infect due to direct physical contact
567 * i.e., AT_PHYSICAL-- called from "hit_player_attacktype" */
568 int
569 check_physically_infect (object *victim, object *hitter)
570 {
571 /* search for diseases, give every disease a chance to infect */
572 for (object *disease = hitter->inv; disease; disease = disease->below)
573 if (disease->type == DISEASE)
574 infect_object (victim, disease, 0);
575
576 return 1;
577 }
578
579 /* do the cure disease stuff, from the spell "cure disease" */
580 int
581 cure_disease (object *sufferer, object *caster, object *spell)
582 {
583 object *disease, *next;
584 int cure = 0;
585
586 int casting_level = caster ? caster->level : 1000; /* if null caster, CURE all. */
587
588 for (disease = sufferer->inv; disease; disease = next)
589 {
590 next = disease->below;
591
592 if (disease->type == DISEASE)
593 { /* attempt to cure this disease */
594 /* If caster level is higher than disease level, cure chance
595 * is automatic. If lower, then the chance is basically
596 * 1 in level_diff - if there is a 5 level difference, chance
597 * is 1 in 5.
598 */
599 if ((casting_level >= disease->level) || (!(random_roll (0, (disease->level - casting_level - 1), caster, PREFER_LOW))))
600 {
601 remove_symptoms (disease);
602 cure = 1;
603
604 if (caster && spell)
605 change_exp (caster, disease->stats.exp, spell->skill, SK_EXP_SKILL_ONLY);
606
607 disease->destroy ();
608 }
609 }
610 }
611
612 if (cure)
613 {
614 /* Only draw these messages once */
615 if (caster)
616 new_draw_info_format (NDI_UNIQUE, 0, caster, "You cure a disease!");
617
618 new_draw_info (NDI_UNIQUE, 0, sufferer, "You no longer feel diseased.");
619 }
620
621 return 1;
622 }
623
624 #if 0 // unused, but seems interesting
625 /* reduces disease progression: reduce_symptoms
626 * return true if we actually reduce a disease.
627 */
628 static int
629 reduce_symptoms (object *sufferer, int reduction)
630 {
631 object *walk;
632 int success = 0;
633
634 for (walk = sufferer->inv; walk; walk = walk->below)
635 {
636 if (walk->type == SYMPTOM)
637 {
638 if (walk->value > 0)
639 {
640 success = 1;
641 walk->value = max (0, walk->value - 2 * reduction);
642 /* give the disease time to modify this symptom,
643 * and reduce its severity. */
644 walk->speed_left = 0;
645 }
646 }
647 }
648
649 if (success)
650 new_draw_info (NDI_UNIQUE, 0, sufferer, "Your illness seems less severe.");
651
652 return success;
653 }
654 #endif