musicmud-2.1.6/data/
musicmud-2.1.6/data/help/
musicmud-2.1.6/data/policy/
musicmud-2.1.6/data/wild/
musicmud-2.1.6/data/world/
musicmud-2.1.6/doc/
musicmud-2.1.6/src/ident/
musicmud-2.1.6/src/lua/
musicmud-2.1.6/src/lua/include/
musicmud-2.1.6/src/lua/src/lib/
musicmud-2.1.6/src/lua/src/lua/
musicmud-2.1.6/src/lua/src/luac/
/* 
 * MusicMUD - Banks and Shops module
 * Copyright (C) 1998-2003 Abigail Brady
 * 
 * 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.
 * 
 */

#include "musicmud.h"
#include "misc.h"
#include "pflags.h"
#include "verbs.h"
#include "util.h"
#include "trap.h"
#include "events.h"

#include "units.h"
#include "match.h"
#include "emsg.h"
#include "href.h"
#include "nations.h"

#define MODULE "trade"

static int strfind(int argc, const char **argv, const char *what) {
    for (int i = 0 ; i < argc ; i++) {
	if (streq(argv[i], what)) return i;
    }
    return 0;
}

static void tell(MudObject *what, MudObject *who, const string &txt)
{
  who->interpretf("tell %s %s", what->id, txt.c_str());
}

struct shopinfo_t {
  NewWorld choices;

  MudObject *room;
  MudObject *list;
  MudObject *mob;

  Nation *cur;

  shopinfo_t(MudObject *where, MudObject *who, int *argc, const char **argv, const char *prep="from") {
    MudObject *tomatch=0;
    
    cur = 0;

    if (argc && argv) {
      TeleWorld m1 = multi_match(who, (*argc)-1, argv+1, 0, LOOK_ROOM, 0, 0, prep);
      int had = 0;
      if (m1.prep) {
	*argc = (strfind((*argc)-1, argv+1, prep))+1;
	argv[*argc] = 0;
	had = 1;
      }
      if (m1.txt == "here") {
	list = room = where;
	mob = where->get_object("shopmob");
	choices.add(mob);
	return;
      }
      if (had && m1.getsize()==1) {
	tomatch = m1.get_nth(0);
      } else if (had) {
	throw emsg("Trade with who?");
      }
    }
    
    room = 0;
    list = 0;
    mob = 0;

    int i;
    MudObject *o;
    foreach(where->children, o, i) {
      if (o->get_flag(FL_STORE) && !o->get_flag(FL_ROOM) && (!tomatch || tomatch==o)) {
	list = mob = o;
	choices.add(mob);
      }
    }
    
    if ((!list || where->get_flag(FL_STORE)) && (!tomatch || tomatch==where->get_object("shopmob"))) {
      list = room = where;
      mob = where->get_object("shopmob");
      choices.add(mob);
    }

    if (choices.getsize()>1) {
      if (argc)
	throw emsg(ssprintf("Trade with who? Choices are %W. (You need to use '%s'.)", &choices, prep));
      else
	throw emsg(ssprintf("Trade with who? Choices are %W.", &choices));
    }

    if (choices.getsize()==0) {
      throw emsg(ssprintf("There is nobody to trade with here."));
    }

    if (choices.getsize()==1 && mob) {
      if ((mob->wander_time > 10))
	mob->wander_time = now + mob->get_int("wander.time", 8)+30;
    }

    cur = currency(mob);
  }
};

static bool names_match(MudObject *what, const char *str, int cat_only=0, int namesonly=0);

static MudObject *empty_of(MudObject *of) {
  MudObject *o = of->get_object("empty");
  if (o)
    return o;
  return of;
}

enum  svt_t {
  SVT_TABLE,
  SVT_SIZE,
  SVT_VALUE,
};

void rec_carry(NewWorld &w, MudObject *o) {
  w.add(*o);
  MudObject *p;
  int i;
  foreach(o->children, p, i) {
    w.add(p);
  }
}

struct name_data {
  string name;
  int value;
  int mass;

  name_data(const name_data &d) : 
    name(d.name), value(d.value), mass(d.mass) {
  }

  name_data(const string &s, int v, int m) :
    name(s), value(v), mass(m) {
  }

  bool operator<(const name_data &w) const {
    if (name < w.name)      return 1;
    if (name > w.name)      return 0;
    if (value < w.value)    return 1;
    if (value > w.value)    return 0;
    if (mass < w.mass)      return 1;
    if (mass > w.mass)      return 0;
    
    return 0;
  }
};

