1437 lines
48 KiB
C
1437 lines
48 KiB
C
/*
|
|
* static char *rcsid_shop_c =
|
|
* "$Id: shop.c 11578 2009-02-23 22:02:27Z lalo $";
|
|
*/
|
|
|
|
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2002 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 at crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Those functions deal with shop handling, bargaining, things like that.
|
|
* @todo
|
|
* isn't there redundance with pay_for_item(), get_payment(), pay_for_amount()?
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <global.h>
|
|
#include <spells.h>
|
|
#include <skills.h>
|
|
#include <living.h>
|
|
#include <newclient.h>
|
|
#ifndef __CEXTRACT__
|
|
#include <sproto.h>
|
|
#endif
|
|
#include <math.h>
|
|
|
|
/**
|
|
* This is a measure of how effective store specialisation is. A general store
|
|
* will offer this proportion of the 'maximum' price, a specialised store will
|
|
* offer a range of prices around it such that the maximum price is always one
|
|
* therefore making this number higher, makes specialisation less effective.
|
|
* setting this value above 1 or to a negative value would have interesting,
|
|
* (though not useful) effects.
|
|
*/
|
|
#define SPECIALISATION_EFFECT 0.5
|
|
|
|
/** Price a shopkeeper will give to someone they disapprove of.*/
|
|
#define DISAPPROVAL_RATIO 0.2
|
|
|
|
/** Price a shopkeeper will give someone they neither like nor dislike */
|
|
#define NEUTRAL_RATIO 0.8
|
|
|
|
static uint64 pay_from_container(object *pl, object *pouch, uint64 to_pay);
|
|
static uint64 value_limit(uint64 val, int quantity, const object *who, int isshop);
|
|
static double shop_specialisation_ratio(const object *item, const mapstruct *map);
|
|
static double shop_greed(const mapstruct *map);
|
|
|
|
#define NUM_COINS 5 /**< Number of coin types */
|
|
|
|
#define LARGEST_COIN_GIVEN 2 /**< Never give amber or jade, but accept them */
|
|
|
|
/** Coins to use for shopping. */
|
|
static const char *const coins[] = {
|
|
"ambercoin",
|
|
"jadecoin",
|
|
"platinacoin",
|
|
"goldcoin",
|
|
"silvercoin",
|
|
NULL
|
|
};
|
|
|
|
/**
|
|
* Return the price of an item for a character.
|
|
*
|
|
* Price will vary based on the shop's specialization ration, the player's
|
|
* approval rate, ...
|
|
*
|
|
* Added F_TRUE flag to define.h to mean that the price should not
|
|
* be adjusted by players charisma. With F_TRUE, it returns the amount
|
|
* that the item is worth, if it was sold, but unadjusted by charisma.
|
|
* This is needed for alchemy, to to determine what value of gold nuggets
|
|
* should be given (the gold nuggets, when sold, will have the adjustment
|
|
* by charisma done at that time). NULL could have been passed as the
|
|
* who parameter, but then the adjustment for expensive items (>10000)
|
|
* would not be done.
|
|
*
|
|
* Added F_APPROX flag, which means that the price returned should be
|
|
* wrong by an amount related to the player's bargaining skill.
|
|
*
|
|
* Added F_SHOP flag to mean that the specialisation of the shop on the
|
|
* player's current map should be taken into account when determining
|
|
* the price. Shops that specialise in what is being traded will give
|
|
* better prices than those that do not.
|
|
*
|
|
* CF 0.91.4 - This function got changed around a bit. Now the
|
|
* number of object is multiplied by the value early on. This fixes
|
|
* problems with items worth very little. What happened before is that
|
|
* various divisions took place, the value got rounded to 0 (Being an
|
|
* int), and thus remained 0.
|
|
*
|
|
* Mark Wedel (mwedel@pyramid.com)
|
|
*
|
|
* @param tmp
|
|
* object we're querying the price of.
|
|
* @param who
|
|
* who is inquiring. Can be NULL, only meaningful if player.
|
|
* @param flag
|
|
* combination of @ref F_xxx "F_xxx" flags.
|
|
* @return
|
|
*/
|
|
uint64 query_cost(const object *tmp, object *who, int flag) {
|
|
uint64 val;
|
|
int number; /* used to better calculate value */
|
|
int no_bargain;
|
|
int identified;
|
|
int not_cursed;
|
|
int approximate;
|
|
int shop;
|
|
float diff;
|
|
float ratio;
|
|
const char *key;
|
|
|
|
no_bargain = flag&F_NO_BARGAIN;
|
|
identified = flag&F_IDENTIFIED;
|
|
not_cursed = flag&F_NOT_CURSED;
|
|
approximate = flag&F_APPROX;
|
|
shop = flag&F_SHOP;
|
|
flag &= ~(F_NO_BARGAIN|F_IDENTIFIED|F_NOT_CURSED|F_APPROX|F_SHOP);
|
|
|
|
number = tmp->nrof;
|
|
if (number == 0)
|
|
number = 1;
|
|
|
|
if ((key = get_ob_key_value(tmp, "price_adjustment")) != NULL) {
|
|
ratio = atof(key);
|
|
return tmp->value*number*ratio;
|
|
}
|
|
if ((flag == F_BUY) && ((key = get_ob_key_value(tmp, "price_adjustment_buy")) != NULL)) {
|
|
ratio = atof(key);
|
|
return tmp->value*number*ratio;
|
|
}
|
|
if ((flag == F_SELL) && ((key = get_ob_key_value(tmp, "price_adjustment_sell")) != NULL)) {
|
|
ratio = atof(key);
|
|
return tmp->value*number*ratio;
|
|
}
|
|
|
|
if (tmp->type == MONEY)
|
|
return (tmp->nrof*tmp->value);
|
|
if (tmp->type == GEM) {
|
|
if (flag == F_TRUE)
|
|
return number*tmp->value;
|
|
if (flag == F_BUY)
|
|
return (1.03*tmp->nrof*tmp->value);
|
|
if (flag == F_SELL)
|
|
return (0.97*tmp->nrof*tmp->value);
|
|
LOG(llevError, "Query_cost: Gem type with unknown flag : %d\n", flag);
|
|
return 0;
|
|
}
|
|
if (QUERY_FLAG(tmp, FLAG_IDENTIFIED)
|
|
|| !need_identify(tmp)
|
|
|| identified) {
|
|
if (!not_cursed
|
|
&& (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)))
|
|
return 0;
|
|
else
|
|
val = tmp->value*number;
|
|
/* This area deals with objects that are not identified, but can be */
|
|
} else {
|
|
if (tmp->arch != NULL) {
|
|
if (flag == F_BUY) {
|
|
LOG(llevError, "Asking for buy-value of unidentified object.\n");
|
|
val = tmp->arch->clone.value*50*number;
|
|
} else { /* Trying to sell something, or get true value */
|
|
if (tmp->type == POTION)
|
|
val = number*1000; /* Don't want to give anything away */
|
|
else {
|
|
/* Get 2/3 value for applied objects, 1/3 for totally
|
|
* unknown objects
|
|
*/
|
|
if (QUERY_FLAG(tmp, FLAG_BEEN_APPLIED))
|
|
val = number*tmp->arch->clone.value*2/3;
|
|
else
|
|
val = number*tmp->arch->clone.value/3;
|
|
}
|
|
}
|
|
} else { /* No archetype with this object */
|
|
LOG(llevDebug, "In sell item: Have object with no archetype: %s\n", tmp->name);
|
|
if (flag == F_BUY) {
|
|
LOG(llevError, "Asking for buy-value of unidentified object without arch.\n");
|
|
val = number*tmp->value*10;
|
|
} else
|
|
val = number*tmp->value/5;
|
|
}
|
|
}
|
|
|
|
/* If the item has been applied or identifed or does not need to be
|
|
* identified, AND the object is magical and the archetype is non
|
|
* magical, then change values accordingly. The tmp->arch==NULL is
|
|
* really just a check to prevent core dumps for when it checks
|
|
* tmp->arch->clone.magic for any magic. The check for archetype
|
|
* magic is to not give extra money for archetypes that are by
|
|
* default magical. This is because the archetype value should have
|
|
* already figured in that value.
|
|
*/
|
|
if ((QUERY_FLAG(tmp, FLAG_IDENTIFIED) || !need_identify(tmp) || identified || QUERY_FLAG(tmp, FLAG_BEEN_APPLIED))
|
|
&& tmp->magic
|
|
&& (tmp->arch == NULL || !tmp->arch->clone.magic)) {
|
|
if (tmp->magic > 0)
|
|
val *= (3*tmp->magic*tmp->magic*tmp->magic);
|
|
else
|
|
/* Note that tmp->magic is negative, so that this
|
|
* will actually be something like val /=2, /=3, etc.
|
|
*/
|
|
val /= (1-tmp->magic);
|
|
}
|
|
|
|
if (tmp->type == WAND) {
|
|
/* Value of the wand is multiplied by the number of
|
|
* charges. the treasure code already sets up the value
|
|
* 50 charges is used as the baseline.
|
|
*/
|
|
if (QUERY_FLAG(tmp, FLAG_IDENTIFIED)
|
|
|| !need_identify(tmp)
|
|
|| identified)
|
|
val = (val*tmp->stats.food)/50;
|
|
else /* if not identified, presume one charge */
|
|
val /= 50;
|
|
}
|
|
|
|
/* Limit amount of money you can get for really great items. */
|
|
if (flag == F_TRUE || flag == F_SELL)
|
|
val = value_limit(val, number, who, shop);
|
|
|
|
/* we need to multiply these by 4.0 to keep buy costs roughly the same
|
|
* (otherwise, you could buy a potion of charisma for around 400 pp.
|
|
* Arguable, the costs in the archetypes should be updated to better
|
|
* reflect values (potion charisma list for 1250 gold)
|
|
*/
|
|
val *= 4;
|
|
|
|
/* This modification is for bargaining skill.
|
|
* Now only players with max level in bargaining
|
|
* AND Cha = 30 will get optimal price.
|
|
* Thus charisma will never get useless.
|
|
* -b.e. edler@heydernet.de
|
|
*/
|
|
|
|
if (who != NULL && who->type == PLAYER) {
|
|
int lev_bargain = 0;
|
|
int lev_identify = 0;
|
|
int idskill1 = 0;
|
|
int idskill2 = 0;
|
|
const typedata *tmptype;
|
|
|
|
/* ratio determines how much of the price modification
|
|
* will come from the basic stat charisma
|
|
* the rest will come from the level in bargaining skill
|
|
*/
|
|
ratio = 0.5;
|
|
tmptype = get_typedata(tmp->type);
|
|
|
|
if (find_skill_by_number(who, SK_BARGAINING)) {
|
|
lev_bargain = find_skill_by_number(who, SK_BARGAINING)->level;
|
|
}
|
|
if (tmptype) {
|
|
idskill1 = tmptype->identifyskill;
|
|
if (idskill1) {
|
|
idskill2 = tmptype->identifyskill2;
|
|
if (find_skill_by_number(who, idskill1)) {
|
|
lev_identify = find_skill_by_number(who, idskill1)->level;
|
|
}
|
|
if (idskill2 && find_skill_by_number(who, idskill2)) {
|
|
lev_identify += find_skill_by_number(who, idskill2)->level;
|
|
}
|
|
}
|
|
} else
|
|
LOG(llevError, "Query_cost: item %s hasn't got a valid type\n", tmp->name);
|
|
if (!no_bargain && (lev_bargain > 0))
|
|
diff = (0.8-0.6*((lev_bargain+settings.max_level*0.05)/(settings.max_level*1.05)));
|
|
else
|
|
diff = 0.8;
|
|
|
|
diff *= 1-ratio;
|
|
|
|
/* Diff is now a float between 0.2 and 0.8 */
|
|
diff += (cha_bonus[who->stats.Cha]-1)/(1+cha_bonus[who->stats.Cha])*ratio;
|
|
|
|
if (flag == F_BUY)
|
|
val = (val*(long)(1000*(1+diff)))/1000;
|
|
else if (flag == F_SELL)
|
|
val = (val*(long)(1000*(1-diff)))/1000;
|
|
|
|
/* If we are approximating, then the value returned should
|
|
* be allowed to be wrong however merely using a random number
|
|
* each time will not be sufficient, as then multiple examinations
|
|
* would give different answers, so we'll use the count instead.
|
|
* By taking the sine of the count, a value between -1 and 1 is
|
|
* generated, we then divide by the square root of the bargaining
|
|
* skill and the appropriate identification skills, so that higher
|
|
* level players get better estimates. (We need a +1 there to
|
|
* prevent dividing by zero.)
|
|
*/
|
|
if (approximate)
|
|
val = (sint64)val+(sint64)((sint64)val*(sin(tmp->count)/sqrt(lev_bargain+lev_identify*2+1.0)));
|
|
}
|
|
|
|
/* I don't think this should really happen - if it does,
|
|
* it indicates an overflow of diff above. That should only
|
|
* happen if we are selling objects - in that case, the person
|
|
* just gets no money.
|
|
*/
|
|
if ((sint64)val < 0)
|
|
val = 0;
|
|
|
|
/* Unidentified stuff won't sell for more than 60gp */
|
|
if (flag == F_SELL
|
|
&& !QUERY_FLAG(tmp, FLAG_IDENTIFIED)
|
|
&& need_identify(tmp)
|
|
&& !identified) {
|
|
val = MIN(val, 600);
|
|
}
|
|
|
|
/* if in a shop, check how the type of shop should affect the price */
|
|
if (shop && who) {
|
|
if (flag == F_SELL)
|
|
val = (sint64)val*shop_specialisation_ratio(tmp, who->map)
|
|
*shopkeeper_approval(who->map, who)/shop_greed(who->map);
|
|
else if (flag == F_BUY) {
|
|
/*
|
|
* When buying, if the item was sold by another player, it is
|
|
* ok to let the item be sold cheaper, according to the
|
|
* specialisation of the shop. If a player sold an item here,
|
|
* then his sale price was multiplied by the specialisation
|
|
* ratio, to do the same to the buy price will not generate
|
|
* extra money. However, the same is not true of generated
|
|
* items, these have to /divide/ by the specialisation, so
|
|
* that the price is never less than what they could
|
|
* be sold for (otherwise players could camp map resets to
|
|
* make money).
|
|
* In game terms, a non-specialist shop might not recognise
|
|
* the true value of the items it sells (much like how people
|
|
* sometimes find antiques in a junk shop in real life).
|
|
*/
|
|
if (QUERY_FLAG(tmp, FLAG_PLAYER_SOLD))
|
|
val = (sint64)val*shop_greed(who->map)
|
|
*shop_specialisation_ratio(tmp, who->map)
|
|
/shopkeeper_approval(who->map, who);
|
|
else
|
|
val = (sint64)val*shop_greed(who->map)
|
|
/(shop_specialisation_ratio(tmp, who->map)
|
|
*shopkeeper_approval(who->map, who));
|
|
}
|
|
/* We will also have an extra 0-5% variation between shops of
|
|
* the same type for valuable items (below a value of 50 this
|
|
* effect wouldn't be very meaningful, and could give fun with
|
|
* rounding.
|
|
*/
|
|
if (who->map->path != NULL && val > 50)
|
|
val = (sint64)val+0.05*(sint64)val*cos(tmp->count+strlen(who->map->path));
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Find the coin type that is worth more than 'c'. Starts at the
|
|
* cointype placement.
|
|
*
|
|
* @param c
|
|
* value we're searching.
|
|
* @param cointype
|
|
* first coin type to search.
|
|
* @return
|
|
* coin archetype, NULL if none found.
|
|
*/
|
|
static archetype *find_next_coin(uint64 c, int *cointype) {
|
|
archetype *coin;
|
|
|
|
do {
|
|
if (coins[*cointype] == NULL)
|
|
return NULL;
|
|
coin = find_archetype(coins[*cointype]);
|
|
if (coin == NULL)
|
|
return NULL;
|
|
*cointype += 1;
|
|
} while (coin->clone.value > c);
|
|
|
|
return coin;
|
|
}
|
|
|
|
/**
|
|
* Converts a price to number of coins.
|
|
*
|
|
* While cost is 64 bit, the number of any coin is still really
|
|
* limited to 32 bit (size of nrof field). If it turns out players
|
|
* have so much money that they have more than 2 billion platinum
|
|
* coins, there are certainly issues - the easiest fix at that
|
|
* time is to add a higher denomination (mithril piece with
|
|
* 10,000 silver or something)
|
|
*
|
|
* @param cost
|
|
* value to transform to currency.
|
|
* @param buf
|
|
* buffer to append to, if NULL a new one is returned.
|
|
* @return
|
|
* buffer containing the price, either buf or if NULL a new StringBuffer.
|
|
*/
|
|
static StringBuffer *cost_string_from_value(uint64 cost, StringBuffer *buf) {
|
|
archetype *coin, *next_coin;
|
|
uint32 num;
|
|
int cointype = LARGEST_COIN_GIVEN;
|
|
|
|
if (!buf)
|
|
buf = stringbuffer_new();
|
|
|
|
coin = find_next_coin(cost, &cointype);
|
|
if (coin == NULL) {
|
|
stringbuffer_append_string(buf, "nothing");
|
|
return buf;
|
|
}
|
|
|
|
num = cost/coin->clone.value;
|
|
/* so long as nrof is 32 bit, this is true.
|
|
* If it takes more coins than a person can possibly carry, this
|
|
* is basically true.
|
|
*/
|
|
if ((cost/coin->clone.value) > UINT32_MAX) {
|
|
stringbuffer_append_string(buf, "an unimaginable sum of money.");
|
|
return buf;
|
|
}
|
|
|
|
cost -= (uint64)num*(uint64)coin->clone.value;
|
|
if (num == 1)
|
|
stringbuffer_append_printf(buf, "1 %s", coin->clone.name);
|
|
else
|
|
stringbuffer_append_printf(buf, "%u %ss", num, coin->clone.name);
|
|
|
|
next_coin = find_next_coin(cost, &cointype);
|
|
if (next_coin == NULL)
|
|
return buf;
|
|
|
|
do {
|
|
coin = next_coin;
|
|
num = cost/coin->clone.value;
|
|
cost -= (uint64)num*(uint64)coin->clone.value;
|
|
|
|
if (cost == 0)
|
|
next_coin = NULL;
|
|
else
|
|
next_coin = find_next_coin(cost, &cointype);
|
|
|
|
if (next_coin) {
|
|
/* There will be at least one more string to add to the list,
|
|
* use a comma.
|
|
*/
|
|
stringbuffer_append_string(buf, ", ");
|
|
} else {
|
|
stringbuffer_append_string(buf, " and ");
|
|
}
|
|
if (num == 1)
|
|
stringbuffer_append_printf(buf, "1 %s", coin->clone.name);
|
|
else
|
|
stringbuffer_append_printf(buf, "%u %ss", num, coin->clone.name);
|
|
} while (next_coin);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* Returns a string representing the money's value, in plain coins.
|
|
*
|
|
* @param coin
|
|
* coin. Must be of type MONEY.
|
|
* @param buf
|
|
* buffer to append to. Must not be NULL.
|
|
* @return
|
|
* buf with the value.
|
|
*/
|
|
static StringBuffer *real_money_value(const object *coin, StringBuffer *buf) {
|
|
assert(coin->type == MONEY);
|
|
assert(buf);
|
|
|
|
stringbuffer_append_printf(buf, "%ld %s", coin->nrof, coin->nrof == 1 ? coin->name : coin->name_pl);
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* Finds the price of an item.
|
|
*
|
|
* Price will be either an approximate value or the real value.
|
|
* @param tmp
|
|
* object to get the price of.
|
|
* @param who
|
|
* who is getting the price.
|
|
* @param flag
|
|
* combination of @ref F_xxx "F_xxx" values.
|
|
* @param buf
|
|
* buffer to append to. If NULL, a newly allocated one will be used and returned.
|
|
* @return
|
|
* buffer containing the price, new if buf was NULL.
|
|
*/
|
|
StringBuffer *query_cost_string(const object *tmp, object *who, int flag, StringBuffer *buf) {
|
|
uint64 real_value = query_cost(tmp, who, flag);
|
|
int idskill1 = 0;
|
|
int idskill2 = 0;
|
|
const typedata *tmptype;
|
|
|
|
if (!buf)
|
|
buf = stringbuffer_new();
|
|
|
|
/* money it's pretty hard to not give the exact price, so skip all logic and just return the real value. */
|
|
if (tmp->type == MONEY) {
|
|
return real_money_value(tmp, buf);
|
|
}
|
|
|
|
tmptype = get_typedata(tmp->type);
|
|
if (tmptype) {
|
|
idskill1 = tmptype->identifyskill;
|
|
idskill2 = tmptype->identifyskill2;
|
|
}
|
|
|
|
/* we show an approximate price if
|
|
* 1) we are approximating
|
|
* 2) there either is no id skill(s) for the item, or we don't have them
|
|
* 3) we don't have bargaining skill either
|
|
*/
|
|
if (flag&F_APPROX) {
|
|
if (!idskill1 || !find_skill_by_number(who, idskill1)) {
|
|
if (!idskill2 || !find_skill_by_number(who, idskill2)) {
|
|
if (!find_skill_by_number(who, SK_BARGAINING)) {
|
|
int num;
|
|
int cointype = LARGEST_COIN_GIVEN;
|
|
archetype *coin = find_next_coin(real_value, &cointype);
|
|
|
|
if (coin == NULL) {
|
|
stringbuffer_append_string(buf, "nothing");
|
|
return buf;
|
|
}
|
|
|
|
num = real_value/coin->clone.value;
|
|
if (num == 1)
|
|
stringbuffer_append_printf(buf, "about one %s", coin->clone.name);
|
|
else if (num < 5)
|
|
stringbuffer_append_printf(buf, "a few %s", coin->clone.name_pl);
|
|
else if (num < 10)
|
|
stringbuffer_append_printf(buf, "several %s", coin->clone.name_pl);
|
|
else if (num < 25)
|
|
stringbuffer_append_printf(buf, "a moderate amount of %s", coin->clone.name_pl);
|
|
else if (num < 100)
|
|
stringbuffer_append_printf(buf, "lots of %s", coin->clone.name_pl);
|
|
else if (num < 1000)
|
|
stringbuffer_append_printf(buf, "a great many %s", coin->clone.name_pl);
|
|
else
|
|
stringbuffer_append_printf(buf, "a vast quantity of %s", coin->clone.name_pl);
|
|
return buf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return cost_string_from_value(real_value, buf);
|
|
}
|
|
|
|
/**
|
|
* Finds out how much money the player is carrying,
|
|
* including what is in containers.
|
|
*
|
|
* @param op
|
|
* item to get money for. Must be a player or a container.
|
|
* @return
|
|
* total money the player is carrying.
|
|
*/
|
|
uint64 query_money(const object *op) {
|
|
object *tmp;
|
|
uint64 total = 0;
|
|
|
|
if (op->type != PLAYER && op->type != CONTAINER) {
|
|
LOG(llevError, "Query money called with non player/container\n");
|
|
return 0;
|
|
}
|
|
for (tmp = op->inv; tmp; tmp = tmp->below) {
|
|
if (tmp->type == MONEY) {
|
|
total += (uint64)tmp->nrof*(uint64)tmp->value;
|
|
} else if (tmp->type == CONTAINER
|
|
&& QUERY_FLAG(tmp, FLAG_APPLIED)
|
|
&& (tmp->race == NULL || strstr(tmp->race, "gold"))) {
|
|
total += query_money(tmp);
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* Takes the amount of money from the the player inventory and from it's various
|
|
* pouches using the pay_from_container() function.
|
|
*
|
|
* @param to_pay
|
|
* amount to pay.
|
|
* @param pl
|
|
* player paying.
|
|
* @return
|
|
* 0 if not enough money, in which case nothing is removed, 1 if money was removed.
|
|
* @todo check if pl is a player, as query_money() expects that. Check if fix_object() call is required.
|
|
*/
|
|
int pay_for_amount(uint64 to_pay, object *pl) {
|
|
object *pouch;
|
|
|
|
if (to_pay == 0)
|
|
return 1;
|
|
if (to_pay > query_money(pl))
|
|
return 0;
|
|
|
|
to_pay = pay_from_container(pl, pl, to_pay);
|
|
|
|
for (pouch = pl->inv; (pouch != NULL) && (to_pay > 0); pouch = pouch->below) {
|
|
if (pouch->type == CONTAINER
|
|
&& QUERY_FLAG(pouch, FLAG_APPLIED)
|
|
&& (pouch->race == NULL || strstr(pouch->race, "gold"))) {
|
|
to_pay = pay_from_container(pl, pouch, to_pay);
|
|
}
|
|
}
|
|
if (to_pay > 0) {
|
|
LOG(llevError, "pay_for_amount: Cannot remove enough money -- %"FMT64U" remains\n", to_pay);
|
|
}
|
|
|
|
fix_object(pl);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* DAMN: This is now a wrapper for pay_from_container, which is
|
|
* called for the player, then for each active container that can hold
|
|
* money until op is paid for. Change will be left wherever the last
|
|
* of the price was paid from.
|
|
*
|
|
* @param op
|
|
* object to buy.
|
|
* @param pl
|
|
* player buying.
|
|
* @return
|
|
* 1 if object was bought, 0 else.
|
|
* @todo check if pl is a player, as query_money() expects a player.
|
|
*/
|
|
int pay_for_item(object *op, object *pl) {
|
|
uint64 to_pay = query_cost(op, pl, F_BUY|F_SHOP);
|
|
object *pouch;
|
|
uint64 saved_money;
|
|
|
|
if (to_pay == 0)
|
|
return 1;
|
|
if (to_pay > query_money(pl))
|
|
return 0;
|
|
|
|
/* We compare the paid price with the one for a player
|
|
* without bargaining skill.
|
|
* This determins the amount of exp (if any) gained for bargaining.
|
|
*/
|
|
saved_money = query_cost(op, pl, F_BUY|F_NO_BARGAIN|F_SHOP)-to_pay;
|
|
|
|
if (saved_money > 0)
|
|
change_exp(pl, saved_money, "bargaining", SK_EXP_NONE);
|
|
|
|
to_pay = pay_from_container(pl, pl, to_pay);
|
|
|
|
for (pouch = pl->inv; (pouch != NULL) && (to_pay > 0); pouch = pouch->below) {
|
|
if (pouch->type == CONTAINER
|
|
&& QUERY_FLAG(pouch, FLAG_APPLIED)
|
|
&& (pouch->race == NULL || strstr(pouch->race, "gold"))) {
|
|
to_pay = pay_from_container(pl, pouch, to_pay);
|
|
}
|
|
}
|
|
if (to_pay > 0) {
|
|
LOG(llevError, "pay_for_item: Cannot remove enough money -- %"FMT64U" remains\n", to_pay);
|
|
}
|
|
if (settings.real_wiz == FALSE && QUERY_FLAG(pl, FLAG_WAS_WIZ))
|
|
SET_FLAG(op, FLAG_WAS_WIZ);
|
|
fix_object(pl);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* This function removes a given amount from a list of coins.
|
|
*
|
|
* @param coin_objs
|
|
* the list coins to remove from; the list must be ordered
|
|
* from least to most valuable coin.
|
|
* @param remain
|
|
* the value (in silver coins) to remove
|
|
* @return
|
|
* the value remaining
|
|
*/
|
|
static sint64 remove_value(object *coin_objs[], sint64 remain) {
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_COINS; i++) {
|
|
int count;
|
|
sint64 num_coins;
|
|
|
|
if (coin_objs[i]->nrof*coin_objs[i]->value > remain) {
|
|
num_coins = remain/coin_objs[i]->value;
|
|
if ((uint64)num_coins*(uint64)coin_objs[i]->value < remain) {
|
|
num_coins++;
|
|
}
|
|
} else {
|
|
num_coins = coin_objs[i]->nrof;
|
|
}
|
|
remain -= (sint64)num_coins*(sint64)coin_objs[i]->value;
|
|
coin_objs[i]->nrof -= num_coins;
|
|
/* Now start making change. Start at the coin value
|
|
* below the one we just did, and work down to
|
|
* the lowest value.
|
|
*/
|
|
count = i-1;
|
|
while (remain < 0 && count >= 0) {
|
|
num_coins = -remain/coin_objs[count]->value;
|
|
coin_objs[count]->nrof += num_coins;
|
|
remain += num_coins*coin_objs[count]->value;
|
|
count--;
|
|
}
|
|
}
|
|
|
|
return remain;
|
|
}
|
|
|
|
/**
|
|
* This function adds a given amount to a list of coins.
|
|
*
|
|
* @param coin_objs the list coins to add to; the list must be ordered
|
|
* from least to most valuable coin
|
|
*
|
|
* @param value the value (in silver coins) to add
|
|
*/
|
|
static void add_value(object *coin_objs[], sint64 value) {
|
|
int i;
|
|
|
|
for (i = NUM_COINS-LARGEST_COIN_GIVEN-1; i >= 0; i--) {
|
|
uint32 nrof;
|
|
|
|
nrof = (uint32)(value/coin_objs[i]->value);
|
|
value -= nrof*coin_objs[i]->value;
|
|
coin_objs[i]->nrof += nrof;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert a list of objects into a player object.
|
|
*
|
|
* @param pl the player to add to
|
|
*
|
|
* @param container the container (inside the player object) to add to
|
|
*
|
|
* @param objects the list of objects to add; the objects will be either
|
|
* inserted into the player object or freed
|
|
*
|
|
* @param objects_len the length of objects
|
|
*/
|
|
static void insert_objects(object *pl, object *container, object *objects[], int objects_len) {
|
|
int i, one = 0;
|
|
|
|
for (i = 0; i < objects_len; i++) {
|
|
if (objects[i]->nrof > 0) {
|
|
insert_ob_in_ob(objects[i], container);
|
|
one = 1;
|
|
} else {
|
|
free_object(objects[i]);
|
|
}
|
|
}
|
|
if (one)
|
|
esrv_update_item(UPD_WEIGHT, pl, container);
|
|
}
|
|
|
|
/**
|
|
* This pays for the item, and takes the proper amount of money off
|
|
* the player.
|
|
*
|
|
* DAMN: This function is used for the player, then for any active
|
|
* containers that can hold money.
|
|
*
|
|
* @param pl
|
|
* player paying.
|
|
* @param pouch
|
|
* container (pouch or player) to remove the coins from.
|
|
* @param to_pay
|
|
* required amount.
|
|
* @return
|
|
* amount still not paid after using "pouch".
|
|
*/
|
|
static uint64 pay_from_container(object *pl, object *pouch, uint64 to_pay) {
|
|
int i;
|
|
sint64 remain;
|
|
object *tmp, *coin_objs[NUM_COINS], *next;
|
|
object *other_money[16]; /* collects MONEY objects not matching coins[] */
|
|
size_t other_money_len; /* number of allocated entries in other_money[] */
|
|
archetype *at;
|
|
|
|
if (pouch->type != PLAYER && pouch->type != CONTAINER)
|
|
return to_pay;
|
|
|
|
remain = to_pay;
|
|
for (i = 0; i < NUM_COINS; i++)
|
|
coin_objs[i] = NULL;
|
|
|
|
/* This hunk should remove all the money objects from the player/container */
|
|
other_money_len = 0;
|
|
for (tmp = pouch->inv; tmp; tmp = next) {
|
|
next = tmp->below;
|
|
if (tmp->type == MONEY) {
|
|
for (i = 0; i < NUM_COINS; i++) {
|
|
if (!strcmp(coins[NUM_COINS-1-i], tmp->arch->name)
|
|
&& (tmp->value == tmp->arch->clone.value)) {
|
|
|
|
/* This should not happen, but if it does, just
|
|
* merge the two.
|
|
*/
|
|
if (coin_objs[i] != NULL) {
|
|
LOG(llevError, "%s has two money entries of (%s)\n", pouch->name, coins[NUM_COINS-1-i]);
|
|
remove_ob(tmp);
|
|
coin_objs[i]->nrof += tmp->nrof;
|
|
free_object(tmp);
|
|
} else {
|
|
remove_ob(tmp);
|
|
coin_objs[i] = tmp;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (i == NUM_COINS) {
|
|
if (other_money_len >= sizeof(other_money)/sizeof(*other_money)) {
|
|
LOG(llevError, "pay_for_item: Cannot store non-standard money object %s\n", tmp->arch->name);
|
|
} else {
|
|
remove_ob(tmp);
|
|
other_money[other_money_len++] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fill in any gaps in the coin_objs array - needed to make change. */
|
|
/* Note that the coin_objs array goes from least value to greatest value */
|
|
for (i = 0; i < NUM_COINS; i++)
|
|
if (coin_objs[i] == NULL) {
|
|
at = find_archetype(coins[NUM_COINS-1-i]);
|
|
if (at == NULL)
|
|
LOG(llevError, "Could not find %s archetype\n", coins[NUM_COINS-1-i]);
|
|
coin_objs[i] = get_object();
|
|
copy_object(&at->clone, coin_objs[i]);
|
|
coin_objs[i]->nrof = 0;
|
|
}
|
|
|
|
/* Try to pay from standard coins first. */
|
|
remain = remove_value(coin_objs, remain);
|
|
|
|
/* Now pay from non-standard coins until all is paid. */
|
|
for (i = 0; i < other_money_len && remain > 0; i++) {
|
|
uint32 nrof;
|
|
object *coin;
|
|
|
|
coin = other_money[i];
|
|
|
|
/* Find the minimal number of coins to use. This prevents converting
|
|
* excess non-standard coins to standard money.
|
|
*/
|
|
nrof = (remain+coin->value-1)/coin->value;
|
|
if (nrof > coin->nrof) {
|
|
nrof = coin->nrof;
|
|
}
|
|
coin->nrof -= nrof;
|
|
add_value(coin_objs, nrof*coin->value);
|
|
|
|
remain = remove_value(coin_objs, remain);
|
|
}
|
|
|
|
/* re-insert remaining coins into player */
|
|
insert_objects(pl, pouch, coin_objs, NUM_COINS);
|
|
insert_objects(pl, pouch, other_money, other_money_len);
|
|
|
|
return(remain);
|
|
}
|
|
|
|
/**
|
|
* Helper function for can_pay(). Checks all items from item and
|
|
* item->below, and recurse if inventory found.
|
|
* coincount is supposed to be of size NUM_COINS. Parameters can't be NULL.
|
|
*
|
|
* @param pl
|
|
* player.
|
|
* @param item
|
|
* item to check for.
|
|
* @param[out] unpaid_count
|
|
* how many unpaid items are left.
|
|
* @param[out] unpaid_price
|
|
* total price unpaid.
|
|
* @param coincount
|
|
* array of NUM_COINS size, will contain how many coins of the type the player has.
|
|
*/
|
|
static void count_unpaid(object *pl, object *item, int *unpaid_count, uint64 *unpaid_price, uint32 *coincount) {
|
|
int i;
|
|
|
|
for (; item; item = item->below) {
|
|
if QUERY_FLAG(item, FLAG_UNPAID) {
|
|
(*unpaid_count)++;
|
|
(*unpaid_price) += query_cost(item, pl, F_BUY|F_SHOP);
|
|
}
|
|
/* Merely converting the player's monetary wealth won't do.
|
|
* If we did that, we could print the wrong numbers for the
|
|
* coins, so we count the money instead.
|
|
*/
|
|
for (i = 0; i < NUM_COINS; i++)
|
|
if (!strcmp(coins[i], item->arch->name)) {
|
|
coincount[i] += item->nrof;
|
|
break;
|
|
}
|
|
if (item->inv)
|
|
count_unpaid(pl, item->inv, unpaid_count, unpaid_price, coincount);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks all unpaid items in op's inventory, adds up all the money they
|
|
* have, and checks that they can actually afford what they want to buy.
|
|
* Prints appropriate messages to the player.
|
|
*
|
|
* @param pl
|
|
* player trying to bug.
|
|
* @retval 1
|
|
* player could buy the items.
|
|
* @retval 0
|
|
* some items can't be bought.
|
|
*/
|
|
int can_pay(object *pl) {
|
|
int unpaid_count = 0, i;
|
|
uint64 unpaid_price = 0;
|
|
uint64 player_wealth = query_money(pl);
|
|
uint32 coincount[NUM_COINS];
|
|
|
|
if (!pl || pl->type != PLAYER) {
|
|
LOG(llevError, "can_pay(): called against something that isn't a player\n");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < NUM_COINS; i++)
|
|
coincount[i] = 0;
|
|
|
|
count_unpaid(pl, pl->inv, &unpaid_count, &unpaid_price, coincount);
|
|
|
|
if (unpaid_price > player_wealth) {
|
|
char buf[MAX_BUF], coinbuf[MAX_BUF];
|
|
int denominations = 0;
|
|
char *value = stringbuffer_finish(cost_string_from_value(unpaid_price, NULL));
|
|
|
|
snprintf(buf, sizeof(buf), "You have %d unpaid items that would cost you %s, ", unpaid_count, value);
|
|
free(value);
|
|
for (i = 0; i < NUM_COINS; i++) {
|
|
if (coincount[i] > 0 && coins[i]) {
|
|
if (denominations == 0)
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "but you only have");
|
|
denominations++;
|
|
snprintf(coinbuf, sizeof(coinbuf), " %u %s,", coincount[i], find_archetype(coins[i])->clone.name_pl);
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s", coinbuf);
|
|
}
|
|
}
|
|
if (denominations == 0)
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "but you don't have any money.");
|
|
else if (denominations > 1)
|
|
make_list_like(buf);
|
|
draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SHOP,
|
|
MSG_TYPE_SHOP_PAYMENT, buf, NULL);
|
|
return 0;
|
|
} else
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Descends containers looking for unpaid items, and pays for them.
|
|
*
|
|
* @param pl
|
|
* player buying the stuff.
|
|
* @param op
|
|
* object we are examining. If op has and inventory, we examine that. IF there are objects
|
|
* below op, we descend down.
|
|
* @retval 0 player still has unpaid items.
|
|
* @retval 1 player has paid for everything.
|
|
*/
|
|
int get_payment(object *pl, object *op) {
|
|
char name_op[MAX_BUF];
|
|
int ret = 1;
|
|
|
|
if (op != NULL && op->inv)
|
|
ret = get_payment(pl, op->inv);
|
|
|
|
if (!ret)
|
|
return 0;
|
|
|
|
if (op != NULL && op->below)
|
|
ret = get_payment(pl, op->below);
|
|
|
|
if (!ret)
|
|
return 0;
|
|
|
|
if (op != NULL && QUERY_FLAG(op, FLAG_UNPAID)) {
|
|
if (!pay_for_item(op, pl)) {
|
|
uint64 i = query_cost(op, pl, F_BUY|F_SHOP)-query_money(pl);
|
|
char *missing = stringbuffer_finish(cost_string_from_value(i, NULL));
|
|
|
|
CLEAR_FLAG(op, FLAG_UNPAID);
|
|
query_name(op, name_op, MAX_BUF);
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_PAYMENT,
|
|
"You lack %s to buy %s.",
|
|
"You lack %s to buy %s.",
|
|
missing, name_op);
|
|
free(missing);
|
|
SET_FLAG(op, FLAG_UNPAID);
|
|
return 0;
|
|
} else {
|
|
object *tmp;
|
|
char *value = stringbuffer_finish(query_cost_string(op, pl, F_BUY|F_SHOP, NULL));
|
|
|
|
CLEAR_FLAG(op, FLAG_UNPAID);
|
|
CLEAR_FLAG(op, FLAG_PLAYER_SOLD);
|
|
query_name(op, name_op, MAX_BUF);
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_PAYMENT,
|
|
"You paid %s for %s.",
|
|
"You paid %s for %s.",
|
|
value, name_op);
|
|
free(value);
|
|
tmp = merge_ob(op, NULL);
|
|
if (pl->type == PLAYER && !tmp) {
|
|
/* If item wasn't merged we update it. If merged, merge_ob handled everything for us. */
|
|
esrv_update_item(UPD_FLAGS|UPD_NAME, pl, op);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Player is selling an item. Give money, print appropriate messages.
|
|
*
|
|
* This function uses the coins[] array to know what coins are available.
|
|
*
|
|
* Modified to fill available race: gold containers before dumping
|
|
* remaining coins in character's inventory.
|
|
*
|
|
* @param op
|
|
* object to sell.
|
|
* @param pl
|
|
* player. Shouldn't be NULL or non player.
|
|
*/
|
|
void sell_item(object *op, object *pl) {
|
|
uint64 i = query_cost(op, pl, F_SELL|F_SHOP), extra_gain;
|
|
int count;
|
|
object *tmp, *pouch;
|
|
archetype *at;
|
|
char name_op[MAX_BUF], *value;
|
|
|
|
if (pl == NULL || pl->type != PLAYER) {
|
|
LOG(llevDebug, "Object other than player tried to sell something.\n");
|
|
return;
|
|
}
|
|
|
|
if (op->custom_name)
|
|
FREE_AND_CLEAR_STR(op->custom_name);
|
|
|
|
if (!i) {
|
|
query_name(op, name_op, MAX_BUF);
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_SELL,
|
|
"We're not interested in %s.",
|
|
"We're not interested in %s.",
|
|
name_op);
|
|
|
|
/* Even if the character doesn't get anything for it, it may still be
|
|
* worth something. If so, make it unpaid
|
|
*/
|
|
if (op->value) {
|
|
SET_FLAG(op, FLAG_UNPAID);
|
|
SET_FLAG(op, FLAG_PLAYER_SOLD);
|
|
}
|
|
identify(op);
|
|
return;
|
|
}
|
|
|
|
/* We compare the price with the one for a player
|
|
* without bargaining skill.
|
|
* This determins the amount of exp (if any) gained for bargaining.
|
|
* exp/10 -> 1 for each gold coin
|
|
*/
|
|
extra_gain = i-query_cost(op, pl, F_SELL|F_NO_BARGAIN|F_SHOP);
|
|
|
|
if (extra_gain > 0)
|
|
change_exp(pl, extra_gain/10, "bargaining", SK_EXP_NONE);
|
|
|
|
for (count = LARGEST_COIN_GIVEN; coins[count] != NULL; count++) {
|
|
at = find_archetype(coins[count]);
|
|
if (at == NULL)
|
|
LOG(llevError, "Could not find %s archetype\n", coins[count]);
|
|
else if ((i/at->clone.value) > 0) {
|
|
for (pouch = pl->inv; pouch; pouch = pouch->below) {
|
|
if (pouch->type == CONTAINER
|
|
&& QUERY_FLAG(pouch, FLAG_APPLIED)
|
|
&& pouch->race
|
|
&& strstr(pouch->race, "gold")) {
|
|
int w = at->clone.weight*(100-pouch->stats.Str)/100;
|
|
int n = i/at->clone.value;
|
|
|
|
if (w == 0)
|
|
w = 1; /* Prevent divide by zero */
|
|
if (n > 0
|
|
&& (!pouch->weight_limit || pouch->carrying+w <= pouch->weight_limit)) {
|
|
if (pouch->weight_limit
|
|
&& (pouch->weight_limit-pouch->carrying)/w < n)
|
|
n = (pouch->weight_limit-pouch->carrying)/w;
|
|
|
|
tmp = get_object();
|
|
copy_object(&at->clone, tmp);
|
|
tmp->nrof = n;
|
|
i -= (uint64)tmp->nrof*(uint64)tmp->value;
|
|
tmp = insert_ob_in_ob(tmp, pouch);
|
|
esrv_update_item(UPD_WEIGHT, pl, pl);
|
|
}
|
|
}
|
|
}
|
|
if (i/at->clone.value > 0) {
|
|
tmp = get_object();
|
|
copy_object(&at->clone, tmp);
|
|
tmp->nrof = i/tmp->value;
|
|
i -= (uint64)tmp->nrof*(uint64)tmp->value;
|
|
tmp = insert_ob_in_ob(tmp, pl);
|
|
esrv_update_item(UPD_WEIGHT, pl, pl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i != 0)
|
|
#ifndef WIN32
|
|
LOG(llevError, "Warning - payment not zero: %llu\n", i);
|
|
#else
|
|
LOG(llevError, "Warning - payment not zero: %I64u\n", i);
|
|
#endif
|
|
|
|
query_name(op, name_op, MAX_BUF);
|
|
value = stringbuffer_finish(query_cost_string(op, pl, F_SELL|F_SHOP, NULL));
|
|
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SHOP, MSG_TYPE_SHOP_SELL,
|
|
"You receive %s for %s.",
|
|
"You receive %s for %s.",
|
|
value,
|
|
name_op);
|
|
|
|
free(value);
|
|
|
|
SET_FLAG(op, FLAG_UNPAID);
|
|
identify(op);
|
|
}
|
|
|
|
/**
|
|
* returns a double that is the ratio of the price that a shop will offer for
|
|
* item based on the shops specialisation. Does not take account of greed,
|
|
* returned value is between (2*SPECIALISATION_EFFECT-1) and 1 and in any
|
|
* event is never less than 0.1 (calling functions divide by it)
|
|
*
|
|
* @param item
|
|
* item to get ratio of.
|
|
* @param map
|
|
* shop map.
|
|
* @return
|
|
* ratio specialisation for the item.
|
|
*/
|
|
static double shop_specialisation_ratio(const object *item, const mapstruct *map) {
|
|
shopitems *items = map->shopitems;
|
|
double ratio = SPECIALISATION_EFFECT, likedness = 0.001;
|
|
int i;
|
|
|
|
if (item == NULL) {
|
|
LOG(llevError, "shop_specialisation_ratio: passed a NULL item for map %s\n", map->path);
|
|
return 0;
|
|
}
|
|
if (!item->type) {
|
|
LOG(llevError, "shop_specialisation_ratio: passed an item with an invalid type\n");
|
|
/*
|
|
* I'm not really sure what the /right/ thing to do here is,
|
|
* these types of item shouldn't exist anyway, but returning
|
|
* the ratio is probably the best bet.."
|
|
*/
|
|
return ratio;
|
|
}
|
|
if (map->shopitems) {
|
|
for (i = 0; i < items[0].index; i++)
|
|
if (items[i].typenum == item->type || (!items[i].typenum && likedness == 0.001))
|
|
likedness = items[i].strength/100.0;
|
|
}
|
|
if (likedness > 1.0) { /* someone has been rather silly with the map headers. */
|
|
LOG(llevDebug, "shop_specialisation ratio: item type %d on map %s is above 100%%\n", item->type, map->path);
|
|
likedness = 1.0;
|
|
}
|
|
if (likedness < -1.0) {
|
|
LOG(llevDebug, "shop_specialisation ratio: item type %d on map %s is below -100%%\n", item->type, map->path);
|
|
likedness = -1.0;
|
|
}
|
|
ratio = ratio+(1.0-ratio)*likedness;
|
|
if (ratio <= 0.1)
|
|
ratio = 0.1; /* if the ratio were much lower than this, we would get silly prices */
|
|
return ratio;
|
|
}
|
|
|
|
/**
|
|
* Gets shop's greed.
|
|
*
|
|
* @param map
|
|
* map to get greed.
|
|
* @return
|
|
* greed of the shop on map, or 1 if it isn't specified.
|
|
*/
|
|
static double shop_greed(const mapstruct *map) {
|
|
double greed = 1.0;
|
|
|
|
if (map->shopgreed)
|
|
return map->shopgreed;
|
|
return greed;
|
|
}
|
|
|
|
/**
|
|
* Returns a double based on how much the shopkeeper approves of the player.
|
|
* this is based on the race of the shopkeeper and that of the player.
|
|
*
|
|
* @param map
|
|
* shop to get ratio for.
|
|
* @param player
|
|
* player to get ratio of.
|
|
* @return
|
|
* approval ratio.
|
|
*/
|
|
double shopkeeper_approval(const mapstruct *map, const object *player) {
|
|
double approval = 1.0;
|
|
|
|
if (map->shoprace) {
|
|
approval = NEUTRAL_RATIO;
|
|
if (player->race && !strcmp(player->race, map->shoprace))
|
|
approval = 1.0;
|
|
}
|
|
return approval;
|
|
}
|
|
|
|
/**
|
|
* Limit the value of items based on the wealth of the shop.
|
|
* If the item is close to the maximum value a shop will offer,
|
|
* we start to reduce it, if the item is below the minimum value
|
|
* the shop is prepared to trade in, then we don't want it and
|
|
* offer nothing. If it isn't a shop, check whether we should do
|
|
* generic value reduction.
|
|
*
|
|
* @param val
|
|
* current price.
|
|
* @param quantity
|
|
* number of items.
|
|
* @param who
|
|
* player selling.
|
|
* @param isshop
|
|
* 0 if not a shop, 1 if a shop.
|
|
* @return
|
|
* maximum global value.
|
|
*/
|
|
static uint64 value_limit(uint64 val, int quantity, const object *who, int isshop) {
|
|
uint64 newval, unit_price;
|
|
mapstruct *map;
|
|
|
|
unit_price = val/quantity;
|
|
if (!isshop || !who) {
|
|
if (unit_price > 10000)
|
|
newval = 8000+isqrt(unit_price)*20;
|
|
else
|
|
newval = unit_price;
|
|
} else {
|
|
if (!who->map) {
|
|
LOG(llevError, "value_limit: asked shop price for ob %s on NULL map\n", who->name);
|
|
return val;
|
|
}
|
|
map = who->map;
|
|
if (map->shopmin && unit_price < map->shopmin)
|
|
return 0;
|
|
else if (map->shopmax && unit_price > map->shopmax/2)
|
|
newval = MIN((map->shopmax/2)+isqrt(unit_price-map->shopmax/2), map->shopmax);
|
|
else if (unit_price > 10000)
|
|
newval = 8000+isqrt(unit_price)*20;
|
|
else
|
|
newval = unit_price;
|
|
}
|
|
newval *= quantity;
|
|
return newval;
|
|
}
|
|
|
|
/**
|
|
* Gives a desciption of the shop on their current map to the player op.
|
|
*
|
|
* @param op
|
|
* player to describe the shop for. Mustn't be NULL.
|
|
* @return
|
|
* 0 if op is not a player, 1 else.
|
|
* @todo is return value meaningful?
|
|
*/
|
|
int describe_shop(const object *op) {
|
|
mapstruct *map = op->map;
|
|
/*shopitems *items=map->shopitems;*/
|
|
int pos = 0, i;
|
|
double opinion = 0;
|
|
char tmp[MAX_BUF] = "\0", *value;
|
|
|
|
if (op->type != PLAYER)
|
|
return 0;
|
|
|
|
/*check if there is a shop specified for this map */
|
|
if (map->shopitems
|
|
|| map->shopgreed
|
|
|| map->shoprace
|
|
|| map->shopmin
|
|
|| map->shopmax) {
|
|
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SHOP, MSG_TYPE_SHOP_LISTING,
|
|
"From looking at the nearby shop you determine that it trades in:",
|
|
NULL);
|
|
|
|
if (map->shopitems) {
|
|
for (i = 0; i < map->shopitems[0].index; i++) {
|
|
if (map->shopitems[i].name && map->shopitems[i].strength > 10) {
|
|
snprintf(tmp+pos, sizeof(tmp)-pos, "%s, ", map->shopitems[i].name_pl);
|
|
pos += strlen(tmp+pos);
|
|
}
|
|
}
|
|
}
|
|
if (!pos)
|
|
strcpy(tmp, "a little of everything.");
|
|
|
|
/* format the string into a list */
|
|
make_list_like(tmp);
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_LISTING, tmp, NULL);
|
|
|
|
if (map->shopmax) {
|
|
value = stringbuffer_finish(cost_string_from_value(map->shopmax, NULL));
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"It won't trade for items above %s.",
|
|
"It won't trade for items above %s.",
|
|
value);
|
|
free(value);
|
|
}
|
|
|
|
if (map->shopmin) {
|
|
value = stringbuffer_finish(cost_string_from_value(map->shopmin, NULL));
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"It won't trade in items worth less than %s.",
|
|
"It won't trade in items worth less than %s.",
|
|
value);
|
|
free(value);
|
|
}
|
|
|
|
if (map->shopgreed) {
|
|
if (map->shopgreed > 2.0)
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"It tends to overcharge massively.", NULL);
|
|
else if (map->shopgreed > 1.5)
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"It tends to overcharge substantially.", NULL);
|
|
else if (map->shopgreed > 1.1)
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"It tends to overcharge slightly.", NULL);
|
|
else if (map->shopgreed < 0.9)
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"It tends to undercharge.", NULL);
|
|
}
|
|
if (map->shoprace) {
|
|
opinion = shopkeeper_approval(map, op);
|
|
if (opinion > 0.8)
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"You think the shopkeeper likes you.", NULL);
|
|
else if (opinion > 0.5)
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"The shopkeeper seems unconcerned by you.", NULL);
|
|
else
|
|
draw_ext_info(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"The shopkeeper seems to have taken a dislike to you.", NULL);
|
|
}
|
|
} else draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SHOP, MSG_TYPE_SHOP_MISC,
|
|
"There is no shop nearby.", NULL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Check if an object is in a shop.
|
|
*
|
|
* @param ob
|
|
* object to check for.
|
|
* @return
|
|
* 1 if in a shop so, 0 otherwise.
|
|
*/
|
|
int is_in_shop(object *ob) {
|
|
if (!ob->map)
|
|
return 0;
|
|
return coords_in_shop(ob->map, ob->x, ob->y);
|
|
}
|
|
|
|
/**
|
|
* Check if given map coords are in a shop.
|
|
* @param map
|
|
* @param x
|
|
* @param y
|
|
* coordinates to check.
|
|
* @return
|
|
* 1 if coordinates are a shop, 0 otherwise.
|
|
*/
|
|
int coords_in_shop(mapstruct *map, int x, int y) {
|
|
object *floor;
|
|
|
|
for (floor = GET_MAP_OB(map, x, y); floor; floor = floor->above)
|
|
if (floor->type == SHOP_FLOOR)
|
|
return 1;
|
|
return 0;
|
|
}
|