/** * Library inherit. Allows players to take out books on loan. If * set to be player contributable, then books (most likely player * written, but any kind) can be added/removed by designated * librarian players. * * @author Aquilo */ // ToDo // Functions to be called when a damaged/lost book is reported for a player // Prevent returning of books w/ pages ripped out. // Gving status of a book, who has it on loan, ue back etc... #include <book_handler.h> #include <money.h> #include <language.h> #include <player.h> #include <player_handler.h> #include <am_time.h> #define BASE_PATHS ({ "/std/book", "/std/book_dir", "/std/leaflet", \ "/obj/misc/paper", "/obj/misc/nroff_paper" }) #define TITLE_PROP "title" #define AUTHOR_PROP "author" #define FROM_LIBRARY_PROP "from library" #define BORROWED_BY_PROP "borrowed by" #define DUE_BACK_PROP "due back" #define REFERENCE_ONLY_PROP "reference only" #define USER_ACCESS 1 #define LIBRARIAN_ACCESS 2 inherit "/std/room/basic_room"; nosave int _player_contributable; nosave string _library_name; nosave string _save_file; nosave function _borrow_func; nosave function _test_allowed; int _loan_length; int _fine_per_day; int _lost_damaged_fine; int _max_loans; mapping _catalog; // ([ librarybook id : class _book ]) mapping _accounts; // ([ playername : class _account ]) mapping _fines; // ([ playername : int fine ]) mapping _access; // ([ playername : int access ]) nosave mapping _catalog_by_title; // ([ "title" : ({ library id }) ]) nosave mapping _catalog_by_author; // ([ "author" : ({ library id }) ]) /** * This is all the info on one of our stored books. * @element path the path to the item, if applicable * @element auto_load if no path, we store the object whole in this string * @element cap_title the capitalized title of the book * @element cap_author the capitalized author of the book * @element borrowable 1 if this book can be borrowed, 0 if not * @element copies Total number of copies owned by library * @element loaned An array of due back dates for loaned out copies * @element total_borrowed total no of times this book has been borrowed */ class _book { string path; string auto_load; string cap_title; string cap_author; mapping loaned; int borrowable; int copies; int total_borrowed; } /** * This holds info on a book a player has borrowed * @element id the librarybook id num * @element due_back the rl time due back, in secs from 1970 */ class _loan { int id; int due_back; } /** * Holds info on a player. * @element total_fines_paid how much this blokie has paid in fines in total * @element damaged_books total number of books he has damaged * @element lost_books total number of books he has lost * @element loans info on what books he currently has out */ class _account { int total_fines_paid; int lost_damaged; class _loan *loans; } int do_add( object *obs, string title, string author ); int do_remove_book( int id ); int do_borrow( mixed arg ); int do_return( object *obs ); int do_list( string arg, string to, string from, string specific ); int do_status( string player ); int do_book_status( int id ); int do_set( string player, string type ); int do_mark( int id, int flag ); int do_set_fine( int i ); int do_set_loan_length( int days ); int do_set_max_loans( int i ); int do_lost_damaged_fine( int i ); int do_report( int id, string arg, string player ); mapping query_loans( string name ); mapping query_borrowed_by( int id ); int query_fine( string name ); void clear_loan( mixed player, int id, int calc_fines ); varargs int add_book_to_library( mixed thing, int copies, string cap_title, string cap_author, int borrowable ); void set_access( string player, int access ); int query_access( mixed player ); int do_set( string player, string type ); void save_me(); protected string make_pretty_catalog( string arg, string from, string to, string specific ); protected int compare_widgets( object new_ob, int existing_id ); /** @ignore yes */ protected void build_catalogs(){ int id; class _book book; foreach( id, book in _catalog ){ if( _catalog_by_author[ lower_case(book->cap_author) ] ) _catalog_by_author[ lower_case(book->cap_author) ] += ({ id }); else _catalog_by_author[ lower_case(book->cap_author) ] = ({ id }); if( _catalog_by_title[ lower_case(book->cap_title) ] ) _catalog_by_title[ lower_case(book->cap_title) ] += ({ id }); else _catalog_by_title[ lower_case(book->cap_title) ] = ({ id }); } } /** @ignore yes */ void create(){ object sign; ::create(); _catalog_by_author = ([ ]); _catalog_by_title = ([ ]); /* Some default settings */ _player_contributable = 1; _library_name = "The illustrious library of fluff"; _save_file = base_name( this_object() ) + ".o"; if( master()->file_exists(_save_file) ){ unguarded( (: restore_object, _save_file :) ); } else { _catalog = ([ ]); _accounts = ([ ]); _fines = ([ ]); _access = ([ ]); _loan_length = 3 * 24 * 60 * 60; _fine_per_day = 400; _lost_damaged_fine = 1050; _max_loans = 5; } build_catalogs(); add_help_file("p_library_user"); add_help_file("p_library_librarian"); sign = add_sign( "The plaque is a traditional brass plate, set on top a piece " "of cherry oak. It looks very formal.\n", "For some reason, reading this sign doesn't give you as much " "information as looking at it would.\n", "brass plaque", "plaque", "common" ); sign->add_extra_look( this_object() ); sign->set_read_mess( 0, 0, 0 ); /* if ( !do_setup ) { this_object()->setup(); } */ } /* void setup(){ set_short("small library"); set_long("A library. Lots of books everywhere.\n"); set_light(70); } */ /** @ignore yes */ int query_library(){ return 1; } /** @ignore yes */ void init(){ if( _player_contributable ){ add_command( "add", "<indirect:object:me'book'> with " "title <string'title'> by author <word'author'>", (: do_add( $1, $4[1], $4[2] ) :) ); add_command( "remove", "<number'book id'>", (: do_remove_book( $4[0] ) :)); } add_command( "borrow", "<number'book id'>", (: do_borrow( $4[0] ) :) ); add_command( "return", "<indirect:object:me-here>" ); add_command( "pay", "[fine]" ); add_command( "view", "catalogue by {author|title}", (: do_list( $4[0], "a", "z", 0 ) :) ); add_command( "view", "catalogue by {author|title} from " "<word'letter'> to <word'letter'>", (: do_list( $4[0], $4[1], $4[2], 0 ) :) ); add_command( "view", "works by <string'author'>", (: do_list( "author", 0, 0, $4[0] ) :) ); add_command( "view", "works titled <string'title'>", (: do_list( "title", 0, 0, $4[0] ) :) ); add_command( "status", "", (: do_status(0) :) ); add_command( "status", "book <number'book id'>", (: do_book_status( $4[0] ) :) ); add_command( "report", "<number'book id'> as {damaged|lost}", (: do_report( $4[0], $4[1], 0 ) :) ); /* Librarian only commands */ add_command( "set", "access for <word'player'> to {allowed|disallowed}", (: do_set( $4[0], $4[1] ) :) ); add_command( "set", "fine per day to <number'amount'>", (: do_set_fine( $4[0] ) :) ); add_command( "set", "loan length to <number'number of days'>", (: do_set_loan_length( $4[0] ) :) ); add_command( "set", "maximum number of loans to <number'number'>", (: do_set_max_loans( $4[0] ) :) ); add_command( "set", "lost or damaged fine to <number'amount'>", (: do_lost_damaged_fine( $4[0] ) :) ); add_command( "mark", "<number'book id'> as reference only", (: do_mark( $4[0], 1) :) ); add_command( "mark", "<number'book id'> as not reference only", (: do_mark( $4[0], 0) :) ); add_command( "status", "<word'player'>", (: do_status($4[0]) :) ); add_command( "report", "<number'book id'> as {damaged|lost} for <word'player'>", (: do_report( $4[0], $4[1], $4[2] ) :) ); } /** @ignore yes */ protected string id_to_name( int id ){ if( !_catalog[id] ) return 0; return "'" +_catalog[id]->cap_title+ "' by " + _catalog[id]->cap_author; } /** @ignore yes */ protected int valid_media( object ob ){ if( ob->query_book() || ob->query_paper() || ob->query_leaflet() ) return 1; } /** @ignore yes */ int do_add( object *obs, string title, string author ){ object t_p, t_o; int num, id, *ids; t_p = this_player(); t_o = this_object(); debug_printf( "Obs: %O\n", obs ); if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("Only librians can do this.\n"); return 0; } if( strlen(author) > 14 ){ add_failed_mess("The author's name is too long. It has to be less than 13 characters.\n"); return 0; } if( strlen(title) > 26 ){ add_failed_mess("The title is too long. It can only use less than 27 characters.\n"); return 0; } if( sizeof(obs) > 1 ){ add_failed_mess("You can only add one item at a time.\n"); return 0; } if( !t_o->valid_media(obs[0]) ){ add_failed_mess( "The library does not lend items like $I.\n", obs); return 0; } if( obs[0]->query_magic_scroll() || obs[0]->query_spell_book() ){ add_failed_mess( "This library does not deal in magical works.\n" ); return 0; } if( obs[0]->query_property(FROM_LIBRARY_PROP) ){ add_failed_mess( "This item belongs to a library and cannot be added.\n" ); return 0; } if( (num = obs[0]->query_book_num()) ){ // then its a player written book if( lower_case(author) != BOOK_HANDLER->query_book_owner(num) ){ add_failed_mess( "The book has been authored by " + capitalize(BOOK_HANDLER->query_book_owner(num)) + " and not " + author + ".\n"); return 0; } } if( (ids = _catalog_by_title[ lower_case(title) ]) ){ foreach( id in ids ){ if( lower_case(_catalog[id]->cap_author) == lower_case(author) ){ // Then we already have a book of the same title by the same author // Make sure that they are infact the same... if( compare_widgets( obs[0], id ) ){ add_book_to_library( id, 1 ); add_succeeded_mess( "$N add$s another copy of " +id_to_name(id)+ " to the library.\n" ); obs[0]->move( "/room/rubbish" ); return 1; } else { add_failed_mess("$I doesn't seem to be the same as the copy held " "in the library. Catalogue it under a different title or author.\n", obs ); return 0; } } } } if( member_array(base_name(obs[0]), BASE_PATHS) == -1 ) add_book_to_library( base_name(obs[0]), 1, title, author, 1 ); else add_book_to_library( obs[0], 1, title, author, 1 ); add_succeeded_mess( "$N $V an item to the library.\n" ); obs[0]->move( "/room/rubbish" ); return 1; } /** @ignore yes */ void remove_book( int id ){ object ob; class _book book; int i; if( !_catalog[id] ) return; book = _catalog[id]; for( i = 0; i < (book->copies - sizeof(book->loaned)); i++ ){ if( book->path ) ob = clone_object( book->path ); if( book->auto_load ) ob = PLAYER_OB->load_auto_load_to_array( book->auto_load, this_player() )[0]; ob->move( this_object() ); } _catalog_by_title[lower_case(book->cap_title)] -= ({ id }); _catalog_by_author[lower_case(book->cap_author)] -= ({ id }); if( !sizeof( _catalog_by_title[lower_case(book->cap_title)] ) ) map_delete( _catalog_by_title, lower_case(book->cap_title) ); if( !sizeof( _catalog_by_author[lower_case(book->cap_author)] ) ) map_delete( _catalog_by_author, lower_case(book->cap_author) ); map_delete( _catalog, id ); save_me(); } /** @ignore yes */ int do_remove_book( int id ){ int out; string player, bit; class _account account; class _loan loan; if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("Only librians can do this.\n"); return 0; } id = to_int(id); if( !_catalog[id] ){ add_failed_mess("There is no item with that id.\n"); return 0; } foreach( player, account in _accounts ){ foreach( loan in account->loans ){ if( loan->id == id ) out++; } } if( out ){ if( out > 1 ) bit = "are " +query_num(out)+ " "; else bit = "is one"+" "; add_failed_mess( "There " +bit+ id_to_name(id) + ", out on loan already. You can only remove a book if all " "copies are in the library.\n" ); return 0; } else { add_succeeded_mess("$N $V " +id_to_name(id) + " from the library.\n" ); remove_book(id); tell_object( this_player(), "Any remaining copies will be placed on the floor.\n"); return 1; } } /** @ignore yes */ int do_borrow( mixed arg ){ int id, ret, t; object ob; class _book book; class _loan loan; string name; name = this_player()->query_name(); id = to_int(arg); if( query_access(this_player()) < USER_ACCESS ){ add_failed_mess("You do not have access to this library.\n"); return 0; } if( _borrow_func ){ ret = evaluate( _borrow_func, this_player(), id ); if( ret != 1 ) return ret; } if( _fines[name] ){ add_failed_mess("You can't borrow a book whilst having outstanding fines.\n" ); return 0; } if( !_catalog[id] ){ add_failed_mess("There is no book with an id of " +id+ ".\n" ); return 0; } else { book = _catalog[id]; } if( _accounts[name] && sizeof(_accounts[name]->loans) >= _max_loans ){ add_failed_mess("You have already borrowed the maximum number of books.\n"); return 0; } if( (book->copies - sizeof(book->loaned)) < 1 ){ add_failed_mess("Unfortunately, all copies of " + id_to_name(id) + " are out on loan.\n"); return 0; } if( _accounts[name] && sizeof( _accounts[name]->loans ) ){ foreach( loan in _accounts[name]->loans ){ if( loan->id == id ){ add_failed_mess("You have already borrowed a copy of " + id_to_name(id) + ".\n"); return 0; } } } if( book->path ) ob = clone_object( book->path ); if( book->auto_load ) ob = PLAYER_OB->load_auto_load_to_array( book->auto_load, this_player() )[0]; if( !ob ){ add_failed_mess("Uh oh, something buggered with book id: " + id + ". Please tell a creator.\n" ); return 0; } t = time() + _loan_length; ob->add_property( TITLE_PROP, book->cap_title ); ob->add_property( AUTHOR_PROP, book->cap_author ); ob->add_property( FROM_LIBRARY_PROP, _library_name ); ob->add_property( BORROWED_BY_PROP, name ); ob->add_property( DUE_BACK_PROP, t ); book->loaned[name] = t; book->total_borrowed++; _catalog[id] = book; loan = new( class _loan, id : id, due_back : t ); if( _accounts[name] ) _accounts[name]->loans += ({ loan }); else { // Start them up an account _accounts[name] = new( class _account, total_fines_paid : 0, lost_damaged : 0, loans : ({ loan }) ); } if( !book->borrowable ){ tell_object( this_player(), id_to_name(id)+ " is a reference only book. " "It will be placed on " "a lecturn for you to read and cannot be removed from the library. Please return it " "in the normal way after use.\n"); ob->move( this_object() ); ob->reset_get(); ob->add_property( "there", "sitting on a lecturn" ); ob->add_property( REFERENCE_ONLY_PROP, 1 ); } else { tell_object( this_player(), "You have until "+ am_time(t) + " to return it.\n"); ob->move( this_player() ); } save_me(); add_succeeded_mess( "$N $V " +id_to_name(id)+ ".\n"); return 1; } /** @ignore yes */ int do_return( object *obs ){ string name, bit; int id, *owed, i, *ours, due; class _loan loan; class _book book; object ob, *not_ours, *wrong_player, *returned; name = this_player()->query_name(); owed = ({ }); ours = not_ours = wrong_player = returned = ({ }); if( !sizeof(_accounts[name]->loans) ){ add_failed_mess( "You don't have any books out on loan.\n" ); return 0; } foreach( loan in _accounts[name]->loans ){ owed += ({ loan->id }); } foreach( ob in obs ){ if( ob->query_property( FROM_LIBRARY_PROP ) == _library_name ){ if( ob->query_property( BORROWED_BY_PROP ) == name ){ foreach( id in owed ){ if( (book = _catalog[id]) ){ if( ob->query_property( TITLE_PROP ) == book->cap_title && ob->query_property( AUTHOR_PROP ) == book->cap_author ){ /* Make sure it has same pages in here... */ map_delete( _catalog[id]->loaned, name ); ob->move("/room/rubbish"); returned += ({ ob }); ours += ({ id }); } } else { // Book no longer matches id....What to do? // Take book from player, clear loan then dest? ob->move("/room/rubbish"); returned += ({ ob }); } } } else { wrong_player += ({ ob }); } } else { not_ours += ({ ob }); } } if( sizeof(returned) ){ foreach( i in ours ) clear_loan( this_player(), i, 1 ); if( _fines[name] ) tell_object(this_player(), "Note: You have fines that are due.\n"); add_succeeded_mess( "$N $V $I.\n", returned ); return 1; } if( (i=sizeof(not_ours)) ){ if( i > 1 ) bit = "do not"; else bit = "does not"; add_failed_mess( "$I " +bit+ " belong to this library.\n", not_ours ); return 0; } if( (i=sizeof(wrong_player)) ){ if( i > 1 ) bit = "were not"; else bit = "was not"; add_failed_mess( "$I " +bit+ " were not loaned to you. Only the borrower may " "return items.\n", wrong_player ); return 0; } } /** @ignore yes */ int do_list( string arg, string from, string to, string specific ){ if( (to && !stringp(to)) || (from && !stringp(from)) ){ add_failed_mess( "The ranges must be letters.\n"); return 0; } if( specific ){ tell_object( this_player(), make_pretty_catalog( arg, 0, 0, lower_case(specific) ) ); return 1; } else { tell_object( this_player(), make_pretty_catalog( arg, from, to, 0 ) ); return 1; } } /** @ignore yes */ int do_pay(){ int cash, fine; string place, str; place = query_property( "place" ); if ( !place || ( place == "" ) ) place = "default"; cash = this_player()->query_value_in( place ); if ( place != "default" ) cash += this_player()->query_value_in( "default" ); if( !cash ){ add_failed_mess("You have money!.\n"); return 0; } fine = _fines[this_player()->query_name()]; if( fine <= 0 ){ add_failed_mess("You have no outstanding fines to pay.\n"); return 0; } if( cash >= fine ) cash = fine; this_player()->pay_money( MONEY_HAND->create_money_array(cash, place), place ); _fines[this_player()->query_name()] -= cash; _accounts[this_player()->query_name()]->total_fines_paid += cash; if( _fines[this_player()->query_name()] <= 0 ){ str = MONEY_HAND->money_value_string( cash, place ); add_succeeded_mess("$N pay$s all $p fines ("+str+").\n"); map_delete( _fines, this_player()->query_name() ); save_me(); return 1; } else { str = MONEY_HAND->money_value_string( fine - cash, place ); add_succeeded_mess("$N pay$s some of $p fine, but sill owe$s ("+str+").\n"); save_me(); return 1; } } /** @ignore yes */ int do_status( string player ){ int fine; string place, str, bit; class _loan loan; if( !player ) player = this_player()->query_name(); player = lower_case(player); if( player != this_player()->query_name() && query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You do not have access to see other accounts.\n"); return 0; } str = "Account status for " + PLAYER_HANDLER->query_cap_name(player) +".\n"; if( _accounts[player] && sizeof(_accounts[player]->loans) ){ str += "Loaned items:\n"; str += sprintf( "%|4s%|=30s%|=20s%|9s\n", "Id", "Item", "Due back", "Overdue" ); foreach( loan in _accounts[player]->loans ){ if( loan->due_back - time() > 0 ) bit = ""; else bit = "*Yes*"; str += sprintf( "%|4d%|=30s%|=20s%|9s\n", loan->id, id_to_name(loan->id), am_time( loan->due_back ), bit ); } } else { str += "No items currently on loan.\n"; } place = query_property( "place" ); if ( !place || ( place == "" ) ) place = "default"; fine = _fines[player]; if( fine >= 0 ){ str += sprintf( "%-15s%-6s\n", "Fines Due:", MONEY_HAND->money_value_string( fine, place ) ); } else { str += "No fines due.\n"; } if( _accounts[player] ){ str += "Total previously paid fines: " + MONEY_HAND->money_value_string( _accounts[player]->total_fines_paid, place ); str += ".\nBooks lost or damaged: " + _accounts[player]->lost_damaged + ".\n"; } tell_object( this_player(), str ); add_succeeded_mess(""); return 1; } /** @ignore yes */ int do_book_status( int id ){ string str, name, date; class _book book; id = to_int(id); if( !_catalog[id] ){ add_failed_mess("The id " +id+ ", does not point to an item.\n"); return 0; } book = _catalog[id]; str = "Status for id: " +id+ " - " + id_to_name(id) + ".\n"; if( sizeof(book->loaned) ){ str += sprintf( "%|=14s%|=30s\n", "On loan to", "Date Due Back " ); foreach( name, date in book->loaned ){ str += sprintf( "%|=12s%|=30s\n", PLAYER_HANDLER->query_cap_name(name), am_time(date) ); } } else { str += "Currently, no copies of the item are on loan."; } str += "The library holds " + (string)book->copies; if( book->copies == 1 ) str += " copy "; else str += " copies "; str += "in total, " + (string)( book->copies - sizeof(book->loaned) ) + " of which are available.\n"; str += "It has been borrowed a total of " + book->total_borrowed; if( book->total_borrowed == 1 ) str += " time.\n"; else str += " times.\n"; if( !book->borrowable ) str += "It is a reference only item.\n"; tell_object( this_player(), str); add_succeeded_mess("$N look$s at the status of a library item.\n"); return 1; } /** @ignore yes */ int do_set( string player, string type ){ player = lower_case(player); if( query_access(this_object()) <= query_access(player) ){ add_failed_mess( "You do not have permission to do this.\n"); return -1; } if( type == "allowed" ){ if( query_access(player) < USER_ACCESS ){ set_access( player, USER_ACCESS ); add_succeeded_mess("$N allow$s " + PLAYER_HANDLER->query_cap_name(player) + " to use the library.\n"); save_me(); return 1; } } if( type == "disallowed" ){ if( query_access(player) == USER_ACCESS ){ set_access( player, 0 ); add_succeeded_mess("$N disallow$s " + PLAYER_HANDLER->query_cap_name(player) + " from using the library.\n"); save_me(); return 1; } } } int do_mark( int id, int flag ){ if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You do not have permission to do this.\n"); return 0; } if( !_catalog[id] ){ add_failed_mess("There is no item with that id.\n"); return 0; } if( flag ){ add_succeeded_mess("$N $V " + id_to_name(id) + " to reference only.\n"); _catalog[id]->borrowable = 0; return 1; } else { add_succeeded_mess("$N $V " + id_to_name(id) + " to not reference only.\n"); _catalog[id]->borrowable = 1; return 1; } save_me(); } int do_set_fine( int i ){ string place; if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You do not have permission to do this.\n"); return 0; } place = query_property( "place" ); if( !place || ( place == "" ) ) place = "default"; i = to_int(i); if( i < 0 ){ add_failed_mess("Don't be silly, thats a negative value!\n"); return 0; } if( i > 4000 ){ add_failed_mess("The maximum fine you can set per day is 4000 units (" + MONEY_HAND->money_value_string( 4000, place ) + ").\n" ); return 0; } add_succeeded_mess("$N set$s the fine per day to " + i + " ("+ MONEY_HAND->money_value_string( i, place ) + ").\n" ); _fine_per_day = i; save_me(); return 1; } int do_set_loan_length( int days ){ string str; if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You do not have permission to do this.\n"); return 0; } days = to_int(days); if( days > 10 ){ add_failed_mess("The maximum loan length is 10 days.\n"); return 0; } if( days < 1 ){ add_failed_mess("The minimum loan length is 1 day.\n"); return 0; } if( days == 1 ) str = " day"; else str = " days"; add_succeeded_mess("$N set$s the loan length to " + query_num(days) + str + ".\n" ); _loan_length = days * AM_SECONDS_PER_DAY; save_me(); return 1; } /** @ignore yes */ int do_set_max_loans( int i ){ if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You do not have permission to do this.\n"); return 0; } i = to_int(i); if( i < 1 ){ add_failed_mess("The minimum amount of loans is one.\n"); return 0; } if( i > 10 ){ add_failed_mess("The maximum amount of loans a player can have is ten.\n"); return 0; } add_succeeded_mess("$N set$s the maximum number of loans to " + query_num(i) + " items.\n" ); _max_loans = i; save_me(); return 1; } int do_lost_damaged_fine( int i ){ string place; if( query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You do not have permission to do this.\n"); return 0; } i = to_int(i); place = query_property( "place" ); if( !place || ( place == "" ) ) place = "default"; if( i < 0 ){ add_failed_mess("Don't be silly, thats a negative value!\n"); return 0; } if( i > 20000 ){ add_failed_mess("The maximum fine you can set per day is 20000 units (" + MONEY_HAND->money_value_string( 20000, place ) + ").\n" ); return 0; } add_succeeded_mess("$N set$s the lost or damaged fine to " + i + " ("+ MONEY_HAND->money_value_string( i, place ) + ").\n" ); _lost_damaged_fine = i; save_me(); return 1; } int do_report( int id, string arg, string player ){ string str; class _loan loan, match; if( player && query_access(this_player()) < LIBRARIAN_ACCESS ){ add_failed_mess("You can only report your damaged or lost books.\n"); return 0; } if( player ){ str = PLAYER_HANDLER->query_cap_name(player)+ " does"; player = lower_case(player); } else { str = "You do"; player = this_player()->query_name(); } if( !_accounts[player] ){ add_failed_mess(str + " not have any items on loan.\n"); return 0; } if( !_catalog[id] ){ add_failed_mess("The id " + id + " does not exist.\n"); return 0; } foreach( loan in _accounts[player]->loans ){ if( loan->id == id ){ match = loan; } } if( !match ){ add_failed_mess(str + " not have " + id_to_name(id) + " out on loan.\n"); return 0; } _accounts[player]->loans -= ({ match }); map_delete( _catalog[id]->loaned, player ); _catalog[id]->copies--; if( player == this_player()->query_name() ) str = ""; else str = " for " + PLAYER_HANDLER->query_cap_name(player); if( _fines[player] ) _fines[player] += _lost_damaged_fine; else _fines[player] = _lost_damaged_fine; if( arg == "lost" ){ add_succeeded_mess("You report the loss of " + id_to_name(id) + str + ".\n"); _accounts[player]->lost_damaged++; save_me(); return 1; } if( arg == "damaged" ){ add_succeeded_mess("You report the damaging of " +id_to_name(id) + str + ".\n"); _accounts[player]->lost_damaged++; save_me(); return 1; } } /** @ignore yes */ protected string make_pretty_catalog( string arg, string from, string to, string specific ){ string widget, c_widget, str, *list; int id, *ids; if( !from ) from = "a"; else from = lower_case(from); if( !to ) to = "z"; else to = lower_case(to); if( from > to ){ /* Then they have said between z and a, rather than a to z, silly buggers */ widget = to; to = from; from = widget; } if( specific ){ if( _catalog_by_title[specific] || _catalog_by_author[specific] ) list = ({ specific }); else list = ({ }); } if( arg == "title" ){ str = sprintf( "%|=4s%|=26s%|=12s%|=9s%|=8s\n", "Id", "Title", "Author", "Copies", "On Loan" ); if( !list ) list = sort_array( keys(_catalog_by_title), 0 ); foreach( widget in list ){ ids = _catalog_by_title[ widget ]; if( widget && sizeof(ids) && (widget[0..0] >= from) && (widget[0..0] <= to) ){ foreach( id in ids ){ c_widget = _catalog[ id ]->cap_title; str += sprintf( "%|=4d%|=26s%|=12s%|=9d%|=8d\n", id, c_widget, _catalog[id]->cap_author, _catalog[id]->copies, sizeof(_catalog[id]->loaned) ); } } } return str; } if( arg == "author" ){ str = sprintf( "%|=4s%|=12s%|=26s%|=9s%|=8s\n", "Id", "Author", "Title", "Copies", "On Loan" ); if( !list ) list = sort_array( keys(_catalog_by_author), 0 ); foreach( widget in list ){ ids = _catalog_by_author[ widget ]; if( widget && sizeof(ids) && (widget[0..0] >= from) && (widget[0..0] <= to) ){ foreach( id in ids ){ c_widget = _catalog[ id ]->cap_title; str += sprintf( "%|=4d%|=12s%|=26s%|=9d%|=8d\n", id, _catalog[id]->cap_author, c_widget, _catalog[id]->copies, sizeof(_catalog[id]->loaned) ); } } } return str; } } /** * This clears the loan of library book id the specified player. * If its overdue it sorts out adding a fine to his name. * @param player Either the player name or object * @param id the library book id * @param calc_fines if this is 1, then we add fines onto the player * if appropriate, if 0 then we ignore fines and just clear the loan */ void clear_loan( mixed player, int id, int calc_fines ){ int fine; class _loan loan; if( objectp(player) && userp(player) ) player = player->query_name(); if( !stringp(player) ) return; if( !_accounts[player] || !sizeof(_accounts[player]->loans) ) return; foreach( loan in _accounts[player]->loans ){ if( loan->id == id ){ if( calc_fines && (loan->due_back - time() < 0) ){ // Naughty! fine = (((time() - loan->due_back) / AM_SECONDS_PER_DAY) + 1) * _fine_per_day; if( _fines[player] ) _fines[player] += fine; else _fines[player] = fine; } _accounts[player]->loans -= ({ loan }); } } save_me(); } /** @ignore yes */ protected int find_blank_id(){ int *ids; int unused_id, i; ids = sort_array( keys(_catalog), -1 ); if( !sizeof(ids) ){ unused_id = 1; } else { if( ids[0] > sizeof(ids) ){ for( i = 0; i < sizeof(ids); i++ ){ if( undefinedp(_catalog[i]) ){ unused_id = i; break; } } } else { unused_id = sizeof(ids) + 1; } } if( unused_id == 0 ) unused_id = 1; return unused_id; } /** @ignore yes */ protected void add_book_to_catalogs( class _book new_book, int id ){ _catalog[id] = new_book; save_me(); if( _catalog_by_author[lower_case(new_book->cap_author)] ) _catalog_by_author[lower_case(new_book->cap_author)] += ({ id }); else _catalog_by_author[lower_case(new_book->cap_author)] = ({ id }); if( _catalog_by_title[lower_case(new_book->cap_title)] ) _catalog_by_title[lower_case(new_book->cap_title)] += ({ id }); else _catalog_by_title[lower_case(new_book->cap_title)] = ({ id }); } /** * This function adds the specified object to the library * @param thing Either a filename, or object. If its an object we record its autoload info * @param copies how many copies to add * @param cap_title The capitalized title its cataloged under * @param cap_author The capitalized author of the work * @param borrowable 1 if it can be borrowed, 0 if its reference only */ varargs int add_book_to_library( mixed thing, int copies, string cap_title, string cap_author, int borrowable ){ class _book book; int id, *ids; if( intp(thing) ){ if( _catalog[thing] ){ _catalog[thing]->copies += copies; return 1; } else { return 0; } } if( !cap_title || !cap_author || cap_title == "" || cap_author == "" ) return 0; if( (ids = _catalog_by_title[ lower_case(cap_title) ]) ){ foreach( id in ids ){ if( lower_case(_catalog[id]->cap_author) == lower_case(cap_author) ) /*= Then we already have a book of the same title by the same author =*/ return add_book_to_library( id, 1 ); } } if( stringp(thing) ){ book = new( class _book ); book->path = thing; book->cap_author = cap_author; book->cap_title = cap_title; book->borrowable = 1; book->copies = copies; book->loaned = ([ ]); book->total_borrowed = 0; id = find_blank_id(); add_book_to_catalogs( book, id ); return 1; } if( objectp(thing) ){ book = new( class _book ); book->auto_load = AUTO_LOAD_OB->create_auto_load( ({ thing }) ); book->cap_author = cap_author; book->cap_title = cap_title; book->borrowable = 1; book->copies = copies; book->loaned = ([ ]); book->total_borrowed = 0; id = find_blank_id(); add_book_to_catalogs( book, id ); return 1; } } /** * Returns 1, if we think that the new object is the same as the existing one we * have in stock. * @ignore yes */ protected int compare_widgets( object new_ob, int existing_id ){ class _book book; object ob; mixed info; int perc; book = _catalog[existing_id]; if( !book ) return 0; if( book->path ) ob = clone_object( book->path ); if( book->auto_load ) ob = PLAYER_OB->load_auto_load_to_array( book->auto_load, this_player() )[0]; if( !ob ){ // Shouldn't happen... return 0; } if( base_name(ob) != base_name(new_ob) ) return 0; // Then they can't be the same surely... if( inherits("/std/book.c", new_ob) ){ perc = BOOK_HANDLER->compare_pages( BOOK_HANDLER->query_pages( new_ob ), BOOK_HANDLER->query_pages( ob ) ); } if( inherits("/std/leaflet.c", new_ob) ){ perc = BOOK_HANDLER->compare_pages( map( new_ob->query_pages(), (: $1[0][0] :) ), map( ob->query_pages(), (: $1[0][0] :) ) ); } if( inherits("/obj/misc/paper", new_ob) || inherits("/obj/misc/nroff_paper", new_ob) ){ perc = BOOK_HANDLER->compare_pages( map( new_ob->query_read_mess(), (: $1[0] :) ), map( ob->query_read_mess(), (: $1[0] :) ) ); } ob->move( "/room/rubbish" ); if( perc > 97 ) return 1; else return 0; } /** * This is used to sort out the look msg of the sign. * @ignore yes */ string extra_look( object ob ){ string *libs, str, place; str = "Welcome to " + _library_name + ".\n"; libs = filter( keys(_access), (: _access[$1] == LIBRARIAN_ACCESS :) ); if( !sizeof(libs) ) str += "Currently there are no caretakers of the books.\n"; if( sizeof(libs) == 1 ) str += PLAYER_HANDLER->query_cap_name(libs[0]) + " is the sole caretaker " " of the books.\n"; if( sizeof(libs) > 1 ){ libs = map( libs, (: PLAYER_HANDLER->query_cap_name($1) :) ); str += query_multiple_short(libs) + " are caretakers of the books.\n"; } place = query_property("place"); if( !place || place == "" ) place = "default"; str += "Books may be borrowed for a maximum of " + (_loan_length / AM_SECONDS_PER_DAY) + " Disc days, before fines are levied.\n"; str += "Fines currently stand at " + MONEY_HAND->money_value_string( _fine_per_day, place ) + " per day the book is overdue.\n"; str += "A charge of " + MONEY_HAND->money_value_string( _lost_damaged_fine, place ) + " will be incurred if you lose or damage a book.\n"; str += "The maximum number of items you can borrow at one time currently stands at "+ query_num(_max_loans) + ".\n"; return str; } /** @ignore yes */ protected void save_it(){ unguarded( (: save_object, _save_file :) ); } /** @ignore yes */ void save_me(){ if( find_call_out("save_it") == -1 ) call_out( "save_it", 2 ); } /* void dest_me(){ object ob, *ref; ref = filter( all_inventory(), (: $1->query_property(REFERENCE_ONLY_PROP) :) ) ); foreach( ob in ref ){ } } */ /** * Returns a mapping of book ids to due back dates for the given player. * @param name the player * @return a mapping in the form ([ book id : due back ]) */ mapping query_loans( string name ){ class _loan loan; mapping m = ([ ]); if( !_accounts[name] ) return ([ ]); foreach( loan in _accounts[name]->loans ){ m[ loan->id ] = loan->due_back; } return m; } /** * Returns a mapping of players who borrowed the given book id, mapped to their * due back dates. * @param id valid book id */ mapping query_borrowed_by( int id ){ if( _catalog[id] ) return _catalog[id]->loaned; else return ([ ]); } /** * @param name the string name of the person to check * @return How much of a fine this person has */ int query_fine( string name ){ return _fines[name]; } /** * Debug only * @ignore yes */ mapping query_catalog(){ return _catalog; } /** @ignore yes */ mapping query_catalog_by_title(){ return _catalog_by_title; } /** @ignore yes */ mapping query_catalog_by_author(){ return _catalog_by_author; } /** @ignore yes */ mapping query_all_accounts(){ return _accounts; } /***** ******/ void set_library_name( string str ){ _library_name = str; } string query_library_name(){ return _library_name; } void set_player_contributable( int i ){ _player_contributable = i; } int query_player_contributable(){ return _player_contributable; } void set_max_loans( int i ){ _max_loans = i; } int query_max_loans(){ return _max_loans; } void set_loan_length( int i ){ _loan_length = i; } int query_loan_length(){ return _loan_length; } void set_fine_per_day( int i ){ _fine_per_day = i; } int query_fine_per_day(){ return _fine_per_day; } void set_lost_damaged_fine( int i ){ _lost_damaged_fine = i; } int query_lost_damaged_fine(){ return _lost_damaged_fine; } void set_save_file( string s ){ _save_file = s; } string query_save_file(){ return _save_file; } void set_borrow_func( function f ){ _borrow_func = f; } function query_borrow_func(){ return _borrow_func; } void set_access( string player, int access ){ if( !access ){ map_delete( _access, player ); } else { _access[player] = access; } save_me(); } int query_access( mixed player ){ /* if( userp(player) ) player = player->query_name(); if( stringp(player) ) player = lower_case(player); else return 0; return _access[player]; */ return LIBRARIAN_ACCESS; }