/* * static char *rcsid_apply_c = * "$Id: apply.c 11578 2009-02-23 22:02:27Z lalo $"; */ /* CrossFire, A Multiplayer game for X-windows Copyright (C) 2006 Mark Wedel & Crossfire Development Team Copyright (C) 1992 Frank Tore Johansen This program 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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The authors can be reached via e-mail to crossfire-devel@real-time.com */ /** * @file * Handles objects being applied, and their effect. */ #include #include #include #include #include #ifndef __CEXTRACT__ #include #endif /* Want this regardless of rplay. */ #include /* need math lib for double-precision and pow() in dragon_eat_flesh() */ #include /** * Can transport hold object op? * This is a pretty trivial function, * but in the future, possible transport may have more restrictions * or weight reduction like containers * * @param transport * transport to check. * @param op * object we're trying to insert. * @param nrof * number of op. * @return * 1 if can hold, 0 else. */ int transport_can_hold(const object *transport, const object *op, int nrof) { if ((op->weight*nrof+transport->carrying) > transport->weight_limit) return 0; else return 1; } /** * Check if op should abort moving victim because of it's race or slaying. * * @param op * detector or equivalent we're testing. Note that its type is not checked. * @param victim * object trying to move on op. * @return * 1 if it should abort, 0 if it should continue. */ int should_director_abort(object *op, object *victim) { int arch_flag, name_flag, race_flag; /* Get flags to determine what of arch, name, and race should be * checked. This is stored in subtype, and is a bitmask, the LSB * is the arch flag, the next is the name flag, and the last is * the race flag. Also note, if subtype is set to zero, that also * goes to defaults of all affecting it. Examples: * subtype 1: only arch * subtype 3: arch or name * subtype 5: arch or race * subtype 7: all three */ if (op->subtype) { arch_flag = (op->subtype&1); name_flag = (op->subtype&2); race_flag = (op->subtype&4); } else { arch_flag = 1; name_flag = 1; race_flag = 1; } /* If the director has race set, only affect objects with a arch, * name or race that matches. */ if ((op->race) && ((!(victim->arch && arch_flag && victim->arch->name) || strcmp(op->race, victim->arch->name))) && ((!(victim->name && name_flag) || strcmp(op->race, victim->name))) && ((!(victim->race && race_flag) || strcmp(op->race, victim->race)))) { return 1; } /* If the director has slaying set, only affect objects where none * of arch, name, or race match. */ if ((op->slaying) && (((victim->arch && arch_flag && victim->arch->name && !strcmp(op->slaying, victim->arch->name))) || ((victim->name && name_flag && !strcmp(op->slaying, victim->name))) || ((victim->race && race_flag && !strcmp(op->slaying, victim->race))))) { return 1; } return 0; } /** * This checks whether the object has a "on_use_yield" field, * and if so generated and drops matching item. * * @param tmp * item that was applied. */ void handle_apply_yield(object *tmp) { const char *yield; yield = get_ob_key_value(tmp, "on_use_yield"); if (yield != NULL) { object *drop = create_archetype(yield); if (tmp->env) { drop = insert_ob_in_ob(drop, tmp->env); } else { drop->x = tmp->x; drop->y = tmp->y; insert_ob_in_map(drop, tmp->map, tmp, INS_BELOW_ORIGINATOR); } } } int check_weapon_power(const object *who, int improvs); /** * Makes an object's face the main face, which is supposed to be the "closed" one. * * Sets an object's face to the 'face' in the archetype. * Meant for showing containers opening and closing. * * @param op * Object to set face on * * @return TRUE if face changed */ int set_object_face_main(object *op) { int newface = op->arch->clone.face->number; sstring saved = get_ob_key_value(op, "face_closed"); if (saved) { newface = find_face(saved, newface); } if (newface && op->face != &new_faces[newface]) { op->face = &new_faces[newface]; return TRUE; } return FALSE; } /** * Makes an object's face the other_arch face, supposed to be the "opened" one. * * Sets an object's face to the other_arch 'face'. * Meant for showing containers opening and closing. * * @param op * Object to set face on * * @return TRUE if face changed */ static int set_object_face_other(object *op) { sstring custom; int newface = 0; if (op->face && op->other_arch && op->other_arch->clone.face) newface = op->other_arch->clone.face->number; if (op->face != op->arch->clone.face) { /* object has a custom face, save it so it gets correctly restored later. */ set_ob_key_value(op, "face_closed", op->face->name, 1); } custom = get_ob_key_value(op, "face_opened"); if (custom) { newface = find_face(custom, newface); } if (newface && op->face->number != newface) { op->face = &new_faces[newface]; return TRUE; } return FALSE; } /** * Handle apply on containers. This is for * containers that are applied by a player, whether in inventory or * on the ground: eg, sacks, luggages, etc. * * Moved to own function and added many features [Tero.Haatanen(at)lut.fi] * This version is for client/server mode. * * Reminder - there are three states for any container - closed (non applied), * applied (not open, but objects that match get tossed into it), and open * (applied flag set, and op->container points to the open container) * * @param op * player. * @param sack * container the player is opening or closing. * @return * 1 if an object is apllied somehow or another, 0 if error/no apply * * @author Eneq(at)(csd.uu.se) */ int apply_container(object *op, object *sack) { char name_sack[MAX_BUF], name_tmp[MAX_BUF]; object *tmp = op->container; if (op->type != PLAYER) return 0; /* This might change */ if (sack == NULL || sack->type != CONTAINER) { LOG(llevError, "apply_container: %s is not container!\n", sack ? sack->name : "NULL"); return 0; } /* If we have a currently open container, then it needs * to be closed in all cases if we are opening this one up. * We then fall through if appropriate for openening the new * container. */ if (op->container && QUERY_FLAG(sack, FLAG_APPLIED)) { if (op->container->env != op) { /* if container is on the ground */ op->container->move_off = 0; } /* Lauwenmark: Handle for plugin close event */ if (execute_event(tmp, EVENT_CLOSE, op, NULL, NULL, SCRIPT_FIX_ALL) != 0) return 1; query_name(op->container, name_tmp, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You close %s.", "You close %s.", name_tmp); CLEAR_FLAG(op->container, FLAG_APPLIED); op->container = NULL; if (set_object_face_main(tmp)) { esrv_update_item(UPD_FLAGS|UPD_FACE, op, tmp); } else { esrv_update_item(UPD_FLAGS, op, tmp); } if (tmp == sack) return 1; } query_name(sack, name_sack, MAX_BUF); /* If the player is trying to open it (which he must be doing * if we got here), and it is locked, check to see if player * has the equipment to open it. */ if (sack->slaying) { /* it's locked */ tmp = find_key(op, op, sack); if (tmp) { query_name(tmp, name_tmp, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You unlock %s with %s.", "You unlock %s with %s.", name_sack, name_tmp); } else { draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "You don't have the key to unlock %s.", "You don't have the key to unlock %s.", name_sack); return 0; } } /* By the time we get here, we have made sure any other container * has been closed and if this is a locked container, the player * has the key to open it. */ /* There are really two cases - the sack is either on the ground, * or the sack is part of the player's inventory. If on the ground, * we assume that the player is opening it, since if it was being * closed, that would have been taken care of above. */ if (sack->env != op) { /* Hypothetical case - the player is trying to open a sack * that belongs to someone else. This normally should not * happen, but a misbehaving client/player could * try to do it, so let's handle it gracefully. */ if (sack->env) { draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "You can't open %s", "You can't open %s", name_sack); return 0; } if (sack->nrof > 1) { object *left = get_split_ob(sack, sack->nrof-1, NULL, 0); insert_ob_in_map_at(left, sack->map, NULL, INS_NO_MERGE, sack->x, sack->y); /* recompute the name so it's nice */ query_name(sack, name_sack, MAX_BUF); } /* set it so when the player walks off, we can unapply the sack */ sack->move_off = MOVE_ALL; /* trying force closing it */ CLEAR_FLAG(sack, FLAG_APPLIED); draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You open %s.", "You open %s.", name_sack); SET_FLAG(sack, FLAG_APPLIED); op->container = sack; if (set_object_face_other(sack)) { esrv_update_item(UPD_FLAGS|UPD_FACE, op, sack); } else { esrv_update_item(UPD_FLAGS, op, sack); } esrv_send_inventory(op, sack); } else { /* sack is in players inventory */ if (QUERY_FLAG(sack, FLAG_APPLIED)) { /* readied sack becoming open */ CLEAR_FLAG(sack, FLAG_APPLIED); draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You open %s.", "You open %s.", name_sack); SET_FLAG(sack, FLAG_APPLIED); op->container = sack; if (set_object_face_other(sack)) { esrv_update_item(UPD_FLAGS|UPD_FACE, op, sack); } else { esrv_update_item(UPD_FLAGS, op, sack); } esrv_send_inventory(op, sack); } else { object *left = NULL; if (sack->nrof > 1) { left = get_split_ob(sack, sack->nrof-1, NULL, 1); } CLEAR_FLAG(sack, FLAG_APPLIED); draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You readied %s.", "You readied %s.", name_sack); SET_FLAG(sack, FLAG_APPLIED); esrv_update_item(UPD_FLAGS, op, sack); if (left) { insert_ob_in_ob(left, sack->env); esrv_send_item(op, left); } } } return 1; } /** * Actually makes op learn spell. * Informs player of new spell and binding. * * @param op * player who'll learn the spell. * @param spell * spell to learn. * @param special_prayer * 1 for god-given prayer, 0 else. */ void do_learn_spell(object *op, object *spell, int special_prayer) { object *tmp; if (op->type != PLAYER) { LOG(llevError, "BUG: do_learn_spell(): not a player\n"); return; } /* Upgrade special prayers to normal prayers */ if ((tmp = check_spell_known(op, spell->name)) != NULL) { if (special_prayer && !QUERY_FLAG(tmp, FLAG_STARTEQUIP)) { LOG(llevError, "BUG: do_learn_spell(): spell already known, but not marked as startequip\n"); return; } return; } play_sound_player_only(op->contr, SOUND_TYPE_SPELL, spell, 0, "learn"); tmp = get_object(); copy_object(spell, tmp); insert_ob_in_ob(tmp, op); if (special_prayer) { SET_FLAG(tmp, FLAG_STARTEQUIP); } draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "Type 'bind cast %s to store the spell in a key.", "Type 'bind cast %s to store the spell in a key.", spell->name); esrv_add_spells(op->contr, tmp); } /** * Erases spell from player's inventory. Will inform player of loss. * * @param op * player. * @param spell * spell name to forget. */ void do_forget_spell(object *op, const char *spell) { object *spob; if (op->type != PLAYER) { LOG(llevError, "BUG: do_forget_spell(): not a player\n"); return; } if ((spob = check_spell_known(op, spell)) == NULL) { LOG(llevError, "BUG: do_forget_spell(): spell not known\n"); return; } draw_ext_info_format(NDI_UNIQUE|NDI_NAVY, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_CURSED, "You lose knowledge of %s.", "You lose knowledge of %s.", spell); player_unready_range_ob(op->contr, spob); esrv_remove_spell(op->contr, spob); remove_ob(spob); free_object(spob); } /** * Checks if an item is restricted to a race. Non players and DMs can always apply. * * @param who * living thing trying to apply an item. * @param item * item being applied. * @return * 0 if item can't be applied, 1 else. */ static int check_race_restrictions(object *who, object *item) { char buf[MAX_BUF]; sstring restriction; if (who->type != PLAYER || QUERY_FLAG(who, FLAG_WIZ)) return 1; restriction = get_ob_key_value(item, "race_restriction"); if (!restriction) return 1; snprintf(buf, sizeof(buf), ":%s:", who->race); buf[sizeof(buf)-1] = '\0'; if (strstr(restriction, buf) != NULL) return 1; query_name(item, buf, sizeof(buf)); draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_PROHIBITION, "Somehow you can't seem to use the %s.", NULL, buf); return 0; } /** * Main apply handler. * * Checks for unpaid items before applying. * * @param op * ::object causing tmp to be applied. * @param tmp * ::object being applied. * @param aflag * special (always apply/unapply) flags. Nothing is done with * them in this function - they are passed to apply_special(). * @return * - 0: player or monster can't apply objects of that type * - 1: has been applied, or there was an error applying the object * - 2: objects of that type can't be applied if not in inventory */ int manual_apply(object *op, object *tmp, int aflag) { if (tmp->head) tmp = tmp->head; if (QUERY_FLAG(tmp, FLAG_UNPAID) && !QUERY_FLAG(tmp, FLAG_APPLIED)) { if (op->type == PLAYER) { draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "You should pay for it first.", NULL); return METHOD_SILENT_ERROR; } return 0; /* monsters just skip unpaid items */ } if (!check_race_restrictions(op, tmp)) return METHOD_SILENT_ERROR; /* Lauwenmark: Handle for plugin apply event */ if (execute_event(tmp, EVENT_APPLY, op, NULL, NULL, SCRIPT_FIX_ALL) != 0) return METHOD_OK; if (op->contr) play_sound_player_only(op->contr, SOUND_TYPE_ITEM, tmp, 0, "apply"); return ob_apply(tmp, op, aflag); } /** * Living thing is applying an object. * * @param pl * ::object causing op to be applied. * @param op * ::object being applied. * @param aflag * special (always apply/unapply) flags. Nothing is done with * them in this function - they are passed to apply_special(). * @param quiet * if 1, suppresses the "don't know how to apply" and "you must get it first" * messages as needed by player_apply_below(). There can still be * "but you are floating high above the ground" messages. * @return * - 0: player or monster can't apply objects of that type * - 1: has been applied, or there was an error applying the object * - 2: objects of that type can't be applied if not in inventory */ int player_apply(object *pl, object *op, int aflag, int quiet) { int tmp; if (op->env == NULL && (pl->move_type&MOVE_FLYING)) { /* player is flying and applying object not in inventory */ if (!QUERY_FLAG(pl, FLAG_WIZ) && !(op->move_type&MOVE_FLYING)) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "But you are floating high above the ground!", NULL); return 0; } } /* Check for PLAYER to avoid a DM to disappear in a puff of smoke if * applied. */ if (op->type != PLAYER && QUERY_FLAG(op, FLAG_WAS_WIZ) && !QUERY_FLAG(pl, FLAG_WAS_WIZ)) { play_sound_map(SOUND_TYPE_ITEM, op, 0, "evaporate"); draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "The object disappears in a puff of smoke!", NULL); draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "It must have been an illusion.", NULL); remove_ob(op); free_object(op); return 1; } pl->contr->last_used = op; pl->contr->last_used_id = op->count; tmp = manual_apply(pl, op, aflag); if (!quiet) { if (tmp == METHOD_UNHANDLED) { char name[MAX_BUF]; query_name(op, name, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "I don't know how to apply the %s.", "I don't know how to apply the %s.", name); } else if (tmp == METHOD_ERROR) draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "You must get it first!\n", NULL); else if (tmp == METHOD_SILENT_ERROR) return tmp; } if (tmp == METHOD_OK) { if (op->anim_suffix != NULL) apply_anim_suffix(pl, op->anim_suffix); } return tmp; } /** * Attempt to apply the object 'below' the player. * If the player has an open container, we use that for below, otherwise * we use the ground. * * @param pl * player. */ void player_apply_below(object *pl) { object *tmp, *next; int floors; if (pl->contr->transport && pl->contr->transport->type == TRANSPORT) { ob_apply(pl->contr->transport, pl, 0); return; } /* If using a container, set the starting item to be the top * item in the container. Otherwise, use the map. */ tmp = (pl->container != NULL) ? pl->container->inv : pl->below; /* This is perhaps more complicated. However, I want to make sure that * we don't use a corrupt pointer for the next object, so we get the * next object in the stack before applying. This is can only be a * problem if player_apply() has a bug in that it uses the object but * does not return a proper value. */ for (floors = 0; tmp != NULL; tmp = next) { next = tmp->below; if (QUERY_FLAG(tmp, FLAG_IS_FLOOR)) floors++; else if (floors > 0) return; /* process only floor objects after first floor object */ /* If it is visible, player can apply it. If it is applied by * person moving on it, also activate. Added code to make it * so that at least one of players movement types be that which * the item needs. */ if (!tmp->invisible || (tmp->move_on&pl->move_type)) { if (player_apply(pl, tmp, 0, 1) == METHOD_OK) return; } if (floors >= 2) return; /* process at most two floor objects */ } } /** * Unapplies specified item. * No check done on cursed/damned. * Break this out of apply_special() - this is just done * to keep the size of apply_special() to a more managable size. * * @param who * living that has op removed. * @param op * ::object. * @param aflags * combination of @ref AP_xxx "AP_xxx" flags. * @return * 0. */ static int unapply_special(object *who, object *op, int aflags) { char name[MAX_BUF]; if (op->type != LAMP) CLEAR_FLAG(op, FLAG_APPLIED); query_name(op, name, MAX_BUF); switch (op->type) { case WEAPON: if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You unwield %s.", "You unwield %s.", name); (void)change_abil(who, op); if (QUERY_FLAG(who, FLAG_READY_WEAPON)) CLEAR_FLAG(who, FLAG_READY_WEAPON); who->current_weapon = NULL; clear_skill(who); break; case SKILL: /* allows objects to impart skills */ case SKILL_TOOL: if (op != who->chosen_skill) { LOG(llevError, "BUG: apply_special(): applied skill is not a chosen skill\n"); } if (who->type == PLAYER) { if (who->contr->shoottype == range_skill) who->contr->shoottype = range_none; if (!op->invisible) { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You stop using the %s.", "You stop using the %s.", name); } else { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You can no longer use the skill: %s.", "You can no longer use the skill: %s.", op->skill); } } (void)change_abil(who, op); who->chosen_skill = NULL; CLEAR_FLAG(who, FLAG_READY_SKILL); break; case ARMOUR: case HELMET: case SHIELD: case RING: case EARRING: case BOOTS: case GLOVES: case AMULET: case GIRDLE: case BRACERS: case CLOAK: if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You unwear %s.", "You unwear %s.", name); (void)change_abil(who, op); break; case BOW: case WAND: case ROD: case HORN: clear_skill(who); if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You unready %s.", "You unready %s.", name); if (who->type == PLAYER) { who->contr->shoottype = range_none; } else { if (op->type == BOW) CLEAR_FLAG(who, FLAG_READY_BOW); else CLEAR_FLAG(who, FLAG_READY_RANGE); } break; case BUILDER: if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You unready %s.", "You unready %s.", name); who->contr->shoottype = range_none; who->contr->ranges[range_builder] = NULL; break; default: if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You unapply %s.", "You unapply %s.", name); break; } fix_object(who); if (!(aflags&AP_NO_MERGE)) { object *tmp; tmp = merge_ob(op, NULL); if (who->type == PLAYER) { if (tmp) { /* it was merged */ op = tmp; } esrv_update_item(UPD_FLAGS, who, op); } } return 0; } /** * Returns the object that is using body location 'loc'. * Note that 'start' is the first object to start examing - we * then go through the below of this. In this way, you can do * something like: * tmp = get_item_from_body_location(who->inv, 1); * if (tmp) tmp1 = get_item_from_body_location(tmp->below, 1); * to find the second object that may use this location, etc. * * Don't return invisible objects unless they are skill objects. * Invisible other objects that use up body locations can be used as restrictions. * * @param start * object to start from. * @param loc * body position to search. Must be between 0 and NUM_BODY_LOCATIONS-1. * @return * object at position, NULL if none. */ static object *get_item_from_body_location(object *start, int loc) { object *tmp; if (!start) return NULL; for (tmp = start; tmp; tmp = tmp->below) if (QUERY_FLAG(tmp, FLAG_APPLIED) && tmp->body_info[loc] && (!tmp->invisible || tmp->type == SKILL)) return tmp; return NULL; } /** * Remove equipment so an object can be applied. * * This should only be called when it is known * that there are objects to unapply. This makes pretty heavy * use of get_item_from_body_location(). It makes no intelligent choice * on objects - rather, the first that matched is used. * * if aflags has AP_PRINT set, we instead print out waht to unapply * instead of doing it. This is a lot less code than having * another function that does just that. * * @param who * living trying to apply op. * @param op * ::object being applied. * @param aflags * combination of @ref AP_xxx "AP_xxx" flags. * @return * 0 on success, 1 if there is some problem. */ static int unapply_for_ob(object *who, object *op, int aflags) { int i; object *tmp = NULL, *last; char name[MAX_BUF]; /* If we are applying a shield or weapon, unapply any equipped shield * or weapons first - only allowed to use one weapon/shield at a time. */ if (op->type == WEAPON || op->type == SHIELD) { for (tmp = who->inv; tmp; tmp = tmp->below) { if (QUERY_FLAG(tmp, FLAG_APPLIED) && tmp->type == op->type) { if ((aflags&AP_IGNORE_CURSE) || (aflags&AP_PRINT) || (!QUERY_FLAG(tmp, FLAG_CURSED) && !QUERY_FLAG(tmp, FLAG_DAMNED))) { if (aflags&AP_PRINT) { query_name(tmp, name, MAX_BUF); draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, name, NULL); } else unapply_special(who, tmp, aflags); } else { /* In this case, we want to try and remove a * cursed item. While we know it won't work, we * want unapply_special to at least generate the * message. */ if (!(aflags&AP_NOPRINT)) { query_name(tmp, name, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "No matter how hard you try, you just can't remove %s.", "No matter how hard you try, you just can't remove %s.", name); } return 1; } } } } for (i = 0; i < NUM_BODY_LOCATIONS; i++) { /* this used up a slot that we need to free */ if (op->body_info[i]) { last = who->inv; /* We do a while loop - may need to remove several items * in order to free up enough slots. */ while ((who->body_used[i]+op->body_info[i]) < 0) { tmp = get_item_from_body_location(last, i); if (!tmp) { return 1; } /* If just printing, we don't care about cursed status */ if ((aflags&AP_IGNORE_CURSE) || (aflags&AP_PRINT) || (!(QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)))) { if (aflags&AP_PRINT) { query_name(tmp, name, MAX_BUF); draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, name, NULL); } else unapply_special(who, tmp, aflags); } else { /* Cursed item that we can't unequip - tell the player. * Note this could be annoying if this is just one of a * few, so it may not be critical (eg, putting on a * ring and you have one cursed ring.) */ if (!(aflags&AP_NOPRINT)) { query_name(tmp, name, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "The %s just won't come off", "The %s just won't come off", name); } } last = tmp->below; } /* if we got here, this slot is freed up - otherwise, if it * wasn't freed up, the return in the !tmp would have * kicked in. */ } /* if op is using this body location */ } /* for body lcoations */ return 0; } /** * Checks to see if 'who' can apply object 'op'. * * @param who * living thing trying to apply op. * @param op * object applied. * @return * 0 if apply can be done without anything special. * Otherwise returns a bitmask of @ref CAN_APPLY_xxx "CAN_APPLY_xxx" - potentially several of these may be * set, but largely depends on circumstance - in the future, processing * may be pruned once we know some status (eg, once CAN_APPLY_NEVER * is set, do we really are what the other flags may be?) * See include/define.h for detailed description of the meaning of * these return values. */ int can_apply_object(object *who, object *op) { int i, retval = 0; object *tmp = NULL, *ws = NULL; /* Players have 2 'arm's, so they could in theory equip 2 shields or * 2 weapons, but we don't want to let them do that. So if they are * trying to equip a weapon or shield, see if they already have one * in place and store that way. */ if (op->type == WEAPON || op->type == SHIELD) { for (tmp = who->inv; tmp && !ws; tmp = tmp->below) { if (QUERY_FLAG(tmp, FLAG_APPLIED) && tmp->type == op->type) { retval = CAN_APPLY_UNAPPLY; ws = tmp; } } } for (i = 0; i < NUM_BODY_LOCATIONS; i++) { if (op->body_info[i]) { /* Item uses more slots than we have */ if (FABS(op->body_info[i]) > who->body_info[i]) { /* Could return now for efficiently - rest of info * below isn't really needed. */ retval |= CAN_APPLY_NEVER; } else if ((who->body_used[i]+op->body_info[i]) < 0) { /* in this case, equipping this would use more free * spots than we have. */ object *tmp1; /* if we have an applied weapon/shield, and unapply * it would free enough slots to equip the new item, * then just set this can continue. We don't care * about the logic below - if you have shield equipped * and try to equip another shield, there is only one * choice. However, the check for the number of body * locations does take into the account cases where what * is being applied may be two handed for example. */ if (ws) { if ((who->body_used[i]-ws->body_info[i]+op->body_info[i]) >= 0) { retval |= CAN_APPLY_UNAPPLY; continue; } } tmp1 = get_item_from_body_location(who->inv, i); if (!tmp1) { retval |= CAN_APPLY_NEVER; } else { /* need to unapply something. However, if this * something is different than we had found before, * it means they need to apply multiple objects */ retval |= CAN_APPLY_UNAPPLY; if (!tmp) tmp = tmp1; else if (tmp != tmp1) { retval |= CAN_APPLY_UNAPPLY_MULT; } /* This object isn't using up all the slots, so * there must be another. If so, and if the new * item doesn't need all the slots, the player * then has a choice. */ if (((who->body_used[i]-tmp1->body_info[i]) != who->body_info[i]) && (FABS(op->body_info[i]) < who->body_info[i])) retval |= CAN_APPLY_UNAPPLY_CHOICE; /* Does unequipping 'tmp1' free up enough slots * for this to be equipped? If not, there must * be something else to unapply. */ if ((who->body_used[i]+op->body_info[i]-tmp1->body_info[i]) < 0) retval |= CAN_APPLY_UNAPPLY_MULT; } } /* if not enough free slots */ } /* if this object uses location i */ } /* for i -> num_body_locations loop */ /* Do checks for can_use_weapon/shield/armour. */ if (IS_WEAPON(op) && !QUERY_FLAG(who, FLAG_USE_WEAPON)) retval |= CAN_APPLY_RESTRICTION; if (IS_SHIELD(op) && !QUERY_FLAG(who, FLAG_USE_SHIELD)) retval |= CAN_APPLY_RESTRICTION; if (IS_ARMOR(op) && !QUERY_FLAG(who, FLAG_USE_ARMOUR)) retval |= CAN_APPLY_RESTRICTION; if (who->type != PLAYER) { if ((op->type == WAND || op->type == HORN || op->type == ROD) && !QUERY_FLAG(who, FLAG_USE_RANGE)) retval |= CAN_APPLY_RESTRICTION; if (op->type == BOW && !QUERY_FLAG(who, FLAG_USE_BOW)) retval |= CAN_APPLY_RESTRICTION; if (op->type == RING && !QUERY_FLAG(who, FLAG_USE_RING)) retval |= CAN_APPLY_RESTRICTION; if (op->type == EARRING && !QUERY_FLAG(who, FLAG_USE_EARRING)) retval |= CAN_APPLY_RESTRICTION; if (op->type == BOW && !QUERY_FLAG(who, FLAG_USE_BOW)) retval |= CAN_APPLY_RESTRICTION; } return retval; } /** * This checks to see of the player (who) is sufficient level to use a weapon * with improvs improvements (typically last_eat). We take an int here * instead of the object so that the improvement code can pass along the * increased value to see if the object is usuable. * we return 1 (true) if the player can use the weapon. * See ../types/weapon_improver/weapon_improver.c * * @param who * living to check * @param improvs * improvement level. * @return * 1 if who can use the item, 0 else. * @todo * remove obsolete code. */ int check_weapon_power(const object *who, int improvs) { /* Old code is below (commented out). Basically, since weapons * are the only object players really have any control to improve, * it's a bit harsh to require high level in some combat skill, * so we just use overall level. */ #if 1 if (((who->level/5)+5) >= improvs) return 1; else return 0; #else int level = 0; /* The skill system hands out wc and dam bonuses to fighters * more generously than the old system (see fix_object). Thus * we need to curtail the power of player enchanted weapons. * I changed this to 1 improvement per "fighter" level/5 -b.t. * Note: Nothing should break by allowing this ratio to be * different or using normal level - it is just a matter of play * balance. */ if (who->type == PLAYER) { object *wc_obj = NULL; for (wc_obj = who->inv; wc_obj; wc_obj = wc_obj->below) if (wc_obj->type == SKILL && IS_COMBAT_SKILL(wc_obj->subtype) && wc_obj->level > level) level = wc_obj->level; if (!level) { LOG(llevError, "Error: Player: %s lacks wc experience object\n", who->name); level = who->level; } } else level = who->level; return (improvs <= ((level/5)+5)); #endif } /** * Apply an object. * * This function doesn't check for unpaid items, but check other restrictions. * * Usage example: apply_special (who, op, AP_UNAPPLY | AP_IGNORE_CURSE) * * @param who * object using op. It can be a monster. * @param op * object being used. Should be an equipment type item, * eg, one which you put on and keep on for a while, and not something * like a potion or scroll. * @param aflags * combination of @ref AP_xxx "AP_xxx" flags. * @return * 1 if the action could not be completed, 0 on success. * However, success is a matter of meaning - if the * user passes the 'apply' flag to an object already applied, * nothing is done, and 0 is returned. */ int apply_special(object *who, object *op, int aflags) { int basic_flag = aflags&AP_BASIC_FLAGS; object *tmp, *skop = NULL; int i; char name_op[MAX_BUF]; if (who == NULL) { LOG(llevError, "apply_special() from object without environment.\n"); return 1; } query_name(op, name_op, MAX_BUF); if (op->env != who) return 1; /* op is not in inventory */ /* trying to unequip op */ if (QUERY_FLAG(op, FLAG_APPLIED)) { /* always apply, so no reason to unapply */ if (basic_flag == AP_APPLY) return 0; if (!(aflags&AP_IGNORE_CURSE) && (QUERY_FLAG(op, FLAG_CURSED) || QUERY_FLAG(op, FLAG_DAMNED))) { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "No matter how hard you try, you just can't remove %s.", "No matter how hard you try, you just can't remove %s.", name_op); return 1; } return unapply_special(who, op, aflags); } if (basic_flag == AP_UNAPPLY) return 0; i = can_apply_object(who, op); /* Can't just apply this object. Lets see what not and what to do */ if (i) { if (i&CAN_APPLY_NEVER) { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BADBODY, "You don't have the body to use a %s", "You don't have the body to use a %s", name_op); return 1; } else if (i&CAN_APPLY_RESTRICTION) { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_PROHIBITION, "You have a prohibition against using a %s", "You have a prohibition against using a %s", name_op); return 1; } if (who->type != PLAYER) { /* Some error, so don't try to equip something more */ if (unapply_for_ob(who, op, aflags)) return 1; } else { if (who->contr->unapply == unapply_never || (i&CAN_APPLY_UNAPPLY_CHOICE && who->contr->unapply == unapply_nochoice)) { if (!(aflags&AP_NOPRINT)) draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_UNAPPLY, "You need to unapply some item(s):", NULL); unapply_for_ob(who, op, AP_PRINT); return 1; } else if (who->contr->unapply == unapply_always || !(i&CAN_APPLY_UNAPPLY_CHOICE)) { i = unapply_for_ob(who, op, aflags); if (i) return 1; } } } if (op->skill && op->type != SKILL && op->type != SKILL_TOOL) { skop = find_skill_by_name(who, op->skill); if (!skop) { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "You need the %s skill to use this item!", "You need the %s skill to use this item!", op->skill); return 1; } else { /* While experience will be credited properly, we want to * change the skill so that the dam and wc get updated */ change_skill(who, skop, (aflags&AP_NOPRINT)); } } if (who->type == PLAYER && op->item_power && (op->item_power+who->contr->item_power) > (settings.item_power_factor*who->level)) { if (!(aflags&AP_NOPRINT)) draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "Equipping that combined with other items would consume your soul!", NULL); return 1; } /* If personalized blessings are activated, the weapon can bite * the wielder if he/she is not the one who initially blessed it. * Chances of being hurt depend on the experience amount * ("willpower") the object has, compared to the experience * amount of the wielder. */ if (settings.personalized_blessings) { const char *owner = get_ob_key_value(op, "item_owner"); if ((owner != NULL) && (strcmp(owner, who->name))) { const char *will = get_ob_key_value(op, "item_willpower"); long item_will = 0; long margin = 0; const char *msg = NULL; int random_effect = 0; int damage_percentile = 0; if (will != NULL) item_will = atol(will); if (item_will > who->stats.exp) { draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "This %s refuses to serve you - it keeps evading your hand !", "This %s refuses to serve you - it keeps evading your hand !", op->name); return 1; } if (item_will != 0) margin = who->stats.exp/item_will; else margin = who->stats.exp; random_effect = (random_roll(0, 100, who, 1)-(margin*20)); if (random_effect > 80) { msg = "You don't know why, but you have the feeling that the %s is angry at you !"; damage_percentile = 60; } else if (random_effect > 60) { msg = "The %s seems to look at you nastily !"; damage_percentile = 45; } else if (random_effect > 40) { msg = "You have the strange feeling that the %s is annoyed..."; damage_percentile = 30; } else if (random_effect > 20) { msg = "The %s seems tired, or bored, in a way. Very strange !"; damage_percentile = 15; } else if (random_effect > 0) { msg = "You hear the %s sighing !"; damage_percentile = 0; } if (msg != NULL) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, msg, msg, op->name); if (damage_percentile > 0) { int weapon_bite = (who->stats.hp*damage_percentile)/100; if (weapon_bite < 1) weapon_bite = 1; who->stats.hp -= weapon_bite; draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_HIT, "You get a nasty bite in the hand !", "You get a nasty bite in the hand !"); } } } /* Ok. We are now at the state where we can apply the new object. * Note that we don't have the checks for can_use_... * below - that is already taken care of by can_apply_object. */ if (op->nrof > 1) tmp = get_split_ob(op, op->nrof-1, NULL, 0); else tmp = NULL; switch (op->type) { case WEAPON: { int ownerlen = 0; char *quotepos = NULL; if (!check_weapon_power(who, op->last_eat)) { if (!(aflags&AP_NOPRINT)) draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "That weapon is too powerful for you to use. It would consume your soul!", NULL); if (tmp != NULL) (void)insert_ob_in_ob(tmp, who); return 1; } /* BUG? It seems the value of quotepos is never used. */ if ((quotepos = strstr(op->name, "'")) != NULL) { ownerlen = (strstr(op->name, "'")-op->name); if (op->level && (strncmp(op->name, who->name, ownerlen))) { /* if the weapon does not have the name as the * character, can't use it. (Ragnarok's sword * attempted to be used by Foo: won't work) */ if (!(aflags&AP_NOPRINT)) draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "The weapon does not recognize you as its owner.", NULL); if (tmp != NULL) (void)insert_ob_in_ob(tmp, who); return 1; } } SET_FLAG(op, FLAG_APPLIED); if (skop) change_skill(who, skop, 1); if (!QUERY_FLAG(who, FLAG_READY_WEAPON)) SET_FLAG(who, FLAG_READY_WEAPON); if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You wield %s.", "You wield %s.", name_op); (void)change_abil(who, op); break; } case ARMOUR: case HELMET: case SHIELD: case BOOTS: case GLOVES: case GIRDLE: case BRACERS: case CLOAK: case RING: case EARRING: case AMULET: SET_FLAG(op, FLAG_APPLIED); if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You wear %s.", "You wear %s.", name_op); (void)change_abil(who, op); break; /* this part is needed for skill-tools */ case SKILL: case SKILL_TOOL: if (who->chosen_skill) { LOG(llevError, "BUG: apply_special(): can't apply two skills\n"); return 1; } if (who->type == PLAYER) { who->contr->shoottype = range_skill; who->contr->ranges[range_skill] = op; if (!op->invisible) { if (!(aflags&AP_NOPRINT)) { draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You ready %s.", "You ready %s.", name_op); draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You can now use the skill: %s.", "You can now use the skill: %s.", op->skill); } } else { if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "Readied skill: %s.", "Readied skill: %s.", op->skill ? op->skill : op->name); } } SET_FLAG(op, FLAG_APPLIED); (void)change_abil(who, op); who->chosen_skill = op; SET_FLAG(who, FLAG_READY_SKILL); break; case BOW: if (!check_weapon_power(who, op->last_eat)) { if (!(aflags&AP_NOPRINT)) { draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "That item is too powerful for you to use.", NULL); draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "It would consume your soul!.", NULL); } if (tmp != NULL) (void)insert_ob_in_ob(tmp, who); return 1; } if (op->level && (strncmp(op->name, who->name, strlen(who->name)))) { if (!(aflags&AP_NOPRINT)) { draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_ERROR, "The weapon does not recognize you as its owner.", NULL); } if (tmp != NULL) (void)insert_ob_in_ob(tmp, who); return 1; } /*FALLTHROUGH*/ case WAND: case ROD: case HORN: /* check for skill, alter player status */ SET_FLAG(op, FLAG_APPLIED); if (skop) change_skill(who, skop, 0); if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You ready %s.", "You ready %s.", name_op); if (who->type == PLAYER) { if (op->type == BOW) { (void)change_abil(who, op); if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You will now fire %s with %s.", "You will now fire %s with %s.", op->race ? op->race : "nothing", name_op); who->contr->shoottype = range_bow; } else { who->contr->shoottype = range_misc; } } else { if (op->type == BOW) SET_FLAG(who, FLAG_READY_BOW); else SET_FLAG(who, FLAG_READY_RANGE); } break; case BUILDER: if (who->contr->ranges[range_builder]) unapply_special(who, who->contr->ranges[range_builder], 0); who->contr->shoottype = range_builder; who->contr->ranges[range_builder] = op; if (!(aflags&AP_NOPRINT)) draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You ready your %s.", "You ready your %s.", name_op); break; default: draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS, "You apply %s.", "You apply %s.", name_op); } /* end of switch op->type */ SET_FLAG(op, FLAG_APPLIED); if (tmp != NULL) tmp = insert_ob_in_ob(tmp, who); fix_object(who); /* We exclude spell casting objects. The fire code will set the * been applied flag when they are used - until that point, * you don't know anything about them. */ if (who->type == PLAYER && op->type != WAND && op->type != HORN && op->type != ROD) SET_FLAG(op, FLAG_BEEN_APPLIED); if (QUERY_FLAG(op, FLAG_CURSED) || QUERY_FLAG(op, FLAG_DAMNED)) { if (who->type == PLAYER) { draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_CURSED, "Oops, it feels deadly cold!", NULL); SET_FLAG(op, FLAG_KNOWN_CURSED); } } if (who->type == PLAYER) { esrv_update_item(UPD_NROF|UPD_FLAGS|UPD_WEIGHT, who, op); } return 0; } /** * Map was just loaded, handle op's initialisation. * * Generates shop floor's item, and treasures. * * @param op * object to initialize. * @return * 1 if object was initialized, 0 else. */ int auto_apply(object *op) { object *tmp = NULL, *tmp2; int i; switch (op->type) { case SHOP_FLOOR: if (!HAS_RANDOM_ITEMS(op)) return 0; do { i = 10; /* let's give it 10 tries */ while ((tmp = generate_treasure(op->randomitems, op->stats.exp ? (int)op->stats.exp : MAX(op->map->difficulty, 5))) == NULL && --i) ; if (tmp == NULL) return 0; if (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)) { free_object(tmp); tmp = NULL; } } while (!tmp); tmp->x = op->x; tmp->y = op->y; SET_FLAG(tmp, FLAG_UNPAID); insert_ob_in_map(tmp, op->map, NULL, 0); CLEAR_FLAG(op, FLAG_AUTO_APPLY); identify(tmp); break; case TREASURE: if (QUERY_FLAG(op, FLAG_IS_A_TEMPLATE)) return 0; while ((op->stats.hp--) > 0) create_treasure(op->randomitems, op, op->map ? GT_ENVIRONMENT : 0, op->stats.exp ? (int)op->stats.exp : op->map == NULL ? 14 : op->map->difficulty, 0); /* If we generated an object and put it in this object's * inventory, move it to the parent object as the current * object is about to disappear. An example of this item * is the random_ *stuff that is put inside other objects. */ for (tmp = op->inv; tmp; tmp = tmp2) { tmp2 = tmp->below; remove_ob(tmp); if (op->env) insert_ob_in_ob(tmp, op->env); else free_object(tmp); } remove_ob(op); free_object(op); break; } return tmp ? 1 : 0; } /** * Go through the entire map (only the first time * when an original map is loaded) and performs special actions for * certain objects (most initialization of chests and creation of * treasures and stuff). Calls auto_apply() if appropriate. * * @param m * map to fix. */ void fix_auto_apply(mapstruct *m) { object *tmp, *above = NULL; int x, y; if (m == NULL) return; for (x = 0; x < MAP_WIDTH(m); x++) for (y = 0; y < MAP_HEIGHT(m); y++) for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = above) { above = tmp->above; if (tmp->inv) { object *invtmp, *invnext; for (invtmp = tmp->inv; invtmp != NULL; invtmp = invnext) { invnext = invtmp->below; if (QUERY_FLAG(invtmp, FLAG_AUTO_APPLY)) auto_apply(invtmp); else if (invtmp->type == TREASURE && HAS_RANDOM_ITEMS(invtmp)) { while ((invtmp->stats.hp--) > 0) create_treasure(invtmp->randomitems, invtmp, 0, m->difficulty, 0); invtmp->randomitems = NULL; } else if (invtmp && invtmp->arch && invtmp->type != TREASURE && invtmp->type != SPELL && invtmp->type != CLASS && HAS_RANDOM_ITEMS(invtmp)) { create_treasure(invtmp->randomitems, invtmp, 0, m->difficulty, 0); /* Need to clear this so that we never try to * create treasure again for this object */ invtmp->randomitems = NULL; } } /* This is really temporary - the code at the * bottom will also set randomitems to null. * The problem is there are bunches of maps/players * already out there with items that have spells * which haven't had the randomitems set * to null yet. * MSW 2004-05-13 * * And if it's a spellbook, it's better to set * randomitems to NULL too, else you get two spells * in the book ^_- * Ryo 2004-08-16 */ if (tmp->type == WAND || tmp->type == ROD || tmp->type == SCROLL || tmp->type == HORN || tmp->type == FIREWALL || tmp->type == POTION || tmp->type == ALTAR || tmp->type == SPELLBOOK) tmp->randomitems = NULL; } if (QUERY_FLAG(tmp, FLAG_AUTO_APPLY)) auto_apply(tmp); else if ((tmp->type == TREASURE || (tmp->type == CONTAINER)) && HAS_RANDOM_ITEMS(tmp)) { while ((tmp->stats.hp--) > 0) create_treasure(tmp->randomitems, tmp, 0, m->difficulty, 0); tmp->randomitems = NULL; } else if (tmp->type == TIMED_GATE) { object *head = tmp->head != NULL ? tmp->head : tmp; if (QUERY_FLAG(head, FLAG_IS_LINKED)) { tmp->speed = 0; update_ob_speed(tmp); } } /* This function can be called everytime a map is loaded, * even when swapping back in. As such, we don't want to * create the treasure over and ove again, so after we * generate the treasure, blank out randomitems so if it * is swapped in again, it won't make anything. This is a * problem for the above objects, because they have * counters which say how many times to make the treasure. */ else if (tmp && tmp->arch && tmp->type != PLAYER && tmp->type != TREASURE && tmp->type != SPELL && tmp->type != PLAYER_CHANGER && tmp->type != CLASS && HAS_RANDOM_ITEMS(tmp)) { create_treasure(tmp->randomitems, tmp, GT_APPLY, m->difficulty, 0); tmp->randomitems = NULL; } } for (x = 0; x < MAP_WIDTH(m); x++) for (y = 0; y < MAP_HEIGHT(m); y++) for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) if (tmp->above && (tmp->type == TRIGGER_BUTTON || tmp->type == TRIGGER_PEDESTAL)) check_trigger(tmp, tmp->above); } /** * op made some mistake with a scroll, this takes care of punishment. * scroll_failure()- hacked directly from spell_failure * * If settings.spell_failure_effects is FALSE, the only nasty things * that can happen are weird spell cast, or mana drain. * * @param op * who failed. * @param failure * what kind of nasty things happen. * @param power * the higher the value, the worse the thing that happens. */ void scroll_failure(object *op, int failure, int power) { if (abs(failure/4) > power) power = abs(failure/4); /* set minimum effect */ if (failure <= -1 && failure > -15) {/* wonder */ object *tmp; draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "Your spell warps!", NULL); tmp = create_archetype(SPELL_WONDER); cast_wonder(op, op, 0, tmp); if (op->stats.sp < 0) /* For some reason the sp can become negative here. */ op->stats.sp = 0; free_object(tmp); return; } if (settings.spell_failure_effects == TRUE) { if (failure <= -35 && failure > -60) { /* confusion */ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "The magic recoils on you!", NULL); confuse_living(op, op, power); return; } if (failure <= -60 && failure > -70) {/* paralysis */ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "The magic recoils and paralyzes you!", NULL); paralyze_living(op, op, power); return; } if (failure <= -70 && failure > -80) {/* blind */ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "The magic recoils on you!", NULL); blind_living(op, op, power); return; } if (failure <= -80) {/* blast the immediate area */ object *tmp; tmp = create_archetype(LOOSE_MANA); cast_magic_storm(op, tmp, power); draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "You unlease uncontrolled mana!", NULL); free_object(tmp); return; } } /* Either no spell failure on this server, or wrong values, * in any case let's punish. */ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "Your mana is drained!", NULL); op->stats.sp -= random_roll(0, power-1, op, PREFER_LOW); if (op->stats.sp < 0) op->stats.sp = 0; } /** * Applies (race) changes to a player. * @param pl * object to change. * @param change * what kind of changes to apply. Should be of type CLASS. */ void apply_changes_to_player(object *pl, object *change) { int excess_stat = 0; /* if the stat goes over the maximum * for the race, put the excess stat some * where else. */ switch (change->type) { case CLASS: { living *stats = &(pl->contr->orig_stats); living *ns = &(change->stats); object *walk; int flag_change_face = 1; /* the following code assigns stats up to the stat max * for the race, and if the stat max is exceeded, * tries to randomly reassign the excess stat */ int i, j; for (i = 0; i < NUM_STATS; i++) { sint8 stat = get_attr_value(stats, i); int race_bonus = get_attr_value(&(pl->arch->clone.stats), i); stat += get_attr_value(ns, i); if (stat > 20+race_bonus) { excess_stat++; stat = 20+race_bonus; } set_attr_value(stats, i, stat); } for (j = 0; excess_stat > 0 && j < 100; j++) { /* try 100 times to assign excess stats */ int i = rndm(0, 6); int stat = get_attr_value(stats, i); int race_bonus = get_attr_value(&(pl->arch->clone.stats), i); if (i == CHA) continue; /* exclude cha from this */ if (stat < 20+race_bonus) { change_attr_value(stats, i, 1); excess_stat--; } } /* insert the randomitems from the change's treasurelist into * the player ref: player.c */ if (change->randomitems != NULL) give_initial_items(pl, change->randomitems); /* modify the player's slaying field */ if (change->slaying != NULL) { if (pl->slaying == NULL) pl->slaying = add_refcount(change->slaying); else pl->slaying = join_strings(pl->slaying, change->slaying, ","); } /* modify the player's attunement */ if (change->path_attuned) { if (pl->path_attuned) pl->path_attuned |= change->path_attuned; else pl->path_attuned = change->path_attuned; } /* set up the face, for some races. */ /* first, look for the force object banning changing the * face. Certain races never change face with class. */ for (walk = pl->inv; walk != NULL; walk = walk->below) if (!strcmp(walk->name, "NOCLASSFACECHANGE")) flag_change_face = 0; if (flag_change_face) { pl->animation_id = GET_ANIM_ID(change); pl->face = change->face; if (QUERY_FLAG(change, FLAG_ANIMATE)) SET_FLAG(pl, FLAG_ANIMATE); else CLEAR_FLAG(pl, FLAG_ANIMATE); } if (change->anim_suffix) { char buf[MAX_BUF]; int anim; snprintf(buf, MAX_BUF, "%s_%s", animations[pl->animation_id].name, change->anim_suffix); anim = try_find_animation(buf); if (anim) { pl->animation_id = anim; pl->anim_speed = -1; CLEAR_FLAG(pl, FLAG_ANIMATE); animate_object(pl, pl->facing); } } /* check the special case of can't use weapons */ /*if(QUERY_FLAG(change, FLAG_USE_WEAPON)) * CLEAR_FLAG(pl, FLAG_USE_WEAPON); */ if (!strcmp(change->name, "monk")) CLEAR_FLAG(pl, FLAG_USE_WEAPON); break; } } } void legacy_apply_container(object *op, object *sack) { apply_container(op, sack); }