/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2001,2007 Mark Wedel & Crossfire Development Team * Copyright (©) 1992,2007 Frank Tore Johansen * * Deliantra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * The authors can be reached via e-mail to */ #include #include #include #include #include #include #include /* Want this regardless of rplay. */ #include /** * 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) || 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 = find_marked_object (pl)) if (!QUERY_FLAG (marked, FLAG_IDENTIFIED) && need_identify (marked)) { if (operate_altar (altar, &money)) { 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 (!QUERY_FLAG (id, FLAG_IDENTIFIED) && !id->invisible && need_identify (id)) { if (operate_altar (altar, &money)) { 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, *floor = 0; 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!"); CLEAR_FLAG (tmp, FLAG_APPLIED); return 0; } if (op->type == PLAYER) if (!QUERY_FLAG (tmp, 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 (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED)) { op->drain_stat (); op->update_stats (); tmp->decrease (); return 1; } if (!(at = archetype::find (ARCH_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 (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, 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 (!QUERY_FLAG (tmp, FLAG_CURSED) && !QUERY_FLAG (tmp, 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 (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, 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 (!QUERY_FLAG (op, 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 (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, 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); CLEAR_FLAG (tmp, FLAG_APPLIED); SET_FLAG (force, 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 ((QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED)) && tmp->value != 0) CLEAR_FLAG (tmp, FLAG_APPLIED); else SET_FLAG (tmp, 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. */ CLEAR_FLAG (tmp, FLAG_APPLIED); op->update_stats (); tmp->decrease (); return 1; } /**************************************************************************** * Weapon improvement code follows ****************************************************************************/ /** * This function just checks whether who can handle equipping an item * with item_power. */ 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 (!QUERY_FLAG (op, FLAG_CURSED) && !QUERY_FLAG (op, FLAG_DAMNED) && /* Loophole bug? -FD- */ !QUERY_FLAG (op, 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->failmsg (format ("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. */ 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 */ 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 (QUERY_FLAG (weapon, 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->failmsg (format ("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. */ int check_improve_weapon (object *op, object *tmp) { object *otmp; if (op->type != PLAYER) return 0; if (!QUERY_FLAG (op, 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; } otmp = find_marked_object (op); 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; } 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. */ int improve_armour (object *op, object *improver, object *armour) { object *tmp; 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. */ 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 (QUERY_FLAG (armour, 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. */ static int convert_item (object *item, object *converter) { sint64 nr, 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 (); CLEAR_FLAG (item, 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]) SET_FLAG (item, FLAG_UNPAID); if (is_in_shop (converter)) { // 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 ()); SET_FLAG (item, 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 (need_identify (item)) 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. */ 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->failmsg (format ("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)) { /* 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; SET_FLAG (op, 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 (QUERY_FLAG (tmp, 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 (QUERY_FLAG (op, 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 (QUERY_FLAG (op, FLAG_UNPAID) || !QUERY_FLAG (op, 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 (is_in_shop (op)) 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 && !is_in_shop (op)) { 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; } } CLEAR_FLAG (op, 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 (!QUERY_FLAG (op, 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 (QUERY_FLAG (op, FLAG_BLIND) && !QUERY_FLAG (op, 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); } /** * '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; /* Only exits affect DMs. */ if (QUERY_FLAG (victim, 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 perfeclty 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 (trap->head) trap = trap->head; if (INVOKE_OBJECT (MOVE_TRIGGER, trap, ARG_OBJECT (victim), ARG_OBJECT (originator))) goto leave; 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 = -FABS (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. */ if (victim->speed_left < -50.f) victim->speed_left = -50.f; /* LOG(llevDebug, "apply, playermove, player speed_left=%f\n", victim->speed_left); */ } goto leave; case SPINNER: if (victim->direction) { victim->direction = absdir (victim->direction - trap->stats.sp); update_turn_face (victim); } goto leave; case DIRECTOR: if (victim->direction && !should_director_abort (trap, victim)) { victim->direction = trap->stats.sp; update_turn_face (victim); } goto leave; case BUTTON: case PEDESTAL: update_button (trap, originator); goto leave; case ALTAR: /* sacrifice victim on trap */ apply_altar (trap, victim, originator); goto leave; case THROWN_OBJ: if (trap->inv == NULL) goto leave; /* 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 ((QUERY_FLAG (victim, FLAG_ALIVE) && trap->speed) && trap->owner != victim) hit_with_arrow (trap, victim); goto leave; case SPELL_EFFECT: apply_spell_effect (trap, victim); goto leave; 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)) goto leave; 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); } } goto leave; } case CONVERTER: if (convert_item (victim, trap) < 0) { originator->failmsg (format ("The %s seems to be broken!", query_name (trap))); archetype::get (shstr_burnout)->insert_at (trap, trap); } goto leave; case TRIGGER_BUTTON: case TRIGGER_PEDESTAL: case TRIGGER_ALTAR: check_trigger (trap, victim); goto leave; case DEEP_SWAMP: walk_on_deep_swamp (trap, victim); goto leave; case CHECK_INV: check_inv (victim, trap); goto leave; case HOLE: move_apply_hole (trap, victim); goto leave; 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).starts_with ("/!")) victim->statusmsg (trap->msg, NDI_NAVY); trap->play_sound (trap->sound); victim->enter_exit (trap); } goto leave; case ENCOUNTER: /* may be some leftovers on this */ goto leave; case SHOP_MAT: apply_shop_mat (trap, victim); goto leave; /* Drop a certain amount of gold, and have one item identified */ case IDENTIFY_ALTAR: apply_id_altar (victim, trap, originator); goto leave; case SIGN: if (victim->type != PLAYER && trap->stats.food > 0) goto leave; /* monsters musn't apply magic_mouths with counters */ apply_sign (victim, trap, 1); goto leave; case CONTAINER: apply_container (victim, trap); goto leave; case RUNE: case TRAP: if (trap->level && QUERY_FLAG (victim, FLAG_ALIVE)) spring_trap (trap, victim); goto leave; 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); goto leave; } leave: 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 (QUERY_FLAG (op, FLAG_BLIND) && !QUERY_FLAG (op, FLAG_WIZ)) { op->failmsg ("You are unable to read while blind!"); return; } if (!tmp->msg) { op->failmsg (format ("You open the %s and find it empty.", &tmp->name)); return; } /* need a literacy skill to read stuff! */ skill_ob = find_skill_by_name (op, tmp->skill); if (!skill_ob) { op->failmsg (format ("You are unable to decipher the strange symbols. H", &tmp->skill)); return; } lev_diff = tmp->level - (skill_ob->level + 5); if (!QUERY_FLAG (op, 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; } readable_message_type *msgType = get_readable_message_type (tmp); 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 (!QUERY_FLAG (tmp, FLAG_NO_SKILL_IDENT)) { /* only if not read before */ int exp_gain = calc_skill_exp (op, tmp, skill_ob); if (!QUERY_FLAG (tmp, FLAG_IDENTIFIED)) { /*exp_gain *= 2; because they just identified it too */ SET_FLAG (tmp, 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); SET_FLAG (tmp, FLAG_NO_SKILL_IDENT); /* so no more xp gained from this book */ } } /** * 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->failmsg (format ("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->failmsg (format ("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 && !QUERY_FLAG (tmp, 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) SET_FLAG (tmp, 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->failmsg (format ("You lose knowledge of %s.", spell)); player_unready_range_ob (op->contr, spob); 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 (QUERY_FLAG (op, FLAG_BLIND) && !QUERY_FLAG (op, 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 = arch_to_object (find_archetype_by_object_name (tmp->slaying)); if (!spell) { op->failmsg (format ("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->failmsg (format ("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->failmsg (format ("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.", get_levelnumber (spell->level), &spell->name)); if (!QUERY_FLAG (tmp, 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->failmsg (format ("You lack the skill %s to use this spell.", &spell->skill)); return; } if (spell_skill->level < spell->level) { op->failmsg (format ("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 (QUERY_FLAG (op, 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 (QUERY_FLAG (tmp, 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 (!QUERY_FLAG (tmp, 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 (QUERY_FLAG (op, FLAG_BLIND) && !QUERY_FLAG (op, 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, skill_names[SK_LITERACY]); if (!skop) { op->failmsg (format ("You are unable to decipher the strange symbols. H", &skill_names[SK_LITERACY])); return; } if ((exp_gain = calc_skill_exp (op, tmp, skop))) change_exp (op, exp_gain, skop->skill, 0); } if (!QUERY_FLAG (tmp, 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 && QUERY_FLAG (op, 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); } /** * 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 && is_dragon_pl (op) && dragon_eat_flesh (op, tmp)) ; else { /* usual case - no dragon meal: */ if (op->stats.food + tmp->stats.food > 999) { 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 (!QUERY_FLAG (tmp, FLAG_CURSED)) { const char *buf; if (!is_dragon_pl (op)) { /* 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 = 999 - 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; if (op->stats.hp > op->stats.maxhp) op->stats.hp = op->stats.maxhp; if (op->stats.food > 999) op->stats.food = 999; } /* special food hack -b.t. */ if (tmp->title || QUERY_FLAG (tmp, FLAG_CURSED)) eat_special_food (op, tmp); } } handle_apply_yield (tmp); tmp->decrease (); } /** * 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 */ 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 || !is_dragon_pl (op)) 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 ((999 - op->stats.food) < meal->stats.food) op->stats.hp += (999 - op->stats.food) / 50; else op->stats.hp += meal->stats.food / 50; if (op->stats.hp > op->stats.maxhp) op->stats.hp = op->stats.maxhp; op->stats.food = MIN (999, 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; } /** * Handles applying an improve armor scroll. * Does some sanity checks, then calls improve_armour. */ static void apply_armour_improver (object *op, object *tmp) { object *armor; if (!QUERY_FLAG (op, 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; } armor = find_marked_object (op); 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; } 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 return true if the exit is not a 2 ways one or it is 2 ways, valid exit. * A valid 2 way exit means: * -You can come back (there is another exit at the other side) * -You are * ° the owner of the exit * ° or in the same party as the owner * * Note: a owner in a 2 way exit is saved as the owner's name * in the field exit->name cause the field exit->owner doesn't * survive in the swapping (in fact the whole exit doesn't survive). */ int is_legal_2ways_exit (object *op, object *exit) { if (exit->stats.exp != 1) return 1; /*This is not a 2 way, so it is legal */ #if 0 //TODO if (!has_been_loaded (EXIT_PATH (exit)) && exit->race) return 0; /* This is a reset town portal */ #endif LOG (llevError | logBacktrace, "sync map load due to %s\n", exit->debug_desc ()); maptile *exitmap = maptile::find_sync (EXIT_PATH (exit), exit->map); if (exitmap) { exitmap->load_sync (); object *tmp = exitmap->at (EXIT_X (exit), EXIT_Y (exit)).bot; if (!tmp) return 0; for (; tmp; tmp = tmp->above) { if (tmp->type != EXIT) continue; /*Not an exit */ if (!EXIT_PATH (tmp)) continue; /*Not a valid exit */ if ((EXIT_X (tmp) != exit->x) || (EXIT_Y (tmp) != exit->y)) continue; /*Not in the same place */ if (exit->map->path != EXIT_PATH (tmp)) continue; /*Not in the same map */ /* From here we have found the exit is valid. However we do * here the check of the exit owner. It is important for the * town portals to prevent strangers from visiting your appartments */ if (!exit->race) return 1; /*No owner, free for all! */ object *exit_owner = 0; for_all_players (pp) { if (!pp->ob) continue; if (pp->ob->name != exit->race) continue; exit_owner = pp->ob; /*We found a player which correspond to the player name */ break; } if (!exit_owner) return 0; /* No more owner */ if (exit_owner->contr == op->contr) return 1; /*It is your exit */ if (exit_owner && /*There is a owner */ (op->contr) && /*A player tries to pass */ ((exit_owner->contr->party == NULL) || /*No pass if controller has no party */ (exit_owner->contr->party != op->contr->party))) /* Or not the same as op */ return 0; return 1; } } return 0; } /** * 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 * ligher - the lighter or 0 if a lighter has yet to be found */ 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->failmsg (format ( "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->failmsg (format ( "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. */ void apply_lighter (object *who, object *lighter) { object *item; int is_player_env = 0; item = find_marked_object (who); if (item) { 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->failmsg (format ( "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. */ void player_apply_lamp_cursed_effect (object *who, object *op) { if (op->level) { who->failmsg (format ( "The %s was cursed, it explodes in a big fireball!", &op->name)); create_exploding_ball_at (who, op->level); } else { who->failmsg (format ( "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 */ 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->failmsg (format ( "The %s is out of fuel! " "H", &op->name)); else who->failmsg (format ( "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); } /** * Main apply handler. * * Checks for unpaid items before applying. * * Return value: * 0: player or monster can't apply objects of that type * 1: has been applied, or there was an error applying the object * 2: objects of that type can't be applied if not in inventory * * 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 */ int manual_apply (object *who, object *op, int aflag) { op = op->head_ (); if (QUERY_FLAG (op, FLAG_UNPAID) && !QUERY_FLAG (op, FLAG_APPLIED)) { if (who->type == PLAYER) { 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); switch (op->type) { case CF_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); return 1; case TRIGGER: if (check_trigger (op, who)) { who->statusmsg ("You turn the handle."); who->play_sound (sound_find ("turn_handle")); } else who->failmsg ("The handle doesn't move."); return 1; case EXIT: if (who->type != PLAYER) return 0; if (!EXIT_PATH (op) || !is_legal_2ways_exit (who, op)) who->failmsg (format ("The %s is closed.", query_name (op))); else { /* Don't display messages for random maps. */ if (op->msg && !EXIT_PATH (op).starts_with ("/!")) who->statusmsg (op->msg, NDI_NAVY); who->enter_exit (op); } return 1; case INSCRIBABLE: who->statusmsg (op->msg); // maybe show a spell menu to chose from or something like that return 1; case SIGN: apply_sign (who, op, 0); return 1; case BOOK: if (who->type == PLAYER) { apply_book (who, op); return 1; } else return 0; case SKILLSCROLL: if (who->type == PLAYER) { apply_skillscroll (who, op); return 1; } else return 0; case SPELLBOOK: if (who->type == PLAYER) { apply_spellbook (who, op); return 1; } else return 0; case SCROLL: apply_scroll (who, op, 0); return 1; case POTION: apply_potion (who, op); return 1; /* Eneq(@csd.uu.se): Handle apply on containers. */ //TODO: remove, as it is unsed? case CLOSE_CON: apply_container (who, op->env); return 1; case CONTAINER: apply_container (who, op); return 1; case TREASURE: if (who->type == PLAYER) { apply_treasure (who, op); return 1; } else return 0; case LAMP: case TORCH: player_apply_lamp (who, op); return 1; case WEAPON: case ARMOUR: case BOOTS: case GLOVES: case AMULET: case GIRDLE: case BRACERS: case SHIELD: case HELMET: case RING: case CLOAK: case WAND: case ROD: case HORN: case SKILL: case BOW: case BUILDER: case SKILL_TOOL: if (op->env != who) return 2; /* not in inventory */ apply_special (who, op, aflag); return 1; case DRINK: case FOOD: case FLESH: apply_food (who, op); return 1; case POISON: apply_poison (who, op); return 1; case SAVEBED: return 1; case ARMOUR_IMPROVER: if (who->type == PLAYER) { apply_armour_improver (who, op); return 1; } else return 0; case WEAPON_IMPROVER: check_improve_weapon (who, op); return 1; case CLOCK: if (who->type == PLAYER) { 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") )); return 1; } else return 0; case MENU: if (who->type == PLAYER) { shop_listing (op, who); return 1; } else return 0; case POWER_CRYSTAL: apply_power_crystal (who, op); /* see egoitem.c */ return 1; case LIGHTER: /* for lighting torches/lanterns/etc */ if (who->type == PLAYER) { apply_lighter (who, op); return 1; } else return 0; case ITEM_TRANSFORMER: apply_item_transformer (who, op); return 1; default: return 0; } } /* quiet suppresses the "don't know how to apply" and "you must get it first" * messages as needed by player_apply_below(). But there can still be * "but you are floating high above the ground" messages. * * Same return value as apply() function. */ int player_apply (object *pl, object *op, int aflag, int quiet) { if (!op->env && (pl->move_type & MOVE_FLYING)) { /* player is flying and applying object not in inventory */ if (!QUERY_FLAG (pl, FLAG_WIZ) && !(op->move_type & MOVE_FLYING)) { pl->failmsg ("But you are floating high above the ground! " "H"); return 0; } } pl->contr->last_used = op; int tmp = manual_apply (pl, op, aflag); if (!quiet) { if (tmp == 0) pl->statusmsg (format ("I don't know how to apply the %s.", query_name (op))); else if (tmp == 2) pl->failmsg ("You must get it first!\n"); } return tmp; } /** * 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) { int floors = 0; /* If using a container, set the starting item to be the top * item in the container. Otherwise, use the map. * This is perhaps more complicated. However, I want to make sure that * we don't use a corrupt pointer for the next object, so we get the * next object in the stack before applying. This is can only be a * problem if player_apply() has a bug in that it uses the object but does * not return a proper value. */ for (object *next, *tmp = pl->container ? pl->container->inv : pl->below; tmp; tmp = next) { next = tmp->below; if (QUERY_FLAG (tmp, FLAG_IS_FLOOR)) floors++; else if (floors > 0) return; /* process only floor objects after first floor object */ /* If it is visible, player can apply it. If it is applied by * person moving on it, also activate. Added code to make it * so that at least one of players movement types be that which * the item needs. */ if (!tmp->invisible || (tmp->move_on & pl->move_type)) if (player_apply (pl, tmp, 0, 1) == 1) return; if (floors >= 2) return; /* process at most two floor objects */ } } /** * Unapplies specified item. * No check done on cursed/damned. * Break this out of apply_special - this is just done * to keep the size of apply_special to a more managable size. */ static int 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); CLEAR_FLAG (op, FLAG_APPLIED); switch (op->type) { case SKILL_TOOL: // unapplying a 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 for (object *tmp = who->inv; tmp; tmp = tmp->below) if (tmp->skill == op->skill && tmp->type == SKILL && tmp->flag [FLAG_APPLIED] && !tmp->flag [FLAG_CAN_USE_SKILL]) unapply_special (who, tmp, 0); change_abil (who, op); break; case WEAPON: if (player *pl = who->contr) if (op == pl->combat_ob) { pl->combat_ob = 0; who->change_weapon (pl->ranged_ob); } who->statusmsg (format ("You unwield %s.", query_name (op))); change_abil (who, op); CLEAR_FLAG (who, FLAG_READY_WEAPON); break; case SKILL: if (who->contr) { if (IS_COMBAT_SKILL (op->subtype)) who->change_weapon (who->contr->combat_ob = 0); else if (IS_RANGED_SKILL (op->subtype)) who->change_weapon (who->contr->ranged_ob = 0); if (op->invisible) who->statusmsg (format ("You can no longer use the skill: %s.", &op->skill)); else who->statusmsg (format ("You stop using the %s.", query_name (op))); } change_abil (who, op); CLEAR_FLAG (who, FLAG_READY_SKILL); 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 BOW: case WAND: case ROD: case HORN: if (player *pl = who->contr) { if (op == pl->ranged_ob) { pl->ranged_ob = 0; who->change_weapon (pl->combat_ob); } who->statusmsg (format ("You unready %s.", query_name (op))); } else { who->change_skill (0); if (op->type == BOW) CLEAR_FLAG (who, FLAG_READY_BOW); else CLEAR_FLAG (who, FLAG_READY_RANGE); } break; case BUILDER: if (who->contr) who->statusmsg (format ("You unready %s.", query_name (op))); break; 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 0; } /** * 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)) 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" int unapply_for_ob (object *who, object *op, int aflags) { if (op->is_range ()) for (object *tmp = who->inv; tmp; tmp = tmp->below) if (QUERY_FLAG (tmp, FLAG_APPLIED) && tmp->is_range ()) if ((aflags & AP_IGNORE_CURSE) || (aflags & AP_PRINT) || (!QUERY_FLAG (tmp, FLAG_CURSED) && !QUERY_FLAG (tmp, 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->failmsg (format ("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) || (!(QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, 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->failmsg (format ("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) { #if 0 /* This is sort of an error, but happens a lot when old players * join in with more stuff equipped than they are now allowed. */ LOG (llevError, "Can't find object using location %d on %s\n", i, who->name); #endif 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 && !QUERY_FLAG (who, FLAG_USE_WEAPON)) retval |= CAN_APPLY_RESTRICTION; if (op->type == SHIELD && !QUERY_FLAG (who, FLAG_USE_SHIELD)) retval |= CAN_APPLY_RESTRICTION; if (who->type != PLAYER) { if ((op->type == WAND || op->type == HORN || op->type == ROD) && !QUERY_FLAG (who, FLAG_USE_RANGE)) retval |= CAN_APPLY_RESTRICTION; if (op->type == BOW && !QUERY_FLAG (who, FLAG_USE_BOW)) retval |= CAN_APPLY_RESTRICTION; if (op->type == RING && !QUERY_FLAG (who, FLAG_USE_RING)) retval |= CAN_APPLY_RESTRICTION; if (op->type == BOW && !QUERY_FLAG (who, 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" int apply_special (object *who, object *op, int aflags) { int basic_flag = aflags & AP_BASIC_FLAGS; object *tmp, *tmp2, *skop = NULL; if (who == NULL) { LOG (llevError, "apply_special() from object without environment.\n"); return 1; } if (op->env != who) return 1; /* op is not in inventory */ /* trying to unequip op */ if (QUERY_FLAG (op, FLAG_APPLIED)) { /* always apply, so no reason to unapply */ if (basic_flag == AP_APPLY) return 0; if (!(aflags & AP_IGNORE_CURSE) && (QUERY_FLAG (op, FLAG_CURSED) || QUERY_FLAG (op, FLAG_DAMNED))) { who->failmsg (format ("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; // if the item is combat/ranged, wield the relevant slot first // to resolve conflicts. if (player *pl = who->contr) switch (op->slottype ()) { case slot_combat: who->change_weapon (pl->combat_ob); break; case slot_ranged: who->change_weapon (pl->ranged_ob); break; } 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->failmsg (format ("You don't have the body to use a %s. H", query_name (op))); return 1; } else if (i & CAN_APPLY_RESTRICTION) { who->failmsg (format ( "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) { skop = find_skill_by_name (who, op->skill); if (!skop) { who->failmsg (format ("You need the %s skill to use this item!", &op->skill)); return 1; } else /* While experience will be credited properly, we want to change the * skill so that the dam and wc get updated */ who->change_skill (skop); } 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. */ 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: //TODO: this obviously fails for players using a shorter prefix // i.e. "R" can use Ragnarok's sword. if (op->level && !op->name.starts_with (who->name)) { /* 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) insert_ob_in_ob (tmp, who); return 1; } if (!skop) { who->failmsg (format ("The %s is broken, please report this to the dungeon master!", query_name (op)));//TODO return 1; } SET_FLAG (op, FLAG_APPLIED); who->change_skill (skop); if (who->contr) who->change_weapon (who->contr->combat_ob = op); who->statusmsg (format ("You wield %s.", query_name (op))); SET_FLAG (who, FLAG_READY_WEAPON); change_abil (who, op); break; case ARMOUR: case HELMET: case SHIELD: case BOOTS: case GLOVES: case GIRDLE: case BRACERS: case CLOAK: case RING: case AMULET: SET_FLAG (op, FLAG_APPLIED); who->statusmsg (format ("You wear %s.", query_name (op))); change_abil (who, op); break; case SKILL_TOOL: // applying a skill tool also readies the skill SET_FLAG (op, FLAG_APPLIED); if (!(aflags & AP_NO_READY)) { skop = find_skill_by_name (who, op->skill); if (!skop->flag [FLAG_APPLIED]) apply_special (who, skop, AP_APPLY); } break; case SKILL: if (player *pl = who->contr) { if (IS_COMBAT_SKILL (op->subtype)) { if (skill_flags [op->subtype] & SF_NEED_WEAPON) { for (object *item = who->inv; item; item = item->below) if (item->type == WEAPON && item->flag [FLAG_APPLIED]) { if (item->skill == op->skill) { who->change_weapon (pl->combat_ob = item); goto found_weapon; } } who->failmsg (format ( "You need to apply a '%s' melee weapon before readying this skill. " "H", &op->skill )); return 1; found_weapon:; } else who->change_weapon (pl->combat_ob = op); } else if (IS_RANGED_SKILL (op->subtype)) { if (skill_flags [op->subtype] & SF_NEED_BOW) { for (object *item = who->inv; item; item = item->below) if (item->type == BOW && item->flag [FLAG_APPLIED]) { //TODO: bows should/must all have skill missile weapon right now who->change_weapon (pl->ranged_ob = item); goto found_bow; } who->failmsg ( "You need to apply a missile weapon before readying this skill. " "H" ); return 1; found_bow:; } else who->change_weapon (pl->ranged_ob = op); } if (!op->invisible) { who->statusmsg (format ( "You ready %s." "You can now use the skill: %s.", query_name (op), &op->skill )); } else who->statusmsg (format ("Readied skill: %s.", op->skill ? &op->skill : &op->name)); } else { SET_FLAG (op, FLAG_APPLIED); change_abil (who, op); who->chosen_skill = op; SET_FLAG (who, FLAG_READY_SKILL); } break; case BOW: if (op->level && !op->name.starts_with (who->name)) { who->failmsg ("The weapon does not recognize you as its owner. " "H"); if (tmp) insert_ob_in_ob (tmp, who); return 1; } /*FALLTHROUGH*/ case WAND: case ROD: case HORN: /* check for skill, alter player status */ if (!skop) { who->failmsg (format ("The %s is broken, please report this to the dungeon master!", query_name (op)));//TODO return 1; } SET_FLAG (op, FLAG_APPLIED); who->change_skill (skop); if (who->contr) { who->contr->ranged_ob = op; who->statusmsg (format ("You ready %s.", query_name (op))); if (op->type == BOW) { who->current_weapon = op; change_abil (who, op); who->statusmsg (format ("You will now fire %s with %s.", op->race ? &op->race : "nothing", query_name (op))); } } else { if (op->type == BOW) SET_FLAG (who, FLAG_READY_BOW); else SET_FLAG (who, FLAG_READY_RANGE); } break; case BUILDER: if (who->type == PLAYER) { //TODO: wtf does this do? shouldn't this be managed automatically (slots?) if (who->contr->ranged_ob && who->contr->ranged_ob->type == BUILDER) unapply_special (who, who->contr->ranged_ob, 0); who->statusmsg (format ("You ready your %s.", query_name (op))); who->contr->ranged_ob = op; } break; default: who->statusmsg (format ("You apply %s.", query_name (op))); } SET_FLAG (op, 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) SET_FLAG (op, FLAG_BEEN_APPLIED); if (QUERY_FLAG (op, FLAG_CURSED) || QUERY_FLAG (op, FLAG_DAMNED)) if (who->type == PLAYER) { who->failmsg ( "Oops, it feels deadly cold! " "H" ); SET_FLAG (op, FLAG_KNOWN_CURSED); } if (object *pl = op->visible_to ()) esrv_send_item (pl, op); return 0; } int monster_apply_special (object *who, object *op, int aflags) { if (QUERY_FLAG (op, FLAG_UNPAID) && !QUERY_FLAG (op, FLAG_APPLIED)) return 1; return apply_special (who, op, aflags); } /** * 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; CLEAR_FLAG (op, 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 (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED)) { tmp->destroy (); tmp = NULL; } } while (!tmp); tmp->x = op->x; tmp->y = op->y; SET_FLAG (tmp, FLAG_UNPAID); insert_ob_in_map (tmp, op->map, NULL, 0); identify (tmp); break; case TREASURE: if (QUERY_FLAG (op, FLAG_IS_A_TEMPLATE)) return 0; while (op->stats.hp-- > 0) create_treasure (op->randomitems, op, op->map ? GT_ENVIRONMENT : 0, op->stats.exp ? (int) op->stats.exp : op->map == NULL ? 14 : op->map->difficulty, 0); /* If we generated an object and put it in this object 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 (QUERY_FLAG (invtmp, 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 (QUERY_FLAG (tmp, 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 (QUERY_FLAG (head, 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); } /** * 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; force = get_archetype (FORCE_NAME); 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] / 2; did_one = 1; } } if (did_one) { force->set_speed (0.1); /* bigger morsel of food = longer effect time */ force->duration = food->stats.food / 5; SET_FLAG (force, FLAG_APPLIED); change_abil (who, force); insert_ob_in_ob (force, who); } else force->destroy (); /* check for hp, sp change */ if (food->stats.hp != 0) { if (QUERY_FLAG (food, 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 (QUERY_FLAG (food, 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 (); } /** * op made some mistake with a scroll, this takes care of punishment. * scroll_failure()- hacked directly from spell_failure */ 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 (); } } } 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(QUERY_FLAG(change,FLAG_USE_WEAPON)) CLEAR_FLAG(pl,FLAG_USE_WEAPON); */ if (change->name == shstr_monk) CLEAR_FLAG (pl, FLAG_USE_WEAPON); break; } } } /** * 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. */ void apply_item_transformer (object *pl, object *transformer) { object *marked; object *new_item; char *find; char *separator; int yield; char got[MAX_BUF]; int len; if (!pl || !transformer) return; marked = find_marked_object (pl); if (!marked) { pl->failmsg (format ("Use the %s with what item?", query_name (transformer))); return; } if (!marked->slaying) { pl->failmsg (format ("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->failmsg (format ("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 = strchr (find, ';')) != NULL) len = separator - find; else len = strlen (find); if (len > MAX_BUF - 1) 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->failmsg (format ("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 (); }