ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/disease.C
Revision: 1.53
Committed: Mon Oct 12 14:00:59 2009 UTC (14 years, 7 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_82, rel-2_81
Changes since 1.52: +7 -6 lines
Log Message:
clarify license

File Contents

# Content
1 /*
2 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 *
4 * Copyright (©) 2005,2006,2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
6 * Copyright (©) 1992,2007 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 (walk->type == 98 && disease->name == walk->name)
157 {
158 walk->level = disease->level;
159 return 1; /* just update the existing immunity. */
160 }
161
162 immunity = get_archetype ("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 unordered_mapwalk (op, -range, -range, range, range)
222 {
223 mapspace &ms = m->at (nx, ny);
224
225 if (ms.flags () & P_IS_ALIVE)
226 for (object *tmp = ms.bot; tmp; tmp = tmp->above)
227 infect_object (tmp, disease, 0);
228 }
229
230 return 1;
231 }
232
233 /* check if victim is susceptible to disease. */
234 static int
235 is_susceptible_to_disease (object *victim, object *disease)
236 {
237 if (!QUERY_FLAG (victim, FLAG_ALIVE))
238 return 0;
239
240 if (victim->flag [FLAG_WIZ])
241 return 0;
242
243 if (disease->race.contains ("*") && !QUERY_FLAG (victim, FLAG_UNDEAD))
244 return 1;
245
246 if ((disease->race == shstr_undead) && QUERY_FLAG (victim, FLAG_UNDEAD))
247 return 1;
248
249 if ((victim->race && disease->race.contains (victim->race)) || disease->race.contains (victim->name))
250 return 1;
251
252 return 0;
253 }
254
255 /* this function monitors the symptoms caused by the disease (if any),
256 causes symptoms, and modifies existing symptoms in the case of
257 existing diseases. */
258 static int
259 do_symptoms (object *disease)
260 {
261 object *symptom;
262 object *victim;
263 object *tmp;
264
265 victim = disease->env;
266
267 if (!victim)
268 return 0; /* no-one to inflict symptoms on */
269
270 /* This is a quick hack - for whatever reason, disease->env will point
271 * back to disease, causing endless loops. Why this happens really needs
272 * to be found, but this should at least prevent the infinite loops.
273 */
274 //TODO: should no longer be the case, monitor, and remove
275 if (victim == disease)
276 {
277 LOG (llevError | logBacktrace, "%s: disease->env points to itself", disease->debug_desc ());
278 return 0;
279 }
280
281 symptom = find_symptom (disease);
282 if (!symptom)
283 {
284 /* no symptom? need to generate one! */
285
286 /* first check and see if the carrier of the disease is immune. If so, no symptoms! */
287 if (!is_susceptible_to_disease (victim, disease))
288 return 0;
289
290 /* check for an actual immunity */
291 /* do an immunity check */
292 for (tmp = victim->head_ ()->inv; tmp; tmp = tmp->below)
293 if (tmp->type == SIGN) /* possibly an immunity, or diseased */
294 if (tmp->name == disease->name && tmp->level >= disease->level)
295 return 0; /* Immune! */
296
297 object *new_symptom = get_archetype ("symptom");
298
299 /* Something special done with dam. We want diseases to be more
300 * random in what they'll kill, so we'll make the damage they
301 * do random, note, this has a weird effect with progressive diseases.
302 */
303 if (disease->stats.dam)
304 {
305 int dam = disease->stats.dam;
306
307 /* reduce the damage, on average, 50%, and making things random. */
308
309 dam = random_roll (1, abs (dam), victim, PREFER_LOW);
310 if (disease->stats.dam < 0)
311 dam = -dam;
312
313 new_symptom->stats.dam = dam;
314 }
315
316 new_symptom->stats.maxsp = disease->stats.maxsp;
317 new_symptom->stats.food = new_symptom->stats.maxgrace;
318
319 new_symptom->name = new_symptom->name_pl = disease->name;
320
321 new_symptom->level = disease->level;
322 new_symptom->speed = disease->speed;
323 new_symptom->value = 0;
324
325 for (int i = 0; i < NUM_STATS; ++i)
326 new_symptom->stats.stat (i) = disease->stats.stat (i);
327
328 new_symptom->stats.sp = disease->stats.sp;
329 new_symptom->stats.food = disease->last_eat;
330 new_symptom->stats.maxsp = disease->stats.maxsp;
331 new_symptom->last_sp = disease->last_sp;
332 new_symptom->stats.exp = 0;
333 new_symptom->stats.hp = disease->stats.hp;
334 new_symptom->msg = disease->msg;
335 new_symptom->attacktype = disease->attacktype;
336 new_symptom->other_arch = disease->other_arch;
337 new_symptom->skill = disease->skill;
338
339 new_symptom->move_block = 0;
340
341 victim->head_ ()->insert (new_symptom);
342
343 // set owner last, as insert clears owner
344 new_symptom->set_owner (disease->owner);
345
346 return 1;
347 }
348
349 /* now deal with progressing diseases: we increase the debility
350 * caused by the symptoms.
351 */
352 if (disease->stats.ac)
353 {
354 symptom->value = clamp (symptom->value + disease->stats.ac, 0, 100*100);
355
356 float scale = 1.f + symptom->value / 100.f;
357
358 /* now rescale all the debilities */
359 for (int i = 0; i < NUM_STATS; ++i)
360 symptom->stats.stat (i) = clamp (int (scale * disease->stats.stat (i)), -128, 127);
361
362 symptom->stats.dam = clamp (scale * disease->stats.dam , -1024, 1024);
363 symptom->stats.food = clamp (scale * disease->last_eat , -1024, 1024);
364 symptom->stats.maxsp = clamp (scale * disease->stats.maxsp, -1024, 1024);
365 symptom->stats.sp = clamp (scale * disease->stats.sp , -1024, 1024);
366 symptom->stats.hp = clamp (scale * disease->stats.hp , -1024, 1024);
367 symptom->stats.exp = 0;
368 symptom->last_sp = disease->last_sp ? clamp (disease->last_sp / scale, 1, 1024) : 0;
369 symptom->msg = disease->msg;
370 symptom->attacktype = disease->attacktype;
371 symptom->other_arch = disease->other_arch;
372 }
373
374 SET_FLAG (symptom, FLAG_APPLIED);
375 victim->update_stats ();
376
377 return 1;
378 }
379
380 int
381 move_disease (object *disease)
382 {
383 /* First task is to determine if the disease is inside or outside of someone.
384 * If outside, we decrement 'value' until we're gone.
385 */
386
387 if (!disease->env)
388 { /* we're outside of someone */
389 if (disease->stats.maxhp > 0)
390 disease->value--;
391
392 if (!disease->value)
393 {
394 disease->destroy ();
395 return 1;
396 }
397 }
398 else
399 {
400 /* if we're inside a person, have the disease run its course */
401 /* negative/zero food denotes "perpetual" diseases. */
402 if (disease->stats.food > 0)
403 {
404 disease->stats.food--;
405
406 if (!disease->stats.food)
407 {
408 remove_symptoms (disease); /* remove the symptoms of this disease */
409 grant_immunity (disease);
410 disease->destroy ();
411 return 1;
412 }
413 }
414 }
415
416 /* check to see if we infect others */
417 check_infection (disease);
418
419 /* impose or modify the symptoms of the disease */
420 if (disease->env && is_susceptible_to_disease (disease->env, disease))
421 do_symptoms (disease);
422
423 return 0;
424 }
425
426 /* check to see if an object is infectable:
427 * objects with immunity aren't infectable.
428 * objects already infected aren't infectable.
429 * dead objects aren't infectable.
430 * undead objects are infectible only if specifically named.
431 */
432 int
433 infect_object (object *victim, object *disease, int force)
434 {
435 object *tmp;
436 object *new_disease;
437
438 /* don't infect inanimate objects */
439 if (!QUERY_FLAG (victim, FLAG_MONSTER) && !(victim->type == PLAYER))
440 return 0;
441
442 /* check and see if victim can catch disease: diseases
443 * are specific
444 */
445 if (!is_susceptible_to_disease (victim, disease))
446 return 0;
447
448 /* roll the dice on infection before doing the inventory check! */
449 if (!force && (random_roll (0, 126, victim, PREFER_HIGH) >= disease->stats.wc))
450 return 0;
451
452 /* do an immunity check */
453
454 /* There used to (IMO) be a flaw in the below - it used to be the case
455 * that if level check was done for both immunity and disease. This could
456 * result in a person with multiple afflictions of the same disease
457 * (eg, level 1 cold, level 2 cold, level 3 cold, etc), as long as
458 * they were cast in that same order. Instead, change it so that
459 * if you diseased, you can't get diseased more.
460 */
461
462 for (tmp = victim->head_ ()->inv; tmp; tmp = tmp->below)
463 if (tmp->type == SIGN && tmp->name == disease->name && tmp->level >= disease->level)
464 return 0; /* Immune! */
465 else if (tmp->type == DISEASE && tmp->name == disease->name)
466 return 0; /* already diseased */
467
468 /* If we've gotten this far, go ahead and infect the victim. */
469
470 new_disease = disease->clone ();
471
472 new_disease->stats.food = disease->stats.maxgrace;
473 new_disease->value = disease->stats.maxhp;
474 new_disease->stats.wc -= disease->last_grace; /* self-limiting factor */
475
476 /* This appears to be a horrible case of overloading 'NO_PASS'
477 * for meaning in the diseases.
478 */
479 new_disease->move_block = 0;
480
481 // insert before setting the owner
482 victim->head_ ()->insert (new_disease);
483
484 if (disease->owner)
485 new_disease->set_owner (disease->owner);
486 else if (object *pl = disease->in_player ())
487 /* for diseases which are passed by hitting, set owner and skill */
488 new_disease->set_owner (pl);
489
490 if (new_disease->owner && new_disease->owner->type == PLAYER)
491 {
492 const char *buf;
493
494 /* if the disease has a title, it has a special infection message
495 * This messages is printed in the form MESSAGE victim
496 */
497 if (new_disease->title)
498 buf = format ("%s %s!!", &disease->title, &victim->name);
499 else
500 buf = format ("You infect %s with your disease, %s!", &victim->name, &new_disease->name);
501
502 if (victim->type == PLAYER)
503 new_draw_info (NDI_UNIQUE | NDI_RED, 0, new_disease->owner, buf);
504 else
505 new_draw_info (0, 4, new_disease->owner, buf);
506 }
507
508 if (victim->type == PLAYER)
509 new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, "You suddenly feel ill.");
510
511 return 1;
512 }
513
514 /* make the symptom do the nasty things it does */
515 int
516 move_symptom (object *symptom)
517 {
518 object *victim = symptom->env;
519 object *new_ob;
520 int sp_reduce;
521
522 if (!victim || !victim->map)
523 { /* outside a monster/player, die immediately */
524 symptom->destroy ();
525 return 0;
526 }
527
528 if (symptom->stats.dam > 0)
529 hit_player (victim, symptom->stats.dam, symptom, symptom->attacktype, 1);
530 else
531 hit_player (victim, max (1, -victim->stats.maxhp * symptom->stats.dam / 100), symptom, symptom->attacktype, 1);
532
533 if (symptom->stats.maxsp > 0)
534 sp_reduce = symptom->stats.maxsp;
535 else
536 sp_reduce = max (1, victim->stats.maxsp * symptom->stats.maxsp / 100);
537
538 victim->stats.sp = max (0, victim->stats.sp - sp_reduce);
539
540 /* create the symptom "other arch" object and drop it here
541 * under every part of the monster
542 * The victim may well have died.
543 */
544 if (victim->map)
545 {
546 victim->play_sound (symptom->sound);
547
548 if (symptom->other_arch)
549 for (object *tmp = victim->head_ (); tmp; tmp = tmp->more)
550 {
551 new_ob = arch_to_object (symptom->other_arch);
552 new_ob->x = tmp->x;
553 new_ob->y = tmp->y;
554 new_ob->map = victim->map;
555 insert_ob_in_map (new_ob, victim->map, victim, 0);
556 }
557 }
558
559 new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, symptom->msg);
560
561 return 1;
562 }
563
564 /* possibly infect due to direct physical contact
565 * i.e., AT_PHYSICAL-- called from "hit_player_attacktype" */
566 int
567 check_physically_infect (object *victim, object *hitter)
568 {
569 /* search for diseases, give every disease a chance to infect */
570 for (object *disease = hitter->inv; disease; disease = disease->below)
571 if (disease->type == DISEASE)
572 infect_object (victim, disease, 0);
573
574 return 1;
575 }
576
577 // find a disease in someone
578 object *
579 find_disease (object *victim)
580 {
581 for (object *disease = victim->inv; disease; disease = disease->below)
582 if (disease->type == DISEASE)
583 return disease;
584
585 return 0;
586 }
587
588 /* do the cure disease stuff, from the spell "cure disease" */
589 int
590 cure_disease (object *sufferer, object *caster, object *spell)
591 {
592 object *disease, *next;
593 int cure = 0;
594
595 int casting_level = caster ? caster->level : 1000; /* if null caster, CURE all. */
596
597 for (disease = sufferer->inv; disease; disease = next)
598 {
599 next = disease->below;
600
601 if (disease->type == DISEASE)
602 { /* attempt to cure this disease */
603 /* If caster level is higher than disease level, cure chance
604 * is automatic. If lower, then the chance is basically
605 * 1 in level_diff - if there is a 5 level difference, chance
606 * is 1 in 5.
607 */
608 if ((casting_level >= disease->level) || (!(random_roll (0, (disease->level - casting_level - 1), caster, PREFER_LOW))))
609 {
610 remove_symptoms (disease);
611 cure = 1;
612
613 if (caster && spell)
614 change_exp (caster, disease->stats.exp, spell->skill, SK_EXP_SKILL_ONLY);
615
616 disease->destroy ();
617 }
618 }
619 }
620
621 if (cure)
622 {
623 /* Only draw these messages once */
624 if (caster)
625 new_draw_info_format (NDI_UNIQUE, 0, caster, "You cure a disease!");
626
627 new_draw_info (NDI_UNIQUE, 0, sufferer, "You no longer feel diseased.");
628 }
629
630 return 1;
631 }
632
633 /* reduces disease progression: reduce_symptoms
634 * return true if we actually reduce a disease.
635 */
636 int
637 reduce_symptoms (object *sufferer, int reduction)
638 {
639 object *walk;
640 int success = 0;
641
642 for (walk = sufferer->inv; walk; walk = walk->below)
643 {
644 if (walk->type == SYMPTOM)
645 {
646 if (walk->value > 0)
647 {
648 success = 1;
649 walk->value = max (0, walk->value - 2 * reduction);
650 /* give the disease time to modify this symptom,
651 * and reduce its severity. */
652 walk->speed_left = 0;
653 }
654 }
655 }
656
657 if (success)
658 new_draw_info (NDI_UNIQUE, 0, sufferer, "Your illness seems less severe.");
659
660 return success;
661 }