From: demon@sequent.com (Dave Richards)
Subject: Transfer Portals: An example
Organization: Sequent Computer Systems Inc.
Date: Sat, 15 Aug 92 04:14:32 GMT


I have been frustrated lately in trying to understand how objects work in
LPC.  I have asked lots of wizards on various MUDs to explain some of the
specifics of how things work with objects.  It's amazing how few can
really explain (I wish I had found one though because it would have saved
me LOTS of debugging time).  And I don't mean to criticize here because it's
neat to see so many wizards who have little or no computer experience
writing code for MUDs.  However, understanding the specifics makes it
easier to code and debug complex objects.

I recently needed an object called a Transfer Portal for my area on
Nightmare.  I wanted to make my area magically (as opposed to physically)
attached to the main MUD.  These portals work how I envision Katherine
Kurtz's portals work in her books.  Pragmatically, however, they were
designed so that portals can span multiple castles in the same MUD and a
rich portal network could be created.

I offer this as an example of a useful object with comments on some of the
not always well understand parts of LPC.  If you know more than me in LPC
(quite possible) and I have made some bad assumption or led folks astray please
comment here.  This module works on a 2.4.5 mudlib with 3.0 driver.  I
did however add 2 player.c functions; query_wizard() and add_spell_points().
These should be easy to duplication or port to your MUD.  It may not be perfect
but I am testing it now and am happy with the results.

I hope the comments help make things clear to other.

	Dave Richards - Cynosure @ Nightmare

/*
 * Dave Richards - Cynosure
 */

/*
 * Transfer Portals
 *
 * This module implements a network of transfer portals.  A transfer portal
 * is an object that allows players to translocate via magical means.  To
 * operate a transfer portal, a player stands on one portal and visualizes
 * a destination portal.  If the player has enough spell points, and the
 * current portal is operational, and the destination is part of the network,
 * and its portal is operational, then the player is teleported to the
 * destination room, (i.e. the environment() of the portal).
 */

/*
 * Configurable parameters
 */
#define	PORTAL_OBJECT	"/players/cynosure/portal"
#define	PORTAL_NODES	2
#define	PORTAL_COST	5

#define	QA

#define	FALSE		0
#define	TRUE		1

string *portal_name;
string *portal_room;
status portal_operational;

/*
 * clone_network() is called from the castle's reset() function to initialize
 * the portal network at reboot or reload.  It clones and installs portals in
 * each room of the portal network.  N.B. This function is called from the
 * master portal object, not one of the clones.  This is important because if
 * the network is changed and portal.c reloaded/updated we want to install
 * the new network, not an inherited one.
 */
void
clone_network()
{
	int i;
	object ob1, ob2;

	for (i = 0; i < PORTAL_NODES; i++) {
		/*
		 * If a room cannot be found (i.e. is not loaded) or it does not
		 * have a portal in its inventory, then we go ahead and attempt
		 * to install a new portal.
		 */
		ob1 = find_object(portal_room[i]);
		if (!ob1 || !present("portal", ob1)) {
			/*
			 * N.B we catch() the move_object() because it is
			 * possible that the object portal_room[i] does not
			 * exist.  This could occur if there were an error in
			 * the network database.  Log an error so a wizard will
			 * know to fix it.
			 */
			ob2 = clone_object(PORTAL_OBJECT);
			if (catch(move_object(ob2, portal_room[i]))) {
				destruct(ob2);
				log_file("portal", "clone_network: '" +
					portal_room[i] + "'");
			}
		}
	}
}

/*
 * Initialize the network database.
 */
void
reset(status flag)
{
	if (!flag) {
		/*
		 * portal_name is an array of destionation names.  When a player
		 * visualizes a destination, that destination is searched for in
		 * this array.
		 */
		portal_name = allocate(PORTAL_NODES);
		portal_name[0] = "holy sanctum";
		portal_name[1] = "castle";

		/*
		 * For each destination name above, portal_room specifies
		 * which room the player will end up in if everything is cool.
		 * portal_name and portal_room are therefore tied to each
		 * other as an association.
		 */
		portal_room = allocate(PORTAL_NODES);
		portal_room[0] = "/room/church";
		portal_room[1] = "/players/cynosure/castle";

		/*
		 * All portals are operational when cloned.  This happens
		 * through inheritance.  It requires a wizard to make a portal
		 * not operational.
		 */
		portal_operational = TRUE;
	}
}

/*
 * lookup_name() searches portal_name for the specified destination.  If it
 * is found, its associated room string is returned.  N.B. because of the
 * way lookup_name() is called from visualize() it will always execute in
 * the context of the master portal object (as opposed to the clone that is
 * actually handling the verb), it is therefore always using the most recently
 * loaded network database.
 */
