/**
* Auction Room Inheritable
* This room will give you everything you need to make an auction
* room.
* @author Terano
* @started 3 October, 1998
* @changed 24 March, 1999 Many exciting things. Terano.
* @changed 2 October, 1999 Added stability! Excitement!
* An excessively complicated browse function! Terano.
* @changed 8 January, 2000! Made Y2K compliant! (Just kidding)
* Added some advertising stuff.
* Fixed a missing case as well.
* @changed 11 January, 2000 Fixed some advertising stuff.
*/
#include <money.h>
#include <move_failures.h>
#include <mail.h>
#define COLOUR_ID "colour code"
#define AUCTION_PLAYER_NAME 0
#define AUCTION_PLAYER_BID 1
#define ERRNOSPACE -1 //No space
#define ERRBADPARM -2 //Bad parameter
#define ERRBADCBOB -3 //Bad call back object
#define ERROBNOMOVE -4 //Autoload gen failed due to bad move
#define ERRGENFAIL -5 //General failure
#define ERRNORECV -6 //Could not recover autoloading
#define ERRRECVEMP -7 //Case recovered as empty
#define WARNUNDEST -8 //An object being converted was undestable.
#define WARNEXCL -9 //Bidder has been excluded
#define WARNNOLOTS "NULL" //We have no lots!
#define TCTP( x, y ) tell_creator( TP, x, y )
#define GECM( x ) "/global/player/events"->convert_message( x )
#define c_item class item
#define c_store class store
#define c_lot class lot
#define OPEN "open"
#define CLOSED "closed"
#define WAIT_SELLER "waitsell"
#define WAIT_BUYER "waitbuy"
#define WAIT_BUYER_LAPSE "waitbuylapse"
#define WAIT_CRE_INTERVENTION "waitcreint"
#define CHECK_CYCLE 10 //Apparently the control function is efficient enough
#define COLOURS ({ "red", "orange", "yellow", "blue", "green", "indigo",\
"violet", "crimson", "puce", "emerald", "ochre", "tangerine", "aquamarine",\
"silver", "gold", "copper", "lilac" })
#define NO_COLOURS sizeof( COLOURS )
nosave inherit ROOM_OBJ; //Why save all those evil room variables?
inherit "/global/player/auto_load";
/**
* In this new version, we aren't keeping the objects loaded,
* because the auto_load code seems flaky outside of
* /global/player.c
* Instead keep a whole bunch of info about it on file.
*/
c_item {
string name;
string *alias;
string *adjectives;
string long;
string read_mess;
string enchant_mess;
}
c_store {
int case_code;
string colour;
string inv_string;
c_item *inventory;
}
/**
* A few notes about this class, I didn't like the way the old one
* worked, when a lot moved to expired_lots, there was no real way to recover it
* Instead, we will keep one array of all lots, with differing status's depending
* on whats happening.
* IE: Open: Accepting bids.
* Waiting: Seller: It did not take any acceptable bids and it is waiting collection
* by the seller.
* Waiting: Buyer: A bid was taken and we are waiting for payment.
* Waiting: Buyer (lapsed): A bid was taken, but lapsed. There are no
* other bidders, so in the next cycle the seller will be mailed and
* status changed to Waiting: Seller.
* Waiting Creator Intervention: A lot has fallen over in some way,
* and needs a creator to fix it.
* Closed: The bid has been collected (by either buyer or seller),
* lots will stay in this status for 2 hours after collection.
* Then payment will be credited to the payment array and the lot will
* be deleted.
* Case code is used in reference to the store class.
* The rest are the same as they were in the last version
*/
c_lot {
int case_code; //The code that matches this bid.
c_store assoc_store; //Associated store, to save processing!
string seller; //Name of the person/object selling
string status; //Either Open, Waiting: Seller, Waiting: Buyer or Closed.
int expiration_time; //Time that this status stops
int reserve_price; //Min price
mapping bids; //Mapping of bids
mixed *lot_autoload; //Autoload info for the bid
mixed *current_bid; //Info on the current bid
string *auto_bids; //I'm not sure yet, but it will become apparent
string notes; //Player added notes
string *excluded; //People player wants excluded from bidding.
}
c_lot *lots = ({ }); //Array of lots
mapping payments = ([ ]); //Stored as ([ "name": amount ]);
int lot_count = 0;
nosave string *used_colours = ({ });
nosave string currency = "default";
nosave string location = "unset";
nosave string shop_name = "REAS Auction Inheritable";
nosave int admin_cost = 2000; //defaults to AM$5
nosave float commission = ( 5.0/100.0 ); //Default to 5%
/**
* I've decided that a large amount of problems with this code
* were from the cases, to add realism, I've left them in, but
* I've removed all the functionality from them and moved it in
* here.
*/
mapping cases = ([ ]); //Something like: ([ /std/object#123: 0001 ])
void save_file();
void load_file();
void set_location( string loc );
void set_currency( string cur );
void set_admin_cost( int value );
void set_commission( int percent );
void set_case_desc( string *bits );
mixed recover_lot( mixed lot_autoload );
int change_status( int lot_ident, string new_status );
void adjust_money( int amount, object player );
mixed generate_auto_load( object *obs );
c_store generate_store( object *things, int lot_id );
c_lot query_status( int lot_ident );
int do_deposit( object *things, int auction_time, string time_type,
string res_price );
int do_bid( string offer, object *boxes );
int do_collect( string pattern );
int do_list();
int do_withdraw( object *boxes);
int do_describe( object *boxes, string int_desc );
int do_exclude( object *cases, string excluded );
int do_browse( object *cases, string target, int identifier );
int do_unexclude( object *cases, string excluded );
string generate_advertising_string( c_lot lot );
string generate_random_adv_str();
/**
* @ignore
*/
void create() {
do_setup++;
::create();
do_setup--;
if ( !do_setup ) {
TO->setup();
TO->reset();
}
call_out( "load_file", 1 ); //To give time for location data to be restored
call_out( "load_store", 2 ); //To give time for save data to be restored
call_out( "lot_check", 5 ); //Lets do some work!
}
/**
* @ignore
*/
void init() {
TP->add_command( "bid", TO,
"<string'amount'> {on|for} <indirect:object:here'case'>",
(: do_bid( $4[0], $1 ) :) );
TP->add_command( "collect", TO,
({ "successful bids", "money", "expired lots",
"all" }),
(: do_collect( $5 ) :) );
TP->add_command( "deposit", TO,
"<indirect:object:me'items'> for <number> "
"{minute|hour|day} auction" ,
(: do_deposit( $1, $4[1], $4[2], "" ) :) );
TP->add_command( "deposit", TO,
"<indirect:object:me'items'> for <number> "
"{minute|hour|day} "
"auction with [a] reserve price of <string'amount'>",
(: do_deposit( $1, $4[1], $4[2], $4[3] ) :) );
TP->add_command( "list", TO, "" );
TP->add_command( "browse", TO,
"<string'object'> in <indirect:object'case'>", (: do_browse( $1, $4[0], 0 ) :) );
TP->add_command( "browse", TO,
"<string'object'> <number> in <indirect:object'case'>",
(: do_browse( $1, $4[0], $4[1] ) :) );
TP->add_command( "withdraw", TO,
"<indirect:object'case'> from auction",
(: do_withdraw( $1 ) :) );
TP->add_command( "write", TO,
"<string'text'> on <indirect:object'case'>",
(: do_describe( $1, $4[0] ) :) );
TP->add_command( "exclude", TO,
"<string'name'> from bidding on <indirect:object'case'>",
(: do_exclude( $1, $4[0] ) :) );
TP->add_command( "exclude", TO,
"list for <indirect:object'case'>",
(: do_exclude( $1, "list" ) :) );
TP->add_command( "exclude", TO,
"current ignore list from bidding on <indirect:object'case'>",
(: do_exclude( $1, "ignore list" ) :) );
TP->add_command( "unexclude", TO,
"<string'name'> from bidding on <indirect:object'case'>",
(: do_unexclude( $1, $4[0] ) :) );
}
/**
* This function allows an object to deposit an item for auction,
* Things are the items to auction, auction length is the length in seconds,
* function_cb and object_cb are function names and object paths to allow a
* call back to be scheduled for when the auction is over, res price is
* the reserve price and int_desc is a description that will be written on
* the case. If an error code is generated, it is upon the object calling
* this function to pull those objects back. An object reference isn't
* kept here.
*/
int automated_deposit( object *things, int auction_length,
string function_cb, string object_cb, int res_price,
string int_desc ) {
mixed *auto_load_string;
c_lot temp;
c_store temp2;
if ( !sizeof( COLOURS - used_colours ) ) {
log_file( "REAS", "%s: ERRNOSPACE generated by: %s!\n", ctime( time() ),
file_name( PO ) );
return ERRNOSPACE;
}
if ( !sizeof( things ) || auction_length < 0 ) {
log_file( "REAS", "%s: ERRBADPARM generated by: %s!\n", ctime( time() ),
file_name( PO ) );
return ERRBADPARM;
}
if ( !objectp( load_object( object_cb ) ) ) {
log_file( "REAS", "%s: ERRBADCBOB generated by: %s!\n", ctime( time() ),
file_name( PO ) );
return ERRBADCBOB;
}
//Work out the new case ID;
lot_count ++;
//Generate the case info
temp2 = generate_store( things, lot_count );
//Store and destroy objects
auto_load_string = generate_auto_load( things );
if ( !arrayp( auto_load_string ) ) {
if ( (int) auto_load_string == ERROBNOMOVE ) {
log_file( "REAS", "%s: ERROBNOMOVE generated by: %s!\n", ctime( time() ),
query_multiple_short( map( things, (: file_name( $1 ) :) ) ) );
return ERROBNOMOVE;
}
log_file( "REAS", "%s: ERRGENFAIL generated by: %s!\n", ctime( time() ),
query_multiple_short( map( things, (: file_name( $1 ) :) ) ) );
return ERRGENFAIL;
}
temp = new( c_lot, assoc_store: temp2, case_code: lot_count, seller: "Auto", status: OPEN,
expiration_time: time() + auction_length, reserve_price: res_price,
bids: ([ ]), current_bid: allocate( 2 ), lot_autoload: auto_load_string,
auto_bids: ({ function_cb, object_cb }), notes: int_desc, excluded: ({ }) );
lots += ({ temp });
return lot_count;
}
/**
* @ignore
*/
mixed generate_auto_load( object *obs ) {
object box = clone_object( BAGGAGE_OBJ );
string *temp;
box->set_name( "box" );
box->set_long( "This is a box used in the auction room inheritable. "
"How did you get hold of one?\n" );
box->set_weight( 500000 );
box->set_max_weight( 500000 );
box->set_volume( 500000 );
if ( sizeof( filter( obs->move( box ), (: $1 != 0 :) ) ) )
//Not all obs moved in properly, scream and die!
return ERROBNOMOVE;
//So far so good.
temp = create_auto_load( ({ box }), 0 );
if ( !arrayp( temp ) )
//Eek!
return ERRGENFAIL;
obs->dest_me();
obs -= ({ 0 });
//I might change this to a fatal error later, I'm not sure how autoloading
//handles undestable objects.
if ( sizeof( obs ) ) {
log_file( "REAS", "%s: WARNUNDEST generated by object %s.\n", ctime( time() ),
query_multiple_short( map( obs, (: file_name( $1 ) :) ) ) );
}
box->dest_me();
return temp;
}
/**
* Return a store class with info on the objects
*/
c_store generate_store( object *things, int lot_id ) {
c_store temp;
c_item temp2;
object temp3;
string *avail_colours = COLOURS - used_colours;
string _colour = avail_colours[ random( sizeof( avail_colours ) ) ];
temp = new( c_store, case_code: lot_id, colour: _colour,
inventory: ({ }), inv_string: GECM( query_multiple_short( things ) ) );
used_colours += ({ _colour });
foreach( temp3 in things ) {
temp2 = new( c_item, name: temp3->query_name(),
alias: temp3->query_alias(), long: temp3->query_long(),
read_mess: temp3->query_readable_message(), enchant_mess:
temp3->enchant_string(), adjectives: temp3->query_adjectives() );
temp->inventory += ({ temp2 });
if ( !stringp( temp2->read_mess ) )
temp2->read_mess = "";
}
return temp;
}
/**
* A major component, this function is called once every few minutes to
* change the state of our bids. It puts new bids out on display, and
* modifies the state of in game lots as required. It also removes old ones.
*/
void lot_check() {
c_lot temp_lot;
c_store current_store;
object new_case;
object *all_new_cases = ({ });
object *obs;
object *exp_cases;
int *active_lots;
string name;
int *bids;
//Lets put the cases in first, this will avoid problems with lots that later
//flip into another status not having cases when the room is loaded.
//Get rid of null entries..
cases = filter( cases, (: objectp( $1 ) :) );
active_lots = map( filter( lots, (: ( (c_lot) $1)->status == OPEN :) ),
(: ( (c_lot) $1)->case_code :) );
if ( sizeof( cases ) < sizeof( active_lots ) ) {
//At least one of our items isn't on display! Find out which one it is
active_lots = active_lots - values( cases );
foreach( int temp in active_lots ) {
//Find our current store..
temp_lot = filter( lots, (: $1->case_code == $(temp) :) )[0];
current_store = temp_lot->assoc_store;
new_case = clone_object( OBJECT_OBJ );
new_case->set_name( "case" );
new_case->set_short( current_store->colour + " case" );
new_case->set_long( "This is a display case used in this auction shop. "
"It contains " + current_store->inv_string +".\nYou can \"browse\" these"
" items.\n" );
new_case->add_adjective( current_store->colour );
new_case->move( TO );
new_case->reset_get();
new_case->add_extra_look( TO );
used_colours += ({ current_store->colour });
cases += ([ new_case: temp ]);
all_new_cases += ({ new_case });
}
//A hook for later on (if you want to put in a nifty message)
if ( sizeof( all_new_cases ) )
TO->add_new_case( all_new_cases );
}
//Lots which require a state change
foreach( temp_lot in ( filter( lots, (: $1->expiration_time < time() :) ) ) ) {
TCTP( "Looking at lot %d. ", temp_lot->case_code );
TCTP( "Status is: %s\n", temp_lot->status );
switch( temp_lot->status ) {
case OPEN: //Switching to another state
TCTP( "In open %s", "\n" );
//Woo. The bidding on this item is done. Did someone grab it?
//If so, let the buyer know someone got it.
//Otherwise let the seller know that they can pick it up
//Change state appropriately.
//There are some situations where its neccessary to look up the inv string
current_store = temp_lot->assoc_store;
//Before we start, lets get the box in the room out of the way, we'll put a
//call in here in case someone wants to use a funky message.
exp_cases = filter( cases, (: $2 == $(temp_lot)->case_code :) );
map( exp_cases, (: TO->remove_lot( $1 ) :) );
//First lets check if its being automatically handled, if it is
//we can let the code know, it should look after the result instantly
//so we can close it. We have to return the objects at the same time.
//This could be tricky.
if ( sizeof( temp_lot->auto_bids ) ) {
//Recover objects first.
obs = recover_lot( temp_lot->lot_autoload );
//Make the call back
call_other( temp_lot->auto_bids[1], temp_lot->auto_bids[0],
temp_lot->case_code, stringp( temp_lot->current_bid[ AUCTION_PLAYER_NAME ] ),
temp_lot->current_bid[ AUCTION_PLAYER_NAME ],
temp_lot->current_bid[ AUCTION_PLAYER_BID ], obs );
//Ok, assuming that worked, the objects are recovered and the other
//object has done what it was supposed to do with them, this means
//we can close this lot.
temp_lot->status = CLOSED;
temp_lot->expiration_time = time() + ( 2 * 60 * 60 );
//Yes, we aren't processing here anymore. The other object is
//responsible for letting the players know.
continue;
}
if ( stringp( name = temp_lot->current_bid[ AUCTION_PLAYER_NAME ] ) ) {
//Someone got it!
//Lets find out what they've won.
if ( "/secure/login"->test_user( name ) ) {
AUTO_MAILER->auto_mail( name, shop_name, "Your successful purchase!",
"","Congratulations! You have successfully purchased "+
current_store->inv_string +" with a bid of "
+ MONEY_HAND->money_value_string( temp_lot->current_bid[ AUCTION_PLAYER_BID ],
currency )+ ".\n\nYou have one week to collect your items before they are "
"forfeit.\n", 0, 0 );
}
temp_lot->status = WAIT_BUYER;
temp_lot->expiration_time = time() + ( 7 * 24 * 60 * 60 );
continue;
}
//We want to drop down through WAIT_BUYER_LAPSE if there was no buyer initially.
case WAIT_BUYER_LAPSE:
//No one got it. Mail seller.
name = temp_lot->seller;
if ( "/secure/login"->test_user( name ) ) {
AUTO_MAILER->auto_mail( name, shop_name, "Your unsuccessful sale!",
"", "Dear "+ capitalize( name ) +",\n\nIt is with some regret that I inform you "
"that we were unable to sell " + current_store->inv_string +
".\n\nYou have one week to collect your items before they are forfeit.\n",
0, 0 );
}
temp_lot->status = WAIT_SELLER;
temp_lot->expiration_time = time() + ( 7 * 24 * 60 * 60 );
continue;
case WAIT_SELLER: //Free object
name = temp_lot->seller;
if ( LOGIN_OBJ->test_user( name ) ) {
AUTO_MAILER->auto_mail( name, shop_name, "Your unsuccessful sale!",
"", "Dear "+ name +",\n\nAs you have not collected your items within "
"7 days, they have been disposed of.\n\nHave a nice day.\n",
0, 0 );
}
temp_lot->status = CLOSED;
temp_lot->expiration_time = time() + ( 2 * 60 * 60 );
continue;
case WAIT_BUYER: //Drop to next bidder
name = temp_lot->current_bid[ AUCTION_PLAYER_NAME ];
if ( LOGIN_OBJ->test_user( name ) ) {
AUTO_MAILER->auto_mail( name, shop_name, "Your successful purchase!",
"","Dear " + name +",\nBecause you have not picked up your items, "+
"they have been sold to another client.\n\nHave a nice day.", 0, 0 );
}
map_delete( temp_lot->bids, name );
if ( sizeof( temp_lot->bids ) == 0 ) {
//there are no other bids, lets go to WAIT_BUYER_LAPSE
temp_lot->status = WAIT_BUYER_LAPSE;
temp_lot->expiration_time = time() + ( 2 * 60 * 60 ); //2 hrs
continue;
}
//Sort all the remaining bids, pick the best one, mail them and reset the
//expiration time.
bids = sort_array( values( temp_lot->bids ), -1 );
TCTP( "bids is %O.\n", bids );
name = filter( temp_lot->bids, (: $2 == $(bids[0]) :) );
//Now the name of the next best bidder should be kept in name,
//and their bid is in bids[0]
temp_lot->current_bid[ AUCTION_PLAYER_NAME ] = name;
temp_lot->current_bid[ AUCTION_PLAYER_BID ] = bids[0];
if ( LOGIN_OBJ->test_user( name ) ) {
AUTO_MAILER->auto_mail( name, shop_name, "Your successful purchase!",
"","Congratulations! You have successfully purchased "+
current_store->inv_string +" with a bid of "
+ MONEY_HAND->money_value_string( temp_lot->current_bid[ AUCTION_PLAYER_BID ],
currency )+ ".\n\nYou have one week to collect your items before they are "
"forfeit.\n", 0, 0 );
}
temp_lot->status = WAIT_BUYER;
temp_lot->expiration_time = time() + ( 7 * 24 * 60 * 60 );
continue;
case WAIT_CRE_INTERVENTION: //Lots waiting cre intervention
log_file("REAS", "%s: Lot %d is still waiting for manual intervention!\n",
ctime( time() ), temp_lot->case_code );
temp_lot->expiration_time = time() + ( 7 * 24 * 60 * 60 );
continue;
case CLOSED: //Discard lot class also discard store class
TCTP( "In closed %s", "" );
lots -= ({ temp_lot }); //Remove the lot array.
log_file( "REAS", "%s: Closed lot %d\n", ctime( time() ), temp_lot->case_code );
continue;
}
}
//Finally finished processing.. hopefully we haven't over evalled yet.
call_out( "lot_check", CHECK_CYCLE );
return ;
}
int do_deposit( object *things, int auction_time, string time_type, string res_price ) {
mixed *auto_load_string;
c_lot temp;
c_store temp2;
int value;
int finishtime;
if ( auction_time <= 0 || ( time_type == "day" && auction_time > 10 )
|| ( time_type == "minute" && auction_time > 59 ) ||
( time_type == "hour" && auction_time > 23 ) ) {
TP->add_failed_mess( TO, "That's not a valid length.\n" );
return 0;
}
if ( sizeof( res_price ) ) {
value = MONEY_HAND->value_from_string( res_price, currency );
if ( value == 0 ) {
TP->add_failed_mess( TO, res_price +" isn't a valid reserve price.\n" );
return 0;
}
}
if ( !sizeof( COLOURS - used_colours ) ) {
TP->add_failed_mess( TO, "The auction house "
"doesn't have any display cases left to auction your item.\n" );
return 0;
}
switch( time_type ) {
case "minute":
if ( auction_time < 5 ) {
TP->add_failed_mess( TO,
"Auctions must go for at least 5 minutes.\n" );
return 0;
}
finishtime = time() + ( auction_time * 60 );
break;
case "hour":
finishtime = time() + ( auction_time * 60 * 60 );
break;
case "day":
if ( auction_time > 14 ) {
TP->add_failed_mess( TO,
"Auction can not go for longer then two weeks.\n" );
return 0;
}
finishtime = time() + ( auction_time * 60 * 60 * 24 );
break;
default:
return 0;
}
foreach( object tmp in things ) {
if ( base_name( tmp ) == "/obj/money.c" ) {
//Argh! Money! Scream and die.
TP->add_failed_mess( TP, "You can't auction money.\n" );
things->move( TP, "" );
filter( things, (: ENV( $1 ) != TP :) )->
move( ENV( TP ), "$N falls to the floor!\n" );
return 0;
}
}
//Work out the new case ID;
lot_count ++;
//Generate the case info
temp2 = generate_store( things, lot_count );
//Store and destroy objects
auto_load_string = generate_auto_load( things );
if ( !arrayp( auto_load_string ) ) {
//Something went wrong! Scream and die.
TP->add_failed_mess( TP, "You can't auction that.\n" );
things->move( TP, "" );
filter( things, (: ENV( $1 ) != TP :) )->
move( ENV( TP ), "$N falls to the floor!\n" );
return 0;
}
temp = new( c_lot, assoc_store: temp2, case_code: lot_count, seller: TP->query_name(),
status: OPEN, expiration_time: finishtime, reserve_price: value,
bids: ([ ]), current_bid: allocate( 2 ), lot_autoload: auto_load_string,
auto_bids: ({ }), notes: "", excluded: ({ }) );
lots += ({ temp });
if ( strlen( res_price ) )
TP->add_succeeded_mess( TO, "$N $V "+
temp2->inv_string + " for "+ add_a( query_num( auction_time, 5000 ) ) +
" " + time_type + " auction, with a reserve price of "+
MONEY_HAND->money_value_string( value, currency ) +".\n" );
else
TP->add_succeeded_mess( TO, "$N $V "+
temp2->inv_string +" for a "+ query_num( auction_time, 5000 ) +
" " + time_type + " auction, with no reserve price.\n" );
TCTP( "Generated lot %d.\n", lot_count );
return 1;
}
int do_list() {
c_lot *_lots;
c_lot _lot;
if ( !sizeof( cases ) ) {
write( "There is nothing up for auction in this store.\n" );
return 1;
}
write( "Items currently for auction:\n" );
foreach( object _case in keys( cases ) ) {
_lots = filter( lots, (: $1->case_code == $( cases[ _case ] ) :) );
if ( !sizeof( _lots ) ) continue;
_lot = _lots[0];
tell_object( TP, " " +
capitalize( _lot->assoc_store->colour + " case" ) + ": " +
GECM( _lot->assoc_store->inv_string ) + ".\n" );
}
return 1;
}
/**
* @ignore
* This function is meant to be masked, but remember to call it
* otherwise your cases will never disappear.
*/
void remove_lot( object ob ) {
if ( member_array( ob, keys( cases ) ) == -1 )
return;
map_delete( cases, ob );
ob->dest_me();
return;
}
/**
* Basic add command function.. lets you bid on stuff
*/
int do_bid( string offer, object *boxes ) {
//Step one, work out what they are bidding on.
c_lot temp;
c_lot *temps;
c_store temp2;
object box;
int value;
if ( sizeof( boxes ) > 1 ) {
TP->add_failed_mess( TO, "You can only $V on one case at a time.\n" );
return 0;
}
box = boxes[0];
if ( member_array( box, keys( cases ) ) == -1 ) {
TP->add_failed_mess( TO, "$I isn't being auctioned here.\n", ({ box }) );
return 0;
}
temps = filter_array( lots, (: $1->case_code == cases[ $(box) ] :) );
//We will need the bid info now.. the store info isn't needed if the lot
//has been closed.. so lets return 1; if thats the case..
temp = temps[0];
temp2 = temp->assoc_store;
if ( temp->status != OPEN ) {
TP->add_failed_mess(TO, "The bidding on this item is finished.\n" );
return 0;
}
if ( member_array( TP->query_name(),
temp->excluded ) != -1 ) {
write( "You have been excluded from bidding on this object.\n" );
return WARNEXCL;
}
if ( !classp( temp2 ) ) {
log_file( "REAS", "%s: Open lot without store! Lot code: %d.\n",
ctime( time() ), temp->case_code );
write( "Something has gone wrong. Please fetch a liaison post haste!\n" );
printf( "Your lot code is: %d.\n", temp->case_code );
temp->status = WAIT_CRE_INTERVENTION;
temp->expiration_time = time() + ( 7 * 24 * 60 * 60 );
TP->add_succeeded_mess( TO, "" );
return 1;
}
value = MONEY_HAND->value_from_string( offer, currency );
if ( value == 0 ) {
TP->add_failed_mess( TO, offer +" isn't worth anything here.\n" );
return 0;
}
if ( TP->query_value_in( currency ) < value ) {
TP->add_failed_mess( TO, "You don't have that much.\n" );
return 0;
}
if ( temp->reserve_price != 0 && value < temp->reserve_price ) {
TP->add_failed_mess( TO, "The reserve price for this lot is "+
MONEY_HAND->money_value_string( temp->reserve_price, currency ) +".\n" );
return 0;
}
if ( value < 400 ) {
TP->add_failed_mess( TO, "You must bid at least "+
MONEY_HAND->money_value_string( 400, currency ) + ".\n" );
return 0;
}
if ( sizeof( temp->current_bid ) &&
temp->current_bid[ AUCTION_PLAYER_BID ] >= value ) {
TP->add_failed_mess(TO, "Someone else has already bid more than that.\n");
return 0;
}
if ( sizeof( temp->current_bid ) &&
( ( value - temp->current_bid[ AUCTION_PLAYER_BID ] ) <
( temp->current_bid[ AUCTION_PLAYER_BID ] / 20 ) ) ) {
TP->add_failed_mess( TO, "You must bid 5% "
"more then the current bid.\n" );
return 0;
}
temp->bids[ TP->query_name() ] = value;
temp->current_bid[0] = TP->query_name();
temp->current_bid[1] = value;
TP->add_succeeded_mess(TO, "$N $V "+ offer +" for "+ temp2->inv_string +".\n" );
return 1;
}
/**
* This function will be a bit horrible, but it is a nicer way of doing it
* then keeping the objects around.
* This function uses Terano's cheap and dirty parser (All rights reserved).
* To be added - plural support! (using query_plural)
*/
int do_browse( object *boxes, string target, int identifier ) {
object box;
c_store container;
c_item temp;
mapping contents = ([ ]); //A list of all things in the box
string *names = ({ });
mixed *longadj = ({ });
string temp2;
mixed *contenders;
string *contender;
string *adjectives;
string name;
string word;
int keep_flag;
if ( sizeof( boxes ) > 1 ) {
TP->add_failed_mess( TO, "You can only $V one case at a time.\n" );
return 0;
}
box = boxes[0];
if ( member_array( box, keys( cases ) ) == -1 ) {
TP->add_failed_mess( TO, "$I isn't being auctioned here.\n", ({ box }) );
return 0;
}
container = filter( lots, (: $1->case_code == cases[ $(box) ] :) )[ 0 ]->assoc_store;
//TCTP( "Container: %O", container );
TCTP( "The case code for this case is: %d.\n", container->case_code );
//Go through the inventory of the array
foreach( temp in container->inventory ) {
//Compile a list of what it wants to be called.
names = temp->alias + ({ temp->name });
//There are some objects with aliases the same as the name
//ie: bottle and bottle :P
names = uniq_array( names );
//Work out what it looks like.
longadj = ({ temp->long + temp->read_mess +
( TP->query_see_octarine() ? temp->enchant_mess : "" ),
temp->adjectives });
//Ok, we have a nice list of all the names of this item
foreach( temp2 in names ) {
//TCTP( "Adding: %O\n", temp2 );
if ( arrayp( contents[ temp2 ] ) )
contents[ temp2 ] += ({ longadj });
else
contents[ temp2 ] = ({ longadj });
}
}
//TCTP( "Object list: %O", contents );
//Seeing if the string is one word, "bing".. or two words.. "blue bing".
adjectives = explode( target, " " );
//The name will be the bit at the end. The rest will be describing words.
name = adjectives[ sizeof( adjectives ) - 1 ];
adjectives -= ({ name });
TCTP( "Search Name: %s ", name );
TCTP( "Search Adj: %O.\n", adjectives );
if ( !arrayp( contents[ name ] ) ) {
TP->add_failed_mess( TO,
"There is nothing like that in the case.\n" );
return 0;
}
//Our matches! Let's strip down a level of complexity here to
//avoid evilness.
contenders = contents[ name ];
//TCTP( "Matches: %O\n", contenders );
if ( sizeof( contenders ) == 1 ) {
//That was easy. Theres only one of them about.
contender = contenders[0];
TP->add_succeeded_mess( TP, "$N $V "+ target +" in $I.\n", ({ box }) );
call_out( (: tell_object( TP, $(GECM( contender[0]) ) ) :) );
return 1;
}
//Ok, we've got more then one. If they gave us a number, we can use that.
//Otherwise its fuzzy matching time. If they didn't give us any
//adjectives to try and parse with, scream and die.
if ( identifier != 0 ) {
if ( identifier > sizeof( contenders ) ) {
TP->add_failed_mess( TP,
"There are only " + sizeof( contenders ) +
" " + pluralize( target ) + " in $I.\n", ({ box }) );
return 0;
}
TP->add_succeeded_mess( TP,
"$N $V "+ add_a( target ) +" in $I.\n", ({ box }) );
call_out( (: tell_object( TP,
GECM( $(contenders[ identifier - 1 ][0]) ) ) :), 0 );
return 1;
}
if ( !sizeof( adjectives ) ) {
TP->add_failed_mess( TO,
"There are "+ sizeof( contenders ) +" objects like that in the case.\n" );
return 0;
}
//Ok, this bit is really going to suck.
//Lets go through our list of contenders, and see how many we can find
//with these descriptive words as adjectives.
foreach ( contender in contenders ) {
keep_flag = 0;
foreach( word in contender[ 1 ] ) {
if ( member_array( word, adjectives ) != -1 )
//Mark it as one to keep.
keep_flag = 0;
continue;
}
//If keep_flag isn't set, dump it
if ( !keep_flag )
contenders -= ({ contender });
continue;
}
//TCTP( "Contenders 2: %O\n", contenders );
if ( !sizeof( contenders ) ) {
//Argh, after all that, we disqualified everything! Bloody players.
TP->add_failed_mess( TO,
"There is nothing matching "+ target +" in the case.\n" );
return 0;
}
if ( sizeof( contenders ) > 1 ) {
//Ok, there are still too many, scream and die.
TP->add_failed_mess( TO,
"There are " + sizeof( contenders ) + " items like that in the case. "
"Please specify which one you want to browse.\n" );
return 0;
}
//Woohoo! We found it!
contender = contenders[0];
TP->add_succeeded_mess( TP,
"$N peruse "+ add_a( target ) +" in $I.\n", ({ box }) );
call_out( (: tell_object( TP, GECM( $(contender[0]) ) ) :), 0 );
return 1;
}
void adjust_money( int amount, object player ) {
object money;
if ( amount < 0 ) { //Taking money
player->pay_money(MONEY_HAND->create_money_array(-amount, currency ));
return;
}
money = MONEY_HAND->create_money_array( amount, currency );
player->adjust_money( money, currency );
}
void load_file() {
if ( !stringp( location ) ) return;
if ( file_size( location +".o" ) < 0 ) return;
unguarded( (: restore_object, location :) );
return;
}
void save_file() {
if ( location == "unset" ) return;
unguarded( (: save_object, location :) );
return;
}
void set_shop_name( string _name ) { shop_name = _name; }
void set_save_path( string path ) { location = path; }
void set_currency( string cur ) { currency = cur; }
void set_admin_cost( int value ) { admin_cost = value; }
void set_commission( int percent ) { commission = percent/100.0; }
mapping query_payments() { return payments; }
void dest_me() {
save_file();
if ( sizeof( cases ) ) keys( cases )->dest_me();
::dest_me();
}
mixed recover_lot( mixed lot_autoload ) {
object *boxes;
object *stuff;
boxes = load_auto_load_to_array( lot_autoload, TP );
if ( !objectp( boxes[0] ) ) {
log_file( "REAS", "%s: Could not recover: %O.\n", ctime( time() ),
lot_autoload );
return ERRNORECV;
}
stuff = INV( boxes[ 0 ] ); //All that should be in this array is a box
if ( !sizeof( stuff ) ) {
log_file( "REAS", "%s: Recovered case as empty: %O.\n", ctime( time() ),
lot_autoload );
return ERRRECVEMP;
}
//5 seconds to deal with inventory of boxes. Lets be generous.
call_out( (: $(boxes)->dest_me() :), 5 );
return stuff;
}
/**
* Withdraw an item from bidding
*/
int do_withdraw( object *boxes ) {
object box;
c_lot *_lots;
object *obs;
if ( sizeof( boxes ) > 1 ) {
TP->add_failed_mess( TO, "You can only $V on one case at a time.\n" );
return 0;
}
box = boxes[0];
if ( member_array( box, keys( cases ) ) == -1 ) {
TP->add_failed_mess( TO, "$I isn't being auctioned here.\n", boxes );
return 0;
}
_lots = filter( lots, (: $1->case_code == cases[ $(box) ] :) );
if ( TP->query_name() != _lots[0]->seller && !creatorp(TP) ) {
TP->add_failed_mess( TP, "This isn't your "
"lot to withdraw!\n" );
return 0;
}
//Close and wrap up in 2 hours
_lots[0]->status = CLOSED;
_lots[0]->expiration_time = time() + ( 2 * 60 * 60 );
obs = recover_lot( _lots[0]->lot_autoload );
if ( !arrayp( obs ) ) {
write( "Something has gone wrong. Please fetch a liaison post haste!\n" );
printf( "Your lot code is: %d.\n", _lots[0]->case_code );
_lots[0]->status = WAIT_CRE_INTERVENTION;
_lots[0]->expiration_time = time() + ( 7 * 24 * 60 * 60 );
TP->add_succeeded_mess( TO, "" );
return 1;
}
obs->move( TP, "You collect $N." );
filter( obs, (: ENV( $1 ) != TP :) )->
move( ENV( TP ), "$N falls to the floor!\n" );
//Avoid problems with listing later on.
TO->remove_lot( box );
TP->add_succeeded_mess( TO, "" );
return 1;
}
int do_describe( object *boxes, string int_desc ) {
object box;
c_lot *_lots;
c_lot _lot;
int code;
if ( sizeof( boxes ) > 1 ) {
TP->add_failed_mess( TO, "You can only $V on one case at a time.\n" );
return 0;
}
box = boxes[0];
if ( member_array( box, keys( cases ) ) == -1 ) {
TP->add_failed_mess( TO, "$I isn't being auctioned here.\n",
({ box }) );
return 0;
}
code = cases[ box ];
_lots = filter( lots, (: $1->case_code == $(code) :) );
if ( sizeof( _lots ) != 1 ) {
printf( "Please inform a creator, there are %d records to "
"match this case.\n", sizeof( _lots ) );
printf( "Your lot code is: %d.\n", code );
TP->add_succeeded_mess( TO, "" );
return 1;
}
_lot = _lots[0];
if ( TP->query_name() != _lot->seller && !creatorp(TP) ) {
TP->add_failed_mess( TP, "This isn't your "
"lot to describe!\n" );
return 0;
}
_lot->notes = int_desc;
printf( "You neatly letter %s on the case.\n", int_desc );
TP->add_succeeded_mess( TO, "" );
return 1;
}
string extra_look( object ob ) {
c_lot temp, *temp2;
mixed bid_info;
string ret;
int code;
if ( member_array( ob, keys( cases ) ) == -1 ) return "";
code = cases[ ob ];
temp2 = filter( lots, (: $1->case_code == $(code) :) );
if ( sizeof( temp2 ) != 1 ) return sizeof( temp2 ) +" found in lot array!\n";
temp = temp2[0];
bid_info = temp->current_bid;
ret = "";
if ( !stringp( bid_info[ AUCTION_PLAYER_NAME ] ) ) {
if ( temp->reserve_price ) {
ret += "Reserve price is: ";
ret += MONEY_HAND->money_value_string( temp->reserve_price, currency );
ret += ".\n";
}
else
ret += "No bid as of yet.\n";
}
else
ret += "The current bid is "+ MONEY_HAND->money_value_string(
bid_info[1], currency ) + ", made by "+ capitalize(
bid_info[ AUCTION_PLAYER_NAME ] ) +".\n";
ret += "The bidding on this lot stops at "+ mudtime( temp->expiration_time )+
".\n";
if ( sizeof( temp->notes ) ) {
ret += "Neatly lettered on the case is: ";
ret += temp->notes;
ret += ".\n";
}
return ret;
}
int do_collect( string pattern ) {
int amount;
c_lot _lot, *_lots = ({ }), *_exp_lots;
object *items = ({ });
//All lots that aren't OPEN, CLOSED or WAITing for a cre.
_exp_lots = filter( lots, (: ((c_lot)$1)->status != OPEN :) );
_exp_lots = filter( _exp_lots, (: ((c_lot)$1)->status != CLOSED :) );
_exp_lots = filter( _exp_lots, (: ((c_lot)$1)->status != WAIT_CRE_INTERVENTION :) );
if ( pattern == "all" ) {
do_collect( "money" );
do_collect( "successful bids" );
do_collect( "expired lots" );
return 1;
}
if ( pattern == "money" ) {
_lots = filter( lots, (: ( $1->seller == $2 ) &&
( stringp( $1->current_bid[ AUCTION_PLAYER_NAME ] ) ) :),
TP->query_name() );
if ( undefinedp( payments[ TP->query_name() ] ) ) {
if ( sizeof( _lots ) ) {
foreach( c_lot tempy in _lots ) {
write( capitalize( tempy->current_bid[ AUCTION_PLAYER_NAME ] ) +
" must pay for the items they bid on before you can collect "
"the money for them! They have until "+ mudtime( tempy->expiration_time )+".\n" );
}
}
}
if ( undefinedp( payments[ TP->query_name() ] ) ) {
write( "You aren't owed any money!\n" );
return 0;
}
amount = payments[ TP->query_name() ];
adjust_money( amount - to_int( amount * commission ), TP );
printf( "You recieve %s, minus %s commission.\n",
MONEY_HAND->money_value_string( amount, currency ),
MONEY_HAND->money_value_string( to_int( amount * commission ),
currency ) );
map_delete( payments, TP->query_name() );
TP->add_succeeded_mess( TO, "$N $V some money from $D.\n" );
save_file();
return 1;
}
if ( pattern == "successful bids" ) {
//Items that have expired with the player as the current bid,
_lots = filter( _exp_lots,
(: $1->current_bid[ AUCTION_PLAYER_NAME ] == $2 :),
TP->query_name() );
if ( !sizeof( _lots ) ) {
write( "You aren't expecting any bids!\n" );
return 0;
}
foreach( _lot in _lots )
amount += _lot->current_bid[ 1 ];
if ( TP->query_value_in( currency ) < amount ) {
printf( "You have %d %s waiting, for a total cost of %s.\n"
"You don't have enough money.\n",
sizeof( _lots ),
sizeof( _lots ) > 1 ? "lots" : "lot",
MONEY_HAND->money_value_string( amount, currency ) );
TP->add_failed_mess( TO, "" );
return 0;
}
adjust_money( -amount, TP );
foreach( _lot in _lots ) {
items += recover_lot( _lot->lot_autoload );
if ( undefinedp( payments[ _lot->seller ] ) )
payments[ _lot->seller ] = _lot->current_bid[ AUCTION_PLAYER_BID ];
else
payments[ _lot->seller ] += _lot->current_bid[ AUCTION_PLAYER_BID ];
_lot->status = CLOSED;
_lot->expiration_time = time() + ( 2 * 60 * 60 ); //2 hrs
}
items->move( TP, "You collect $N." );
filter( items, (: ENV( $1 ) != TP :) )->
move( ENV( TP ), "$N falls to the floor!\n" );
printf( "You had %d %s waiting, for a total cost of %s.\nYou "
"hand over the money.\n", sizeof( _lots ),
sizeof( _lots ) > 1 ? "lots" : "lot",
MONEY_HAND->money_value_string( amount, currency ) );
return 1;
}
if ( pattern == "expired lots" ) {
//Items that didnt sell
_lots = filter( _exp_lots,
(: !stringp( $1->current_bid[ AUCTION_PLAYER_NAME ] ) &&
$1->seller == $2 :), TP->query_name() );
if ( !sizeof( _lots ) ) {
TP->add_failed_mess( TO, "You aren't expecting "
"any items.\n" );
return 0;
}
amount = admin_cost * sizeof( _lots );
if ( TP->query_value_in( currency ) < amount ) {
printf( "You have %d %s waiting, for a total administration "
"cost of %s.\n"
"You don't have enough money.\n",
sizeof( _lots ),
sizeof( _lots ) > 1 ? "lots" : "lot",
MONEY_HAND->money_value_string( amount, currency ) );
return 0;
}
adjust_money( -amount, TP );
foreach( _lot in _lots ) {
items += recover_lot( _lot->lot_autoload );
if ( undefinedp( payments[ _lot->seller ] ) )
payments[ _lot->seller ] = _lot->current_bid[ AUCTION_PLAYER_BID ];
else
payments[ _lot->seller ] += _lot->current_bid[ AUCTION_PLAYER_BID ];
_lot->status = CLOSED;
_lot->expiration_time = time() + ( 2 * 60 * 60 ); //2 hrs
}
items->move( TP, "You collect $N." );
filter( items, (: ENV( $1 ) != TP :) )->
move( ENV( TP ), "$N falls to the floor!\n" );
printf( "You had %d %s waiting, for a total administration "
"cost of %s.\n"
"You hand over the money.\n",
sizeof( _lots ), sizeof( _lots ) > 1 ?
"lots" : "lot",
MONEY_HAND->money_value_string( amount, currency ) );
return 1;
}
}
int do_exclude( object *boxes, string excluded ) {
object box;
c_lot *_lots;
c_lot _lot;
int code;
string *ignored = TP->query_property( "ignoring" );
if ( sizeof( boxes ) > 1 ) {
TP->add_failed_mess( TO, "You can only $V people from one case at "
"a time.\n" );
return 0;
}
box = boxes[0];
if ( member_array( box, keys( cases ) ) == -1 ) {
TP->add_failed_mess( TO, "$I isn't being auctioned here.\n",
({ box }) );
return 0;
}
code = cases[ box ];
_lots = filter( lots, (: $1->case_code == $(code) :) );
if ( sizeof( _lots ) != 1 ) {
printf( "Please inform a creator, there are %d records to "
"match this case.\n", sizeof( _lots ) );
printf( "Your lot code is: %d.\n", code );
TP->add_succeeded_mess( TO, "" );
return 1;
}
_lot = _lots[0];
if ( TP->query_name() != _lot->seller && !creatorp(TP) ) {
TP->add_failed_mess( TP, "This isn't your "
"lot to exclude people from!\n" );
return 0;
}
if ( excluded == "list" ) {
if ( !sizeof( _lot->excluded ) )
write( "No one is being excluded from bidding on this lot.\n" );
else
write( query_multiple_short( _lot->excluded ) + " is being excluded from "
"bidding on this lot.\n" );
return 1;
}
if ( excluded != "ignore list" ) {
if ( member_array( excluded, _lot->excluded ) != -1 ) {
write( excluded + " is already excluded from this lot.\n" );
return 1;
}
if ( !LOGIN_OBJ->test_user( excluded ) )
return notify_fail( excluded +" is not a player here!\n" );
else {
_lot->excluded += ({ excluded });
_lot->excluded = uniq_array( _lot->excluded );
write( excluded +" will not be allowed to bid on "+
query_multiple_short( boxes ) +".\n" );
return 1;
}
}
if ( !arrayp( ignored ) || !sizeof( ignored ) ) {
write( "You aren't ignoring anyone!\n" );
return 1;
}
_lot->excluded += ignored;
_lot->excluded = uniq_array( _lot->excluded );
write( query_multiple_short( ignored ) + " will not be allowed to bid on "+
query_multiple_short( boxes ) +".\n" );
TP->add_succeeded_mess( TO, "" );
return 1;
}
int do_unexclude( object *boxes, string excluded ) {
object box;
c_lot *_lots;
c_lot _lot;
int code;
if ( sizeof( boxes ) > 1 ) {
TP->add_failed_mess( TO, "You can only $V people from one case at "
"a time.\n" );
return 0;
}
box = boxes[0];
if ( member_array( box, keys( cases ) ) == -1 ) {
TP->add_failed_mess( TO, "$I isn't being auctioned here.\n",
({ box }) );
return 0;
}
code = cases[ box ];
_lots = filter( lots, (: $1->case_code == $(code) :) );
if ( sizeof( _lots ) != 1 ) {
printf( "Please inform a creator, there are %d records to "
"match this case.\n", sizeof( _lots ) );
printf( "Your lot code is: %d.\n", code );
TP->add_succeeded_mess( TO, "" );
return 1;
}
_lot = _lots[0];
if ( TP->query_name() != _lot->seller && !creatorp(TP) ) {
TP->add_failed_mess( TP, "This isn't your "
"lot to unexclude people from!\n" );
return 0;
}
if ( member_array( excluded, _lot->excluded ) == -1 ) {
if ( !sizeof( _lot->excluded ) ) {
write( "No one is excluded from bidding on this case!\n" );
return 1;
}
write( "Only "+ query_multiple_short( _lot->excluded ) +
( sizeof( excluded ) == 1 ? " is " : " are " ) +
" being excluded from bidding on this case!\n" );
return 1;
}
_lot->excluded -= ({ excluded });
write( capitalize( excluded ) + " is now allowed to bid on this case.\n" );
TP->add_succeeded_mess( TO, "" );
return 1;
}
/**
* @ignore
*/
c_lot find_spec_lot( int pos ) { return lots[ pos ]; }
/**
* This function generates a string that can be used in any advertising
* you might want to do, it returns a human friendly string that passes
* information about the lot. (Specifically - contents, cost and exp time)
* It takes a lot as an arg.
*/
string generate_advertising_string( c_lot lot ) {
//Return a string that we can do something with!
//Something like: "<lot QMS>, currently going for <currency string>, "
//"but hurry, bidding stops on <time>!"
string c_string;
if ( intp( lot->current_bid[ 0 ] ) && ( lot->current_bid[ 0 ] ) )
c_string = MONEY_HAND->money_value_string( lot->current_bid[ 0 ], currency );
else
if ( intp( lot->reserve_price ) && lot->reserve_price )
c_string = MONEY_HAND->money_value_string( lot->reserve_price, currency );
else
c_string = "make an offer";
if ( c_string == "make an offer" )
return lot->assoc_store->inv_string + ", " + c_string +
" but hurry, bidding stops at " + mudtime( lot->expiration_time );
return lot->assoc_store->inv_string + ", currently going for " + c_string +
" but hurry, bidding stops at " + mudtime( lot->expiration_time );
}
/**
* A more usable version of above function, call it and it picks a lot
* at random and generates a string for it.
* returns "NULL" if there are no lots.
* Also returns NULL if the lot chosen isnt open
*/
string generate_random_adv_string() {
c_lot temp;
if ( sizeof( lots ) ) {
temp = lots[ random( sizeof( lots ) ) ];
if ( temp->status != OPEN )
return WARNNOLOTS;
else return generate_advertising_string( temp );
}
else
return WARNNOLOTS;
}