static void sizevaltable(MudObject *who, NewWorld &of, svt_t type, int yescash) {
  int total = 0, i, stotal = 0;
  int h = 0;
  MudObject *o;

  NewWorld what;
  foreach((&of), o, i)
    rec_carry(what, o);

  map<name_data, int> m;

  foreach((&what), o, i) {
    if (!name(o))
      continue;

    name_data w(name(o), object_value(o)+totalcash(o), mass_in_grams(o, 0));

    total += w.value;
    stotal += w.mass;

    if (m.find(w)!=m.end()) {
      m[w]++;
    } else {
      m[w] = 1;
    }
  }

  Nation *cur = currency(who);

  if (type==SVT_TABLE) {
    map<name_data, int>::iterator it = m.begin();
    while (it != m.end()) {
      if (!(h++)) 
	who->printf("%s\n\n", title_for("Size / Value", who).c_str());

      who->printf("%30s * %2i - %10s %12s\n", 
		  it->first.name.c_str(),
		  it->second,
		  formatvalue(it->first.value, cur, 0),
		  format_grams(it->first.mass, who));
      it++;
    }
  }

   long long capacity = mass_capacity_in_grams(who);

   if (type==SVT_TABLE) {
      if (h) {
	if (h>1) {
	  who->printf("\n     %30s - %10s %12s (max %s) \n\n", 
		      "total", 
		      formatvalue(total, cur, 0), 
		      format_grams(stotal, who), 
		      format_grams(capacity, who));
	} else {
	  who->printf("\n");
	}
	

	if (yescash)
	  if (int amt=totalcash(who)) {
	    who->printf("     %30s - %10s\n\n", 
			"cash", formatvalue(amt, cur, 0));
	    
	}
	
	who->printf("%s\n", footer_for(who).c_str());
      }
      
      else {
	if (yescash) {
	  if (totalcash(who)) {
	    who->printf("You have %s in cash, but no possessions worth anything.\n", 
		      formatvalue(totalcash(who), cur, 1));
	  } else {
	    who->printf("You have nothing of value in your possession.\n");
	  }
	}
      }
      return;
   }

   if (type==SVT_SIZE) {
     string blah;

     if (stotal) {
       blah = ssprintf("You are carrying %s", format_grams(stotal, who).c_str());
     } else {
       blah = "You are carrying nothing";
     }

     if (capacity == SIZE_INFINITE)
       blah += " (you can carry anything)";
     else
       blah += ssprintf(" (you can carry %s)", format_grams(capacity, who).c_str());

     who->printf("%s\n", blah.c_str());
     return;
   }
   
   if (type==SVT_VALUE) {

      if (!total && !totalcash(who)) {
	who->printf("You have nothing of value.\n");
      } else if (total && !totalcash(who)) {
	who->printf("You have possessions worth %K.\n", total);
      } else if (!total && totalcash(who)) {
	who->printf("You have %K in cash.\n", totalcash(who));
      } else if (total && totalcash(who)) {
	who->printf("You have %K in cash, and other possessions worth %K.\n", totalcash(who), total);
      }
      return;
   }

   return;
}

static bool verb_value(MudObject *who, int argc, const char *argv[]) {
  NewWorld w;
  Divert d(who, "value");
  MudObject *o;
  int i;
  foreach((who->children), o, i) {
    w.add(*o);
  }
  if (argc==2 && streq(argv[1], "all")) {
    sizevaltable(who, w, SVT_TABLE, 1);
  } else if (argc==1) {
    sizevaltable(who, w, SVT_VALUE, 1);
  } else {
    w = match(who, argc-1, argv+1, 0, LOOK_BOTH|IGNORE_EXITS|ALLOW_ME);

    if (w.getsize()==0) {
      who->printf("Value what?\n");
      return true;
    }
    if (w.getsize()==1) {
      MudObject *what = w.get_nth(0);
      int value = object_value(what) + totalcash(what);
      who->printf("%#P %[is/are] worth %K.\n", what, value);
      return true;
    }

    sizevaltable(who, w, SVT_TABLE, 0);
  }
  return true;
}



static bool verb_size(MudObject *player, int argc, const char *argv[]) {
  NewWorld w;
  Divert d(player, "size");
  MudObject *o;
  int i;
  foreach((player->children), o, i) {
    w.add(*o);
  }
  if (argc < 2) {
    sizevaltable(player, w, SVT_SIZE, 1);
    return true;
  }

  if (argc==2 && streq(argv[1], "all")) {
    sizevaltable(player, w, SVT_TABLE, 0);
    return true;
  } 

  w = match(player, argc-1, argv+1, 0, LOOK_BOTH|IGNORE_EXITS|ALLOW_ME);
  MudObject *what = 0;
  if (w.getsize()==1) {
    what = w.get_nth(0);
  } else if (w.getsize()) {
    sizevaltable(player, w, SVT_TABLE, 0);    
    return true;
  }

  if (!what) {
      player->printf("Size what?\n", argv[1]);
      return true;
  }

  int m = mass_in_grams(what, 0);
  if (m || !what->get_flag(FL_FIXED)) {
    player->printf("%#Y %[masses/mass] %s.\n", what, format_grams(mass_in_grams(what, 0),player).c_str());
  } else if (what->get_flag(FL_FIXED)) {
    player->printf("%#Y %[seems/seem] immovable.\n", what);
  }

  if (int w=mass_used_in_grams(what)) {
    player->printf("%#Y %[contains/contain] %s.\n", what, format_grams(w, player).c_str());
  }

  MudObject *can = empty_of(what);
  if (!can) can = what;
  int volume = can->get_int("volume", -1);
  if (volume>0) {
    if (!(volume%PINT))
      player->printf("It can contain %,i mL (%i pint%s).\n", volume, volume/PINT, volume==PINT?"":"s");
    else if (volume>10000)
      player->printf("It can contain %,i L.\n", volume/1000);
    else
      player->printf("It can contain %,i mL.\n", volume);
    return true;
  }

  if (is_container(what) || (mass_capacity_in_grams(what, 0))) {
    
    int capacity = mass_capacity_in_grams(what);
    
    if (capacity != SIZE_INFINITE)
      player->printf("Its maximum load is %s.\n", format_grams(capacity, player).c_str());
    else 
      player->printf("Its has no maximum load.\n");
  }
  
  return true;
}

