Atrinik Server 2.5
types/potion.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 
00030 #include <global.h>
00031 
00037 #define POTION_IMPROVE_STATS 3
00038 
00039 #define POTION_IMPROVE_HP 0
00040 
00041 #define POTION_IMPROVE_SP 1
00042 
00043 #define POTION_IMPROVE_GRACE 2
00044 
00049 static const size_t potion_improve_stat_offsets[POTION_IMPROVE_STATS] =
00050 {
00051     offsetof(player, levhp), offsetof(player, levsp), offsetof(player, levgrace)
00052 };
00053 
00058 static const char *const potion_improve_stat_names[POTION_IMPROVE_STATS] =
00059 {
00060     "health", "mana", "grace"
00061 };
00062 
00068 static int potion_improve_apply(object *op, object *tmp)
00069 {
00070     int indices[POTION_IMPROVE_STATS] = {POTION_IMPROVE_HP, POTION_IMPROVE_SP, POTION_IMPROVE_GRACE};
00071     int stat_id, i, level, max, cursed;
00072     char *levarr, oldlev;
00073 
00074     /* Shuffle the array so we check all the stats for possible
00075      * improvement in random order. */
00076     permute(indices, 0, POTION_IMPROVE_STATS);
00077     /* Determine whether the potion is cursed or not. */
00078     cursed = QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED);
00079 
00080     /* A potion will be used up even if we don't actually increase any
00081      * stats. */
00082     decrease_ob(tmp);
00083 
00084     /* Check all the stats. As 'indices' has been shuffled, that is used
00085      * to determine which stat we're working on, instead of directly
00086      * using 'stat'. */
00087     for (stat_id = 0; stat_id < POTION_IMPROVE_STATS; stat_id++)
00088     {
00089         /* Determine level related to this stat, and the maximum possible
00090          * value it can be improved to. */
00091         if (indices[stat_id] == POTION_IMPROVE_HP)
00092         {
00093             level = op->level;
00094             max = op->arch->clone.stats.maxhp;
00095         }
00096         else if (indices[stat_id] == POTION_IMPROVE_SP)
00097         {
00098             level = CONTR(op)->exp_ptr[EXP_MAGICAL]->level;
00099             max = op->arch->clone.stats.maxsp;
00100         }
00101         else
00102         {
00103             level = CONTR(op)->exp_ptr[EXP_WISDOM]->level;
00104             max = op->arch->clone.stats.maxgrace;
00105         }
00106 
00107         /* Check to see if we can increase (or decrease, if the potion is
00108          * cursed) any stats. If the potion is cursed, we start at level
00109          * 2, because level 1 has the improvement fixed to the maximum
00110          * value (since it's the first level, obviously). */
00111         for (i = cursed ? 2 : 1; i <= level; i++)
00112         {
00113             /* Get pointer to the array that stores the level
00114              * improvements for this stat. */
00115             levarr = (void *) ((char *) CONTR(op) + potion_improve_stat_offsets[indices[stat_id]]);
00116 
00117             if (cursed)
00118             {
00119                 /* The potion is cursed and there is a stat improvement
00120                  * for this level, so go ahead and remove the
00121                  * improvement. */
00122                 if (levarr[i] != 1)
00123                 {
00124                     oldlev = levarr[i];
00125                     levarr[i] = 1;
00126                     fix_player(op);
00127                     insert_spell_effect("meffect_purple", op->map, op->x, op->y);
00128                     play_sound_map(op->map, CMD_SOUND_EFFECT, "poison.ogg", op->x, op->y, 0, 0);
00129                     new_draw_info_format(0, COLOR_WHITE, op, "The foul potion burns like fire inside you, and your %s decreases by %d!", potion_improve_stat_names[indices[stat_id]], oldlev - levarr[i]);
00130                     return 1;
00131                 }
00132             }
00133             else
00134             {
00135                 /* The stat improvement for this level has not been maxed
00136                  * yet, so go ahead and increase it. */
00137                 if (levarr[i] != max)
00138                 {
00139                     oldlev = levarr[i];
00140                     levarr[i] = max;
00141                     fix_player(op);
00142                     insert_spell_effect("meffect_yellow", op->map, op->x, op->y);
00143                     play_sound_map(op->map, CMD_SOUND_EFFECT, "magic_default.ogg", op->x, op->y, 0, 0);
00144                     new_draw_info_format(0, COLOR_WHITE, op, "You feel a little more perfect, and your %s increases by %d!", potion_improve_stat_names[indices[stat_id]], levarr[i] - oldlev);
00145                     return 1;
00146                 }
00147             }
00148         }
00149     }
00150 
00151     /* No effect (because there is nothing to increase [or decrease, in
00152      * case of cursed potions]); inform the player. */
00153     if (cursed)
00154     {
00155         play_sound_map(op->map, CMD_SOUND_EFFECT, "poison.ogg", op->x, op->y, 0, 0);
00156         new_draw_info(0, COLOR_WHITE, op, "The potion was foul but had no effect on your tortured body.");
00157     }
00158     else
00159     {
00160         play_sound_map(op->map, CMD_SOUND_EFFECT, "magic_default.ogg", op->x, op->y, 0, 0);
00161         new_draw_info(0, COLOR_WHITE, op, "The potion had no effect - you are already perfect.");
00162     }
00163 
00164     return 1;
00165 }
00166 
00173 static int potion_special_apply(object *op, object *tmp)
00174 {
00175     object *force;
00176     int i, val;
00177 
00178     /* Create a force and copy the effects in. */
00179     force = get_archetype("force");
00180 
00181     if (!force)
00182     {
00183         LOG(llevBug, "potion_special_apply(): Can't create force object.\n");
00184         return 0;
00185     }
00186 
00187     force->type = POTION_EFFECT;
00188     /* Copy the amount of time the effect should last. */
00189     force->stats.food = tmp->stats.food;
00190     SET_FLAG(force, FLAG_IS_USED_UP);
00191 
00192     /* Make sure the effect lasts for at least a little while. */
00193     if (force->stats.food <= 0)
00194     {
00195         force->stats.food = 1;
00196     }
00197 
00198     if (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED))
00199     {
00200         int protection;
00201 
00202         /* The potion is cursed/damned, so the negative effects stay
00203          * longer. */
00204         force->stats.food *= 3;
00205 
00206         /* Copy over protection values to the force from the potion, but
00207          * reverse them first. Attacks are ignored, as it's not actually
00208          * possible to store negative attack values in objects. */
00209         for (i = 0; i < NROFATTACKS; i++)
00210         {
00211             protection = tmp->protection[i] > 0 ? -tmp->protection[i] : tmp->protection[i];
00212 
00213             /* If the potion is damned, the effects worsen... */
00214             if (QUERY_FLAG(tmp, FLAG_DAMNED))
00215             {
00216                 protection *= 2;
00217             }
00218 
00219             /* Actually set the protection value, but make sure it's in a
00220              * valid range. */
00221             force->protection[i] = MIN(100, MAX(-100, protection));
00222         }
00223 
00224         insert_spell_effect("meffect_purple", op->map, op->x, op->y);
00225         play_sound_map(op->map, CMD_SOUND_EFFECT, "poison.ogg", op->x, op->y, 0, 0);
00226     }
00227     else
00228     {
00229         memcpy(force->protection, tmp->protection, sizeof(tmp->protection));
00230         memcpy(force->attack, tmp->attack, sizeof(tmp->attack));
00231 
00232         insert_spell_effect("meffect_green", op->map, op->x, op->y);
00233         play_sound_map(op->map, CMD_SOUND_EFFECT, "magic_default.ogg", op->x, op->y, 0, 0);
00234     }
00235 
00236     /* Copy over stat values. */
00237     for (i = 0; i < NUM_STATS; i++)
00238     {
00239         /* Get the stat value from the potion. */
00240         val = get_attr_value(&tmp->stats, i);
00241 
00242         /* No value, nothing to do. */
00243         if (!val)
00244         {
00245             continue;
00246         }
00247 
00248         /* If the potion is cursed/damned and the stat increase is
00249          * positive, reverse it. */
00250         if ((QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)) && val > 0)
00251         {
00252             val = -val;
00253         }
00254 
00255         /* The potion is damned, so the negative effect is worse. */
00256         if (QUERY_FLAG(tmp, FLAG_DAMNED))
00257         {
00258             val *= 2;
00259         }
00260 
00261         /* Now set the stat value of the force to the one calculcated
00262          * above, but make sure it doesn't overflow sint8. */
00263         change_attr_value(&force->stats, i, MIN(SINT8_MAX, MAX(SINT8_MIN, val)));
00264     }
00265 
00266     /* Insert the force into the player and apply it. */
00267     force->speed_left = -1;
00268     force = insert_ob_in_ob(force, op);
00269     SET_FLAG(force, FLAG_APPLIED);
00270 
00271     if (!change_abil(op, force))
00272     {
00273         new_draw_info(0, COLOR_WHITE, op, "Nothing happened.");
00274     }
00275 
00276     decrease_ob(tmp);
00277     return 1;
00278 }
00279 
00286 static int potion_restoration_apply(object *op, object *tmp)
00287 {
00288     object *depletion;
00289     archetype *at;
00290     int i;
00291 
00292     /* Cursed potion of minor restoration; reverse effects (stats are
00293      * depleted). */
00294     if (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED))
00295     {
00296         /* Drain 2 stats if the potion is cursed, 4 if it's damned. */
00297         for (i = QUERY_FLAG(tmp, FLAG_DAMNED) ? 0 : 2; i < 4; i++)
00298         {
00299             drain_stat(op);
00300         }
00301 
00302         insert_spell_effect("meffect_purple", op->map, op->x, op->y);
00303         play_sound_map(op->map, CMD_SOUND_EFFECT, "poison.ogg", op->x, op->y, 0, 0);
00304         decrease_ob(tmp);
00305         return 1;
00306     }
00307 
00308     at = find_archetype("depletion");
00309 
00310     if (!at)
00311     {
00312         LOG(llevBug, "potion_restoration_apply(): Could not find archetype 'depletion'.\n");
00313         return 0;
00314     }
00315 
00316     depletion = present_arch_in_ob(at, op);
00317 
00318     if (depletion)
00319     {
00320         for (i = 0; i < NUM_STATS; i++)
00321         {
00322             if (get_attr_value(&depletion->stats, i))
00323             {
00324                 new_draw_info(0, COLOR_WHITE, op, restore_msg[i]);
00325             }
00326         }
00327 
00328         remove_ob(depletion);
00329         fix_player(op);
00330     }
00331     else
00332     {
00333         new_draw_info(0, COLOR_WHITE, op, "You are not depleted.");
00334     }
00335 
00336     insert_spell_effect("meffect_green", op->map, op->x, op->y);
00337     play_sound_map(op->map, CMD_SOUND_EFFECT, "magic_default.ogg", op->x, op->y, 0, 0);
00338     decrease_ob(tmp);
00339     return 1;
00340 }
00341 
00347 int apply_potion(object *op, object *tmp)
00348 {
00349     if (op->type != PLAYER)
00350     {
00351         return 0;
00352     }
00353 
00354     if (!CONTR(op) || !CONTR(op)->exp_ptr[EXP_MAGICAL] || !CONTR(op)->exp_ptr[EXP_WISDOM])
00355     {
00356         LOG(llevBug, "apply_potion(): Called with invalid player object (no controller or no magic/wisdom exp object).\n");
00357         return 0;
00358     }
00359 
00360     /* Use magic devices skill for using potions. */
00361     if (!change_skill(op, SK_USE_MAGIC_ITEM))
00362     {
00363         return 0;
00364     }
00365 
00366     /* We are using it, so we now know what it does. */
00367     if (!QUERY_FLAG(tmp, FLAG_IDENTIFIED))
00368     {
00369         identify(tmp);
00370     }
00371 
00372     /* Potions with temporary effects. */
00373     if (tmp->last_eat == -1)
00374     {
00375         CONTR(op)->stat_potions_used++;
00376         return potion_special_apply(op, tmp);
00377     }
00378     /* Potion of minor restoration (removes depletion). */
00379     else if (tmp->last_eat == 1)
00380     {
00381         CONTR(op)->stat_potions_used++;
00382         return potion_restoration_apply(op, tmp);
00383     }
00384     /* Improvement potion. */
00385     else if (tmp->last_eat == 2)
00386     {
00387         CONTR(op)->stat_potions_used++;
00388         return potion_improve_apply(op, tmp);
00389     }
00390 
00391     if (tmp->stats.sp == SP_NO_SPELL)
00392     {
00393         new_draw_info(0, COLOR_WHITE, op, "Nothing happens as you apply it.");
00394         decrease_ob(tmp);
00395         return 0;
00396     }
00397 
00398     CONTR(op)->stat_potions_used++;
00399 
00400     /* Fire in the player's facing direction, unless the spell is
00401      * something like healing or cure disease. */
00402     cast_spell(op, tmp, spells[tmp->stats.sp].flags & SPELL_DESC_SELF ? 0 : op->facing, tmp->stats.sp, 1, CAST_POTION, NULL);
00403     decrease_ob(tmp);
00404 
00405     if (!QUERY_FLAG(op, FLAG_REMOVED))
00406     {
00407         fix_player(op);
00408     }
00409 
00410     return 1;
00411 }