Created
February 10, 2023 05:10
-
-
Save jalbright015/cacb55b0ce2744245256be6ca92de95e to your computer and use it in GitHub Desktop.
Realm mapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <events.h> | |
#include <daemons.h> | |
#define SAVE_DATA 0 | |
#define BARRIERS_ENABLED 1 | |
#define OVERLAP_DETECTION 1 | |
#define OVERLAP_LIST_DISPLAY 1 | |
#define HTML_OUTPUT_FILE "/open/jezu/mapper.html" | |
#define MAX_CALLS 750 | |
inherit OBJECT; | |
#if SAVE_DATA | |
inherit "/std/save.c"; | |
#define DATA_FILE "/open/jezu/mapper.o" | |
#endif | |
// How far you want to map if you are at the center | |
// You are roughly standing at half the X/Y values | |
#define MAX_X 30 | |
#define MAX_Y 30 | |
// Bit flags | |
#define D_NORTH 1 | |
#define D_SOUTH 2 | |
#define D_EAST 4 | |
#define D_WEST 8 | |
#define D_NORTHEAST 16 | |
#define D_NORTHWEST 32 | |
#define D_SOUTHEAST 64 | |
#define D_SOUTHWEST 128 | |
// Bit flag checks | |
#define BIT_AND(b) (intp(x) ? (x & b) : 0) | |
// Bitwise AND to verify flag inclusion in bitfield | |
#define NORTH(x) BIT_AND(D_NORTH) | |
#define SOUTH(x) BIT_AND(D_SOUTH) | |
#define EAST(x) BIT_AND(D_EAST) | |
#define WEST(x) BIT_AND(D_WEST) | |
#define NORTHEAST(x) BIT_AND(D_NORTHEAST) | |
#define NORTHWEST(x) BIT_AND(D_NORTHWEST) | |
#define SOUTHEAST(x) BIT_AND(D_SOUTHEAST) | |
#define SOUTHWEST(x) BIT_AND(D_SOUTHWEST) | |
// These are the indices used for each of the supported directions | |
#define NORTH_IDX [i-1][j] | |
#define SOUTH_IDX [i+1][j] | |
#define EAST_IDX [i][j+1] | |
#define WEST_IDX [i][j-1] | |
#define NORTHEAST_IDX [i-1][j+1] | |
#define NORTHWEST_IDX [i-1][j-1] | |
#define SOUTHEAST_IDX [i+1][j+1] | |
#define SOUTHWEST_IDX [i+1][j-1] | |
#define SUPPORTED_DIRECTIONS \ | |
({"north","south","east","west","northwest","northeast","southwest","southeast"}) | |
#define BARRIERS ({ "#", "%" }) | |
#define ALLOCATE_MAP map_array(allocate(_max_y), (: allocate(_max_x) :)) | |
#define GH "/d/astaria/city/rooms/starkeep/grand_hall" | |
#define MAPPER_ROOM_MAPPED "mapper_room_mapped" | |
#define MAPPER_QUEUE_EMPTY "mapper_queue_empty" | |
// Master room class. | |
// This is how all room data is held for every room | |
// that is being mapped. Fast and efficient, baby. | |
class RoomStruct { | |
int x; | |
int y; | |
string file; | |
object obj; | |
mapping exits; | |
mapping gates; | |
mapping doors; | |
string barrier; | |
int distance; | |
} | |
int do_exit_bitshift(mapping exits); | |
void do_mapping(object room); | |
int is_room(object o); | |
int is_member(mixed v, mixed *arr); | |
int mark_room(class RoomStruct Room); | |
int get_distance(class RoomStruct Room); | |
void push_queue(class RoomStruct Room); | |
mixed pop_queue(); | |
//class RoomStruct CurrentRoom; | |
nosave object *mapped = ({}); | |
nosave int _max_x = MAX_X; | |
nosave int _max_y = MAX_Y; | |
nosave int X = _max_x/2; | |
nosave int Y = _max_y/2; | |
nosave int calls = 0; | |
nosave float delay = 0.0; | |
nosave int *_map; | |
nosave string _html; | |
nosave class RoomStruct StartingRoom = new(class RoomStruct); | |
nosave object *_queue; | |
nosave int startmap; | |
nosave string _overlaps = ({}); | |
// How large the visual map should be. | |
// Should be twice the size as your initial MAX | |
#define MAP_X (_max_x*2) | |
#define MAP_Y (_max_y*2) | |
// A circular buffer object that will queue and dequeue values | |
#define QUEUE "/std/adt/cstack" | |
// Pythagorean Theorem up in this bitch | |
// This is how many queues there are. | |
// Doing it this way ensures we have the correct queue size for | |
// perfoming an inside-out preferential queue | |
#define LONG_SIDE to_int(ceil(_max_x/2.0)) | |
#define QUEUE_SIZE to_int(ceil(sqrt(((_max_y/2.0)*(_max_y/2.0))+(LONG_SIDE*LONG_SIDE)))) | |
#define SHORT_SIDE to_int(ceil(sqrt((QUEUE_SIZE*QUEUE_SIZE)-(LONG_SIDE*LONG_SIDE)))) | |
#define PERIMETER 2*((_max_y*_max_y)+(_max_x*_max_x)) | |
// How large each queue is | |
mapping query_map() | |
{ | |
return _map; | |
} | |
varargs int *allocate_map() | |
{ | |
return map_array(allocate(_max_y), (: allocate(_max_x) :)); | |
} | |
void create() | |
{ | |
int i = 0; | |
seteuid(getuid()); | |
_map = ALLOCATE_MAP; | |
add("id", ({ "map", "mapper", "realm map", "realm mapper"})); | |
set("short", "a realm mapper"); | |
set("long", "long"); | |
set("mass", 1); | |
set("bulk", 1); | |
if ( !clonep() ) | |
return; | |
// Heart beat is used for informational messages during | |
// the mapping process | |
set_heart_beat(1); | |
// We use events to notify when to initiate actions | |
// in the mapper. MAPPER_ROOM_MAPPED is a terrible | |
// event name that doesn't describe what the event | |
// is meant for, but it will do. | |
EVENTS->add_event(MAPPER_ROOM_MAPPED); | |
EVENTS->add_event(MAPPER_QUEUE_EMPTY); | |
// We add this object as a subscriber to the named | |
// events we added above. | |
SUBSCRIBER->add_handler(MAPPER_ROOM_MAPPED, TO); | |
SUBSCRIBER->add_handler(MAPPER_QUEUE_EMPTY, TO); | |
} | |
void init() | |
{ | |
add_action("start_map", "startmap"); | |
add_action("clear_map", "clearmap"); | |
add_action("stop_map", "stopmap"); | |
add_action("read_map", "readmap"); | |
add_action("size_map", "sizemap"); | |
} | |
int size_map(string s) | |
{ | |
int i; | |
if ( !s || !stringp(s) || !strlen(s) ) | |
return notify_fail("sizemap requires an integer argument.\n"); | |
if ( !intp(i = atoi(s)) ) | |
return notify_fail("Invalid argument passed to 'sizemap'.\n"); | |
_max_x = i; | |
_max_y = i; | |
X = _max_x/2; | |
Y = _max_y/2; | |
_map = ALLOCATE_MAP; | |
message("info", sprintf("Map has been resized to be: %ix%i\n", _max_x, _max_y), ETO); | |
return 1; | |
} | |
void build_queue() | |
{ | |
int i = 0; | |
seteuid(getuid()); | |
// This is where we build our circular buffer | |
// This is a pretty damn neat setup, and it works well. | |
if (sizeof(_queue)) | |
_queue->remove(); | |
_queue = allocate(QUEUE_SIZE); | |
for (i; i < QUEUE_SIZE; i++) | |
{ | |
_queue[i] = clone_object(QUEUE); | |
_queue[i]->alloc((((i*2)+1)*4)-2); | |
} | |
} | |
int read_map() | |
{ | |
string *ret; | |
mixed x; | |
int i, j, mi, mj; | |
string *s; | |
string r; | |
stop_map(); | |
i = 0; | |
_map = trim_map(_map); | |
// Create the array we will use to build the return value | |
ret = map_array(allocate(sizeof(_map)*2), (: allocate(sizeof(_map[0])*2) :)); | |
// mi - _map's i | |
// mj - _map's j | |
for ( mi = 0; mi < sizeof(_map); mi++, i=i+2 ) | |
{ | |
j = 0; | |
for ( mj = 0; mj < sizeof(_map[mi]); mj++, j=j+2 ) | |
{ | |
x = _map[mi][mj]; | |
// Value must be the default '0' so let's pop in some spaces | |
if ( !x ) | |
{ | |
if ( !ret[i][j] ) | |
ret[i][j] = " "; | |
if ( i < MAP_Y && !ret SOUTH_IDX) | |
ret SOUTH_IDX = " "; | |
if ( j < MAP_X && !ret EAST_IDX ) | |
ret EAST_IDX = " "; | |
if ( j && i < MAP_Y && !ret SOUTHWEST_IDX ) | |
ret SOUTHWEST_IDX = " "; | |
continue; | |
} | |
// EAST | |
if ( j < MAP_X ) | |
{ | |
ret EAST_IDX = EAST(x) ? "-" : " "; | |
} | |
if ( intp(x) && WEST(x) && j && (!ret WEST_IDX || ret WEST_IDX == " ") ) | |
ret WEST_IDX = "-"; | |
if ( NORTHWEST(x) && i && j ) | |
ret NORTHWEST_IDX= "\\"; | |
if ( NORTHEAST(x) && i && j < MAP_X ) | |
ret NORTHEAST_IDX = "/"; | |
if ( NORTH(x) && i && ret NORTH_IDX ) | |
ret NORTH_IDX = "|"; | |
if ( i < MAP_Y ) | |
{ | |
// SOUTH | |
if ( !ret SOUTH_IDX ) | |
ret SOUTH_IDX = SOUTH(x) ? "|" : " "; | |
// SOUTHEAST | |
if ( SOUTHEAST(x) && j < MAP_X ) | |
ret SOUTHEAST_IDX = "\\"; | |
// SOUTHWEST | |
if ( j && !ret SOUTHWEST_IDX ) | |
ret SOUTHWEST_IDX = SOUTHWEST(x) ? "/" : " "; | |
} | |
// Highlight the center room where the user is | |
// Look for the starting location | |
// This is why we set the value to negative up above | |
if ( Y == mi && X == mj ) | |
ret[i][j] = "%^BOLD%^%^BLUE%^@%^RESET%^"; | |
else if ( !intp(x) ) | |
{ | |
switch(x) | |
{ | |
case "#" : | |
ret[i][j] = "%^BOLD%^%^BLACK%^#%^RESET%^"; | |
break; | |
case "%" : | |
ret[i][j] = "%^YELLOW%^\%%^RESET%^"; | |
break; | |
case "X" : | |
ret[i][j] = "%^BOLD%^%^RED%^X%^RESET%^"; | |
break; | |
default : | |
ret[i][j] = "%^BOLD%^%^YELLOW%^?%^RESET%^"; | |
} | |
continue; | |
} | |
else | |
ret[i][j] = "%^BOLD%^%^WHITE%^O%^RESET%^"; | |
} | |
} | |
if ( file_exists(HTML_OUTPUT_FILE) ) | |
rm(HTML_OUTPUT_FILE); | |
write_file(HTML_OUTPUT_FILE, "<html><body><pre>\n"); | |
foreach(s in ret) | |
{ | |
r = implode(s, "")+"\n"; | |
write(r); | |
write_file(HTML_OUTPUT_FILE, sprintf("%s", TERMINAL_D->convert_pinkfish(r, "html"))); | |
} | |
write_file(HTML_OUTPUT_FILE, "</pre></body></html>"); | |
return 1; | |
} | |
// Not gonna lie. This shit is going to confuse you. | |
// ys - Y start | |
// ye - Y end | |
// xs - X start | |
// se - X end | |
// | |
// This is my algorithm to take an oversized grid array | |
// and crop it down to the actual size of the map it | |
// contains. This is important for the different stages | |
// of processing that happen to this. Including the printed | |
// map on the buffer, and the resulting html document. | |
mixed *trim_map(mixed *str) | |
{ | |
int sz = sizeof(str); | |
int i, y, t, xs = sizeof(str[0]); | |
int x, ret, ys, ye, xe; | |
y = 0; | |
t = 0; | |
for (y; y < sz; y++) | |
{ | |
i = sizeof(str[y]); | |
for (x = 0; x < i; x++) | |
{ | |
if ( str[y][x] ) | |
{ | |
if ( x < xs ) | |
xs = x; | |
if ( xe < x ) | |
xe = x; | |
if ( undefinedp(ys) ) | |
ys = y; | |
} | |
else if ( !undefinedp(ys) && !xe && x == (i-1) ) | |
{ | |
ye = y; | |
break; | |
} | |
} | |
if ( xe ) | |
{ | |
if ( xe > t ) | |
t = xe; | |
xe = 0; | |
} | |
if ( ye ) | |
break; | |
} | |
xe = t; | |
if ( undefinedp(ye) ) | |
ye = sz; | |
// Adjust X and Y so that the 'current location' marker is in | |
// the right spot | |
Y = Y - ys; | |
X = X - xs; | |
str = str[ys..ye]; | |
str = map_array(str, function(mixed *row, int xs, int xe) { | |
return row[xs..xe]; | |
}, xs, xe); | |
return str; | |
} | |
int stop_map() | |
{ | |
remove_call_out("do_mapping"); | |
return 1; | |
} | |
int clear_map() | |
{ | |
_map = ALLOCATE_MAP; | |
calls = 0; | |
delay = 0.0; | |
mapped = ({}); | |
build_queue(); | |
#if SAVE_DATA | |
save_object(DATA_FILE); | |
#endif | |
write("Map data cleared.\n"); | |
return 1; | |
} | |
int is_room(object o) | |
{ | |
if ( !objectp(o) ) | |
return 0; | |
if ( environment(o) ) | |
return 0; | |
return o->query_is_room(); | |
} | |
int start_map(string s) | |
{ | |
object start = environment(TP); | |
build_queue(); | |
startmap = 1; | |
message("info", "Mapping started.\n", ETO); | |
if ( stringp(s) && strlen(s) ) | |
{ | |
if ( s == "gh" ) | |
s = GH; | |
if ( !objectp(start = load_object(s || GH)) ) | |
start = environment(TP); | |
} | |
start = start || load_object(GH); | |
if ( !is_room(start) ) | |
return notify_fail("Invalid start location.\n"); | |
StartingRoom.obj = start; | |
StartingRoom.x = X; | |
StartingRoom.y = Y; | |
push_queue(StartingRoom); | |
EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
return 1; | |
} | |
varargs mixed do_mapping(class RoomStruct CurrentRoom) | |
{ | |
mapping exits, gates, doors; | |
class RoomStruct TempRoom; | |
function filter_directions = (: !sizeof(({$1}) - SUPPORTED_DIRECTIONS) :); | |
#if SAVE_DATA | |
save_object(DATA_FILE); | |
#endif | |
if ( !classp(CurrentRoom) ) | |
return EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
if ( !objectp(CurrentRoom.obj) ) | |
CurrentRoom.obj = load_object(CurrentRoom.file); | |
if ( !objectp(CurrentRoom.obj) ) | |
return EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
if ( !sizeof(exits = CurrentRoom.obj->query("exits")) ) | |
return EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
CurrentRoom.exits = filter(exits, filter_directions); | |
if ( !sizeof(CurrentRoom.exits) ) | |
return EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
#if BARRIERS_ENABLED | |
if ( gates = (mapping)CurrentRoom.obj->query("gates") ) | |
CurrentRoom.gates = filter(gates, filter_directions); | |
if ( doors = (mapping)CurrentRoom.obj->query("doors") ) | |
CurrentRoom.doors = filter(doors, filter_directions); | |
#endif | |
if ( !mark_room(CurrentRoom) ) | |
return EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
foreach (string exit in keys(CurrentRoom.exits)) | |
{ | |
TempRoom = new(class RoomStruct); | |
TempRoom.x = CurrentRoom.x; | |
TempRoom.y = CurrentRoom.y; | |
TempRoom = shift_coordinates(TempRoom, exit); | |
if ( TempRoom.x < 0 || TempRoom.y < 0 || TempRoom.x > _max_x-1 || TempRoom.y > _max_y-1 ) | |
continue; | |
TempRoom.file = CurrentRoom.exits[exit]; | |
if (TempRoom.file) | |
TempRoom.obj = load_object(TempRoom.file); | |
#if BARRIERS_ENABLED | |
if ( mapp(CurrentRoom.gates) && !undefinedp(CurrentRoom.gates[exit]) && TempRoom.obj->query_estate() ) | |
TempRoom.barrier = "#"; | |
#endif | |
push_queue(copy(TempRoom)); | |
} | |
return EVENTS->fire_event(MAPPER_ROOM_MAPPED); | |
} | |
class RoomStruct pop_queue() | |
{ | |
class RoomStruct TempRoom = new(class RoomStruct); | |
foreach(object o in _queue) | |
{ | |
if ( !intp(TempRoom = o->dequeue()) ) | |
{ | |
return TempRoom; | |
} | |
} | |
return 0; | |
} | |
void push_queue(class RoomStruct Room) | |
{ | |
Room.distance = get_distance(Room); | |
_queue[Room.distance]->enqueue(Room); | |
} | |
int get_distance(class RoomStruct Room) | |
{ | |
int y, x, h; | |
y = abs(Y - Room.y); | |
x = abs(X - Room.x); | |
h = to_int(sqrt((y*y) + (x*x))); | |
return h; | |
} | |
int mark_room(class RoomStruct Room) | |
{ | |
if ( _map[Room.y][Room.x] != 0 ) | |
{ | |
if ( member_array(Room.obj, mapped) != -1 ) | |
return 0; | |
#if OVERLAP_DETECTION | |
_overlaps = _overlaps + ({ Room.file }); | |
_map[Room.y][Room.x] = "X"; | |
#endif | |
} | |
else | |
if ( Room.barrier ) | |
{ | |
#if BARRIERS_ENABLED | |
_map[Room.y][Room.x] = Room.barrier; | |
mapped = mapped + ({ Room.obj }); | |
return 0; | |
#endif | |
} | |
else if ( member_array(Room.obj, mapped) != -1 ) | |
{ | |
return 0; | |
} | |
else | |
{ | |
_map[Room.y][Room.x] = do_exit_bitshift(Room.exits); | |
} | |
mapped = mapped + ({ Room.obj }); | |
return 1; | |
} | |
int is_member(mixed v, mixed *arr) | |
{ | |
return (member_array(v, arr) != -1); | |
} | |
class RoomStruct shift_coordinates(class RoomStruct TempRoom, string exit) | |
{ | |
switch ( exit ) | |
{ | |
case "north" : | |
TempRoom.y--; | |
break; | |
case "south" : | |
TempRoom.y++; | |
break; | |
case "east" : | |
TempRoom.x++; | |
break; | |
case "west" : | |
TempRoom.x--; | |
break; | |
case "northeast" : | |
TempRoom.x++; | |
TempRoom.y--; | |
break; | |
case "northwest" : | |
TempRoom.x--; | |
TempRoom.y--; | |
break; | |
case "southeast" : | |
TempRoom.x++; | |
TempRoom.y++; | |
break; | |
case "southwest" : | |
TempRoom.x--; | |
TempRoom.y++; | |
break; | |
default : | |
} | |
return TempRoom; | |
} | |
string do_exit_bitshift(mapping exits) | |
{ | |
int hb = 0; | |
int lb = 0; | |
int i = 0; | |
foreach (string exit in keys(exits)) | |
{ | |
switch ( exit ) | |
{ | |
case "north" : | |
i = i + D_NORTH; | |
break; | |
case "south" : | |
i = i + D_SOUTH; | |
break; | |
case "east" : | |
i = i + D_EAST; | |
break; | |
case "west" : | |
i = i + D_WEST; | |
break; | |
case "northeast" : | |
i = i + D_NORTHEAST; | |
break; | |
case "northwest" : | |
i = i + D_NORTHWEST; | |
break; | |
case "southeast" : | |
i = i + D_SOUTHEAST; | |
break; | |
case "southwest" : | |
i = i + D_SOUTHWEST; | |
break; | |
default : | |
} | |
} | |
return i; | |
} | |
int _dequeue_attempts = 0; | |
varargs void receive_event(object ob, string event, mixed state) | |
{ | |
float t = 0.0; | |
class RoomStruct Room; | |
if ( !clonep() ) | |
return; | |
if ( ob != TO ) | |
return; | |
Room = new(class RoomStruct); | |
switch (event) | |
{ | |
case MAPPER_ROOM_MAPPED : | |
// We pop the queue twice just to make sure! | |
if ( !(Room = pop_queue()) && !(Room = pop_queue()) ) | |
break; | |
// We need to make sure we do not hit the max nested call out | |
// which is currently 1000 | |
if ( calls++ > MAX_CALLS ) | |
{ | |
t = 0.1; | |
calls = 0; | |
} | |
// Again, we use call_out here to break out of the call | |
// chain | |
call_out("do_mapping", t, Room); | |
break; | |
case MAPPER_QUEUE_EMPTY : | |
_queue->remove(); | |
break; | |
default : | |
return; | |
} | |
} | |
void remove() | |
{ | |
if ( clonep() ) | |
{ | |
// The circular buffer objects should be removed automatically | |
// when this object is removed, but just to make sure... | |
_queue->remove(); | |
// Let's remove the event data we added in create() | |
EVENTS->remove_event(MAPPER_ROOM_MAPPED); | |
EVENTS->remove_event(MAPPER_QUEUE_EMPTY); | |
EVENTS->unsubscribe(MAPPER_ROOM_MAPPED, TO); | |
EVENTS->unsubscribe(MAPPER_QUEUE_EMPTY, TO); | |
} | |
::remove(); | |
} | |
void display_overlap_list() | |
{ | |
string s; | |
foreach(s in _overlaps) | |
message("info", sprintf("%s\n", s), ETO); | |
} | |
void heart_beat() | |
{ | |
if (startmap) | |
{ | |
if ( find_call_out("do_mapping") == -1 ) | |
{ | |
startmap = 0; | |
message("info", "Mapping complete.\n", ETO); | |
if ( OVERLAP_LIST_DISPLAY ) | |
display_overlap_list(); | |
} | |
else | |
message("info", "Mapping...\n", ETO); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment