/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992,2007 Frank Tore Johansen
*
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Affero GNU General Public License
* and the GNU General Public License along with this program. If not, see
* .
*
* The authors can be reached via e-mail to
*/
/*
* Object (handling) commands
*/
#include
#include
#include
#include
#include
/*
* Object id parsing functions
*/
#define ADD_ITEM(NEW,COUNT)\
if(!first) {\
first = new objectlink;\
last=first;\
} else {\
last->next = new objectlink;\
last=last->next;\
}\
last->next=0;\
last->ob=(NEW);\
last->id=(COUNT);
/**
* Search the inventory of 'pl' for what matches best with params.
* we use item_matched_string above - this gives us consistent behaviour
* between many commands. Return the best match, or NULL if no match.
* aflag is used with apply -u , and apply -a to
* only unapply applied, or apply unapplied objects
**/
static object *
find_best_apply_object_match (object *pl, const char *params, enum apply_flag aflag)
{
object *best = 0;
int match_val = 0;
for (object *tmp = pl->inv; tmp; tmp = tmp->below)
{
if (tmp->invisible)
continue;
int tmpmatch = item_matched_string (pl, tmp, params);
if (tmpmatch > match_val)
{
if ((aflag == AP_APPLY) && (QUERY_FLAG (tmp, FLAG_APPLIED)))
continue;
if ((aflag == AP_UNAPPLY) && (!QUERY_FLAG (tmp, FLAG_APPLIED)))
continue;
match_val = tmpmatch;
best = tmp;
}
}
return best;
}
/**
* Shortcut to find_best_apply_object_match(pl, params, AF_NULL);
**/
object *
find_best_object_match (object *pl, const char *params)
{
return find_best_apply_object_match (pl, params, AP_TOGGLE);
}
static bool
can_split (object *pl, object *&op, sint32 nrof)
{
if (object *tmp = op->split (nrof ? nrof : op->number_of ()))
{
op = tmp;
return true;
}
else
{
if (op->nrof > 1)
new_draw_info_format (NDI_UNIQUE, 0, pl, "There are only %d %s.", op->nrof, &op->name_pl);
else
new_draw_info_format (NDI_UNIQUE, 0, pl, "There is only one %s.", &op->name);
return false;
}
}
int
command_uskill (object *pl, char *params)
{
if (!params)
{
new_draw_info (NDI_UNIQUE, 0, pl, "Usage: use_skill ");
return 0;
}
return use_skill (pl, params);
}
int
command_rskill (object *pl, char *params)
{
object *skill;
if (!params)
{
new_draw_info (NDI_UNIQUE, 0, pl, "Usage: ready_skill ");
return 0;
}
skill = find_skill_by_name_fuzzy (pl, params);
if (!skill)
{
new_draw_info_format (NDI_UNIQUE, 0, pl, "You have no knowledge of the skill %s", params);
return 0;
}
pl->change_skill (0);
apply_special (pl, skill, AP_APPLY);
return 1;
}
/* A little special because we do want to pass the full params along
* as it includes the object to throw.
*/
int
command_throw (object *op, char *params)
{
if (object *skop = find_skill_by_name (op, skill_names[SK_THROWING]))
return do_skill (op, op, skop, op->facing, params);
else
new_draw_info (NDI_UNIQUE, 0, op, "You have no knowledge of the skill throwing.");
return 0;
}
int
command_apply (object *op, char *params)
{
if (!params)
{
player_apply_below (op);
return 0;
}
else
{
apply_flag aflag = (apply_flag)0;
while (*params == ' ')
params++;
if (!strncmp (params, "-a ", 3))
{
aflag = AP_APPLY;
params += 3;
}
if (!strncmp (params, "-u ", 3))
{
aflag = AP_UNAPPLY;
params += 3;
}
while (*params == ' ')
params++;
if (object *inv = find_best_apply_object_match (op, params, aflag))
player_apply (op, inv, aflag, 0);
else
new_draw_info_format (NDI_UNIQUE, 0, op, "Could not find any match to the %s.", params);
}
return 0;
}
/*
* Check if an item op can be put into a sack. If pl exists then tell
* a player the reason of failure.
* returns 1 if it will fit, 0 if it will not. nrof is the number of
* objects (op) we want to put in. We specify it separately instead of
* using op->nrof because often times, a player may have specified a
* certain number of objects to drop, so we can pass that number, and
* not need to use split_ob and stuff.
*/
int
sack_can_hold (object *pl, object *sack, object *op, uint32 nrof)
{
if (!QUERY_FLAG (sack, FLAG_APPLIED))
{
new_draw_info_format (NDI_UNIQUE, 0, pl, "The %s is not active.", query_name (sack));
return 0;
}
if (sack == op)
{
new_draw_info_format (NDI_UNIQUE, 0, pl, "You can't put the %s into itself.", query_name (sack));
return 0;
}
if (sack->race && (sack->race != op->race || op->type == CONTAINER || (sack->stats.food && sack->stats.food != op->type)))
{
new_draw_info_format (NDI_UNIQUE, 0, pl, "You can put only %s into the %s.", &sack->race, query_name (sack));
return 0;
}
if (op->type == SPECIAL_KEY && sack->slaying && op->slaying)
{
new_draw_info_format (NDI_UNIQUE, 0, pl, "You can't put the key into %s.", query_name (sack));
return 0;
}
if (sack->weight_limit && (sint32) (sack->carrying + (nrof ? nrof : 1) *
(op->weight + (op->type == CONTAINER ? (op->carrying * op->stats.Str) : 0))
* (100 - sack->stats.Str) / 100) > sack->weight_limit)
{
new_draw_info_format (NDI_UNIQUE, 0, pl, "That won't fit in the %s!", query_name (sack));
return 0;
}
/* All other checks pass, must be OK */
return 1;
}
/* Pick up commands follow */
/* pl = player (not always - monsters can use this now)
* op is the object to put tmp into,
* tmp is the object to pick up, nrof is the number to
* pick up (0 means all of them)
*/
static void
pick_up_object (object *pl, object *op, object *tmp, int nrof)
{
object *env = tmp->env;
uint32 weight, effective_weight_limit;
int tmp_nrof = tmp->number_of ();
/* IF the player is flying & trying to take the item out of a container
* that is in his inventory, let him. tmp->env points to the container
* (sack, luggage, etc), tmp->env->env then points to the player (nested
* containers not allowed as of now)
*/
if ((pl->move_type & MOVE_FLYING) && !QUERY_FLAG (pl, FLAG_WIZ) && tmp->in_player () != pl)
{
pl->failmsg ("You are levitating, you can't reach the ground! "
"H");
return;
}
if (QUERY_FLAG (tmp, FLAG_NO_DROP))
return;
if (nrof > tmp_nrof || nrof <= 0)
nrof = tmp_nrof;
/* Figure out how much weight this object will add to the player */
weight = tmp->weight * nrof;
if (tmp->inv)
weight += tmp->carrying * (100 - tmp->stats.Str) / 100;
effective_weight_limit = weight_limit [min (MAX_STAT, pl->stats.Str)];
if ((pl->weight + pl->carrying + weight) > effective_weight_limit)
{
new_draw_info (0, 0, pl, "That item is too heavy for you to pick up.");
return;
}
if (!can_split (pl, tmp, nrof))
return;
if (QUERY_FLAG (tmp, FLAG_UNPAID))
{
tmp->flag.reset (FLAG_UNPAID);
new_draw_info_format (NDI_UNIQUE, 0, pl, "%s will cost you %s.", query_name (tmp), query_cost_string (tmp, pl, F_BUY | F_SHOP));
tmp->flag.set (FLAG_UNPAID);
}
else
new_draw_info_format (NDI_UNIQUE, 0, pl, "You pick up the %s.", query_name (tmp));
op->insert (tmp);
}
/* modified slightly to allow monsters use this -b.t. 5-31-95 */
void
pick_up (object *op, object *alt)
{
int need_fix_tmp = 0;
object *tmp = NULL;
maptile *tmp_map = NULL;
int count;
/* Decide which object to pick. */
if (alt)
{
if (!can_pick (op, alt))
{
new_draw_info_format (NDI_UNIQUE, 0, op, "You can't pick up the %s.", &alt->name);
goto leave;
}
tmp = alt;
}
else
{
if (op->below == NULL || !can_pick (op, op->below))
{
new_draw_info (NDI_UNIQUE, 0, op, "There is nothing to pick up here.");
goto leave;
}
tmp = op->below;
}
/* Try to catch it. */
tmp_map = tmp->map;
tmp = stop_item (tmp);
if (tmp == NULL)
goto leave;
need_fix_tmp = 1;
if (!can_pick (op, tmp))
goto leave;
if (op->type == PLAYER)
{
count = op->contr->count;
if (count == 0)
count = tmp->nrof;
}
else
count = tmp->nrof;
/* container is open, so use it */
if (tmp->flag [FLAG_STARTEQUIP])
alt = op;
else if ((alt = op->container_ ()))
{
if (alt != tmp->env && !sack_can_hold (op, alt, tmp, count))
goto leave;
}
else
{ /* non container pickup */
for (alt = op->inv; alt; alt = alt->below)
if (alt->type == CONTAINER && QUERY_FLAG (alt, FLAG_APPLIED) &&
alt->race && alt->race == tmp->race && sack_can_hold (NULL, alt, tmp, count))
break; /* perfect match */
if (!alt)
for (alt = op->inv; alt; alt = alt->below)
if (alt->type == CONTAINER && QUERY_FLAG (alt, FLAG_APPLIED) && sack_can_hold (NULL, alt, tmp, count))
break; /* General container comes next */
if (!alt)
alt = op; /* No free containers */
}
if (tmp->env == alt)
{
/* here it could be possible to check rent,
* if someone wants to implement it
*/
alt = op;
}
#ifdef PICKUP_DEBUG
LOG (llevDebug, "Pick_up(): %s picks %s (%d) and inserts it %s.\n", op->name, tmp->name, op->contr->count, alt->name);
#endif
/* startequip items are not allowed to be put into containers: */
if (op->type == PLAYER && alt->type == CONTAINER && QUERY_FLAG (tmp, FLAG_STARTEQUIP))
{
new_draw_info (NDI_UNIQUE, 0, op, "This object cannot be put into containers!");
goto leave;
}
pick_up_object (op, alt, tmp, count);
if (tmp->destroyed () || tmp->env)
need_fix_tmp = 0;
if (op->type == PLAYER)
op->contr->count = 0;
goto leave;
leave:
if (need_fix_tmp)
fix_stopped_item (tmp, tmp_map, op);
}
/* This takes (picks up) and item. op is the player
* who issued the command. params is a string to
* match against the item name. Basically, always
* returns zero, but that should be improved.
*/
int
command_take (object *op, char *params)
{
object *tmp, *next;
if (op->container_ ())
tmp = op->container_ ()->inv;
else
{
tmp = op->above;
if (tmp)
while (tmp->above)
tmp = tmp->above;
if (!tmp)
tmp = op->below;
}
if (!tmp)
{
new_draw_info (NDI_UNIQUE, 0, op, "Nothing to take!");
return 0;
}
/* Makes processing easier */
if (params && *params == '\0')
params = 0;
int cnt = MAX_ITEM_PER_ACTION;
while (tmp)
{
next = tmp->below;
if (tmp->invisible)
{
tmp = next;
continue;
}
/* This following two if and else if could be merged into line
* but that probably will make it more difficult to read, and
* not make it any more efficient
*/
if (params && item_matched_string (op, tmp, params))
{
if (--cnt < 0) break;
pick_up (op, tmp);
}
else if (can_pick (op, tmp) && !params)
{
if (--cnt < 0) break;
pick_up (op, tmp);
break;
}
tmp = next;
}
if (cnt < 0)
{
op->failmsg ("Couldn't pick up so many items at once.");
return 0;
}
if (!params && !tmp)
{
for (tmp = op->below; tmp; tmp = tmp->below)
if (!tmp->invisible)
{
new_draw_info_format (NDI_UNIQUE, 0, op, "You can't pick up a %s.", &tmp->name);
break;
}
if (!tmp)
new_draw_info (NDI_UNIQUE, 0, op, "There is nothing to pick up.");
}
return 0;
}
/*
* This function was part of drop, now is own function.
* Player 'op' tries to put object 'tmp' into sack 'sack',
* if nrof is non zero, then nrof objects is tried to put into sack.
*
* Note that the 'sack' in question can now be a transport,
* so this function isn't named very good anymore.
*/
void
put_object_in_sack (object *op, object *sack, object *tmp, uint32 nrof)
{
object *tmp2, *sack2;
char buf[MAX_BUF];
if (sack == tmp)
return; /* Can't put an object in itself */
if (QUERY_FLAG (tmp, FLAG_STARTEQUIP) || QUERY_FLAG (tmp, FLAG_NO_DROP))
{
new_draw_info_format (NDI_UNIQUE, 0, op, "You cannot put the %s in the %s.", query_name (tmp), query_name (sack));
return;
}
if (tmp->type == CONTAINER && tmp->inv)
{
/* Eneq(@csd.uu.se): If the object to be dropped is a container
* we instead move the contents of that container into the active
* container, this is only done if the object has something in it.
*/
sack2 = tmp;
new_draw_info_format (NDI_UNIQUE, 0, op, "You move the items from %s into %s.", query_name (tmp), query_name (sack));
for (tmp2 = tmp->inv; tmp2; tmp2 = tmp)
{
tmp = tmp2->below;
if ((sack->type == CONTAINER && sack_can_hold (op, op->container_ (), tmp2, tmp2->nrof)))
put_object_in_sack (op, sack, tmp2, 0);
else
{
sprintf (buf, "Your %s fills up.", query_name (sack));
new_draw_info (NDI_UNIQUE, 0, op, buf);
break;
}
}
return;
}
/* Don't worry about this for containers - our caller should have
* already checked this.
*/
if ((sack->type == CONTAINER) && !sack_can_hold (op, sack, tmp, (nrof ? nrof : tmp->nrof)))
return;
if (QUERY_FLAG (tmp, FLAG_APPLIED))
if (apply_special (op, tmp, AP_UNAPPLY | AP_NO_MERGE))
return;
/* we want to put some portion of the item into the container */
if (!can_split (op, tmp, nrof))
return;
new_draw_info_format (NDI_UNIQUE, 0, op, "You put the %s in %s.", query_name (tmp), query_name (sack));
sack->insert (tmp);
}
/* In contrast to drop_object (op, tmp, nrof) above this function takes the
* already split off object, and feeds it to the event handlers and does
* other magic with it.
*
* is the object that dropped this object and is the
* object that was dropped.
*
* Make sure to check what happened with after this function returns!
* Otherwise you may leak this object.
*/
void
drop_object (object *dropper, object *obj)
{
if (INVOKE_OBJECT (DROP, obj, ARG_OBJECT (dropper)))
return;
if (obj->destroyed () || obj->is_inserted ())
return;
if (QUERY_FLAG (obj, FLAG_STARTEQUIP))
{
dropper->statusmsg (format ("You drop the %s.", query_name (obj)));
dropper->statusmsg ("The god who lent it to you retrieves it.");
obj->destroy ();
dropper->update_stats ();
return;
}
for (object *floor = GET_MAP_OB (dropper->map, dropper->x, dropper->y);
floor;
floor = floor->above)
if (INVOKE_OBJECT (DROP_ON, floor, ARG_OBJECT (obj), ARG_OBJECT (dropper)))
return;
if (obj->destroyed () || obj->is_inserted ())
return;
if (is_in_shop (dropper) && !QUERY_FLAG (obj, FLAG_UNPAID) && obj->type != MONEY)
if (!sell_item (obj, dropper))
return;
if (!obj->can_drop_at (dropper->map, dropper->x, dropper->y, dropper))
return;
/* If nothing special happened with this object, the default action is to
* insert it below the dropper:
*/
obj->x = dropper->x;
obj->y = dropper->y;
insert_ob_in_map (obj, dropper->map, dropper, INS_BELOW_ORIGINATOR);
}
/*
* This function was part of drop, now is own function.
* Player 'op' tries to drop object 'tmp', if tmp is non zero, then
* nrof objects are tried to drop.
* This is used when dropping objects onto the floor.
*/
void
drop_object (object *op, object *tmp, uint32 nrof)
{
if (QUERY_FLAG (tmp, FLAG_NO_DROP))
return;
if (QUERY_FLAG (tmp, FLAG_APPLIED))
if (apply_special (op, tmp, AP_UNAPPLY | AP_NO_MERGE))
return; /* can't unapply it */
/* We are only dropping some of the items. We split the current object
* off
*/
if (!can_split (op, tmp, nrof))
return;
drop_object (op, tmp);
if (!tmp->destroyed () && !tmp->is_inserted ())
{
// if nothing happened with the object we give it back
op->insert (tmp);
}
}
void
drop (object *op, object *tmp)
{
/* Hopeful fix for disappearing objects when dropping from a container -
* somehow, players get an invisible object in the container, and the
* old logic would skip over invisible objects - works fine for the
* playes inventory, but drop inventory wants to use the next value.
*/
if (tmp->invisible)
{
/* if the following is the case, it must be in an container. */
if (tmp->env && tmp->env->type != PLAYER)
{
/* Just toss the object - probably shouldn't be hanging
* around anyways
*/
tmp->destroy ();
return;
}
else
while (tmp && tmp->invisible)
tmp = tmp->below;
}
if (!tmp)
{
new_draw_info (NDI_UNIQUE, 0, op, "You don't have anything to drop.");
return;
}
if (QUERY_FLAG (tmp, FLAG_INV_LOCKED))
{
new_draw_info (NDI_UNIQUE, 0, op, "This item is locked");
return;
}
if (QUERY_FLAG (tmp, FLAG_NO_DROP))
{
#if 0
/* Eneq(@csd.uu.se): Objects with NO_DROP defined can't be dropped. */
new_draw_info (NDI_UNIQUE, 0, op, "This item can't be dropped.");
#endif
return;
}
if (op->type == PLAYER && op->contr->last_used == tmp)
op->contr->last_used = tmp->below ? tmp->below
: tmp->above ? tmp->above
: (object *)0;
if (op->container_ ())
{
if (op->type == PLAYER)
put_object_in_sack (op, op->container_ (), tmp, op->contr->count);
else
put_object_in_sack (op, op->container_ (), tmp, 0);
}
else
{
if (op->type == PLAYER)
drop_object (op, tmp, op->contr->count);
else
drop_object (op, tmp, 0);
}
if (op->type == PLAYER)
op->contr->count = 0;
}
/* Command will drop all items that have not been locked */
int
command_dropall (object *op, char *params)
{
if (op->inv == NULL)
{
new_draw_info (NDI_UNIQUE, 0, op, "Nothing to drop!");
return 0;
}
object *curinv = op->inv;
object *nextinv;
/*
This is the default. Drops everything not locked or considered
not something that should be dropped.
*/
/*
Care must be taken that the next item pointer is not to money as
the drop() routine will do unknown things to it when dropping
in a shop. --Tero.Pelander@utu.fi
*/
int cnt = MAX_ITEM_PER_ACTION;
if (!params)
{
while (curinv)
{
nextinv = curinv->below;
while (nextinv && nextinv->type == MONEY)
nextinv = nextinv->below;
if (!QUERY_FLAG (curinv, FLAG_INV_LOCKED)
&& curinv->type != MONEY
&& curinv->type != FOOD
&& curinv->type != KEY
&& curinv->type != SPECIAL_KEY
&& curinv->type != GEM
&& !curinv->invisible
&& (curinv->type != CONTAINER || op->container_ () != curinv))
{
drop (op, curinv);
if (--cnt <= 0) break;
}
curinv = nextinv;
}
}
else if (strcmp (params, "weapons") == 0)
{
while (curinv)
{
nextinv = curinv->below;
while (nextinv && nextinv->type == MONEY)
nextinv = nextinv->below;
if (!QUERY_FLAG (curinv, FLAG_INV_LOCKED) && ((curinv->type == WEAPON) || (curinv->type == BOW) || (curinv->type == ARROW)))
{
drop (op, curinv);
if (--cnt <= 0) break;
}
curinv = nextinv;
}
}
else if (strcmp (params, "armor") == 0 || strcmp (params, "armour") == 0)
{
while (curinv)
{
nextinv = curinv->below;
while (nextinv && nextinv->type == MONEY)
nextinv = nextinv->below;
if (!QUERY_FLAG (curinv, FLAG_INV_LOCKED) && ((curinv->type == ARMOUR) || curinv->type == SHIELD || curinv->type == HELMET))
{
drop (op, curinv);
if (--cnt <= 0) break;
}
curinv = nextinv;
}
}
else if (strcmp (params, "misc") == 0)
{
while (curinv)
{
nextinv = curinv->below;
while (nextinv && nextinv->type == MONEY)
nextinv = nextinv->below;
if (!QUERY_FLAG (curinv, FLAG_INV_LOCKED) && !QUERY_FLAG (curinv, FLAG_APPLIED))
{
switch (curinv->type)
{
case HORN:
case BOOK:
case SPELLBOOK:
case GIRDLE:
case AMULET:
case RING:
case CLOAK:
case BOOTS:
case GLOVES:
case BRACERS:
case SCROLL:
case ARMOUR_IMPROVER:
case WEAPON_IMPROVER:
case WAND:
case ROD:
case POTION:
drop (op, curinv);
curinv = nextinv;
break;
default:
curinv = nextinv;
break;
}
if (--cnt <= 0) break;
}
curinv = nextinv;
}
}
if (cnt <= 0)
op->failmsg ("Only dropped some items, can't drop that many items at once.");
/* draw_look(op);*/
return 0;
}
/* This function tries to drop all objects in the vector.
* is the object that wants to drop them.
* can be a 0 pointer or a pointer to the maximum number of
* drop operations to perform.
*
* Returns true if at least one item was dropped.
*/
static bool
drop_vector (object *dropper, vector