//***************************************************************************** // // inform.c // // These are all of the functions that are used for listing off processed // information about things (e.g. room descriptions + contents, shopping // lists, etc...) // //***************************************************************************** #include "mud.h" #include "utils.h" #include "character.h" #include "object.h" #include "world.h" #include "room.h" #include "exit.h" #include "extra_descs.h" #include "body.h" #include "races.h" #include "handler.h" #include "socket.h" #include "log.h" #include "inform.h" #include "hooks.h" //***************************************************************************** // mandatory modules //***************************************************************************** #include "items/items.h" #include "items/furniture.h" //***************************************************************************** // local functions //***************************************************************************** // // Show a character who is all sitting at one piece of furniture // void list_one_furniture(CHAR_DATA *ch, OBJ_DATA *furniture) { LIST *can_see = find_all_chars(ch,objGetUsers(furniture), "", NULL, TRUE); listRemove(can_see, ch); char *chars = print_list(can_see, charGetName, charGetMultiName); if(*chars) send_to_char(ch, "{g%s %s %s %s%s.\r\n", chars, (listSize(can_see) == 1 ? "is" : "are"), (furnitureGetType(furniture)==FURNITURE_AT?"at":"on"), objGetName(furniture), (charGetFurniture(ch) == furniture ?" with you": "")); // everyone was invisible to us... we should still show the furniture though else send_to_char(ch, "{g%s\r\n", objGetRdesc(furniture)); deleteList(can_see); free(chars); } // // Lists furniture that is assumed to be used. // void list_used_furniture(CHAR_DATA *ch, LIST *furniture) { OBJ_DATA *obj; LIST_ITERATOR *obj_i = newListIterator(furniture); ITERATE_LIST(obj, obj_i) // hmmm... how should we handle invisible furniture? list_one_furniture(ch, obj); deleteListIterator(obj_i); } // // Get a sublist of characters not using furniture. The returned list must // be deleted after use. // LIST *get_nofurniture_chars(CHAR_DATA *ch, LIST *list, bool invis_ok, bool include_self) { LIST *newlist = newList(); LIST_ITERATOR *char_i = newListIterator(list); CHAR_DATA *i = NULL; ITERATE_LIST(i, char_i) { // don't show ourself if(i == ch && !include_self) continue; // check for invis and hidden ... if(!(invis_ok || can_see_char(ch, i))) continue; // make sure they're not on furniture if(charGetFurniture(i)) continue; listPut(newlist, i); }; deleteListIterator(char_i); return newlist; } // // uhhh... "contents" isn't really the right word, since we list both // objects AND characters. Alas, my lexicon is not as verbose as it // could be. // void list_room_contents(CHAR_DATA *ch, ROOM_DATA *room) { LIST *list = NULL; list = get_nofurniture_chars(ch, roomGetCharacters(room), FALSE, FALSE); show_list(ch, list, charGetRdesc, charGetMultiRdesc); deleteList(list); // show all of the objects that have people using them list = get_used_items(ch, roomGetContents(room), FALSE); list_used_furniture(ch, list); deleteList(list); // show all of the objects that don't have people using them that we can see list = get_unused_items(ch, roomGetContents(room), FALSE); show_list(ch, list, objGetRdesc, objGetMultiRdesc); deleteList(list); } //***************************************************************************** // implementaiton of inform.h // look_at_xxx and show_xxx functions. //***************************************************************************** void look_at_obj(CHAR_DATA *ch, OBJ_DATA *obj) { // make our working copy of the description bufferClear(charGetLookBuffer(ch)); bufferCat(charGetLookBuffer(ch), objGetDesc(obj)); // do all of the preprocessing on the new descriptions hookRun("preprocess_obj_desc", hookBuildInfo("obj ch", obj, ch)); // append anything that might also go onto it hookRun("append_obj_desc", hookBuildInfo("obj ch", obj, ch)); // colorize all of the edescs edescTagDesc(charGetLookBuffer(ch), objGetEdescs(obj), "{c", "{g"); // format the desc, and send it bufferFormat(charGetLookBuffer(ch), SCREEN_WIDTH, PARA_INDENT); if(bufferLength(charGetLookBuffer(ch)) == 0) send_to_char(ch, "{g%s\r\n", NOTHING_SPECIAL); else send_to_char(ch, "{g%s", bufferString(charGetLookBuffer(ch))); hookRun("look_at_obj", hookBuildInfo("obj ch", obj, ch)); send_to_char(ch, "{n"); } void look_at_exit(CHAR_DATA *ch, EXIT_DATA *exit) { // make the working copy of the description, and fill it up with info bufferClear(charGetLookBuffer(ch)); bufferCat(charGetLookBuffer(ch), exitGetDesc(exit)); // do all of our preprocessing of the description before we show it hookRun("preprocess_exit_desc", hookBuildInfo("ex ch", exit, ch)); // append anything that might also go onto it hookRun("append_exit_desc", hookBuildInfo("ex ch", exit, ch)); // colorize all of the edescs edescTagDesc(charGetLookBuffer(ch), roomGetEdescs(exitGetRoom(exit)), "{c", "{g"); // format our description bufferFormat(charGetLookBuffer(ch), SCREEN_WIDTH, PARA_INDENT); // if the buffer has nothing in it, send a "nothing special" message if(bufferLength(charGetLookBuffer(ch)) == 0) send_to_char(ch, "{g%s\r\n", NOTHING_SPECIAL); else send_to_char(ch, "{g%s", bufferString(charGetLookBuffer(ch))); hookRun("look_at_exit", hookBuildInfo("ex ch", exit, ch)); send_to_char(ch, "{n"); } // // shows a single exit to a character void list_one_exit(CHAR_DATA *ch, EXIT_DATA *exit, const char *dir) { char buf[100] = "\0"; // for the room class ROOM_DATA *dest = worldGetRoom(gameworld, exitGetTo(exit)); if(bitIsOneSet(charGetUserGroups(ch), "builder")) sprintf(buf, "[%s] ", roomGetClass(dest)); send_to_char(ch, "{g %-10s :: %s%s\r\n", dir, buf, (exitIsClosed(exit) ? // if it's closed, print the exit name (*exitGetName(exit) ? exitGetName(exit) : "closed" ) : // if it's open, print where it leads to roomGetName(dest))); } void list_room_exits(CHAR_DATA *ch, ROOM_DATA *room) { EXIT_DATA *exit = NULL; ROOM_DATA *to = NULL; int i = 0; LIST *ex_list = roomGetExitNames(room); LIST_ITERATOR *ex_i = newListIterator(ex_list); char *dir = NULL; // first, we list all of the normal exit for(i = 0; i < NUM_DIRS; i++) { if( (exit = roomGetExit(room, dirGetName(i))) != NULL) { // make sure the destination exists if( (to = worldGetRoom(gameworld, exitGetTo(exit))) == NULL) log_string("ERROR: room %s heads %s to room %s, which does not exist.", roomGetClass(room), dirGetName(i), exitGetTo(exit)); else if(can_see_exit(ch, exit)) list_one_exit(ch, exit, dirGetName(i)); } } // next, we list all of the special exits ITERATE_LIST(dir, ex_i) { if(dirGetNum(dir) == DIR_NONE) { exit = roomGetExit(room, dir); // make sure the destination exists if( (to = worldGetRoom(gameworld, exitGetTo(exit))) == NULL) log_string("ERROR: room %s heads %s to room %s, which does not exist.", roomGetClass(room), dir, exitGetTo(exit)); else if(can_see_exit(ch, exit)) list_one_exit(ch, exit, dir); } } deleteListIterator(ex_i); deleteListWith(ex_list, free); } void look_at_char(CHAR_DATA *ch, CHAR_DATA *vict) { bufferClear(charGetLookBuffer(ch)); bufferCat(charGetLookBuffer(ch), charGetDesc(vict)); // preprocess our desc before it it sent to the person hookRun("preprocess_char_desc", hookBuildInfo("ch ch", vict, ch)); // append anything that might also go onto it hookRun("append_char_desc", hookBuildInfo("ch ch", vict, ch)); // format and send it bufferFormat(charGetLookBuffer(ch), SCREEN_WIDTH, PARA_INDENT); if(bufferLength(charGetLookBuffer(ch)) == 0) send_to_char(ch, "{g%s\r\n", NOTHING_SPECIAL); else send_to_char(ch, "{g%s{n", bufferString(charGetLookBuffer(ch))); hookRun("look_at_char", hookBuildInfo("ch ch", vict, ch)); } void look_at_room(CHAR_DATA *ch, ROOM_DATA *room) { if(bitIsOneSet(charGetUserGroups(ch), "builder")) send_to_char(ch, "{c[%s] [%s] ", roomGetClass(room), terrainGetName(roomGetTerrain(room))); send_to_char(ch, "{c%s\r\n", roomGetName(room)); // make the working copy of the description, and fill it up with info bufferClear(charGetLookBuffer(ch)); bufferCat(charGetLookBuffer(ch), roomGetDesc(room)); // do all of our preprocessing of the description before we show it hookRun("preprocess_room_desc", hookBuildInfo("rm ch", room, ch)); // append anything that might also go onto it hookRun("append_room_desc", hookBuildInfo("rm ch", room, ch)); // colorize all of the edescs edescTagDesc(charGetLookBuffer(ch), roomGetEdescs(room), "{c", "{g"); // format our description bufferFormat(charGetLookBuffer(ch), SCREEN_WIDTH, PARA_INDENT); if(bufferLength(charGetLookBuffer(ch)) == 0) send_to_char(ch, "{g%s\r\n", NOTHING_SPECIAL); else send_to_char(ch, "{g%s", bufferString(charGetLookBuffer(ch))); hookRun("look_at_room", hookBuildInfo("rm ch", room, ch)); send_to_char(ch, "{n"); } //***************************************************************************** // // implementaiton of inform.h // send_to_xxx functions // //***************************************************************************** void send_outdoors(const char *format, ...) { if(format && *format) { // form the message static char buf[MAX_BUFFER]; va_list args; va_start(args, format); vsprintf(buf, format, args); va_end(args); // send it out to everyone LIST_ITERATOR *list_i = newListIterator(mobile_list); CHAR_DATA *ch = NULL; ITERATE_LIST(ch, list_i) if(charGetRoom(ch) != NULL && roomGetTerrain(charGetRoom(ch)) != TERRAIN_INDOORS && roomGetTerrain(charGetRoom(ch)) != TERRAIN_CAVERN) text_to_char(ch, buf); deleteListIterator(list_i); } } void text_to_char(CHAR_DATA *ch, const char *txt) { // if (txt && *txt && charGetSocket(ch) && // socketGetState(charGetSocket(ch)) == STATE_PLAYING) { if(txt && *txt && charGetSocket(ch)) { text_to_buffer(charGetSocket(ch), txt); socketBustPrompt(charGetSocket(ch)); } // if it's a PC or we are not in game, then // don't send the mesage to us if(!charIsNPC(ch)) try_log(charGetName(ch), txt); } void send_to_char(CHAR_DATA *ch, const char *format, ...) { if(charGetSocket(ch) && format && *format) { static char buf[MAX_BUFFER]; va_list args; va_start(args, format); vsprintf(buf, format, args); va_end(args); text_to_char(ch, buf); return; } } void send_around_char(CHAR_DATA *ch, bool hide_nosee, const char *format, ...) { static char buf[MAX_BUFFER]; va_list args; va_start(args, format); vsprintf(buf, format, args); va_end(args); LIST_ITERATOR *room_i = newListIterator(roomGetCharacters(charGetRoom(ch))); CHAR_DATA *vict = NULL; ITERATE_LIST(vict, room_i) { if(ch == vict) continue; if(hide_nosee && !can_see_char(vict, ch)) continue; text_to_char(vict, buf); } deleteListIterator(room_i); return; } void send_to_groups(const char *groups, const char *format, ...) { static char buf[MAX_BUFFER]; va_list args; va_start(args, format); vsprintf(buf, format, args); va_end(args); LIST_ITERATOR *ch_i = newListIterator(mobile_list); CHAR_DATA *ch = NULL; ITERATE_LIST(ch, ch_i) { if(!charGetSocket(ch) || !bitIsSet(charGetUserGroups(ch), groups)) continue; text_to_char(ch, buf); } deleteListIterator(ch_i); } void send_to_list(LIST *list, const char *format, ...) { if(format && *format) { // form the message static char buf[MAX_BUFFER]; va_list args; va_start(args, format); vsprintf(buf, format, args); va_end(args); // send it out to everyone LIST_ITERATOR *list_i = newListIterator(list); CHAR_DATA *ch = NULL; ITERATE_LIST(ch, list_i) text_to_char(ch, buf); deleteListIterator(list_i); } }; //***************************************************************************** // // implementation of inform.h // game commands // //***************************************************************************** // // cmd_more skips onto the next page in the socket's read buffer // COMMAND(cmd_more) { if(charGetSocket(ch)) page_continue(charGetSocket(ch)); } // // cmd_back goes back to the previous page in the socket's read buffer // COMMAND(cmd_back) { if(charGetSocket(ch)) page_back(charGetSocket(ch)); } // // lists all of the commands available to a character via some group. If the // character is not part of the group, s/he cannot see the commands COMMAND(cmd_groupcmds) { if(!*arg) send_to_char(ch, "Which group did you want to commands for: %s\r\n", bitvectorGetBits(charGetUserGroups(ch))); else if(!bitIsOneSet(charGetUserGroups(ch), arg)) send_to_char(ch, "You are not a member of user group, %s.\r\n", arg); else show_commands(ch, arg); } // // cmd_look lets a character get information about a specific thing. Objects, // characters, extra descriptions, and just about anything else that can exist // in the MUD can be looked at. // usage: look [at] <target> [[<on> <thing>] [<in> <thing>]] // // examples: // look bob look at bob // look at 2.woman look at the 2nd woman // look at sword in sheath look at the sword in the sheath // look at earrings on sue look at the earrings sue is wearing COMMAND(cmd_look) { if(!arg || !*arg) look_at_room(ch, charGetRoom(ch)); else { int found_type = FOUND_NONE; void *found = generic_find(ch, arg, FIND_TYPE_ALL, FIND_SCOPE_IMMEDIATE, FALSE, &found_type); // nothing! if(found == NULL) send_to_char(ch, "What did you want to look at?\r\n"); // is it an extra description? else if(found_type == FOUND_EDESC) { EDESC_SET *set = edescGetSet(found); BUFFER *edesc = bufferCopy(edescGetDescBuffer(found)); edescTagDesc(edesc, set, "{c", "{g"); bufferFormat(edesc, SCREEN_WIDTH, PARA_INDENT); send_to_char(ch, "{g%s", bufferString(edesc)); deleteBuffer(edesc); } // is it an item? else if(found_type == FOUND_OBJ || found_type == FOUND_IN_OBJ) look_at_obj(ch, found); // is it another character? else if(found_type == FOUND_CHAR) look_at_char(ch, found); // is it an exit? else if(found_type == FOUND_EXIT) look_at_exit(ch, found); // couldn't find anything. too bad! else send_to_char(ch, "What did you want to at?\r\n"); } } // // show a list of all commands available to the character COMMAND(cmd_commands) { if(!*arg) show_commands(ch, bitvectorGetBits(charGetUserGroups(ch))); else if(!bitIsAllSet(charGetUserGroups(ch), arg)) send_to_char(ch, "You are not a member of all user groups: %s.\r\n", arg); else show_commands(ch, arg); } //***************************************************************************** // below this line are all of the subfunctions related to the message() // function //***************************************************************************** // // Send a message out // // Converts the following symbols: // $n = ch name // $N = vict name // $m = him/her of char // $M = him/her of vict // $s = his/hers of char // $S = his/hers of vict // $e = he/she of char // $E = he/she of vict // // $o = obj name // $O = vobj name // $a = a/an of obj // $A = a/an of vobj void send_message(CHAR_DATA *to, const char *str, CHAR_DATA *ch, CHAR_DATA *vict, OBJ_DATA *obj, OBJ_DATA *vobj) { char buf[MAX_BUFFER]; int i, j; // if there's nothing to send the message to, don't go through all // the work it takes to parse the string if(charGetSocket(to) == NULL) return; for(i = 0, j = 0; str[i] != '\0'; i++) { if(str[i] != '$') { buf[j] = str[i]; j++; } else { i++; switch(str[i]) { case 'n': if(!ch) break; sprintf(buf+j, see_char_as(to, ch)); while(buf[j] != '\0') j++; break; case 'N': if(!vict) break; sprintf(buf+j, see_char_as(to, vict)); while(buf[j] != '\0') j++; break; case 'm': if(!ch) break; sprintf(buf+j, (can_see_char(to, ch) ? HIMHER(ch) : SOMEONE)); while(buf[j] != '\0') j++; break; case 'M': if(!vict) break; sprintf(buf+j, (can_see_char(to, vict) ? HIMHER(vict) : SOMEONE)); while(buf[j] != '\0') j++; break; case 's': if(!ch) break; sprintf(buf+j, (can_see_char(to, ch) ? HISHER(ch) :SOMEONE"'s")); while(buf[j] != '\0') j++; break; case 'S': if(!vict) break; sprintf(buf+j, (can_see_char(to, vict) ? HISHER(vict) :SOMEONE"'s")); while(buf[j] != '\0') j++; break; case 'e': if(!ch) break; sprintf(buf+j, (can_see_char(to, ch) ? HESHE(ch) : SOMEONE)); while(buf[j] != '\0') j++; break; case 'E': if(!vict) break; sprintf(buf+j, (can_see_char(to, vict) ? HESHE(vict) : SOMEONE)); while(buf[j] != '\0') j++; break; case 'o': if(!obj) break; sprintf(buf+j, see_obj_as(to, obj)); while(buf[j] != '\0') j++; break; case 'O': if(!vobj) break; sprintf(buf+j, see_obj_as(to, vobj)); while(buf[j] != '\0') j++; break; case 'a': if(!obj) break; sprintf(buf+j, AN(see_obj_as(to, obj))); while(buf[j] != '\0') j++; break; case 'A': if(!vobj) break; sprintf(buf+j, AN(see_obj_as(to, vobj))); while(buf[j] != '\0') j++; break; case '$': buf[j] = '$'; j++; break; default: // do nothing break; } } } // buf[0] = toupper(buf[0]); sprintf(buf+j, "\r\n"); text_to_char(to, buf); } void message(CHAR_DATA *ch, CHAR_DATA *vict, OBJ_DATA *obj, OBJ_DATA *vobj, int hide_nosee, bitvector_t range, const char *mssg) { if(!mssg || !*mssg) return; // what's our scope? if(IS_SET(range, TO_VICT) && vict && (!hide_nosee || // make sure the vict can the character, or the // object if there is no character ((!ch || can_see_char(vict, ch)) && (ch || (!obj || can_see_obj(vict, obj)))))) send_message(vict, mssg, ch, vict, obj, vobj); // characters can always see themselves. No need to do checks here if(IS_SET(range, TO_CHAR) && ch) send_message(ch, mssg, ch, vict, obj, vobj); LIST *recipients = NULL; // check if the scope of this message is everyone in the world if(IS_SET(range, TO_WORLD)) recipients = mobile_list; else if(IS_SET(range, TO_ROOM)) recipients = roomGetCharacters(charGetRoom(ch)); // if we have a list to send the message to, do it if(recipients != NULL) { LIST_ITERATOR *rec_i = newListIterator(recipients); CHAR_DATA *rec = NULL; // go through everyone in the list ITERATE_LIST(rec, rec_i) { // if we wanted to send to ch or vict, we would have already... if(rec == vict || rec == ch) continue; // skip by people who are in the game but not in the world yet if(charGetRoom(rec) == NULL) continue; if(rec == ch || (!hide_nosee || // make sure the vict can see the character, or the // object if there is no character ((!ch || can_see_char(rec, ch)) && (ch || (!obj || can_see_obj(rec, obj)))))) send_message(rec, mssg, ch, vict, obj, vobj); } deleteListIterator(rec_i); } } void mssgprintf(CHAR_DATA *ch, CHAR_DATA *vict, OBJ_DATA *obj, OBJ_DATA *vobj, int hide_nosee, bitvector_t range, const char *fmt, ...) { if(fmt && *fmt) { // form the message static char buf[MAX_BUFFER]; va_list args; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); message(ch, vict, obj, vobj, hide_nosee, range, buf); } } //***************************************************************************** // hooks //***************************************************************************** // // appends all of our exit extra descriptions to the room description. void exit_append_room_hook(BUFFER *buf, ROOM_DATA *room, CHAR_DATA *ch) { LIST *exnames = roomGetExitNames(room); LIST *ex_same = newList(); // leads to room w/ same name LIST *ex_diff = newList(); // leads to room w/ diff name LIST *ex_closed = newList(); // there is a closed door blocking us LIST_ITERATOR *ex_i = newListIterator(exnames); char *ex = NULL; // figure out our exits that lead to same-room-name // or different-room-name destinations. ITERATE_LIST(ex, ex_i) { EXIT_DATA *exit = roomGetExit(room, ex); ROOM_DATA *dest = worldGetRoom(gameworld, exitGetTo(exit)); if(dest && can_see_exit(ch, exit) && dirGetNum(ex) != DIR_NONE) { if(exitIsClosed(exit)) listPut(ex_closed, ex); else if(!strcasecmp(roomGetName(room), roomGetName(dest))) listPush(ex_same, ex); else listQueue(ex_diff, ex); } } deleteListIterator(ex_i); // append info for dirs that are blocked by doors ex_i = newListIterator(ex_closed); ITERATE_LIST(ex, ex_i) { EXIT_DATA *exit = roomGetExit(room, ex); bprintf(buf, " %s%s, you see %s.", (dirGetNum(ex) == DIR_NONE ? "At the exit " : ""), ex, (*exitGetName(exit) ? exitGetName(exit) : "a door")); } deleteListIterator(ex_i); // append info for dirs that exit to other room names ex_i = newListIterator(ex_diff); ITERATE_LIST(ex, ex_i) { ROOM_DATA *dest = worldGetRoom(gameworld, exitGetTo(roomGetExit(room, ex))); bprintf(buf, " Continuing %s would take you to %s.", ex, roomGetName(dest)); } deleteListIterator(ex_i); // and now print stuff for exits that go to rooms with the name name if(listSize(ex_same) > 0) { // if we just have a couple exits, list them off if(listSize(ex_same) <= 3) { char *list = print_list(ex_same, identity_func, NULL); bprintf(buf, " %s continues %s.", roomGetName(room), list); free(list); } // else display in bulk else { bprintf(buf, " All %sdiretions continue to %s.", (listSize(ex_same) == listSize(exnames) ? "" : "other "), roomGetName(room)); } } // clean up our garbage deleteList(ex_diff); deleteList(ex_same); deleteList(ex_closed); deleteListWith(exnames, free); } void exit_append_hook(const char *info) { // before anything, figure out some basic information like our dir and dest EXIT_DATA *exit = NULL; CHAR_DATA *ch = NULL; hookParseInfo(info, &exit, &ch); BUFFER *buf = charGetLookBuffer(ch); ROOM_DATA *room = exitGetRoom(exit); ROOM_DATA *dest = worldGetRoom(gameworld, exitGetTo(exit)); LIST *exnames = roomGetExitNames(room); LIST_ITERATOR *ex_i = newListIterator(exnames); char *ex = NULL; char *dir = NULL; // figure out which direction we came from ITERATE_LIST(ex, ex_i) { if(roomGetExit(room, ex) == exit) { dir = strdup(ex); break; } } deleteListIterator(ex_i); deleteListWith(exnames, free); // tell us where it would take us if(dest && !*exitGetDesc(exit) && !exitIsClosed(exit)) { if(!strcasecmp(roomGetName(dest), roomGetName(room))) bprintf(buf, " %s continues %s.", roomGetName(dest), dir); else bprintf(buf, " Continuing %s would take you to %s.", dir, roomGetName(dest)); } // we have a door ... gotta print its status if(exitIsClosable(exit)) { bprintf(buf, " %s%s, you see %s which is currently %s.", (dirGetNum(dir) == DIR_NONE ? "At the exit " : ""), dir, (*exitGetName(exit) ? exitGetName(exit) : "a door"), (exitIsClosed(exit) ? "closed" : "open")); } // garbage collection if(dir) free(dir); } void exit_look_hook(const char *info) { EXIT_DATA *exit = NULL; CHAR_DATA *ch = NULL; hookParseInfo(info, &exit, &ch); // the door is not closed, list off the people we can see as well if(!exitIsClosed(exit)) { ROOM_DATA *room = worldGetRoom(gameworld, exitGetTo(exit)); if(room != NULL) list_room_contents(ch, room); } } void room_look_hook(const char *info) { ROOM_DATA *room = NULL; CHAR_DATA *ch = NULL; hookParseInfo(info, &room, &ch); list_room_exits(ch, room); list_room_contents(ch, room); } //***************************************************************************** // initialization of inform.h //***************************************************************************** void init_inform(void) { // attach hooks hookAdd("append_exit_desc", exit_append_hook); // enable if you want exits to append to the end of room descs // hookAdd("append_room_desc", exit_append_room_hook); hookAdd("look_at_exit", exit_look_hook); hookAdd("look_at_room", room_look_hook); }