string
lookup_name(string name)
{
	int i;

	i = member_array(name, portal_name);
	if (i == -1)
		return ((string)0);
	return (portal_room[i]);
}

/*
 * visualize() actually does the real work for verb_visualize().  It is easier
 * to break it out like this because of all the checking that goes on, (the ifs
 * kept marching off the screen).  If a player has at leat PORTAL_COST spell
 * points, and the current portal is operational, and the destination name is
 * in the network database, and the destination room is loaded and contains a
 * portal, and the destination portal is operational, then the player is moved
 * from the current room to the destination room.  visualize() returns TRUE
 * if there nothing more to do and FALSE if verb_visualize() needs to tell the
 * player that it didn't work.
 */
status
visualize(string s)
{
	object ob1, ob2, ob3;
	string room;

	/*
	 * If the player does not have PORTAL_COST spell points then give him
	 * some excuse and return.
	 */
	ob1 = this_player();
	if (ob1->query_spell_points() < PORTAL_COST) {
		write("You are unable to focus.\n");
		return (TRUE);
	}

	/*
	 * If the source portal is not operational then return.
	 */
	if (!portal_operational)
		return (FALSE);

	/*
	 * If the specified destination name is not in the network database
	 * then return.
	 */
	room = (string)PORTAL_OBJECT->lookup_name(s);
	if (!room)
		return (FALSE);

	/*
	 * If the destination room is not loaded then there is no need to
	 * check for the portal, because it won't be there.  Just return.
	 */
	ob2 = find_object(room);
	if (!ob2)
		return (FALSE);

	/*
	 * If the destination room does not have a transfer portal then
	 * return.
	 */
	ob3 = present("portal", ob2);
	if (!ob3)
		return (FALSE);

	/*
	 * If the destination portal is not operational then return.
	 */
	if (!ob3->query_portal_operational())
		return (FALSE);

	/*
	 * If the player is trying to teleport to his current location then
	 * tell him he is being stupid and return.
	 */
	if (environment(ob1) == ob2) {
		write("You are already here.\n");
		return (TRUE);
	}

	/*
	 * Subtract out PORTAL_COST spell points from the player and move
	 * him to the new location.
	 */
	write("You feel a moment of vertigo...\n");
	ob1->add_spell_points(0 - PORTAL_COST);
	ob1->move_player("X", ob2);

	return (TRUE);
}

/*
 * Register verbs
 */
void
init()
{
	add_action("verb_disable",   "disable");
	add_action("verb_enable",    "enable");
	add_action("verb_query",     "query");
	add_action("verb_visualize", "visualize");
}

/*
 * Invisible objects are such a pain, so if the player is a wizard then
 * return something useful so the wizard can decide to destruct the
 * portal if necessary.  Otherwise, it is invisible.
 */
string
short()
{
	if (this_player()->query_wizard())
		return "An (invisible) Transfer Portal";
	else
		return (string)0;
}

/*
 * It's only invisible if you don't know it's there.  If you 'look at portal'
 * it will display 'A Transfer Portal.'
 */
void
long()
{
	write("A Transfer Portal.\n");
}

/*
 * Transfer Portals answer to 'portal'
 */
status
id(string s)
{
	return (s == "portal");
}

/*
 * Return the operational status of a portal
 */
status
query_portal_operational()
{
	return (portal_operational);
}

/*
 * Display the current operational status of a portal
 */
void
portal_status()
{
	if (portal_operational)
		write("This Transfer Portal is operational.\n");
	else
		write("This Transfer Portal is NOT operational.\n");
}

/*
 * Disable a portal (for wizards only)
 */
status
verb_disable(string s)
{
	if (!id(s) || !this_player()->query_wizard())
		return (FALSE);
	portal_operational = FALSE;
	portal_status();
	return (TRUE);
}

/*
 * Enable a portal (for wizards only)
 */
status
verb_enable(string s)
{
	if (!id(s) || !this_player()->query_wizard())
		return (FALSE);
	portal_operational = TRUE;
	portal_status();
	return (TRUE);
}

/*
 * Query the operational status of a portal (for wizards only)
 */
status
verb_query(string s)
{
	if (!id(s) || !this_player()->query_wizard())
		return (FALSE);
	portal_status();
	return (TRUE);
}

/*
 * Attempt to use a portal.  'visualize <destination>'
 */
status
verb_visualize(string s)
{
#ifdef QA
	/*
	 * Don't allow non-wizards to use the portals while they are
	 * being tested.
	 */
	if (!this_player()->query_wizard())
		return (FALSE);
#endif

	/*
	 * Call visualize() to do the real work.  If it return FALSE then
	 * there is some sort of problem so report an error to the player.
	 */
	if (!visualize(s))
		write("You are unable to visualize " + s + ".\n");

	return (TRUE);
}
Newsgroups: rec.games.mud.lp