/* generic shop equipped with LSC parser */
/* written by square@imperial.mud.lp */
#include <lsc.h>
#include <mudlib.h>
#include <move.h>
#include <money.h>
inherit LSC;
inherit ROOM ;
inherit COINVALUE ;
#define NOWEALTH 0
#define NOCHANGE 1
static object office;
static object storeroom;
static object shopkeeper;
static object last_customer;
static object customer;
static object item; /* goods being asked/valued/bought/sold */
static int item_value; /* estimated true value */
static int desired_buy_price; /* relative to the shop, not customer */
static int desired_sell_price;
static int fail_reason;
static int *give_coins, *get_coins; /* again, relative to the shop */
static mapping cost; /* object in storeroom->value in copper units */
mapping wealth;
string owner;
string * money_types ;
int * type_value ;
int money_type_size;
int partial_wealth(mapping w, int level);
int sum(int * array, int size);
int _end();
int _show_goods();
string Convert_amount_string(int value);
int read_lsc_file();
void create() {
seteuid(getuid());
lsc2::create();
Reset();
wait_time = 1;
_speed = 6; /* shop code shouldn't be run fast */
_max_ch_size = 0; /* can't spawn child processes */
cost = ([]);
wealth = (["platinum":2, "gold":10, "silver":8, "copper":100]);
set("light",10);
set("volume", 10000000);
set("capacity", 10000000);
set("long",
"This is a generic shop.\n"+
"commands: list, value, ask, buy, sell\n");
storeroom = clone_object(STORAGE);
shopkeeper = clone_object("obj/orc");
shopkeeper->set_name("The shop keeper");
shopkeeper->set("short", "A shop keeper");
shopkeeper->set("id", ({"shop keeper", "keeper", "man"}) );
shopkeeper->move(this_object());
customer = 0;
money_types = ({ "copper", "silver", "gold","platinum" });
type_value = ({ 1, 10, 100, 1000 });
money_type_size = sizeof(money_types);
give_coins = allocate(money_type_size);
get_coins = allocate(money_type_size);
read_lsc_file();
}
/***************************************************************/
/* it returns the best coin combination extractable from the */
/* wealth mapping w1 to w2. If it's impossible, 0 is returned. */
/* c1 and c2 are the capacity of the 2 wealths mapping. */
/* change is indicated by negative number in its slot */
/***************************************************************/
int * coin_combination(
int amount,
mapping w1,
mapping w2,
int c1,int c2,
int level);
int * best_coin_combination(int amount, mapping w1, mapping w2, int c1,int c2) {
fail_reason = NOCHANGE;
if (!w1 || !w2) return 0;
return coin_combination(amount, w1, w2, c1, c2, money_type_size);
}
/********************************************************/
/* it returns the coin combination extractable from the */
/* wealth mapping w. If it's impossible, 0 is returned. */
/********************************************************/
int * coin_combination(
int amount,
mapping w1,
mapping w2,
int c1,int c2,
int level)
{
int * coins, i, tmp;
int pay_coins;
int * alternative;
if (partial_wealth(w1, level) < amount) {
if (level==money_type_size) fail_reason = NOWEALTH;
return 0;
}
coins = allocate(i=level);
while(i--) coins[i] = 0;
if (!amount) return coins;
pay_coins = amount / type_value[level-1];
if ( w1[money_types[level-1]] < pay_coins) {
pay_coins = w1[money_types[level-1]];
}
if (alternative =
coin_combination(amount-pay_coins*type_value[level-1],
w1, w2, c1+pay_coins, c2-pay_coins, level-1)) {
tmp=sum(alternative,level-1)+pay_coins;
if (tmp>0) {
if (tmp <= c2)
return alternative + ({ pay_coins });
} else {
if (-tmp <= c1)
return alternative + ({ pay_coins });
}
}
if (w1[money_types[level-1]]==pay_coins)
return 0;
/* see if w2 can make change */
if (alternative =
coin_combination((pay_coins+1)*type_value[level-1]-amount,
w2, w1, c1+pay_coins+1, c2-pay_coins-1, level-1)) {
for(i=0;i<level-1;i++) alternative[i] = -alternative[i];
tmp = pay_coins + 1 + sum(alternative, level-1);
if (tmp>0) {
if (tmp <= c2)
return alternative + ({ pay_coins + 1});
} else {
if (-tmp <= c1)
return alternative + ({ pay_coins + 1});
}
}
/* oh well, not possible then */
return 0;
}
int partial_wealth(mapping w, int level) {
int i,ret,tmp;
string tmpstr;
ret = 0;
if (level > money_type_size) level = money_type_size;
for(i = 0; i<level ;i++) {
ret += type_value[i] * w[money_types[i]];
}
return ret;
}
int sum(int * array, int size) {
int ret,i;
if (!size) size = sizeof(array);
ret = 0;
while(size--) ret+=array[size];
return ret;
}
remove() {
if(shopkeeper) shopkeeper->remove();
if(storeroom) storeroom->remove();
::remove();
}
void init() {
add_action ("list", "list") ;
add_action ("value", "value") ;
add_action ("ask", "ask") ;
add_action ("buy", "buy") ;
add_action ("sell", "sell") ;
add_action ("enter_storeroom", "storeroom") ;
add_action ("enter_office", "office");
}
int enter_storeroom() {
mixed owners;
if (!storeroom) {
notify_fail("where is the storeroom?\n");
return 0;
}
if (!wizardp(this_player()) &&
(string) this_player()->query("name") != owner &&
(undefinedp(owners=_varlist["owners"]) ||
pointerp(owners) &&
member_array(this_player()->query("name"), owners)==-1)) {
notify_fail("You aren't allowed to enter the storeroom\n");
return 0;
}
}
int shop_busy() {
if (!shopkeeper || environment(shopkeeper)!=this_object()) {
notify_fail("There's no one around to help you!\n");
return 1;
}
if (customer) {
if (environment(customer) != this_object()) {
_end();
return 0;
}
if (customer == this_player()) {
notify_fail( "The shopkeeper says: "+
"I'm already serving you, please be patient.\n");
return 1;
}
notify_fail( "The shopkeeper says: "+
"I'm now busy with "+customer->query("cap_name")+
", but I'll be with you in a moment...\n");
return 1;
}
return 0;
}
int list() {
mixed tmp;
if (shop_busy()) return 0;
write ("You ask the shopkeeper: What do you have?\n");
say(this_player()->query("cap_name")+
" asks the shopkeeper: What do you have?\n");
customer = this_player();
if (!undefinedp(tmp = _varlist["list"]) && Is_Block(tmp) ) {
customer = this_player();
Push("("+this_player()->query("name")+")");
Compile("list");
return 1;
}
tell_room(this_object(),"The shopkeeper says: hmm... lemme see..\n");
say("The shopkeeper shows the wares to "
+this_player()->query("cap_name")+".\n");
_show_goods();
customer = 0;
return 1;
}
int _say() {
mixed i;
object env, ob;
string out;
i = Pop();
if (Is_String(i)) {
out = Parse_String(i);
}
else if (Is_Object(i)) {
ob = find_object(i[2..strlen(i)-1]);
if (!ob) out = "something";
else out = (string) ob->query("short");
if (!out) out = "some weird thing";
}
else if (objectp(i)) {
if (!i) out = "something";
else out = (string) ob->query("short");
if (!out) out = "some weird thing";
}
else if (intp(i)) {
out = ""+i;
}
else if (pointerp(i)) {
out = Restore_Array(i);
}
if (out) {
tell_room(this_object(),
"The shopkeeper says: "+out+"\n");
}
return 0;
}
int _end() {
last_customer = customer;
customer = 0;
_counter = 0;
_stack_ptr = 0;
_pending_input = 0;
_stack = ({ });
remove_call_out("Compile");
}
int _show_goods() {
int i,n;
object *ob;
mixed * value;
string type;
int number;
/* Objects in local storage */
if (!customer || environment(customer)!=this_object()) return 0;
ob = all_inventory(storeroom) ;
n = sizeof(ob);
if (!n) tell_room(this_object(),
"The shopkeeper says: we have nothing right now.\n");
for (i=0;i<n;i++) {
value = ob[i]->query("value") ;
if (!value) continue ;
type = value[1] ;
number = value[0] ;
if (number==0) continue ;
tell_object(customer, " "+ob[i]->query("short")+"\n");
}
/* this command is too costly, must make LSC to rest for 2 seconds */
_counter = 0;
return 0;
}
int _evaluate() {
mixed obj;
mixed *value;
string type;
int number, j;
obj = Pop();
if (!objectp(obj)) {
error("evaluate: not an object");
return -1;
}
value = obj->query("value");
/* can add randomness here ... */
type = value[1];
number = value[0];
Push(coinvalue(type) * number);
}
int _set_desired_buy_price() {
mixed price;
price = Pop();
if (!intp(price)) {
error("set_desired_buy_price: not an integer");
return -1;
}
desired_buy_price = price;
return 0;
}
int _set_desired_sell_price() {
mixed price;
price = Pop();
if (!intp(price)) {
error("set_desired_sell_price: not an integer");
return -1;
}
desired_sell_price = price;
return 0;
}
int _convert_amount_str() {
mixed value;
value = Pop();
if (!intp(value)) {
error("convert_amount_str: not an integer");
return -1;
}
return Push(Convert_amount_string(value));
}
string Convert_amount_string(int value) {
int i,n,num, mod, first;
string str;
mixed *types;
str = "";
for(i=money_type_size-1;i>=0;i--) {
num = value / type_value[i];
mod = value - num * type_value[i];
value = mod;
if (!num) continue;
if (!value && str != "") {
str += " and ";
if (num==1) str+="one "+money_types[i]+" coin";
else str+=num+" "+money_types[i]+ " coins";
break;
}
if (str!="") str += ", ";
if (num==1) str+="one "+money_types[i]+" coin";
else str+=num+" "+money_types[i]+" coins";
}
return str;
}
string coins_string(int * coins) {
int i;
int tmp;
tmp = 0;
for(i=0;i<money_type_size;i++)
tmp+=coins[i] * type_value[i];
return Convert_amount_string(tmp);
}
int value(string str) {
object obj ;
string word ;
string *types ;
int i, number, changenum ;
string type, changetype ;
mixed *value ;
mixed tmp;
int newval, oldval, newnum, j ;
if (!str) {
notify_fail("What do you want to value?\n");
return 0;
}
if (shop_busy()) return 0;
obj = present(str,this_player()) ;
if (!obj) {
notify_fail ("You don't have a "+str+".\n") ;
return 0 ;
}
value = obj->query("value") ;
if (!value) {
notify_fail ("The shopkeeper says: "+
"That object is not worth anything.\n") ;
return 0 ;
}
type = value[1] ;
number = value[0] ;
if (number==0) {
notify_fail ("The shopkeeper says: "+
"That object is not worth anything.\n") ;
return 0 ;
}
j = member_array(type,money_types) ;
if (j==-1) {
write("The shopkeeper says: I don't know how to value this.\n");
return 1;
}
write("You ask the shopkeeper to value it for you...\n");
say(this_player()->query("cap_name")+" puts forward something and"+
" asks the shopkeeper a question.\n");
if (!undefinedp(tmp = _varlist["value"]) && Is_Block(tmp) ) {
item = obj;
customer = this_player();
Push("("+this_player()->query("name")+")");
Push(item);
Compile("value");
return 1;
}
write("The shopkeeper says: "+obj->query("short") + " is worth "+
Convert_amount_string(number*type_value[j]*SHOP_SALES_FRAC));
return 1 ;
}
int _buy_price() {
mixed * value;
if (!desired_buy_price) {
if (!item) {
error("buy_price: no reference");
return -1;
}
error("buy_price: don't know buy price");
return -1;
}
return Push(desired_buy_price);
}
int _sell_price() {
mixed * value;
if (!desired_sell_price) {
if (!item) {
error("sell_price: no reference");
return -1;
}
error("sell_price: don't know buy price");
return -1;
}
return Push(desired_sell_price);
}
int Do_buy(object ob,int how_much,object from_whom) {
int * coins, ncoins, cap, i;
string tmpstr;
if (ob->move(storeroom)!=MOVE_OK) {
tell_room(this_object(), "The shopkeeper says: "+
"Our storeroom can't hold your stuff, sorry\n");
return -1;
}
/* calculate money to give customer */
coins = best_coin_combination(how_much,
wealth,
(mapping) from_whom->query("wealth"),
(int) storeroom->query("capacity"),
cap = (int) from_whom->query("capacity"));
if (!coins) {
if (fail_reason == NOWEALTH) {
tell_room(this_object(), "The shopkeeper says: "+
"I'm running out of coins, sorry\n");
}
if (fail_reason == NOCHANGE) {
tell_room(this_object(), "The shopkeeper says: "+
"We don't have enough change to make "+
"the deal fair to us\n");
}
ob->move(from_whom);
return -1;
}
ncoins = 0;
for(i=0;i<money_type_size;i++) {
ncoins += coins[i];
if (coins[i]>=0) {
give_coins[i] = coins[i];
get_coins[i] = 0;
} else {
give_coins[i] = 0;
get_coins[i] = - coins[i];
}
}
from_whom->set("capacity", cap+ncoins);
for(i=0;i<money_type_size;i++) {
int tmp;
wealth[money_types[i]] -= coins[i];
tmp = from_whom->query("wealth/"+money_types[i]);
from_whom->set("wealth/"+money_types[i], tmp+coins[i]);
}
tell_object(from_whom,"You get "+coins_string(give_coins));
if ((tmpstr=coins_string(get_coins))=="")
tell_object(from_whom,".\n");
else
tell_object(from_whom," and give "+tmpstr+" as change.\n");
cost[ob] = how_much;
item = 0;
return 0;
}
int _buy_it() {
mixed how_much;
if (!customer || environment(customer)!=this_object()) return Push(-1);
if (!shopkeeper || environment(shopkeeper) != this_object())
return Push(-1);
how_much = Pop();
if (!item) {
tell_object(customer,
"The shopkeeper says: Where's the thing now?\n");
return Push(-1);
}
if (environment(item) != this_object() && environment(item)!=customer ||
environment(customer) != this_object() )
return Push(-1);
_counter = 0;
Push(Do_buy(item, how_much, customer));
}
int sell(string str) {
int i, j ;
object ob ;
mixed *value, tmp ;
string type, tmpstr;
int number ;
if (shop_busy()) return 0;
if (!str) {
notify_fail("What do you want to sell?\n") ;
return 0 ;
}
ob = present(str,this_player()) ;
if (!ob) {
notify_fail("You don't have one to sell!\n") ;
return 0 ;
}
value = ob->query("value") ;
if (value==0) {
notify_fail ("The shopkeeper says: That has no value.\n") ;
return 0 ;
}
number=value[0] ;
if (number<1) {
notify_fail ("The shopkeeper says: That has no value.\n") ;
return 0 ;
}
type = value[1] ;
j = member_array(type,money_types) ;
if (j==-1) {
write("The shopkeeper says: I'm not interested in this.\n");
return 1;
}
write("You put "+str+" forward to the shopkeeper...\n");
say(this_player()->query("cap_name")+" wants to sell something.\n");
if (!undefinedp(tmp = _varlist["sell"]) && Is_Block(tmp) &&
!undefinedp(tmp = _varlist["myvalue"]) && Is_Block(tmp) ) {
tmpstr = "("+this_player()->query("name")+")";
if (last_customer == this_player() && item == ob) {
/* the item has already been valued */
customer = this_player();
Push(tmpstr);
Push(item);
Compile("sell");
return 1;
}
customer = this_player();
item = ob;
Push(tmpstr);
Push(item);
Push(tmpstr);
Push(item);
Compile("myvalue pop sell");
return 1;
}
Do_buy(ob, number*type_value[j]*SHOP_SALES_FRAC, this_player());
return 1 ;
}
int _cost() {
mixed ob;
int i;
mixed * value;
string type;
int number;
ob = Pop();
if (!objectp(ob)) {
error("cost: not an object");
return -1;
}
i = cost[ob];
if (undefinedp(i) || !i) {
value = ob->query("value");
if (!value) return Push(0);
type = value[0];
number = value[1];
i = member_array(type,money_types);
if (i=-1) return Push(0);
return Push( number * type_value[i]);
}
return Push(i);
}
int ask(string str) {
object ob ;
string type, *types ;
int i, number;
mixed *value ;
mixed tmp;
if (!str) {
notify_fail("What item's price you want to ask?\n");
return 0;
}
if (shop_busy()) return 0;
ob = present(str, storeroom);
if (!ob) {
notify_fail("We don't have one of those for sale.\n");
return 0;
}
value = ob->query("value");
if (!value) {
notify_fail ("That object should not have been in the shop.\n");
ob->remove() ;
return 0 ;
}
type = value[1] ;
number = value[0] ;
if (number==0) {
notify_fail ("That object should not have been in the shop.\n");
ob->remove() ;
return 0 ;
}
write("You ask the shopkeeper about the price of "+str+"....\n");
say(this_player()->query("cap_name")+
" asks about the price of something.\n");
if (!undefinedp(tmp = _varlist["ask"]) && Is_Block(tmp) ) {
item = ob;
customer = this_player();
Push("("+this_player()->query("name")+")");
Push(item);
Compile("ask");
return 1;
}
write("The shopkeeper says: "+ ob->query("short")+" costs "+
Convert_amount_string(cost[ob] * 5/4)
+"\n");
return 1;
}
int Do_sell(object ob,int how_much,object to_whom) {
int * coins, ncoins, cap, i;
string tmpstr;
if (ob->move(to_whom)!=MOVE_OK) {
tell_room(this_object(), "The shopkeeper says: "+
"You can't carry anymore, sorry\n");
return -1;
}
/* calculate money to give customer */
coins = best_coin_combination(how_much,
(mapping) to_whom->query("wealth"),
wealth,
cap = (int) to_whom->query("capacity"),
(int) storeroom->query("capacity"));
if (!coins) {
if (fail_reason == NOWEALTH) {
tell_room(this_object(), "The shopkeeper says: "+
"You don't have enough money!\n");
}
if (fail_reason == NOCHANGE) {
tell_room(this_object(), "The shopkeeper says: "+
"We don't have enough change to make "+
"the deal fair to us\n");
}
ob->move(storeroom);
return -1;
}
ncoins = 0;
for(i=0;i<money_type_size;i++) {
ncoins += coins[i];
if (coins[i]>=0) {
get_coins[i] = coins[i];
give_coins[i] = 0;
} else {
get_coins[i] = 0;
give_coins[i] = - coins[i];
}
}
to_whom->set("capacity", cap-ncoins);
for(i=0;i<money_type_size;i++) {
int tmp;
wealth[money_types[i]] += coins[i];
tmp = to_whom->query("wealth/"+money_types[i]);
to_whom->set("wealth/"+money_types[i], tmp-coins[i]);
}
tell_object(to_whom,"You pay "+coins_string(get_coins));
if ((tmpstr=coins_string(give_coins))=="")
tell_object(to_whom,".\n");
else
tell_object(to_whom," and get back "+tmpstr+" as change.\n");
map_delete(cost, ob);
item = 0;
return 0;
}
int _sell_it() {
mixed how_much;
if (!customer || environment(customer)!=this_object()) return Push(-1);
if (!shopkeeper || environment(shopkeeper) != this_object())
return Push(-1);
how_much = Pop();
if (!item) {
tell_object(customer,
"The shopkeeper says: Oops, where is it?\n");
return Push(-1);
}
if (environment(item) != storeroom) {
tell_object(customer,
"The shopkeeper says: We don't have it\n");
return Push(-1);
}
_counter = 0;
Push(Do_sell(item, how_much, customer));
}
int buy(string str) {
int i, j ;
object ob ;
mixed *value, tmp ;
string type, tmpstr;
int number ;
if (shop_busy()) return 0;
if (!str) {
notify_fail("What do you want to buy?\n") ;
return 0 ;
}
ob = present(str,storeroom) ;
if (!ob) {
notify_fail("You don't see any!\n") ;
return 0 ;
}
value = ob->query("value") ;
if (value==0) {
notify_fail("The shopkeeper says: That one is not for sale.\n");
return 0 ;
}
number=value[0] ;
if (number<1) {
notify_fail("The shopkeeper says: That one is not for sale.\n");
return 0 ;
}
type = value[1] ;
write("You tell the shopkeeper that you want to buy "+str+"...\n");
say(this_player()->query("cap_name")+" wants to buy something.\n");
if (!undefinedp(tmp = _varlist["buy"]) && Is_Block(tmp) &&
!undefinedp(tmp = _varlist["myask"]) && Is_Block(tmp) ) {
tmpstr = "("+this_player()->query("name")+")";
if (last_customer == this_player() && item == ob) {
/* the item has already been valued */
customer = this_player();
Push(tmpstr);
Push(item);
Compile("buy");
return 1;
}
customer = this_player();
item = ob;
Push(tmpstr);
Push(item);
Push(tmpstr);
Push(item);
Compile("myask pop buy");
return 1;
}
Do_sell(ob, cost[ob]*5/4, this_player());
return 1 ;
}
int read_lsc_file() {
int i,n, tmp_wait_time, tmp_speed;
string file;
string * lines;
file = file_name(this_object());
sscanf(file,"%s#%d",file, n);
file += ".lsc";
if (file_size(file) <= 0) return 0;
lines = explode(read_file(file),"\n");
n = sizeof(lines);
tmp_wait_time = wait_time;
tmp_speed = _speed;
wait_time = 4;
_speed = 1;
for(i=0;i<n;i++)
Compile(lines[i]);
wait_time = tmp_wait_time;
_speed = tmp_speed;
return 1;
}
error(string str) {
tell_room(this_object(),str+"\n");
_status = ERROR;
}
clean_up() { return; }