#include <kernel/kernel.h>
#include <phantasmal/log.h>
#include <phantasmal/map.h>
#include <phantasmal/lpc_names.h>
#include <gameconfig.h>
#include <type.h>
/* The Mapd keeps track of room objects, their groupings and their
relationship to each other. It also loads in rooms in
fundamentally data-based formats rather than code-based formats and
turns them into proper room objects. */
/* room_objects keeps track of rooms by object name and certain aliases */
private mapping room_objects;
/* which unq tags are mapped to which lpc code */
private mapping tag_code;
private object default_binding_handler;
private object room_dtd, bind_dtd;
private int initialized;
/* Rooms that haven't yet fully resolved... */
private object* unresolved_rooms;
#define PHR(x) PHRASED->new_simple_english_phrase(x)
#define ROOM_BIND_FILE "/usr/common/sys/room_binder.unq"
/* Prototypes */
object get_room_by_num(int num);
int* rooms_in_zone(int zone);
private int assign_room_to_zone(int num, object room, int zone);
void upgraded(varargs int clone);
static void create(varargs int clone) {
room_objects = ([ ]);
tag_code = ([ ]);
default_binding_handler = nil;
upgraded(clone);
initialized = 0;
}
void upgraded(varargs int clone) {
if(!SYSTEM() && previous_program() != MAPD)
return;
if(!find_object(UNQ_PARSER))
compile_object(UNQ_PARSER);
if(!find_object(UNQ_DTD))
compile_object(UNQ_DTD);
if(!unresolved_rooms)
unresolved_rooms = ({ });
}
void init(string room_dtd_str, string bind_dtd_str) {
int ctr;
mixed *unq_data;
string bind_file, tag, file;
if(!SYSTEM())
return;
if(!initialized) {
room_dtd = clone_object(UNQ_DTD);
room_dtd->load(room_dtd_str);
/* read the binder file */
bind_dtd = clone_object(UNQ_DTD);
bind_dtd->load(bind_dtd_str);
bind_file = read_file(ROOM_BIND_FILE);
if (!bind_file)
error("Cannot read binder file " + ROOM_BIND_FILE + "!");
unq_data = UNQ_PARSER->unq_parse_with_dtd(bind_file, bind_dtd);
if(!unq_data)
error("Cannot parse binder text in MAPD::init()!");
if (sizeof(unq_data) % 2)
error("Odd sized unq chunk in MAPD::init()!");
for (ctr = 0; ctr < sizeof(unq_data); ctr += 2) {
if (STRINGD->stricmp(unq_data[ctr],"bind"))
error("Not a code/tag binding in MAPD::init()!");
if (typeof(unq_data[ctr+1]) != T_ARRAY || sizeof(unq_data[ctr+1]) != 2) {
/* Should never get here for proper DTD */
error("Internal error in MAPD->init()");
}
if (!STRINGD->stricmp(unq_data[ctr+1][0][0],"tag")) {
tag = unq_data[ctr+1][0][1];
file = unq_data[ctr+1][1][1];
} else {
tag = unq_data[ctr+1][1][1];
file = unq_data[ctr+1][0][1];
}
if (tag_code[tag] != nil) {
error("Tag " + tag + " is already bound in MAPD::init()!");
}
/* Assign file to tag, and make sure it exists and is clonable */
tag_code[tag] = file;
if(!find_object(file))
compile_object(file);
}
initialized = 1;
} else error("MAPD already initialized!");
}
void destructed(int clone) {
mixed* rooms;
int numzones, riter, ziter;
if(!SYSTEM())
return;
if(room_dtd)
destruct_object(room_dtd);
if(bind_dtd)
destruct_object(bind_dtd);
/* Now go through and destruct all rooms */
numzones = ZONED->num_zones();
for(ziter = 0; ziter < numzones; ziter++) {
rooms = rooms_in_zone(ziter);
for(riter = 0; riter < sizeof(rooms); riter++) {
destruct_object(get_room_by_num(rooms[riter]));
}
}
}
void add_unq_binding(string tag_name, string tag_path) {
if(!GAME() && !COMMON() && !SYSTEM())
error("Only privileged code may add a room binding!");
if(!tag_name)
error("(Nil) isn't a valid tag in add_unq_binding!");
if (tag_code[tag_name] != nil) {
error("Tag name '" + tag_name
+ "' is already bound in MAPD::add_unq_binding()!");
}
/* Assign file to tag, and make sure it exists and is clonable */
if(!find_object(tag_path))
compile_object(tag_path);
/* If we make it through all that without error, do the assignment. */
tag_code[tag_name] = tag_path;
}
void set_binding_handler(object bhandler) {
if(previous_program() != GAME_INITD)
error("Only GAME_INITD may set the room-binding handler!");
if(!bhandler)
error("(Nil) isn't a valid handler in set_binding_handler!");
/* do the assignment */
default_binding_handler = bhandler;
}
void add_room_object(object room) {
string name;
if(!SYSTEM() && !COMMON() && !GAME())
return;
name = object_name(room);
if(room_objects[name])
error("Room already registered in add_room_object!");
room_objects[name] = room;
}
void add_room_to_zone(object room, int num, int req_zone) {
int seg, allocated, zone;
if(!SYSTEM() && !COMMON() && !GAME())
return;
allocated = 0;
if(!room_objects[object_name(room)])
error("Adding num for unregistered object " + object_name(room) + "!");
num = assign_room_to_zone(num, room, req_zone);
if(num < 0) {
error("Error assigning room number!");
}
seg = num / 100;
zone = ZONED->get_segment_zone(seg);
if(zone != req_zone && req_zone != -1)
error("Room assigned to unreasonable segment! Wrong zone!");
if(zone == -1)
zone = 0; /* Fix offset */
room->set_number(num);
}
void remove_room_object(object room) {
string name;
if(!SYSTEM() && !COMMON() && !GAME())
return;
name = object_name(room);
if(!room_objects[name]) {
error("Removing room not in room_objects mapping!");
}
room_objects[name] = nil;
room->set_number(-1);
}
object get_room_by_num(int num) {
if(!SYSTEM() && !COMMON() && !GAME())
return nil;
if(num < 0) return nil;
return OBJNUMD->get_object(num);
}
/* Find appropriate room number in the requested zone (if any) */
private int assign_room_to_zone(int num, object room, int req_zone) {
int segnum, ctr;
string segown;
if(num >= 0) {
int zone;
segnum = num / 100;
segown = OBJNUMD->get_segment_owner(segnum);
if(segown && strlen(segown) && segown != MAPD) {
LOGD->write_syslog("Can't allocate room number " + num
+ " in non-MAPD segment!", LOG_WARN);
return -1;
}
zone = ZONED->get_segment_zone(segnum);
if(zone != req_zone && req_zone >= 0)
error("Room number (#" + num
+ ", zone #" + zone + ") not in requested zone (#" + req_zone
+ ") in assign_room_to_zone!");
if(zone < 0) {
error("Illegal zone in assign_room_to_zone(" + num
+ ", <room>, " + req_zone + ")!");
}
OBJNUMD->allocate_in_segment(segnum, num, room);
return num;
} else {
/* This section happens if there is no specific requested room
number */
int *zoneseg;
/* If no specific room number or zone is requested, give us something
unzoned */
if(req_zone < 0)
req_zone = 0;
zoneseg = ZONED->get_segments_in_zone(req_zone);
for(ctr = 0; ctr < sizeof(zoneseg); ctr++) {
num = OBJNUMD->new_in_segment(zoneseg[ctr], room);
if(num != -1)
break;
}
if(num == -1) {
segnum = OBJNUMD->allocate_new_segment();
num = OBJNUMD->new_in_segment(segnum, room);
if(req_zone)
ZONED->set_segment_zone(segnum, req_zone);
}
return num;
}
}
/* Take an UNQ description parsed with a DTD and add the appropriate room
to mapd. */
private object add_struct_for_room(mixed* unq) {
object room;
int num;
string tag_name, room_program, err;
/* no unq passed in, so no object passed out */
if (sizeof(unq) == 0) {
return nil;
}
if(STRINGD->stricmp(unq[0], "object")) {
error("Label '" + unq[0] + "' doesn't look like the start of an object!");
}
if(!STRINGD->stricmp(unq[1][0][0], "obj_type")) {
if(typeof(unq[1][0][1]) != T_STRING)
error("UNQ obj_type data is not a string!");
tag_name = STRINGD->trim_whitespace(unq[1][0][1]);
} else {
/* Default object type */
tag_name = "object";
}
if (tag_code[tag_name] == nil
&& default_binding_handler) {
err = catch(room_program
= default_binding_handler->type_for_tag(tag_name));
if(err) {
error("Error calling type_for_tag on binding handler, type " + tag_name
+ ": " + err);
}
} else {
room_program = tag_code[tag_name];
}
if(room_program == nil) {
error("Tag " + tag_name
+ " is not bound to any room type!");
}
if (!find_object(room_program)) {
catch {
compile_object(room_program);
} : {
error("Could not compile object '" + room_program
+ "' for tag '" + tag_name + "'!");
}
}
err = catch(room = clone_object(room_program));
if(err) {
error("Error cloning program " + room_program + " of type '" + tag_name
+ "' when making new room: " + err);
}
room->from_dtd_unq(unq);
/* Get the requested number from the room, or -1 for default.
Attempt to assign this number. */
num = room->get_number();
num = assign_room_to_zone(num, room, -1); /* assign to any zone */
if(num < 0) {
error("Can't assign room number!");
}
room->set_number(num);
if(!room_objects[object_name(room)])
error("You forgot to register with MAPD in a room object def maybe?");
return room;
}
private int resolve_parent(object room) {
int* pending_parents;
int ctr;
object* parents, *pending_phr;
object parent;
pending_parents = room->get_pending_parents();
parents = ({ });
if(pending_parents && sizeof(pending_parents)) {
for(ctr = 0; ctr < sizeof(pending_parents); ctr++) {
parent = MAPD->get_room_by_num(pending_parents[ctr]);
if(!parent) {
return 0;
}
parents += ({ parent });
}
room->set_archetypes(parents);
/* Remove nouns in pending_removed_nouns */
pending_phr = room->get_pending_removed_nouns();
for(ctr = 0; ctr < sizeof(pending_phr); ctr++) {
room->remove_noun(pending_phr[ctr]);
}
/* Removed adjectives in pending_removed_adjectives */
pending_phr = room->get_pending_removed_adjectives();
for(ctr = 0; ctr < sizeof(pending_phr); ctr++) {
room->remove_adjective(pending_phr[ctr]);
}
return 1;
}
/* Don't need to set removed_nouns and removed_adjectives if there
are no parents... */
return 1;
}
private int resolve_removed_details(object room) {
int *rem_det;
int ctr;
object tmp;
object *new_rem_det;
rem_det = room->get_pending_removed_details();
new_rem_det = ({ });
for(ctr = 0; ctr < sizeof(rem_det); ctr++) {
tmp = MAPD->get_room_by_num(rem_det[ctr]);
if(!tmp)
return 0;
new_rem_det += ({ tmp });
}
room->set_removed_details(new_rem_det);
return 1;
}
private int resolve_location(object room) {
int pending;
object container;
/* Resolve details (rather than regular containment) */
pending = room->get_pending_detail_of();
if(pending != -1) {
container = get_room_by_num(pending);
if(!container) {
return 0;
}
container->add_detail(room);
return 1;
}
/* Resolve regular containment */
pending = room->get_pending_location();
if(pending != -1) {
container = get_room_by_num(pending);
if(!container) {
return 0;
}
container->add_to_container(room);
return 1;
} else {
container = get_room_by_num(0); /* Else, add to The Void */
if(!container)
error("Can't find room #0! Panic!");
container->add_to_container(room);
return 1;
}
}
object *get_deferred_rooms(void) {
if(SYSTEM())
return unresolved_rooms[..];
return nil;
}
void do_room_resolution(int fully) {
int iter, res_tmp, finished;
mapping done_resolve;
object* new_unres;
if(!SYSTEM() && !COMMON() && !GAME())
error("Only privileged code can request room resolution!");
done_resolve = ([ ]);
finished = 0;
while(finished < sizeof(unresolved_rooms)) {
res_tmp = 0;
/* Do one pass through the list, trying once each to
resolve each unresolved room. */
for(iter = 0; iter < sizeof(unresolved_rooms); iter++) {
/* Skip things we've already resolved. */
if(done_resolve[unresolved_rooms[iter]])
continue;
if(resolve_location(unresolved_rooms[iter])) {
res_tmp = 1;
done_resolve[unresolved_rooms[iter]] = 1;
finished++;
}
}
if(!res_tmp) {
/* We're not resolving any more rooms... */
break;
}
}
/* Now resolve parents and removed details. */
new_unres = ({ });
for(iter = 0; iter < sizeof(unresolved_rooms); iter++) {
if(!done_resolve[unresolved_rooms[iter]]
|| !resolve_parent(unresolved_rooms[iter])
|| !resolve_removed_details(unresolved_rooms[iter])) {
new_unres += ({ unresolved_rooms[iter] });
continue;
}
/* Clear all pending data */
unresolved_rooms[iter]->clear_pending();
}
unresolved_rooms = new_unres;
/* See if we were asked to resolve 100% of remaining rooms */
if(fully && sizeof(unresolved_rooms)) {
string tmp;
int ctr;
tmp = "Can't resolve the following rooms: ";
for(ctr = 0; ctr < sizeof(unresolved_rooms) - 1; ctr++) {
tmp += unresolved_rooms[ctr]->get_number() + ", ";
}
/* This makes sure there's no final comma */
tmp += unresolved_rooms[sizeof(unresolved_rooms) - 1]->get_number();
LOGD->write_syslog("Can't resolve rooms: " + tmp, LOG_FATAL);
error("Can't resolve all rooms! Edit room files to fix this!");
}
}
void add_dtd_unq_rooms(mixed* unq, string filename) {
int iter;
mixed* resolve_rooms;
object room;
if(!initialized)
error("Can't add rooms to uninitialized mapd!");
if(!SYSTEM() && !COMMON() && !GAME())
return;
/* TODO: we'll need the filename for objectd notify dependencies
-- we'll need to keep track of the fact that if that file
changes, these room objects change. */
iter = 0;
resolve_rooms = ({ });
while(iter < sizeof(unq)) {
room = add_struct_for_room( ({ unq[iter], unq[iter + 1] }) );
resolve_rooms += ({ room });
iter += 2;
}
unresolved_rooms += resolve_rooms;
do_room_resolution(0);
}
/* Take a chunk of text to parse as UNQ and add rooms appropriately... */
void add_unq_text_rooms(string text, string filename) {
mixed* unq_data;
if(!initialized)
error("Can't add rooms to uninitialized mapd!");
if(!SYSTEM() && !COMMON() && !GAME())
return;
unq_data = UNQ_PARSER->unq_parse_with_dtd(text, room_dtd, filename);
if(!unq_data)
error("Cannot parse text in add_unq_text_rooms!");
add_dtd_unq_rooms(unq_data, filename);
}
int* segments_in_zone(int zone) {
if(!SYSTEM() && !COMMON() && !GAME())
return nil;
return ZONED->get_segments_in_zone(zone);
}
int* rooms_in_segment(int segment) {
if(!SYSTEM() && !COMMON() && !GAME())
return nil;
if(OBJNUMD->get_segment_owner(segment) != MAPD)
return nil;
return OBJNUMD->objects_in_segment(segment);
}
int* rooms_in_zone(int zone) {
int *segs, *rooms, *tmp;
int iter;
if(!SYSTEM() && !COMMON() && !GAME())
return nil;
segs = segments_in_zone(zone);
if(!segs) return nil;
rooms = ({ });
for(iter = 0; iter < sizeof(segs); iter++) {
tmp = rooms_in_segment(segs[iter]);
if(tmp)
rooms += tmp;
}
return rooms;
}