/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team * Copyright (©) 1992,2007 Frank Tore Johansen * * Deliantra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * The authors can be reached via e-mail to */ #include #include #include #include #include #include /* cast_magic_storm: This is really used mostly for spell * fumbles at the like. tmp is the object to propogate. * op is what is casting this. */ void cast_magic_storm (object *op, object *tmp, int lvl) { if (!tmp) return; /* error */ tmp->level = op->level; tmp->range += lvl / 5; /* increase the area of destruction */ tmp->duration += lvl / 5; /* Put a cap on duration for this - if the player fails in their * apartment, don't want it to go on so long that it kills them * multiple times. Also, damge already increases with level, * so don't really need to increase the duration as much either. */ if (tmp->duration >= 40) tmp->duration = 40; tmp->stats.dam = lvl; /* nasty recoils! */ tmp->stats.maxhp = tmp->count; /* tract single parent */ tmp->insert_at (op, op); } int recharge (object *op, object *caster, object *spell_ob) { object *wand, *tmp; int ncharges; wand = find_marked_object (op); if (!wand || wand->type != WAND) { new_draw_info (NDI_UNIQUE, 0, op, "You need to mark the wand you want to recharge."); return 0; } if (!(random_roll (0, 3, op, PREFER_HIGH))) { new_draw_info_format (NDI_UNIQUE, 0, op, "The %s vibrates violently, then explodes!", query_name (wand)); op->play_sound (sound_find ("ob_explode")); wand->destroy (); tmp = get_archetype ("fireball"); tmp->stats.dam = (spell_ob->stats.dam + SP_level_dam_adjust (caster, spell_ob)) / 10; if (!tmp->stats.dam) tmp->stats.dam = 1; tmp->stats.hp = tmp->stats.dam / 2; if (tmp->stats.hp < 2) tmp->stats.hp = 2; tmp->insert_at (op); return 1; } ncharges = (spell_ob->stats.dam + SP_level_dam_adjust (caster, spell_ob)); if (wand->inv && wand->inv->level) ncharges /= wand->inv->level; else { new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s is broken.", query_name (wand)); return 0; } if (!ncharges) ncharges = 1; wand->stats.food += ncharges; new_draw_info_format (NDI_UNIQUE, 0, op, "The %s glows with power.", query_name (wand)); if (wand->arch && QUERY_FLAG (wand->arch, FLAG_ANIMATE)) { SET_FLAG (wand, FLAG_ANIMATE); wand->set_speed (wand->arch->speed); } return 1; } /* Create a missile (nonmagic - magic +4). Will either create bolts or arrows * based on whether a crossbow or bow is equiped. If neither, it defaults to * arrows. * Sets the plus based on the casters level. It is also settable with the * invoke command. If the caster attempts to create missiles with too * great a plus, the default is used. * The # of arrows created also goes up with level, so if a 30th level mage * wants LOTS of arrows, and doesn't care what the plus is he could * create nonnmagic arrows, or even -1, etc... */ int cast_create_missile (object *op, object *caster, object *spell, int dir, const char *stringarg) { int bonus_plus = 0; const char *missile_name = "arrow"; for (object *tmp = op->inv; tmp; tmp = tmp->below) if (tmp->type == BOW && QUERY_FLAG (tmp, FLAG_APPLIED)) missile_name = tmp->race; int missile_plus = spell->stats.dam + SP_level_dam_adjust (caster, spell); archetype *missile_arch = archetype::find (missile_name); if (!missile_arch) { LOG (llevDebug, "Cast create_missile: could not find archetype %s\n", missile_name); return 0; } object *missile = missile_arch->instance (); if (stringarg) { /* If it starts with a letter, presume it is a description */ if (isalpha (*stringarg)) { artifact *al = find_artifactlist (missile->type)->items; for (; al; al = al->next) if (!strcasecmp (al->item->name, stringarg)) break; if (!al) { missile->destroy (); new_draw_info_format (NDI_UNIQUE, 0, op, "No such object %ss of %s", missile_name, stringarg); return 0; } if (al->item->slaying) { missile->destroy (); new_draw_info_format (NDI_UNIQUE, 0, op, "You are not allowed to create %ss of %s", missile_name, stringarg); return 0; } give_artifact_abilities (missile, al->item); /* These special arrows cost something extra. Don't have them also be magical - * otherwise, in most cases, not enough will be created. I don't want to get into * the parsing of having to do both plus and type. */ bonus_plus = 1 + (al->item->value / 5); missile_plus = 0; } else if (atoi (stringarg) < missile_plus) missile_plus = atoi (stringarg); } missile_plus = clamp (missile_plus, -4, 4); missile->nrof = spell->duration + SP_level_duration_adjust (caster, spell); missile->nrof -= 3 * (missile_plus + bonus_plus); if (missile->nrof < 1) missile->nrof = 1; missile->magic = missile_plus; /* Can't get any money for these objects */ missile->value = 0; SET_FLAG (missile, FLAG_IDENTIFIED); if (!cast_create_obj (op, caster, missile, dir) && op->type == PLAYER && !missile->destroyed ()) pick_up (op, missile); return 1; } /* allows the choice of what sort of food object to make. * If stringarg is NULL, it will create food dependent on level --PeterM*/ int cast_create_food (object *op, object *caster, object *spell_ob, int dir, const char *stringarg) { int food_value; archetype *at = NULL; object *new_op; food_value = spell_ob->stats.food + 50 * SP_level_duration_adjust (caster, spell_ob); if (stringarg) { at = find_archetype_by_object_type_name (FOOD, stringarg); if (at == NULL) at = find_archetype_by_object_type_name (DRINK, stringarg); if (at == NULL || at->stats.food > food_value) stringarg = NULL; } if (!stringarg) { archetype *at_tmp; /* We try to find the archetype with the maximum food value. * This removes the dependancy of hard coded food values in this * function, and addition of new food types is automatically added. * We don't use flesh types because the weight values of those need * to be altered from the donor. */ /* We assume the food items don't have multiple parts */ for_all_archetypes (at_tmp) { if (at_tmp->type == FOOD || at_tmp->type == DRINK) { /* Basically, if the food value is something that is creatable * under the limits of the spell and it is higher than * the item we have now, take it instead. */ if (at_tmp->stats.food <= food_value && (!at || at_tmp->stats.food > at->stats.food || (at_tmp->stats.food == at->stats.food && at_tmp->weight < at->weight))) at = at_tmp; } } } /* Pretty unlikely (there are some very low food items), but you never * know */ if (!at) { new_draw_info (NDI_UNIQUE, 0, op, "You don't have enough experience to create any food."); return 0; } food_value /= at->stats.food; new_op = arch_to_object (at); new_op->nrof = food_value; new_op->value = 0; if (new_op->nrof < 1) new_op->nrof = 1; cast_create_obj (op, caster, new_op, dir); return 1; } int probe (object *op, object *caster, object *spell_ob, int dir) { int r, mflags, maxrange; object *tmp; maptile *m; if (!dir) { examine_monster (op, op); return 1; } maxrange = spell_ob->range + SP_level_range_adjust (caster, spell_ob); for (r = 1; r < maxrange; r++) { sint16 x = op->x + r * freearr_x[dir], y = op->y + r * freearr_y[dir]; m = op->map; mflags = get_map_flags (m, &m, x, y, &x, &y); if (mflags & P_OUT_OF_MAP) break; if (!QUERY_FLAG (op, FLAG_WIZCAST) && (mflags & P_NO_MAGIC)) { new_draw_info (NDI_UNIQUE, 0, op, "Something blocks your magic."); return 0; } if (mflags & P_IS_ALIVE) { for (tmp = GET_MAP_OB (m, x, y); tmp; tmp = tmp->above) if (QUERY_FLAG (tmp, FLAG_ALIVE) && (tmp->type == PLAYER || QUERY_FLAG (tmp, FLAG_MONSTER))) { new_draw_info (NDI_UNIQUE, 0, op, "You detect something."); if (tmp->head != NULL) tmp = tmp->head; examine_monster (op, tmp); return 1; } } } new_draw_info (NDI_UNIQUE, 0, op, "You detect nothing."); return 1; } /* This checks to see if 'pl' is invisible to 'mon'. * does race check, undead check, etc * Returns TRUE if mon can't see pl, false * otherwise. This doesn't check range, walls, etc. It * only checks the racial adjustments, and in fact that * pl is invisible. */ int makes_invisible_to (object *pl, object *mon) { if (!pl->invisible) return 0; if (pl->type == PLAYER) { /* If race isn't set, then invisible unless it is undead */ if (!pl->contr->invis_race) { if (QUERY_FLAG (mon, FLAG_UNDEAD)) return 0; return 1; } /* invis_race is set if we get here */ if (!strcmp (pl->contr->invis_race, "undead") && is_true_undead (mon)) return 1; /* No race, can't be invisible to it */ if (!mon->race) return 0; if (strstr (mon->race, pl->contr->invis_race)) return 1; /* Nothing matched above, return 0 */ return 0; } else { /* monsters are invisible to everything */ return 1; } } /* Makes the player or character invisible. * Note the spells to 'stack', but perhaps in odd ways. * the duration for all is cumulative. * In terms of invis undead/normal invis, it is the last one cast that * will determine if you are invisible to undead or normal monsters. * For improved invis, if you cast it with a one of the others, you * lose the improved part of it, and the above statement about undead/ * normal applies. */ int cast_invisible (object *op, object *caster, object *spell_ob) { if (op->invisible > 1000) { new_draw_info (NDI_UNIQUE, 0, op, "You can not extend the duration of your invisibility any further"); return 0; } /* Remove the switch with 90% duplicate code - just handle the differences with * and if statement or two. */ op->invisible += spell_ob->duration + SP_level_duration_adjust (caster, spell_ob); /* max duration */ if (op->invisible > 1000) op->invisible = 1000; if (op->type == PLAYER) { op->contr->invis_race = spell_ob->race; if (QUERY_FLAG (spell_ob, FLAG_MAKE_INVIS)) op->contr->tmp_invis = 0; else op->contr->tmp_invis = 1; op->contr->hidden = 0; } if (makes_invisible_to (op, op)) new_draw_info (NDI_UNIQUE, 0, op, "You can't see your hands!"); else new_draw_info (NDI_UNIQUE, 0, op, "You feel more transparent!"); update_object (op, UP_OBJ_CHANGE); /* Only search the active objects - only these should actually do * harm to the player. */ for_all_actives (tmp) if (tmp->enemy == op) tmp->enemy = 0; return 1; } /* earth to dust spell. Basically destroys earthwalls in the area. */ int cast_earth_to_dust (object *op, object *caster, object *spell_ob) { object *tmp, *next; int range, i, j, mflags; sint16 sx, sy; maptile *m; if (op->type != PLAYER) return 0; range = spell_ob->range + SP_level_range_adjust (caster, spell_ob); for (i = -range; i <= range; i++) for (j = -range; j <= range; j++) { sx = op->x + i; sy = op->y + j; m = op->map; mflags = get_map_flags (m, &m, sx, sy, &sx, &sy); if (mflags & P_OUT_OF_MAP) continue; // earth to dust tears down everything that can be teared down for (tmp = GET_MAP_OB (m, sx, sy); tmp != NULL; tmp = next) { next = tmp->above; if (QUERY_FLAG (tmp, FLAG_TEAR_DOWN)) hit_player (tmp, 9998, op, AT_PHYSICAL, 0); } } return 1; } void execute_word_of_recall (object *op) { if (object *pl = op->in_player ()) { if (pl->ms ().flags () & P_NO_CLERIC && !QUERY_FLAG (pl, FLAG_WIZCAST)) new_draw_info (NDI_UNIQUE, 0, pl, "You feel something fizzle inside you."); else pl->player_goto (op->slaying, op->stats.hp, op->stats.sp); } op->destroy (); } /* Word of recall causes the player to return 'home'. * we put a force into the player object, so that there is a * time delay effect. */ int cast_word_of_recall (object *op, object *caster, object *spell_ob) { object *dummy; int time; if (op->type != PLAYER) return 0; if (find_obj_by_type_subtype (op, SPELL_EFFECT, SP_WORD_OF_RECALL)) { new_draw_info (NDI_UNIQUE, 0, op, "You feel a force starting to build up inside you."); return 1; } dummy = get_archetype (FORCE_NAME); if (!dummy) { new_draw_info (NDI_UNIQUE, 0, op, "Oops, program error!"); LOG (llevError, "cast_word_of_recall: get_archetype(force) failed!\n"); return 0; } time = spell_ob->duration - SP_level_duration_adjust (caster, spell_ob); if (time < 1) time = 1; /* value of speed really doesn't make much difference, as long as it is * positive. Lower value may be useful so that the problem doesn't * do anything really odd if it say a -1000 or something. */ dummy->set_speed (0.002); dummy->speed_left = -dummy->speed * time; dummy->type = SPELL_EFFECT; dummy->subtype = SP_WORD_OF_RECALL; /* If we could take advantage of enter_player_savebed() here, it would be * nice, but until the map load fails, we can't. */ EXIT_PATH (dummy) = op->contr->savebed_map; EXIT_X (dummy) = op->contr->bed_x; EXIT_Y (dummy) = op->contr->bed_y; op->insert (dummy); new_draw_info (NDI_UNIQUE, 0, op, "You feel a force starting to build up inside you."); return 1; } /* cast_wonder * wonder is really just a spell that will likely cast another * spell. */ int cast_wonder (object *op, object *caster, int dir, object *spell_ob) { object *newspell; if (!rndm (0, 3)) return cast_cone (op, caster, dir, spell_ob); if (spell_ob->randomitems) { newspell = generate_treasure (spell_ob->randomitems, caster->level); if (!newspell) { LOG (llevError, "cast_wonder: Unable to get a spell!\n"); return 0; } if (newspell->type != SPELL) { LOG (llevError, "cast_wonder: spell returned is not a spell (%d, %s)!\n", &newspell->type, &newspell->name); return 0; } /* Prevent inifinit recursion */ if (newspell->subtype == SP_WONDER) { LOG (llevError, "cast_wonder: spell returned is another wonder spell!\n"); return 0; } return cast_spell (op, caster, dir, newspell, NULL); } return 1; } int perceive_self (object *op) { const char *cp = describe_item (op, op); archetype *at = archetype::find (ARCH_DEPLETION); dynbuf_text buf; if (player *pl = op->contr) if (object *race = archetype::find (op->race)) buf << "You are a " << (pl->gender ? "female" : "male") << " " << &race->name << ".\n"; if (object *god = find_god (determine_god (op))) buf << "You worship " << &god->name << ".\n"; else buf << "You worship no god.\n"; object *tmp = present_arch_in_ob (at, op); if (*cp == '\0' && tmp == NULL) buf << "You feel very mundane. "; else { buf << "You have: " << cp << ".\n"; if (tmp) for (int i = 0; i < NUM_STATS; i++) if (tmp->stats.stat (i) < 0) buf.printf ("Your %s is depleted by %d.\n", statname[i], -tmp->stats.stat (i)); } if (is_dragon_pl (op)) /* now grab the 'dragon_ability'-force from the player's inventory */ for (tmp = op->inv; tmp; tmp = tmp->below) { if (tmp->type == FORCE && tmp->arch->archname == shstr_dragon_ability_force) { if (tmp->stats.exp == 0) buf << "Your metabolism isn't focused on anything.\n"; else buf << "Your metabolism is focused on " << change_resist_msg[tmp->stats.exp] << ".\n"; break; } } buf << '\0'; // zero-terminate new_draw_info (NDI_UNIQUE, 0, op, buf.linearise ()); return 1; } /* This creates magic walls. Really, it can create most any object, * within some reason. */ int magic_wall (object *op, object *caster, int dir, object *spell_ob) { object *tmp; int i, posblocked, negblocked, maxrange; sint16 x, y; maptile *m; const char *name; archetype *at; if (!dir) { dir = op->facing; x = op->x; y = op->y; } else { x = op->x + freearr_x[dir]; y = op->y + freearr_y[dir]; } m = op->map; if ((spell_ob->move_block || x != op->x || y != op->y) && (get_map_flags (m, &m, x, y, &x, &y) & (P_OUT_OF_MAP | P_IS_ALIVE) || ((spell_ob->move_block & GET_MAP_MOVE_BLOCK (m, x, y)) == spell_ob->move_block))) { new_draw_info (NDI_UNIQUE, 0, op, "Something is in the way."); return 0; } if (spell_ob->other_arch) tmp = arch_to_object (spell_ob->other_arch); else if (spell_ob->race) { char buf1[MAX_BUF]; sprintf (buf1, spell_ob->race, dir); at = archetype::find (buf1); if (!at) { LOG (llevError, "summon_wall: Unable to find archetype %s\n", buf1); new_draw_info (NDI_UNIQUE, 0, op, "This spell is broken."); return 0; } tmp = arch_to_object (at); } else { LOG (llevError, "magic_wall: spell %s lacks other_arch\n", &spell_ob->name); return 0; } if (tmp->type == SPELL_EFFECT) { tmp->attacktype = spell_ob->attacktype; tmp->duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob); tmp->stats.dam = spell_ob->stats.dam + SP_level_dam_adjust (caster, spell_ob); tmp->range = 0; } else if (QUERY_FLAG (tmp, FLAG_ALIVE)) { tmp->stats.hp = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob); tmp->stats.maxhp = tmp->stats.hp; } if (QUERY_FLAG (spell_ob, FLAG_IS_USED_UP) || QUERY_FLAG (tmp, FLAG_IS_USED_UP)) { tmp->stats.food = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob); SET_FLAG (tmp, FLAG_IS_USED_UP); } if (QUERY_FLAG (spell_ob, FLAG_TEAR_DOWN)) { tmp->stats.hp = spell_ob->stats.dam + SP_level_dam_adjust (caster, spell_ob); tmp->stats.maxhp = tmp->stats.hp; SET_FLAG (tmp, FLAG_TEAR_DOWN); SET_FLAG (tmp, FLAG_ALIVE); } /* This can't really hurt - if the object doesn't kill anything, * these fields just won't be used. Do not set the owner for * earthwalls, though, so they survive restarts. */ if (tmp->type != EARTHWALL) //TODO tmp->set_owner (op); set_spell_skill (op, caster, spell_ob, tmp); tmp->level = caster_level (caster, spell_ob) / 2; name = tmp->name; if (!(tmp = m->insert (tmp, x, y, op))) { new_draw_info_format (NDI_UNIQUE, 0, op, "Something destroys your %s", name); return 0; } /* If this is a spellcasting wall, need to insert the spell object */ if (tmp->other_arch && tmp->other_arch->type == SPELL) insert_ob_in_ob (arch_to_object (tmp->other_arch), tmp); /* This code causes the wall to extend some distance in * each direction, or until an obstruction is encountered. * posblocked and negblocked help determine how far the * created wall can extend, it won't go extend through * blocked spaces. */ maxrange = spell_ob->range + SP_level_range_adjust (caster, spell_ob); posblocked = 0; negblocked = 0; for (i = 1; i <= maxrange; i++) { int dir2; dir2 = (dir < 4) ? (dir + 2) : dir - 2; x = tmp->x + i * freearr_x[dir2]; y = tmp->y + i * freearr_y[dir2]; m = tmp->map; if (!(get_map_flags (m, &m, x, y, &x, &y) & (P_OUT_OF_MAP | P_IS_ALIVE)) && ((spell_ob->move_block & GET_MAP_MOVE_BLOCK (m, x, y)) != spell_ob->move_block) && !posblocked) { object *tmp2 = tmp->clone (); m->insert (tmp2, x, y, op); /* If this is a spellcasting wall, need to insert the spell object */ if (tmp2->other_arch && tmp2->other_arch->type == SPELL) tmp2->insert (arch_to_object (tmp2->other_arch)); } else posblocked = 1; x = tmp->x - i * freearr_x[dir2]; y = tmp->y - i * freearr_y[dir2]; m = tmp->map; if (!(get_map_flags (m, &m, x, y, &x, &y) & (P_OUT_OF_MAP | P_IS_ALIVE)) && ((spell_ob->move_block & GET_MAP_MOVE_BLOCK (m, x, y)) != spell_ob->move_block) && !negblocked) { object *tmp2 = tmp->clone (); m->insert (tmp2, x, y, op); if (tmp2->other_arch && tmp2->other_arch->type == SPELL) tmp2->insert (arch_to_object (tmp2->other_arch)); } else negblocked = 1; } if (QUERY_FLAG (tmp, FLAG_BLOCKSVIEW)) update_all_los (op->map, op->x, op->y); return 1; } int dimension_door (object *op, object *caster, object *spob, int dir) { uint32 dist, maxdist; int mflags; maptile *m; sint16 sx, sy; if (op->type != PLAYER) return 0; if (!dir) { new_draw_info (NDI_UNIQUE, 0, op, "In what direction?"); return 0; } /* Given the new outdoor maps, can't let players dimension door for * ever, so put limits in. */ maxdist = spob->range + SP_level_range_adjust (caster, spob); if (op->contr->count) { if (op->contr->count > maxdist) { new_draw_info (NDI_UNIQUE, 0, op, "You can't dimension door that far!"); return 0; } for (dist = 0; dist < op->contr->count; dist++) { mflags = get_map_flags (op->map, &m, op->x + freearr_x[dir] * (dist + 1), op->y + freearr_y[dir] * (dist + 1), &sx, &sy); if (mflags & (P_NO_MAGIC | P_OUT_OF_MAP)) break; if ((mflags & P_BLOCKSVIEW) && OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, sx, sy))) break; } if (dist < op->contr->count) { new_draw_info (NDI_UNIQUE, 0, op, "Something blocks the magic of the spell.\n"); op->contr->count = 0; return 0; } op->contr->count = 0; /* Remove code that puts player on random space on maps. IMO, * a lot of maps probably have areas the player should not get to, * but may not be marked as NO_MAGIC (as they may be bounded * by such squares). Also, there are probably treasure rooms and * lots of other maps that protect areas with no magic, but the * areas themselves don't contain no magic spaces. */ /* This call here is really just to normalize the coordinates */ mflags = get_map_flags (op->map, &m, op->x + freearr_x[dir] * dist, op->y + freearr_y[dir] * dist, &sx, &sy); if (mflags & P_IS_ALIVE || OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, sx, sy))) { new_draw_info (NDI_UNIQUE, 0, op, "You cast your spell, but nothing happens.\n"); return 1; /* Maybe the penalty should be more severe... */ } } else { /* Player didn't specify a distance, so lets see how far * we can move the player. Don't know why this stopped on * spaces that blocked the players view. */ for (dist = 0; dist < maxdist; dist++) { mflags = get_map_flags (op->map, &m, op->x + freearr_x[dir] * (dist + 1), op->y + freearr_y[dir] * (dist + 1), &sx, &sy); if (mflags & (P_NO_MAGIC | P_OUT_OF_MAP)) break; if ((mflags & P_BLOCKSVIEW) && OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, sx, sy))) break; } /* If the destination is blocked, keep backing up until we * find a place for the player. */ for (; dist > 0; dist--) { if (get_map_flags (op->map, &m, op->x + freearr_x[dir] * dist, op->y + freearr_y[dir] * dist, &sx, &sy) & (P_OUT_OF_MAP | P_IS_ALIVE)) continue; if (!OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, sx, sy))) break; } if (!dist) { new_draw_info (NDI_UNIQUE, 0, op, "Your spell failed!\n"); return 0; } } /* Actually move the player now */ if (!(op = op->map->insert (op, op->x + freearr_x[dir] * dist, op->y + freearr_y[dir] * dist, op))) return 1; op->speed_left = -FABS (op->speed) * 5; /* Freeze them for a short while */ return 1; } /* cast_heal: Heals something. * op is the caster. * dir is the direction he is casting it in. * spell is the spell object. */ int cast_heal (object *op, object *caster, object *spell, int dir) { object *tmp; archetype *at; object *poison; int heal = 0, success = 0; tmp = find_target_for_friendly_spell (op, dir); if (!tmp) return 0; /* Figure out how many hp this spell might cure. * could be zero if this spell heals effects, not damage. */ heal = spell->stats.dam; if (spell->stats.hp) heal += random_roll (spell->stats.hp, 6, op, PREFER_HIGH) + spell->stats.hp; if (heal) { if (tmp->stats.hp >= tmp->stats.maxhp) new_draw_info (NDI_UNIQUE, 0, tmp, "You are already fully healed."); else { /* See how many points we actually heal. Instead of messages * based on type of spell, we instead do messages based * on amount of damage healed. */ if (heal > tmp->stats.maxhp - tmp->stats.hp) heal = tmp->stats.maxhp - tmp->stats.hp; tmp->stats.hp += heal; if (tmp->stats.hp >= tmp->stats.maxhp) new_draw_info (NDI_UNIQUE, 0, tmp, "You feel just fine!"); else if (heal > 50) new_draw_info (NDI_UNIQUE, 0, tmp, "Your wounds close!"); else if (heal > 25) new_draw_info (NDI_UNIQUE, 0, tmp, "Your wounds mostly close."); else if (heal > 10) new_draw_info (NDI_UNIQUE, 0, tmp, "Your wounds start to fade."); else new_draw_info (NDI_UNIQUE, 0, tmp, "Your wounds start to close."); success = 1; } } if (spell->attacktype & AT_DISEASE) if (cure_disease (tmp, op, spell)) success = 1; if (spell->attacktype & AT_POISON) { at = archetype::find ("poisoning"); poison = present_arch_in_ob (at, tmp); if (poison) { success = 1; new_draw_info (NDI_UNIQUE, 0, tmp, "Your body feels cleansed"); poison->stats.food = 1; } } if (spell->attacktype & AT_CONFUSION) { poison = present_in_ob_by_name (FORCE, "confusion", tmp); if (poison) { success = 1; new_draw_info (NDI_UNIQUE, 0, tmp, "Your mind feels clearer"); poison->duration = 1; } } if (spell->attacktype & AT_BLIND) { at = archetype::find ("blindness"); poison = present_arch_in_ob (at, tmp); if (poison) { success = 1; new_draw_info (NDI_UNIQUE, 0, tmp, "Your vision begins to return."); poison->stats.food = 1; } } if (spell->last_sp && tmp->stats.sp < tmp->stats.maxsp) { tmp->stats.sp += spell->last_sp; if (tmp->stats.sp > tmp->stats.maxsp) tmp->stats.sp = tmp->stats.maxsp; success = 1; new_draw_info (NDI_UNIQUE, 0, tmp, "Magical energy surges through your body!"); } if (spell->last_grace && tmp->stats.grace < tmp->stats.maxgrace) { tmp->stats.grace += spell->last_grace; if (tmp->stats.grace > tmp->stats.maxgrace) tmp->stats.grace = tmp->stats.maxgrace; success = 1; new_draw_info (NDI_UNIQUE, 0, tmp, "You feel redeemed with your god!"); } if (spell->stats.food && tmp->stats.food < 999) { tmp->stats.food += spell->stats.food; if (tmp->stats.food > 999) tmp->stats.food = 999; success = 1; /* We could do something a bit better like the messages for healing above */ new_draw_info (NDI_UNIQUE, 0, tmp, "You feel your belly fill with food"); } return success; } /* This is used for the spells that gain stats. There are no spells * right now that icnrease wis/int/pow on a temp basis, so no * good comments for those. */ static const char *const no_gain_msgs[NUM_STATS] = { "You grow no stronger.", "You grow no more agile.", "You don't feel any healthier.", "You didn't grow any more intelligent.", "You do not feel any wiser.", "You don't feel any more powerful." "You are no easier to look at.", }; int cast_change_ability (object *op, object *caster, object *spell_ob, int dir, int silent) { object *force = NULL; int i; /* if dir = 99 op defaults to tmp, eat_special_food() requires this. */ object *tmp = dir ? find_target_for_friendly_spell (op, dir) : op; if (!tmp) return 0; /* If we've already got a force of this type, don't add a new one. */ for (object *tmp2 = tmp->inv; tmp2; tmp2 = tmp2->below) { if (tmp2->type == FORCE && tmp2->subtype == FORCE_CHANGE_ABILITY) { if (tmp2->name == spell_ob->name) { force = tmp2; /* the old effect will be "refreshed" */ break; } else if (spell_ob->race && spell_ob->race == tmp2->name) { if (!silent) new_draw_info_format (NDI_UNIQUE, 0, op, "You can not cast %s while %s is in effect", &spell_ob->name, &tmp2->name_pl); return 0; } } } if (force == NULL) { force = get_archetype (FORCE_NAME); force->subtype = FORCE_CHANGE_ABILITY; if (spell_ob->race) force->name = spell_ob->race; else force->name = spell_ob->name; force->name_pl = spell_ob->name; new_draw_info (NDI_UNIQUE, 0, op, "You create an aura of magical force."); } else { int duration; duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob) * 50; if (duration > force->duration) { force->duration = duration; new_draw_info (NDI_UNIQUE, 0, op, "You recast the spell while in effect."); } else { new_draw_info (NDI_UNIQUE, 0, op, "Recasting the spell had no effect."); } return 1; } force->duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob) * 50; force->speed = 1.0; force->speed_left = -1.0; SET_FLAG (force, FLAG_APPLIED); /* Now start processing the effects. First, protections */ for (i = 0; i < NROFATTACKS; i++) { if (spell_ob->resist[i]) { force->resist[i] = spell_ob->resist[i] + SP_level_dam_adjust (caster, spell_ob); if (force->resist[i] > 100) force->resist[i] = 100; } } if (spell_ob->stats.hp) force->stats.hp = spell_ob->stats.hp + SP_level_dam_adjust (caster, spell_ob); if (tmp->type == PLAYER) { /* Stat adjustment spells */ for (i = 0; i < NUM_STATS; i++) { if (sint8 stat = spell_ob->stats.stat (i)) { sint8 sm = 0; for (sint8 k = 0; k < stat; k++) sm += rndm (1, 3); if (tmp->stats.stat (i) + sm > 15 + 5 * stat) sm = max (0, (15 + 5 * stat) - tmp->stats.stat (i)); force->stats.stat (i) = sm; if (!sm) new_draw_info (NDI_UNIQUE, 0, op, no_gain_msgs[i]); } } } force->move_type = spell_ob->move_type; if (QUERY_FLAG (spell_ob, FLAG_SEE_IN_DARK)) SET_FLAG (force, FLAG_SEE_IN_DARK); if (QUERY_FLAG (spell_ob, FLAG_XRAYS)) SET_FLAG (force, FLAG_XRAYS); /* Haste/bonus speed */ if (spell_ob->stats.exp) { if (op->speed > 0.5f) force->stats.exp = (sint64) ((float) spell_ob->stats.exp / (op->speed + 0.5f)); else force->stats.exp = spell_ob->stats.exp; } force->stats.wc = spell_ob->stats.wc; force->stats.ac = spell_ob->stats.ac; force->attacktype = spell_ob->attacktype; insert_ob_in_ob (force, tmp); change_abil (tmp, force); /* Mostly to display any messages */ tmp->update_stats (); return 1; } /* This used to be part of cast_change_ability, but it really didn't make * a lot of sense, since most of the values it derives are from the god * of the caster. */ int cast_bless (object *op, object *caster, object *spell_ob, int dir) { int i; object *god = find_god (determine_god (op)), *tmp2, *force = NULL, *tmp; /* if dir = 99 op defaults to tmp, eat_special_food() requires this. */ if (dir != 0) { tmp = find_target_for_friendly_spell (op, dir); } else { tmp = op; } /* If we've already got a force of this type, don't add a new one. */ for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below) { if (tmp2->type == FORCE && tmp2->subtype == FORCE_CHANGE_ABILITY) { if (tmp2->name == spell_ob->name) { force = tmp2; /* the old effect will be "refreshed" */ break; } else if (spell_ob->race && spell_ob->race == tmp2->name) { new_draw_info_format (NDI_UNIQUE, 0, op, "You can not cast %s while %s is in effect", &spell_ob->name, &tmp2->name_pl); return 0; } } } if (force == NULL) { force = get_archetype (FORCE_NAME); force->subtype = FORCE_CHANGE_ABILITY; if (spell_ob->race) force->name = spell_ob->race; else force->name = spell_ob->name; force->name_pl = spell_ob->name; new_draw_info (NDI_UNIQUE, 0, op, "You create an aura of magical force."); } else { int duration; duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob) * 50; if (duration > force->duration) { force->duration = duration; new_draw_info (NDI_UNIQUE, 0, op, "You recast the spell while in effect."); } else { new_draw_info (NDI_UNIQUE, 0, op, "Recasting the spell had no effect."); } return 0; } force->duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob) * 50; force->speed = 1.0; force->speed_left = -1.0; SET_FLAG (force, FLAG_APPLIED); if (!god) { new_draw_info (NDI_UNIQUE, 0, op, "Your blessing seems empty."); } else { /* Only give out good benefits, and put a max on it */ for (i = 0; i < NROFATTACKS; i++) { if (god->resist[i] > 0) { force->resist[i] = MIN (god->resist[i], spell_ob->resist[ATNR_GODPOWER]); } } force->path_attuned |= god->path_attuned; if (spell_ob->attacktype) force->slaying = god->slaying; if (tmp != op) { new_draw_info_format (NDI_UNIQUE, 0, op, "You bless %s.", &tmp->name); new_draw_info_format (NDI_UNIQUE, 0, tmp, "%s blessed you.", &op->name); } else { new_draw_info_format (NDI_UNIQUE, 0, tmp, "You are blessed by %s!", &god->name); } } force->stats.wc = spell_ob->stats.wc; force->stats.ac = spell_ob->stats.ac; change_abil (tmp, force); /* Mostly to display any messages */ insert_ob_in_ob (force, tmp); tmp->update_stats (); return 1; } /* Alchemy code by Mark Wedel * * This code adds a new spell, called alchemy. Alchemy will turn * objects to pyrite ("false gold"), henceforth called gold nuggets. * * The value of the gold nuggets being about 90% of that of the item * itself. It uses the value of the object before charisma adjustments, * because the nuggets themselves will be will be adjusted by charisma * when sold. * * There is also a chance (1:30) that you will get nothing at all * for the object. There is also a maximum weight that will be * alchemised. */ static void alchemy_object (object *obj, uint64 &total_value, int &total_weight) { uint64 value = query_cost (obj, NULL, F_TRUE); /* Give third price when we alchemy money (this should hopefully * make it so that it isn't worth it to alchemy money, sell * the nuggets, alchemy the gold from that, etc. * Otherwise, give 9 silver on the gold for other objects, * so that it would still be more affordable to haul * the stuff back to town. */ if (QUERY_FLAG (obj, FLAG_UNPAID)) value = 0; else if (obj->type == MONEY || obj->type == GEM) value /= 3; else value = value * 9 / 10; if (obj->value > 0 && rndm (0, 29)) total_value += value; total_weight += obj->total_weight (); obj->destroy (); } int alchemy (object *op, object *caster, object *spell_ob) { if (op->type != PLAYER) return 0; archetype *nugget[3]; nugget[0] = archetype::find ("pyrite3"); nugget[1] = archetype::find ("pyrite2"); nugget[2] = archetype::find ("pyrite"); /* Put a maximum weight of items that can be alchemised. Limits the power * some, and also prevents people from alchemising every table/chair/clock * in sight */ int duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob); int weight_max = duration * 1000; uint64 value_max = duration * 1000; int weight = 0; for (int y = op->y - 1; y <= op->y + 1; y++) { for (int x = op->x - 1; x <= op->x + 1; x++) { uint64 value = 0; sint16 nx = x; sint16 ny = y; maptile *mp = op->map; int mflags = get_map_flags (mp, &mp, nx, ny, &nx, &ny); if (mflags & (P_OUT_OF_MAP | P_NO_MAGIC)) continue; /* Treat alchemy a little differently - most spell effects * use fly as the movement type - for alchemy, consider it * ground level effect. */ if (GET_MAP_MOVE_BLOCK (mp, nx, ny) & MOVE_WALK) continue; for (object *next, *tmp = mp->at (nx, ny).bot; tmp; tmp = next) { next = tmp->above; if (tmp->weight > 0 && !QUERY_FLAG (tmp, FLAG_NO_PICK) && !QUERY_FLAG (tmp, FLAG_ALIVE) && !QUERY_FLAG (tmp, FLAG_IS_CAULDRON)) { if (tmp->inv) { object *next1, *tmp1; for (tmp1 = tmp->inv; tmp1; tmp1 = next1) { next1 = tmp1->below; if (tmp1->weight > 0 && !QUERY_FLAG (tmp1, FLAG_NO_PICK) && !QUERY_FLAG (tmp1, FLAG_ALIVE) && !QUERY_FLAG (tmp1, FLAG_IS_CAULDRON)) alchemy_object (tmp1, value, weight); } } alchemy_object (tmp, value, weight); if (weight > weight_max) break; } } value -= rndm (value >> 4); value = min (value, value_max); for (int i = 0; i < sizeof (nugget) / sizeof (nugget [0]); ++i) if (int nrof = value / nugget [i]->value) { value -= nrof * nugget[i]->value; object *tmp = arch_to_object (nugget[i]); tmp->nrof = nrof; tmp->flag [FLAG_IDENTIFIED] = true; op->map->insert (tmp, x, y, op, 0); } if (weight > weight_max) goto bailout; } } bailout: return 1; } /* This function removes the cursed/damned status on equipped * items. */ int remove_curse (object *op, object *caster, object *spell) { object *tmp; int success = 0, was_one = 0; for (tmp = op->inv; tmp; tmp = tmp->below) if (QUERY_FLAG (tmp, FLAG_APPLIED) && ((QUERY_FLAG (tmp, FLAG_CURSED) && QUERY_FLAG (spell, FLAG_CURSED)) || (QUERY_FLAG (tmp, FLAG_DAMNED) && QUERY_FLAG (spell, FLAG_DAMNED)))) { was_one++; if (tmp->level <= caster_level (caster, spell)) { success++; if (QUERY_FLAG (spell, FLAG_DAMNED)) CLEAR_FLAG (tmp, FLAG_DAMNED); CLEAR_FLAG (tmp, FLAG_CURSED); CLEAR_FLAG (tmp, FLAG_KNOWN_CURSED); tmp->value = 0; /* Still can't sell it */ if (object *pl = tmp->visible_to ()) esrv_update_item (UPD_FLAGS, pl, tmp); } } if (op->type == PLAYER) { if (success) new_draw_info (NDI_UNIQUE, 0, op, "You feel like some of your items are looser now."); else { if (was_one) new_draw_info (NDI_UNIQUE, 0, op, "You failed to remove the curse."); else new_draw_info (NDI_UNIQUE, 0, op, "You are not using any cursed items."); } } return success; } /* Identifies objects in the players inventory/on the ground */ int cast_identify (object *op, object *caster, object *spell) { dynbuf_text buf; object *tmp; int num_ident = spell->stats.dam + SP_level_dam_adjust (caster, spell); if (num_ident < 1) num_ident = 1; for (tmp = op->inv; tmp; tmp = tmp->below) { if (!QUERY_FLAG (tmp, FLAG_IDENTIFIED) && !tmp->invisible && need_identify (tmp)) { identify (tmp); if (op->type == PLAYER) { buf.printf ("You identified: %s.\n\n", long_desc (tmp, op)); if (tmp->msg) buf << "The item has a story:\n\n" << tmp->msg << "\n\n"; } num_ident--; if (!num_ident) break; } } /* If all the power of the spell has been used up, don't go and identify * stuff on the floor. Only identify stuff on the floor if the spell * was not fully used. */ if (num_ident) { for (tmp = GET_MAP_OB (op->map, op->x, op->y); tmp; tmp = tmp->above) if (!QUERY_FLAG (tmp, FLAG_IDENTIFIED) && !tmp->invisible && need_identify (tmp)) { identify (tmp); if (object *pl = tmp->visible_to ()) { buf.printf ("On the ground you identified: %s.\n\n", long_desc (tmp, op)); if (tmp->msg) buf << "The item has a story:\n\n" << tmp->msg << "\n\n"; } num_ident--; if (!num_ident) break; } } if (buf.empty ()) { op->failmsg ("You can't reach anything unidentified."); return 0; } else { if (op->contr) op->contr->infobox (MSG_CHANNEL ("identify"), buf); spell_effect (spell, op->x, op->y, op->map, op); return 1; } } int cast_detection (object *op, object *caster, object *spell, object *skill) { object *tmp, *last, *god, *detect; int done_one, range, mflags, floor, level; sint16 x, y, nx, ny; maptile *m; /* We precompute some values here so that we don't have to keep * doing it over and over again. */ god = find_god (determine_god (op)); level = caster_level (caster, spell); range = spell->range + SP_level_range_adjust (caster, spell); if (!skill) skill = caster; for (x = op->x - range; x <= op->x + range; x++) for (y = op->y - range; y <= op->y + range; y++) { m = op->map; mflags = get_map_flags (m, &m, x, y, &nx, &ny); if (mflags & P_OUT_OF_MAP) continue; /* For most of the detections, we only detect objects above the * floor. But this is not true for show invisible. * Basically, we just go and find the top object and work * down - that is easier than working up. */ for (last = NULL, tmp = GET_MAP_OB (m, nx, ny); tmp; tmp = tmp->above) last = tmp; /* Shouldn't happen, but if there are no objects on a space, this * would happen. */ if (!last) continue; done_one = 0; floor = 0; detect = NULL; for (tmp = last; tmp; tmp = tmp->below) { /* show invisible */ if (QUERY_FLAG (spell, FLAG_MAKE_INVIS) && /* Might there be other objects that we can make visible? */ (tmp->invisible && (QUERY_FLAG (tmp, FLAG_MONSTER) || (tmp->type == PLAYER && !QUERY_FLAG (tmp, FLAG_WIZ)) || tmp->type == CF_HANDLE || tmp->type == TRAPDOOR || tmp->type == EXIT || tmp->type == HOLE || tmp->type == BUTTON || tmp->type == TELEPORTER || tmp->type == GATE || tmp->type == LOCKED_DOOR || tmp->type == WEAPON || tmp->type == ALTAR || tmp->type == SIGN || tmp->type == TRIGGER_PEDESTAL || tmp->type == SPECIAL_KEY || tmp->type == TREASURE || tmp->type == BOOK || tmp->type == HOLY_ALTAR))) { if (random_roll (0, skill->level - 1, op, PREFER_HIGH) > level / 4) { tmp->invisible = 0; done_one = 1; } } if (QUERY_FLAG (tmp, FLAG_IS_FLOOR)) floor = 1; /* All detections below this point don't descend beneath the floor, * so just continue on. We could be clever and look at the type of * detection to completely break out if we don't care about objects beneath * the floor, but once we get to the floor, not likely a very big issue anyways. */ if (floor) continue; /* I had thought about making detect magic and detect curse * show the flash the magic item like it does for detect monster. * however, if the object is within sight, this would then make it * difficult to see what object is magical/cursed, so the * effect wouldn't be as apparant. */ /* detect magic */ if (QUERY_FLAG (spell, FLAG_KNOWN_MAGICAL) && !QUERY_FLAG (tmp, FLAG_KNOWN_MAGICAL) && !QUERY_FLAG (tmp, FLAG_IDENTIFIED) && is_magical (tmp)) { SET_FLAG (tmp, FLAG_KNOWN_MAGICAL); /* make runes more visibile */ if (tmp->type == RUNE && tmp->attacktype & AT_MAGIC) tmp->stats.Cha /= 4; done_one = 1; } /* detect monster */ if (QUERY_FLAG (spell, FLAG_MONSTER) && (QUERY_FLAG (tmp, FLAG_MONSTER) || tmp->type == PLAYER)) { done_one = 2; if (!detect) detect = tmp; } /* Basically, if race is set in the spell, then the creatures race must * match that. if the spell race is set to GOD, then the gods opposing * race must match. */ if (spell->race && QUERY_FLAG (tmp, FLAG_MONSTER) && tmp->race && ((!strcmp (spell->race, "GOD") && god && god->slaying && strstr (god->slaying, tmp->race)) || (strstr (spell->race, tmp->race)))) { done_one = 2; if (!detect) detect = tmp; } if (QUERY_FLAG (spell, FLAG_KNOWN_CURSED) && !QUERY_FLAG (tmp, FLAG_KNOWN_CURSED) && (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))) { SET_FLAG (tmp, FLAG_KNOWN_CURSED); done_one = 1; } } /* for stack of objects on this space */ /* Code here puts an effect of the spell on the space, so you can see * where the magic is. */ if (done_one) { object *detect_ob = arch_to_object (spell->other_arch); /* if this is set, we want to copy the face */ if (done_one == 2 && detect) { detect_ob->face = detect->face; detect_ob->animation_id = detect->animation_id; detect_ob->anim_speed = detect->anim_speed; detect_ob->last_anim = 0; /* by default, the detect_ob is already animated */ if (!QUERY_FLAG (detect, FLAG_ANIMATE)) CLEAR_FLAG (detect_ob, FLAG_ANIMATE); } m->insert (detect_ob, nx, ny, op); } } /* for processing the surrounding spaces */ /* Now process objects in the players inventory if detect curse or magic */ if (QUERY_FLAG (spell, FLAG_KNOWN_CURSED) || QUERY_FLAG (spell, FLAG_KNOWN_MAGICAL)) { done_one = 0; for (tmp = op->inv; tmp; tmp = tmp->below) { if (!tmp->invisible && !QUERY_FLAG (tmp, FLAG_IDENTIFIED)) { if (QUERY_FLAG (spell, FLAG_KNOWN_MAGICAL) && is_magical (tmp) && !QUERY_FLAG (tmp, FLAG_KNOWN_MAGICAL)) { SET_FLAG (tmp, FLAG_KNOWN_MAGICAL); if (object *pl = tmp->visible_to ()) esrv_update_item (UPD_FLAGS, pl, tmp); } if (QUERY_FLAG (spell, FLAG_KNOWN_CURSED) && !QUERY_FLAG (tmp, FLAG_KNOWN_CURSED) && (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))) { SET_FLAG (tmp, FLAG_KNOWN_CURSED); if (object *pl = tmp->visible_to ()) esrv_update_item (UPD_FLAGS, pl, tmp); } } /* if item is not identified */ } /* for the players inventory */ } /* if detect magic/curse and object is a player */ return 1; } /** * Checks if victim has overcharged mana. caster_level is the caster's (skill) * level whos spell did cause the overcharge. */ static void charge_mana_effect (object *victim, int caster_level) { /* Prevent explosions for objects without mana. Without this check, doors * will explode, too. */ if (victim->stats.maxsp <= 0) return; new_draw_info (NDI_UNIQUE, 0, victim, "You feel energy course through you."); if (victim->stats.sp >= victim->stats.maxsp * 2) { object *tmp; new_draw_info (NDI_UNIQUE, 0, victim, "Your head explodes!"); /* Explodes a fireball centered at player */ tmp = get_archetype (EXPLODING_FIREBALL); tmp->dam_modifier = random_roll (1, caster_level, victim, PREFER_LOW) / 5 + 1; tmp->stats.maxhp = random_roll (1, caster_level, victim, PREFER_LOW) / 10 + 2; tmp->insert_at (victim); victim->stats.sp = 2 * victim->stats.maxsp; } else if (victim->stats.sp >= victim->stats.maxsp * 1.88) new_draw_info (NDI_UNIQUE, NDI_ORANGE, victim, "You feel like your head is going to explode."); else if (victim->stats.sp >= victim->stats.maxsp * 1.66) new_draw_info (NDI_UNIQUE, 0, victim, "You get a splitting headache!"); else if (victim->stats.sp >= victim->stats.maxsp * 1.5) { new_draw_info (NDI_UNIQUE, 0, victim, "Chaos fills your world."); confuse_player (victim, victim, 99); } else if (victim->stats.sp >= victim->stats.maxsp * 1.25) new_draw_info (NDI_UNIQUE, 0, victim, "You start hearing voices."); } /* cast_transfer * This spell transfers sp from the player to another person. * We let the target go above their normal maximum SP. */ int cast_transfer (object *op, object *caster, object *spell, int dir) { object *plyr = NULL; sint16 x, y; maptile *m; int mflags; m = op->map; x = op->x + freearr_x[dir]; y = op->y + freearr_y[dir]; mflags = get_map_flags (m, &m, x, y, &x, &y); if (!(mflags & P_OUT_OF_MAP) && mflags & P_IS_ALIVE) { for (plyr = GET_MAP_OB (m, x, y); plyr != NULL; plyr = plyr->above) if (plyr != op && QUERY_FLAG (plyr, FLAG_ALIVE)) break; } /* If we did not find a player in the specified direction, transfer * to anyone on top of us. This is used for the rune of transference mostly. */ if (plyr == NULL) for (plyr = GET_MAP_OB (op->map, op->x, op->y); plyr != NULL; plyr = plyr->above) if (plyr != op && QUERY_FLAG (plyr, FLAG_ALIVE)) break; if (!plyr) { new_draw_info (NDI_BLACK, 0, op, "There is no one there."); return 0; } /* give sp */ if (spell->stats.dam > 0) { plyr->stats.sp += spell->stats.dam + SP_level_dam_adjust (caster, spell); charge_mana_effect (plyr, caster_level (caster, spell)); return 1; } /* suck sp away. Can't suck sp from yourself */ else if (op != plyr) { /* old dragin magic used floats. easier to just use ints and divide by 100 */ int rate = -spell->stats.dam + SP_level_dam_adjust (caster, spell), sucked; if (rate > 95) rate = 95; sucked = (plyr->stats.sp * rate) / 100; plyr->stats.sp -= sucked; if (QUERY_FLAG (op, FLAG_ALIVE)) { /* Player doesn't get full credit */ sucked = (sucked * rate) / 100; op->stats.sp += sucked; if (sucked > 0) { charge_mana_effect (op, caster_level (caster, spell)); } } return 1; } return 0; } /* counterspell: nullifies spell effects. * op is the counterspell object, dir is the direction * it was cast in. * Basically, if the object has a magic attacktype, * this may nullify it. */ void counterspell (object *op, int dir) { object *tmp, *head, *next; int mflags; maptile *m; sint16 sx, sy; sx = op->x + freearr_x[dir]; sy = op->y + freearr_y[dir]; m = op->map; mflags = get_map_flags (m, &m, sx, sy, &sx, &sy); if (mflags & P_OUT_OF_MAP) return; for (tmp = GET_MAP_OB (m, sx, sy); tmp != NULL; tmp = next) { next = tmp->above; /* Need to look at the head object - otherwise, if tmp * points to a monster, we don't have all the necessary * info for it. */ if (tmp->head) head = tmp->head; else head = tmp; /* don't attack our own spells */ if (tmp->owner && tmp->owner == op->owner) continue; /* Basically, if the object is magical and not counterspell, * we will more or less remove the object. Don't counterspell * monsters either. */ if (head->attacktype & AT_MAGIC && !(head->attacktype & AT_COUNTERSPELL) && !QUERY_FLAG (head, FLAG_MONSTER) && (op->level > head->level)) head->destroy (); else switch (head->type) { case SPELL_EFFECT: // XXX: Don't affect floor spelleffects. See also XXX comment // about sanctuary in spell_util.C if (QUERY_FLAG (tmp, FLAG_IS_FLOOR)) continue; if (op->level > head->level) head->destroy (); break; /* I really don't get this rune code that much - that * random chance seems really low. */ case RUNE: if (rndm (0, 149) == 0) { head->stats.hp--; /* weaken the rune */ if (!head->stats.hp) head->destroy (); } break; } } } /* cast_consecrate() - a spell to make an altar your god's */ int cast_consecrate (object *op, object *caster, object *spell) { char buf[MAX_BUF]; object *tmp, *god = find_god (determine_god (op)); if (!god) { new_draw_info (NDI_UNIQUE, 0, op, "You can't consecrate anything if you don't worship a god!"); return 0; } for (tmp = op->below; tmp; tmp = tmp->below) { if (QUERY_FLAG (tmp, FLAG_IS_FLOOR)) break; if (tmp->type == HOLY_ALTAR) { if (tmp->level > caster_level (caster, spell)) { new_draw_info_format (NDI_UNIQUE, 0, op, "You are not powerful enough to reconsecrate the %s", &tmp->name); return 0; } else { /* If we got here, we are consecrating an altar */ sprintf (buf, "Altar of %s", &god->name); tmp->name = buf; tmp->level = caster_level (caster, spell); tmp->other_arch = god->arch; if (op->type == PLAYER) esrv_update_item (UPD_NAME, op, tmp); new_draw_info_format (NDI_UNIQUE, 0, op, "You consecrated the altar to %s!", &god->name); return 1; } } } new_draw_info (NDI_UNIQUE, 0, op, "You are not standing over an altar!"); return 0; } /* animate_weapon - * Generalization of staff_to_snake. Makes a golem out of the caster's weapon. * The golem is based on the archetype specified, modified by the caster's level * and the attributes of the weapon. The weapon is inserted in the golem's * inventory so that it falls to the ground when the golem dies. * This code was very odd - code early on would only let players use the spell, * yet the code wass full of player checks. I've presumed that the code * that only let players use it was correct, and removed all the other * player checks. MSW 2003-01-06 */ int animate_weapon (object *op, object *caster, object *spell, int dir) { object *weapon, *tmp; char buf[MAX_BUF]; int a, i; sint16 x, y; maptile *m; if (!spell->other_arch) { new_draw_info (NDI_UNIQUE, 0, op, "Oops, program error!"); LOG (llevError, "animate_weapon failed: spell %s missing other_arch!\n", &spell->name); return 0; } /* exit if it's not a player using this spell. */ if (op->type != PLAYER) return 0; /* if player already has a golem, abort */ if (object *golem = op->contr->golem) { control_golem (golem, dir); return 0; } /* if no direction specified, pick one */ if (!dir) dir = find_free_spot (spell->other_arch, op->map, op->x, op->y, 1, 9); m = op->map; x = op->x + freearr_x[dir]; y = op->y + freearr_y[dir]; /* if there's no place to put the golem, abort */ if (dir < 0 || (get_map_flags (m, &m, x, y, &x, &y) & P_OUT_OF_MAP) || ((spell->other_arch->move_type & GET_MAP_MOVE_BLOCK (m, x, y)) == spell->other_arch->move_type)) { new_draw_info (NDI_UNIQUE, 0, op, "There is something in the way."); return 0; } /* Use the weapon marked by the player. */ weapon = find_marked_object (op); if (!weapon) { new_draw_info (NDI_BLACK, 0, op, "You must mark a weapon to use with this spell!"); return 0; } if (spell->race && strcmp (weapon->arch->archname, spell->race)) { new_draw_info (NDI_UNIQUE, 0, op, "The spell fails to transform your weapon."); return 0; } if (weapon->type != WEAPON) { new_draw_info (NDI_UNIQUE, 0, op, "You need to wield a weapon to animate it."); return 0; } if (QUERY_FLAG (weapon, FLAG_APPLIED)) { new_draw_info_format (NDI_BLACK, 0, op, "You need to unequip %s before using it in this spell", query_name (weapon)); return 0; } weapon = weapon->split (); /* create the golem object */ tmp = arch_to_object (spell->other_arch); /* if animated by a player, give the player control of the golem */ CLEAR_FLAG (tmp, FLAG_MONSTER); tmp->stats.exp = 0; add_friendly_object (tmp); tmp->type = GOLEM; tmp->set_owner (op); op->contr->golem = tmp; set_spell_skill (op, caster, spell, tmp); /* Give the weapon to the golem now. A bit of a hack to check the * removed flag - it should only be set if weapon->split was * used above. */ if (!QUERY_FLAG (weapon, FLAG_REMOVED)) weapon->remove (); tmp->insert (weapon); /* To do everything necessary to let a golem use the weapon is a pain, * so instead, just set it as equipped (otherwise, we need to update * body_info, skills, etc) */ SET_FLAG (tmp, FLAG_USE_WEAPON); SET_FLAG (weapon, FLAG_APPLIED); tmp->update_stats (); /* There used to be 'odd' code that basically seemed to take the absolute * value of the weapon->magic an use that. IMO, that doesn't make sense - * if you're using a crappy weapon, it shouldn't be as good. */ /* modify weapon's animated wc */ tmp->stats.wc = tmp->stats.wc - SP_level_range_adjust (caster, spell) - 5 * weapon->stats.Dex - 2 * weapon->stats.Str - weapon->magic; if (tmp->stats.wc < -127) tmp->stats.wc = -127; /* Modify hit points for weapon */ tmp->stats.maxhp = tmp->stats.maxhp + spell->duration + SP_level_duration_adjust (caster, spell) + +8 * weapon->magic + 12 * weapon->stats.Con; if (tmp->stats.maxhp < 0) tmp->stats.maxhp = 10; tmp->stats.hp = tmp->stats.maxhp; /* Modify weapon's damage */ tmp->stats.dam = spell->stats.dam + SP_level_dam_adjust (caster, spell) + weapon->stats.dam + weapon->magic + 5 * weapon->stats.Str; if (tmp->stats.dam < 0) tmp->stats.dam = 127; /* attacktype */ if (!tmp->attacktype) tmp->attacktype = AT_PHYSICAL; if (materialtype_t *mt = name_to_material (op->materialname)) { for (i = 0; i < NROFATTACKS; i++) tmp->resist[i] = 50 - (mt->save[i] * 5); a = mt->save[0]; } else { for (i = 0; i < NROFATTACKS; i++) tmp->resist[i] = 5; a = 10; } /* Set weapon's immunity */ tmp->resist[ATNR_CONFUSION] = 100; tmp->resist[ATNR_POISON] = 100; tmp->resist[ATNR_SLOW] = 100; tmp->resist[ATNR_PARALYZE] = 100; tmp->resist[ATNR_TURN_UNDEAD] = 100; tmp->resist[ATNR_FEAR] = 100; tmp->resist[ATNR_DEPLETE] = 100; tmp->resist[ATNR_DEATH] = 100; tmp->resist[ATNR_BLIND] = 100; /* Improve weapon's armour value according to best save vs. physical of its material */ if (a > 14) a = 14; tmp->resist[ATNR_PHYSICAL] = 100 - (int) ((100.0 - (float) tmp->resist[ATNR_PHYSICAL]) / (30.0 - 2.0 * a)); /* Determine golem's speed */ tmp->set_speed (min (3.33, 0.4 + 0.1 * SP_level_range_adjust (caster, spell))); if (!spell->race) { sprintf (buf, "animated %s", &weapon->name); tmp->name = buf; tmp->face = weapon->face; tmp->animation_id = weapon->animation_id; tmp->anim_speed = weapon->anim_speed; tmp->last_anim = weapon->last_anim; tmp->state = weapon->state; tmp->flag [FLAG_ANIMATE] = weapon->flag [FLAG_ANIMATE]; } /* make experience increase in proportion to the strength of the summoned creature. */ tmp->stats.exp *= 1 + (MAX (spell->stats.maxgrace, spell->stats.sp) / caster_level (caster, spell)); tmp->speed_left = -1; tmp->direction = dir; m->insert (tmp, x, y, op); return 1; } /* cast_daylight() - changes the map darkness level *lower* */ /* cast_change_map_lightlevel: Was cast_daylight/nightfall. * This changes the light level for the entire map. */ int cast_change_map_lightlevel (object *op, object *caster, object *spell) { int success; if (!op->map) return 0; /* shouldnt happen */ success = op->map->change_map_light (spell->stats.dam); if (!success) { if (spell->stats.dam < 0) new_draw_info (NDI_UNIQUE, 0, op, "It can be no brighter here."); else new_draw_info (NDI_UNIQUE, 0, op, "It can be no darker here."); } return success; } /* create an aura spell object and put it in the player's inventory. * as usual, op is player, caster is the object casting the spell, * spell is the spell object itself. */ int create_aura (object *op, object *caster, object *spell) { int refresh = 0; object *new_aura; new_aura = present_arch_in_ob (spell->other_arch, op); if (new_aura) refresh = 1; else new_aura = arch_to_object (spell->other_arch); new_aura->duration = spell->duration + 10 * SP_level_duration_adjust (caster, spell); new_aura->stats.dam = spell->stats.dam + SP_level_dam_adjust (caster, spell); set_spell_skill (op, caster, spell, new_aura); new_aura->attacktype = spell->attacktype; new_aura->level = caster_level (caster, spell); if (refresh) new_draw_info (NDI_UNIQUE, 0, op, "You recast the spell while in effect."); else new_draw_info (NDI_UNIQUE, 0, op, "You create an aura of magical force."); insert_ob_in_ob (new_aura, op); new_aura->set_owner (op); return 1; } /* move aura function. An aura is a part of someone's inventory, * which he carries with him, but which acts on the map immediately * around him. * Aura parameters: * duration: duration counter. * attacktype: aura's attacktype * other_arch: archetype to drop where we attack */ void move_aura (object *aura) { /* auras belong in inventories */ object *env = aura->env; object *owner = aura->owner; /* no matter what we've gotta remove the aura... * we'll put it back if its time isn't up. */ aura->remove (); /* exit if we're out of gas */ if (aura->duration-- < 0) { aura->destroy (); return; } /* auras only exist in inventories */ if (!env || !env->map) { aura->destroy (); return; } /* we need to jump out of the inventory for a bit * in order to hit the map conveniently. */ aura->insert_at (env, aura); for (int i = 1; i < 9; i++) { mapxy pos (env); pos.move (i); /* Consider the movement type of the person with the aura as * movement type of the aura. Eg, if the player is flying, the aura * is flying also, if player is walking, it is on the ground, etc. */ if (pos.normalise () && !(OB_TYPE_MOVE_BLOCK (env, pos->move_block))) { hit_map (aura, i, aura->attacktype, 0); if (aura->other_arch) pos.insert (arch_to_object (aura->other_arch), aura); } } /* put the aura back in the player's inventory */ env->insert (aura); aura->set_owner (owner); } /* moves the peacemaker spell. * op is the piece object. */ void move_peacemaker (object *op) { object *tmp; for (tmp = GET_MAP_OB (op->map, op->x, op->y); tmp != NULL; tmp = tmp->above) { int atk_lev, def_lev; object *victim = tmp->head_ (); if (!QUERY_FLAG (victim, FLAG_MONSTER)) continue; if (QUERY_FLAG (victim, FLAG_UNAGGRESSIVE)) continue; if (victim->stats.exp == 0) continue; def_lev = MAX (1, victim->level); atk_lev = MAX (1, op->level); if (rndm (0, atk_lev - 1) > def_lev) { /* make this sucker peaceful. */ INVOKE_OBJECT (KILL, victim, ARG_OBJECT (op)); change_exp (op->owner, victim->stats.exp, op->skill, 0); victim->stats.exp = 0; #if 0 /* No idea why these were all set to zero - if something * makes this creature agressive, he should still do damage. */ victim->stats.dam = 0; victim->stats.sp = 0; victim->stats.grace = 0; victim->stats.Pow = 0; #endif victim->attack_movement = RANDO2; SET_FLAG (victim, FLAG_UNAGGRESSIVE); SET_FLAG (victim, FLAG_RUN_AWAY); SET_FLAG (victim, FLAG_RANDOM_MOVE); CLEAR_FLAG (victim, FLAG_MONSTER); if (victim->name) new_draw_info_format (NDI_UNIQUE, 0, op->owner, "%s no longer feels like fighting.", &victim->name); } } } /* This writes a rune that contains the appropriate message. * There really isn't any adjustments we make. */ int write_mark (object *op, object *spell, const char *msg) { char rune[HUGE_BUF]; object *tmp; if (!msg || msg[0] == 0) { new_draw_info (NDI_UNIQUE, 0, op, "Write what?"); return 0; } if (strcasestr_local (msg, "endmsg")) { new_draw_info (NDI_UNIQUE, 0, op, "Trying to cheat are we?"); LOG (llevInfo, "write_rune: player %s tried to write bogus rune %s\n", &op->name, msg); return 0; } if (!spell->other_arch) return 0; tmp = arch_to_object (spell->other_arch); snprintf (rune, sizeof (rune), "%s\n", msg); tmp->race = op->name; /*Save the owner of the rune */ tmp->msg = rune; tmp->insert_at (op, op, INS_BELOW_ORIGINATOR); return 1; }