static int cost_atfor(MudObject *object, MudObject *shop, MudObject *who) {
  int ripoff = shop->get_int("shop.ripoff", 100);
  int cost = object->get_int("cost", 0);
  
  int levelcost = object->get_int("levelcost", 0);
  levelcost *= privs_of(who);
  
  cost += levelcost;

  return cost * ripoff / 100;
}

static int value_at(MudObject *object, MudObject *shop, MudObject *who)
{
  double v = cost_atfor(object, shop, who);
  if (object->get_int("value")!=-1) {
    v = object->get_int("value");
    double so = shop->get_int("shop.offer", 90) / 90.0;
    v *= so;
    return (int)v;
  }
  //  return (v * shop->get_int("shop.offer", 90))/100;

  double so = shop->get_int("shop.offer", 90)/100.0;
  v *= so;
  return (int)v;
}

static const char *remove_prefixes(const char *a) 
{
    const char *bnam = a;
    if (strncmp(bnam, "a pair of", 9)==0) {
      bnam+=10;
    }
    if (strncmp(bnam, "a ", 2)==0) {
      bnam+=2;
    }
    if (strncmp(bnam, "an ", 3)==0) {
      bnam+=3;
    }
    if (strncmp(bnam, "some ", 5)==0) {
      bnam+=5;
    }
    return bnam;
}

static void output_item(int idx, MudObject *b, int stok, int cost, MudObject *player, MudObject *keeper,
			MudObject *cur) {
  const char *bnam = "[Eh]";
  
  const char *nm = b->get("long");
  if (!nm) nm = b->get("name");
  if (nm) {
    bnam = remove_prefixes(nm);
  }

  const char *done = " ";
  if (b->get_int("holoid", -1) != -1) {
    int hid = b->get_int("holoid", -1);
    int hpt = player->get_int("holodone", 0);
    if (hpt & (1 << hid)) {
      done = "^Y*^n";
    }
  }
  
  if (MudObject*qu=b->get_object("quest")) {
    if (quest_done(player, qu)) {
      done = "^Y*^n";
    }
  }
  
  string sstr= player_knows(player, 0x221e)?"^#x221e;":"many";
  if (stok>0) {
    if (stok>99)
      sstr = "lots";
    else
      sstr = ssprintf("%i", stok);
  }
  if (stok == 0) {
    sstr = "none";
  }

  string coststr = "^Yfree^n";
  if (cost)
    coststr = formatcash(convertcash(cost, NULL, cur), cur, 0);

  string link = ssprintf("\3send href='buy %i from %s|preview %i from %s'\4%s\3/send\4", 
			 idx, 
			 genref(player, keeper).c_str(),
			 idx, 
			 genref(player, keeper).c_str(),
			 bnam);

  player->printf("^C^| ^w%2i ^C^|^n %-31s %s ^C^|^n %9s ^C^|^n %5s ^C^|^n\n" , 
		 idx, link.c_str(), done, coststr.c_str(), sstr.c_str());
}


static bool verb_list(MudObject *player, int argc, const char **argv) {
    if (player->owner->get("dock") && argc==1) {
      player->interpret("dslist");
      return true;
    }

  int i = player->owner->get_int("floor.count");
  if (i>=1) {
    for (int j=0;j<=i;j++) {
      char name[100];
      sprintf(name, "floor.%i", j);
      MudObject *floor = planet->get(player->owner->get(name));
      if (floor)
	player->printf("%i : %s\n", j, floor->get("name"));
    }
    return true;
  }

  shopinfo_t shop(player->owner, player, &argc, argv);

  if (!shop.list || !shop.list->get_flag(FL_STORE)) {
    player->printf("There is no list here.\n");
    return true;
  }

  if(!shop.mob || (shop.room && shop.mob->owner != shop.room)) {
    player->printf("The proprietor seems to be missing.\n");
    return true;
  }

  int items = shop.list->array_size("shop");
  if (!items && shop.list->get_int("$shop.count",0)==0) {
    player->printf("%#P %[has/have] nothing to sell.\n", shop.mob);
    return true;
  }

  Divert d(player, "list");

  player->spec_printf("^C^/^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^\\\n");
  player->spec_printf("^C^| ^RProprietor^r: ^g%-46M ^C^|\n", shop.mob);
  player->spec_printf("^C^>^-^-^-^-^!^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^!^-^-^-^-^-^-^-^-^-^-^-^!^-^-^-^-^-^-^-^<\n");
  player->spec_printf("^C^| ^GID ^C^| ^G%-33s ^C^|     ^GCost  ^C^| ^GStock ^C^|\n", argc==1?"Item Name":ssprintf("Items Matching '%s'", argv[1]).c_str());
  player->spec_printf("^C^>^-^-^-^-^+^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^+^-^-^-^-^-^-^-^-^-^-^-^+^-^-^-^-^-^-^-^<\n");

  int need = 0;
  int matched = 0;
  int forsale = 0;

  for (int i=0;i<items;i++) {
    MudObject *b = shop.list->array_get_object("shop", i);
    
    if (!b) continue;

    if (dotrap(E_ONWONTSELLTO, player, b, shop.mob)) {
      /* ::: wont_sellto o1==object, o2==shopkeeper; return 1 to not be allowed to sell. Invoked by list. Must be silent. */
      continue;
    }

    int cost = cost_atfor(b, shop.list, player);
    int stok = shop.list->array_get_int("$stock", i);

    if (argc==1 || names_match(b, argv[1], 0, 0)) {
      output_item(i+1, b, stok, cost, player, shop.mob, shop.cur);
      matched++;
      need = 1;
    }
    forsale++;
  }

  int idx = items;

  items = shop.list->array_size("$shop");

  for (int i=0;i<items;i++) {
    MudObject *b = shop.list->array_get_object("$shop", i);
    idx++;
    
    if (!b) continue;

    if (dotrap(E_ONWONTSELLTO, player, b, shop.mob))
      continue;

    int cost = cost_atfor(b, shop.list, player);
    int stok = shop.list->array_get_int("$shop", i, "stock");

    if (!stok)
      continue;
    
    if (argc==1 || names_match(b, argv[1], 0, 0)) {
      
      if (need) {
	player->printf("^C^>^-^-^-^-^+^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^+^-^-^-^-^-^-^-^-^-^-^-^+^-^-^-^-^-^-^-^<\n");
	need = 0;
      }
      
      output_item(idx, b, stok, cost, player, shop.mob, shop.cur);
      matched++;
    }
    forsale++;
  }

  if (matched == 0) {
    player->cancel_printf();
    if (forsale) 
      player->printf("No items for sale matching '%s'.\n", argv[1]);
    else
      player->printf("%#P %[doesn't/don't] seem to have anything for sale.\n", shop.mob);
    return true;
  }
  
  player->printf("^C^[^-^-^-^-^~^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^~^-^-^-^-^-^-^-^-^-^-^-^~^-^-^-^-^-^-^-^]^n\n");
  
  if (player->owner->get("shop.note")) {
    player->printf("%s\n", player->owner->get("shop.note"));
  }
	
  return true;
}

