/**
* The standard inheritable object for player-run shop npc shopkeepers.
*
* <p><b>Description</b></p>
* <p>With the exception of the office, the shopkeeper contains the most
* complex code. This npc is intended to mimic a player as closely as
* possible. It adds and removes stock, and uses the cash register in the
* same way as a player. It moves through the shop to the correct area to
* perform its tasks. It logs sales and purchases in the same way as an
* employee. The exceptions, as far as a customer is concerned, are that
* money and items are implicitly given to the npc when a transaction is
* initiated.
* </p>
* <p>Probably the most 'intelligent' functions of the npc are to do with
* money. Historically, the npc was limited to the lowest denomination and
* was therefore limited by the amount of this coin in the customer's
* possession, or the contents of the register. It also meant that a large
* transaction could result in several hundred coins being exchanged.
* </p>
* <p>The npc now has the ability to take the appropriate coins from a
* player during a sale, and to get the correct amount of money from the
* register depending upon its contents. It will start with the highest
* value coins and work downwards until it has the correct amount of money
* to pay for the goods received. Unfortunately, the npc cannot yet handle
* change, and will therefore return the goods to the customer if unable to
* get the correct money from the register.
* </p>
* <p>The npc will work between the hours specified by the creator, and can
* also be sent home by an employee. The npc will also return home if
* attacked. In this instance, the attacker will be banned from the shop
* automatically, and fired if they are an employee.
* </p>
* <p>The npc will conduct some standard tests on items before they are
* bought from a customer. These are mainly basic stuff like checking the
* item is stocked by the shop. Any custom rules (such as not buying
* unpickled bits, uncharged magic wands, certain types of 'things') can
* be specified by the creator. Obviously, this may have an impact on
* code-maintenance if players change the list of stocked items regularly.
* However, I haven't had to change these rules significantly in the last
* year or so at Tarnach's.
* </p>
*
* @example
* #include "path.h"
*
* inherit "/std/shops/player_shop/shopkeeper";
*
* void setup()
* {
* set_name( "betty" );
* set_short( "Miss Betty Scuttle" );
* basic_setup( "human" , "fighter" , 15 );
* set_gender( "female" );
* add_alias( ({ "scuttle", "betty scuttle", "miss betty scuttle" }) );
* set_long( "This is Miss Scuttle, an amiable middle-aged lady who "
* "helps out in the shop from time to time.\n" );
* set_office( PATH + "office" );
* set_dearie( ({ "love", "my dear", "dear", "honey" }) );
* set_work_hours( ({0,0,23,59,}) );
* }
*
* object *item_test( mixed * items )
* {
* object *give_back_wand= ({}),
* *give_back_unpickled = ({});
*
* foreach( object ob in items )
* {
* if ( ob->query_magic_wand() && !ob->query_charges() )
* {
* give_back_wand += ({ ob });
* }
* }
*
* if ( sizeof( give_back_wand ) )
* {
* do_command( "'I'm sorry, we don't buy uncharged magic wands." );
* }
*
* foreach( object ob in items )
* {
* if ( ( ob->query_property( "corpse bit" ) && !ob->no_decay() &&
* !ob->query_cured() ) || ob->query_decay_speed() )
* {
* give_back_unpickled += ({ ob });
* }
* }
* if ( sizeof( give_back_unpickled ) )
* {
* do_command( "'We can't accept unpickled items." );
* }
* return ( give_back_wand + give_back_unpickled );
* }
*
* @see /include/player_shop.h
* @see /std/shops/player_shop/office.c
* @see /std/shops/player_shop/mgr_office.c
* @see /std/shops/player_shop/storeroom.c
* @see /std/shops/player_shop/shop_front.c
* @see /std/shops/player_shop/shopkeeper.c
* @author Ringo
* @started 1st August 1999
*/
inherit "/obj/monster";
#include <player_shop.h>
#include <money.h>
#include <living.h>
#include <move_failures.h>
#define set_busy(O) (_busy = O)
#define reset_busy() (_busy = 0)
#define NOT_EIGHT (({ "between seven and nine", \
"just over seven", \
"just under nine", \
"seven plus one", \
"nine minus one", \
"two times four", \
})[random(5)])
#define num_str(i) ((i == 8)?NOT_EIGHT:query_num(i))
#define DEARIE (", "+ _dearie[random(sizeof(_dearie)-1)])
private nosave object _busy = 0;
private nosave mixed _info = 0;
private nosave string _office = "",
_storeroom = "",
_shop_front = "",
_counter = "",
_place = "",
_smallest_in,
_smallest_plural_in,
*_dearie = ({""});
private nosave int * _work_hrs = ({9,0,17,30,}),
_smallest_value_in,
_failed = 0;
private nosave mapping _values_sing = ([]),
_values_plural = ([]);
private void announce_number();
private void announce_price();
private void buy_something(object *);
private void check_money(mixed);
private void deposit_money();
int do_buy(string);
int do_browse(string);
int do_list(mixed *);
int do_sell(object *);
private void end_shift();
private void get_from_store();
private void get_money();
private void get_more_stuff();
private void give_stuff();
private void how_many(string);
private void how_much(string);
private void look_chart();
private void look_how_much();
private void no_coins();
private int outside_hours(int);
private void put_in_store();
private int query_busy();
string query_office();
private void say_to_room(object, string, string);
private void sell_something(string);
protected object * item_test(object *);
protected void set_dearie(string *);
void set_failed(int);
protected void set_office(string);
protected void set_work_hours(int *);
void shuffle_to(string,function);
private int sort_keys(string, string, int);
void start_shift();
private void taken_over();
private void what_items();
/** @ignore yes */
void create()
{
do_setup++;
::create();
do_setup--;
add_property("determinate", "");
add_property("unique", 1);
add_property("run away", -1);
add_alias("shopkeeper");
if (!do_setup)
{
this_object()->setup();
this_object()->reset();
}
}
/* create() */
/** @ignore yes */
private void announce_number()
{
if (stringp(_info))
{
int number = _storeroom->query_num_items(_info);
if (!number) do_command("'I'm afraid we don't have any "+ _info +
" in stock"+ DEARIE+ ".");
else do_command("'You're in luck"+ DEARIE+ ". We have "+
num_str(number) + " in stock.");
}
reset_busy();
_info = 0;
}
/* announce_number() */
/** @ignore yes */
private void announce_price()
{
if (stringp(_info))
do_command("'We sell "+ _info + " for "+
num_str(_office->query_sell(_info))+ " "+ _smallest_plural_in+
" each, and buy them for "+ num_str(_office->query_buy(_info))+
" "+ _smallest_plural_in + " each.");
reset_busy();
_info = 0;
}
/* announce_price() */
/** @ignore yes */
void attack_by(object baddie)
{
if (_office == "") return;
this_object()->stop_fight(baddie);
baddie->stop_fight(this_object());
do_command("'Why are you attacking me, "+ baddie->query_cap_name()+ "?");
do_command("'I'm not staying here for this!");
do_command(_office->query_channel()+ "@ is being attacked by "+
baddie->query_cap_name()+ ".");
call_out("shuffle_to", NPC_DELAY, _office, (: end_shift() :));
_office->event_death(this_object(), 0, baddie, 0, 0);
#ifdef DEBUG
tell_creator(CREATOR, "Attacked by %s.\n", baddie->query_short());
#endif
}
/* attack_by() */
/** @ignore yes */
private void buy_something(object *obs)
{
int spare, number, amount = 0;
object *give_back, cont = clone_object("/std/container");
obs->move(cont);
_info = ({});
foreach(string item in _office->query_list_array())
_info += ((class obj_match)match_objects_in_environments(item, cont))->objects;
give_back = obs - _info;
if (sizeof(give_back))
{
give_back->move(_busy);
do_command("'I'm afraid I can't buy "+
query_multiple_short(give_back)+ " from you"+ DEARIE+ ".");
}
if (number = sizeof(_info))
{
if (_info[0]->query_collective())
number = _info[0]->query_amount();
}
if (number > (MAX_INVEN + query_dex()))
{
_info->move(_busy);
do_command("'I'm sorry"+ DEARIE+ ", but I can only carry "+
num_str(MAX_INVEN+ query_dex())+ " items.");
cont->dest_me();
reset_busy();
return;
}
give_back = ({});
give_back = filter(_info, (: ($1)->query_continuous() :));
if (sizeof(give_back))
{
_info -= give_back;
do_command("'I have nothing to carry "+
query_multiple_short(give_back)+ " in.");
give_back->move(_busy);
}
give_back = item_test(_info);
if (sizeof(give_back))
{
give_back->move(_busy);
_info -= give_back;
}
if (!(number = sizeof(_info)))
{
do_command("'You don't have anything to sell"+ DEARIE+ ".");
cont->dest_me();
reset_busy();
return;
}
if (_info[0]->query_collective())
number = _info[0]->query_amount();
#ifdef DEBUG
tell_creator(CREATOR, "Checking stock.\n");
#endif
spare = _office->query_max(pluralize(_info[0]->query_name())) -
_office->query_stock(pluralize(_info[0]->query_name()));
if (spare < 1)
foreach(string alias in _info[0]->query_alias())
{
int check_spare;
if((check_spare = _office->query_max(pluralize(alias)) -
_office->query_stock(pluralize(alias))) > 0)
spare = check_spare;
}
if (spare < 1)
spare = _office->query_max(pluralize(_info[0]->query_short())) -
_office->query_stock(pluralize(_info[0]->query_short()));
if (spare < 1)
{
do_command("'I'm afraid we don't need any of those"+ DEARIE+ ".");
_info->move(_busy);
cont->dest_me();
reset_busy();
return;
}
if (number > spare)
{
do_command("'I'm afraid we don't need that many"+ DEARIE+
", but I could take "+ num_str(spare)+ " from you.");
_info->move(_busy);
cont->dest_me();
reset_busy();
return;
}
/* Let's check if we can afford to buy this stuff... */
#ifdef DEBUG
tell_creator(CREATOR, "Checking money.\n");
#endif
foreach(string item in _office->query_list_array())
{
obs = ((class obj_match)match_objects_in_environments(item, this_object()))->objects;
if (number = sizeof(obs))
{
if (obs[0]->query_collective())
number = obs[0]->query_amount();
amount += _office->query_buy(item) * number;
}
if (_counter->query_register() < amount * _smallest_value_in)
{
do_command("'I'm afraid we don't have enough money to buy "+
query_multiple_short(_info) + " from you" + DEARIE+ ".");
_info->move(_busy);
cont->dest_me();
reset_busy();
return;
}
}
give_back = ({});
#ifdef DEBUG
tell_creator(CREATOR, "Checking we can carry it.\n");
#endif
if (sizeof(_info))
{
obs = _info;
foreach(object thing in obs)
if ((int)thing->move(this_object()) != MOVE_OK)
{
#ifdef DEBUG
tell_creator(CREATOR, "Can't carry %O.\n", thing);
#endif _info -= ({ thing });
give_back += ({ thing });
}
tell_room(environment(this_object()), query_short()+
" takes "+ query_multiple_short(_info) +".\n");
}
obs = all_inventory(cont) - give_back;
if (sizeof(give_back))
{
do_command("'I cannot carry "+ query_multiple_short(give_back, "the")
+DEARIE+".");
give_back->move(_busy);
}
if (sizeof(obs))
{
do_command("'I'm afraid we can't buy "+
query_multiple_short(obs, "the") +" from you"+ DEARIE+ ".");
obs->move(_busy);
}
obs = all_inventory(cont);
if (sizeof(obs)) obs->move(environment(_busy),
"$N manage$s to get knocked onto the floor.");
if (sizeof(_info))
{
#ifdef DEBUG
tell_creator(CREATOR, "Buying: %O\n", _info);
#endif
do_command("'I'll be right back.");
call_out("shuffle_to", NPC_DELAY, _counter, (: get_money() :));
}
else reset_busy();
cont->dest_me();
}
/* buy_something() */
/* Check have correct change */
private void check_money(mixed monies)
{
#ifdef DEBUG
tell_creator(CREATOR, "Buying %d %s for %d each - total %d.\n", monies[1],
monies[2], monies[3], monies[0] );
#endif
if (monies[0] > query_value_in(_place))
{
#ifdef DEBUG
tell_creator(CREATOR, "No correct change (%d).\n", query_value_in(_place));
#endif
do_command("put coins in register");
call_out("shuffle_to", NPC_DELAY, _shop_front, (: no_coins() :));
return;
}
do_command("log shop bought "+ monies[1] +" "+ monies[2] +
" for "+ (monies[3] * monies[1]));
#ifdef DEBUG
tell_creator(CREATOR, "Total: %d My value: %d\n", monies[0],
query_value_in( _place ) );
#endif
call_out("shuffle_to", NPC_DELAY, _storeroom, (: put_in_store() :));
}
/* check_money() */
/** @ignore yes */
private void deposit_money()
{
if (pointerp(_info))
{
do_command("log shop sold "+ _info[1]+ " "+ _info[0]+ " for "+
_info[2]);
do_command("put coins in register");
}
call_out("shuffle_to", NPC_DELAY, _storeroom, (: get_from_store() :));
}
/* deposit_money() */
/** @ignore yes */
int do_buy(string stuff)
{
#ifdef DEBUG
tell_creator(CREATOR, "In do_buy()\n");
#endif
if (outside_hours(TRUE)) return 1;
say_to_room(this_player(), "say", "I would like to buy "+ stuff +
", please.\n%^RESET%^");
if (query_busy())
init_command(":seems busy at the moment.", NPC_DELAY);
else
{
set_busy(this_player());
call_out((: sell_something($(stuff)) :), NPC_DELAY);
}
return 1;
}
/* do_buy() */
/** @ignore yes */
int do_browse(string item)
{
#ifdef DEBUG
tell_creator(CREATOR, "In do_browse()\n");
#endif
if (outside_hours(TRUE)) return 1;
say_to_room(this_player(), "ask", "How much do "+ item +" cost?\n");
if (query_busy())
init_command(":seems busy at the moment.", NPC_DELAY);
else
{
set_busy(this_player());
call_out((: how_much($(item)) :), NPC_DELAY);
}
return 1;
}
/* do_browse() */
/** @ignore yes */
int do_list(mixed *args)
{
#ifdef DEBUG
tell_creator(CREATOR, "In do_list()\n");
#endif
if (outside_hours(TRUE)) return 1;
if (!sizeof(args) || !args[0] || args[0] == "")
{
say_to_room( this_player(), "ask", "What items do you buy and sell?\n");
if (query_busy())
init_command(":seems busy at the moment.", NPC_DELAY);
else
{
set_busy(this_player());
call_out((: what_items() :), NPC_DELAY);
}
return 1;
}
say_to_room(this_player(), "ask", "How many "+ args[0]+ " do you have?\n");
if (query_busy())
init_command(":seems busy at the moment.", NPC_DELAY);
else
{
set_busy(this_player());
call_out((: how_many($(args[0])) :), NPC_DELAY);
}
return 1;
}
/* do_list() */
/** @ignore yes */
int do_sell(object *obs)
{
#ifdef DEBUG
tell_creator(CREATOR, "In do_sell()\n" );
#endif
if (outside_hours(TRUE)) return 1;
say_to_room(this_player(), "say", "I would like to sell " +
query_multiple_short(obs, "the")+ ", please.\n%^RESET%^");
if (query_busy())
init_command(":seems busy at the moment.", NPC_DELAY);
else
{
set_busy(this_player());
call_out((: buy_something($(obs)) :), NPC_DELAY);
}
return 1;
}
/* do_sell() */
/** @ignore yes */
private void end_shift()
{
if (environment() != find_object("/room/rubbish"))
do_command(_office->query_channel()+ "@ is clocking out.");
do_command("clock out");
move("/room/rubbish", "$N make$s some tea.", "$N go$s home for tea.");
}
/* end_shift() */
/** @ignore yes */
void event_person_say(object thing, string start, string mess, string lang)
{
string words;
#ifdef DEBUG
tell_creator(CREATOR, "In event_person_say()\n" );
#endif
if (outside_hours(TRUE)) return;
if (!mess || (mess == "")) return;
mess = lower_case(mess);
if (query_busy()) return;
set_busy(thing);
if (sscanf(mess, "%*s buy %s, please.", words) == 2)
{
call_out((: sell_something($(words)) :), NPC_DELAY);
return;
}
if (sscanf(mess, "%*show many %s", words) == 2)
{
call_out((: how_many($(words)) :), NPC_DELAY);
return;
}
if (sscanf(mess, "%*show much %s", words) == 2)
{
call_out((: how_much($(words)) :), NPC_DELAY);
return;
}
if (( sscanf( mess, "%*s take over%*s") == 2) &&
(_office->query_employee(thing->query_name()) ||
thing->query_name() == CREATOR))
{
call_out((: taken_over() :), NPC_DELAY);
return;
}
reset_busy();
::event_person_say(thing, start, mess, lang);
}
/* event_person_say() */
/** @ignore yes */
private void get_from_store()
{
if (pointerp(_info))
{
int number, bag_item_weight, info1 = to_int(_info[1]);
object *inven;
_failed = 0;
if (info1 > (MAX_INVEN + query_dex())) number = MAX_INVEN + query_dex();
else number = info1;
#ifdef DEBUG
tell_creator(CREATOR, "Removing %d %s\n", number, _info[0]);
#endif
inven = all_inventory();
do_command("remove " + number+ " "+ _info[0]);
if (_failed)
{
do_command("add "+ _failed+ " "+ _info[0]);
do_command("get "+ _info[0]);
}
inven = all_inventory() - inven;
number -= _failed;
info1 -= number;
foreach(object thing in inven)
if (thing->query_weight() < MAX_BAG_WEIGHT/5)
bag_item_weight += thing->query_weight();
bag_item_weight = (MAX_BAG_WEIGHT+bag_item_weight-1)/MAX_BAG_WEIGHT;
if ( number > 9 )
{
number = (((number+9)/10) > bag_item_weight)?
(number+9)/10:bag_item_weight;
for(int i=0; i < number; i++) do_command("pull roll");
for(int i=0; i < number; i++)
do_command("put "+ _info[0]+ " in bag "+ (i+1));
}
if (info1)
{
_info[1] = sprintf("%d",info1);
call_out("shuffle_to", NPC_DELAY, _shop_front,
(: get_more_stuff() :));
return;
}
}
call_out("shuffle_to", NPC_DELAY, _shop_front, (: give_stuff() :));
}
/* get_from_store() */
/** @ignore yes */
private void get_money()
{
if (pointerp(_info))
{
int total;
mixed monies = ({0,0,0,0,});
string *sort_keys_sing = sort_array(keys(_values_sing),
(: sort_keys($1,$2,1) :));
string *sort_keys_plural = sort_array(keys(_values_plural),
(: sort_keys($1,$2,0) :));
#ifdef DEBUG
tell_creator(CREATOR, "Singular: %O\n", sort_keys_sing);
tell_creator(CREATOR, "Plural: %O\n", sort_keys_plural);
#endif
foreach(string item in _office->query_list_array())
{
object *things = ((class obj_match)match_objects_in_environments(item,
this_object()))->objects;
#ifdef DEBUG
tell_creator(CREATOR, "We have %d %s\n", sizeof(things), item);
#endif
if (monies[1] = sizeof(things))
{
if (things[0]->query_collective())
monies[1] = things[0]->query_amount();
monies[3] = _office->query_buy(item);
do_command( "'"+ capitalize(item) +" are worth "+
num_str(monies[3]) +" "+ _smallest_plural_in +
" each, and I have "+ num_str(monies[1]) + ".");
monies[0] += monies[3] * monies[1];
monies[2] = item;
break;
}
}
monies[0] *= _smallest_value_in;
total = monies[0];
for (int i=0; i < sizeof(sort_keys_sing); i++)
{
if (total < _values_sing[sort_keys_sing[i]]) continue;
for (int c = total / _values_sing[sort_keys_sing[i]]; c > 0; c--)
{
string coins = sprintf("%d %s", c,
(c == 1)?sort_keys_sing[i]:sort_keys_plural[i]);
#ifdef DEBUG
tell_creator(CREATOR, "Trying to get %s\n", coins);
#endif
do_command("get "+ coins+ " from register");
if (sizeof(match_objects_for_existence(coins, this_object())))
{
total -= (c * _values_sing[sort_keys_sing[i]]);
#ifdef DEBUG
tell_creator(CREATOR, "New total: %d\n", total);
#endif
break;
}
}
}
#ifdef DEBUG
tell_creator(CREATOR, "Buying %d %s for %d each.\n", monies[1],
monies[2], monies[3]);
#endif
call_out((: check_money($(monies)) :), 1);
}
else
call_out("shuffle_to", NPC_DELAY, _storeroom, (: put_in_store() :));
}
/* get_money() */
/** @ignore yes */
private void get_more_stuff()
{
if (query_busy())
{
do_command("give all to "+ _busy->query_name());
do_command("drop all");
do_command("'I still need to get you "+ num_str(to_int(_info[1]))+ " "+
_info[0]+ DEARIE+ ". I won't be long.");
call_out( "shuffle_to", NPC_DELAY, _storeroom,
(: get_from_store() :) );
return;
}
else
{
do_command("'Well now. I wonder where they could have gone.");
do_command("ponder");
}
do_command("drop all");
reset_busy();
_info = 0;
}
/* get_more_stuff() */
/** @ignore yes */
private void give_stuff()
{
if (query_busy())
{
do_command("give all to "+ _busy->query_name());
do_command("'Thank you for your custom" + DEARIE+ ".");
}
else
{
do_command("'Well now. I wonder where they could have gone.");
do_command("ponder");
}
do_command("drop all");
reset_busy();
_info = 0;
}
/* give_stuff() */
/** @ignore yes */
private void how_many(string item)
{
_info = 0;
foreach (string thing in _office->query_list_array())
if (sscanf(item, "%*s"+ thing +"%*s") == 2) _info = item;
if (!_info)
{
do_command("'I'm afraid we don't deal in those" + DEARIE+ ".");
reset_busy();
return;
}
do_command("'"+ capitalize( _info ) +"? I'll just go and see.");
call_out("shuffle_to", NPC_DELAY, _office, (: look_chart() :));
}
/* how_many() */
/** @ignore yes */
private void how_much(string item)
{
_info = 0;
foreach (string thing in _office->query_list_array())
if (sscanf(item, "%*s"+ thing +"%*s") == 2) _info = thing;
if (!_info)
{
do_command("'I'm afraid we don't deal in those"+ DEARIE+ ".");
reset_busy();
return;
}
do_command("'"+ capitalize( _info ) +"? I'll just go and see.");
call_out("shuffle_to", NPC_DELAY, _office, (: look_how_much() :));
}
/* how_much() */
/** @ignore yes */
void init()
{
object tp = this_player();
#ifdef DEBUG
tell_creator(CREATOR, "In init()\n" );
#endif
if (environment(this_object()) == find_object("/room/rubbish")) return;
#ifdef DEBUG
tell_creator(CREATOR, "Not in /room/rubbish\n");
#endif
::init();
tp->add_command("buy", this_object(), "<string'item(s)'>",
(: do_buy($4[0]) :));
tp->add_command("sell", this_object(), "<indirect:object:me>",
(: do_sell($1) :));
tp->add_command("list", this_object(), ({"", "<string'item(s)'>"}),
(: do_list($4) :));
tp->add_command("browse", this_object(), "<string'item(s)'>",
(: do_browse($4[0]) :));
outside_hours(TRUE);
}
/* init() */
/** @ignore yes */
private void look_chart()
{
if (stringp(_info)) do_command("'Now, let me see, "+ _info +"...");
call_out("shuffle_to", NPC_DELAY, _shop_front, (: announce_number() :));
}
/* look_chart() */
/** @ignore yes */
private void look_how_much()
{
if (stringp(_info)) do_command("'Now, let me see, "+ _info +"...");
call_out("shuffle_to", NPC_DELAY, _shop_front, (: announce_price() :));
}
/* look_how_much() */
/** @ignore yes */
private void no_coins()
{
if (query_busy())
{
do_command("give all to "+ _busy->query_name());
do_command("'I'm sorry" + DEARIE+ ". I don't have the correct "
"change to give you. Please call again later.");
}
else
{
do_command("'Well now. I wonder where they could have gone.");
do_command("ponder");
}
do_command("drop all");
reset_busy();
_info = 0;
}
/* no_coins() */
/** @ignore yes */
private int outside_hours(int working)
{
int hrs, mins;
string am_pm;
if (query_busy())
{
#ifdef DEBUG
tell_creator(CREATOR, "Outside working hours but already busy. "
"Continuing.\n");
#endif
return FALSE;
}
if (sscanf(amtime(time()), "%d:%d%s,%*s", hrs, mins, am_pm) == 4)
{
#ifdef DEBUG
tell_creator(CREATOR, "Time is %02d:%02d%s.\n", hrs, mins, am_pm);
#endif
if (am_pm == "am")
{
if (hrs == 12) hrs = 0;
}
else
if (hrs != 12) hrs += 12;
#ifdef DEBUG
tell_creator(CREATOR, "Which is %02d:%02d.\n", hrs, mins);
#endif
if ((hrs < _work_hrs[0] ||
(hrs == _work_hrs[0] && mins < _work_hrs[1])) ||
(hrs > _work_hrs[2] ||
(hrs == _work_hrs[2] && mins > _work_hrs[3])))
{
if (!working)
{
#ifdef DEBUG
tell_creator(CREATOR, "Outside working hours & not "
"working. Sending to /room/rubbish.\n");
#endif
move("/room/rubbish");
return TRUE;
}
#ifdef DEBUG
tell_creator(CREATOR, "Outside working hours & working. "
"Sending home now.\n");
#endif
call_out("shuffle_to", NPC_DELAY, _office, (: end_shift() :));
return TRUE;
}
}
return FALSE;
}
/* outside_hours() */
/** @ignore yes */
private void put_in_store()
{
if (pointerp(_info)) do_command("add all");
do_command("drop all");
foreach (string value in keys(_values_sing))
do_command("get every "+ value);
call_out("shuffle_to", NPC_DELAY, _shop_front, (: give_stuff() :));
}
/* put_in_store() */
/** @ignore yes */
private int query_busy()
{
if (!objectp(_busy) || !interactive(_busy) || _busy->query_invis())
return 0;
if (environment(_busy) != find_object(_shop_front)) return 0;
return (_busy != 0);
}
/* query_busy() */
/**
* Query the path to the office.
* @return The path to the office.
*/
string query_office() { return copy(_office); }
/** @ignore yes */
private void say_to_room(object me, string t, string message)
{
tell_object(me, "You "+ t+ ": "+ message);
foreach(object player in all_inventory(environment(this_object())))
if (interactive(player) && player != me)
tell_object(player, player->colour_event("say", "%^CYAN%^")+
me->query_short()+ " "+ t + "s: "+ message);
}
/* say_to_room() */
/** @ignore yes */
private void sell_something(string items)
{
int each, number, in_stock;
mixed *money_array;
_info = 0;
foreach (string item in _office->query_list_array())
if (sscanf(items, "%*s"+ item +"%*s") == 2) _info = item;
else if (sscanf(pluralize(items), "%*s"+ item +"%*s") == 2)
_info = item;
if (!_info)
{
do_command("'I'm afraid we don't deal in those"+ DEARIE+ ".");
reset_busy();
return;
}
if (sscanf(items, "%*s%d "+ _info +"%*s", number) != 3) number = 1;
#ifdef DEBUG
tell_creator(CREATOR, "Customer asked for %d %s.\n", number, _info);
#endif
if (number > (in_stock = _storeroom->query_num_items(_info)))
{
if (!in_stock)
{
do_command("'I'm afraid we don't have any "+ _info+ " in stock"+
DEARIE+ ".");
reset_busy();
return;
}
do_command("'I'm afraid we don't have that many "+ _info +
" in stock, but I can sell you "+ num_str(in_stock)+ ".");
reset_busy();
return;
}
if (number < 1)
{
do_command("'My, my. You are amusing aren't you" + DEARIE+ "?");
do_command("peer "+ _busy->query_name());
reset_busy();
return;
}
each = _office->query_sell(_info);
do_command( "'"+ capitalize(_info) +" are worth "+
num_str(each) +" "+ _smallest_plural_in + " each, and you want "+
num_str(number) + ".");
each *= number;
if (_busy->query_value_in(_place) < (each * _smallest_value_in))
{
do_command("'I'm afraid you don't have enough money" + DEARIE+ ".");
reset_busy();
return;
}
money_array = MONEY_HAND->create_money_array(each * _smallest_value_in,
_place);
_busy->pay_money(money_array, _place);
adjust_money(money_array);
#ifdef DEBUG
tell_creator(CREATOR, "Monies adjusted by %O.\n", money_array);
#endif
tell_room( environment( this_object() ), query_short()+
" takes the money.\n" );
do_command("'I'll be right back"+ DEARIE+ ".");
_info = ({ _info, sprintf("%d", number), sprintf("%d", each) });
#ifdef DEBUG
tell_creator(CREATOR, "Selling: %O\n", _info);
#endif
call_out("shuffle_to", NPC_DELAY, _counter, (: deposit_money() :));
}
/* sell_something() */
/**
* Test items before buying.
* This function adds more shop-specific checks to see if we should buy
* something not covered by the standard checks.
* The function should take a parameter of an mixed array, being the objects
* the customer is trying to sell. It should return an array of the objects
* we are not buying. Any response by the npc should be handled within the
* function. All other processing will be handled by the inheritable.
* If you need such processing, create this function within your own
* object.
*
* @example object *item_test( mixed * items )
* {
* object *give_back = ({});
*
* foreach( object ob in items )
* {
* if ( ( ob->query_property( "corpse bit" ) && !ob->no_decay() &&
* !ob->query_cured() ) || ob->query_decay_speed() )
* {
* give_back += ({ ob });
* }
* }
* if ( sizeof( give_back ) )
* {
* do_command( "'We can't accept unpickled items, my dear." );
* }
*
* return ( give_back );
* }
*
* @param items The objects to test
* @return object The objects to give back to the customer
*/
protected object * item_test(mixed * items) { return ({}); }
/**
* Set the pleasantries to be used by this npc, i.e.
* @example set_dearie( ({ "dearie", "my dear", ... }) );
* @param dearie An array of strings to be used
*/
protected void set_dearie(string *dearie) { _dearie = dearie; }
/** @ignore yes */
void set_failed(int failed) { _failed = failed; }
/**
* Set the path of the main office.
* @param path The full path & filename.
*/
protected void set_office(string path)
{
mapping values;
string new_key;
_office = path;
_place = _office->query_place();
_smallest_in = MONEY_HAND->smallest_in( _place );
_smallest_value_in = MONEY_HAND->smallest_value_in(_place);
_smallest_plural_in = MONEY_HAND->query_plural_for(_smallest_in);
values = MONEY_HAND->query_mapped_values_in(_place);
foreach(string key in keys(values))
{
new_key = MONEY_HAND->query_aliases_for(key)[0];
_values_sing += ([new_key:values[key]]);
new_key = MONEY_HAND->query_details_for(key)[5];
_values_plural += ([new_key:values[key]]);
}
#ifdef DEBUG
tell_creator(CREATOR, "Singular values: %O.\n", _values_sing);
tell_creator(CREATOR, "Plural values: %O.\n", _values_plural);
#endif
_storeroom = _office->query_storeroom();
_counter = _office->query_counter();
_shop_front = _office->query_shop_front();
add_respond_to_with(({ "@say", "hello" }), "'Hello, $hcname$. What can I do for you?");
add_respond_to_with(({ "@smile", query_name() }), "smile at $hname$");
}
/* set_office() */
/**
* Set the working hours for this shopkeeper.
* Will set the hours that this shopkeeper will work. This is an array
* of the format ({start_hour,start_min,end_hour,end_min})
* @param hours an array as above
*/
protected void set_work_hours(int *hours) { _work_hrs = hours; }
/** @ignore yes */
void shuffle_to(string location, function next_action)
{
string direc = environment()->directions_to(location);
if (!direc)
{
if (environment() == find_object("/room/rubbish")) return;
if (environment() == _shop_front->query_outside())
{
move(_shop_front, "$N enter$s the shop.",
"$N enter$s _shop_front->query_short().");
}
else
{
do_command("'I seem to be lost...");
do_command("sob");
move(_office, "$N runs in, sobbing.", "$N leave$s, sobbing.");
end_shift();
return;
}
call_out("shuffle_to", NPC_DELAY, location, next_action);
return;
}
if (direc == "here")
{
do_command("smile brightly");
do_command("'Here we are.");
if (next_action) call_out(next_action, NPC_DELAY);
else reset_busy();
return;
}
do_command(direc);
call_out("shuffle_to", NPC_DELAY, location, next_action);
}
/* shuffle_to() */
/** @ignore yes */
private int sort_keys(string s1, string s2, int sing)
{
if (sing) return (_values_sing[s1] < _values_sing[s2])?1:-1;
else return (_values_plural[s1] < _values_plural[s2])?1:-1;
}
/* sort_keys() */
/** @ignore yes */
void start_shift()
{
#ifdef DEBUG
tell_creator(CREATOR, "In start_shift()\n" );
#endif
if (outside_hours(FALSE)) return;
set_busy(this_object());
move(_office, "$N arrive$s for " + query_possessive()+ " shift.");
if (_office->query_employee(query_name()) & CLOCKED_IN)
do_command("clock out");
do_command("claim badge");
do_command("wear badge");
do_command("clock in");
do_command(_office->query_channel()+ "@ is clocking in.");
call_out("shuffle_to", NPC_DELAY, _shop_front, 0);
}
/* start_shift() */
/** @ignore yes */
private void taken_over()
{
do_command("'Okay then" + DEARIE+ ". See you later.");
set_busy(this_object());
call_out("shuffle_to", NPC_DELAY, _office, (: end_shift() :));
}
/* taken_over() */
/** @ignore yes */
private void what_items()
{
do_command("'Well" + DEARIE+ ", we trade in "+
_office->query_list_string()+ ".");
reset_busy();
}
/* what_items() */