/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2001 Mark Wedel & Crossfire Development Team * Copyright (©) 1992 Frank Tore Johansen * * Deliantra is free software: you can redistribute it and/or modify it under * the terms of the Affero 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 Affero GNU General Public License * and 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 #include // these must be in the inventory before they can be applied static const struct apply_types_inv_only : typeset { apply_types_inv_only () { set (WEAPON); set (ARMOUR); set (BOOTS); set (GLOVES); set (AMULET); set (GIRDLE); set (BRACERS); set (SHIELD); set (HELMET); set (RING); set (CLOAK); set (WAND); set (ROD); set (HORN); set (SKILL); set (SPELL); set (BOW); set (RANGED); set (BUILDER); set (SKILL_TOOL); } } apply_types_inv_only; // these only make sense for the player static const struct apply_types_player_only : typeset { apply_types_player_only () { set (TRANSPORT); set (EXIT); set (BOOK); set (SIGN); set (BOOK); set (SKILLSCROLL); set (SPELLBOOK); set (INSCRIBABLE); set (TREASURE); set (SAVEBED); set (ARMOUR_IMPROVER); set (WEAPON_IMPROVER); set (CLOCK); set (MENU); set (LIGHTER); /* for lighting torches/lanterns/etc */ } } apply_types_player_only; // applying these _can_ be attempted, others cannot // be applied at all. used by e.g. apply below. static const struct apply_types : typeset { apply_types () : typeset ((typeset)apply_types_player_only | (typeset)apply_types_inv_only) { set (T_HANDLE); set (TRIGGER); set (SCROLL); set (POTION); set (CLOSE_CON); set (CONTAINER); set (LAMP); set (TORCH); set (DRINK); set (FOOD); set (FLESH); set (POISON); set (POWER_CRYSTAL); set (ITEM_TRANSFORMER); } } apply_types; /**************************************************************************** * Weapon improvement code follows ****************************************************************************/ /** * This function just checks whether who can handle equipping an item * with item_power. */ static bool check_item_power (object *who, int item_power) { if (who->type == PLAYER && item_power && item_power + who->contr->item_power > settings.item_power_factor * who->level) return false; else return true; } /** * This returns the sum of nrof of item (arch name). */ static int check_item (object *op, shstr_cmp item) { int count = 0; if (!item) return 0; for (op = op->below; op; op = op->below) if (op->arch->archname == item) if (!op->flag [FLAG_CURSED] && !op->flag [FLAG_DAMNED] && /* Loophole bug? -FD- */ !op->flag [FLAG_UNPAID]) count += op->number_of (); return count; } /** * This removes 'nrof' of what item->slaying says to remove. * op is typically the player, which is only * really used to determine what space to look at. * Modified to only eat 'nrof' of objects. */ static void eat_item (object *op, shstr_cmp item, uint32 nrof) { object *prev; prev = op; op = op->below; while (op) { if (op->arch->archname == item) { if (op->nrof >= nrof) { op->decrease (nrof); return; } else { op->decrease (nrof); nrof -= op->nrof; } op = prev; } prev = op; op = op->below; } } /** * Returns how many items of type improver->slaying there are under op. * Will display a message if none found, and 1 if improver->slaying is NULL. */ static int check_sacrifice (object *op, const object *improver) { int count = 0; if (improver->slaying) { count = check_item (op, improver->slaying); if (count < 1) { op->failmsgf ("The gods want more %ss", &improver->slaying); return 0; } } else count = 1; return count; } /** * Actually improves the weapon, and tells user. */ static int improve_weapon_stat (object *op, object *improver, object *weapon, sint8 &stat, int sacrifice_count, const char *statname) { stat += sacrifice_count; weapon->last_eat++; improver->decrease (); /* So it updates the players stats and the window */ op->update_stats (); op->statusmsg (format ( "Your sacrifice was accepted.\n" "Weapon's bonus to %s improved by %d.", statname, sacrifice_count )); return 1; } /* Types of improvements, hidden in the sp field. */ #define IMPROVE_PREPARE 1 #define IMPROVE_DAMAGE 2 #define IMPROVE_WEIGHT 3 #define IMPROVE_ENCHANT 4 #define IMPROVE_STR 5 #define IMPROVE_DEX 6 #define IMPROVE_CON 7 #define IMPROVE_WIS 8 #define IMPROVE_CHA 9 #define IMPROVE_INT 10 #define IMPROVE_POW 11 /** * This does the prepare weapon scroll. * Checks for sacrifice, and so on. */ static int prepare_weapon (object *op, object *improver, object *weapon) { int sacrifice_count, i; char buf[MAX_BUF]; if (weapon->level != 0) { op->failmsg ("Weapon is already prepared!"); return 0; } for (i = 0; i < NROFATTACKS; i++) if (weapon->resist[i]) break; /* If we break out, i will be less than nrofattacks, preventing * improvement of items that already have protections. */ if (i < NROFATTACKS || weapon->stats.hp || /* regeneration */ (weapon->stats.sp && weapon->type == WEAPON) || /* sp regeneration */ weapon->stats.exp || /* speed */ weapon->stats.ac) /* AC - only taifu's I think */ { op->failmsg ("You cannot prepare magic weapons. " "H"); return 0; } sacrifice_count = check_sacrifice (op, improver); if (sacrifice_count <= 0) return 0; weapon->level = isqrt (sacrifice_count); eat_item (op, improver->slaying, sacrifice_count); op->statusmsg (format ( "Your sacrifice was accepted." "Your *%s may be improved %d times.", &weapon->name, weapon->level )); sprintf (buf, "%s's %s", &op->name, &weapon->name); weapon->name = weapon->name_pl = buf; weapon->nrof = 0; /* prevents preparing n weapons in the same slot at once! */ improver->decrease (); weapon->last_eat = 0; return 1; } /** * Does the dirty job for 'improve weapon' scroll, prepare or add something. * This is the new improve weapon code. * Returns 0 if it was not able to work for some reason. * * Checks if weapon was prepared, if enough potions on the floor, ... * * We are hiding extra information about the weapon in the level and * last_eat numbers for an object. Hopefully this won't break anything ?? * level == max improve last_eat == current improve */ static int improve_weapon (object *op, object *improver, object *weapon) { int sacrifice_count, sacrifice_needed = 0; if (improver->stats.sp == IMPROVE_PREPARE) return prepare_weapon (op, improver, weapon); if (weapon->level == 0) { op->failmsg ( "This weapon has not been prepared." " H"); return 0; } if (weapon->last_eat >= weapon->level // improvements used up || weapon->item_power >= 100) // or item_power >= arbitrary limit of 100 { op->failmsg ("This weapon cannot be improved any more."); return 0; } if (weapon->flag [FLAG_APPLIED] && !check_item_power (op, weapon->item_power + 1)) { op->failmsg ("Improving the weapon will make it too " "powerful for you to use. Unready it if you " "really want to improve it."); return 0; } /* This just increases damage by 5 points, no matter what. No sacrifice * is needed. Since stats.dam is now a 16 bit value and not 8 bit, * don't put any maximum value on damage - the limit is how much the * weapon can be improved. */ if (improver->stats.sp == IMPROVE_DAMAGE) { weapon->stats.dam += 5; weapon->weight += 5000; /* 5 KG's */ op->statusmsg (format ("Damage has been increased by 5 to %d.", weapon->stats.dam)); weapon->last_eat++; weapon->item_power++; improver->decrease (); return 1; } if (improver->stats.sp == IMPROVE_WEIGHT) { /* Reduce weight by 20% */ weapon->weight = (weapon->weight * 8) / 10; if (weapon->weight < 1) weapon->weight = 1; op->statusmsg (format ("Weapon weight reduced to %6.1fkg.", (float) weapon->weight / 1000.0)); weapon->last_eat++; weapon->item_power++; improver->decrease (); return 1; } if (improver->stats.sp == IMPROVE_ENCHANT) { weapon->magic++; weapon->last_eat++; op->statusmsg (format ("Weapon magic increased to %d.", weapon->magic)); improver->decrease (); weapon->item_power++; return 1; } sacrifice_needed = weapon->stats.Str + weapon->stats.Int + weapon->stats.Dex + weapon->stats.Pow + weapon->stats.Con + weapon->stats.Cha + weapon->stats.Wis; if (sacrifice_needed < 1) sacrifice_needed = 1; sacrifice_needed *= 2; sacrifice_count = check_sacrifice (op, improver); if (sacrifice_count < sacrifice_needed) { op->failmsgf ("You need at least %d %s.", sacrifice_needed, &improver->slaying); return 0; } eat_item (op, improver->slaying, sacrifice_needed); weapon->item_power++; switch (improver->stats.sp) { case IMPROVE_STR: return improve_weapon_stat (op, improver, weapon, weapon->stats.Str, 1, "strength"); case IMPROVE_DEX: return improve_weapon_stat (op, improver, weapon, weapon->stats.Dex, 1, "dexterity"); case IMPROVE_CON: return improve_weapon_stat (op, improver, weapon, weapon->stats.Con, 1, "constitution"); case IMPROVE_WIS: return improve_weapon_stat (op, improver, weapon, weapon->stats.Wis, 1, "wisdom"); case IMPROVE_CHA: return improve_weapon_stat (op, improver, weapon, weapon->stats.Cha, 1, "charisma"); case IMPROVE_INT: return improve_weapon_stat (op, improver, weapon, weapon->stats.Int, 1, "intelligence"); case IMPROVE_POW: return improve_weapon_stat (op, improver, weapon, weapon->stats.Pow, 1, "power"); default: op->failmsg ("Unknown improvement type."); } LOG (llevError, "improve_weapon: Got to end of function\n"); return 0; } /** * Handles the applying of improve/prepare/enchant weapon scroll. * Checks a few things (not on a non-magic square, marked weapon, ...), * then calls improve_weapon to do the dirty work. */ static int check_improve_weapon (object *op, object *tmp) { if (op->type != PLAYER) return 0; if (!op->flag [FLAG_WIZCAST] && (get_map_flags (op->map, NULL, op->x, op->y, NULL, NULL) & P_NO_MAGIC)) { op->failmsg ("Something blocks the magic of the scroll!"); return 0; } object *otmp = op->mark (); if (!otmp) { op->failmsg ("You need to mark a weapon object. H"); return 0; } if (otmp->type != WEAPON && otmp->type != BOW) { op->failmsg ("Marked item is not a weapon or bow!"); return 0; } if (!op->apply (otmp, AP_UNAPPLY)) { op->failmsg ("You are unable to take off your weapon to improve it!"); return 0; } op->statusmsg ("Applied weapon builder."); improve_weapon (op, tmp, otmp); esrv_send_item (op, otmp); return 1; } /** * This code deals with the armour improvment scrolls. * Change limits on improvement - let players go up to * +5 no matter what level, but they are limited by item * power. * Try to use same improvement code as in the common/treasure.c * file, so that if you make a +2 full helm, it will be just * the same as one you find in a shop. * * deprecated comment: * this code is by b.t. (thomas@nomad.astro.psu.edu) - * only 'enchantment' of armour is possible - improving * the stats of a player w/ armour as well as a weapon * will probably horribly unbalance the game. Magic enchanting * depends on the level of the character - ie the plus * value (magic) of the armour can never be increased beyond * the level of the character / 10 -- rounding upish, nor may * the armour value of the piece of equipment exceed either * the users level or 90) * Modified by MSW for partial resistance. Only support * changing of physical area right now. */ static int improve_armour (object *op, object *improver, object *armour) { if (armour->magic >= settings.armor_max_enchant) { op->failmsg ("This armour can not be enchanted any further!"); return 0; } /* Dealing with random artifact armor is a lot trickier (in terms of value, weight, * etc), so take the easy way out and don't worry about it. * Note - maybe add scrolls which make the random artifact versions (eg, armour * of gnarg and what not?) */ if (armour->title) { op->failmsg ("This armour will not accept further enchantment."); return 0; } /* Split objects if needed. Can't insert tmp until the * end of this function - otherwise it will just re-merge. */ object *tmp = armour->nrof > 1 ? armour->split (armour->nrof - 1) : 0; armour->magic++; if (!settings.armor_speed_linear) { int base = 100; int pow = 0; while (pow < armour->magic) { base = base - (base * settings.armor_speed_improvement) / 100; pow++; } ARMOUR_SPEED (armour) = (ARMOUR_SPEED (armour->arch) * base) / 100; } else ARMOUR_SPEED (armour) = (ARMOUR_SPEED (armour->arch) * (100 + armour->magic * settings.armor_speed_improvement)) / 100; if (!settings.armor_weight_linear) { int base = 100; int pow = 0; while (pow < armour->magic) { base = base - (base * settings.armor_weight_reduction) / 100; pow++; } armour->weight = (armour->arch->weight * base) / 100; } else armour->weight = (armour->arch->weight * (100 - armour->magic * settings.armor_weight_reduction)) / 100; if (armour->weight <= 0) { LOG (llevInfo, "Warning: enchanted armours can have negative weight\n."); armour->weight = 1; } armour->item_power = get_power_from_ench (armour->arch->item_power + armour->magic); if (op->type == PLAYER) { esrv_send_item (op, armour); if (armour->flag [FLAG_APPLIED]) op->update_stats (); } improver->decrease (); if (tmp) op->insert (tmp); return 1; } /* * convert_item() returns 1 if anything was converted, 0 if the item was not * what the converter wants, -1 if the converter is broken. * * Takes one type of items and makes another. * converter is the object that is doing the conversion. * item is the object that triggered the converter - if it is not * what the converter wants, this will not do anything. */ int convert_item (object *item, object *converter) { sint64 nr = 0, price_in; if (item->flag [FLAG_UNPAID]) return 0; shstr conv_from = converter->slaying; archetype *conv_to = converter->other_arch; sint64 need = converter->stats.food; sint64 give = converter->stats.sp; /* We make some assumptions - we assume if it takes money as it type, * it wants some amount. We don't make change (ie, if something costs * 3 gp and player drops a platinum, tough luck) */ if (conv_from == shstr_money) { if (item->type != MONEY) return 0; nr = sint64 (item->nrof) * item->value / need; if (!nr) return 0; converter->play_sound (sound_find ("shop_buy")); sint64 cost = (nr * need + item->value - 1) / item->value; item->decrease (cost); price_in = cost * item->value; } else { if (item->type == PLAYER || conv_from != item->arch->archname || (need && need > (uint16) item->nrof)) return 0; converter->play_sound (sound_find ("convert_item")); if (need) { nr = sint64 (item->nrof) / need; item->decrease (nr * need); price_in = nr * need * item->value; } else { price_in = item->value; item->destroy (); } } if (converter->inv) { object *ob; int i; object *ob_to_copy; /* select random object from inventory to copy */ ob_to_copy = converter->inv; for (ob = converter->inv->below, i = 1; ob; ob = ob->below, i++) if (rndm (0, i) == 0) ob_to_copy = ob; item = ob_to_copy->deep_clone (); item->clr_flag (FLAG_IS_A_TEMPLATE); unflag_inv (item, FLAG_IS_A_TEMPLATE); } else { if (!conv_to) { LOG (llevError, "move_creator: Converter doesn't have other arch set: %s (%s, %d, %d)\n", &converter->name, &converter->map->path, converter->x, converter->y); return -1; } item = object_create_arch (conv_to); fix_generated_item (item, converter, 0, 0, GT_MINIMAL); } if (give) item->nrof = give; if (nr) item->nrof *= nr; if (converter->flag [FLAG_PRECIOUS]) item->set_flag (FLAG_UNPAID); if (converter->is_in_shop ()) { // converters on shop floors don't work anymore, bug lets check for it // and report in case someone still does it. LOG (llevDebug, "ITEMBUG: broken converter, converters on shop floor don't work: %s\n", converter->debug_desc ()); item->set_flag (FLAG_UNPAID); } else if (price_in < sint64 (item->nrof) * item->value) { LOG (llevDebug, "converter output price higher than input: %s at %s (%d, %d) in value %d, out value %d for %s\n", &converter->name, &converter->map->path, converter->x, converter->y, price_in, item->nrof * item->value, &item->name); /** * elmex: we are going to let the game continue, as the mapcreator * hopefully had something in mind when doing this. */ } // elmex: only identify if we need to, for example so that generated money doesn't // get an 'identified' flag so easily. if (item->need_identify ()) identify (item); insert_ob_in_map_at (item, converter->map, converter, 0, converter->x, converter->y); return 1; } /** * Handle apply on containers. * By Eneq(@csd.uu.se). * Moved to own function and added many features [Tero.Haatanen@lut.fi] * added the alchemical cauldron to the code -b.t. */ static int apply_container (object *op, object *sack) { if (op->type != PLAYER || !op->contr->ns) return 0; /* This might change */ if (!sack || sack->type != CONTAINER) { LOG (llevError, "apply_container: %s is not container!\n", sack ? &sack->name : "[nullobject]"); return 0; } op->contr->last_used = 0; if (sack->env && sack->env != op) { op->failmsg ("You must put it onto the floor or into your inventory first."); return 1; } // already applied == open on ground, or open in inv, or active in inv if (sack->flag [FLAG_APPLIED]) { if (op->container_ () == sack) { // open on ground or inv, so close op->close_container (); return 1; } else if (!sack->env) { // active, but not ours: some other player has opened it op->failmsgf ("Somebody else is using the %s already.", query_name (sack)); return 1; } // fall through to opening it (active in inv) } else if (sack->env) { // it is in our env, so activate it, do not open yet op->close_container (); sack->flag [FLAG_APPLIED] = 1; esrv_update_item (UPD_FLAGS, op, sack); op->statusmsg (format ("You ready %s.", query_name (sack))); return 1; } // it's locked? if (sack->slaying) { if (object *tmp = find_key (op, op, sack)) op->statusmsg (format ("You unlock %s with %s.", query_name (sack), query_name (tmp))); else { op->statusmsg (format ("You don't have the key to unlock %s.", query_name (sack))); return 1; } } op->open_container (sack); return 1; } /** * Handles dropping things on altar. * Returns true if sacrifice was accepted. */ static int apply_altar (object *altar, object *sacrifice, object *originator) { /* Only players can make sacrifices on spell casting altars. */ if (altar->inv && (!originator || originator->type != PLAYER)) return 0; if (operate_altar (altar, &sacrifice, originator)) { /* Simple check. Unfortunately, it means you can't cast magic bullet * with an altar. We call it a Potion - altars are stationary - it * is up to map designers to use them properly. */ if (altar->inv && altar->inv->type == SPELL) { originator->statusmsg (format ("The altar casts %s.", &altar->inv->name)); cast_spell (originator, altar, 0, altar->inv, NULL); /* If it is connected, push the button. Fixes some problems with * old maps. */ /* push_button (altar);*/ } else { altar->value = 1; /* works only once */ push_button (altar, originator); } return !sacrifice; } else return 0; } /** * Handles 'movement' of shop mats. * Returns 1 if 'op' was destroyed, 0 if not. * Largely re-written to not use nearly as many gotos, plus * some of this code just looked plain out of date. * MSW 2001-08-29 */ int apply_shop_mat (object *shop_mat, object *op) { int rv = 0; double opinion; object *tmp, *next; op->set_flag (FLAG_NO_APPLY); /* prevent loops */ bool has_unpaid = false; // quite inefficient to do this here twice, but the api doesn't lend itself to // a quick and small change :( for (object::depth_iterator item = op->begin (); item != op->end (); ++item) if (item->flag [FLAG_UNPAID]) { has_unpaid = true; break; } if (!op->is_player ()) { /* Remove all the unpaid objects that may be carried here. * This could be pets or monsters that are somehow in * the shop. */ for (tmp = op->inv; tmp; tmp = next) { next = tmp->below; if (tmp->flag [FLAG_UNPAID]) { int i = find_free_spot (tmp, op->map, op->x, op->y, 1, 9); if (i >= 0) tmp->move (i); } } /* Don't teleport things like spell effects */ if (op->flag [FLAG_NO_PICK]) return 0; /* unpaid objects, or non living objects, can't transfer by * shop mats. Instead, put it on a nearby space. */ if (op->flag [FLAG_UNPAID] || !op->flag [FLAG_ALIVE]) { /* Somebody dropped an unpaid item, just move to an adjacent place. */ int i = find_free_spot (op, op->map, op->x, op->y, 1, 9); if (i != -1) rv = transfer_ob (op, op->x + freearr_x[i], op->y + freearr_y[i], 0, shop_mat); return 0; } /* Removed code that checked for multipart objects - it appears that * the teleport function should be able to handle this just fine. */ rv = teleport (shop_mat, SHOP_MAT, op); } else if (can_pay (op) && get_payment (op)) { /* this is only used for players */ rv = teleport (shop_mat, SHOP_MAT, op); if (has_unpaid) op->contr->play_sound (sound_find ("shop_buy")); else if (op->is_in_shop ()) op->contr->play_sound (sound_find ("shop_enter")); else op->contr->play_sound (sound_find ("shop_leave")); if (shop_mat->msg) op->statusmsg (shop_mat->msg); /* This check below is a bit simplistic - generally it should be correct, * but there is never a guarantee that the bottom space on the map is * actually the shop floor. */ else if (!rv && !op->is_in_shop ()) { opinion = shopkeeper_approval (op->map, op); op->statusmsg ( opinion >= 0.90 ? "The shopkeeper gives you a friendly wave." : opinion >= 0.75 ? "The shopkeeper waves to you." : opinion >= 0.50 ? "The shopkeeper ignores you." : "The shopkeeper glares at you with contempt." ); } } else { /* if we get here, a player tried to leave a shop but was not able * to afford the items he has. We try to move the player so that * they are not on the mat anymore */ int i = find_free_spot (op, op->map, op->x, op->y, 1, 9); if (i == -1) LOG (llevError, "Internal shop-mat problem.\n"); else { op->remove (); op->x += freearr_x[i]; op->y += freearr_y[i]; rv = insert_ob_in_map (op, op->map, shop_mat, 0) == NULL; } } op->clr_flag (FLAG_NO_APPLY); return rv; } /** * Handles applying a sign. */ static void apply_sign (object *op, object *sign, int autoapply) { if (!op->is_player()) return; if (sign->has_dialogue ()) { op->statusmsg (format ("Maybe you should I to the %s instead?", &sign->name)); return; } if (!sign->msg) { op->contr->infobox (MSG_CHANNEL ("examine"), format ("T<%s>\n\n Nothing %sis written on it.", &sign->name, sign->name == sign->arch->name ? "" : "else ")); return; } if (sign->stats.food) { if (sign->last_eat >= sign->stats.food) { if (!sign->move_on) op->failmsg ("You cannot read it anymore."); return; } if (!op->flag [FLAG_WIZPASS]) sign->last_eat++; } /* Sign or magic mouth? Do we need to see it, or does it talk to us? * No way to know for sure. The presumption is basically that if * move_on is zero, it needs to be manually applied (doesn't talk * to us). */ if (op->flag [FLAG_BLIND] && !op->flag [FLAG_WIZ] && !sign->move_on) { op->failmsg ("You are unable to read while blind!"); return; } if (op->contr) if (client *ns = op->contr->ns) { if (sign->sound) ns->play_sound (sign->sound); else if (autoapply) ns->play_sound (sound_find ("msg_voice")); op->contr->infobox (MSG_CHANNEL ("examine"), format ("T<%s>\n\n%s", &sign->name, &sign->msg)); } } static void move_apply_hole (object *trap, object *victim) { /* Hole not open? */ if (trap->stats.wc > 0) return; /* Is this a multipart monster and not the head? If so, return. * Processing will happen if the head runs into the pit */ if (victim->head) return; // now find all possible locations and randomly pick one int dir = find_free_spot (victim, trap->map, EXIT_X (trap), EXIT_Y (trap), 0, trap->range >= 3 ? SIZEOFFREE3 + 1 : trap->range >= 2 ? SIZEOFFREE2 + 1 : trap->range >= 1 ? SIZEOFFREE1 + 1 : SIZEOFFREE0 + 1); if (dir < 0) return; victim->play_sound (trap->sound ? trap->sound : sound_find ("fall_hole")); victim->statusmsg ("You fall through the hole!", NDI_RED); transfer_ob (victim, EXIT_X (trap) + freearr_x[dir], EXIT_Y (trap) + freearr_y[dir], 0, victim); } /** * 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. */ static bool unapply_special (object *who, object *op, int aflags) { if (INVOKE_OBJECT (BE_UNREADY, op, ARG_OBJECT (who), ARG_INT (aflags)) || INVOKE_OBJECT (UNREADY, who, ARG_OBJECT (op), ARG_INT (aflags))) return RESULT_INT (0); if (who->current_weapon == op) who->current_weapon = 0; op->flag [FLAG_APPLIED] = false; switch (op->type) { case SKILL: if (player *pl = who->contr) if (op->invisible) pl->statusmsg (format ("You can no longer use the %s skill.", &op->skill)); else pl->statusmsg (format ("You stop using the %s.", query_name (op))); change_abil (who, op); who->flag [FLAG_READY_SKILL] = false; break; case WEAPON: who->statusmsg (format ("You unwield %s.", query_name (op))); change_abil (who, op); who->flag [FLAG_READY_WEAPON] = false; // unapplying a weapon or skill tool should also unapply the skill it governs // but this is hard, as it shouldn't do so when the skill can // be used for other reasons if (who->chosen_skill) if (!who->chosen_skill->flag [FLAG_CAN_USE_SKILL]) unapply_special (who, op, 0); break; case BOW: case WAND: case ROD: case HORN: case RANGED: if (player *pl = who->contr) { who->statusmsg (format ("You unready %s.", query_name (op))); change_abil (who, op); } else { if (op->type == BOW) op->flag [FLAG_READY_BOW ] = false; else op->flag [FLAG_READY_RANGE] = false; } break; case ARMOUR: case HELMET: case SHIELD: case RING: case BOOTS: case GLOVES: case AMULET: case GIRDLE: case BRACERS: case CLOAK: who->statusmsg (format ("You unwear %s.", query_name (op))); change_abil (who, op); break; case SPELL: case BUILDER: who->statusmsg (format ("You unready %s.", query_name (op))); break; //case SKILL_TOOL://TODO default: who->statusmsg (format ("You unapply %s.", query_name (op))); break; } if (aflags & AP_NO_MERGE || !merge_ob (op, 0)) if (object *pl = op->visible_to ()) esrv_send_item (pl, op); who->update_stats (); return 1; } /** * Returns the object that is using 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_next_item_from_body_location(who->inv, 1); * if (tmp) tmp1 = get_next_item_from_body_location(tmp->below, 1); * to find the second object that may use this location, etc. * Returns NULL if no match is found. * loc is the index into the array we are looking for a match. * don't return invisible objects unless they are skill objects * invisible other objects that use * up body locations can be used as restrictions. */ static object * get_next_item_from_body_location (int loc, object *start) { for (object *tmp = start; tmp; tmp = tmp->below) if (tmp->flag [FLAG_APPLIED] && tmp->slot [loc].info && (!tmp->invisible || tmp->type == SKILL || tmp->type == SPELL)) return tmp; return 0; } /** * 'op' wants to apply an object, but can't because of other equipment. * 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 is matched is used. * Returns 0 on success, returns 1 if there is some problem. * if aflags is AP_PRINT, 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. */ #define CANNOT_REMOVE_CURSED \ "H" static bool unapply_for_ob (object *who, object *op, int aflags) { if (op->is_range ()) for (object *tmp = who->inv; tmp; tmp = tmp->below) if (tmp->flag [FLAG_APPLIED] && tmp->is_range ()) if ((aflags & AP_IGNORE_CURSE) || (aflags & AP_PRINT) || (!tmp->flag [FLAG_CURSED] && !tmp->flag [FLAG_DAMNED])) { if (aflags & AP_PRINT) who->failmsg (query_name (tmp)); 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. */ who->failmsgf ("No matter how hard you try, you just can't remove the %s." CANNOT_REMOVE_CURSED, query_name (tmp)); return 1; } for (int i = 0; i < NUM_BODY_LOCATIONS; i++) { /* this used up a slot that we need to free */ if (op->slot[i].info) { object *last = who->inv; /* We do a while loop - may need to remove several items in order * to free up enough slots. */ while ((who->slot[i].used + op->slot[i].info) < 0) { object *tmp = get_next_item_from_body_location (i, last); if (!tmp) { #if 0 /* Not a bug - we'll get this if the player has cursed items * equipped. */ LOG (llevError, "Can't find object using location %d (%s) on %s\n", i, body_locations[i].save_name, who->name); #endif return 1; } /* If we are just printing, we don't care about cursed status */ if ((aflags & AP_IGNORE_CURSE) || (aflags & AP_PRINT) || (!(tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]))) { if (aflags & AP_PRINT) who->failmsg (query_name (tmp)); 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.) */ who->failmsgf ("The %s just won't come off." CANNOT_REMOVE_CURSED, query_name (tmp)); } 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'. * Returns 0 if apply can be done without anything special. * Otherwise returns a bitmask - 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 care 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) { if (INVOKE_OBJECT (CAN_BE_APPLIED, op, ARG_OBJECT (who)) || INVOKE_OBJECT (CAN_APPLY, who, ARG_OBJECT (op))) return RESULT_INT (0); int retval = 0; object *tmp = 0, *ws = 0; for (int i = 0; i < NUM_BODY_LOCATIONS; i++) { if (op->slot[i].info) { /* Item uses more slots than we have */ if (who->slot[i].info + op->slot [i].info < 0) { /* Could return now for efficiency - rest of info below isn't * really needed. */ retval |= CAN_APPLY_NEVER; } else if (who->slot[i].used + op->slot[i].info < 0) { /* in this case, equipping this would use more free spots than * we have. */ /* if we have an applied weapon/shield, and unapply it would free * enough slots to equip the new item, then just set "can * apply unapply". We don't care about the logic below - if you have a * 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->slot[i].used - ws->slot[i].info + op->slot[i].info) >= 0) { retval |= CAN_APPLY_UNAPPLY; continue; } object *tmp1 = get_next_item_from_body_location (i, who->inv); 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 it the new item doesn't need all * the slots, the player then has a choice. */ if ((who->slot[i].used - tmp1->slot[i].info != who->slot[i].info) && abs (op->slot[i].info) < who->slot[i].info) retval |= CAN_APPLY_UNAPPLY_CHOICE; /* Does unequippint 'tmp1' free up enough slots for this to be * equipped? If not, there must be something else to unapply. */ if (who->slot[i].used + op->slot[i].info < tmp1->slot[i].info) retval |= CAN_APPLY_UNAPPLY_MULT; } } /* if not enough free slots */ } /* if this object uses location i */ } /* for i -> num_body_locations loop */ /* Note that we don't check for FLAG_USE_ARMOUR - that should * really be controlled by use of body locations. We do have * the weapon/shield checks, and the range checks for monsters, * because you can't control those just by body location - bows, shields, * and weapons all use the same slot. Similar for horn/rod/wand - they * all use the same location. */ if (op->type == WEAPON && !who->flag [FLAG_USE_WEAPON]) retval |= CAN_APPLY_RESTRICTION; if (op->type == SHIELD && !who->flag [FLAG_USE_SHIELD]) retval |= CAN_APPLY_RESTRICTION; if (who->type != PLAYER) { if ((op->type == WAND || op->type == HORN || op->type == ROD) && !who->flag [FLAG_USE_RANGE]) retval |= CAN_APPLY_RESTRICTION; if (op->type == BOW && !who->flag [FLAG_USE_BOW]) retval |= CAN_APPLY_RESTRICTION; if (op->type == RING && !who->flag [FLAG_USE_RING]) retval |= CAN_APPLY_RESTRICTION; if (op->type == BOW && !who->flag [FLAG_USE_BOW]) retval |= CAN_APPLY_RESTRICTION; } return retval; } /** * who is the object using the object. It can be a monster. * op is the object they are using. op is an equipment type item, * eg, one which you put on and keep on for a while, and not something * like a potion or scroll. * * function returns 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. * * aflags is special flags (0 - normal/toggle, AP_APPLY=always apply, * AP_UNAPPLY=always unapply). * * Optional flags: * AP_NO_MERGE: don't merge an unapplied object with other objects * AP_IGNORE_CURSE: unapply cursed items * AP_NO_READY: do not ready skills when applying skill tools * * Usage example: apply_special (who, op, AP_UNAPPLY | AP_IGNORE_CURSE) * * apply_special() doesn't check for unpaid items. */ #define LACK_ITEM_POWER \ " H" static bool apply_special (object *who, object *op, int aflags) { int basic_flag = aflags & AP_MODE; object *tmp, *tmp2, *skop = NULL; if (who == NULL) { LOG (llevError, "apply_special() from object without environment.\n"); return 1; } //TODO: remove these when apply_special is no longer exposed if (op->env != who) return 1; /* op is not in inventory */ /* trying to unequip op */ if (op->flag [FLAG_APPLIED]) { /* always apply, so no reason to unapply */ if (basic_flag == AP_APPLY) return 0; if (!(aflags & AP_IGNORE_CURSE) && (op->flag [FLAG_CURSED] || op->flag [FLAG_DAMNED])) { who->failmsgf ("No matter how hard you try, you just can't remove %s." CANNOT_REMOVE_CURSED, query_name (op)); return 1; } return unapply_special (who, op, aflags); } else if (basic_flag == AP_UNAPPLY) return 0; splay (op); /* Can't just apply this object. Lets see what not and what to do */ if (int i = can_apply_object (who, op)) { if (i & CAN_APPLY_NEVER) { who->failmsgf ("You don't have the body to use a %s. H", query_name (op)); return 1; } else if (i & CAN_APPLY_RESTRICTION) { who->failmsgf ( "You have a prohibition against using a %s. " "H", query_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)) { who->failmsg ("You need to unapply some of the following item(s) or change your applymode:"); unapply_for_ob (who, op, AP_PRINT); return 1; } else if (who->contr->unapply == unapply_always || !(i & CAN_APPLY_UNAPPLY_CHOICE)) if (unapply_for_ob (who, op, aflags)) return 1; } } if (op->skill && op->type != SKILL && op->type != SKILL_TOOL) { // try to ready attached skill first skop = find_skill_by_name (who, op->skill); if (!skop) { who->failmsgf ("You need the %s skill to use this item!", &op->skill); return 1; } else if (!who->apply (skop, AP_APPLY | AP_NO_SLOT)) { who->failmsgf ("You can't use the required %s skill!", &op->skill); return 1; } } if (!check_item_power (who, op->item_power)) { who->failmsg ("Equipping that combined with other items would consume your soul!" LACK_ITEM_POWER); return 1; } /* 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. */ // split away all the other items from the stack, so only one item is left tmp = op->nrof > 1 ? op->split (op->nrof - 1) : 0; if (INVOKE_OBJECT (BE_READY, op, ARG_OBJECT (who)) || INVOKE_OBJECT (READY, who, ARG_OBJECT (op))) return RESULT_INT (0); switch (op->type) { case WEAPON: if (op->level && (!op->name.starts_with (who->name) || op->name [who->name.length ()] != '\'')) { /* 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) */ who->failmsg ("The weapon does not recognize you as its owner. " "H"); if (tmp) who->insert (tmp); return 1; } op->flag [FLAG_APPLIED] = true; if (player *pl = who->contr) { who->statusmsg (format ("You wield %s.", query_name (op))); change_abil (who, op); } op->flag [FLAG_READY_WEAPON] = true; break; case ARMOUR: case HELMET: case SHIELD: case BOOTS: case GLOVES: case GIRDLE: case BRACERS: case CLOAK: case RING: case AMULET: op->set_flag (FLAG_APPLIED); who->statusmsg (format ("You wear %s.", query_name (op))); change_abil (who, op); break; case SKILL_TOOL: // applying a skill tool does not ready the skill // if something needs the skill, it has to ready it itself //TODO: unapplying should unapply the skill, though op->set_flag (FLAG_APPLIED); break; case SKILL: if (!(aflags & AP_NO_SLOT)) { // skill is used on it's own, as opposed to being a chosen_skill if (skill_flags [op->subtype] & (SF_NEED_ITEM | SF_MANA)) { who->failmsgf ( "You feel as if you wanted to do something funny, but you can't remember what. " "H", &op->skill ); if (tmp) who->insert (tmp); return 1; } if (skill_flags [op->subtype] & SF_AUTARK || !(skill_flags [op->subtype] & (SF_COMBAT | SF_RANGED))) { if (skill_flags [op->subtype] & SF_USE) who->failmsgf ( "You feel as if you wanted to do something funny, but you can't remember what. " "H.>", &op->skill, &op->skill ); else who->failmsgf ( "You feel as if you wanted to do something funny, but you can't remember what. " "H", &op->skill ); if (tmp) who->insert (tmp); return 1; } if (who->contr) if (op->invisible) who->statusmsg (format ("You can now use the %s skill.", &op->skill)); else who->statusmsg (format ("You ready %s.", query_name (op))); } who->set_flag (FLAG_READY_SKILL); op->set_flag (FLAG_APPLIED); change_abil (who, op); break; case BOW: if (op->level && (!op->name.starts_with (who->name) || op->name [who->name.length ()] != '\'')) { who->failmsg ("The weapon does not recognize you as its owner. " "H"); if (tmp) who->insert (tmp); return 1; } if (player *pl = who->contr) { op->flag [FLAG_APPLIED] = true; who->statusmsg (format ("You wield the %s.", query_name (op))); change_abil (who, op); } break; case RANGED: if (player *pl = who->contr) { op->flag [FLAG_APPLIED] = true; who->statusmsg (format ("You applied the %s.", query_name (op))); } break; case SPELL: if (player *pl = who->contr) { op->flag [FLAG_APPLIED] = true; who->statusmsg (format ("You ready the spell %s.", query_name (op))); } break; /*FALLTHROUGH*/ case WAND: case ROD: case HORN: op->flag [FLAG_APPLIED] = true; if (player *pl = who->contr) { who->statusmsg (format ("You ready %s.", query_name (op))); if (op->type == BOW) who->statusmsg (format ("You will now fire %s with %s.", op->race ? &op->race : "nothing", query_name (op))); change_abil (who, op); } else { if (op->type == BOW) op->flag [FLAG_READY_BOW ] = true; else op->flag [FLAG_READY_RANGE] = true; } break; case BUILDER: if (player *pl = who->contr) { who->statusmsg (format ("You ready your %s.", query_name (op))); //TODO: change_abil? } break; default: who->statusmsg (format ("You apply %s.", query_name (op))); } op->set_flag (FLAG_APPLIED); if (tmp) who->insert (tmp); who->update_stats (); /* 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) op->set_flag (FLAG_BEEN_APPLIED); if (op->flag [FLAG_CURSED] || op->flag [FLAG_DAMNED]) if (who->type == PLAYER) { who->failmsg ( "Oops, it feels deadly cold! " "H" ); op->set_flag (FLAG_KNOWN_CURSED); } if (object *pl = op->visible_to ()) esrv_send_item (pl, op); return 0; } /** * Check if op should abort moving victim because of it's race or slaying. * Returns 1 if it should abort, returns 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->archname) || op->race != victim->arch->archname)) && ((!(victim->name && name_flag) || op->race != victim->name)) && ((!(victim->race && race_flag) || 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->archname && op->slaying == victim->arch->archname))) || ((victim->name && name_flag && op->slaying == victim->name)) || ((victim->race && race_flag && op->slaying == victim->race))) return 1; return 0; } /** * This handles a player dropping money on an altar to identify stuff. * It'll identify marked item, if none all items up to dropped money. * Return value: 1 if money was destroyed, 0 if not. */ static int apply_id_altar (object *money, object *altar, object *pl) { dynbuf_text &buf = msg_dynbuf; buf.clear (); if (!pl || pl->type != PLAYER) return 0; /* Check for MONEY type is a special hack - it prevents 'nothing needs * identifying' from being printed out more than it needs to be. */ if (!check_altar_sacrifice (altar, money, pl) || money->type != MONEY) return 0; /* if the player has a marked item, identify that if it needs to be * identified. If it doesn't, then go through the player inventory. */ if (object *marked = pl->mark ()) if (!marked->flag [FLAG_IDENTIFIED] && marked->need_identify ()) { if (operate_altar (altar, &money, pl)) { identify (marked); buf.printf ("You have %s.\r", long_desc (marked, pl)); if (marked->msg) buf << "The item has a story:\r" << marked->msg << "\n\n"; return !money; } } for (object *id = pl->inv; id; id = id->below) { if (!id->flag [FLAG_IDENTIFIED] && !id->invisible && id->need_identify ()) { if (operate_altar (altar, &money, pl)) { identify (id); buf.printf ("You have %s.\r", long_desc (id, pl)); if (id->msg) buf << "The item has a story:\r" << id->msg << "\n\n"; /* If no more money, might as well quit now */ if (!money || !check_altar_sacrifice (altar, money)) break; } else { LOG (llevError, "check_id_altar: Couldn't do sacrifice when we should have been able to\n"); break; } } } if (buf.empty ()) pl->failmsg ("You have nothing that needs identifying"); else pl->contr->infobox (MSG_CHANNEL ("identify"), buf); return !money; } /** * This checks whether the object has a "on_use_yield" field, and if so generated and drops * matching item. **/ void handle_apply_yield (object *tmp) { if (shstr_tmp yield = tmp->kv (shstr_on_use_yield)) archetype::get (yield)->insert_at (tmp, tmp, INS_BELOW_ORIGINATOR); } /** * Handles applying a potion. */ int apply_potion (object *op, object *tmp) { int got_one = 0, i; object *force = 0; object *floor = GET_MAP_OB (op->map, op->x, op->y); if (get_map_flags (op->map, NULL, op->x, op->y, NULL, NULL) & P_SAFE) { op->failmsg ("Gods prevent you from using this here, it's sacred ground!"); tmp->clr_flag (FLAG_APPLIED); return 0; } if (op->type == PLAYER) if (!tmp->flag [FLAG_IDENTIFIED]) identify (tmp); handle_apply_yield (tmp); /* Potion of restoration - only for players */ if (op->type == PLAYER && (tmp->attacktype & AT_DEPLETE)) { object *depl; archetype *at; if (tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]) { op->drain_stat (); op->update_stats (); tmp->decrease (); return 1; } if (!(at = archetype::find (shstr_depletion))) { LOG (llevError, "Could not find archetype depletion\n"); return 0; } depl = present_arch_in_ob (at, op); if (depl) { for (i = 0; i < NUM_STATS; i++) if (depl->stats.stat (i)) op->statusmsg (restore_msg[i]); depl->destroy (); op->update_stats (); } else op->statusmsg ("Your potion had no effect."); tmp->decrease (); return 1; } /* improvement potion - only for players */ if (op->type == PLAYER && (tmp->attacktype & AT_GODPOWER)) { for (i = 1; i < min (11, op->level); i++) { if (tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]) { if (op->contr->levhp[i] != 1) { op->contr->levhp[i] = 1; break; } if (op->contr->levsp[i] != 1) { op->contr->levsp[i] = 1; break; } if (op->contr->levgrace[i] != 1) { op->contr->levgrace[i] = 1; break; } } else { if (op->contr->levhp[i] < 9) { op->contr->levhp[i] = 9; break; } if (op->contr->levsp[i] < 6) { op->contr->levsp[i] = 6; break; } if (op->contr->levgrace[i] < 3) { op->contr->levgrace[i] = 3; break; } } } /* Just makes checking easier */ if (i < min (11, op->level)) got_one = 1; if (!tmp->flag [FLAG_CURSED] && !tmp->flag [FLAG_DAMNED]) { if (got_one) { op->update_stats (); op->statusmsg ("The Gods smile upon you and remake you " "a little more in their image. " "You feel a little more perfect.", NDI_GREEN); } else op->statusmsg ("The potion had no effect - you are already perfect."); } else { /* cursed potion */ if (got_one) { op->update_stats (); op->failmsg ("The Gods are angry and punish you."); } else op->statusmsg ("You are fortunate that you are so pathetic.", NDI_DK_ORANGE); } tmp->decrease (); return 1; } /* A potion that casts a spell. Healing, restore spellpoint (power potion) * and heroism all fit into this category. Given the spell object code, * there is no limit to the number of spells that potions can be cast, * but direction is problematic to try and imbue fireball potions for example. */ if (tmp->inv) { if (tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]) { op->failmsg ("Yech! Your lungs are on fire!"); create_exploding_ball_at (op, op->level); } else cast_spell (op, tmp, op->facing, tmp->inv, NULL); tmp->decrease (); /* if youre dead, no point in doing this... */ if (!op->flag [FLAG_REMOVED]) op->update_stats (); return 1; } /* Deal with protection potions */ force = NULL; for (i = 0; i < NROFATTACKS; i++) { if (tmp->resist[i]) { if (!force) force = get_archetype (FORCE_NAME); memcpy (force->resist, tmp->resist, sizeof (tmp->resist)); force->type = POTION_EFFECT; break; /* Only need to find one protection since we copy entire batch */ } } /* This is a protection potion */ if (force) { /* cursed items last longer */ if (tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]) { force->stats.food *= 10; for (i = 0; i < NROFATTACKS; i++) if (force->resist[i] > 0) force->resist[i] = -force->resist[i]; /* prot => vuln */ } force->speed_left = -1; force = insert_ob_in_ob (force, op); tmp->clr_flag (FLAG_APPLIED); force->set_flag (FLAG_APPLIED); change_abil (op, force); tmp->decrease (); return 1; } /* Only thing left are the stat potions */ if (op->type == PLAYER) { /* only for players */ if ((tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]) && tmp->value != 0) tmp->clr_flag (FLAG_APPLIED); else tmp->set_flag (FLAG_APPLIED); if (!change_abil (op, tmp)) op->statusmsg ("Nothing happened."); } /* CLEAR_FLAG is so that if the character has other potions * that were grouped with the one consumed, his * stat will not be raised by them. fix_player just clears * up all the stats. */ tmp->clr_flag (FLAG_APPLIED); op->update_stats (); tmp->decrease (); return 1; } /** * 'victim' moves onto 'trap' * 'victim' leaves 'trap' * effect is determined by move_on/move_off of trap and move_type of victime. * * originator: Player, monster or other object that caused 'victim' to move * onto 'trap'. Will receive messages caused by this action. May be NULL. * However, some types of traps require an originator to function. */ void move_apply (object *trap, object *victim, object *originator) { static int recursion_depth = 0; trap = trap->head_ (); /* Only exits affect DMs. */ if (victim->flag [FLAG_WIZPASS] && trap->type != EXIT && trap->type != SIGN) return; /* move_apply() is the most likely candidate for causing unwanted and * possibly unlimited recursion. */ /* The following was changed because it was causing perfectly correct * maps to fail. 1) it's not an error to recurse: * rune detonates, summoning monster. monster lands on nearby rune. * nearby rune detonates. This sort of recursion is expected and * proper. This code was causing needless crashes. */ if (recursion_depth >= 500) { LOG (llevDebug, "WARNING: move_apply(): aborting recursion " "[trap arch %s, name %s; victim arch %s, name %s]\n", &trap->arch->archname, &trap->name, &victim->arch->archname, &victim->name); return; } recursion_depth++; if (!INVOKE_OBJECT (MOVE_TRIGGER, trap, ARG_OBJECT (victim), ARG_OBJECT (originator))) switch (trap->type) { case PLAYERMOVER: if (trap->attacktype && (trap->level || victim->type != PLAYER) && !should_director_abort (trap, victim)) { if (!trap->stats.maxsp) trap->stats.maxsp = 2; /* Is this correct? From the docs, it doesn't look like it * should be divided by trap->speed */ victim->speed_left = -trap->stats.maxsp * victim->speed / trap->speed; /* Just put in some sanity check. I think there is a bug in the * above with some objects have zero speed, and thus the player * getting permanently paralyzed. */ victim->speed_left = max (-50.f, victim->speed_left); /* LOG(llevDebug, "apply, playermove, player speed_left=%f\n", victim->speed_left); */ } break; case SPINNER: if (victim->direction) { victim->direction = absdir (victim->direction - trap->stats.sp); update_turn_face (victim); } break; case DIRECTOR: if (victim->direction && !should_director_abort (trap, victim)) { victim->direction = trap->stats.sp; update_turn_face (victim); } break; case BUTTON: case PEDESTAL: case T_MATCH: update_button (trap, originator); break; case ALTAR: /* sacrifice victim on trap */ apply_altar (trap, victim, originator); break; case THROWN_OBJ: if (trap->inv == NULL) break; /* fallthrough */ case ARROW: /* bad bug: monster throw a object, make a step forwards, step on object , * trigger this here and get hit by own missile - and will be own enemy. * Victim then is his own enemy and will start to kill herself (this is * removed) but we have not synced victim and his missile. To avoid senseless * action, we avoid hits here */ if ((victim->flag [FLAG_ALIVE] && trap->has_active_speed ()) && trap->owner != victim) hit_with_arrow (trap, victim); break; case SPELL_EFFECT: apply_spell_effect (trap, victim); break; case TRAPDOOR: { int max, sound_was_played; object *ab, *ab_next; if (!trap->value) { int tot; for (ab = trap->above, tot = 0; ab; ab = ab->above) if ((ab->move_type && trap->move_on) || ab->move_type == 0) tot += ab->head_ ()->total_weight (); if (!(trap->value = (tot > trap->weight) ? 1 : 0)) break; SET_ANIMATION (trap, trap->value); update_object (trap, UP_OBJ_FACE); } for (ab = trap->above, max = 100, sound_was_played = 0; --max && ab; ab = ab_next) { /* need to set this up, since if we do transfer the object, * ab->above would be bogus */ ab_next = ab->above; if ((ab->move_type && trap->move_on) || ab->move_type == 0) { if (!sound_was_played) { trap->play_sound (trap->sound ? trap->sound : sound_find ("fall_hole")); sound_was_played = 1; } ab->statusmsg ("You fall into a trapdoor!", NDI_RED); transfer_ob (ab, EXIT_X (trap), EXIT_Y (trap), 0, ab); } } break; } case CONVERTER: if (convert_item (victim, trap) < 0) { originator->failmsgf ("The %s seems to be broken!", query_name (trap)); archetype::get (shstr_burnout)->insert_at (trap, trap); } break; case TRIGGER_BUTTON: case TRIGGER_PEDESTAL: case TRIGGER_ALTAR: check_trigger (trap, victim, originator); break; case DEEP_SWAMP: walk_on_deep_swamp (trap, victim); break; case CHECK_INV: check_inv (victim, trap); break; case HOLE: move_apply_hole (trap, victim); break; case EXIT: if (victim->type == PLAYER && EXIT_PATH (trap)) { /* Basically, don't show exits leading to random maps the * players output. */ if (trap->msg && EXIT_PATH (trap) != shstr_random_map_exit) victim->statusmsg (trap->msg, NDI_NAVY); trap->play_sound (trap->sound); victim->enter_exit (trap); } break; case ENCOUNTER: /* may be some leftovers on this */ break; case SHOP_MAT: apply_shop_mat (trap, victim); break; /* Drop a certain amount of gold, and have one item identified */ case IDENTIFY_ALTAR: apply_id_altar (victim, trap, originator); break; case SIGN: if (victim->type != PLAYER && trap->stats.food > 0) break; /* monsters musn't apply magic_mouths with counters */ apply_sign (victim, trap, 1); break; case CONTAINER: apply_container (victim, trap); break; case RUNE: case TRAP: if (trap->level && victim->flag [FLAG_ALIVE]) spring_trap (trap, victim); break; default: LOG (llevDebug, "name %s, arch %s, type %d with fly/walk on/off not " "handled in move_apply()\n", &trap->name, &trap->arch->archname, trap->type); break; } recursion_depth--; } /** * Handles reading a regular (ie not containing a spell) book. */ static void apply_book (object *op, object *tmp) { int lev_diff; object *skill_ob; if (op->flag [FLAG_BLIND] && !op->flag [FLAG_WIZ]) { op->failmsg ("You are unable to read while blind!"); return; } if (!tmp->msg) { op->failmsgf ("The %s contains nothing but meaningless gibberish. H", &tmp->name); return; } /* need a literacy skill to read stuff! */ skill_ob = find_skill_by_name (op, tmp->skill); if (!skill_ob) { op->failmsgf ("You are unable to decipher the strange symbols. H", &tmp->skill); return; } lev_diff = tmp->level - (skill_ob->level + 5); if (!op->flag [FLAG_WIZ] && lev_diff > 0) { op->failmsg (lev_diff < 2 ? "This book is just barely beyond your comprehension." : lev_diff < 3 ? "This book is slightly beyond your comprehension." : lev_diff < 5 ? "This book is beyond your comprehension." : lev_diff < 8 ? "This book is quite a bit beyond your comprehension." : lev_diff < 15 ? "This book is way beyond your comprehension." : "This book is totally beyond your comprehension."); return; } // we currently don't use the message types for anything. // readable_message_type *msgType = get_readable_message_type (tmp); tmp->play_sound (tmp->sound ? tmp->sound : sound_find ("apply_book")); if (player *pl = op->contr) if (client *ns = pl->ns) pl->infobox (MSG_CHANNEL ("book"), format ("T<%s>\n\n%s", (char *)long_desc (tmp, op), &tmp->msg)); /* gain xp from reading */ if (!tmp->flag [FLAG_NO_SKILL_IDENT]) { /* only if not read before */ int exp_gain = calc_skill_exp (op, tmp, skill_ob); if (!tmp->flag [FLAG_IDENTIFIED]) { /*exp_gain *= 2; because they just identified it too */ tmp->set_flag (FLAG_IDENTIFIED); if (object *pl = tmp->visible_to ()) esrv_update_item (UPD_FLAGS | UPD_NAME, pl, tmp); } change_exp (op, exp_gain, skill_ob->skill, 0); tmp->set_flag (FLAG_NO_SKILL_IDENT); /* so no more xp gained from this book */ } } /** * op made some mistake with a scroll, this takes care of punishment. * scroll_failure()- hacked directly from spell_failure */ static 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; op->failmsg ("Your spell warps!"); tmp = get_archetype (SPELL_WONDER); cast_wonder (op, op, 0, tmp); tmp->destroy (); } else if (failure <= -15 && failure > -35) { /* drain mana */ op->failmsg ("Your mana is drained!"); op->stats.sp -= random_roll (0, power - 1, op, PREFER_LOW); if (op->stats.sp < 0) op->stats.sp = 0; } else if (settings.spell_failure_effects == TRUE) { if (failure <= -35 && failure > -60) { /* confusion */ op->failmsg ("The magic recoils on you!"); confuse_player (op, op, power); } else if (failure <= -60 && failure > -70) { /* paralysis */ op->failmsg ("The magic recoils and paralyzes you!"); paralyze_player (op, op, power); } else if (failure <= -70 && failure > -80) { /* blind */ op->failmsg ("The magic recoils on you!"); blind_player (op, op, power); } else if (failure <= -80) { /* blast the immediate area */ object *tmp = get_archetype (LOOSE_MANA); cast_magic_storm (op, tmp, power); op->failmsg ("You unleash uncontrolled mana!"); tmp->destroy (); } } } /** * Handles the applying of a skill scroll, calling learn_skill straight. * op is the person learning the skill, tmp is the skill scroll object */ static void apply_skillscroll (object *op, object *tmp) { switch (learn_skill (op, tmp)) { case 0: op->play_sound (sound_find ("generic_fail")); op->failmsgf ("You already possess the knowledge held within the %s.", query_name (tmp)); break; case 1: tmp->decrease (); op->play_sound (sound_find ("skill_learn")); op->statusmsg (format ("You succeed in learning %s", &tmp->skill)); break; default: tmp->decrease (); op->play_sound (sound_find ("generic_fail")); op->failmsgf ("You fail to learn the knowledge of the %s.\n", query_name (tmp)); break; } } /** * Actually makes op learn spell. * Informs player of what happens. */ 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 && !tmp->flag [FLAG_STARTEQUIP]) { LOG (llevError, "BUG: do_learn_spell(): spell already known, but not marked as startequip\n"); return; } return; } op->contr->play_sound (sound_find ("learn_spell")); tmp = spell->clone (); insert_ob_in_ob (tmp, op); if (special_prayer) tmp->set_flag (FLAG_STARTEQUIP); esrv_add_spells (op->contr, tmp); } /** * Erases spell from player's inventory. */ 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; } op->failmsgf ("You lose knowledge of %s.", spell); esrv_remove_spell (op->contr, spob); spob->destroy (); } /** * Handles player applying a spellbook. * Checks whether player has knowledge of required skill, doesn't already know the spell, * stuff like that. Random learning failure too. */ static void apply_spellbook (object *op, object *tmp) { object *skop, *spell, *spell_skill; if (op->flag [FLAG_BLIND] && !op->flag [FLAG_WIZ]) { op->failmsg ("You are unable to read while blind."); return; } /* artifact_spellbooks have 'slaying' field point to a spell name, * instead of having their spell stored in stats.sp. These are * legacy spellbooks */ if (tmp->slaying) { spell = find_archetype_by_object_name (tmp->slaying)->instance (); if (!spell) { op->failmsgf ("The book's formula for %s is incomplete.", &tmp->slaying); return; } else insert_ob_in_ob (spell, tmp); tmp->slaying = 0; } skop = find_skill_by_name (op, tmp->skill); /* need a literacy skill to learn spells. Also, having a literacy level * lower than the spell will make learning the spell more difficult */ if (!skop) { op->failmsgf ("You can't read! Your attempt fails. H", &tmp->skill); return; } spell = tmp->inv; if (!spell) { LOG (llevError, "apply_spellbook: Book %s has no spell in it!\n", &tmp->name); op->failmsg ("The spellbook symbols make no sense. This is a bug, please report!"); return; } int learn_level = sqrtf (spell->level) * 1.5f; if (skop->level < learn_level) { op->failmsgf ("You are unable to decipher the strange symbols. H", &tmp->skill, learn_level); return; } op->statusmsg (format ("The spellbook contains the %s level spell %s.", ordinal (spell->level), &spell->name)); if (!tmp->flag [FLAG_IDENTIFIED]) identify (tmp); /* I removed the check for special_prayer_mark here - it didn't make * a lot of sense - special prayers are not found in spellbooks, and * if the player doesn't know the spell, doesn't make a lot of sense that * they would have a special prayer mark. */ if (check_spell_known (op, spell->name)) { op->statusmsg ("You already know that spell. H\n"); return; } if (spell->skill) { spell_skill = find_skill_by_name (op, spell->skill); if (!spell_skill) { op->failmsgf ("You lack the %s skill to use this spell.", &spell->skill); return; } if (spell_skill->level < spell->level) { op->failmsgf ("You need to be level %d in %s to learn this spell.", spell->level, &spell->skill); return; } } /* Logic as follows * * 1- MU spells use Int to learn, Cleric spells use Wisdom * * 2- The learner's skill level in literacy adjusts the chance to learn * a spell. * * 3 -Automatically fail to learn if you read while confused * * Overall, chances are the same but a player will find having a high * literacy rate very useful! -b.t. */ if (op->flag [FLAG_CONFUSED]) { op->failmsg ("In your confused state you flub the wording of the text!"); scroll_failure (op, 0 - random_roll (0, spell->level, op, PREFER_LOW), max (spell->stats.sp, spell->stats.grace)); } else if (tmp->flag [FLAG_STARTEQUIP] || (random_roll (0, 100, op, PREFER_LOW) - (5 * skop->level)) < learn_spell[spell->stats.grace ? op->stats.Wis : op->stats.Int]) { op->statusmsg ("You succeed in learning the spell!", NDI_GREEN); do_learn_spell (op, spell, 0); /* xp gain to literacy for spell learning */ if (!tmp->flag [FLAG_STARTEQUIP]) change_exp (op, calc_skill_exp (op, tmp, skop), skop->skill, 0); } else { op->contr->play_sound (sound_find ("fumble_spell")); op->failmsg ("You fail to learn the spell. H\n"); } tmp->decrease (); } /** * Handles applying a spell scroll. */ void apply_scroll (object *op, object *tmp, int dir) { object *skop; if (op->flag [FLAG_BLIND] && !op->flag [FLAG_WIZ]) { op->failmsg ("You are unable to read while blind."); return; } if (!tmp->inv || tmp->inv->type != SPELL) { op->failmsg ("The scroll just doesn't make sense! H<...and never will make sense.>"); return; } if (op->type == PLAYER) { /* players need a literacy skill to read stuff! */ int exp_gain = 0; /* hard code literacy - tmp->skill points to where the exp * should go for anything killed by the spell. */ skop = find_skill_by_name (op, shstr_literacy); if (!skop) { op->failmsgf ("You are unable to decipher the strange symbols. H"); return; } if ((exp_gain = calc_skill_exp (op, tmp, skop))) change_exp (op, exp_gain, skop->skill, 0); } if (!tmp->flag [FLAG_IDENTIFIED]) identify (tmp); op->statusmsg (format ("The scroll of %s turns to dust.", &tmp->inv->name)); cast_spell (op, tmp, dir, tmp->inv, NULL); tmp->decrease (); } /** * Applies a treasure object - by default, chest. op * is the person doing the applying, tmp is the treasure * chest. */ static void apply_treasure (object *op, object *tmp) { /* Nice side effect of this treasure creation method is that the treasure * for the chest is done when the chest is created, and put into the chest * inventory. So that when the chest burns up, the items still exist. Also * prevents people from moving chests to more difficult maps to get better * treasure */ object *treas = tmp->inv; if (!treas) { op->statusmsg ("The chest was empty."); tmp->decrease (); return; } while (tmp->inv) { treas = tmp->inv; treas->remove (); treas->x = op->x; treas->y = op->y; treas = insert_ob_in_map (treas, op->map, op, INS_BELOW_ORIGINATOR); if (treas && (treas->type == RUNE || treas->type == TRAP) && treas->level && op->flag [FLAG_ALIVE]) spring_trap (treas, op); /* If either player or container was destroyed, no need to do * further processing. I think this should be enclused with * spring trap above, as I don't think there is otherwise * any way for the treasure chest or player to get killed. */ if (op->destroyed () || tmp->destroyed ()) break; } if (!tmp->destroyed () && !tmp->inv) tmp->decrease (true); } /** * A dragon is eating some flesh. If the flesh contains resistances, * there is a chance for the dragon's skin to get improved. * * attributes: * object *op the object (dragon player) eating the flesh * object *meal the flesh item, getting chewed in dragon's mouth * return: * int 1 if eating successful, 0 if it doesn't work */ static int dragon_eat_flesh (object *op, object *meal) { object *skin = NULL; /* pointer to dragon skin force */ object *abil = NULL; /* pointer to dragon ability force */ object *tmp = NULL; /* tmp. object */ double chance; /* improvement-chance of one resistance type */ double totalchance = 1; /* total chance of gaining one resistance */ double bonus = 0; /* level bonus (improvement is easier at lowlevel) */ double mbonus = 0; /* monster bonus */ int atnr_winner[NROFATTACKS]; /* winning candidates for resistance improvement */ int winners = 0; /* number of winners */ int i; /* index */ /* let's make sure and doublecheck the parameters */ if (meal->type != FLESH || !op->is_dragon ()) return 0; /* now grab the 'dragon_skin'- and 'dragon_ability'-forces from the player's inventory */ for (tmp = op->inv; tmp; tmp = tmp->below) if (tmp->type == FORCE) if (tmp->arch->archname == shstr_dragon_skin_force) skin = tmp; else if (tmp->arch->archname == shstr_dragon_ability_force) abil = tmp; /* if either skin or ability are missing, this is an old player which is not to be considered a dragon -> bail out */ if (skin == NULL || abil == NULL) return 0; /* now start by filling stomache and health, according to food-value */ if ((MAX_FOOD - op->stats.food) < meal->stats.food) op->stats.hp += (MAX_FOOD - op->stats.food) / 50; else op->stats.hp += meal->stats.food / 50; min_it (op->stats.hp, op->stats.maxhp); op->stats.food = min (MAX_FOOD, op->stats.food + meal->stats.food); /*LOG(llevDebug, "-> player: %d, flesh: %d\n", op->level, meal->level); */ /* on to the interesting part: chances for adding resistance */ for (i = 0; i < NROFATTACKS; i++) { if (meal->resist[i] > 0 && atnr_is_dragon_enabled (i)) { /* got positive resistance, now calculate improvement chance (0-100) */ /* this bonus makes resistance increase easier at lower levels */ bonus = (settings.max_level - op->level) * 30. / ((double) settings.max_level); if (i == abil->stats.exp) bonus += 5; /* additional bonus for resistance of ability-focus */ /* monster bonus increases with level, because high-level flesh is too rare */ mbonus = op->level * 20. / ((double) settings.max_level); chance = (((double)min (op->level + bonus, meal->level + bonus + mbonus)) * 100. / ((double)settings.max_level)) - skin->resist[i]; if (chance >= 0.) chance += 1.; else chance = (chance < -12) ? 0. : 1. / pow (2., -chance); /* chance is proportional to amount of resistance (max. 50) */ chance *= ((double)(min (meal->resist[i], 50))) / 50.; /* doubled chance for resistance of ability-focus */ if (i == abil->stats.exp) chance = min (100., chance * 2.); /* now make the throw and save all winners (Don't insert luck bonus here!) */ if (rndm (10000) < (unsigned int)(chance * 100)) { atnr_winner[winners] = i; winners++; } if (chance >= 0.01) totalchance *= 1 - chance / 100; /*LOG(llevDebug, " %s: bonus %.1f, chance %.1f\n", attacks[i], bonus, chance); */ } } /* inverse totalchance as until now we have the failure-chance */ totalchance = 100 - totalchance * 100; /* print message according to totalchance */ const char *buf; if (totalchance > 50.) buf = format ("Hmm! The %s tasted delicious!", &meal->name); else if (totalchance > 10.) buf = format ("The %s tasted very good.", &meal->name); else if (totalchance > 1.) buf = format ("The %s tasted good.", &meal->name); else if (totalchance > 0.1) buf = format ("The %s tasted bland.", &meal->name); else if (totalchance >= 0.01) buf = format ("The %s had a boring taste.", &meal->name); else if (meal->last_eat > 0 && atnr_is_dragon_enabled (meal->last_eat)) buf = format ("The %s tasted strange.", &meal->name); else buf = format ("The %s had no taste.", &meal->name); op->statusmsg (buf); /* now choose a winner if we have any */ i = -1; if (winners > 0) i = atnr_winner [rndm (winners)]; if (i >= 0 && i < NROFATTACKS && skin->resist[i] < 95) { /* resistance increased! */ skin->resist[i]++; op->update_stats (); op->statusmsg (format ("Your skin is now more resistant to %s!", change_resist_msg[i])); } /* if this flesh contains a new ability focus, we mark it into the ability_force and it will take effect on next level */ if (meal->last_eat > 0 && atnr_is_dragon_enabled (meal->last_eat) && meal->last_eat != abil->last_eat) { abil->last_eat = meal->last_eat; /* write: last_eat */ if (meal->last_eat != abil->stats.exp) op->statusmsg (format ( "Your metabolism prepares to focus on %s!\n" "The change will happen at level %d.", change_resist_msg[meal->last_eat], abil->level + 1 )); else { op->statusmsg (format ("Your metabolism will continue to focus on %s.", change_resist_msg[meal->last_eat])); abil->last_eat = 0; } } return 1; } /** * op eats food. * If player, takes care of messages and dragon special food. */ static void apply_food (object *op, object *tmp) { int capacity_remaining; if (op->type != PLAYER) op->stats.hp = op->stats.maxhp; else { /* check if this is a dragon (player), eating some flesh */ if (tmp->type == FLESH && op->is_dragon () && dragon_eat_flesh (op, tmp)) ; else { /* usual case - no dragon meal: */ if (op->stats.food + tmp->stats.food > MAX_FOOD) { if (tmp->type == FOOD || tmp->type == FLESH) op->failmsg ("You feel full, but what a waste of food!"); else op->statusmsg ("Most of the drink goes down your face not your throat!"); } tmp->play_sound ( tmp->sound ? tmp->sound : tmp->type == DRINK ? sound_find ("eat_drink") : sound_find ("eat_food") ); if (!tmp->flag [FLAG_CURSED]) { const char *buf; if (!op->is_dragon ()) { /* eating message for normal players */ if (tmp->type == DRINK) buf = format ("Ahhh...that %s tasted good.", &tmp->name); else buf = format ("The %s tasted %s", &tmp->name, tmp->type == FLESH ? "terrible!" : "good."); } else /* eating message for dragon players */ buf = format ("The %s tasted terrible!", &tmp->name); op->statusmsg (buf); capacity_remaining = MAX_FOOD - op->stats.food; op->stats.food += tmp->stats.food; if (capacity_remaining < tmp->stats.food) op->stats.hp += capacity_remaining / 50; else op->stats.hp += tmp->stats.food / 50; min_it (op->stats.hp, op->stats.maxhp); min_it (op->stats.food, MAX_FOOD); } /* special food hack -b.t. */ if (tmp->title || tmp->flag [FLAG_CURSED]) eat_special_food (op, tmp); } } handle_apply_yield (tmp); tmp->decrease (); } /** * Handles applying an improve armor scroll. * Does some sanity checks, then calls improve_armour. */ static void apply_armour_improver (object *op, object *tmp) { if (!op->flag [FLAG_WIZCAST] && (get_map_flags (op->map, 0, op->x, op->y, 0, 0) & P_NO_MAGIC)) { op->failmsg ("Something blocks the magic of the scroll. H"); return; } object *armor = op->mark (); if (!armor) { op->failmsg ("You need to mark an armor object. Use the right mouse button popup or the mark command to do this."); return; } if (armor->type != ARMOUR && armor->type != CLOAK && armor->type != BOOTS && armor->type != GLOVES && armor->type != BRACERS && armor->type != SHIELD && armor->type != HELMET) { op->failmsg ("Your marked item is not armour!\n"); return; } if (!op->apply (armor, AP_UNAPPLY)) { op->failmsg ("You are unable to take off your armour to improve it!"); return; } op->statusmsg ("Applying armour enchantment."); improve_armour (op, tmp, armor); } void apply_poison (object *op, object *tmp) { // need to do it now when it is still on the map handle_apply_yield (tmp); object *poison = tmp->split (1); if (op->type == PLAYER) { op->contr->play_sound (sound_find ("drink_poison")); op->failmsg ("Yech! That tasted poisonous!"); op->contr->killer = poison; } if (poison->stats.hp > 0) { LOG (llevDebug, "Trying to poison player/monster for %d hp\n", poison->stats.hp); hit_player (op, poison->stats.hp, tmp, AT_POISON, 1); } op->stats.food -= op->stats.food / 4; poison->destroy (); } /** * This function will try to apply a lighter and in case no lighter * is specified it will try to find a lighter in the players inventory, * and inform him about this requirement. * * who - the player * op - the item we want to light * lighter - the lighter or 0 if a lighter has yet to be found */ static object * auto_apply_lighter (object *who, object *op, object *lighter) { if (lighter == 0) { for (object *tmp = who->inv; tmp; tmp = tmp->below) { if (tmp->type == LIGHTER) { lighter = tmp; break; } } if (!lighter) { who->failmsgf ( "You can't light up the %s with your bare hands! " "H", &op->name ); return 0; } } // last_eat == 0 means the lighter is not being used up! if (lighter->last_eat && lighter->stats.food) { /* lighter gets used up */ lighter = lighter->split (); lighter->stats.food--; who->insert (lighter); } else if (lighter->last_eat) { /* no charges left in lighter */ who->failmsgf ( "You attempt to light the %s with a used up %s.", &op->name, &lighter->name ); return 0; } return lighter; } /** * Designed primarily to light torches/lanterns/etc. * Also burns up burnable material too. First object in the inventory is * the selected object to "burn". -b.t. */ static void apply_lighter (object *who, object *lighter) { int is_player_env = 0; if (object *item = who->mark ()) { if (!auto_apply_lighter (who, item, lighter)) return; /* Perhaps we should split what we are trying to light on fire? * I can't see many times when you would want to light multiple * objects at once. */ save_throw_object (item, AT_FIRE, who); if (item->destroyed () || ((item->type == LAMP || item->type == TORCH) && item->glow_radius > 0)) who->statusmsg (format ( "You light the %s with the %s.", &item->name, &lighter->name )); else who->failmsgf ( "You attempt to light the %s with the %s and fail.", &item->name, &lighter->name ); } else who->failmsg ("You need to mark a lightable object."); } /** * This function generates a cursed effect for cursed lamps and torches. */ static void player_apply_lamp_cursed_effect (object *who, object *op) { if (op->level) { who->failmsgf ( "The %s was cursed, it explodes in a big fireball!", &op->name ); create_exploding_ball_at (who, op->level); } else { who->failmsgf ( "The %s was cursed, it crumbles to dust, at least it didn't explode.!", &op->name ); } op->destroy (); } /** * Apply for players and lamps * * who - the player * op - the lamp */ static void player_apply_lamp (object *who, object *op) { bool switch_on = op->glow_radius ? false : true; if (switch_on) { object *lighter = 0; if (op->flag [FLAG_IS_LIGHTABLE] && !(lighter = auto_apply_lighter (who, op, 0))) return; if (op->stats.food < 1) { if (op->type == LAMP) who->failmsgf ( "The %s is out of fuel! " "H", &op->name ); else who->failmsgf ( "The %s is burnt out! " "H", &op->name ); return; } if (op->flag [FLAG_CURSED]) { player_apply_lamp_cursed_effect (who, op); return; } if (lighter) who->statusmsg (format ( "You light up the %s with the %s.", &op->name, &lighter->name)); else who->statusmsg (format ("You light up the %s.", &op->name)); } else { if (op->flag [FLAG_CURSED]) { player_apply_lamp_cursed_effect (who, op); return; } if (op->type == TORCH) { if (!op->flag [FLAG_IS_LIGHTABLE]) { who->statusmsg (format ( "You put out the %s. " "H", &op->name, &op->name)); } else who->statusmsg (format ( "You put out the %s." "H", &op->name)); } else who->statusmsg (format ("You turn off the %s.", &op->name)); } apply_lamp (op, switch_on); } void get_animation_from_arch (object *op, arch_ptr a) { op->animation_id = a->animation_id; op->flag [FLAG_IS_TURNABLE] = a->flag [FLAG_IS_TURNABLE]; op->flag [FLAG_ANIMATE] = a->flag [FLAG_ANIMATE]; op->anim_speed = a->anim_speed; op->last_anim = 0; op->state = 0; op->face = a->face; if (NUM_ANIMATIONS(op) > 1) { SET_ANIMATION(op, 0); animate_object (op, op->direction); } else update_object (op, UP_OBJ_FACE); } /** * Apply for LAMPs and TORCHes. * * op - the lamp * switch_on - a flag which says whether the lamp should be switched on or off */ void apply_lamp (object *op, bool switch_on) { op->set_glow_radius (switch_on ? op->range : 0); op->set_speed (switch_on ? op->arch->speed : 0); // torches wear out if you put them out if (op->type == TORCH && !switch_on) { if (op->flag [FLAG_IS_LIGHTABLE]) { op->stats.food -= (double) op->arch->stats.food / 15; if (op->stats.food < 0) op->stats.food = 0; } else op->stats.food = 0; } // lamps and torched get worthless when used up if (op->stats.food <= 0) op->value = 0; // FIXME: This is a hack to make the more sane torches and lamps // still animated ;-/ if (op->other_arch) get_animation_from_arch (op, switch_on ? op->other_arch : op->arch); if (object *pl = op->visible_to ()) esrv_update_item (UPD_ANIM | UPD_FACE | UPD_NAME, pl, op); } /** * This handles items of type 'transformer'. * Basically those items, used with a marked item, transform both items into something * else. * "Transformer" item has food decreased by 1, removed if 0 (0 at start means illimited).. * Change information is contained in the 'slaying' field of the marked item. * The format is as follow: transformer:[number ]yield[;transformer:...]. * This way an item can be transformed in many things, and/or many objects. * The 'slaying' field for transformer is used as verb for the action. */ static void apply_item_transformer (object *pl, object *transformer) { object *new_item; const char *find; char *separator; int yield; char got[MAX_BUF]; int len; if (!pl || !transformer) return; object *marked = pl->mark (); if (!marked) { pl->failmsgf ("Use the %s with what item?", query_name (transformer)); return; } if (!marked->slaying) { pl->failmsgf ("You can't use the %s with your %s!", query_name (transformer), query_name (marked)); return; } /* check whether they are compatible or not */ find = strstr (&marked->slaying, transformer->arch->archname); if (!find || (*(find + strlen (transformer->arch->archname)) != ':')) { pl->failmsgf ("You can't use the %s with your %s!", query_name (transformer), query_name (marked)); return; } find += strlen (transformer->arch->archname) + 1; /* Item can be used, now find how many and what it yields */ if (isdigit (*(find))) { yield = atoi (find); if (yield < 1) { LOG (llevDebug, "apply_item_transformer: item %s has slaying-yield %d.", query_base_name (marked, 0), yield); yield = 1; } } else yield = 1; while (isdigit (*find)) find++; while (*find == ' ') find++; memset (got, 0, MAX_BUF); if ((separator = (char *) strchr (find, ';'))) len = separator - find; else len = strlen (find); min_it (len, MAX_BUF - 1); strcpy (got, find); got[len] = '\0'; /* Now create new item, remove used ones when required. */ new_item = get_archetype (got); if (!new_item) { pl->failmsgf ("This %s is strange, better to not use it.", query_base_name (marked, 0)); return; } new_item->nrof = yield; pl->statusmsg (format ("You %s the %s.", &transformer->slaying, query_base_name (marked, 0))); pl->insert (new_item); /* Eat up one item */ marked->decrease (); /* Eat one transformer if needed */ if (transformer->stats.food) if (--transformer->stats.food == 0) transformer->decrease (); } /** * Main apply handler. * * Checks for unpaid items before applying. * * Return value is currently not used * * who is the object that is causing object to be applied, op is the object * being applied. * * aflag is special (always apply/unapply) flags. Nothing is done with * them in this function - they are passed to apply_special */ static bool manual_apply (object *who, object *op, int aflag) { op = op->head_ (); if (op->flag [FLAG_UNPAID] && !op->flag [FLAG_APPLIED]) { if (who->contr) { examine (who, op); //who->failmsg ("You should pay for it first! H");//TODO remove return 1; } else return 0; /* monsters just skip unpaid items */ } if (INVOKE_OBJECT (APPLY, op, ARG_OBJECT (who))) return RESULT_INT (0); else if (apply_types_inv_only [op->type]) { // special item, using slot system, needs to be in inv if (op->env == who) return apply_special (who, op, aflag); who->failmsgf ("You must get it first! H\n", query_name (op)); } else if (!who->contr && apply_types_player_only [op->type]) return 0; // monsters shouldn't try to apply player-only stuff else if (apply_types [op->type]) { // ordinary stuff, may be on the floor switch (op->type) { case T_HANDLE: who->play_sound (sound_find ("turn_handle")); who->statusmsg ("You turn the handle."); op->value = op->value ? 0 : 1; SET_ANIMATION (op, op->value); update_object (op, UP_OBJ_FACE); push_button (op, who); break; case TRIGGER: if (check_trigger (op, who, who)) { who->statusmsg ("You turn the handle."); who->play_sound (sound_find ("turn_handle")); } else who->failmsg ("The handle doesn't move."); break; case EXIT: if (!EXIT_PATH (op)) who->failmsgf ("The %s is closed. H", query_name (op)); else { /* Don't display messages for random maps. */ if (op->msg && EXIT_PATH (op) != shstr_random_map_exit) who->statusmsg (op->msg, NDI_NAVY); who->enter_exit (op); } break; case INSCRIBABLE: who->statusmsg (op->msg); // maybe show a spell menu to chose from or something like that break; case SIGN: apply_sign (who, op, 0); break; case BOOK: apply_book (who, op); break; case SKILLSCROLL: apply_skillscroll (who, op); break; case SPELLBOOK: apply_spellbook (who, op); break; case SCROLL: apply_scroll (who, op, 0); break; case POTION: apply_potion (who, op); break; /* Eneq(@csd.uu.se): Handle apply on containers. */ //TODO: remove, as it is unsed? case CLOSE_CON: apply_container (who, op->env); break; case CONTAINER: apply_container (who, op); break; case TREASURE: apply_treasure (who, op); break; case LAMP: case TORCH: player_apply_lamp (who, op); break; case DRINK: case FOOD: case FLESH: apply_food (who, op); break; case POISON: apply_poison (who, op); break; case SAVEBED: break; case ARMOUR_IMPROVER: apply_armour_improver (who, op); break; case WEAPON_IMPROVER: check_improve_weapon (who, op); break; case CLOCK: { char buf[MAX_BUF]; timeofday_t tod; get_tod (&tod); who->play_sound (sound_find ("sound_clock")); who->statusmsg (format ( "It is %d minute%s past %d o'clock %s", tod.minute + 1, ((tod.minute + 1 < 2) ? "" : "s"), ((tod.hour % 14 == 0) ? 14 : ((tod.hour) % 14)), ((tod.hour >= 14) ? "pm" : "am") )); } break; case MENU: shop_listing (op, who); break; case POWER_CRYSTAL: apply_power_crystal (who, op); /* see egoitem.c */ break; case LIGHTER: /* for lighting torches/lanterns/etc */ apply_lighter (who, op); break; case ITEM_TRANSFORMER: apply_item_transformer (who, op); break; } return 1; } else { who->statusmsg (format ("I don't know how to apply the %s.", query_name (op))); return 0; } } /** * player_apply_below attempts to apply the object 'below' the player. * If the player has an open container, we use that for below, otherwise * we use the ground. */ void player_apply_below (object *pl) { object *top = pl->container_ () ? pl->container_ ()->inv : pl->below; /* If using a container, set the starting item to be the top * item in the container. Otherwise, use the map. */ // first try to apply "applyables" for (object *tmp = top; tmp; tmp = tmp->below) if (!tmp->invisible && apply_types [tmp->type]) { // If it is visible, player can apply it. pl->apply (tmp); return; } while (top && top->invisible) top = top->below; if (!top || top->flag [FLAG_IS_FLOOR]) pl->failmsg ("You inspect this space, but find no way to apply anything. " "H"); else // next, try to explain the topmost object switch (top->type) { // TODO: all this should move to examine case ALTAR: case IDENTIFY_ALTAR: case TRIGGER_ALTAR: case CONVERTER: //case TRIGGER_PEDESTAL: pl->failmsgf ( "You see no obvious mechanism on the %s." "H", query_short_name (top) ); break; case BUTTON: case TRIGGER_BUTTON: pl->failmsgf ( "The %s looks as if you could activate it with somehting heavy. " "H", query_short_name (top) ); break; default: examine (pl, top); break; } } // saner interface, returns successful status bool object::apply (object *ob, int aflags) { if (!ob) // simplifies a lot of callers return true; if (contr) { if (!ob->env && (move_type & MOVE_FLYING)) { /* player is flying and applying object not in inventory */ if (!flag [FLAG_WIZ] && !(ob->move_type & MOVE_FLYING)) { failmsg ("But you are floating high above the ground! " "H"); return 0; } } contr->last_used = ob; } bool want_apply = aflags & AP_APPLY ? true : aflags & AP_UNAPPLY ? false : !ob->flag [FLAG_APPLIED]; // AP_TOGGLE object_ptr *slot = 0; // detect the slot, if this is a player if (contr && !(aflags & AP_NO_SLOT)) { object *oslot; switch (ob->type) { case WEAPON: slot = &contr->combat_ob; oslot = contr->ranged_ob; break; case BOW: case RANGED: case SPELL: case WAND: case ROD: case HORN: case BUILDER: slot = &contr->ranged_ob; oslot = contr->combat_ob; break; // oh, the humanity case SKILL: if (aflags & AP_NO_SLOT) break; if (skill_flags [ob->subtype] & SF_NEED_ITEM) break; if (skill_flags [ob->subtype] & SF_COMBAT) { slot = &contr->combat_ob; oslot = contr->ranged_ob; } else if (skill_flags [ob->subtype] & SF_RANGED) { slot = &contr->ranged_ob; oslot = contr->combat_ob; } break; } // now handle slot exclusions if (slot) { // only one slot can be active if (want_apply) { // clear slot unless we are in it already if (*slot != ob) apply (*slot, AP_UNAPPLY); // unapply other slot, because we want to become active apply (oslot, AP_UNAPPLY); } // clear item from slot if applied if (!want_apply && current_weapon == ob) current_weapon = 0; } } if (ob->flag [FLAG_APPLIED] != want_apply) manual_apply (this, ob, aflags); if (ob->flag [FLAG_APPLIED] != want_apply) return false; if (slot && want_apply) current_weapon = *slot = ob; return true; } /** * Map was just loaded, handle op's initialisation. * * Generates shop floor's item, and treasures. */ int auto_apply (object *op) { object *tmp = NULL, *tmp2; int i; op->clr_flag (FLAG_AUTO_APPLY); switch (op->type) { case SHOP_FLOOR: if (!op->has_random_items ()) 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 (tmp->flag [FLAG_CURSED] || tmp->flag [FLAG_DAMNED]) { tmp->destroy (); tmp = NULL; } } while (!tmp); tmp->x = op->x; tmp->y = op->y; tmp->set_flag (FLAG_UNPAID); insert_ob_in_map (tmp, op->map, NULL, 0); identify (tmp); break; case TREASURE: if (op->flag [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 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. */ if (op->env) while (op->inv) op->env->insert (op->inv); op->destroy (); break; } return !!tmp; } /** * fix_auto_apply goes through the entire map every time a map * is loaded or swapped in and performs special actions for * certain objects (most initialization of chests and creation of * treasures and stuff). Calls auto_apply if appropriate. */ void maptile::fix_auto_apply () { if (!spaces) return; for (mapspace *ms = spaces + size (); ms-- > spaces; ) for (object *tmp = ms->bot; tmp; ) { object *above = tmp->above; if (tmp->inv) { object *invtmp, *invnext; for (invtmp = tmp->inv; invtmp; invtmp = invnext) { invnext = invtmp->below; if (invtmp->flag [FLAG_AUTO_APPLY]) auto_apply (invtmp); else if (invtmp->type == TREASURE && invtmp->has_random_items ()) { while (invtmp->stats.hp-- > 0) create_treasure (invtmp->randomitems, invtmp, 0, difficulty, 0); invtmp->randomitems = NULL; } else if (invtmp && invtmp->arch && invtmp->type != TREASURE && invtmp->type != SPELL && invtmp->type != CLASS && invtmp->has_random_items ()) { create_treasure (invtmp->randomitems, invtmp, 0, 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 (tmp->flag [FLAG_AUTO_APPLY]) auto_apply (tmp); else if ((tmp->type == TREASURE || (tmp->type == CONTAINER)) && tmp->has_random_items ()) { while ((tmp->stats.hp--) > 0) create_treasure (tmp->randomitems, tmp, 0, difficulty, 0); tmp->randomitems = NULL; } else if (tmp->type == TIMED_GATE) { object *head = tmp->head != NULL ? tmp->head : tmp; if (head->flag [FLAG_IS_LINKED]) tmp->set_speed (0); } /* 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 && tmp->has_random_items ()) { create_treasure (tmp->randomitems, tmp, GT_APPLY, difficulty, 0); tmp->randomitems = NULL; } // close all containers else if (tmp->type == CONTAINER) tmp->flag [FLAG_APPLIED] = 0; tmp = above; } for (mapspace *ms = spaces + size (); ms-- > spaces; ) for (object *tmp = ms->bot; tmp; tmp = tmp->above) if (tmp->above && (tmp->type == TRIGGER_BUTTON || tmp->type == TRIGGER_PEDESTAL)) check_trigger (tmp, tmp->above, tmp->above); } /** * Handles player eating food that temporarily changes status (resistances, stats). * This used to call cast_change_attr(), but * that doesn't work with the new spell code. Since we know what * the food changes, just grab a force and use that instead. */ void eat_special_food (object *who, object *food) { object *force; int i, did_one = 0; char buf[64]; snprintf (buf, sizeof(buf), "sp_food_%s", &food->title); shstr key (buf); /* bigger morsel of food = longer effect time */ int duration = TIME2TICK (food->stats.food); if (force = who->force_find (key)) { if (duration > fabs (force->speed_left / force->speed)) { new_draw_info_format (NDI_UNIQUE, 0, who, "More magical force spreads through you. H", TICK2TIME (duration)); force->force_set_timer (duration); } else new_draw_info (NDI_UNIQUE, 0, who, "It had no additional effect."); return; } else { force = who->force_add (key, duration); force->name = key; /* check if the food affects a stat */ for (i = 0; i < NUM_STATS; i++) if (sint8 k = food->stats.stat (i)) { force->stats.stat (i) = k; did_one = 1; } /* check if we can protect the eater */ for (i = 0; i < NROFATTACKS; i++) { if (food->resist[i] > 0) { force->resist[i] = food->resist[i]; did_one = 1; } } if (did_one) { new_draw_info_format (NDI_UNIQUE, 0, who, "You are suffused with magical force. H", TICK2TIME (duration)); /* make the force take effect and report effects to user */ change_abil (who, force); } else force->destroy (); } /* check for hp, sp change */ if (food->stats.hp != 0) { if (food->flag [FLAG_CURSED]) { who->contr->killer = food; hit_player (who, food->stats.hp, food, AT_POISON, 1); who->failmsg ("Eck!...that was poisonous!"); } else { if (food->stats.hp > 0) who->statusmsg ("You begin to feel better."); else who->failmsg ("Eck!...that was poisonous!"); who->stats.hp += food->stats.hp; } } if (food->stats.sp != 0) { if (food->flag [FLAG_CURSED]) { who->failmsg ("You are drained of mana!"); who->stats.sp -= food->stats.sp; if (who->stats.sp < 0) who->stats.sp = 0; } else { who->statusmsg ("You feel a rush of magical energy!"); who->stats.sp += food->stats.sp; /* place limit on max sp from food? */ } } who->update_stats (); } 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++) { int race_bonus = pl->arch->stats.stat (i); sint8 stat = stats->stat (i) + ns->stat (i); if (stat > 20 + race_bonus) { excess_stat++; stat = 20 + race_bonus; } stats->stat (i) = stat; } for (j = 0; excess_stat > 0 && j < 100; j++) { /* try 100 times to assign excess stats */ int i = rndm (0, 6); if (i == CHA) continue; /* exclude cha from this */ int stat = stats->stat (i); int race_bonus = pl->arch->stats.stat (i); 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) give_initial_items (pl, change->randomitems); /* 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; walk = walk->below) if (walk->name == shstr_NOCLASSFACECHANGE) flag_change_face = 0; if (flag_change_face) { pl->face = change->face; pl->animation_id = change->animation_id; pl->flag [FLAG_ANIMATE] = change->flag [FLAG_ANIMATE]; } /* check the special case of can't use weapons */ /*if(change->flag [FLAG_USE_WEAPON]) pl->clr_flag (FLAG_USE_WEAPON); */ if (change->name == shstr_monk) pl->clr_flag (FLAG_USE_WEAPON); break; } } }