static bool stocks(MudObject *store, MudObject *item) 
{
  if (item->get_object("cloneof"))
    item = item->get_object("cloneof");

  int items = store->array_size("shop");
  
  for (int i=0;i<items;i++) {
    MudObject *o = store->array_get_object("shop", i);
    if (o==item) {
      return 1;
    }
  }
  return 0;
}

static bool offer_stocks(MudObject *store, MudObject *item) 
{
  if (!item->get_object("cloneof"))
    return true;
    
  item = item->get_object("cloneof");

  if (item->id[0]=='@')
    return true;

  int items = store->array_size("shop");
  
  for (int i=0;i<items;i++) {
    MudObject *o = store->array_get_object("shop", i);
    if (o==item) {
      if (store->array_get_int("$stock", i)!=-1) {
	store->array_set("$stock", i, store->array_get_int("$stock", i)+1);
      }
      return true;
    }
  }

  items = store->array_size("$shop");
  
  for (int i=0;i<items;i++) {
    MudObject *o = store->array_get_object("$shop", i);
    if (o==item) {
      store->array_set("$shop", i, "stock", store->array_get_int("$shop", i, "stock")+1);
      return true;
    }  }

  store->array_set("$shop", items, item);
  store->array_set("$shop", items, "stock", 1);
  store->set("$shop.count", items+1);

  return false;
}

static bool verb_sell(MudObject *player, int argc, const char **argv) {

  shopinfo_t shop(player->owner, player, &argc, argv, "to");
  if (!shop.list || !shop.list->get_flag(FL_STORE)) {
    player->printf("There is no list here.\n");
    return true;
  }

  if(!shop.mob || (shop.room && shop.mob->owner != shop.room)) {
    player->printf("The proprietor seems to be missing.\n");
    return true;
  }

  if (argc < 2) {
    player->printf("Sell what?\n");
    return true;
  }

  MudObject *keeper = shop.mob;

  NewWorld tosell = match(player, argc-1, argv+1, 0, LOOK_INV|IGNORE_EXITS|ALLOW_ME);
  
  MudObject *o;
  int i;

  foreach((&tosell), o, i)
    if (is_player(o) || is_mobile(o)) {
      tell(player, keeper, lang("What do you take me for, a slave trader?", 
			      keeper));
      return true;
    }
  
  if (!keeper->get_flag(FL_CONTRABAND))
    foreach((&tosell), o, i)
      if (o->get_flag(FL_CONTRABAND)) {
	tell(player, keeper, lang("I'm not some kind of smuggler, you know!",
				  keeper));
	return true;
      }

  if (tosell.getsize()==0) {
    player->printf("Sell what?\n");
    return true;
  }

  MudObject *sell=0;
  NewWorld willbuy;
  int total=0;
  foreach((&tosell), sell, i) {
    int value = value_at(sell, shop.list, player);
    int dontwant = 0;
    if (sell->get_int("food")!=-1) 
      dontwant = 2;
    if (sell->get_int("alcohol")!=-1) 
      dontwant = 2;
    if (const char *s =shop.list->get("stype")) {
      if (streq(s, "none")) {
	dontwant = 3;
      }
      if (!names_match(sell, s, 1)) {
	if (dontwant != 2)
	  dontwant = 1;
      }
    } else {
      if (dontwant != 2)
	dontwant = !stocks(shop.list, sell);
    }    
    sell->set("!donstock", 0);
    sell->unset("!offered");
    if (dotrap(E_ONSELL, player, keeper, sell) || dotrap(E_BEFORESOLD, player, sell, keeper)) {
    /* ::: sell o1==shopkeeper, o2==object offered; if the shopkeeper wants the item, return 1 and set !offered on o2 to the offer, if he doesn't want it, just return 1 */
    /* ::: before_sold o1==object, o2==shopkeeper; if the shopkeeper wants the item, return 1 and set !offered on o2 to the offer, if he doesn't want it, just return 1 */
      value = sell->get_int("!offered", 0);
      if (value) {
	dontwant = 0;
      } else {
	dontwant = 1;
      }
    }
    sell->unset("!offered");
    if (value && !dontwant) {
      willbuy.add(*sell);
      total += value;
    }
  }

  if (willbuy.getsize()==0) {
    if (tosell.getsize()==1)
      tell(player, keeper, lang("Sorry, I don't want that.", keeper));
    else
      tell(player, keeper, lang("Sorry, I don't want anything from that.", keeper));
    return true;
  }

  keeper->printf("%#M %[gives/give] %W to you.\n", player, &willbuy);
  keeper->oprintf(cansee && avoid(player), "%#M %[gives/give] %W to %M.\n",
		  player, &willbuy, keeper);
  player->printf("You give %W to %M.\n",
		 &willbuy, keeper);
  
  tell(player, keeper, ssprintf(lang("Thank you. Here is %s. "
				     "Have a nice day.", keeper), 
				formatvalue(total, shop.cur, 1)).c_str());
  
  set_cash(player, shop.cur, cash(player, shop.cur) + convertcash(total, NULL, shop.cur));
  
  foreach((&willbuy), sell, i) {
    if (dotrap(E_AFTERSOLD, player, sell, keeper)) {
      /* ::: after_sold o1==object, o2==keeper; return 1 to NOT VANISH the object and not stock the shop with this object */
      continue;
    }
  
    vanish(sell);
    if (sell->get_int("!dontstock", 0)==0) {
      offer_stocks(shop.list, sell);
    }
  }
  
  return true;
}

