Skip to content

Instantly share code, notes, and snippets.

@jalbright015
Created February 10, 2023 05:10
Show Gist options
  • Save jalbright015/cacb55b0ce2744245256be6ca92de95e to your computer and use it in GitHub Desktop.
Save jalbright015/cacb55b0ce2744245256be6ca92de95e to your computer and use it in GitHub Desktop.
Realm mapper
#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