#pragma strong_types
/* obj/master.c (compat default)
**
** The master is the gateway between the gamedriver and the mudlib to perform
** actions with mudlib specific effects.
** Calls to the master by the gamedriver have an automatic catch() in effect.
**
** Note that the master is loaded first of all objects. Thus it is possible,
** you shouldn't inherit an other object (as most files expect the master
** to exist), nor is the compiler able to search include files
** (read: they must be specified with full path).
**
** This master was written specifically for the 2.4.5 compat mode mudlib.
** However it contains in comments marked with 'PLAIN' explanations how
** a non-compat master differs from a compat one.
*/
#include "/sys/wizlist.h"
#include "/sys/driver_hook.h"
#include "/sys/functionlist.h"
#include "/sys/objectinfo.h"
#include "/sys/erq.h"
#define INIT_FILE "/room/init_file"
#define BACKBONE_WIZINFO_SIZE 5
#define MUDWHO_INDEX 1
/* Index of the mudwho information in the extra wizinfo */
#define SIMUL_EFUN_FILE "obj/simul_efun"
#define SPARE_SIMUL_EFUN_FILE "obj/spare_simul_efun"
#ifdef __COMPAT_MODE__
# define ADD_SLASH(p) p
# define GETUID(p) creator(p)
# define TRANSFER(a,b) transfer(a,b)
#else
# define ADD_SLASH(p) "/"+p
# define GETUID(p) getuid(p)
# define TRANSFER(a,b) funcall(symbol_function('transfer), a,b)
#endif
#ifdef MUDWHO
static void mudwho_init(int arg);
static void mudwho_connect (object ob);
static void mudwho_disconnect (object ob);
static void mudwho_shutdown();
static void mudwho_exec(object obfrom, object ob);
#else
# define mudwho_init(arg) 0
# define mudwho_connect(ob) 0
# define mudwho_disconnect(ob) 0
# define mudwho_shutdown() 0
# define mudwho_exec(a,b) 0
#endif
void save_wiz_file(); // forward
int query_player_level (string what); // forward
//===========================================================================
// Compatmode compat functions
//
// The functions here adapt a handful of efuns which differ between compat
// and plain mode. They are a subset of the functions in simul_efun.c
//===========================================================================
#ifndef __COMPAT_MODE__
//---------------------------------------------------------------------------
string file_name(object ob)
{
string rc;
rc = efun::file_name(ob);
return stringp(rc) ? rc[1..] : 0;
}
#endif /* __COMPAT_MODE__ */
//===========================================================================
// Hooks
//
// These functions are set as driver hooks to perform low-level functions
// for the mud.
//===========================================================================
//---------------------------------------------------------------------------
static string _include_dirs_hook (string include_name, string current_file)
// Return the full pathname of an include file.
//
// Argument:
// include_name: the name given in the #include <...> directive.
// current_file: the filename of the file compiled.
//
// Result:
// The full pathname of the include file.
// 0 if no such file exists.
//
// If include_name can't be found as such, the function looks in /sys
// and /room for /sys/<include_name> resp. /room/<include_name>.
{
string name, part;
int pos;
if (file_size(ADD_SLASH(include_name)) >= 0)
return include_name;
name = "sys/"+include_name;
if (file_size(ADD_SLASH(name)) >= 0)
return name;
name = "room/"+include_name;
if (file_size(ADD_SLASH(name)) >= 0)
return name;
return 0;
}
//---------------------------------------------------------------------------
static void _move_hook_fun (object item, object dest)
// Move object <item> into object <dest>.
//
// Argument:
// item: the object to be moved.
// dest: the destination for the object.
//
// The function performs all the checks for a valid move (item exists,
// destination exists, destination is not current environment, etc).
// In addition, it implements the init() protocol needed for add_action()
// to work.
{
object *others;
int i;
/* PLAIN:
if (item != this_object)
raise_error("Illegal to move other object than this_object()\n");
*/
/* PLAIN: the call to exit() is needed in compat mode only */
if (living(item) && environment(item))
{
efun::set_this_player(item);
environment(item)->exit(item);
}
efun::set_environment(item, dest);
if (living(item)) {
efun::set_this_player(item);
dest->init();
if (!item)
raise_error(sprintf("%O->init() destructed moved item\n", dest));
if (environment(item) != dest)
return;
}
others = all_inventory(dest);
others[member(others, item)] = 0;
for (i = 0; i < sizeof(others); i++)
{
if (living(others[i])) {
efun::set_this_player(others[i]);
item->init();
}
if (!item)
raise_error(sprintf("item->init() for %O (#%d) destructed moved item\n", others[i], i));
}
if (living(item)) {
efun::set_this_player(item);
filter_objects(others, "init");
}
if (living(dest) && item) {
efun::set_this_player(dest);
item->init();
}
}
//---------------------------------------------------------------------------
static mixed _load_uids_fun (mixed object_name, object prev)
// Return the uids given to an object just loaded.
//
// Argument:
// object_name: name of the object loaded
// prev : loading object.
//
// Result:
// The uid to give to the object. For objects /players/<name>/xxx
// it is "<name>", for everything else it is 1 which translates
// into the 0-uid (which is actually the backbone-uid).
//
// In general, the function can have these results (<num> is a non-zero
// number):
// "<uid>" -> uid = "<uid>", euid = "<uid>"
// ({ "<uid>", "<euid>" }) -> uid = "<uid>", euid = "<euid>"
// ({ "<uid>", not-a-string }) -> uid = "<uid>", euid = 0
//
// If strict-euids is not set, the following results are possible, too:
// <num> -> uid = 0, euid = 0
// ({ <num>, "<euid>" }) -> uid = 0, euid = "<euid>"
// ({ <num>, not-a-string }) -> uid = 0, euid = 0
{
string * parts;
parts = explode(object_name, "/");
if (sizeof(parts) > 2 && parts[0] == "players")
return parts[1];
return 1;
}
#if 0
// PLAIN: The following code fragment is used in OSB to give 'backbone'
// objects (e.g. objects from /obj, /room) the uids of the loader. Other
// objects get their uid as normal, but a 0 euid. One important effect is
// that when user A loads his own objects, the come with a valid euid, but
// other users objects come with a 0 euid.
{
string creator_name;
creator_name = get_wiz_name(object_name);
if (prev && creator_name == getuid(prev))
return geteuid(prev);
if (prev && creator_name == get_bb_uid())
return geteuid(prev);
return ({ creator_name, 1 });
}
#endif
//---------------------------------------------------------------------------
static mixed _clone_uids_fun (object blueprint, string new_name, object prev)
// Return the uids given to an object just cloned.
//
// Argument:
// blueprint: the blueprint cloned.
// new_name : name of the object cloned
// prev : cloning object.
//
// Result:
// The uids to give.
// This is either, the uid of the blueprint, or the uid of the previous
// object, or 1 (tested in this order).
//
// The possible results in general are the same as for _load_uids_fun().
{
string creator_name;
return GETUID(blueprint) || GETUID(prev) || 1;
}
#if 0
// PLAIN: The following code fragment is used in OSB to give 'backbone'
// objects (e.g. objects from /obj, /room) the uids of the cloner. Other
// objects get their uid as normal, but a 0 euid. One important effect is
// that when user A clones his own objects, the come with a valid euid, but
// other users objects come with a 0 euid.
{
string creator_name;
creator_name = get_wiz_name(new_name);
if (prev && creator_name == getuid(prev))
return geteuid(prev);
if (prev && creator_name == get_bb_uid())
return geteuid(prev);
return ({ creator_name, 1 });
}
#endif
//===========================================================================
// Initialisation
//
// These functions are called after (re)loading the master to establish the
// most basic operation parameters.
//
// The initialisation of LPMud on startup follows this schedule:
// - The gamedriver evaluates the commandline options and initializes
// itself.
// - The master is loaded, but since the driverhooks are not set yet,
// no standard initialisation lfun is called.
// - get_master_uid() is called. If the result is valid, it becomes the
// masters uid and euid.
// - inaugurate_master() is called.
// - flag() is called for each given '-f' commandline option.
// - get_simul_efun() is called.
// - the WIZLIST is read in.
// - epilog() is called. If it returns an array of strings, they are given
// one at a time as argument to preload().
// Traditionally, these strings are the filenames of the objects to
// preload, read from /room/init_file, which preload() then does.
// - The gamedriver sets up the IP communication and enters the backend
// loop.
//
// If the master is reloaded during the game, this actions are taken:
// - The master is loaded, and its initialisation lfun is called according
// to the settings of the driverhooks (if set).
// - Any auto-include string and all driverhooks are cleared.
// - get_master_uid() is called. If the result is valid, it becomes the
// masters uid and euid.
// - inaugurate_master() is called.
//
// If the master was destructed, but couldn't be reloaded, the old
// master object could be reactivated. In that case:
// - reactivate_destructed_master() is called.
// - inaugurate_master() is called.
//===========================================================================
//---------------------------------------------------------------------------
// Initialization of the master object.
//
// As the lfuns which are called to initialize objects after a load are
// defined through driver hooks, and these hooks are cleared prior to
// a master (re)load, the first function called is inaugurate_master().
// Anyway it's not very sensible to do anything earlier as the master is
// not recognized as such at that time, and so a number of (important) things
// would not work.
//
// Which lfun is called during runtime to reset the master is also depending
// on the driverhook settings. Arbitrary actions may be done on a reset.
//---------------------------------------------------------------------------
void inaugurate_master (int arg)
// Perform mudlib specific setup of the master.
//
// Argument:
// arg: 0 if the mud just started.
// 1 if the master is reactivated destructed one.
// 2 if the master is a reactivated destructed one, which lost all
// variables.
// 3 if the master was just reloaded.
//
// This function is called whenever the master becomes fully operational
// after (re)loading (it is now recognized as _the_ master).
// This doesn't imply that the game is up and running.
//
// This function has at least to set up the driverhooks to use. Also, any
// mudwho or wizlist handling has to be initialized here.
//
// Besides that, do whatever you feel you need to do,
// e.g. set_auto_include_string(), or give the master a decent euid.
{
if (!arg) {
if (previous_object() && previous_object() != this_object())
return;
set_extra_wizinfo(0, allocate(BACKBONE_WIZINFO_SIZE));
}
mudwho_init(arg);
// Wizlist simulation
if (find_call_out("wiz_decay") < 0)
call_out("wiz_decay", 3600);
set_driver_hook(
H_MOVE_OBJECT0,
unbound_lambda( ({'item, 'dest}),
({#'_move_hook_fun, 'item, 'dest })
)
);
set_driver_hook(
H_LOAD_UIDS,
unbound_lambda( ({'object_name}), ({
#'_load_uids_fun, 'object_name, ({#'previous_object}) })
)
);
set_driver_hook(
H_CLONE_UIDS,
unbound_lambda( ({ /* object */ 'blueprint, 'new_name}), ({
#'_clone_uids_fun, 'blueprint, 'new_name, ({#'previous_object}) })
)
);
set_driver_hook(H_CREATE_SUPER, "reset");
set_driver_hook(H_CREATE_OB, "reset");
set_driver_hook(H_CREATE_CLONE, "reset");
/* PLAIN: Non-compat muds like OSB use "create" or other functions
* for the above.
*/
set_driver_hook(H_RESET, "reset");
set_driver_hook(H_CLEAN_UP, "clean_up");
set_driver_hook(H_MODIFY_COMMAND,
([ "e":"east", "w":"west", "s":"south", "n":"north"
, "d":"down", "u":"up", "nw":"northwest", "ne":"northeast"
, "sw":"southwest", "se":"southeast" ]));
set_driver_hook(H_MODIFY_COMMAND_FNAME, "modify_command");
set_driver_hook(H_NOTIFY_FAIL, "What?\n");
set_driver_hook(H_INCLUDE_DIRS, #'_include_dirs_hook);
}
//---------------------------------------------------------------------------
mixed get_master_uid ()
// Return the value to be used as uid (and -euid) of a (re)loaded master.
//
// Possible results are in general:
//
// "<uid"> -> uid = "<uid>", euid = "<euid>"
//
// In non-strict-euids mode, more results are possible:
//
// 0 -> uid = 0, euid = 0
// <num> -> uid = 'default', euid = 0
//
// If your uids are in general based on filenames, it is wise to return
// a value here which can not be legally generated from any filename.
// OSB for example uses 'ze/us'.
{
return 1;
}
//---------------------------------------------------------------------------
void flag (string arg)
// Evaluate an argument given as option '-f' to the driver.
//
// Arguments:
// arg: The argument string from the option text '-f<arg>'.
// If several '-f' options are given, this function
// will be called sequentially with all given arguments.
//
// This function can be used to pass the master commands via arguments to
// the driver. This is useful when building a new mudlib from scratch.
// It is called only when the game is started.
//
// The code given implements these commands:
// '-fcall <ob> <fun> <arg>': call function <fun> in object <ob> with
// argument <arg>.
// '-fshutdown': shutdown the game immediately.
// Thus, starting the game as 'parse "-fcall foo bar Yow!" -fshutdown' would
// first do foo->bar("Yow!") and then shutdown the game.
{
string obj, fun, rest;
if (arg == "shutdown")
{
shutdown();
return;
}
if (sscanf(arg, "call %s %s %s", obj, fun, rest) >= 2)
{
write(obj+"->"+fun+"(\""+rest+"\") = ");
write(call_other(obj, fun, rest));
write("\n");
return;
}
write("master: Unknown flag "+arg+"\n");
}
//---------------------------------------------------------------------------
static mixed current_time;
/* Saved start time of epilog, updated during preloading */
string *epilog (int eflag)
// Perform final actions before opening the game to players.
//
// Arguments:
// eflag: This is the number of '-e' options given to the parser.
// Normally it is just 0 or 1.
//
// Result:
// An array of strings, which traditionally designate the objects to be
// preloaded with preload(), read from the file /room/init_file.
//
// Any other result is interpreted as 'no object to preload'.
// The resulting strings will be passed one at the time as
// arguments to preload().
{
if (eflag)
return ({});
debug_message(sprintf("Loading init file %s\n", INIT_FILE));
current_time = rusage();
current_time = current_time[0] + current_time[1];
return explode(read_file(INIT_FILE), "\n");
}
//---------------------------------------------------------------------------
void preload (string file)
// Preload a given object.
//
// Arguments:
// file: The filename of the object to preload, as returned by epilog().
//
// It is task of the epilog()/preload() pair to ensure the validity of
// the given strings (e.g. filtering out comments and blank lines).
// For preload itself a 'load_object(file)' is sufficient, but it
// should be guarded by a catch() to avoid premature blockings.
// Also it is wise to change the master's euid from master_uid to something
// less privileged for the time of the preload.
//
// You can of course do anything else with the passed strings - preloading
// is just the traditional task.
{
int last_time;
if (strlen(file) && file[0] != '#')
{
last_time = current_time;
debug_message(sprintf("Preloading file: %s", file));
load_object(file);
current_time = rusage();
current_time = current_time[0] + current_time[1];
debug_message(sprintf(" %.2f\n", (current_time - last_time) / 1000.0));
}
}
//---------------------------------------------------------------------------
//void external_master_reload ()
// Master was reloaded on external request by SIGUSR1.
//
// If the gamedriver destruct and reloads the master on external request
// via SIGUSR1, it does this by a call to this function.
// It will be called after inaugurate_master() of course.
// If you plan to do additional magic here, you're welcome.
//---------------------------------------------------------------------------
//void reactivate_destructed_master (int removed)
// Reactivate a formerly destructed master.
//
// Arguments:
// removed: True if the master was already on the list of destructed
// objects.
//
// This function is called in an formerly destructed master since a new master
// couldn't be loaded.
// This function has to reinitialize all variables at least to continue
// operation.
//---------------------------------------------------------------------------
mixed get_simul_efun ()
// Load the simul_efun object(s) and return one or more paths of it.
//
// Result:
// Either a single string with the file_name() of the simul_efun object,
// or an array of strings which has to start with that file_name().
// Return 0 if this feature isn't wanted.
//
// Note that the object(s) must be loaded by this function!
//
// When you return an array of strings, the first string is taken as path
// to the simul_efun object, and all other paths are used for backup
// simul_efun objects to call simul_efuns that are not present in the
// main simul_efun object. This allows to remove simul_efuns at runtime
// without getting errors from old compiled programs that still use the
// obsolete simul_efuns. A side use of this mechanism is to provide
// a 'spare' simul_efun object in case the normal one fails to load.
//
// If the game depends on the simul_efun object, and none could be loaded,
// an immediate shutdown should occur.
{
mixed error;
object ob;
error = catch(ob = load_object(SIMUL_EFUN_FILE));
if (!error)
{
ob->start_simul_efun();
return SIMUL_EFUN_FILE;
}
efun::write("Failed to load " + SIMUL_EFUN_FILE + ": "+error);
error = catch(ob = load_object(SPARE_SIMUL_EFUN_FILE));
if (!error)
{
ob->start_simul_efun();
return SPARE_SIMUL_EFUN_FILE;
}
efun::write("Failed to load " + SPARE_SIMUL_EFUN_FILE + ": "+error);
efun::shutdown();
return 0;
}
//===========================================================================
// Handling of player connections
//
// See also valid_exec().
//===========================================================================
//---------------------------------------------------------------------------
object connect ()
// Handle the request for a new connection.
//
// Result:
// An login object the requested connection should be bound to,
// for us a copy of obj/player.c .
//
// Note that the connection is not bound yet!
//
// The gamedriver will call the lfun 'logon()' in the login object after
// binding the connection to it. That lfun has to return !=0 to succeed.
{
object ob;
string ret;
write("Lars says: Let's get a body for your character ...");
ob = clone_object("obj/player");
write("\n");
if (ret) {
write(ret + "\n");
return 0;
}
mudwho_connect(ob);
return ob;
}
//---------------------------------------------------------------------------
void disconnect (object obj)
// Handle the loss of an IP connection.
//
// Argument:
// obj: The (formerly) interactive object (player).
//
// This called by the gamedriver to handle the removal of an IP connection,
// either because the connection is already lost ('netdeath') or due to
// calls to exec() or remove_interactive().
// The connection will be unbound upon return from this call.
{
mudwho_disconnect(ob);
}
//---------------------------------------------------------------------------
void remove_player (object player)
// Remove a player object from the game.
//
// Argument:
// player: The player object to be removed.
//
// This function is called by the gamedriver to expell remaining players
// from the game on shutdown in a polite way.
// If this functions fails to quit/destruct the player, it will be
// destructed the hard way by the gamedriver.
//
// Note: This function must not cause runtime errors.
{
catch(player->quit());
if (player)
destruct(player);
}
//---------------------------------------------------------------------------
void stale_erq (closure callback)
// Notify the loss of the erq demon.
//
// Argument:
// callback: the callback closure set for an erq request.
//
// If the erq connection dies prematurely, the driver will call this lfun for
// every pending request with set callback. This function should notify the
// originating object that the answer will never arrive.
//
// In our case, we simply reattach the default erq.
{
attach_erq_demon("", 0);
}
//===========================================================================
// Runtime Support
//
// Various functions used to implement advanced runtime features.
//===========================================================================
//---------------------------------------------------------------------------
object compile_object (string filename)
// Compile an virtual object.
//
// Arguments:
// previous_object(): The object requesting the virtual object.
// filename : The requested filename for the virtual object.
//
// Result:
// The object to serve as the requested virtual object, or 0.
//
// This function is called if the compiler can't find the filename for an
// object to compile. The master has now the opportunity to return an other
// which will then serve as if it was compiled from <filename>.
// If the master returns 0, the usual 'Could not load'-error will occur.
//
// The function will try several possible VMasters in sequence, calling
// compile_object(<filename>) in each of them, until on of them returns
// an object. The objects tried in order (if existing) are:
// 1. <path_of_filename>/vmaster.c
// 2 <path_of_filename>.c
// In both cases, only the basename part of <filename> is passed
// to compile_object().
{
object obj, room;
mixed vmaster;
string filepath;
if (filename[0] != '/') filename = "/"+filename;
filepath = implode(explode(filename,"/")[0..<2],"/");
vmaster = filepath+"/vmaster";
if (0 <= file_size(vmaster+".c"))
room = (object)vmaster->compile_object(explode(filename,"/")[<1]);
if (!room && 0 <= file_size(filepath+".c"))
room = (object)filepath->compile_object(explode(filename,"/")[<1]);
return room;
}
//---------------------------------------------------------------------------
string get_wiz_name (string file)
// Return the author of a file.
//
// Arguments:
// file: The name of the file in question.
//
// Result:
// The name of the file's author (or 0 if there is none).
//
// This function is called for maintenance of the wiz-list, to score errors
// to the right wizard.
{
string name, rest;
if (sscanf(file, "players/%s/%s", name, rest) == 2) {
return name;
}
return 0;
}
//---------------------------------------------------------------------------
// string printf_obj_name (object obj)
// Return a printable name for an object.
//
// Arguments:
// obj: The object which name is of interest.
//
// Result:
// A string with the objects name, or 0.
//
// This function is called by sprintf() to print a meaningful name
// in addition to the normal object_name().
// If this functions returns a string, the object will be printed
// as "<obj_name> (<printf_obj_name>)".
//---------------------------------------------------------------------------
void destruct_environment_of(object ob)
/* When an object is destructed, this function is called with every
* item in that room. We get the chance to save players !
*/
{
mixed error;
if (!interactive(ob))
return;
tell_object(ob, "Everything you see is disolved. Luckily, you are transported somewhere...\n");
if (error = catch(ob->move_player("is transfered#room/void"))) {
write(error);
if (error = catch(move_object(ob, "room/void"))) {
object new_player;
write(error);
new_player = clone_object("obj/player");
if (!function_exists("replace_player", new_player)) {
destruct(new_player);
return;
}
exec(new_player, ob);
if (error = catch(new_player->replace_player(ob, "room/void"))) {
write(error);
}
}
}
}
//---------------------------------------------------------------------------
void move_or_destruct(object what, object to)
/* Move <what> into <to>, or destruct <what> if that is not possible.
*
* An error in this function can be very nasty. Note that unlimited recursion
* is likely to cause errors when environments are deeply nested
*/
{
int res;
/* PLAIN: the following loop is for compat mode only */
do {
if (catch( res = TRANSFER(what, to) )) res = 5;
if ( !(res && what) ) return;
} while( (res == 1 || res == 4 || res == 5) && (to = environment(to)) );
/* PLAIN: native muds make this
if (!catch(what->move(to, 1)))
return;
*/
/*
* Failed to move the object. Therefore it is destroyed.
*/
destruct(what);
}
//---------------------------------------------------------------------------
mixed prepare_destruct (object ob)
// Prepare the destruction of the given object.
//
// Argument:
// obj : The object to destruct.
//
// Result:
// Return 0 if the object is ready for destruction, any other value
// will abort the attempt.
// If a string is returned, an error with the string as message will
// be issued.
//
// The gamedriver calls this function whenever an object shall be destructed.
// It expects, that this function cleans the inventory of the object, or
// the destruct will fail.
// Furthermore, the function could notify the former inventory objects that
// their holder is under destruction (useful to move players out of rooms which
// are updated); and it could announce mudwide the destruction(quitting) of
// players.
{
object super;
int i;
object sh, next;
/* Remove all pending shadows */
if (!query_shadowing(ob)) for (sh = shadow(ob, 0); sh; sh = next) {
next = shadow(sh, 0);
funcall(bind_lambda(#'unshadow, sh)); /* avoid deep recursion */
destruct(sh);
}
super = environment(ob);
/* PLAIN: This whole if (super) {...} block is for compat muds only */
if (super) {
mixed error;
mixed weight;
object me;
me = this_object();
set_this_object(ob);
if ( living(ob) ) {
if (error = catch(super->exit(ob),0))
write("exit"+": "+error);
}
if ( error = catch((weight = (mixed)ob->query_weight()),0) ) {
set_this_object(me);
write("query_weight"+": "+error);
return;
}
if (weight && intp(weight)) {
if (error = catch(super->add_weight(-weight),0)) {
set_this_object(me);
write("add_weight"+": "+error);
return;
}
}
set_this_object(me);
}
/* PLAIN: end of compat-mud block */
if (!super) {
object item;
while ( item = first_inventory(ob) ) {
destruct_environment_of(item);
if (item && environment(item) == ob) destruct(item);
}
} else {
while ( first_inventory(ob) )
move_or_destruct(first_inventory(ob), super);
}
if (interactive(ob))
disconnect(ob);
return 0; /* success */
}
//---------------------------------------------------------------------------
//void quota_demon (void)
// Handle quotas in times of memory shortage.
//
// This function is called during the final phase of a garbage collection if
// the reserved user area couldn't be reallocated. This function (or a called
// demon) has now the opportunity to remove some (still active) objects from
// the game. If this does not free enough memory to reallocate the user
// reserve, slow_shut_down() will be called to start Armageddon.
//
// Note: Up to now, the wizlist lacks various informations needed to detect
// the memory-hungriest wizards.
//---------------------------------------------------------------------------
//void receive_imp (string host, string msg, int port)
// Handle a received IMP message.
//
// Arguments:
// host: Name of the host the message comes from.
// msg : The received message.
// port: the port number from which the message was sent.
//
// This function is called for every message received on the IMP port.
//---------------------------------------------------------------------------
void slow_shut_down (int minutes)
// Schedule a shutdown for the near future.
//
// Argument:
// minutes: The desired time in minutes till the shutdown:
// 6, if just the user reserve has been put into use;
// 1, if the (smaller) system or even the master reserve
// has been put into use as well.
//
// The gamedriver calls this function when it runs low on memory.
// At this time, it has freed its reserve, but since it won't last long,
// the games needs to be shut down. Don't take the 'minutes' as granted
// remaining uptime, just deduce the urgency of the shutdown from it.
// The delay is to give the players the opportunity to finish quests,
// sell their stuff, etc.
// It is possible that the driver may reallocate some memory after the
// function has been called, and then run again into a low memory situation,
// calling this function again.
//
// In our case, this function loads an 'Armageddon' object and tells
// it what to do. It is the Armageddon object then which performs
// the shutdown.
//
// Technical:
// The memory handling of the gamedriver includes three reserved areas:
// user, system and master. All three are there to insure that the game
// shuts down gracefully when the memory runs out: the user area to give
// the players time to quit normally, the others to enable emergency-logouts
// when the user reserve is used up as well.
// The areas are allocated at start of the gamedriver, and released when
// no more memory could be obtained from the host. In such a case, one
// of the remaining areas is freed (so the game can continue a short
// while) and a garbagecollection is initiated.
// If the garbagecollection recycles enough memory (either true garbage
// or by the aid of the quota_demon) to reallocate the areas, all is
// fine, else the game is shut down by a call to this function.
{
filter(users(), #'tell_object,
"Game driver shouts: The memory is getting low !\n");
"obj/shut"->shut(minutes);
}
//---------------------------------------------------------------------------
varargs void notify_shutdown (string crash_reason)
// Notify the master about an immediate shutdown. If <crash_reason> is 0,
// it is a normal shutdown, otherwise it is a crash and <crash_reason>
// gives a hint at the reason.
//
// The function has the opportunity to perform any cleanup operation, like
// informing the mudwho server that the mud is down. This can not be done
// when remove_player() is called because the udp connectivity is already
// gone then.
//
// If the gamedriver shuts down normally , this is the last function called
// before the mud shuts down the udp connections and the accepting socket
// for new players.
//
// If the gamedriver crashes, this is the last function called before the
// mud attempts to dump core and exit. WARNING: Since the driver is in
// an unstable state, this function may not be able to run to completion!
// The following crash reasons are defined:
// "Fatal Error": an internal sanity check failed.
{
if (previous_object() && previous_object() != this_object())
return;
if (!crash_reason)
filter(users(), #'tell_object,
"Game driver shouts: LPmud shutting down immediately.\n");
else
filter(users(), #'tell_object,
"Game driver shouts: PANIC! "+ crash_reason+"!\n");
save_wiz_file();
mudwho_shutdown();
}
//===========================================================================
// Error Handling
//
//===========================================================================
//---------------------------------------------------------------------------
void dangling_lfun_closure ()
// Handle a dangling lfun-closure.
//
// This is called when the gamedriver executes a closure using a vanished lfun.
// A proper handling is to raise a runtime error.
//
// Technical:
// Upon replacing programs (see efun replace_program()), any existing
// lambda closures of the object are adjusted to the new environment.
// If a closure uses a lfun which vanished in the replacement process,
// the reference to this lfun is replaced by a reference to this function.
// The error will then occur when the execution of the adjusted lambda
// reaches the point of the lfun reference.
// There are two reasons for the delayed handling. First is that the
// program replacement and with it the closure adjustment happens at
// the end of a backend cycle, outside of any execution thread: noone
// would see the error at this time.
// Second, smart closures might know/recognize the program replacement
// and skip the call to the vanished lfun.
{
raise_error("dangling lfun closure\n");
}
//---------------------------------------------------------------------------
void log_error (string file, string err)
// Announce a compiler-time error.
//
// Arguments:
// file: The name of file containing the error (it needn't be an object
// file!).
// err : The error message.
//
// Whenever the LPC compiler detects an error, this function is called.
// It should at least log the error in a file, and also announce it
// to the active player if it is an wizard.
{
string name;
name = get_wiz_name(file);
if (name == 0)
name = "log";
write_file("/log/"+name, err);
}
//---------------------------------------------------------------------------
mixed heart_beat_error (object culprit, string err,
string prg, string curobj, int line)
// Announce an error in the heart_beat() function.
//
// Arguments:
// culprit: The object which lost the heart_beat.
// err : The error message.
// prg : The executed program (might be 0).
// curobj : The object causing the error (might be 0).
// line : The line number where the error occured (might be 0).
//
// Result:
// Return anything != 0 to restart the heart_beat in culprit.
//
// This function has to announce an error in the heart_beat() function
// of culprit.
// At time of call, the heart_beat has been turned off.
// A player should at least get a "You have no heartbeat!" message, a more
// advanced handling would destruct the offending object and allow the
// heartbeat to restart.
//
// Note that <prg> denotes the program actually executed (which might be
// inherited one) whereas <curobj> is just the offending object.
{
if ( query_ip_number(culprit) ) {
tell_object(
culprit,
"Game driver tells you: You have no heart beat !\n"
);
}
return 0; /* Don't restart */
}
//---------------------------------------------------------------------------
void runtime_error ( string err, string prg, string curobj, int line
, mixed culprit)
// Announce a runtime error.
//
// Arguments:
// err : The error message.
// prg : The executed program.
// curobj : The object causing the error.
// line : The line number where the error occured.
// culprit: -1 for runtime errors; the object holding the heart_beat()
// function for heartbeat errors.
//
// This function has to announce a runtime error to the active user,
// resp. handle a runtime error which occured during the execution of
// heart_beat() of <culprit>.
//
// For a normal runtime error, if the active user is a wizard, it might
// give him the full error message together with the source line; if the
// user is a is a player, it should issue a decent message ("Your sensitive
// mind notices a wrongness in the fabric of space") and could also announce
// the error to the wizards online.
//
// If the error is a heartbeat error, the heartbeat for the offending
// <culprit> has been turned off. The function itself shouldn't do much, since
// the lfun heart_beat_error() will be called right after this one.
//
// Note that <prg> denotes the program actually executed (which might be
// inherited) whereas <curobj> is just the offending object for which the
// program was executed.
{
if (this_player() && query_ip_number(this_player()))
catch( write(
query_player_level("error messages") ?
curobj ?
err +
"program: " + prg +
", object: " + curobj +
" line " + line + "\n"
:
err
:
"Your sensitive mind notices a wrongness in the fabric of space.\n"
) );
}
//===========================================================================
// Security and Permissions
//
// Most of these functions guard critical efuns. A good approach to deal
// with them is to redefine the efuns by simul_efuns (which can then avoid
// trouble prematurely) and give root objects only the permission to
// execute the real efuns.
//
// See also creator_file(), valid_read() and valid_write().
//===========================================================================
//---------------------------------------------------------------------------
int privilege_violation (string op, mixed who, mixed arg, mixed arg2)
// Validate the execution of a privileged operation.
//
// Arguments:
// op : the requestion operation (see below)
// who : the object requesting the operation (filename or object pointer)
// arg : additional argument, depending on <op>.
// arg2 : additional argument, depending on <op>.
//
// Result:
// >0: The caller is allowed for this operation.
// 0: The caller was probably misleaded; try to fix the error
// else: A real privilege violation; handle it as error.
//
// Privileged operations are:
// attach_erq_demon : Attach the erq demon to object <arg> with flag <arg2>.
// bind_lambda : Bind a lambda-closure to object <arg>.
// call_out_info : Return an array with all call_out informations.
// erq : A the request <arg4> is to be send to the
// erq-demon by the object <who>.
// input_to : Object <who> issues an 'ignore-bang'-input_to() for
// commandgiver <arg3>; the exakt flags are <arg4>.
// nomask simul_efun : Attempt to get an efun <arg> via efun:: when it
// is shadowed by a 'nomask'-type simul_efun.
// rename_object : The current object <who> renames object <arg>
// to name <arg2>.
// send_imp : Send UDP-data to host arg3 (deprecated).
// send_udp : Send UDP-data to host <arg>.
// set_auto_include_string : Set the string automatically included by
// the compiler (deprecated).
// get_extra_wizinfo : Get the additional wiz-list info for wizard <arg>.
// set_extra_wizinfo : Set the additional wiz-list info for wizard <arg>.
// set_extra_wizinfo_size : Set the size of the additional wizard info
// in the wiz-list to <arg>.
// set_driver_hook : Set hook <arg> to <arg2>.
// limited: : Execute <arg> with reduced/changed limits.
// set_limits : Set limits to <arg>.
// set_this_object : Set this_object() to <arg>.
// shadow_add_action : Add an action to function <arg> from a shadow.
// symbol_variable : Attempt to create symbol of a hidden variable
// of object <arg> with with index <arg2> in the
// objects variable table.
// wizlist_info : Return an array with all wiz-list information.
//
// call_out_info can return the arguments to functions and lambda closures
// to be called by call_out(); you should consider that read access to
// closures, mappings and pointers means write access and/or other privileges.
// wizlist_info() will return an array which holds, among others, the extra
// wizlist field. While a toplevel array, if found, will be copied, this does
// not apply to nested arrays or to any mappings. You might also have some
// sensitive closures there.
// send_imp() should be watched as it could be abused to mess up the IMP.
// The xxx_extra_wizinfo operations are necessary for a proper wizlist and
// should therefore be restricted to admins.
// All other operations are potential sources for direct security breaches -
// any use of them should be scrutinized closely.
{
/* This object and the simul_efun objects may do everything */
if (who == this_object()
|| who == find_object(SIMUL_EFUN_FILE)
|| who == find_object(SPARE_SIMUL_EFUN_FILE))
return 1;
switch(op) {
case "erq":
switch(arg) {
case ERQ_RLOOKUP:
return 1;
case ERQ_EXECUTE:
case ERQ_FORK:
case ERQ_AUTH:
case ERQ_SPAWN:
case ERQ_SEND:
case ERQ_KILL:
default:
return -1;
}
default:
return -1; /* Make this violation an error */
}
}
//---------------------------------------------------------------------------
int query_allow_shadow (object victim)
// Validate a shadowing.
//
// Arguments:
// previous_object(): the wannabe shadow
// victim : the object to be shadowed.
//
// Result:
// Return 0 to disallow the shadowing, any other value to allow it.
// Destructing the shadow or the victim is another way of disallowing.
//
// This function simply asks the victim if it denies a shadow.
{
if (object_info(victim, OINFO_MEMORY)[OIM_NO_SHADOW])
return 0;
return !victim->query_prevent_shadow(previous_object());
}
//---------------------------------------------------------------------------
int query_player_level (string what)
// Check if the player is of high enough level for several things.
//
// Argument:
// what: The 'thing' type (see below).
//
// Result:
// Return 0 to disallow, any other value to allow it.
//
// Types asked for so far are:
// "error messages": Is the player allowed to see error messages (used
// by the master)?
// (min-level: 20)
// "wizard" : Is the player is considered a wizard (used by
// the mudlib)?
// (min-level: 20)
{
int level;
if (this_player() == 0)
return 0;
level = (int)this_player()->query_level();
switch(what) {
case "wizard":
return level >= 20;
case "error messages":
return level >= 20;
}
}
//---------------------------------------------------------------------------
int valid_trace (string what)
// Check if the player is allowed to use tracing.
//
// Argument:
// what: The actual action (see below).
//
// Result:
// Return 0 to disallow, any other value to allow it.
//
// Actions asked for so far are:
// "trace": Is the user allowed to use tracing?
// "traceprefix": Is the user allowed to set a traceprefix?
// (min-level: 24 for both)
{
int level;
if (this_player() == 0)
return 0;
level = (int)this_player()->query_level();
switch(what) {
case "trace":
case "traceprefix":
return level >= 24;
}
}
//---------------------------------------------------------------------------
int valid_exec (string name, object ob, object obfrom)
// Validate the rebinding of an IP connection by usage of efun exec().
//
// Arguments:
// name : The name of the _program_ attempting to rebind the connection.
// This is not the file_name() of the object, and has no leading
// slash.
// ob : The object to receive the connection.
// obfrom: The object giving the connection away.
//
// Result:
// Return a non-zero number to allow the action,
// any other value to disallow it.
//
// Only obj/master.c and secure/login.c are allowed to do that.
{
switch(name) {
case "secure/login.c":
case "obj/master.c":
if (!interactive(ob)) {
mudwho_exec(obfrom, ob);
return 1;
}
}
return 0;
}
//---------------------------------------------------------------------------
int valid_query_snoop (object obj)
// Validate if the snoopers of an object may be revealed by usage of the
// efun query_snoop().
//
// Arguments:
// previous_object(): the asking object.
// obj : the object which snoopers are to be revealed.
//
// Result:
// Return a non-zero number to allow the action,
// any other value to disallow it.
//
// Every true wizard can test for a snoop.
{
return this_player()->query_level() >= 22;
}
//---------------------------------------------------------------------------
int valid_snoop (object snoopee, object snooper)
// Validate the start/stop of a snoop.
//
// Arguments:
// snoopee: The victim of the snoop.
// snooper: The wannabe snooper, or 0 when stopping a snoop.
//
// Result:
// Return a non-zero number to allow the action,
// any other value to disallow it.
//
// It is up to the simul_efun object to start/stop snoops.
{
/* PLAIN:
if (!geteuid(previous_object()))
return 0;
*/
if (object_name(previous_object()) == get_simul_efun())
return 1;
}
//===========================================================================
// Userids and depending Security
//
// For each object in the mud exists a string attribute which determines the
// objects rights in security-sensitive matters. In compat muds this attribute
// is called the "creator" of the object, in !compat muds the object's "userid"
// ("uid" for short).
//
// "Effective Userids" are an extension of this system, to allow the easier
// implementation of mudlib security by diffentiating between an objects
// theoretical permissions (uid) and its current permissions (euid) (some
// experts think that this attempt has failed (Heya Macbeth!)).
//
// The driver mainly implements the setting/querying of the (e)uids -- it is
// task of the mudlib to give out the right (e)uid to the right object, and
// to check them where necessary.
//
// If the driver is set to use 'strict euids', the loading and cloning
// of objects requires the initiating object to have a non-zero euid.
//
// The main use for (e)uids is for determination of file access rights, but
// you can of course use the (e)uids for other identification purposes as well.
//===========================================================================
//---------------------------------------------------------------------------
// string get_bb_uid()
// Return the string (or 0) to be used as backbone-euid.
// It is just used by process_string() only if no this_object() is present.
// If strict-euids, the function must exist and return a string.
//---------------------------------------------------------------------------
// int valid_seteuid (object obj, string neweuid)
// Validate the change of an objects euid by efun seteuid().
//
// Arguments:
// obj : The object requesting the new euid.
// neweuid: The new euid requested.
//
// Result:
// Return 1 to allow the change, any other value to disallow it.
//---------------------------------------------------------------------------
mixed valid_read (string path, string euid, string fun, object caller)
// Validate a reading/writing file operation.
//
// Arguments:
// path : The (possibly partial) filename given to the operation.
// euid : the euid of the caller (might be 0).
// fun : The name of the operation requested (see below).
// caller : The calling object.
//
// Result:
// The full pathname of the file to operate on, or 0 if the action is not
// allowed.
// You can also return 1 to indicate that the path can be used unchanged.
//
// The path finally to be used must not contain spaces or '..'s .
//
// These are the central functions establishing the various file access
// rights. In this implementation, the main work is done by
// obj/player->valid_read(<path>).
//
// valid_read() is called for these operations:
// ed_start (when reading a file)
// file_size
// get_dir
// print_file
// read_bytes
// read_file
// restore_object
// tail
{
string user;
switch ( fun ) {
case "restore_object": return 1;
case "ed_start":
if ( previous_object() && previous_object() != this_player() )
return 0;
if (!path) {
/* request for file with last error */
mixed *error;
error =
get_error_file(({string})this_player()->query_real_name());
if (!error || error[3]) {
write("No error.\n");
return 0;
}
write(error[0][1..]+" line "+error[1]+": "+error[2]+"\n");
return ADD_SLASH(error[0]);
}
if (path[0] != '/')
path = "/"+path;
case "read_file":
case "read_bytes":
case "file_size":
case "get_dir":
case "do_rename":
if (caller == this_object()) return 1;
case "tail":
case "print_file":
case "make_path_absolute": /* internal use, see below */
set_this_object(caller);
if( this_player() && query_ip_number(this_player()) ) {
path = (string)this_player()->valid_read(path);
if (!stringp(path)) {
write("Bad file name.\n");
return 0;
}
return ADD_SLASH(path);
}
path = (string)"obj/player"->valid_read(path);
if (stringp(path))
return ADD_SLASH(path);
return 0;
}
/* if a case failed to return a value or the caller function wasn't
* recognized, we come here.
* The default returned zero indicates deny of access.
*/
}
//---------------------------------------------------------------------------
mixed valid_write (string path, string euid, string fun, object caller)
// Validate a writing file operation.
//
// Arguments:
// path : The (possibly partial) filename given to the operation.
// euid : the euid of the caller (might be 0).
// fun : The name of the operation requested (see below).
// caller : The calling object.
//
// Result:
// The full pathname of the file to operate on, or 0 if the action is not
// allowed.
// You can also return 1 to indicate that the path can be used unchanged.
//
// The path finally to be used must not contain spaces or '..'s .
//
// These are the central functions establishing the various file access
// rights. In this implementation, the main work is done by
// obj/player->valid_write(<path>).
//
// valid_write() is called for these operations:
// ed_start (when writing a file)
// rename_from (for each the old name of a rename())
// rename_to (for the new name of a rename())
// mkdir
// save_object
// objdump
// opcdump
// remove_file
// rmdir
// write_bytes
// write_file
{
string user;
if (path[0] == '/' && path != "/")
path = path[1..];
switch ( fun ) {
case "objdump":
if (path == "OBJ_DUMP") return path;
return 0;
case "opcdump":
if (path == "OPC_DUMP") return path;
return 0;
case "save_object":
if ( user = GETUID(previous_object()) ) {
if ( path[0 .. strlen(user)+7] == "players/" + user
&& sscanf(path, ".%s", user) == 0)
return ADD_SLASH(path);
} else {
user = efun::object_name(previous_object());
#ifndef __COMPAT_MODE__
user = user[1..];
#endif
if ( user[0..3] == "obj/"
|| user[0..4] == "room/"
|| user[0..3] == "std/" )
return ADD_SLASH(path);
}
return 0; /* deny access */
default:
return 0; /* deny access */
case "write_file":
if (caller == this_object()) return 1;
if (path[0..3] == "log/"
&& !( sizeof(regexp(({path[4..33]}), "/"))
|| path[4] == '.'
|| strlen(path) > 34
) ) {
return ADD_SLASH(path);
}
break;
case "ed_start":
if (path[0] != '/')
path = "/"+path;
break;
case "rename_from":
case "rename_to":
if (( efun::object_name(caller) == SIMUL_EFUN_FILE
|| efun::object_name(caller) == SPARE_SIMUL_EFUN_FILE)
&& path[0..3] == "log/"
&& !( sizeof(regexp(({path[4..33]}), "/"))
|| path[4] == '.'
|| strlen(path) > 34
) ) {
return 1;
}
case "mkdir":
case "rmdir":
case "write_bytes":
case "remove_file":
if (caller == this_object()) return 1;
}
set_this_object(caller);
if( this_player() && query_ip_number(this_player()) )
{
path = (string)this_player()->valid_write(path);
if (!stringp(path)) {
write("Bad file name.\n");
return 0;
}
return ADD_SLASH(path);
}
path = (string)"obj/player"->valid_write(path);
if (stringp(path))
return ADD_SLASH(path);
return 0;
}
//===========================================================================
// ed() Support
//
//===========================================================================
//---------------------------------------------------------------------------
string make_path_absolute (string str)
// Absolutize a relative filename given to the editor.
//
// Argument:
// str : The relative filename (without leading slash).
//
// Result:
// The full pathname of the file to use.
// Any non-string result will act as 'bad file name'.
{
return valid_read(str,0,"make_path_absolute", this_player());
}
//---------------------------------------------------------------------------
int save_ed_setup (object who, int code)
// Save individual settings of ed for a wizard.
//
// Arguments:
// who : The wizard using the editor.
// code: The encoded options to be saved.
//
// Result:
// Return 0 on failure, any other value for success.
//
// This function has to save the given integer into a safe place in the
// realm of the given wizard, either a file, or in the wizard itself.
//
// Be aware of possible security breaches: under !compat, a write_file()
// should be surrounded by a temporary setting of the masters euid to
// that of the wizard.
{
string file;
if (!intp(code))
return 0;
file = "/players/" + lower_case((string)who->query_name()) + "/.edrc";
rm(file);
return write_file(file, code + "");
}
//---------------------------------------------------------------------------
int retrieve_ed_setup (object who)
// Retrieve individual settings of ed for a wizard.
//
// Arguments:
// who : The wizard using the editor.
//
// Result:
// The encoded options retrieved (0 if there are none).
{
string file;
int code;
file = "/players/" + lower_case((string)who->query_name()) + "/.edrc";
if (file_size(file) <= 0)
return 0;
sscanf(read_file(file), "%d", code);
return code;
}
//---------------------------------------------------------------------------
string get_ed_buffer_save_file_name (string file)
// Return a filename for the ed buffer to be saved into.
//
// Arguments:
// this_player(): The wizard using the editor.
// file : The name of the file currently in the buffer.
//
// Result:
// The name of the file to save the buffer into, or 0.
//
// This function is called whenever a wizard is destructed/goes netdeath
// while editing. Using this function, his editing is not done in vain.
{
string *file_ar;
string path;
path = "/players/"+this_player()->query_real_name()+"/.dead_ed_files";
if (file_size(path) == -1) {
mkdir(path);
}
file_ar=explode(file,"/");
file=file_ar[sizeof(file_ar)-1];
return path+"/"+file;
}
//===========================================================================
// parse_command() Support (!compat, SUPPLY_PARSE_COMMAND defined)
//
// LPMud has a builtin support for parsing complex commands.
// It does this by requestion several types of ids from the objects.
// The same queried functions are also in the master to provide decent
// defaults, especially for generic ids like 'all the blue ones'.
//
// Each of the functions has to return an array of strings (with the exception
// of parse_command_all_word), each string being one of the ids for that type
// of id.
//
// The whole parsing has a preference for the english language, so the
// the code for parsing english constructs is given as well.
//===========================================================================
//---------------------------------------------------------------------------
string *parse_command_id_list ()
// Return generic singular ids.
{
return ({ "one", "thing" });
}
//---------------------------------------------------------------------------
string *parse_command_plural_id_list ()
// Return generic plural ids.
{
return ({ "ones", "things", "them" });
}
//---------------------------------------------------------------------------
string *parse_command_adjectiv_id_list ()
// Return generic adjective ids.
// If there are none (like here), return some junk which is likely never
// typed.
{
return ({ "iffish" });
}
//---------------------------------------------------------------------------
string *parse_command_prepos_list ()
// Return common prepositions.
{
return ({ "in", "on", "under", "behind", "beside" });
}
//---------------------------------------------------------------------------
string parse_command_all_word()
// Return the one(!) 'all' word.
{
return "all";
}
#ifdef MUDWHO
//===========================================================================
// Mudwho support functions
//
// Mudwho was (is?) a system of interconnected servers which keep track
// who is in which mud. Once quite popular, it is now propably defunct.
// But anyway, here is the code once used by Nightfall.
//===========================================================================
#define MUDWHO_SERVER "134.2.62.161"
#define MUDWHO_PORT 6888
/* IP and port of the closest mudwho server */
#define MUDWHO_NAME "TestNase"
#define MUDWHO_PASSWORD "TestPassword"
/* Participation in the mudwho service required registration */
#define MUDWHO_REFRESH_TIME 100
#define QUOTE_PERCENT(s) (implode(explode(s, "%"), "%%"))
private string mudwho_ping;
private mapping mudwho_info = ([]);
private closure send_mudwho_info;
//---------------------------------------------------------------------------
static void mudwho_init (int arg)
{
send_mudwho_info
= lambda( ({'key, 'info})
, ({#'send_imp, MUDWHO_SERVER, MUDWHO_PORT, 'info }));
if (!arg)
{
send_imp(MUDWHO_SERVER, MUDWHO_PORT
, sprintf("U\t%.20s\t%.20s\t%.20s\t%:010d\t0\t%.25s"
, MUDWHO_NAME, MUDWHO_PASSWORD, MUDWHO_NAME
, time(), __VERSION__)
);
get_extra_wizinfo(0)[MUDWHO_INDEX] = mudwho_info;
}
else
{
mudwho_info = get_extra_wizinfo(0)[MUDWHO_INDEX];
}
mudwho_ping = sprintf("M\t%.20s\t%.20s\t%.20s\t%%:010d\t0\t%.25s"
, QUOTE_PERCENT(MUDWHO_NAME)
, QUOTE_PERCENT(MUDWHO_PASSWORD)
, QUOTE_PERCENT(MUDWHO_NAME)
, QUOTE_PERCENT(__VERSION__)
);
if (find_call_out("send_mudwho_info") < 0)
call_out("send_mudwho_info", MUDWHO_REFRESH_TIME);
}
//---------------------------------------------------------------------------
static void mudwho_shutdown()
{
send_imp(MUDWHO_SERVER, MUDWHO_PORT
, sprintf("D\t%.20s\t%.20s\t%.20s"
, MUDWHO_NAME, MUDWHO_PASSWORD, MUDWHO_NAME));
}
//---------------------------------------------------------------------------
static void mudwho_connect (object ob)
{
mudwho_info[ob] = sprintf("A\t%.20s\t%.20s\t%.20s\t%:010d\t0\tlogin"
, MUDWHO_NAME, MUDWHO_PASSWORD, MUDWHO_NAME
, explode(object_name(ob), "#")[<1]
+ "@" MUDWHO_NAME
, time()
);
}
//---------------------------------------------------------------------------
static void send_mudwho_info()
{
send_imp(MUDWHO_SERVER, MUDWHO_PORT, sprintf(mudwho_ping, time()));
walk_mapping(mudwho_info, send_mudwho_info);
call_out("send_mudwho_info", MUDWHO_REFRESH_TIME);
}
//---------------------------------------------------------------------------
static void adjust_mudwho (object ob)
{
if (ob && interactive(ob) && mudwho_info[ob][<5..] == "login")
{
mudwho_info[ob][<5..] = ob->query_real_name()[0..24];
send_imp(MUDWHO_SERVER, MUDWHO_PORT, mudwho_info[ob]);
}
}
//---------------------------------------------------------------------------
static void mudwho_exec (object obfrom, object ob)
{
if (interactive(obfrom))
{
mudwho_info[ob] = mudwho_info[obfrom];
efun::m_delete(mudwho_info, obfrom);
call_out("adjust_mudwho", 0, ob);
}
}
//---------------------------------------------------------------------------
static void mudwho_disconnect (object ob)
{
send_imp(MUDWHO_SERVER, MUDWHO_PORT
, "Z\t"+implode(explode(mudwho_info[ob], "\t")[1..4], "\t"));
}
//---------------------------------------------------------------------------
#endif /* MUDWHO */
//===========================================================================
// 2.4.5-Lib related functions
//===========================================================================
//---------------------------------------------------------------------------
static void wiz_decay()
/* Decay the 'worth' entry in the wizlist
*/
{
mixed *wl;
int i;
wl = wizlist_info();
for (i=sizeof(wl); i--; ) {
set_extra_wizinfo(wl[i][WL_NAME], wl[i][WL_EXTRA] * 99 / 100);
}
call_out("wiz_decay", 3600);
}
//---------------------------------------------------------------------------
void save_wiz_file()
/* Save the wizlist file.
*/
{
rm("/WIZLIST");
write_file(
"/WIZLIST",
implode(
map(wizlist_info(),
lambda(({'a}),
({#'sprintf, "%s %d %d\n",
({#'[, 'a, WL_NAME}),
({#'[, 'a, WL_COMMANDS}),
({#'[, 'a, WL_EXTRA})
})
)
), ""
)
);
}
//---------------------------------------------------------------------------
int verify_create_wizard (object ob)
/* This function is called for a wizard that has dropped a castle.
* The argument is the file name of the object that called create_wizard().
* Verify that this object is allowed to do this call.
*/
{
int dummy;
if (sscanf(object_name(ob), "room/port_castle#%d", dummy) == 1
|| sscanf(object_name(ob), "global/port_castle#%d", dummy) == 1)
return 1;
return 0;
}
//---------------------------------------------------------------------------
string master_create_wizard(string owner, string domain, object caller)
/* Create a home dritectory and a castle for a new wizard. It is called
* automatically from create_wizard(). We don't use the 'domain' info.
* The create_wizard() efun is not really needed any longer, as a call
* could be done to this function directly.
*
* This function can create directories and files in /players. It is
* garded from calls from the wrong places.
*/
{
string def_castle;
string dest, castle, wizard;
object player;
/* find_player() is a simul_efun. Resolve it at run time. */
player = funcall(symbol_function('find_player),owner);
if (!player)
return 0;
if (!verify_create_wizard(caller)) {
tell_object(player, "That is an illegal attempt!\n");
return 0;
}
if (caller != previous_object()) {
tell_object(player, "Faked call!\n");
return 0;
}
wizard = "/players/" + owner;
castle = "/players/" + owner + "/castle.c";
if (file_size(wizard) == -1) {
tell_object(player, "You now have a home directory: " +
wizard + "\n");
mkdir(wizard);
}
dest = object_name(environment(player));
def_castle = "#define NAME \"" + owner + "\"\n#define DEST \"" +
dest + "\"\n" + read_file("/room/def_castle.c");
if (file_size(castle) > 0) {
tell_object(player, "You already had a castle !\n");
} else {
/* The master object can do this ! */
if (write_file(castle, def_castle)) {
tell_object(player, "You now have a castle: " + castle + "\n");
if (!write_file("/room/init_file", castle[1..] + "\n"))
tell_object(player, "It couldn't be loaded automatically!\n");
} else {
tell_object(player, "Failed to make castle for you!\n");
}
}
return castle;
}
/****************************************************************************/