struct stockitem {
  const char *error;

  MudObject *shop;
  const char *aname;
  int idx;

  int atonce;

  stockitem() {
    error = 0;
    shop = 0;
    aname = 0;
    idx = 0;
    atonce = 0;
  }

  stockitem(const char *why) {
    error = why;
    shop = 0;
    aname = 0;
    idx = 0;
    atonce = 0;
  }

  int stock() {
    if (streq(aname, "$shop")) {
      return shop->array_get_int(aname, idx, "stock");
    } else if (streq(aname, "shop")) {
      return shop->array_get_int("$stock", idx);
    }
    return -1;
  }
  
  MudObject *obj() {
    return shop->array_get_object(aname, idx);
  }

  void destock(MudObject *who, int by) {
    if (stock() != -1) {
      if (streq(aname, "shop")) {
	shop->array_set("$stock", idx, stock()-by);
      } else {
	shop->array_set(aname, idx, "stock", stock()-by);
      }
    }

    if (streq(aname, "$shop")) {
      if (stock()==0) {
	int i=0;
	for (i=idx+1;i<shop->array_size(aname);i++) {
	  shop->array_set(aname, i-1, shop->array_get_object(aname, i));
	  shop->array_set(aname, i-1, "stock", shop->array_get_int(aname, i, "stock"));
	}
	shop->set("$shop.count", shop->array_size("$shop")-1);
	char foo[100];
	sprintf(foo, "$shop.%i", shop->get_int("$shop.count"));
	shop->unset(foo);
	sprintf(foo, "$shop.%i.stock", shop->get_int("$shop.count"));
	shop->unset(foo);
      }
    }
  }
};

const stockitem naughtforsale = stockitem("The shop has nothing to sell.");
const stockitem notthatmany = stockitem("The shop doesn't have that many items to sell.");
const stockitem notstock = stockitem("The shop doesn't have that in stock.");

static stockitem find_stock(MudObject *shop, int idx);

static stockitem find_stock(MudObject *store, MudObject *what) {
  int i;
  for (i=0;i<store->array_size("shop");i++) {
    MudObject *w = store->array_get_object("shop", i);
    if (w == what) {
      stockitem si;
      si.shop = store;
      si.aname = "shop";
      si.idx = i;
      si.atonce = store->array_get_int("shop", i, "atonce", 0);
      if (w->get_flag(FL_ONEPERSON)) {
	si.atonce = 1;
      }
      return si;
    }
  }

  for (i=0;i<store->array_size("$shop");i++) {
    MudObject *w = store->array_get_object("$shop", i);
    if (w == what) {
      stockitem si;
      si.shop = store;
      si.aname = "$shop";
      si.idx = i;
      si.atonce = 0;
      return si;
    }
  }

  return notstock;
}

static stockitem find_stock(MudObject *shop, int idx) 
{
  if (shop->array_size("shop")<1 && shop->array_size("$shop")<1)
    return naughtforsale;

  if (idx < shop->array_size("shop")) {
    MudObject *b = shop->array_get_object("shop", idx);
    if (b) {
      stockitem i;
      i.shop = shop;
      i.aname = "shop";
      i.idx = idx;
      i.atonce = shop->array_get_int("shop", idx, "atonce", 0);
      if (b->get_flag(FL_ONEPERSON)) {
	i.atonce = 1;
      }
      return i;
    }
  }

  idx -= shop->array_size("shop");
  if (idx < shop->array_size("$shop")) {
    MudObject *b = shop->array_get_object("$shop", idx);
    if (b) {
      stockitem i;
      i.shop = shop;
      i.aname = "$shop";
      i.idx = idx;
      return i;
    }
  }
    
  return notthatmany;
}

