Atrinik Server 2.5
types/disease.c
Go to the documentation of this file.
00001 /************************************************************************
00002 *            Atrinik, a Multiplayer Online Role Playing Game            *
00003 *                                                                       *
00004 *    Copyright (C) 2009-2011 Alex Tokar and Atrinik Development Team    *
00005 *                                                                       *
00006 * Fork from Daimonin (Massive Multiplayer Online Role Playing Game)     *
00007 * and Crossfire (Multiplayer game for X-windows).                       *
00008 *                                                                       *
00009 * This program is free software; you can redistribute it and/or modify  *
00010 * it under the terms of the GNU General Public License as published by  *
00011 * the Free Software Foundation; either version 2 of the License, or     *
00012 * (at your option) any later version.                                   *
00013 *                                                                       *
00014 * This program is distributed in the hope that it will be useful,       *
00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00017 * GNU General Public License for more details.                          *
00018 *                                                                       *
00019 * You should have received a copy of the GNU General Public License     *
00020 * along with this program; if not, write to the Free Software           *
00021 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             *
00022 *                                                                       *
00023 * The author can be reached at admin@atrinik.org                        *
00024 ************************************************************************/
00025 
00036 #include <global.h>
00037 
00038 static int is_susceptible_to_disease(object *victim, object *disease);
00039 static void remove_symptoms(object *disease);
00040 static object *find_symptom(object *disease);
00041 static void check_infection(object *disease);
00042 static void do_symptoms(object *disease);
00043 static void grant_immunity(object *disease);
00044 
00050 static int is_susceptible_to_disease(object *victim, object *disease)
00051 {
00052     if (!IS_LIVE(victim))
00053     {
00054         return 0;
00055     }
00056 
00057     if (strstr(disease->race, "*") && !QUERY_FLAG(victim, FLAG_UNDEAD))
00058     {
00059         return 1;
00060     }
00061 
00062     if (strstr(disease->race, "undead") && QUERY_FLAG(victim, FLAG_UNDEAD))
00063     {
00064         return 1;
00065     }
00066 
00067     if ((victim->race && strstr(disease->race, victim->race)) || strstr(disease->race, victim->name))
00068     {
00069         return 1;
00070     }
00071 
00072     return 0;
00073 }
00074 
00079 int move_disease(object *disease)
00080 {
00081     /* first task is to determine if the disease is inside or outside of someone.
00082      * If outside, we decrement 'value' until we're gone. */
00083 
00084     /* we're outside of someone */
00085     if (disease->env == NULL)
00086     {
00087         disease->value--;
00088 
00089         if (disease->value == 0)
00090         {
00091             /* drop inv since disease may carry secondary infections */
00092             destruct_ob(disease);
00093             return 1;
00094         }
00095     }
00096     else
00097     {
00098         /* if we're inside a person, have the disease run its course.
00099          * negative foods denote "perpetual" diseases. */
00100         if (disease->stats.food > 0 && is_susceptible_to_disease(disease->env, disease))
00101         {
00102             disease->stats.food--;
00103 
00104             if (disease->stats.food == 0)
00105             {
00106                 /* remove the symptoms of this disease */
00107                 remove_symptoms(disease);
00108                 grant_immunity(disease);
00109                 /* drop inv since disease may carry secondary infections */
00110                 destruct_ob(disease);
00111                 return 1;
00112             }
00113         }
00114     }
00115 
00116     /* check to see if we infect others */
00117     check_infection(disease);
00118 
00119     /* impose or modify the symptoms of the disease */
00120     if (disease->env)
00121     {
00122         do_symptoms(disease);
00123     }
00124 
00125     return 0;
00126 }
00127 
00131 static void remove_symptoms(object *disease)
00132 {
00133     object *symptom = find_symptom(disease);
00134 
00135     if (symptom != NULL)
00136     {
00137         object *victim = symptom->env;
00138         destruct_ob(symptom);
00139 
00140         if (victim)
00141         {
00142             fix_player(victim);
00143         }
00144     }
00145 }
00146 
00151 static object *find_symptom(object *disease)
00152 {
00153     object *walk;
00154 
00155     /* check the inventory for symptoms */
00156     for (walk = disease->env->inv; walk; walk = walk->below)
00157     {
00158         if (!strcmp(walk->name, disease->name) && walk->type == SYMPTOM)
00159         {
00160             return walk;
00161         }
00162     }
00163 
00164     return NULL;
00165 }
00166 
00170 static void check_infection(object *disease)
00171 {
00172     int x, y, i, j, range, xt, yt;
00173     mapstruct *map, *m;
00174     object *tmp;
00175     rv_vector rv;
00176 
00177     range = abs(disease->magic);
00178 
00179     if (disease->env)
00180     {
00181         x = disease->env->x;
00182         y = disease->env->y;
00183         map = disease->env->map;
00184     }
00185     else
00186     {
00187         x = disease->x;
00188         y = disease->y;
00189         map = disease->map;
00190     }
00191 
00192     if (map == NULL)
00193     {
00194         return;
00195     }
00196 
00197     for (i = -range; i <= range; i++)
00198     {
00199         for (j = -range; j <= range; j++)
00200         {
00201             xt = x + i;
00202             yt = y + j;
00203 
00204             if (!(m = get_map_from_coord(map, &xt, &yt)))
00205             {
00206                 continue;
00207             }
00208 
00209             if (!(GET_MAP_FLAGS(m, xt, yt) & (P_IS_ALIVE | P_IS_PLAYER)))
00210             {
00211                 continue;
00212             }
00213 
00214             for (tmp = GET_MAP_OB(m, xt, yt); tmp; tmp = tmp->above)
00215             {
00216                 if (!QUERY_FLAG(tmp, FLAG_MONSTER) && tmp->type != PLAYER)
00217                 {
00218                     continue;
00219                 }
00220 
00221                 if (!get_rangevector(disease->env ? disease->env : disease, tmp, &rv, 0) || !obj_in_line_of_sight(tmp, &rv))
00222                 {
00223                     continue;
00224                 }
00225 
00226                 infect_object(tmp, disease, 0);
00227             }
00228         }
00229     }
00230 }
00231 
00243 int infect_object(object *victim, object *disease, int force)
00244 {
00245     object *tmp, *new_disease, *owner;
00246 
00247     /* only use the head */
00248     if (victim->head)
00249     {
00250         victim = victim->head;
00251     }
00252 
00253     /* don't infect inanimate objects */
00254     if (!QUERY_FLAG(victim, FLAG_MONSTER) && victim->type != PLAYER)
00255     {
00256         return 0;
00257     }
00258 
00259     if ((owner = get_owner(disease)))
00260     {
00261         if (is_friend_of(owner, victim))
00262         {
00263             return 0;
00264         }
00265     }
00266 
00267     /* check and see if victim can catch disease:  diseases are specific */
00268     if (!is_susceptible_to_disease(victim, disease))
00269     {
00270         return 0;
00271     }
00272 
00273     /* roll the dice on infection before doing the inventory check! */
00274     if (!force && (rndm(0, 126) >= disease->stats.wc))
00275     {
00276         return 0;
00277     }
00278 
00279     for (tmp = victim->inv; tmp; tmp = tmp->below)
00280     {
00281         /* possibly an immunity, or diseased */
00282         if (tmp->type == SIGN || tmp->type == DISEASE)
00283         {
00284             if (!strcmp(tmp->name, disease->name) && tmp->level >= disease->level)
00285             {
00286                 return 0;
00287             }
00288         }
00289     }
00290 
00291     /* If we've gotten this far, go ahead and infect the victim. */
00292     new_disease = get_object();
00293     copy_object(disease, new_disease, 0);
00294     new_disease->stats.food = disease->stats.maxgrace;
00295     new_disease->value = disease->stats.maxhp;
00296     /* self-limiting factor */
00297     new_disease->stats.wc -= disease->last_grace;
00298 
00299     /* Unfortunately, set_owner does the wrong thing to the skills pointers
00300      * resulting in exp going into the owners *current* chosen skill. */
00301     if (get_owner(disease))
00302     {
00303         set_owner(new_disease, disease->owner);
00304         new_disease->chosen_skill = disease->chosen_skill;
00305         new_disease->exp_obj = disease->exp_obj;
00306     }
00307     /* for diseases which are passed by hitting, set owner and praying skill */
00308     else
00309     {
00310         if (disease->env && disease->env->type == PLAYER)
00311         {
00312             object *pl = disease->env;
00313 
00314             /* hm, we should for hit use the weapon? or the skill attached to this
00315              * specific disease? hmmm */
00316             new_disease->chosen_skill = find_skill(pl, SK_PRAYING);
00317 
00318             if (new_disease->chosen_skill)
00319             {
00320                 set_owner(new_disease, pl);
00321                 new_disease->exp_obj = new_disease->chosen_skill->exp_obj;
00322             }
00323         }
00324     }
00325 
00326     insert_ob_in_ob(new_disease, victim);
00327     CLEAR_FLAG(new_disease, FLAG_NO_PASS);
00328 
00329     if (new_disease->owner && new_disease->owner->type == PLAYER)
00330     {
00331         char buf[128];
00332 
00333         /* if the disease has a title, it has a special infection message */
00334         if (new_disease->title)
00335         {
00336             snprintf(buf, sizeof(buf), "%s %s!!", disease->title, victim->name);
00337         }
00338         else
00339         {
00340             snprintf(buf, sizeof(buf), "You infect %s with your disease, %s!", victim->name, new_disease->name);
00341         }
00342 
00343         if (victim->type == PLAYER)
00344         {
00345             new_draw_info(0, COLOR_RED, new_disease->owner, buf);
00346         }
00347         else
00348         {
00349             new_draw_info(0, COLOR_WHITE, new_disease->owner, buf);
00350         }
00351     }
00352 
00353     if (victim->type == PLAYER)
00354     {
00355         new_draw_info(0, COLOR_RED, victim, "You suddenly feel ill.");
00356     }
00357 
00358     return 1;
00359 }
00360 
00366 static void do_symptoms(object *disease)
00367 {
00368     object *symptom, *victim = disease->env, *tmp;
00369 
00370     /* This is a quick hack - for whatever reason, disease->env will point
00371     * back to disease, causing endless loops. Why this happens really needs
00372     * to be found, but this should at least prevent the infinite loops. */
00373 
00374     /* no-one to inflict symptoms on. */
00375     if (victim == NULL || victim == disease)
00376     {
00377         return;
00378     }
00379 
00380     symptom = find_symptom(disease);
00381 
00382     /* no symptom?  need to generate one! */
00383     if (symptom == NULL)
00384     {
00385         object *new_symptom;
00386         int i;
00387 
00388         /* first check and see if the carrier of the disease
00389          * is immune. If so, no symptoms!  */
00390         if (!is_susceptible_to_disease(victim, disease))
00391         {
00392             return;
00393         }
00394 
00395         /* check for an actual immunity */
00396         /* do an immunity check */
00397         /* hm, disease should be NEVER in a tail of a multi part object */
00398         if (victim->head)
00399         {
00400             tmp = victim->head->inv;
00401         }
00402         else
00403         {
00404             tmp = victim->inv;
00405         }
00406 
00407         /* tmp initialized in if, above */
00408         for (; tmp; tmp = tmp->below)
00409         {
00410             /* possibly an immunity, or diseased */
00411             if (tmp->type == SIGN)
00412             {
00413                 if (!strcmp(tmp->name, disease->name) && tmp->level >= disease->level)
00414                 {
00415                     return;
00416                 }
00417             }
00418         }
00419 
00420         new_symptom = get_archetype("symptom");
00421 
00422         /* Something special done with dam. We want diseases to be more
00423          * random in what they'll kill, so we'll make the damage they
00424          * do random, note, this has a weird effect with progressive diseases. */
00425         if (disease->stats.dam != 0)
00426         {
00427             int dam = disease->stats.dam;
00428 
00429             /* reduce the damage, on average, 50%, and making things random. */
00430             dam = rndm(1, FABS(dam));
00431 
00432             if (disease->stats.dam < 0)
00433             {
00434                 dam = -dam;
00435             }
00436 
00437             new_symptom->stats.dam = dam;
00438         }
00439 
00440         new_symptom->stats.maxsp = disease->stats.maxsp;
00441         new_symptom->stats.food = new_symptom->stats.maxgrace;
00442 
00443         FREE_AND_COPY_HASH(new_symptom->name, disease->name);
00444         new_symptom->level = disease->level;
00445         new_symptom->speed = disease->speed;
00446         new_symptom->value = 0;
00447         new_symptom->stats.Str = disease->stats.Str;
00448         new_symptom->stats.Dex = disease->stats.Dex;
00449         new_symptom->stats.Con = disease->stats.Con;
00450         new_symptom->stats.Wis = disease->stats.Wis;
00451         new_symptom->stats.Int = disease->stats.Int;
00452         new_symptom->stats.Pow = disease->stats.Pow;
00453         new_symptom->stats.Cha = disease->stats.Cha;
00454         new_symptom->stats.sp  = disease->stats.sp;
00455         new_symptom->stats.food = disease->last_eat;
00456         new_symptom->stats.maxsp = disease->stats.maxsp;
00457         new_symptom->last_sp = disease->last_sp;
00458         new_symptom->stats.exp = 0;
00459         new_symptom->stats.hp = disease->stats.hp;
00460         FREE_AND_COPY_HASH(new_symptom->msg, disease->msg);
00461         new_symptom->other_arch = disease->other_arch;
00462 
00463         for (i = 0; i < NROFATTACKS; i++)
00464         {
00465             if (disease->attack[i])
00466             {
00467                 new_symptom->attack[i] = disease->attack[i];
00468             }
00469         }
00470 
00471         set_owner(new_symptom, disease->owner);
00472 
00473         /* Unfortunately, set_owner does the wrong thing to the skills pointers
00474          * resulting in exp going into the owners *current* chosen skill. */
00475         new_symptom->chosen_skill = disease->chosen_skill;
00476         new_symptom->exp_obj = disease->exp_obj;
00477 
00478         CLEAR_FLAG(new_symptom, FLAG_NO_PASS);
00479         insert_ob_in_ob(new_symptom, victim);
00480         return;
00481     }
00482 
00483     /* now deal with progressing diseases: we increase the debility
00484      * caused by the symptoms.  */
00485     if (disease->stats.ac != 0)
00486     {
00487         float scale;
00488 
00489         symptom->value += disease->stats.ac;
00490         scale = (float) 1.0 + (float) symptom->value / (float) 100.0;
00491 
00492         /* now rescale all the debilities */
00493         symptom->stats.Str = (int) (scale * disease->stats.Str);
00494         symptom->stats.Dex = (int) (scale * disease->stats.Dex);
00495         symptom->stats.Con = (int) (scale * disease->stats.Con);
00496         symptom->stats.Wis = (int) (scale * disease->stats.Wis);
00497         symptom->stats.Int = (int) (scale * disease->stats.Int);
00498         symptom->stats.Pow = (int) (scale * disease->stats.Pow);
00499         symptom->stats.Cha = (int) (scale * disease->stats.Cha);
00500         symptom->stats.dam = (int) (scale * disease->stats.dam);
00501         symptom->stats.sp = (int) (scale * disease->stats.sp);
00502         symptom->stats.food = (int) (scale * disease->last_eat);
00503         symptom->stats.maxsp = (int) (scale * disease->stats.maxsp);
00504         symptom->last_sp = (int) (scale * disease->last_sp);
00505         symptom->stats.exp = 0;
00506         symptom->stats.hp = (int) (scale * disease->stats.hp);
00507         FREE_AND_COPY_HASH(symptom->msg, disease->msg);
00508         symptom->other_arch = disease->other_arch;
00509     }
00510 
00511     SET_FLAG(symptom, FLAG_APPLIED);
00512     fix_player(victim);
00513 }
00514 
00518 static void grant_immunity(object *disease)
00519 {
00520     object *immunity, *walk;
00521 
00522     /* Don't give immunity to this disease if last_heal is set. */
00523     if (disease->last_heal)
00524     {
00525         return;
00526     }
00527 
00528     /* first, search for an immunity of the same name */
00529     for (walk = disease->env->inv; walk; walk = walk->below)
00530     {
00531         if (walk->type == SIGN && !strcmp(disease->name, walk->name))
00532         {
00533             walk->level = disease->level;
00534             /* just update the existing immunity. */
00535             return;
00536         }
00537     }
00538 
00539     immunity = get_archetype("immunity");
00540     FREE_AND_COPY_HASH(immunity->name, disease->name);
00541     immunity->level = disease->level;
00542     CLEAR_FLAG(immunity, FLAG_NO_PASS);
00543     insert_ob_in_ob(immunity, disease->env);
00544 }
00545 
00549 void move_symptom(object *symptom)
00550 {
00551     object *victim = symptom->env;
00552     object *new_ob;
00553     int sp_reduce;
00554 
00555     /* outside a monster/player, die immediately */
00556     if (victim == NULL || victim->map == NULL)
00557     {
00558         remove_ob(symptom);
00559         check_walk_off(symptom, NULL, MOVE_APPLY_VANISHED);
00560         return;
00561     }
00562 
00563     if (symptom->stats.dam > 0)
00564     {
00565         hit_player(victim, symptom->stats.dam, symptom, AT_INTERNAL);
00566     }
00567     else
00568     {
00569         hit_player(victim, (int) MAX((float) 1, (float) - victim->stats.maxhp * (float) symptom->stats.dam / (float) 100.0), symptom, AT_INTERNAL);
00570     }
00571 
00572     if (symptom->stats.maxsp > 0)
00573     {
00574         sp_reduce = symptom->stats.maxsp;
00575     }
00576     else
00577     {
00578         sp_reduce = (int) MAX((float) 1, (float) victim->stats.maxsp * (float) symptom->stats.maxsp / (float) 100.0);
00579     }
00580 
00581     victim->stats.sp = MAX(0, victim->stats.sp - sp_reduce);
00582 
00583     /* create the symptom "other arch" object and drop it here
00584      * under every part of the monster */
00585 
00586     /* The victim may well have died. */
00587     if (victim->map == NULL)
00588     {
00589         return;
00590     }
00591 
00592     if (symptom->other_arch)
00593     {
00594         object *tmp = victim;
00595 
00596         if (tmp->head != NULL)
00597         {
00598             tmp = tmp->head;
00599         }
00600 
00601         /* tmp initialized above */
00602         for (; tmp != NULL; tmp = tmp->more)
00603         {
00604             new_ob = arch_to_object(symptom->other_arch);
00605             new_ob->x = tmp->x;
00606             new_ob->y = tmp->y;
00607             new_ob->map = victim->map;
00608             insert_ob_in_map(new_ob, victim->map, victim, INS_NO_MERGE | INS_NO_WALK_ON);
00609         }
00610     }
00611 
00612     if (victim->type == PLAYER)
00613     {
00614         new_draw_info(0, COLOR_RED, victim, symptom->msg);
00615     }
00616 }
00617 
00624 void check_physically_infect(object *victim, object *hitter)
00625 {
00626     object *walk;
00627 
00628     /* search for diseases, give every disease a chance to infect */
00629     for (walk = hitter->inv; walk != NULL; walk = walk->below)
00630     {
00631         if (walk->type == DISEASE)
00632         {
00633             infect_object(victim, walk, 0);
00634         }
00635     }
00636 }
00637 
00645 int cure_disease(object *sufferer, object *caster)
00646 {
00647     object *disease, *next;
00648     int casting_level, is_disease = 0;
00649 
00650     if (caster)
00651     {
00652         casting_level = caster->level;
00653     }
00654     else
00655     {
00656         caster = sufferer;
00657         /* if null caster, CURE all. */
00658         casting_level = 1000;
00659     }
00660 
00661     if (caster != sufferer && sufferer->type == PLAYER)
00662     {
00663         new_draw_info_format(0, COLOR_WHITE, sufferer, "%s casts cure disease on you!", caster->name ? caster->name : "someone");
00664     }
00665 
00666     for (disease = sufferer->inv; disease; disease = next)
00667     {
00668         next = disease->below;
00669 
00670         /* attempt to cure this disease */
00671         /* If caster level is higher than disease level, cure chance
00672          * is automatic. If lower, then the chance is basically
00673          * 1 in level_diff - if there is a 5 level difference, chance
00674          * is 1 in 5. */
00675         if (disease->type == DISEASE)
00676         {
00677             is_disease = 1;
00678 
00679             if ((casting_level >= disease->level) || (!(rndm(0, (disease->level - casting_level - 1)))))
00680             {
00681                 if (sufferer->type == PLAYER)
00682                 {
00683                     new_draw_info_format(0, COLOR_WHITE, sufferer, "You are healed from disease %s.", disease->name);
00684                 }
00685 
00686                 if (sufferer != caster && caster->type == PLAYER)
00687                 {
00688                     new_draw_info_format(0, COLOR_WHITE, caster, "You heal %s from disease %s.", sufferer->name, disease->name);
00689                 }
00690 
00691                 remove_symptoms(disease);
00692                 remove_ob(disease);
00693 
00694                 /* we assume the caster has the right casting skill applied */
00695                 if (caster && !caster->type == PLAYER)
00696                 {
00697                     add_exp(caster, disease->stats.exp, caster->chosen_skill->stats.sp, 0);
00698                 }
00699             }
00700             else
00701             {
00702                 if (sufferer->type == PLAYER)
00703                 {
00704                     new_draw_info_format(0, COLOR_WHITE, sufferer, "The disease %s resists the cure prayer!", disease->name);
00705                 }
00706 
00707                 if (sufferer != caster && caster->type == PLAYER)
00708                 {
00709                     new_draw_info_format(0, COLOR_WHITE, caster, "The disease %s resists the cure prayer!", disease->name);
00710                 }
00711             }
00712         }
00713     }
00714 
00715     if (!is_disease)
00716     {
00717         if (sufferer->type == PLAYER)
00718         {
00719             new_draw_info(0, COLOR_WHITE, sufferer, "You are not diseased!");
00720         }
00721 
00722         if (sufferer != caster && caster->type == PLAYER)
00723         {
00724             new_draw_info_format(0, COLOR_WHITE, caster, "%s is not diseased!", sufferer->name ? sufferer->name : "someone");
00725         }
00726     }
00727 
00728     return 1;
00729 }
00730 
00736 int reduce_symptoms(object *sufferer, int reduction)
00737 {
00738     object *walk;
00739     int success = 0;
00740 
00741     for (walk = sufferer->inv; walk; walk = walk->below)
00742     {
00743         if (walk->type == SYMPTOM)
00744         {
00745             if (walk->value > 0)
00746             {
00747                 success = 1;
00748                 walk->value = MAX(0, walk->value - 2 * reduction);
00749                 /* give the disease time to modify this symptom,
00750                  * and reduce its severity. */
00751                 walk->speed_left = 0;
00752             }
00753         }
00754     }
00755 
00756     if (success)
00757     {
00758         new_draw_info(0, COLOR_WHITE, sufferer, "Your illness seems less severe.");
00759     }
00760 
00761     return success;
00762 }