Crossfire Server, Branch 1.12  R12190
rune.c
Go to the documentation of this file.
00001 /*
00002  * static char *rcsid_rune_c =
00003  *   "$Id: rune.c 11578 2009-02-23 22:02:27Z lalo $";
00004  */
00005 
00006 /*
00007     CrossFire, A Multiplayer game for X-windows
00008 
00009     Copyright (C) 2003,2006 Mark Wedel & Crossfire Development Team
00010     Copyright (C) 1992 Frank Tore Johansen
00011 
00012     This program is free software; you can redistribute it and/or modify
00013     it under the terms of the GNU General Public License as published by
00014     the Free Software Foundation; either version 2 of the License, or
00015     (at your option) any later version.
00016 
00017     This program is distributed in the hope that it will be useful,
00018     but WITHOUT ANY WARRANTY; without even the implied warranty of
00019     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020     GNU General Public License for more details.
00021 
00022     You should have received a copy of the GNU General Public License
00023     along with this program; if not, write to the Free Software
00024     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00025 
00026     The authors can be reached via e-mail at crossfire-devel@real-time.com
00027 */
00028 
00035 #include <global.h>
00036 #ifndef __CEXTRACT__
00037 #include <sproto.h>
00038 #endif
00039 #include <spells.h>
00040 
00041 
00042 #ifndef sqr
00043 #define sqr(x) ((x)*(x))
00044 #endif
00045 
00046 
00066 int write_rune(object *op, object *caster, object *spell, int dir, const char *runename) {
00067     object *tmp, *rune_spell, *rune;
00068     char buf[MAX_BUF];
00069     mapstruct *m;
00070     sint16 nx, ny;
00071 
00072     if (!dir) {
00073         dir = 1;
00074     }
00075 
00076     nx = op->x+freearr_x[dir];
00077     ny = op->y+freearr_y[dir];
00078     m = op->map;
00079 
00080     if (get_map_flags(m, &m, nx, ny, &nx, &ny)) {
00081         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00082                       "Can't make a rune there!", NULL);
00083         return 0;
00084     }
00085     for (tmp = GET_MAP_OB(m, nx, ny); tmp != NULL; tmp = tmp->above)
00086         if (tmp->type == RUNE)
00087             break;
00088 
00089     if (tmp) {
00090         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00091                       "You can't write a rune there.", NULL);
00092         return 0;
00093     }
00094 
00095     if (spell->other_arch) {
00096         rune_spell = arch_to_object(spell->other_arch);
00097     } else {
00098         /* Player specified spell.  The player has to know the spell, so
00099          * lets just look through the players inventory see if they know it
00100          * use the item_matched_string for our typical matching method.
00101          */
00102         int bestmatch = 0, ms;
00103 
00104         if (!runename || *runename == 0) {
00105             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00106                           "Write a rune of what?", NULL);
00107             return 0;
00108         }
00109 
00110         rune_spell = NULL;
00111         for (tmp = op->inv; tmp; tmp = tmp->below) {
00112             if (tmp->type == SPELL) {
00113                 ms = item_matched_string(op, tmp, runename);
00114                 if (ms > bestmatch) {
00115                     bestmatch = ms;
00116                     rune_spell = tmp;
00117                 }
00118             }
00119         }
00120         if (!rune_spell) {
00121             draw_ext_info_format(NDI_UNIQUE, 0, op,
00122                                  MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00123                                  "You don't know any spell named %s",
00124                                  "You don't know any spell named %s",
00125                                  runename);
00126             return 0;
00127         }
00128         if (rune_spell->skill != spell->skill) {
00129             draw_ext_info_format(NDI_UNIQUE, 0, op,
00130                                  MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00131                                  "You can't cast %s with %s",
00132                                  "You can't cast %s with %s",
00133                                  rune_spell->name, spell->name);
00134             return 0;
00135         }
00136         if (caster->path_denied&spell->path_attuned) {
00137             draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00138                                  "%s belongs to a spell path denied to you.",
00139                                  "%s belongs to a spell path denied to you.",
00140                                  rune_spell->name);
00141             return 0;
00142         }
00143         if (caster_level(caster, rune_spell) < rune_spell->level) {
00144             draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00145                                  "%s is beyond your ability to cast!",
00146                                  "%s is beyond your ability to cast!",
00147                                  rune_spell->name);
00148             return 0;
00149         }
00150         if (SP_level_spellpoint_cost(caster, rune_spell, SPELL_MANA) > op->stats.sp) {
00151             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00152                           "You don't have enough mana.", NULL);
00153             return 0;
00154         }
00155         if (SP_level_spellpoint_cost(caster, rune_spell, SPELL_GRACE) > op->stats.grace) {
00156             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR,
00157                           "You don't have enough grace.", NULL);
00158             return 0;
00159         }
00160         op->stats.grace -= SP_level_spellpoint_cost(caster, rune_spell, SPELL_GRACE);
00161         op->stats.sp -= SP_level_spellpoint_cost(caster, rune_spell, SPELL_MANA);
00162     }
00163     /* already proper rune.  Note this should only be the case if other_arch was set */
00164     if (rune_spell->type == RUNE) {
00165         rune = rune_spell;
00166     } else {
00167         rune = create_archetype(GENERIC_RUNE);
00168         snprintf(buf, sizeof(buf), "You set off a rune of %s\n", rune_spell->name);
00169         rune->msg = add_string(buf);
00170         tmp = get_object();
00171         copy_object(rune_spell, tmp);
00172         insert_ob_in_ob(tmp, rune);
00173         if (spell->face != blank_face)
00174             rune->face = spell->face;
00175     }
00176     rune->level = caster_level(caster, spell);
00177     rune->stats.Cha = rune->level/2;  /* the invisibility parameter */
00178     rune->x = nx;
00179     rune->y = ny;
00180     rune->map = m;
00181     rune->direction = dir;  /* where any spell will go upon detonation */
00182     set_owner(rune, op); /* runes without need no owner */
00183     set_spell_skill(op, caster, spell, rune);
00184     insert_ob_in_map(rune, m, op, 0);
00185     return 1;
00186 }
00187 
00197 static void rune_attack(object *op, object *victim) {
00198     if (victim) {
00199         tag_t tag = victim->count;
00200         hit_player(victim, op->stats.dam, op, op->attacktype, 1);
00201         if (was_destroyed(victim, tag))
00202             return;
00203         /*  if there's a disease in the needle, put it in the player */
00204         if (HAS_RANDOM_ITEMS(op))
00205             create_treasure(op->randomitems, op, 0, (victim->map ? victim->map->difficulty : 1), 0);
00206         if (op->inv && op->inv->type == DISEASE) {
00207             object *disease = op->inv;
00208 
00209             infect_object(victim, disease, 1);
00210             remove_ob(disease);
00211             free_object(disease);
00212         }
00213     } else
00214         hit_map(op, 0, op->attacktype, 1);
00215 }
00216 
00227 void spring_trap(object *trap, object *victim) {
00228     object *env;
00229     tag_t trap_tag = trap->count;
00230     rv_vector rv;
00231     int i, has_spell;
00232 
00233     /* Prevent recursion */
00234     if (trap->stats.hp <= 0)
00235         return;
00236 
00237     if (QUERY_FLAG(trap, FLAG_IS_LINKED))
00238         use_trigger(trap);
00239 
00240     /* Check if this trap casts a spell */
00241     has_spell = ((trap->inv && trap->inv->type == SPELL) || (trap->other_arch && trap->other_arch->clone.type == SPELL));
00242 
00243     env = object_get_env_recursive(trap);
00244 
00245     /* If the victim is not next to this trap, and the trap doesn't cast
00246      * a spell, don't set it off.
00247      */
00248     get_rangevector(env, victim, &rv, 0);
00249     if (rv.distance > 1 && !has_spell)
00250         return;
00251 
00252     /* Only living objects can trigger runes that don't cast spells, as
00253      * doing direct damage to a non-living object doesn't work anyway.
00254      * Typical example is an arrow attacking a door.
00255      */
00256     if (!QUERY_FLAG(victim, FLAG_ALIVE) && !has_spell)
00257         return;
00258 
00259     trap->stats.hp--;  /*decrement detcount */
00260 
00261     if (victim && victim->type == PLAYER && trap->msg != NULL)
00262         draw_ext_info(NDI_UNIQUE, 0, victim, MSG_TYPE_APPLY, MSG_TYPE_APPLY_TRAP,
00263                       trap->msg, trap->msg);
00264 
00265     /*  Flash an image of the trap on the map so the poor sod
00266      *   knows what hit him.
00267      */
00268     trap_show(trap, env);
00269 
00270     /* Only if it is a spell do we proceed here */
00271     if (has_spell) {
00272         object *spell;
00273 
00274         /* This is necessary if the trap is inside something else */
00275         remove_ob(trap);
00276         trap->x = victim->x;
00277         trap->y = victim->y;
00278         insert_ob_in_map(trap, victim->map, trap, 0);
00279 
00280         if (was_destroyed(trap, trap_tag))
00281             return;
00282 
00283         for (i = 0; i < MAX(1, trap->stats.maxhp); i++) {
00284             if (trap->inv)
00285                 cast_spell(trap, trap, trap->direction, trap->inv, NULL);
00286             else {
00287                 spell = arch_to_object(trap->other_arch);
00288                 cast_spell(trap, trap, trap->direction, spell, NULL);
00289                 free_object(spell);
00290             }
00291         }
00292     } else {
00293         rune_attack(trap, victim);
00294         if (was_destroyed(trap, trap_tag))
00295             return;
00296     }
00297 
00298     if (trap->stats.hp <= 0) {
00299         trap->type = SIGN;  /* make the trap impotent */
00300         trap->stats.food = 20;  /* make it stick around until its spells are gone */
00301         SET_FLAG(trap, FLAG_IS_USED_UP);
00302     }
00303 }
00304 
00323 int dispel_rune(object *op, object *caster, object *spell, object *skill, int dir) {
00324     object *tmp, *tmp2;
00325     int searchflag = 1, mflags;
00326     sint16 x, y;
00327     mapstruct *m;
00328 
00329     x = op->x+freearr_x[dir];
00330     y = op->y+freearr_y[dir];
00331     m = op->map;
00332 
00333     mflags = get_map_flags(m, &m, x, y, &x, &y);
00334 
00335     /* Should we perhaps not allow player to disable traps if a monster/
00336      * player is standing on top?
00337      */
00338     if (mflags&P_OUT_OF_MAP) {
00339         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE,
00340                       "There's nothing there!", NULL);
00341         return 0;
00342     }
00343 
00344     /* This can happen if a player does a 'magic rune of dispel'.  Without
00345      * any skill, chance of success is zero, and we don't know who to tell
00346      * (as otherwise we would have a skill pointer).  Plus, trap_disarm()
00347      * presumes skill is not null and will crash if it is.
00348      */
00349     if (!skill)
00350         return 0;
00351 
00352     for (tmp = GET_MAP_OB(m, x, y); tmp != NULL;  tmp = tmp->above)  {
00353         if (tmp->type == RUNE || tmp->type == TRAP)
00354             break;
00355 
00356         /* we could put a probability chance here, but since nothing happens
00357          * if you fail, no point on that.  I suppose we could do a level
00358          * comparison so low level players can't erase high level players runes.
00359          */
00360         if (tmp->type == SIGN && !strcmp(tmp->arch->name, "rune_mark")) {
00361             remove_ob(tmp);
00362             free_object(tmp);
00363             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_SUCCESS,
00364                           "You wipe out the rune of marking!", NULL);
00365             return 1;
00366         }
00367 
00368         /* now search tmp's inventory for traps
00369          * This is for chests, where the rune is in the chests inventory.
00370          */
00371         for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below) {
00372             if (tmp2->type == RUNE || tmp2->type == TRAP) {
00373                 tmp = tmp2;
00374                 searchflag = 0;
00375                 break;
00376             }
00377         }
00378         if (!searchflag)
00379             break;
00380     }
00381 
00382     /* no rune there. */
00383     if (tmp == NULL) {
00384         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE,
00385                       "There's nothing there!", NULL);
00386         return 0;
00387     }
00388     trap_disarm(op, tmp, 0, skill);
00389     return 1;
00390 }
00391 
00403 int trap_see(object *op, object *trap) {
00404     int chance;
00405 
00406     chance = random_roll(0, 99, op, PREFER_HIGH);
00407 
00408     /* decide if we see the rune or not */
00409     if ((trap->stats.Cha == 1)
00410     || (chance > MIN(95, MAX(5, ((int)((float)(op->map->difficulty+trap->level+trap->stats.Cha-op->level)/10.0*50.0)))))) {
00411         draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
00412                              "You spot a %s!",
00413                              "You spot a %s!",
00414                              trap->name);
00415         return 1;
00416     }
00417     return 0;
00418 }
00419 
00431 int trap_show(object *trap, object *where) {
00432     object *tmp2;
00433 
00434     if (where == NULL)
00435         return 0;
00436     tmp2 = create_archetype("runedet");
00437     tmp2->face = &new_faces[GET_ANIMATION(trap, 0)];
00438     tmp2->x = where->x;
00439     tmp2->y = where->y;
00440     tmp2->map = where->map;
00441     insert_ob_in_map(tmp2, where->map, NULL, 0);
00442     return 1;
00443 }
00444 
00459 int trap_disarm(object *disarmer, object *trap, int risk, object *skill) {
00460     int trapworth;  /* need to compute the experience worth of the trap
00461                      before we kill it */
00462 
00463     /* this formula awards a more reasonable amount of exp */
00464     trapworth = MAX(1, trap->level)*disarmer->map->difficulty*
00465                 sqr(MAX(trap->stats.dam, trap->inv ? trap->inv->level : 1))/skill->level;
00466 
00467     if (!(random_roll(0, (MAX(2, MIN(20, trap->level-skill->level+5-disarmer->stats.Dex/2))-1), disarmer, PREFER_LOW))) {
00468         draw_ext_info_format(NDI_UNIQUE, 0, disarmer,
00469                              MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
00470                              "You successfully disarm the %s!",
00471                              "You successfully disarm the %s!",
00472                              trap->name);
00473         destroy_object(trap);
00474         /* If it is your own trap, (or any players trap), don't you don't
00475          * get exp for it.
00476          */
00477         if (trap->owner && trap->owner->type != PLAYER && risk)
00478             return trapworth;
00479         else
00480             return 1; /* give minimal exp and say success */
00481     } else {
00482         draw_ext_info_format(NDI_UNIQUE, 0, disarmer,
00483                              MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
00484                              "You fail to disarm the %s.",
00485                              "You fail to disarm the %s.",
00486                              trap->name);
00487         if (!(random_roll(0, (MAX(2, skill->level-trap->level+disarmer->stats.Dex/2-6))-1, disarmer, PREFER_LOW))
00488         && risk) {
00489             draw_ext_info(NDI_UNIQUE, 0, disarmer,
00490                           MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
00491                           "In fact, you set it off!", NULL);
00492             spring_trap(trap, disarmer);
00493         }
00494         return 0;
00495     }
00496 }
00497 
00508 void trap_adjust(object *trap, int difficulty) {
00509     int i;
00510 
00511     /* now we set the trap level to match the difficulty of the level
00512      * the formula below will give a level from 1 to (2*difficulty) with
00513      * a peak probability at difficulty
00514      */
00515 
00516     trap->level = rndm(0, difficulty-1)+rndm(0, difficulty-1);
00517     if (trap->level < 1)
00518         trap->level = 1;
00519 
00520     /* set the hiddenness of the trap, similar formula to above */
00521     trap->stats.Cha = rndm(0, 19)+rndm(0, difficulty-1)+rndm(0, difficulty-1);
00522 
00523     if (!trap->other_arch && !trap->inv) {
00524         /* set the damage of the trap.
00525          * we get 0-4 pts of damage per level of difficulty of the map in
00526          * the trap
00527          */
00528 
00529         trap->stats.dam = 0;
00530         for (i = 0; i < difficulty; i++)
00531             trap->stats.dam += rndm(0, 4);
00532 
00533         /*  the poison trap special case */
00534         if (trap->attacktype&AT_POISON) {
00535             trap->stats.dam = rndm(0, difficulty-1);
00536             if (trap->stats.dam < 1)
00537                 trap->stats.dam = 1;
00538         }
00539 
00540         /*  so we get an appropriate amnt of exp for AT_DEATH traps */
00541         if (trap->attacktype&AT_DEATH)
00542             trap->stats.dam = 127;
00543     }
00544 
00545 }