static bool verb_preview(MudObject *player, int argc, const char **argv) {
  shopinfo_t shop(player->owner, player, &argc, argv);
  if (!shop.list || !shop.list->get_flag(FL_STORE)) {
    player->printf("There is no list here.\n");
    return true;
  }

  if(!shop.mob || (shop.room && shop.mob->owner != shop.room)) {
    player->printf("The proprietor seems to be missing.\n");
    return true;
  }

  if (shop.mob->get_flag(FL_STUNNED)) {
    player->printf("%#M has been stunned.\n");
    return true;
  }

  if (argc < 2) {
    player->printf("Syntax : preview <what>.\n");
    return true;
  }

  int which = 1;

  char *w=0;
  int item = strtol(argv[which], &w, 10);

  stockitem si;

  MudObject *b = 0;
  if (*w) {
    NewWorld wot;
    for (int i=0;i<shop.list->array_size("shop");i++) {
      MudObject *s = shop.list->array_get_object("shop", i);
      if (s)
	wot.add(*s);
    }
    for (int i=0;i<shop.list->array_size("$shop");i++) {
      MudObject *s = shop.list->array_get_object("$shop", i);
      if (s)
	wot.add(*s);
    }
    NewWorld wh = match(player, argc-which, argv+which, 
			0, LOOK_SPECIAL, 0, &wot);
    if (wh.getsize()) {
      b = wh.get_nth(0);
    }
    if (wh.getsize()>1) {
      player->printf("You can only preview one thing at a time.\n");
      return true;
    }

    if (!b) {
      si = notstock;
    }

    if (si.error) {
      player->printf("%s\n", si.error);
      return true;
    }

    si = find_stock(shop.list, b);
  } else {
    if (!b) {
      item--;
      if (item < 0) {
	player->printf("Items only have ID numbers greater than one\n");
	return true;
      }
      
      si = find_stock(shop.list, item);
      if (si.error) {
	player->printf("%s\n", si.error);
	return true;
      }
      
      b = si.obj();
    }
  }

  if (!b) {
    player->printf("Cannot find that item to buy...\n");
    return true;
  }
  player->printf("%#M %[shows/show] %M to you.\n", shop.mob, b);
  player->printf("%s\n", b->get("desc"));
  string ab = abilities(b);
  if (ab.length()) {
    if (ab[ab.length()-1]==',')
      ab.erase(ab.length()-1);
    player->printf("^WNotes: %s.^n\n", ab.c_str());
  }
  return true;
}


