#include <kernel/kernel.h> #include <phantasmal/map.h> #include <phantasmal/phrase.h> #include <phantasmal/lpc_names.h> /* Mobile: structure for a sentient, not-necessarily-player critter's mind. The mobile will be attached to a body under any normal circumstances. */ inherit unq DTD_UNQABLE; inherit tag TAGGED; /* * cached vars */ static object body; /* The mobile's body -- an OBJECT of some type */ static object location; static int number; static void create(varargs int clone) { unq::create(); tag::create(); } void upgraded(void) { unq::upgraded(); tag::upgraded(); } /* * System functions * * Functions used to deal with the mobile elsewhere in the MUD */ void assign_body(object new_body) { if(!SYSTEM() && !COMMON() && !GAME()) { error("Only authorized objects can assign a mobile a new body!"); } if(body) { body->set_mobile(nil); body = nil; } if(new_body) { new_body->set_mobile(this_object()); } body = new_body; location = body->get_location(); } object get_body(void) { return body; } object get_user(void) { /* return nil, the default mobile doesn't have a user */ return nil; } void set_user(object new_user) { error("Can't set the user of this kind of mobile"); } int get_number(void) { return number; } nomask void set_number(int new_num) { if(previous_program() != MOBILED) { error("Only MOBILED can set mobile numbers!"); } number = new_num; } void notify_moved(object obj) { if(SYSTEM() || COMMON()) location = body->get_location(); } /* * Action functions * * Functions called by the user object or inherited objects to do * stuff through mobile's body */ /* * Say * * Tells something to everyone in the room */ nomask void say(string msg) { if(!SYSTEM() && !COMMON() && !GAME()) return; location->enum_room_mobiles("hook_say", ({ this_object() }), body, msg ); if (get_user()) { get_user()->message("You say: " + msg + "\r\n"); } } /* * emote() * * Sends an emote to everyone in the room. Emotes may be completely replaced * by souls eventually. (Keith Dunwoody's personal preference). */ nomask void emote(string str) { if(!SYSTEM() && !COMMON() && !GAME()) return; /* For an emote, show the user the same message everybody else sees. For instance "Bob sits still." rather than "You sits still.". */ location->enum_room_mobiles("hook_emote", ({ }), body, str); } /* * social() * * Does a social/soul action. This will be visible to everyone in the * room and may appear different to the (optional) target. The "target" * parameter should point to the target's body. */ nomask void social(string verb, object target) { if(!SYSTEM() && !COMMON() && !GAME()) return; location->enum_room_mobiles("hook_social", ({ }), body, target, verb); return; } /* * void whisper() * * object to: body of the object to whisper to. * * Whisper to someone or something. They must be in the same location as you. */ nomask void whisper(object to, string str) { object mob; if(!SYSTEM() && !COMMON() && !GAME()) return; if (to->get_location() != location) { return; } mob = to->get_mobile(); if (mob == nil) { return; } mob->hook_whisper(body, str); if (get_user()) { get_user()->message("You whisper to "); get_user()->send_phrase(to->get_brief()); get_user()->message(": " + str + "\r\n"); } location->enum_room_mobiles("hook_whisper_other", ({ this_object(), mob }), body, to); return; } /* * path_place() * * moves the object along the path, removing the object from all objects * in rem_path, and adding it to all objects in add_path, in order. * rem_path & add_path must already be in the proper order -- no checks * are performed. */ private atomic void path_place(object user, object *rem_path, object *add_path, object obj) { int i; object env, my_user; string reason; /* assume the full move can be performed. If it can't we'll throw an error, which will cause this function to be completely undone when the error occurs. Hurray for atomics! */ my_user = get_user(); for (i = 0; i < sizeof(rem_path); ++i) { env = rem_path[i]->get_location(); if ((reason = rem_path[i]->can_remove(my_user, body, obj, env)) || (reason = obj->can_get(my_user, body, env)) || (reason = env->can_put(my_user, body, obj, rem_path[i]))) { error("$" + reason); } else { /* call function in object for removing from the current room */ obj->get(body, env); rem_path[i]->remove(body, obj, env); rem_path[i]->remove_from_container(obj); env->add_to_container(obj); env->put(body, obj, rem_path[i]); } } /* now add this object to the objects in the add path in order */ for (i = 0; i < sizeof(add_path); ++i) { env = add_path[i]->get_location(); if ((reason = add_path[i]->can_put(my_user, body, obj, env)) || (reason = obj->can_get(my_user, body, add_path[i])) || (reason = env->can_remove(my_user, body, obj, add_path[i]))) { error("$" + reason); } else { obj->get(body, add_path[i]); env->remove(body, obj, add_path[i]); env->remove_from_container(obj); add_path[i]->add_to_container(obj); add_path[i]->put(body, obj, env); } } } /* * place() * * move the object obj from its current position into the object to. * obj and to must both be contained somewhere in the current room, * though not necessarily directly in it. * * returns nil on success, a reason for the failure on failure */ nomask string place(object obj, object to) { object cur_loc; object *rem_tree, *add_tree; int common; string err; object user; int i; if(!SYSTEM() && !COMMON() && !GAME()) return "Access denied!"; /* find out how many rooms this object can be removed from, ending * when we find the mobile's location. */ rem_tree = ({ }); add_tree = ({ }); cur_loc = obj->get_location(); while(cur_loc != location) { if (cur_loc == nil) { /* the object to move isn't a descendent of the mobile's current * location */ if (get_user()) { err = obj->get_brief()->to_string(get_user()); } else { err = obj->get_brief()->get_content_by_lang(LANG_englishUS); } err += " is not in this room"; return err; } rem_tree += ({ cur_loc }); cur_loc = cur_loc->get_location(); } /* do the same thing for moving the object into the container, except * include the container */ cur_loc = to; while (cur_loc != location) { if (cur_loc == nil) { /* the place to move to is not a descendent of the mob's location * so return an error */ if (get_user()) { err = to->get_brief()->to_string(get_user()); + " is not in this room"; } else { err = to->get_brief()->get_content_by_lang(LANG_englishUS); } err += " is not in this room"; return err; } add_tree += ({ cur_loc }); cur_loc = cur_loc->get_location(); } /* remove all common elements from the ends of the remove & add lists */ common = sizeof(add_tree & rem_tree); if (common != 0) { add_tree = add_tree[..(sizeof(add_tree)-common-1)]; rem_tree = rem_tree[..(sizeof(rem_tree)-common-1)]; } err = catch(path_place(get_user(), rem_tree, add_tree, obj)); if (err) { /* non-serious errors will be prefixed with a '$' -- in which case we * strip the leading $, and return the error. */ if (err[0] == '$') { return err[1..]; } else { /* serious error -- re-throw */ error(err); } } return nil; } /* * string open() * * have the mobile attempt to open the given object. * * return nil on success, or a string indicating the reason for * the failure on failure. (replace this by a phrase later?) */ nomask string open(object obj) { object link_exit, obj2; int isexit, objnum; if(!SYSTEM() && !COMMON() && !GAME()) return "Access denied!"; isexit = 0; objnum = obj->get_number(); obj2 = EXITD->get_exit_by_num(objnum); if (obj2) { isexit = 1; } if(!obj->is_openable() || (!obj->is_container())) { return "That can't be opened!"; } if(obj->is_open()) { return "That's already open."; } if(obj->is_locked()) { return "That appears to be locked."; } if (isexit) { obj->set_open(1); if (obj->get_link()!=-1) { link_exit = EXITD->get_exit_by_num(obj->get_link()); link_exit->set_open(1); } } else { obj->set_open(1); } if(!isexit && obj->get_location() != body && obj->get_location() != location) { return "You can't reach that from here."; } return nil; } /* * string close() * * have the mobile attempt to close the given object. * * return nil on success, or a string indicating the reason for * the failure on failure. (replace this by a phrase later?) */ nomask string close(object obj) { object link_exit, obj2; int isexit, objnum; if(!SYSTEM() && !COMMON() && !GAME()) return "Access denied!"; isexit = 0; objnum = obj->get_number(); obj2 = EXITD->get_exit_by_num(objnum); if (obj2) { isexit = 1; } if(!obj->is_openable() || (!obj->is_container())) { return "That can't be closed!"; } if(!obj->is_open()) { return "That's already closed."; } if(obj->get_location() != location && obj->get_location() != body && !isexit) { return "You can't reach that from here."; } if (isexit) { if (obj->get_link()!=-1) { link_exit = EXITD->get_exit_by_num(obj->get_link()); link_exit->set_open(0); } } obj->set_open(0); return nil; } /* * string move() * * move's the mobile's body through the given exit. * * return nil on success, or a string indicating the reason for * the failure on failure. (replace this by a phrase later?) */ nomask string move(int dir) { object dest; object exit; string reason; if(!SYSTEM() && !COMMON() && !GAME()) return "Access Denied!"; exit = location->get_exit(dir); if (!exit) { return "There's no exit in that direction!"; } dest = exit->get_destination(); if (!dest) { return "That exit doesn't seem to lead anywhere!"; } /* NB. I do want a = (not == ), as in other places like this*/ if (reason = location->can_leave(get_user(), body, dir)) { return reason; } if (reason = exit->can_pass(get_user(), body)) { return reason; } if (reason = dest->can_enter(get_user(), body, EXITD->opposite_direction(dir))) { return reason; } /* if (!exit->is_open()) { return "That way is closed.\n\r"; } */ location->leave(body, dir); location->remove_from_container(body); exit->pass(body); dest->add_to_container(body); dest->enter(body, EXITD->opposite_direction(dir)); return nil; } /* * string teleport() * * teleport's the mobile's body to the given destination. * * parameters: * force -- if true, forces the teleport to always succeed. * * return -- the reason why the teleport didn't succeed, or nil on success */ nomask string teleport(object dest, int force) { string reason; if(!SYSTEM() && !COMMON() && !GAME()) return "Access Denied!"; if (!force) { if (location) { if (reason = location->can_leave(get_user(), body, DIR_TELEPORT)) { return reason; } } if (reason = dest->can_enter(get_user(), body, DIR_TELEPORT)) { return reason; } } if (location) { location->leave(body, DIR_TELEPORT); location->remove_from_container(body); } dest->add_to_container(body); dest->enter(body, DIR_TELEPORT); return nil; } /* * Hook functions * * Functions which can be overridden in a derived class to respond to external * events. In the standard mobile object, these have empty definitions * * Don't bother calling these base functions, as they will never do anyting. * they're here as documentation, nothing else. */ void hook_say(object body, string message) { } void hook_emote(object body, string message) { } void hook_social(object body, object target, string verb) { } void hook_whisper(object body, string message) { } void hook_whisper_other(object body, object target) { } void hook_leave(object leaving_body, int direction) { } void hook_enter(object entering_body, int direction) { } /* * UNQ functions */ string to_unq_text(void) { string ret; int bodynum; if(!SYSTEM() && !COMMON() && !GAME()) return nil; if(body) { bodynum = body->get_number(); } else { bodynum = -1; } ret = "~mobile{\n"; ret += " ~type{" + this_object()->get_type() + "}\n"; ret += " ~name{" + body->get_brief()->get_content_by_lang(LANG_englishUS) + "}\n"; ret += " ~number{" + number + "}\n"; ret += " ~body{" + bodynum + "}\n"; if(function_object("mobile_unq_fields", this_object())) { ret += " ~data{\n"; ret += this_object()->mobile_unq_fields(); ret += " }\n"; } ret += "}\n\n"; return ret; } void from_dtd_unq(mixed* unq) { error("Override from_dtd_unq to call it!"); } /* We don't override from_dtd_unq, but we *do* provide parsing functionality for the really basic stuff like number and body. Child objects may choose to use this. It extracts the fields it uses, leaving the rest. */ static mixed mobile_from_dtd_unq(mixed* unq) { mixed *ret, *ctr; int bodynum; ctr = unq; while(sizeof(ctr) > 0) { if(!STRINGD->stricmp(ctr[0][0], "body")) { bodynum = ctr[0][1]; if(bodynum != -1) { body = MAPD->get_room_by_num(bodynum); if(!body) error("Can't find body for mobile, object #" + bodynum + "!\n"); location = body->get_location(); } else { body = nil; location = nil; } } else if(!STRINGD->stricmp(ctr[0][0], "number")) { number = ctr[0][1]; } else if(!STRINGD->stricmp(ctr[0][0], "type")) { /* Do nothing, already taken care of */ } else if(!STRINGD->stricmp(ctr[0][0], "data")) { ret = ({ ctr[0][1] }); } else if(!STRINGD->stricmp(ctr[0][0], "name")) { /* This is just a comment. Ignore it. */ } else { error("Unrecognized field in mobile structure!"); } ctr = ctr[1..]; } return ret; } string get_type(void) { error("Called get_type on /usr/common/lib/mobile without overriding!"); }