ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/disease.C
Revision: 1.74
Committed: Sat Nov 17 23:40:03 2018 UTC (5 years, 5 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.73: +1 -0 lines
Log Message:
copyright update 2018

File Contents

# Content
1 /*
2 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 *
4 * Copyright (©) 2017,2018 Marc Alexander Lehmann / the Deliantra team
5 * Copyright (©) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
6 * Copyright (©) 2002 Mark Wedel & Crossfire Development Team
7 * Copyright (©) 1992 Frank Tore Johansen
8 *
9 * Deliantra is free software: you can redistribute it and/or modify it under
10 * the terms of the Affero GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the Affero GNU General Public License
20 * and the GNU General Public License along with this program. If not, see
21 * <http://www.gnu.org/licenses/>.
22 *
23 * The authors can be reached via e-mail to <support@deliantra.net>
24 */
25
26 /* This file contains all the code implementing diseases,
27 * except for odds and ends in attack.c and in
28 * living.c
29 */
30
31 /*
32
33 For DISEASES:
34 Stat Property Definition
35
36 attacktype Attack effects Attacktype of the disease. usu. AT_GODPOWER.
37 other_arch Creation object created and dropped when symptom moved.
38 title Message When the "disease" "infects" something, it will
39 print "title victim!!!" to the player who owns
40 the "disease".
41 wc+ Infectiousness How well the plague spreads person-to-person
42 magic+ Range range of infection
43 Stats* Disability What stats are reduced by the disease (str con...)
44 maxhp+ Persistence How long the disease can last OUTSIDE the host.
45 value TimeLeft Counter for persistence
46 dam^ Damage How much damage it does (%?).
47 maxgrace+ Duration How long before the disease is naturally cured.
48 food DurCount Counter for Duration
49
50 speed Speed How often the disease moves.
51 last_sp^ Lethargy Percentage of max speed. 10 = 10% speed.
52
53 maxsp^ Mana deplete Saps mana.
54 ac^ Progressiveness How the diseases increases in severity.
55 last_eat*^ Deplete food saps food if negative
56 last_heal GrantImmunity If nonzero, disease does NOT grant immunity
57 when it runs out
58
59 exp experience experience awarded when plague cured
60 hp*^ ReduceRegen reduces regeneration of disease-bearer
61 sp*^ ReduceSpRegen reduces spellpoint regeneration
62
63 name Name Name of the plague
64 msg message What the plague says when it strikes.
65 race those affected species/race the plague strikes (* = everything)
66 level Plague Level General description of the plague's deadliness
67 armour Attenuation reduction in wc per generation of disease.
68 This builds in a self-limiting factor.
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 (INVOKE_OBJECT (INFECT, victim, ARG_OBJECT (disease), ARG_OBJECT (new_disease)))
493 return 1;
494
495 if (new_disease->owner && new_disease->owner->type == PLAYER)
496 {
497 const char *buf;
498
499 /* if the disease has a title, it has a special infection message
500 * This messages is printed in the form MESSAGE victim
501 */
502 if (new_disease->title)
503 buf = format ("%s %s!!", &disease->title, &victim->name);
504 else
505 buf = format ("You infect %s with your disease, %s!", &victim->name, &new_disease->name);
506
507 if (victim->type == PLAYER)
508 new_draw_info (NDI_UNIQUE | NDI_RED, 0, new_disease->owner, buf);
509 else
510 new_draw_info (0, 4, new_disease->owner, buf);
511 }
512
513 if (victim->type == PLAYER)
514 new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, "You suddenly feel ill.");
515
516 return 1;
517 }
518
519 /* make the symptom do the nasty things it does */
520 int
521 move_symptom (object *symptom)
522 {
523 object *victim = symptom->env;
524
525 if (!victim || !victim->map)
526 { /* outside a monster/player, die immediately */
527 symptom->destroy ();
528 return 0;
529 }
530
531 /* create the symptom "other arch" object and drop it here
532 * under every part of the monster
533 * The victim may well have died.
534 */
535 if (victim->map)
536 {
537 victim->play_sound (symptom->sound);
538
539 if (symptom->other_arch)
540 for (object *tmp = victim->head_ (); tmp; tmp = tmp->more)
541 {
542 object *new_ob = symptom->other_arch->instance ();
543 new_ob->x = tmp->x;
544 new_ob->y = tmp->y;
545 new_ob->map = victim->map;
546 insert_ob_in_map (new_ob, victim->map, victim, 0);
547 }
548 }
549
550 int damage =
551 symptom->stats.dam > 0
552 ? symptom->stats.dam
553 : max (1, victim->stats.maxhp * -symptom->stats.dam / 100);
554
555 hit_player (victim, damage, symptom, symptom->attacktype, 1);
556
557 int sp_reduce =
558 symptom->stats.maxsp > 0
559 ? symptom->stats.maxsp
560 : max (1, victim->stats.maxsp * -symptom->stats.maxsp / 100);
561
562 victim->stats.sp = max (0, victim->stats.sp - sp_reduce);
563
564 new_draw_info (NDI_UNIQUE | NDI_RED, 0, victim, symptom->msg);
565
566 return 1;
567 }
568
569 /* possibly infect due to direct physical contact
570 * i.e., AT_PHYSICAL-- called from "hit_player_attacktype" */
571 int
572 check_physically_infect (object *victim, object *hitter)
573 {
574 /* search for diseases, give every disease a chance to infect */
575 for (object *disease = hitter->inv; disease; disease = disease->below)
576 if (disease->type == DISEASE)
577 infect_object (victim, disease, 0);
578
579 return 1;
580 }
581
582 /* do the cure disease stuff, from the spell "cure disease" */
583 int
584 cure_disease (object *sufferer, object *caster, object *spell)
585 {
586 object *disease, *next;
587 int cure = 0;
588
589 int casting_level = caster ? caster->level : 1000; /* if null caster, CURE all. */
590
591 for (disease = sufferer->inv; disease; disease = next)
592 {
593 next = disease->below;
594
595 if (disease->type == DISEASE)
596 { /* attempt to cure this disease */
597 /* If caster level is higher than disease level, cure chance
598 * is automatic. If lower, then the chance is basically
599 * 1 in level_diff - if there is a 5 level difference, chance
600 * is 1 in 5.
601 */
602 if ((casting_level >= disease->level) || (!(random_roll (0, (disease->level - casting_level - 1), caster, PREFER_LOW))))
603 {
604 remove_symptoms (disease);
605 cure = 1;
606
607 if (caster && spell)
608 change_exp (caster, disease->stats.exp, spell->skill, SK_EXP_SKILL_ONLY);
609
610 disease->destroy ();
611 }
612 }
613 }
614
615 if (cure)
616 {
617 /* Only draw these messages once */
618 if (caster)
619 new_draw_info_format (NDI_UNIQUE, 0, caster, "You cure a disease!");
620
621 new_draw_info (NDI_UNIQUE, 0, sufferer, "You no longer feel diseased.");
622 }
623
624 return 1;
625 }
626
627 #if 0 // unused, but seems interesting
628 /* reduces disease progression: reduce_symptoms
629 * return true if we actually reduce a disease.
630 */
631 static int
632 reduce_symptoms (object *sufferer, int reduction)
633 {
634 object *walk;
635 int success = 0;
636
637 for (walk = sufferer->inv; walk; walk = walk->below)
638 {
639 if (walk->type == SYMPTOM)
640 {
641 if (walk->value > 0)
642 {
643 success = 1;
644 walk->value = max (0, walk->value - 2 * reduction);
645 /* give the disease time to modify this symptom,
646 * and reduce its severity. */
647 walk->speed_left = 0;
648 }
649 }
650 }
651
652 if (success)
653 new_draw_info (NDI_UNIQUE, 0, sufferer, "Your illness seems less severe.");
654
655 return success;
656 }
657 #endif