static bool verb_buy(MudObject *player, int argc, const char **argv) {
  shopinfo_t shop(player->owner, player, &argc, argv);

  if (argc < 2) {
    player->printf("Buy what?\n");
    return true;
  }

  int howmany = 1, all = 0;
  int which = 1;

  if (argc > 2) {
    char *w;
    if (streq(argv[which], "all")) {
      howmany = 1;
      all = 1;
      which = 2;
    } else {
      howmany = strtol(argv[which], &w, 10);
      if (!*w) {
	which = 2;
      } else {
	howmany = decardinator(argv[which]);
      	if (howmany==-1) {
	  howmany = 1;
	} else {
	  which = 2;
	}
      }
    }
  }
  if (howmany < 0) {
    player->printf("Use sell to sell items.\n");
    return true;
  }
  if (howmany == 0) {
    player->printf("You buy nothing.\n");
    return true;
  }

  MudObject *mfor = player;

  if (strhas(argc, argv, "for"))
    {
      TeleWorld wfor = multi_match(player, argc, argv,
			    0, LOOK_ROOM, 0, 0, "for");
      if (wfor.getsize()==0) {
	player->printf("Buy for who?\n");
	return true;
      }
      if (wfor.getsize()>1) {
	player->printf("You can only buy for one person at a time.\n");
	return true;
      }
      mfor = wfor.get_nth(0);
      if (!is_player(mfor)) {
	player->printf("You can only buy for players.\n");
	return true;
      }
    }

  if (!shop.list || !shop.list->get_flag(FL_STORE)) {
    player->printf("There is no list here.\n");
    return true;
  }

  if(!shop.mob || (shop.room && shop.mob->owner != shop.room)) {
    player->printf("The proprietor seems to be missing.\n");
    return true;
  }

  if (shop.mob->get_flag(FL_STUNNED)) {
    player->printf("%#M has been stunned.\n");
    return true;
  }

  char *w;
  int item = strtol(argv[which], &w, 10);

  stockitem si;

  MudObject *b = 0;
  if (*w) {
    NewWorld wot;
    for (int i=0;i<shop.list->array_size("shop");i++) {
      MudObject *s = shop.list->array_get_object("shop", i);
      if (s)
	wot.add(*s);
    }
    for (int i=0;i<shop.list->array_size("$shop");i++) {
      MudObject *s = shop.list->array_get_object("$shop", i);
      if (s)
	wot.add(*s);
    }
    NewWorld wh = match(player, argc-which, argv+which, 
			0, LOOK_SPECIAL, 0, &wot);
    if (wh.getsize()) {
      b = wh.get_nth(0);
    }

    if (wh.getsize()>1) {
      player->printf("You can only buy one type of thing at a time.\n");
      return true;
    }

    if (!b) {
      si = notstock;
    }

    if (si.error) {
      player->printf("%s\n", si.error);
      return true;
    }

    si = find_stock(shop.list, b);
  }

  if (!b) {

    item--;
    if (item < 0) {
      player->printf("Items only have ID numbers greater than one\n");
      return true;
    }

    si = find_stock(shop.list, item);
    if (si.error) {
      player->printf("%s\n", si.error);
      return true;
    }

    b = si.obj();
  }

  if (!b) {
    player->printf("Cannot find that item to buy...\n");
    return true;
  }

  int toopricy = 0;

  if (all==1) {
    howmany = si.stock();
    if (howmany == -1) {
      howmany = 100000;
      toopricy = 1;
    }
  }

  if (si.stock() != -1 && (howmany>si.stock() || (si.stock()==0))) {
    if (si.stock()==0) {
      tell(player, shop.mob, lang("We are out of stock of those.", shop.mob));
    } else {
      tell(player, shop.mob, ssprintf(lang("We only have %i of those in stock.", shop.mob), si.stock()).c_str());
    }
    return true;
  }

  int unit_cost = cost_atfor(b, shop.list, player);

  int cost = convertcash(unit_cost * howmany, NULL, shop.cur);
  int kash = cash(player, shop.cur);
  
  if ((cost && (kash - cost) < 0)||toopricy) {
    tell(player, shop.mob, lang("You haven't got enough cash.", shop.mob));
    return true;
  }

  if (dotrap(E_ONWONTSELLTO, player, b)) {
    tell(player, shop.mob, lang("I can't sell that to you.", shop.mob));
    return true;
  }

  int mass = mass_in_grams(b) * howmany;
  if (mass > mass_capacity_left_in_grams(mfor)) {
    if (howmany>1 || b->get_flag(FL_PLURAL)) { 
      tell(player, shop.mob, lang("They'd be too heavy.", shop.mob));
    } else
      tell(player, shop.mob, lang("It'd be too heavy.", shop.mob));
    return true;
  }

  int hands = freehands(mfor);
  if (hands < howmany) {
    if (howmany>1 || b->get_flag(FL_PLURAL)) {
      tell(player, shop.mob, lang("Wouldn't be able to carry so many.", shop.mob));
    } else {
      tell(player, shop.mob, lang("Wouldn't be able to carry so much.", shop.mob));
    }
    return true;
  }
  
  int maxlimit = si.atonce?si.atonce:10;
  if (howmany>maxlimit) {
    tell(player, shop.mob, ssprintf(lang("You can only buy %i of those at a time.", shop.mob), maxlimit).c_str());
    return true;
  }
  
  if (dotrap(E_ONBUY, player, b, shop.mob)) {
    /* ::: buy o1==template, o2==shopkeeper; return 1 to abort. before anything is actually cloned. set !virtrade to 1 on o2 if you want it to not actually clone the items, but to do the paying thing. */
    return true;
  }

  if (dotrap(E_BEFOREVEND, player, shop.mob, b)) {
    /* ::: before_vend o1==shopkeeper, o2==object to buy; return 1 to abort. */
    return true;
  }
  
  if (b->get_int("!notrade", 0)) {
    return true;
  }

  NewWorld things;

  if (b->get_int("!virtrade", 0)==0) {

    for (int i=0;i<howmany;i++) {

      MudObject *thing = clone_object(b, mfor, 0);
      thing->set_bflag(FL_NOSAVE, 0);
      thing->set("zone", "@auto");

      thing->set("$clonedat", now);
      thing->set("$clonedby", shop.mob->id);
      
      if (!dotrap(E_ONBOUGHT, player, thing, shop.mob, 0)) {
	/* ::: bought o1==actual item, o2==shopkeeper; return 1 if you don't want the shopkeeper to 'give' item to player */
	
	things.add(*thing);
	
	if (thing->get_flag(FL_FIXED))
	  set_owner(thing, shop.mob->owner);

	if (!cost) {
	  if (thing) {
	    thing->set("value", 0);
	  } 
	}
      }
    }

    if (things.getsize()) {   
      if (!b->get_flag(FL_FIXED)) {
	if (shop.mob == mfor) {
	  player->oprintf(cansee && avoid(shop.mob), "%#M %[conjures up/conjure up] %s for %s.\n", shop.mob, give_number(b, howmany).c_str(), 
			  himself_or_herself(mfor));
	  shop.mob->printf("You conjure up %s for yourself.\n", give_number(b, howmany).c_str(), player);
	} else {
	  mfor->printf("%#M %[gives/give] %s to you.\n", shop.mob, give_number(b, howmany).c_str());
	  mfor->oprintf(cansee && avoid(shop.mob), "%#M %[gives/give] %s to %M.\n", shop.mob, give_number(b, howmany).c_str(), mfor);
	  shop.mob->printf("You give %s to %M.\n", give_number(b, howmany).c_str(), mfor);
	  if (mfor==player) {
	    set_prons(player, things);
	  }
	}
      } else {
	shop.mob->printf("You drop %s.\n", give_number(b, howmany).c_str());
	shop.mob->oprintf(cansee, "%#M %[drops/drop] %s.\n", shop.mob, give_number(b, howmany).c_str());
	set_prons(player, things);
      }
    }
  }

  if (!cost) {
    if (howmany>1 || b->get_flag(FL_PLURAL)) {
      if (shop.mob != player)
	tell(player, shop.mob, lang("You can have those for free.", shop.mob));
     } else {
       if (shop.mob != player)
	 tell(player, shop.mob, lang("You can have that for free.", shop.mob));
     }
  } else {
    if (shop.mob != player)
      tell(player, shop.mob, ssprintf(lang("That will be %s thanks.", shop.mob), formatcash(cost, shop.cur, 1)).c_str());

      if (!IS_CAPTAIN(player)) {
	if (streq(si.aname, "shop")) {
	  shop.list->array_set("$sold", si.idx, shop.list->array_get_int("$sold", si.idx, 0)+howmany);
	}
      }
  }

  set_cash(player, shop.cur, kash - cost);
  dotrap(E_AFTERBUY, player, b, shop.mob);
  /* ::: after_buy o1==template bought, o2==shopkeeper; after everything but before object removed from stock */

  si.destock(player, howmany);

  return true;
}

