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