/* -*- LPC -*- */
/*
* $Locker: $
* $Id: interroom.c,v 1.2 2002/02/28 23:26:13 rhinehold Exp $
*
*
*/
/*
** interroom.c - a handler to handle being "in between" the regular rooms
** on a long road.
**
** The milestones are the "normal" rooms that people travel between when
** moving compass directions. milestone_coords[] is a cache of the
** milestone coordinates.
**
** irooms[][] is an array of arrays of rooms; one array of rooms for
** each segment of the road (one less than the number of milestone_coords).
**
** bases[] is an array of base rooms to clone for the interrooms (one for
** each gap between the milestones).
*/
/**
* The interroom handler is a scheme for handling multiple levels of
* detail along piecewise linear paths (eg, roads). The vertices of
* the path (called "milestones") behave like the rooms along a
* traditional road. However, between each milestone are a number of
* "in-between" rooms (called "interrooms" or "irooms"). Irooms are
* generally cloned as needed, while milestones are generally unique
* rooms. The irooms between two milestones are reached via exits
* with the same names as those connecting the milestones, but
* prepended with a "motion verb" (eg, "walk", giving iroom exits of
* "walk east", "walk west", etc.).<p>
*
* The interroom handler is a sort of "mini-handler". It is meant to
* be used as an inheritable, with each path having its own handler.
* Although there is no limit to the number of milestones along a
* given path, a few points should be kept in mind:
* <ul>
* <li> Each milestone can be specified only once in a given handler,
* except the first and last, which may be identical (ie, you can have
* a loop, but not a figure-eight).
*
* <li> The calculations are based on the bounding box of the path. A
* long relatively straight road (ie, north/south or east/west is
* fairly efficient, but a mostly diagonal road is the least
* efficient. So a road that's straight on one half and diagonal on
* the other half might be better broken into two paths.
* </ul>
* Each handler specifies the milestones, the rooms to clone for the
* irooms (the irooms in each segment are clones of the same room),
* the sizes of the irooms (again, the irooms in each segment are of the
* same size), and the save file to use for caching information (like
* room coordinates).
*
* In the documentation, directions along the path are sometimes referred
* to as "to the left" or "to the right". These mean "toward the lower
* numberd milestone" or "toward the higher numbered milestone",
* respectively, regardless of the compass direction of the path.
*
* @author Jeremy
* @see iroom
* @see milestone
* @see topography
*/
#include <map.h>
inherit "/std/room";
class IROOM_INFO {
string base;
int *size;
mixed *bbox; // Keeps track of bounding box of iroom segment
int sgn_n; // Keeps track of N/S direction (N = 1)
int sgn_e; // Keeps track of E/W direction (E = 1)
string *exits; // In order of direction to milestone[i], milestone[i+1]
}
int closed; // this path forms a closed loop
nosave int dbg_lvl = 0;
mixed *irooms; // an array of arrays of objects
string *milestones; // milestone room names
mixed *milestone_coords; // cache of milestone coordinates
mapping milestone_idx = ([ ]); // mapping of room name to index
class IROOM_INFO *iroom_info;
mixed *overall_bbox;
string save_file;
nosave string debugger = "jeremy";
mixed *bbox(int *coord_a, int *coord_b);
int in_bbox(int *coord_a, mixed bbox);
/**
* This method sets the creator to whom debugging messages are sent.
* @param d name of creator
*/
void set_debugger(string s) { debugger = s; }
/**
* This method returns the name of the creator to whom debugging messages
* are sent.
* @return name of creator
*/
string query_debugger() { return debugger; }
/**
* This method sets the level of debugging messages printed.
* @param l debugging level (0 is off)
*/
void set_debug_level(int l) { dbg_lvl = l; }
/**
* This method returns the current debugging level.
* @return current debug level (0 is off)
*/
int query_debug_level() { return dbg_lvl; }
/**
* @ignore
* For debugging only
*/
mixed query(string s) { return fetch_variable(s); }
/**
* @ignore
* Integer absolute value function.
*/
int abs(int i) {
return (i<0)?-i:i;
}
/**
* @ignore
* Float absolute value function.
*/
float fabs(float i) {
return (i<0)?-i:i;
}
/**
* This method sets the path of the file to use for saving data that
* shouldn't have to be recalculated very often.
* @param s pathname of save file
*/
void set_save_file(string s) { save_file = s; }
/**
* This method returns the path of the file used for saving.
* return pathname of save file
*/
string query_save_file() { return save_file; }
/**
* This method is used to set the pathnames of the milestones. Each
* milestone should inherit one of the milestone inheritables.
* @param array of filenames of the milestones for this path.
* @see milestone
*/
void set_milestones(string *s) {
int i;
milestones = copy(s);
if (milestones[0] == milestones[<1]) {
// This is a closed loop
closed = 1;
}
for (i = 0; i < sizeof(milestones) - closed; i++) {
milestone_idx[s[i]] = i;
}
milestone_coords = allocate(sizeof(milestones));
irooms = allocate(sizeof(milestones)-1);
iroom_info = allocate(sizeof(milestones)-1);
for (i = 0; i < sizeof(iroom_info); i++) {
iroom_info[i] = new(class IROOM_INFO);
iroom_info[i]->exits = allocate(2);
}
overall_bbox = 0;
} /* set_milestones() */
/**
* This method returns the array of milestone filenames.
* @return an array of milestone filenames
* @see set_milestones()
*/
string *query_milestones() { return milestones; }
/**
* This method returns the filename of the given milestone.
* @param i index number of milestone
* @returns filename of milestone i
* @see set_milestones()
*/
string query_milestone(int i) { return milestones[i]; }
/**
* @ignore
* This method is used internally to set (or update) the coordinates
* of a milestone. It also updates related info, such as the bounding
* boxes.
*/
void set_milestone_coords(int i, int *c) {
if (!arrayp(c) || (sizeof(c) != 3)) {
return;
}
if (!arrayp(milestone_coords[i]) || (milestone_coords[i][0] != c[0])
|| (milestone_coords[i][1] != c[1]) || (milestone_coords[i][2] != c[2])) {
// Update iroom_info on either side of milestone
milestone_coords[i] = copy(c);
if ((i > 0) && arrayp(milestone_coords[i-1])) {
iroom_info[i-1]->bbox = bbox(milestone_coords[i-1], milestone_coords[i]);
if (milestone_coords[i-1][0] < milestone_coords[i][0]) {
iroom_info[i-1]->sgn_n = 1;
} else if (milestone_coords[i-1][0] > milestone_coords[i][0]) {
iroom_info[i-1]->sgn_n = -1;
} else {
iroom_info[i-1]->sgn_n = 0;
}
if (milestone_coords[i-1][1] < milestone_coords[i][1]) {
iroom_info[i-1]->sgn_e = 1;
} else if (milestone_coords[i-1][1] > milestone_coords[i][1]) {
iroom_info[i-1]->sgn_e = -1;
} else {
iroom_info[i-1]->sgn_e = 0;
}
// Update overall bounding box (do I need to add in room size?)
if (!arrayp(overall_bbox)) {
overall_bbox = copy(iroom_info[i-1]->bbox);
} else {
if (iroom_info[i-1]->bbox[0][0] < overall_bbox[0][0]) {
overall_bbox[0][0] = iroom_info[i-1]->bbox[0][0];
}
if (iroom_info[i-1]->bbox[0][1] < overall_bbox[0][1]) {
overall_bbox[0][1] = iroom_info[i-1]->bbox[0][1];
}
if (iroom_info[i-1]->bbox[1][0] > overall_bbox[1][0]) {
overall_bbox[1][0] = iroom_info[i-1]->bbox[1][0];
}
if (iroom_info[i-1]->bbox[1][1] > overall_bbox[1][1]) {
overall_bbox[1][1] = iroom_info[i-1]->bbox[1][1];
}
}
}
if ((i < sizeof(iroom_info)) && arrayp(milestone_coords[i+1])) {
iroom_info[i]->bbox = bbox(milestone_coords[i], milestone_coords[i+1]);
if (milestone_coords[i][0] < milestone_coords[i+1][0]) {
iroom_info[i]->sgn_n = 1;
} else if (milestone_coords[i][0] > milestone_coords[i+1][0]) {
iroom_info[i]->sgn_n = -1;
} else {
iroom_info[i]->sgn_n = 0;
}
if (milestone_coords[i][1] < milestone_coords[i+1][1]) {
iroom_info[i]->sgn_e = 1;
} else if (milestone_coords[i][1] > milestone_coords[i+1][1]) {
iroom_info[i]->sgn_e = -1;
} else {
iroom_info[i]->sgn_e = 0;
}
// Update overall bounding box (do I need to add in room size?)
if (!arrayp(overall_bbox)) {
overall_bbox = copy(iroom_info[i]->bbox);
} else {
if (iroom_info[i]->bbox[0][0] < overall_bbox[0][0]) {
overall_bbox[0][0] = iroom_info[i]->bbox[0][0];
}
if (iroom_info[i]->bbox[0][1] < overall_bbox[0][1]) {
overall_bbox[0][1] = iroom_info[i]->bbox[0][1];
}
if (iroom_info[i]->bbox[1][0] > overall_bbox[1][0]) {
overall_bbox[1][0] = iroom_info[i]->bbox[1][0];
}
if (iroom_info[i]->bbox[1][1] > overall_bbox[1][1]) {
overall_bbox[1][1] = iroom_info[i]->bbox[1][1];
}
}
}
//printf("Setting room %d to %O\n", i, c);
}
} /* set_milestone_coords() */
/**
* @ignore
* This method returns the array of coordinates for the milestones.
*/
mixed query_milestone_coords() { return milestone_coords; }
/**
* This method is used to set the filenames of the rooms to clone for
* the irooms. Each segment of the path (ie, between two milestones)
* can use a different base room. Each base room should inherit one
* of the iroom inheritables.
* @param s array of filenames of irooms
* @see iroom
*/
void set_bases(string *s) {
int i;
if (sizeof(s) != sizeof(iroom_info)) {
error("Wrong number of bases (" + sizeof(s) + ") for given milestones (" +
sizeof(milestones) + ").\n");
return;
}
for (i = 0; i < sizeof(s); i++) {
iroom_info[i]->base = s[i];
}
} /* set_bases() */
/**
* This method is used to set the sizes of the irooms. Each segment
* of the path (ie, between two milestones) can use a different size.
* However, the distance between milestones must be an integer
* multiple of the size (in each dimension). The number of irooms in
* each segment is calculated automatically.
* @param s array of sizes of irooms (n x 1 or n x 3)
* @see iroom
*/
void set_sizes(mixed *s) {
int i;
if (sizeof(s) != sizeof(iroom_info)) {
error("Wrong number of sizes (" + sizeof(s) + ") for given milestones (" +
sizeof(milestones) + ").\n");
return;
}
for (i = 0; i < sizeof(s); i++) {
iroom_info[i]->size = allocate(3);
if (intp(s[i])) {
iroom_info[i]->size[0] = s[i];
iroom_info[i]->size[1] = s[i];
iroom_info[i]->size[2] = s[i];
} else if (arrayp(s[i])) {
iroom_info[i]->size[0] = s[i][0];
iroom_info[i]->size[1] = s[i][1];
iroom_info[i]->size[2] = s[i][2];
} else {
error("Illegal room size given (" + i + ").\n");
continue;
}
}
} /* set_sizes() */
/**
* @ignore
*/
class IROOM_INFO *query_iroom_info() { return iroom_info; }
/**
* This method determines if iroom j (between milestones i & i+1) is
* just before a milestone when going in direction dir. If dir == 0,
* the direction is towards milestone i (ie, to the "left"); if
* dir == 1, the direction is towards milestone i+1 (ie, to the "right").
* @param i milestone "to the left" of the iroom
* @param j iroom index
* @param dir direction
* @return true if the iroom is just before a milestone in direction dir
* @see query_irooms_inv()
*/
int query_endpoint(int i, int j, int dir) {
// See query_irooms_inv() for comments on dir
if ((dir && (j == sizeof(irooms[i])-1)) || (!dir && (j == 0))) {
return 1;
}
return 0;
}
/**
* This method returns all of the objects in the rooms between this
* room and the next milestone (non-inclusive). This is used when the
* player uses the exit directly to the next milestone, to inform the
* player of what [s]he passed along the way.<p>
* 'dir' is a bit of a kludge. If it is 1, the movement is to milestone
* i+1; if it is 0, the movement is to milestone i, <i>unless</i> j == -1,
* in which case the movement is to milestone i-1 (this latter case is
* only used when the current room is a milestone).
* @param i milestone "to the left" of the current iroom
* @param j iroom index (or -1 if a milestone)
* @param dir direction
* @return an array of the objects between this room and the next milestone
*/
object *query_irooms_inv(int i, int j, int dir) {
// dir==1 -> moving towards milestone i+1
// dir==0 ->
// if j == -1 -> moving towards milestone i-1 (only used by milestones)
// else -> moving towards milestone i
int k;
object *invs;
invs = ({ });
if (dir) {
if (!arrayp(irooms[i])) {
return invs;
}
for (k = j+1; k < sizeof(irooms[i]); k++) {
//tell_creator("jeremy", "Checking irooms[%d][%d]\n", i, k);
if (objectp(irooms[i][k])) {
invs += all_inventory(irooms[i][k]);
}
}
} else {
if (j == -1) {
if (closed) {
// Wraparound for closed path
i = sizeof(milestones) - 2;
} else {
i--;
}
if (!arrayp(irooms[i])) {
return invs;
} else {
j = sizeof(irooms[i]);
}
} else if (!arrayp(irooms[i])) {
return invs;
}
for (k = j-1; k >= 0; k--) {
//tell_creator("jeremy", "Checking irooms[%d][%d]\n", i, k);
if (objectp(irooms[i][k])) {
invs += all_inventory(irooms[i][k]);
}
}
}
return invs;
} /* query_irooms_inv() */
/**
* This method returns the coordinates of a rectangle that bounds the
* given points. The parameters are each arrays of three integers. The
* returned value is a 2-element array, each element being an array of
* three integers. The first element is the minimum values (ie, the
* lower-southwestern corner) and the second is the maximum values (the
* upper-northeastern corner).
* @param coord_a the coordinates of one point
* @param coord_b the coordinates of the other point
* @return the bounding box
*/
mixed *bbox(int *coord_a, int *coord_b) {
// Finds rectangle that bounds the given points. Returns a 2-element
// array of coordinates (min, max);
int c;
mixed *bbox;
bbox = allocate(2);
bbox[0] = allocate(3);
bbox[1] = allocate(3);
for (c = 0; c < 3; c++) {
if (coord_a[c] < coord_b[c]) {
bbox[0][c] = coord_a[c];
bbox[1][c] = coord_b[c];
} else {
bbox[0][c] = coord_b[c];
bbox[1][c] = coord_a[c];
}
}
return bbox;
} /* bbox() */
/**
* This method returns 1 if the given coordinates lie within the given
* bounding box. coord_a is an array of three integers (north,
* east, up). Currently, the "up" value is not checked. bbox is a
* two-element array of coordinates.
* @param coord_a the coordinates of the point to check
* @param bbox the bounding box to check the point against
* @return true if the point lies within the bounding box
*/
int in_bbox(int *coord_a, mixed bbox) {
// Note: this does not check the height (3rd coordinate)
if ((coord_a[0] < bbox[0][0]) || (coord_a[0] > bbox[1][0])
|| (coord_a[1] < bbox[0][1]) || (coord_a[1] > bbox[1][1]))
return 0;
return 1;
} /* in_bbox() */
/**
* This method calculates the number of irooms between milestone i and
* milestone i+1.
* @param i "left-hand" milestone
* @return the number of irooms between the milestones
*/
int number_of_irooms(int i) {
int n;
mixed bbox;
class IROOM_INFO ir;
ir = iroom_info[i];
bbox = ir->bbox;
if (ir->sgn_n == 0) {
n = (bbox[1][1] - bbox[0][1])/(ir->size[1]*2);
} else if (ir->sgn_e == 0) {
n = (bbox[1][0] - bbox[0][0])/(ir->size[0]*2);
} else {
// Diagonal
n = (bbox[1][1] - bbox[0][1])/(ir->size[1]*2);
if (n != (bbox[1][0] - bbox[0][0])/(ir->size[0]*2)) {
error("Interroom sizes don't match diagonal.\n");
return 0;
}
}
return n-1;
} /* number_of_irooms() */
/**
* This method clones an iroom at the requested location, and sets up
* the pertinent details.
* @param i milestone "to the left" of the iroom
* @param idx index of this iroom within its segment
* @param icoord the coordinates of the new iroom
* @return the cloned iroom
*/
object clone_iroom(int i, int idx, int *icoord) {
object iroom;
iroom = clone_object(iroom_info[i]->base);
// Set up new room
iroom->set_room_size(iroom_info[i]->size);
iroom->set_co_ord(icoord);
iroom->set_milestone_index( ({ i, idx }) );
iroom->add_exit(iroom_info[i]->exits[0], milestones[i], "road");
iroom->add_exit(iroom_info[i]->exits[1], milestones[i+1], "road");
iroom->add_milestone_dir(iroom_info[i]->exits[0]);
iroom->add_milestone_dir(iroom_info[i]->exits[1]);
// This adds the topo exits that were set up with add_topo_exits();
// this way, everything else is all set up.
iroom->add_topo_exits_int();
return iroom;
} /* clone_iroom() */
/**
* This method is called to set up a milestone. It sets up the coordinates
* and some of the exit information.
* @param ms milestone to be set up
*/
void setup_milestone(object ms) {
int *coord, i, j, idir;
string *dir;
if (undefinedp(i = milestone_idx[file_name(ms)])) {
error("This room (" + file_name(ms) + ") is not a milestone.\n");
return;
}
ms->set_milestone_index(file_name(this_object()), i);
coord = ms->query_co_ord();
if (arrayp(coord)) {
set_milestone_coords(i, coord);
} else {
if (arrayp(milestone_coords[i])) {
coord = milestone_coords[i];
} else {
coord = MAP->static_query_co_ord(file_name(ms));
set_milestone_coords(i, coord);
}
ms->set_co_ord(coord);
}
if (closed && (i == 0)) {
milestone_coords[<1] = milestone_coords[0];
}
dir = ms->query_dest_dir();
if ((i > 0) || closed) {
// Left-hand room exit
if (closed && i == 0) {
// Wraparound for closed path
j = sizeof(milestones) - 2;
} else {
j = i - 1;
}
if ((idir = member_array(milestones[j], dir)) < 0) {
// This is no longer an error, but we still need an entry.
//error("No exit to " + milestones[j] + " from " + milestones[i] +
// ".\n");
ms->add_milestone_dir(file_name(this_object()), 0);
} else {
iroom_info[j]->exits[0] = dir[idir-1];
ms->add_milestone_dir(file_name(this_object()), dir[idir-1]);
}
} else {
// Need a dummy entry
ms->add_milestone_dir(file_name(this_object()), 0);
}
if (i < sizeof(irooms)) {
// Right-hand room exit
j = i + 1;
if ((idir = member_array(milestones[j], dir)) < 0) {
// This is no longer an error, but we still need an entry.
//error("No exit to " + milestones[j] + " from " + milestones[i] +
// ".\n");
ms->add_milestone_dir(file_name(this_object()), 0);
} else {
iroom_info[i]->exits[1] = dir[idir-1];
ms->add_milestone_dir(file_name(this_object()), dir[idir-1]);
}
}
} /* setup_milestone() */
/**
* This method finds the room along the path that contains the given point.
* Obviously, things will go wrong if the rooms somehow overlap.
* @param coord_a the coordinates of the point
* @param hint_idx indicates that only this index should be checked
* (optional)
* @return the room that contains coord_a, or 0 if no matches
*/
varargs object find_room_at_coord(int *coord_a, int hint_idx) {
int i, idx, in_bbox;
int start_i, stop_i;
int *icoord = ({ 0, 0, 0 });
mixed bbox;
float slope;
class IROOM_INFO ir;
if (dbg_lvl > 0) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" coord_a: %O\n"
" hint_idx: %s\n",
file_name(this_object()), coord_a,
undefinedp(hint_idx)?"NULL":hint_idx+"");
}
// The only way I can think to do this is check every pair of
// adjacent milestones; I originally planned a binary-search type of
// check, but I think I'd still need to scan all the milestones to find
// the bounding boxes.
// First check that we're in the neighborhood
if (!in_bbox(coord_a, overall_bbox)) {
return 0;
}
in_bbox = 0;
// See if the choice has been narrowed down for us
if (undefinedp(hint_idx)) {
start_i = 0;
stop_i = sizeof(iroom_info);
} else {
start_i = hint_idx;
stop_i = hint_idx + 1;
}
for (i = start_i; i < stop_i; i++) {
ir = iroom_info[i];
bbox = ir->bbox;
if (dbg_lvl > 1) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" Checking bbox(%d,%d): %O\n",
file_name(this_object()), i, i+1, bbox);
}
if (in_bbox(coord_a, bbox)) {
if (dbg_lvl > 1) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" In bbox(%d,%d)...\n",
file_name(this_object()), i, i+1);
}
// Check that the coordinates lie on the line
if (ir->sgn_e == 0) {
// N/S (vertical) line
if (abs(coord_a[1]-milestone_coords[i][1]) < ir->size[1]) {
if (dbg_lvl > 2) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" On vline between (%d,%d)...\n",
file_name(this_object()), i, i+1);
}
break;
}
} else {
slope = to_float(milestone_coords[i][0] - milestone_coords[i+1][0])
/ to_float(milestone_coords[i][1] - milestone_coords[i+1][1]);
if (abs(slope * (coord_a[1] - milestone_coords[i+1][1]) +
milestone_coords[i+1][0] - coord_a[0]) < ir->size[0]) {
if (dbg_lvl > 2) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" On line between (%d,%d)...\n",
file_name(this_object()), i, i+1);
}
break;
}
}
}
}
if (i == stop_i) {
// Couldn't find one
if (dbg_lvl > 2) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" Couldn't find a room...\n",
file_name(this_object()));
}
return 0;
}
// We now know what rooms the iroom is between...
if (ir->sgn_n == 0) {
// East/west road
idx = abs(coord_a[1] - milestone_coords[i][1]
+ ir->size[1]*ir->sgn_e)/(ir->size[1]*2);
icoord[0] = milestone_coords[i][0];
icoord[1] = milestone_coords[i][1] + idx*ir->sgn_e*ir->size[1]*2;
} else {
idx = abs(coord_a[0] - milestone_coords[i][0]
+ ir->size[0]*ir->sgn_n)/(ir->size[0]*2);
icoord[0] = milestone_coords[i][0] + idx*ir->sgn_n*ir->size[0]*2;
if (ir->sgn_e == 0) {
icoord[1] = milestone_coords[i][1];
} else {
icoord[1] = milestone_coords[i][1] + idx*ir->sgn_e*ir->size[1]*2;
}
}
if (dbg_lvl > 2) {
tell_creator(debugger, "%s::find_room_at_coord()\n"
" idx: %O\n",
file_name(this_object()), idx);
}
if (idx == 0) {
// We hit right on a milestone
return load_object(milestones[i]);
}
if (!arrayp(irooms[i])) {
irooms[i] = allocate(number_of_irooms(i));
}
if (idx == sizeof(irooms[i])+1) {
// We hit on the other end
return load_object(milestones[i+1]);
}
// Decrement idx to index irooms[i] (since idx==0 is the milestone)
idx--;
if (!objectp(irooms[i][idx])) {
icoord[2] = (milestone_coords[i+1][2] - milestone_coords[i][2])
* idx / sizeof(irooms[i]) + milestone_coords[i][2];
// Clone new iroom
irooms[i][idx] = clone_iroom(i, idx, icoord);
}
return irooms[i][idx];
} /* find_room_at_coord() */
/**
* This method returns the room at the given index, or 0 if there is
* no such room. Generally, idx is the index of the iroom within its
* segment; the following are for special cases:<ul>
* <li> -1 means milestone i
* <li> sizeof(irooms[i]) means milestone i+1
* <li> -2 means the last iroom of this segment
* <ul>
* @param i milestone "to the left" of the room (but see above)
* @param idx index of iroom (but see above)
* @return the room at the given index
*/
object find_room_at_index(int i, int idx) {
// A couple of kludges:
// idx == -1 means milestone i
// idx == sizeof(irooms[i]) means milestone i+1
// idx == -2 means the last iroom of this segment
int *icoord = ({ 0, 0, 0 });
class IROOM_INFO ir;
if (dbg_lvl > 0) {
tell_creator(debugger, "%s::find_room_at_index()\n"
" i: %O, idx: %O\n",
file_name(this_object()), i, idx);
}
if (closed && (i == -1)) {
// Assume we wrapped around on a closed path
i = sizeof(milestones) - 2;
}
if ((i < 0) || (i >= sizeof(irooms))) {
return 0;
}
if (idx == -1) {
return load_object(milestones[i]);
}
if (!arrayp(irooms[i])) {
irooms[i] = allocate(number_of_irooms(i));
}
if (idx == sizeof(irooms[i])) {
return load_object(milestones[i+1]);
}
if (idx == -2) {
idx = sizeof(irooms[i]) - 1;
}
if ((idx < 0) || (idx >= sizeof(irooms[i]))) {
return 0;
}
if (!objectp(irooms[i][idx])) {
ir = iroom_info[i];
if (ir->sgn_n == 0) {
// East/west road
icoord[0] = milestone_coords[i][0];
icoord[1] = milestone_coords[i][1] + (idx+1)*ir->sgn_e*ir->size[1]*2;
} else {
icoord[0] = milestone_coords[i][0] + (idx+1)*ir->sgn_n*ir->size[0]*2;
if (ir->sgn_e == 0) {
icoord[1] = milestone_coords[i][1];
} else {
icoord[1] = milestone_coords[i][1] + (idx+1)*ir->sgn_e*ir->size[1]*2;
}
}
icoord[2] = (milestone_coords[i+1][2] - milestone_coords[i][2])
* idx / sizeof(irooms[i]) + milestone_coords[i][2];
// Clone new iroom
irooms[i][idx] = clone_iroom(i, idx, icoord);
}
return irooms[i][idx];
} /* find_room_at_index() */
/**
* This method determines which side(s) of bbox the given point is on,
* setting a bit in the return value as follows:<ul>
* <li> bit 0: south
* <li> bit 1: north
* <li> bit 2: west
* <li> bit 3: east
* <\ul>
* This effectively divides the plane into nine regions. If two points
* have the same region number, then their line can't cross bbox. An
* easy way to check this is to bitwise-and ("&") the query_region()
* for two points: if the result is non-zero, then the line between
* the points can't cross bbox (note however that a result of zero doesn't
* guarantee that they do cross bbox).
* @param coord the coordinates of the point
* @param bbox the bounding box
* @return an int indicating on which side of bbox the point lies
*/
int query_region(int *coord, mixed bbox) {
// This checks which side(s) of the box the point is on, setting
// a different bit for each side; the idea is that if two points are
// on the same side, they don't cross the box.
int ret;
if (coord[0] < bbox[0][0]) {
ret += 1;
} else if (coord[0] > bbox[1][0]) {
ret += 2;
}
if (coord[1] < bbox[0][1]) {
ret += 4;
} else if (coord[1] > bbox[1][1]) {
ret += 8;
}
return ret;
} /* query_region() */
/**
* This method returns the room on the path that lies along the line
* between coord_a and coord_b. Note that if the line crosses more than
* one room, the one closest to coord_a is returned.
* @param coord_a the coordinates of the first point
* @param coord_b the coordinates of the second poing
* @return the room between the two points, or 0 if no such room exists
*/
object find_room_at_crossing(int *coord_a, int *coord_b) {
int i, a, b, c, d, dist2, ret_dist2;
float e, f, det;
int *isect;
object ret;
mixed bbox, road_a, road_b;
if (dbg_lvl > 0) {
tell_creator(debugger, "%s::find_room_at_crossing()\n"
" coord_a: %O\n"
" coord_b: %O\n",
file_name(this_object()), coord_a, coord_b);
}
// First check that we're in the neighborhood
if (query_region(coord_a, overall_bbox)
& query_region(coord_b, overall_bbox)) {
return 0;
}
for (i = 0; i < sizeof(iroom_info); i++) {
// Check if the line crosses the bounding box
bbox = iroom_info[i]->bbox;
if (query_region(coord_a, iroom_info[i]->bbox)
& query_region(coord_b, iroom_info[i]->bbox)) {
//tell_creator("jeremy", "Boxes don't overlap (%d).\n", i);
continue;
}
// Find intersection of lines.
// I really wanted to avoid floats, but the sizes of some of the
// coordinates made it a necessity when multiplying them together.
road_a = allocate(3);
road_b = allocate(3);
if (iroom_info[i]->sgn_n > 0) {
road_a[0] = bbox[0][0];
road_b[0] = bbox[1][0];
} else {
road_a[0] = bbox[1][0];
road_b[0] = bbox[0][0];
}
if (iroom_info[i]->sgn_e > 0) {
road_a[1] = bbox[0][1];
road_b[1] = bbox[1][1];
} else {
road_a[1] = bbox[1][1];
road_b[1] = bbox[0][1];
}
a = coord_b[0] - coord_a[0];
b = coord_a[1] - coord_b[1];
c = road_b[0] - road_a[0];
d = road_a[1] - road_b[1];
e = to_float(a)*coord_a[1] + to_float(b)*coord_a[0];
f = to_float(c)*road_a[1] + to_float(d)*road_a[0];
det = a*d - b*c;
// The compiler apparently doesn't recognize scientific notation
if (fabs(det) < 0.0000000000001) {
//tell_creator("jeremy", "Determinant is 0.\n");
continue;
}
isect = allocate(3);
isect[0] = to_int(floor((a*f - e*c)/det + 0.5));
isect[1] = to_int(floor((e*d - b*f)/det + 0.5));
if (!in_bbox(isect, bbox) || !in_bbox(isect, bbox(coord_a, coord_b))) {
// They intersect, but not between the endpoints
continue;
}
dist2 = (isect[0]-coord_a[0])*(isect[0]-coord_a[0]) +
(isect[1]-coord_a[1])*(isect[1]-coord_a[1]);
if (objectp(ret) && (dist2 >= ret_dist2)) {
// This one is further away
continue;
}
ret = find_room_at_coord(isect, i);
if (dbg_lvl > 0) {
tell_creator(debugger, "%s::find_room_at_crossing()\n"
" find_room_at_coord() returned %O\n",
file_name(this_object()), ret);
}
ret_dist2 = dist2;
// I don't think there's any way this should happen, but I'll check anyway
if (!objectp(ret)) {
error("Couldn't find a room where there should be one!\n");
}
}
return ret;
} /* find_room_at_crossing() */
/**
* @ignore
*/
void create() {
string std_euid;
std_euid = "/secure/master"->creator_file(file_name(this_object()));
seteuid(std_euid);
do_setup++;
::create();
do_setup--;
if ( !do_setup ) {
this_object()->setup();
this_object()->reset();
}
// Keeps us from being unloaded (one of the drawbacks of making the
// handler a room; I wonder if that was a smart move...).
set_keep_room_loaded(1);
} /* create() */
/**
* @ignore
*/
void dest_me() {
if (stringp(save_file)) {
unguarded( (: save_object, save_file :) );
}
::dest_me();
} /* dest_me() */
// These are testing convenience functions
/**
* @ignore
*/
int goto_room_at_coord(int *coord) {
return this_player()->move_with_look(find_room_at_coord(coord));
}
/**
* @ignore
*/
int goto_room_at_index(int i, int j) {
return this_player()->move_with_look(find_room_at_index(i, j));
}
/**
* @ignore
*/
int goto_room_at_crossing(int *a, int *b) {
return this_player()->move_with_look(find_room_at_crossing(a, b));
}
/**
* @ignore
* This function isn't quite for public use yet.
*/
void recalc_milestones(int idx, int callouts, object tp) {
// This should initially be called with both parameters 0 (or unspecified);
// It callouts itself until it gets through all the milestones.
int *c;
string ms;
if (callouts > 10) {
error(sprintf("Too many callouts in %s:recalc_iroom_info(%d, %d)\n",
file_name(this_object()), idx, callouts));
return;
}
if (!idx && !callouts && !tp) {
// Reinitialize everything
rm(save_file);
this_object()->setup();
idx = 0;
tp = this_player();
}
ms = milestones[idx];
if (!arrayp(c = ms->query_co_ord())) {
// The call_out is necessary to give time for the coords to be set
call_out("recalc_milestones", 0, idx, callouts+1, tp);
tell_object(tp, sprintf("Waiting on %s (%d, %d)...\n",
ms, idx, callouts));
return;
}
ms->setup_milestone(ms);
tell_object(tp, sprintf("Loaded room %s at (%d, %d, %d)...\n",
ms, c[0], c[1], c[2]));
idx++;
if (idx < sizeof(milestones)) {
// This could be a direct call, but hey, this won't be run that
// often, and it makes the code more uniform.
call_out("recalc_milestones", 0, idx, 0, tp);
return;
}
tell_object(tp, "IRoom info successfully recalculated.\n");
} /* recalc_milestones() */
/**
* @ignore
*/
void clear_map_handler() {
// This is necessary if the rooms move to new coordinates, since
// the map handler doesn't seem to update itself very often.
// For right now, this has to be called manually.
int i;
string ms, directory, troom;
for (i = 0; i < sizeof(milestones); i++) {
ms = milestones[i];
// These are needed for the map handler
directory = implode( explode( ms, "/" )[ 0..<2 ], "/" );
troom = explode( ms, "/" )[ <1 ];
if ( troom[ <2.. ] == ".c" ) {
troom = troom[ 0..<3];
}
MAP->del(directory, troom);
printf("Deleting %s in %s from map handler.\n", directory, troom);
}
} /* clear_map_handler() */
/**
* @ignore
*/
int sanity_checks() {
// This does a few sanity checks, like making sure all milestones
// have coordinates and sizes, the interroom areas have bounding
// boxes, etc. It returns 1 on success.
int i, success = 1;
for (i = 0; i < sizeof(milestone_coords); i++) {
if (sizeof(milestone_coords[i]) != 3) {
printf("Milestone %d (%s) doesn't have proper coordinates: %O\n",
i, milestones[i], milestone_coords[i]);
success = 0;
}
}
for (i = 0; i < sizeof(iroom_info); i++) {
if (sizeof(iroom_info[i]->size) != 3) {
printf("Interroom segment %d has improper size: %O\n",
i, iroom_info[i]->size);
success = 0;
}
if ((sizeof(iroom_info[i]->bbox) != 2) ||
(sizeof(iroom_info[i]->bbox[0]) != 3) ||
(sizeof(iroom_info[i]->bbox[1]) != 3)) {
printf("Interroom segment %d has improper bounding box: %O\n",
i, iroom_info[i]->bbox);
success = 0;
}
}
return success;
}