static bool names_match(MudObject *what, const char *str, int catonly, int namesonly) 
{
  if (!catonly) {
    if (what->get("name") && names_match(what->get("name"), str)) return 1;
    if (what->get("short") && names_match(what->get("short"), str)) return 1;
    if (what->get("altshort") && names_match(what->get("altshort"), str)) return 1;
  }
  if (namesonly)
    return 0;
  if (streq(str, "anything")) {
    return 1;
  }
  if (streq(str, "junk")) {
    if (what->get_int("food")!=-1 || what->get_int("alcohol")!=-1) {
      return 0;
    }
    return 1;
  }
  if (category_match(what, str)) 
    return 1;

  return 0;
}

static bool verb_shopfind(MudObject *who, int argc, const char **argv) {
  Divert d(who, "shopfind");

  MudObject *sought=0;
  int se = 0;
  if (argc>=2) {
    sought = planet->get(argv[1]);
    se = 1;
  }
  MudObject *o;
  int i;
  foreach_alpha(planet, o, i) if (o->get_flag(FL_STORE)) if (se) {
    int max = o->array_size("shop");
    for (int j=0;j<max;j++) {
      if (sought) {
	if (o->array_get_object("shop", j)==sought) {
	  who->printf("  %#-30M (%s).\n", o, o->id);
	}
      } else {
	MudObject *s = o->array_get_object("shop", j);
	if (s && s->get("name") && (names_match(s, argv[1]))) {
	  who->printf("  %30M at %-30M (%s %s)\n", s, o, s->id, o->id);
	}
      }
    }
  } else {
    who->printf("%#-30M %10s %s\n", o, o->get("stype")?o->get("stype"):"", o->id);
  }
  return true;
}

static bool verb_restock(MudObject *who, int argc, const char **argv) 
{
	MudObject *shop = who->owner;
	if (!shop->get_flag(FL_STORE)) {
		who->printf("This is not a shop.\n");
		return true;
	}
	if (!IS_CAPTAIN(who) && shop->get_object("shopmob") != who) {
		who->printf("You don't own this shop.\n");
		return true;
	}
	int stock = 0;
	MudObject *of=0;
	for (int i=0;i<shop->array_size("shop");i++) {
		if (shop->array_get_int("$stock", i) <
		    shop->array_get_int("shop", i, "istock")) {
			stock++;
			of = shop->array_get_object("shop", i);
			shop->array_set("$stock", i, shop->array_get_int("shop", i, "istock"));
		}
	}
	if (stock) {
		if (stock == 1) {
			who->printf("You order in more %s.\n", plural_name(of).c_str());
			who->oprintf(cansee, "%#M orders in more %s.\n", who, plural_name(of).c_str());
		} else {	
			who->oprintf(cansee, "%#M orders in more stock.\n", who);
			who->printf("You order in more stock.\n");
		}
	} else {
	       who->printf("Everything is fully in stock.\n");
	}
	return true;
}

static bool verb_tradestats(MudObject *who, int argc, const char **argv)
{
  Divert d(who, "tradestats");

  int i;
  MudObject *o;

  int income=0;
  int expend=0;

  foreach_alpha(planet, o, i) 
    if (o->get_flag(FL_STORE)) {
      int j=0;

      income += o->get_int("$income",0);
      expend += o->get_int("$expend",0);

      for (j=0;j<o->array_size("shop");j++) {
	int sold = o->array_get_int("$sold", j);
	MudObject *it = o->array_get_object("shop", j);
	if (sold != -1 && it) {
	  who->printf("%-30M:%30M:%3i:%6i:%10i\n", o, it, sold,
		      it->get_int("cost",0),
		      it->get_int("cost",0)*sold);
	}
      }
  }
  return true;
}


#include "verbmodule.h"
void startup() {

AUTO_VERB(value, 2, 0,       PFL_AWAKE);
AUTO_VERB(list, 3, 0, PFL_AWAKE);
AUTO_VERB(preview, 3, 0, PFL_AWAKE);
AUTO_VERB(buy, 3, 0, PFL_AWAKE);
AUTO_VERB(sell, 3, 0, PFL_AWAKE);
AUTO_VERB(size,      2, 0,       PFL_AWAKE);

AUTO_VERB(restock, 6, 0, PFL_AWAKE);
AUTO_VERB(tradestats, 6, 0, PFL_GOTO);

AUTO_VERB(shopfind, 5, 0, PFL_GOTO);


}