/** * A nice book object. A book will be a collection of pages. * You can tear pages out of a book, you can add pages (though adding * a page isnt overly useful, unless you bind it in of course). *<p> * Each page is an object. Pages that are torn out are handled * as empty spots in the array. *<p> * @change 26-Feb-96 Jeremy * I took out the "my book" checks in test_add() and test_remove() * because they were keeping written pages from loading. If * anyone knows why the checks were necessary, let me know. * @change 25-Aug-96 Jeremy * Changed test_add() so that it won't add things held by players. * This is because if they "put" something in the book, it's inaccessible * (maybe this is why pages had the "my book" property? I like this * solution better, but not much). * @author Pinkfish */ #include <move_failures.h> #include <player.h> inherit OBJECT_OBJ; private mixed _pages; private string _default_page_object; private object _def_p_obj; /* Open_page set to 0 means closed. */ private int _open_page; private int _book_num; /* * If this is set, we ignore the flag and only print the real * short of the book. */ private int _ignore_open_page; /* * This should be used in conjuction with created books * that yuou wish the contents to update when changed. */ private int _ignore_saved_pages; private nosave object _player; protected int do_open(int page); protected int do_tear(int number); protected int do_turn(int number); protected int do_close(); object create_default_page_object(); private void replace_page(mixed page, int pos); void create() { _pages = ({ }); _default_page_object = PAPER_OBJ; load_object(_default_page_object); _def_p_obj = find_object(_default_page_object); ::create(); } /* create() */ /** * Tells us if this is a book object. * @return always returns 1 */ int query_book() { return 1; } /** * Adds the commands onto the player. The commands are "read", "open", * "tear", "turn" and "close". * @ignore yes */ void init() { add_command("open", "<direct:object>", (: do_open(1) :)); add_command("open", "<direct:object> to [page] <number>", (: do_open($4[1]) :)); add_command("tear", "page from <direct:object>", (: do_tear(1) :)); add_command("tear", "[all] pages from <direct:object>", (: do_tear(0) :)); add_command("tear", "<number> [of] pages from <direct:object>", (: do_tear($4[0]) :)); add_command("rip", "page from <direct:object>", (: do_tear(1) :)); add_command("rip", "<number> [of] pages from <direct:object>", (: do_tear($4[0]) :)); add_command("rip", "[all] pages from <direct:object>", (: do_tear(0) :)); add_command("turn", "[a] page of <direct:object>", (: do_turn(1) :)); add_command("turn", "<number> pages of <direct:object>", (: do_turn($4[0]) :)); add_command("turn", "<direct:object> to [page] <number>", (: do_open($4[1]) :) ); add_command("turn", "to page <number> of <direct:object>", (: do_open($4[0]) :) ); add_command("close", "<direct:object>", (: do_close() :)); } /* init() */ /** * @ignore yes */ int add_weight( int number ) { adjust_weight( number ); return 1; } /* add_weight() */ /** * To see if things can be added to us. This has a few nasty * cludges in it to stop a few things. * @see /std/basic/misc * @param ob the object being added * @param flag the flag sent to this function * @return 1 if it can be added, 0 if it cannot * @ignore */ int test_add(object ob, int flag) { return (object)ob->query_property("my book") == TO; // This is a kludge to keep people from putting things in the book. // It's not foolproof, but it should do until I think of a better way. /* if (!environment(ob) || !living(environment(ob))) return 1; return 0; */ } /* test_add() */ /** * To check to see if things can be removed from us. * @see /std/basic/misc * @param ob the object being removed * @param flag the flag sent to the function * @param dest where it is going to * @return 1 if it can be removed, 0 if it cannot * @ignore */ int test_remove( object ob, int flag, mixed dest ) { return ob->query_property("my book") != TO; //return 1; } /* test_remove() */ /** * Set the number of pages in the book. If there are too many pages * in the book, then pages are removed from the top to create the * correct size and if there are too few pages then pages are * added onto the end of the pages array. * @param no the number of pages * @see query_pages() * @see query_num_pages() */ void set_no_pages(int no) { int i; int siz; siz = sizeof(_pages); if (no < siz) { /* removeing pages? Oh well, if you insist. */ for (i = no; i < siz; i++) { if (objectp(_pages[i])) { _pages[i]->dest_me(); } } _pages = _pages[0..no-1]; if (_open_page >= no) { _open_page = no; } } else { /* Adding pages, more normal... */ _pages = _pages + allocate(no-siz); for (i=siz;i<no;i++) { /* Set them to 1 and only make them real if someone wants them. */ _pages[i] = 1; } } } /* set_no_pages() */ /** * Returns the pages array. * @return the array containing the pages information */ object *query_pages() { return _pages; } /** * Sets the currently open page. This does all the magic needed to make * the book appear as if it is the currently opened page. If the open * page is 0 then the book is closed. If it is out of the upper * bound then the book is opened to the last page. * @param i the page to open to * @see query_open_page() * @see query_current_page() */ void set_open_page(int i) { /* Valid page or already there. */ if (i < 0 || i == _open_page) { return ; } if (!_open_page && i) { add_alias("page"); add_plural("pages"); } if (i > sizeof(_pages)) { _open_page = sizeof(_pages); } else { _open_page = i; } if (!_open_page) { remove_alias("page"); remove_plural("pages"); } } /* set_open_page() */ /** * What is the current open page. Returns 0 if the book is not * open. * @return the current open page * @see set_open_page() * @see query_current_page() */ int query_open_page() { return _open_page; } /* query_open_page() */ /** * This method checks to see if the current page is torn out. * @return 1 if torn out, 0 if not * @see query_current_page() * @see query_open_page() * @see is_page_torn_out() */ int is_current_page_torn_out() { if (!_open_page) { /* The cover cannot be torn out. */ return 0; } if ( !_pages ) { return 0; } if (!_pages[_open_page-1]) { return 1; } return 0; } /* is_current_page_torn_out() */ /** * This method checks to see if the specified page is torn out. * @param page the page number to check * @return 1 if it is torn out, - if not * @see is_current_page_torn_out() */ int is_page_torn_out(int page) { if (page < 1 || page > sizeof(_pages)) { return 0; } if (!_pages[page - 1]) { return 1; } return 0; } /* is_page_torn_out() */ /** * Returns the object associated with the current open page. If the * page does not actualy exist yet then the default base object * is returned. This object should be handled carefully... If the * page is torn out then the next readable page is returned or * 0 is returned. * @return the current page object * @see query_current_page_clone() * @see set_open_page() * @see query_open_page() * @see query_current_page_clone() */ object query_current_page() { int i; if (!_open_page) { return TO; /* The book itself... */ } for (i = _open_page - 1; i < sizeof(_pages); i++) { if (_pages[i]) { if (objectp(_pages[i])) { return _pages[i]; } else { return _def_p_obj; } } } return 0; } /* query_current_page() */ /** * This function makes sure the page actually * exists. If it does not exist, then it will clone one * up for us. This one should be used as you will not end up with the * base object in this case. If you are planning to modify the page, * then use this call. * @see query_current_page() * @return the object of the current page */ object query_current_page_clone() { if (_open_page) { if (!_pages[_open_page-1]) { return 0; } else if (objectp(_pages[_open_page-1])) { return _pages[_open_page-1]; } else { replace_page(create_default_page_object(), _open_page - 1); return _pages[_open_page-1]; } } else { return TO; } } /* query_current_page_clone() */ /** * This method checks to see if the page is still the default page object * or if it is something else altogether. * @return 1 if it is the default page object, 0 if not */ int is_default_page(int num) { if (num > 0 && num <= sizeof(_pages)) { if (_pages[num - 1] && !objectp(_pages[num - 1])) { return 1; } } return 0; } /* is_default_page() */ /** * This method returns the contents of the selected page. The number * must be greator than 0. This will return the exact value of the * page, it will not search for non-torn out pages. Becare with * the return value of this as the default page object might be * returned if the page does not actually exist. If you need a * real object remember to use the clone version. * @param num the page to return * @return 0 on failure or if the page is torn out, the object on success */ object query_selected_page(int num) { if (!intp(num) || num <= 0 || num > sizeof(_pages)) { return 0; } if (_pages[num - 1]) { if (objectp(_pages[num - 1])) { return _pages[num - 1]; } else { return _def_p_obj; } } return 0; } /* query_selected_page() */ /** * This method returns the contents of the selected page, if a default * object is returned a real clone for the page is created instead. * @param num the page to return * @return 0 on failure or if the page is torn out, the object on success */ object query_selected_page_clone(int num) { if (!intp(num) || num <= 0 || num > sizeof(_pages)) { return 0; } if (_pages[num - 1]) { if (objectp(_pages[num - 1])) { return _pages[num - 1]; } else { replace_page(create_default_page_object(), num - 1); return _pages[num - 1]; } } return 0; } /* query_selected_page_clone() */ /** * This method tears the current page out of the book and returns it * to us. This object will be moved into the destination so that it is * no longer inside us. If it cannot be moved of the page has already * been remove then the function will return 0. * @param dest the destination to move the page to * @return the torn out page, or 0 on failure */ object tear_current_page_out(object dest) { object page; if (is_current_page_torn_out()) { /* Too late... */ return 0; } page = query_current_page_clone(); // Fiddle with the properties so the page is moveable. page->remove_property("my book"); if (page && page->move(dest) == MOVE_OK) { _pages[_open_page-1] = 0; return page; } page->add_property("my book", TO); return 0; } /* tear_current_page_out() */ /** * This method adds a new page in after the selected page. The pages * are numbered from 1, so adding a page after page 0 will place a * page on the start and after the last page onto the end. * @param page the page to add * @param after the page to add it after * @return 1 on success, 0 on failure * @see query_current_page() * @see query_num_pages() */ int add_page_after(object page, int after) { if (after < 0 || after > sizeof(_pages) + 1 || !objectp(page) || !intp(after)) { return 0; } _pages = _pages[0..after - 1] + ({ 1 }) + _pages[after..]; replace_page(page, after); return 1; } /* add_page_after() */ /** * This is the internal function used to replace pages. This will do * all the cleaning up of the pages needed to make this work * properly. * @param page the page to replace it with * @param pos the position to replace */ private void replace_page(mixed page, int pos) { if (objectp(_pages[pos]) && _pages[pos] != page) { _pages[pos]->dest_me(); } if (objectp(page)) { page->add_property("my book", TO); page->move(TO); } _pages[pos] = page; } /* replace_page() */ /** * This method replaces the selected page with a new page. The old page * is dested if it can be and replaced with a nice shiny new page object. * @param page the page to replace with * @param num the page number to replace * @return 1 on success, 0 on failure * @see add_page_after() */ int replace_page_with(object page, int num) { if (num < 1 || num > sizeof(_pages) || !objectp(page) || !intp(num)) { return 0; } replace_page(page, num - 1); } /* replace_page_with() */ /** * This method makes the selected page blank. It defaults it back to a * default page object as if the book had just been created. * @param num the page to make blank * @see replace_page_with() * @see add_page_after() * @see add_blank_page_after() */ int make_page_blank(int num) { if (num < 1 || num > sizeof(_pages) || !intp(num)) { return 0; } replace_page(1, num - 1); } /* make_page_blank() */ /** * @ignore yes */ string short(int flags) { if (!flags || _ignore_open_page) { return ::short(flags); } if (_open_page) { return "open " + ::short(flags); } return "closed " + ::short(flags); } /* short() */ /** * This is over ridden here to allow the open/closed sttus of the book to be * matched. * @ignore yes */ string *parse_command_adjectiv_id_list() { if (_open_page) { return ::parse_command_adjectiv_id_list() + ({ "open" }); } return ::parse_command_adjectiv_id_list() + ({ "closed" }); } /* parse_command_adjectiv_id_list() */ /** * @ignore yes */ string long(string str, int dark) { string ret; int i; if (!_open_page) { return ::long(str, dark)+"It is closed.\n"; } ret = ::long(str, dark)+"It is open at page "+_open_page+".\n"; for (i=_open_page-1;i<sizeof(_pages) && !_pages[i];i++) { if (!_pages[i]) { ret += "Page "+(i+1)+" has been torn out.\n"; } } if (i >= sizeof(_pages)) { ret += "All the rest of the pages have been torn out!\n"; } else { if (i != _open_page -1) { /* A page has been torn out... */ ret += "You can see page "+(i+1)+" however.\n"; } // Only show the page if they "look page of book" or somesuch. if(str && strsrch(str, "page") != -1) { if (objectp(_pages[i])) { ret += (string)_pages[i]->long(str, dark); } else { ret += _default_page_object->long(str, dark); } } } return ret; } /* long() */ /** * Opens the book to the right place. This is the add_command function. * @param indir the indirect objects (not used) * @param id_mactch the string matching the id (not used) * @param ind_match the string matching the indirect ob (not used) * @param args the arguments associated with the pattern * @param the pattern which was matched. * @see /global/new_parse->add_command() * @return 1 if it succeeded, 0 if it failed * @ignore yes */ protected int do_open(int page) { if (!page || page > sizeof(_pages)) { add_failed_mess("Could not open to page " + page + ".\n", ({ })); return 0; } if (query_open_page() == page) { add_failed_mess("The $D is already open at page " + page + ".\n"); } _ignore_open_page = 1; call_out((: _ignore_open_page = 0 :), 4); set_open_page(page); add_succeeded_mess("$N $V $D to page " + page + ".\n", ({ })); return 1; } /* do_open() */ /** * Turns a page of the book. * @return always returns 1 * @ignore yes */ protected int do_turn(int number) { int tmp; tmp = query_open_page(); if (tmp+number > sizeof(_pages)) { set_open_page(0); add_succeeded_mess("$N close$s $D.\n"); } else { if (tmp == 0) { add_succeeded_mess("$N turn$s $D to page " + number + ".\n"); } set_open_page(tmp + number); } if (tmp == query_open_page()) { add_failed_mess("Unable to turn page of $D.\n", ({ })); return 0; } _ignore_open_page = 1; call_out((: _ignore_open_page = 0 :), 4); return 1; } /* do_turn() */ /** * Closes the book. * @return 0 if it failed, 1 if it succeeded * @ignore yes */ protected int do_close() { if (!query_open_page()) { TP->add_failed_mess(TO, "$D is already closed.\n", ({})); return 0; } _ignore_open_page = 1; call_out((: _ignore_open_page = 0 :), 4); set_open_page(0); return 1; } /* do_close() */ /** * Tears out the current page. * @return 0 if it failed, 1 if it succeded * @ignore yes */ int do_tear(int number) { int i; if (!_open_page) { TP->add_failed_mess(TO, "$D is closed!\n", ({})); return 0; } // do all the pages. if(number == 0) { _open_page = 1; number = sizeof(_pages); } for(i=0; i<number; i++) { if(!tear_current_page_out(TP)) break; if ( _open_page != sizeof( _pages ) ) { _open_page++; } } if(i) { if(i > 1) add_succeeded_mess("$N $V " + i + " pages from $D.\n"); else add_succeeded_mess("$N $V a page from $D.\n"); return 1; } return 0; } /* do_tear() */ /* The stuff redefined for handleing books... */ /** * @ignore yes */ varargs void set_read_mess(string str, string lang, int size) { object ob; if (_open_page) { ob = query_current_page_clone(); if (!ob) return 0; return ob->set_read_mess(str, lang, size); } return ::set_read_mess(str, lang, size); } /* set_read_mess() */ /** @ignore yes */ string add_read_mess( string str, string type, string lang, int size ) { if( _open_page ) { object ob; if( ob = query_current_page_clone() ) return ob->add_read_mess(str, type, lang, size); return 0; } return ::add_read_mess( str, type, lang, size ); } /* add_read_mess() */ /** * @ignore yes */ mixed query_read_mess() { if (_open_page) { object ob; ob = query_current_page(); if (!ob) { return 0; } return ob->query_read_mess(); } return ::query_read_mess(); } /* query_read_mess() */ /** * @ignore yes */ void set_max_size(int siz) { object ob; if (!_open_page) return ::set_max_size(siz); ob = query_current_page_clone(); if (!ob) return 0; ob->set_max_size(siz); } /* query_max_size() */ /** * @ignore yes */ void set_cur_size(int siz) { object ob; if (!_open_page) return ::set_cur_size(siz); ob = query_current_page_clone(); if (!ob) return 0; ob->set_cur_size(siz); } /* set_cur_size() */ /** * @ignore yes */ int query_max_size() { object ob; if (!_open_page) return ::query_max_size(); ob = query_current_page_clone(); if (!ob) return 0; return (int)ob->query_max_size(); } /* query_max_size() */ /** * @ignore yes */ int query_cur_size() { object ob; if (!_open_page) return ::query_cur_size(); ob = query_current_page_clone(); if (!ob) return 0; return (int)ob->query_cur_size(); } /* query_cur_size() */ /** @ignore yes */ mixed stats() { return ({ ({ "num pages" , sizeof(_pages) }), ({ "ignore saved pages" , _ignore_saved_pages }), ({ "default page ob" , _default_page_object }), ({ "open page", _open_page }), ({ "book number" , _book_num }), }) + ::stats(); } /* stats() */ /** @ignore yes */ void dest_me() { int i; for (i=0;i<sizeof(_pages);i++) { if (objectp(_pages[i])) { _pages[i]->dest_me(); } } ::dest_me(); } /* dest_me() */ /** @ignore yes */ mixed query_static_auto_load() { if (file_name(TO)[0..8] == "/std/book") { return int_query_static_auto_load(); } } /* query_static_auto_load() */ /** @ignore yes */ mapping query_dynamic_auto_load() { int i; mixed ret; mapping bing; mixed stuff; bing = ([ "::" : ::query_dynamic_auto_load(), "default page object" : _default_page_object, "open page" : _open_page, "book num" : _book_num, ]); if (!_ignore_saved_pages) { ret = ({ }); for (i=0;i<sizeof(_pages);i++) { if (intp(_pages[i])) { ret += ({ _pages[i] }); } else { stuff = 0; if (TP) { catch(stuff = TP->create_auto_load(_pages[i..i]) ); } if (!stuff) { catch(stuff = PLAYER_OB->create_auto_load(_pages[i..i]) ); } if (!stuff) { catch(stuff = AUTO_LOAD_OB->create_auto_load(_pages[i..i]) ); } ret += ({ stuff }); } } bing["pages"] = ret; } return bing; } /* query_dynamic_auto_load() */ /** @ignore yes */ void init_dynamic_arg( mapping map ) { int i; object *tmp, player; player = _player; if( !player ) player = TP; if( map["::"] ) ::init_dynamic_arg( map["::"] ); if( map["default page object"] ) { _default_page_object = map["default page object"]; load_object(_default_page_object); _def_p_obj = find_object(_default_page_object); } if( map["pages"] && !_ignore_saved_pages ) { // If setup() created any pages, dest them in favor of the saved pages. if( _pages ) { for( i = 0; i < sizeof(_pages); i++ ) { if( objectp(_pages[i]) ) _pages[i]->move("/room/rubbish"); } } _pages = ({ }); for( i = 0; i < sizeof(map["pages"]); i++ ) { if( intp(map["pages"][i]) ) { _pages += ({ map["pages"][i] }); } else { tmp = (object *)player->load_auto_load_to_array( map["pages"][i], TO, player ); tmp->add_property("my book", TO ); tmp->move(TO); _pages += tmp; } } } _book_num = map["book num"]; set_open_page( map["open page"] ); } /* init_dynamic_arg() */ /** * Sets the object to use as the default page. If this is not set the * the object /std/stationery/paper.c will be used. * @param ob the object to use as the default page */ void set_default_page_object(string obj) { load_object(obj); if (find_object(obj)) { _default_page_object = obj; _def_p_obj = find_object(obj); } } /* set_default_page_object() */ /** * This method returns the default page object. * @return the default page object (as a string) */ string query_default_page_object() { return _default_page_object; } /* query_default_page_object() */ /** * This method creates a new default page object for this book. THis * can be used for whatever nefarious purposes you want. * @return a new default page object */ object create_default_page_object() { return clone_object(_default_page_object); } /* create_default_page_object() */ /** * Queries the number of pages in the book. * @return the number of pages in the book * @see set_no_pages() */ int query_num_pages() { return sizeof(_pages); } /* query_num_pages() */ /** * This method returns the current number of the book. This is assigned * and used in conjunction with the book handler. * @see /handlers/book_handler.c * @see /std/print_shop.c * @return the current book number */ int query_book_num() { return _book_num; } /* query_book_num() */ /** * This method sets the current number of the book. This is assigned * and used in conjunction with the book handler. * @see /handlers/book_handler.c * @see /std/print_shop.c * @param num the new book number */ void set_book_num(int num) { _book_num = num; } /* set_book_num() */ /** * This method returns the current setting of the ignore saved * pages variable. If this is true then * the pages form the players auto_load stuff will be ignored totaly * and only the page creates in setup would be * used. * @see set_ignore_saved_pages() * @return the current ignore saved page attribute */ int query_ignore_saved_pages() { return _ignore_saved_pages; } /* query_ignore_saved_pages() */ /** * This method sets the current setting of the ignore saved * pages variable. If this is true then * the pages form the players auto_load stuff will be ignored totaly * and only the page creates in setup would be * used. This can only be called from an object * which inherits this one (hence the protected method). * @param saved the new value of the saved attribute * @see query_ignore_saved_pages() */ protected void set_ignore_saved_pages(int saved) { _ignore_saved_pages = saved; } /* set_ignore_saved_pages() */ /** * This method is called by the auto loading process to set the current * player on this object. * @param player the player to set * @see query_player() */ void set_player(object player) { _player = player; } /* set_player() */ /** * This method will return the player associated with the auto loading * process. * @return the player in use by the autoloading * @see set_player() */ object query_player() { return _player; } /* query_player() */ /** * @ignore * This will determine the books read short, which is different to a * normal short because it says wild stuff about the cover and things. */ string query_read_short(object player, int ignore_labels) { if(!::query_read_short(player, 0)) return 0; if (!_open_page) { return "the cover of " + ::query_read_short(player, 0); } return "page " + query_num(_open_page) + " of " + ::query_read_short(player, 1); } /* query_read_short() */ /** * @ignore * This is overridden specifically to stop labels appearing on anything * but the cover. */ string query_readable_message(object player, int ignore_labels) { return ::query_readable_message(player, _open_page != 0); } /* query_readable_message() */