aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFranklin Wei <frankhwei536@gmail.com>2016-01-31 19:53:45 -0500
committerFranklin Wei <frankhwei536@gmail.com>2016-01-31 19:53:45 -0500
commit0730fc3924dd4e04efbe51287d1d69850404d05f (patch)
tree495d79d0dc26e39c9065c6ceb7d16b9a3e76561d /src
parent8405274a91e3652ee98a423608a8496ead1edc05 (diff)
downloadnetcosm-0.5.0-rc1.zip
netcosm-0.5.0-rc1.tar.gz
netcosm-0.5.0-rc1.tar.bz2
netcosm-0.5.0-rc1.tar.xz
bump version to 0.5.0-rc10.5.0-rc1
* implements objects using reference counts rather than copying * implements both room-local and global verbs * refactors the world_* functions into a separate module * numerous other changes
Diffstat (limited to 'src')
-rw-r--r--src/client.c26
-rw-r--r--src/globals.h2
-rw-r--r--src/hash.c9
-rw-r--r--src/hash.h3
-rw-r--r--src/obj.c21
-rw-r--r--src/obj.h12
-rw-r--r--src/room.c286
-rw-r--r--src/room.h26
-rw-r--r--src/server.c31
-rw-r--r--src/server_reqs.c46
-rw-r--r--src/server_reqs.h3
-rw-r--r--src/userdb.c16
-rw-r--r--src/util.c14
-rw-r--r--src/util.h3
-rw-r--r--src/verb.c82
-rw-r--r--src/verb.h55
-rw-r--r--src/world.c340
-rw-r--r--src/world.h61
18 files changed, 722 insertions, 314 deletions
diff --git a/src/client.c b/src/client.c
index e67cfb6..1de6818 100644
--- a/src/client.c
+++ b/src/client.c
@@ -171,7 +171,7 @@ void send_master(unsigned char cmd, const void *data, size_t sz)
free(req);
}
-#define BUFSZ 128
+#define CLIENT_READ_SZ 128
char *client_read(void)
{
@@ -179,11 +179,9 @@ char *client_read(void)
size_t bufidx;
tryagain:
- buf = malloc(BUFSZ);
+ buf = calloc(1, CLIENT_READ_SZ);
bufidx = 0;
- memset(buf, 0, BUFSZ);
-
/* set of the client fd and the pipe from our parent */
struct pollfd fds[2];
@@ -209,11 +207,11 @@ tryagain:
}
else if(fds[i].fd == client_fd)
{
- ssize_t len = read(client_fd, buf + bufidx, BUFSZ - bufidx - 1);
+ ssize_t len = read(client_fd, buf + bufidx, CLIENT_READ_SZ - bufidx - 1);
if(len <= 0)
error("lost connection (%d)", fds[i].revents);
- buf[BUFSZ - 1] = '\0';
+ buf[CLIENT_READ_SZ - 1] = '\0';
enum telnet_status ret = telnet_parse_data((unsigned char*)buf + bufidx, len);
@@ -298,7 +296,7 @@ bool poll_requests(void)
reqdata_type = TYPE_BOOLEAN;
returned_reqdata.boolean = status;
if(!status)
- out("Cannot go that way.\n");
+ out("You cannot go that way.\n");
break;
}
case REQ_GETUSERDATA:
@@ -529,17 +527,20 @@ auth:
else
client_change_state(STATE_LOGGEDIN);
- /* authenticated */
+ /* authenticated, begin main command loop */
debugf("client: Authenticated as %s\n", current_user);
client_change_user(current_user);
current_room = 0;
+
client_change_room(current_room);
+
client_look();
+
while(1)
{
out(">> ");
char *cmd = client_read();
-
+ char *orig = strdup(cmd);
char *save = NULL;
char *tok = strtok_r(cmd, WSPACE, &save);
@@ -754,10 +755,17 @@ auth:
char *what = strtok_r(NULL, " ", &save);
client_drop(what);
}
+ else
+ {
+ /* we can't handle it, send it to the master */
+
+ send_master(REQ_EXECVERB, orig, strlen(orig) + 1);
+ }
next_cmd:
free(cmd);
+ free(orig);
}
done:
diff --git a/src/globals.h b/src/globals.h
index 5edc153..e33ff5c 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -66,7 +66,7 @@
#define WORLD_MAGIC 0x31415926
#define USERDB_MAGIC 0x27182818
#define MAX_FAILURES 3
-#define NETCOSM_VERSION "0.4.0-rc1"
+#define NETCOSM_VERSION "0.5.0-rc1"
/* username length */
#define MAX_NAME_LEN 32
diff --git a/src/hash.c b/src/hash.c
index da15157..23e4bba 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -34,6 +34,7 @@ struct hash_map {
size_t table_sz;
void (*free_key)(void *key);
void (*free_data)(void *data);
+ void* (*dup_data)(void *data);
size_t n_entries;
};
@@ -279,7 +280,15 @@ void *hash_dup(void *ptr)
if(!data)
break;
ptr = NULL;
+ if(map->dup_data)
+ data = map->dup_data(data);
hash_insert(ret, key, data);
}
return ret;
}
+
+void hash_setdupdata_cb(void *ptr, void *(*cb)(void*))
+{
+ struct hash_map *map = ptr;
+ map->dup_data = cb;
+}
diff --git a/src/hash.h b/src/hash.h
index c2ed749..277fd3f 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -89,3 +89,6 @@ void *hash_getkeyptr(void*, const void *key);
size_t hash_size(void*);
void *hash_dup(void*);
+
+/* sets the callback for when duplicating a data node */
+void hash_setdupdata_cb(void*, void *(*cb)(void*));
diff --git a/src/obj.c b/src/obj.c
index 0f9c554..20714f7 100644
--- a/src/obj.c
+++ b/src/obj.c
@@ -44,6 +44,8 @@ struct object_t *obj_new(const char *class_name)
struct object_t *obj = calloc(1, sizeof(struct object_t));
+ obj->refcount = 1;
+
obj->class = hash_lookup(obj_class_map, class_name);
if(!obj->class)
{
@@ -80,23 +82,24 @@ struct object_t *obj_read(int fd)
struct object_t *obj_dup(struct object_t *obj)
{
- struct object_t *new = calloc(1, sizeof(struct object_t));
- memcpy(new, obj, sizeof(*new));
- new->name = strdup(obj->name);
- new->userdata = obj->class->hook_clone(obj->userdata);
- return new;
+ ++obj->refcount;
+ return obj;
}
void obj_free(void *ptr)
{
struct object_t *obj = ptr;
debugf("Freeing obj %s\n", obj->name);
+ --obj->refcount;
- if(obj->class->hook_destroy)
- obj->class->hook_destroy(obj);
+ if(!obj->refcount)
+ {
+ if(obj->class->hook_destroy)
+ obj->class->hook_destroy(obj);
- free(obj->name);
- free(obj);
+ free(obj->name);
+ free(obj);
+ }
}
void obj_shutdown(void)
diff --git a/src/obj.h b/src/obj.h
index 240b4a9..ff26399 100644
--- a/src/obj.h
+++ b/src/obj.h
@@ -52,9 +52,8 @@ struct obj_class_t {
* NULL: can drop
*/
bool (*hook_drop)(struct object_t*, user_t *user);
- void* (*hook_clone)(void*); /* clone the user data pointer */
- void (*hook_destroy)(struct object_t*);
- const char* (*hook_desc)(struct object_t*, user_t*);
+ void (*hook_destroy)(struct object_t*); // free resources
+ const char* (*hook_desc)(struct object_t*, user_t*); // get object description
};
struct object_t {
@@ -64,6 +63,8 @@ struct object_t {
bool list;
+ unsigned refcount;
+
void *userdata;
};
@@ -76,10 +77,11 @@ void obj_write(int fd, struct object_t *obj);
/* deserialize an object */
struct object_t *obj_read(int fd);
-/* make a duplicate of an object
- * used for "moving" an object */
+/* this used to actually make a duplicate of an object...
+ * now it just increments its reference count */
struct object_t *obj_dup(struct object_t *obj);
+/* this only frees the object if its reference count is zero */
void obj_free(void*);
/* shut down the obj_* module */
diff --git a/src/room.c b/src/room.c
index de0950d..58563a8 100644
--- a/src/room.c
+++ b/src/room.c
@@ -22,17 +22,7 @@
#include "server.h"
#include "room.h"
#include "userdb.h"
-
-/* processed world data */
-
-static struct room_t *world;
-static size_t world_sz;
-static char *world_name;
-
-struct room_t *room_get(room_id id)
-{
- return world + id;
-}
+#include "world.h"
bool room_user_add(room_id id, struct child_data *child)
{
@@ -61,276 +51,78 @@ bool room_user_del(room_id id, struct child_data *child)
return false;
}
-void world_save(const char *fname)
-{
- int fd = open(fname, O_CREAT | O_WRONLY, 0644);
- write_uint32(fd, WORLD_MAGIC);
- write(fd, &world_sz, sizeof(world_sz));
- write_string(fd, world_name);
- for(unsigned i = 0; i < world_sz; ++i)
- {
- write_roomid(fd, &world[i].id);
- /* unique ID never changes */
- //write_string(fd, world[i].data.uniq_id);
- write_string(fd, world[i].data.name);
- write_string(fd, world[i].data.desc);
- /* adjacency strings not serialized, only adjacent IDs */
-
- /* callbacks are static, so are not serialized */
-
- write(fd, world[i].adjacent, sizeof(world[i].adjacent));
-
- /* now we serialize all the objects in this room */
-
- size_t n_objects = room_obj_count(i);
- write(fd, &n_objects, sizeof(n_objects));
-
- room_id id = i;
- void *save;
- while(1)
- {
- struct object_t *obj = room_obj_iterate(id, &save);
- if(!obj)
- break;
- id = ROOM_NONE;
- obj_write(fd, obj);
- }
- }
- close(fd);
-}
-
-static void room_free(struct room_t *room)
+void room_free(struct room_t *room)
{
hash_free(room->users);
room->users = NULL;
- hash_setfreedata_cb(room->objects, obj_free);
+
hash_free(room->objects);
room->objects = NULL;
+
+ hash_free(room->verbs);
+ room->verbs = NULL;
+
free(room->data.name);
free(room->data.desc);
}
-void world_free(void)
-{
- if(world)
- {
- if(world_name)
- free(world_name);
- for(unsigned i = 0; i < world_sz; ++i)
- {
- room_free(world + i);
- }
- free(world);
- world = NULL;
- }
-}
-
bool room_obj_add(room_id room, struct object_t *obj)
{
return !hash_insert(room_get(room)->objects, obj->name, obj);
}
-#define OBJMAP_SIZE 8
-
-/* initialize the room's hash tables */
-static void room_init_maps(struct room_t *room)
+struct object_t *room_obj_iterate(room_id room, void **save)
{
- room->users = hash_init((userdb_size() / 2) + 1, hash_djb, compare_strings);
-
- room->objects = hash_init(OBJMAP_SIZE, hash_djb, compare_strings);
- hash_setfreedata_cb(room->objects, obj_free);
+ if(room != ROOM_NONE)
+ return hash_iterate(room_get(room)->objects, save, NULL);
+ else
+ return hash_iterate(NULL, save, NULL);
}
-/**
- * Loads a world using data on disk and in memory.
- *
- * @param fname world file
- * @param data world module data
- * @param data_sz number of rooms in world module
- */
-bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz, const char *name)
+struct object_t *room_obj_get(room_id room, const char *name)
{
- int fd = open(fname, O_RDONLY);
- if(fd < 0)
- return false;
-
- if(read_uint32(fd) != WORLD_MAGIC)
- return false;
-
- if(world)
- world_free();
-
- read(fd, &world_sz, sizeof(world_sz));
-
- if(world_sz != data_sz)
- return false;
-
- world = calloc(world_sz, sizeof(struct room_t));
-
- world_name = read_string(fd);
- if(strcmp(name, world_name))
- {
- free(world_name);
- debugf("Incompatible world state.\n");
- return false;
- }
-
- for(unsigned i = 0; i < world_sz; ++i)
- {
- room_init_maps(world + i);
-
- world[i].id = read_roomid(fd);
- memcpy(&world[i].data, data + i, sizeof(struct roomdata_t));
- world[i].data.name = read_string(fd);
- world[i].data.desc = read_string(fd);
- if(read(fd, world[i].adjacent, sizeof(world[i].adjacent)) < 0)
- return false;
-
- size_t n_objects;
- if(read(fd, &n_objects, sizeof(n_objects)) != sizeof(n_objects))
- error("world file corrupt");
-
- for(unsigned j = 0; j < n_objects; ++j)
- {
- struct object_t *obj = obj_read(fd);
-
- if(!room_obj_add(i, obj))
- error("duplicate object name in room '%s'", world[i].data.name);
- }
- }
-
- close(fd);
- return true;
+ return hash_lookup(room_get(room)->objects, name);
}
-static SIMP_HASH(enum direction_t, dir_hash);
-static SIMP_EQUAL(enum direction_t, dir_equal);
-
-/* loads room data (supplied by the world module) into our internal format */
-void world_init(const struct roomdata_t *data, size_t sz, const char *name)
+size_t room_obj_count(room_id room)
{
- debugf("Loading world with %lu rooms.\n", sz);
- world = calloc(sz, sizeof(struct room_t));
- world_sz = 0;
- world_name = strdup(name);
-
- void *map = hash_init(sz / 2 + 1, hash_djb, compare_strings);
-
- for(size_t i = 0; i < sz; ++i)
- {
- world[i].id = i;
- memcpy(&world[i].data, &data[i], sizeof(data[i]));
-
- /* have to strdup these strings so they can be freed later */
- //world[i].uniq_id = strdup(world[i].uniq_id);
- world[i].data.name = strdup(world[i].data.name);
- world[i].data.desc = strdup(world[i].data.desc);
- debugf("Loading room '%s'\n", world[i].data.uniq_id);
-
- if(hash_insert(map, world[i].data.uniq_id, world + i))
- error("Duplicate room ID '%s'", world[i].data.uniq_id);
-
- for(int dir = 0; dir < NUM_DIRECTIONS; ++dir)
- {
- const char *adjacent_room = world[i].data.adjacent[dir];
- if(adjacent_room)
- {
- struct room_t *room = hash_lookup(map, adjacent_room);
- if(room)
- world[i].adjacent[dir] = room->id;
- }
- }
- room_init_maps(world + i);
-
- world_sz = i + 1;
- }
-
- /* second pass to fill in missing references */
- for(size_t i = 0; i < sz; ++i)
- {
- for(int dir = 0; dir < NUM_DIRECTIONS; ++dir)
- {
- const char *adjacent_room = world[i].data.adjacent[dir];
- if(adjacent_room)
- {
- struct room_t *room = hash_lookup(map, adjacent_room);
- if(room)
- world[i].adjacent[dir] = room->id;
- else
- error("unknown room '%s' referenced from '%s'",
- adjacent_room, world[i].data.uniq_id);
- }
- else
- world[i].adjacent[dir] = ROOM_NONE;
- }
- }
+ return hash_size(room_get(room)->objects);
+}
- struct direction_pair {
- enum direction_t dir, opp;
- } pairs[] = {
- { DIR_N, DIR_S },
- { DIR_NE, DIR_SW },
- { DIR_E, DIR_W },
- { DIR_SE, DIR_NW },
- { DIR_UP, DIR_DN },
- { DIR_IN, DIR_OT },
- };
+bool room_obj_del(room_id room, const char *name)
+{
+ return hash_remove(room_get(room)->objects, name);
+}
- void *dir_map = hash_init(ARRAYLEN(pairs) * 2, dir_hash, dir_equal);
- for(int n = 0; n < 2; ++n)
- {
- for(unsigned i = 0; i < ARRAYLEN(pairs); ++i)
- {
- if(!n)
- hash_insert(dir_map, &pairs[i].dir, &pairs[i].opp);
- else
- hash_insert(dir_map, &pairs[i].opp, &pairs[i].dir);
- }
- }
+#define OBJMAP_SIZE 8
+#define VERBMAP_SZ 8
- /* third pass to call all the init handlers and check accessibility */
- for(room_id i = 0; i < (int)world_sz; ++i)
- {
- if(world[i].data.hook_init)
- world[i].data.hook_init(world[i].id);
- /* check that all rooms are accessible */
- for(enum direction_t j = 0; j < NUM_DIRECTIONS; ++j)
- {
- if(world[i].adjacent[j] != ROOM_NONE)
- {
- enum direction_t *opp = hash_lookup(dir_map, &j);
- struct room_t *adj = room_get(world[i].adjacent[j]);
- if(adj->adjacent[*opp] != i)
- debugf("WARNING: Rooms '%s' and '%s' are one-way\n",
- world[i].data.uniq_id, adj->data.uniq_id);
- }
- }
- }
+/* initialize the room's hash tables */
+void room_init_maps(struct room_t *room)
+{
+ room->users = hash_init((userdb_size() / 2) + 1, hash_djb, compare_strings);
- hash_free(dir_map);
+ room->objects = hash_init(OBJMAP_SIZE, hash_djb, compare_strings);
+ hash_setfreedata_cb(room->objects, obj_free);
- hash_free(map);
-}
+ room->verbs = hash_init(VERBMAP_SZ,
+ hash_djb,
+ compare_strings);
-struct object_t *room_obj_iterate(room_id room, void **save)
-{
- if(room != ROOM_NONE)
- return hash_iterate(room_get(room)->objects, save, NULL);
- else
- return hash_iterate(NULL, save, NULL);
+ hash_setfreedata_cb(room->verbs, verb_free);
}
-struct object_t *room_obj_get(room_id room, const char *name)
+void *room_verb_map(room_id id)
{
- return hash_lookup(room_get(room)->objects, name);
+ return room_get(id)->verbs;
}
-size_t room_obj_count(room_id room)
+bool room_verb_add(room_id id, struct verb_t *verb)
{
- return hash_size(room_get(room)->objects);
+ return !hash_insert(room_get(id)->verbs, verb->name, verb);
}
-bool room_obj_del(room_id room, const char *name)
+bool room_verb_del(room_id id, const char *name)
{
- return hash_remove(room_get(room)->objects, name);
+ return hash_remove(room_get(id), name);
}
diff --git a/src/room.h b/src/room.h
index 88de823..64cfeae 100644
--- a/src/room.h
+++ b/src/room.h
@@ -21,6 +21,7 @@
#include "globals.h"
#include "obj.h"
+#include "verb.h"
/* Our world is an array of rooms, each having a list of objects in
them, as well as actions that can be performed in the room. Objects
@@ -49,13 +50,6 @@ struct roomdata_t {
void (* const hook_leave)(room_id room, user_t *user);
};
-struct verb_t {
- const char *name;
-
- /* toks is strtok_r's pointer */
- void (*execute)(const char *toks, user_t *user);
-};
-
struct room_t {
room_id id;
struct roomdata_t data;
@@ -69,11 +63,6 @@ struct room_t {
};
/* room/world */
-void world_init(const struct roomdata_t *data, size_t sz, const char *name);
-bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz, const char *world_name);
-void world_save(const char *fname);
-
-struct room_t *room_get(room_id id);
bool room_user_add(room_id id, struct child_data *child);
bool room_user_del(room_id id, struct child_data *child);
@@ -94,4 +83,15 @@ size_t room_obj_count(room_id room);
bool room_obj_del(room_id room, const char *name);
-void world_free(void);
+/* local verbs override global verbs */
+bool room_verb_add(room_id room, struct verb_t*);
+bool room_verb_del(room_id room, const char *verbname);
+
+/* get the local map of verbs */
+void *room_verb_map(room_id room);
+
+/* free a room and its resources */
+void room_free(struct room_t *room);
+
+/* semi-protected, should only be called from world_ */
+void room_init_maps(struct room_t *room);
diff --git a/src/server.c b/src/server.c
index 3b428d3..d65a3f1 100644
--- a/src/server.c
+++ b/src/server.c
@@ -23,6 +23,7 @@
#include "server.h"
#include "userdb.h"
#include "util.h"
+#include "world.h"
#define DEFAULT_PORT 1234
#define BACKLOG 512
@@ -116,8 +117,14 @@ static void __attribute__((noreturn)) serv_cleanup(void)
close(server_socket);
- world_free();
+ /* shut down modules */
+ obj_shutdown();
reqmap_free();
+ userdb_shutdown();
+ verb_shutdown();
+ world_free();
+
+ /* free internal data structures */
hash_free(child_map);
child_map = NULL;
@@ -125,14 +132,11 @@ static void __attribute__((noreturn)) serv_cleanup(void)
hash_free(dir_map);
dir_map = NULL;
- userdb_shutdown();
-
- obj_shutdown();
-
extern char *current_user;
if(current_user)
free(current_user);
+ /* shut down libev */
ev_default_destroy();
exit(0);
@@ -254,24 +258,25 @@ static void new_connection_cb(EV_P_ ev_io *w, int revents)
/* child */
are_child = true;
+ /* close our file descriptors */
close(readpipe[0]);
close(outpipe[1]);
close(server_socket);
- /* only the master process controls the world */
- world_free();
+ /* shut down modules */
+ obj_shutdown();
reqmap_free();
+ userdb_shutdown();
+ verb_shutdown();
+ world_free();
+
+ /* free our data structures */
hash_free(child_map);
child_map = NULL;
- /* we don't need libev anymore */
+ /* shut down libev */
ev_default_destroy();
- /* user DB requests go through the master */
- userdb_shutdown();
-
- obj_shutdown();
-
debugf("Child with PID %d spawned\n", getpid());
server_socket = new_sock;
diff --git a/src/server_reqs.c b/src/server_reqs.c
index 7767027..fd1ef19 100644
--- a/src/server_reqs.c
+++ b/src/server_reqs.c
@@ -21,6 +21,7 @@
#include "hash.h"
#include "server.h"
#include "userdb.h"
+#include "world.h"
/* sends a single packet to a child, virtually guarantees receipt */
static void send_packet(struct child_data *child, unsigned char cmd,
@@ -47,8 +48,7 @@ tryagain:
}
}
-static void __attribute__((format(printf,2,3)))
-send_msg(struct child_data *child, const char *fmt, ...)
+void __attribute__((format(printf,2,3))) send_msg(struct child_data *child, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
@@ -381,6 +381,39 @@ static void req_listusers(unsigned char *data, size_t datalen, struct child_data
}
}
+static void req_execverb(unsigned char *data, size_t datalen, struct child_data *sender)
+{
+ (void) datalen;
+ /* first look for a room-local verb */
+ char *save;
+ char *tok = strtok_r((char*)data, " \t", &save);
+
+ all_lower(tok);
+
+ debugf("server: exec verb '%s'\n", tok);
+
+ void *local_map = room_verb_map(sender->room);
+ struct verb_t *verb = hash_lookup(local_map, tok);
+ if(verb)
+ goto exec_verb;
+
+ /* now search the global map */
+ void *global_map = world_verb_map();
+ verb = hash_lookup(global_map, tok);
+
+ if(verb)
+ goto exec_verb;
+
+ send_msg(sender, "I don't know how to do that.\n");
+ return;
+
+ char *args;
+exec_verb:
+ args = strtok_r(NULL, "", &save);
+ debugf("args is %s\n", args);
+ verb->class->hook_exec(verb, args, sender);
+}
+
static const struct child_request {
unsigned char code;
@@ -402,9 +435,6 @@ static const struct child_request {
{ REQ_KICK, true, CHILD_ALL, req_kick_client, NULL, },
{ REQ_KICKALL, true, CHILD_ALL_BUT_SENDER, req_kick_always, NULL, },
{ REQ_LISTCLIENTS, false, CHILD_ALL, req_send_clientinfo, req_send_geninfo, },
- { REQ_WAIT, false, CHILD_NONE, NULL, req_wait, },
- { REQ_GETROOMDESC, false, CHILD_NONE, NULL, req_send_desc, },
- { REQ_GETROOMNAME, false, CHILD_NONE, NULL, req_send_roomname, },
{ REQ_SETROOM, true, CHILD_NONE, NULL, req_set_room, },
{ REQ_MOVE, true, CHILD_NONE, NULL, req_move_room, },
{ REQ_GETUSERDATA, true, CHILD_NONE, NULL, req_send_user, },
@@ -412,8 +442,12 @@ static const struct child_request {
{ REQ_ADDUSERDATA, true, CHILD_NONE, NULL, req_add_user, },
{ REQ_LOOKAT, true, CHILD_NONE, NULL, req_look_at, },
{ REQ_TAKE, true, CHILD_NONE, NULL, req_take, },
- { REQ_PRINTINVENTORY, false, CHILD_NONE, NULL, req_inventory, },
{ REQ_DROP, true, CHILD_NONE, NULL, req_drop, },
+ { REQ_EXECVERB, true, CHILD_NONE, NULL, req_execverb },
+ { REQ_WAIT, false, CHILD_NONE, NULL, req_wait, },
+ { REQ_GETROOMDESC, false, CHILD_NONE, NULL, req_send_desc, },
+ { REQ_GETROOMNAME, false, CHILD_NONE, NULL, req_send_roomname, },
+ { REQ_PRINTINVENTORY, false, CHILD_NONE, NULL, req_inventory, },
{ REQ_LISTUSERS, false, CHILD_NONE, NULL, req_listusers },
//{ REQ_ROOMMSG, true, CHILD_ALL, req_send_room_msg, NULL, },
};
diff --git a/src/server_reqs.h b/src/server_reqs.h
index 3401ba2..5137f9d 100644
--- a/src/server_reqs.h
+++ b/src/server_reqs.h
@@ -43,6 +43,7 @@
#define REQ_PRINTINVENTORY 21 /* server: print user inventory */
#define REQ_DROP 22 /* server: drop user object if allowed */
#define REQ_LISTUSERS 23 /* server: list users in USERFILE */
+#define REQ_EXECVERB 24 /* server: execute a verb with its arguments */
/* child states, sent as an int to the master */
#define STATE_INIT 0 /* initial state */
@@ -56,3 +57,5 @@ bool handle_child_req(int in_fd);
void master_ack_handler(int s, siginfo_t *info, void *v);
void reqmap_init(void);
void reqmap_free(void);
+
+void send_msg(user_t *child, const char *fmt, ...) __attribute__((format(printf,2,3)));
diff --git a/src/userdb.c b/src/userdb.c
index 3d42133..b434e54 100644
--- a/src/userdb.c
+++ b/src/userdb.c
@@ -26,27 +26,18 @@
static void *map = NULL;
static char *db_file = NULL;
-static void free_userdata_and_objs(void *ptr)
+static void free_userdata(void *ptr)
{
struct userdata_t *data = ptr;
if(data->objects)
{
- hash_setfreedata_cb(data->objects, obj_free);
hash_free(data->objects);
data->objects = NULL;
}
free(data);
}
-static void free_userdata(void *ptr)
-{
- struct userdata_t *data = ptr;
-
- hash_free(data->objects);
- free(data);
-}
-
/*
* the user DB is stored on disk as an binary flat file
*
@@ -87,6 +78,9 @@ void userdb_init(const char *file)
hash_djb,
compare_strings);
+ hash_setfreedata_cb(data->objects, obj_free);
+ hash_setdupdata_cb(data->objects, (void*(*)(void*))obj_dup);
+
for(unsigned i = 0; i < n_objects; ++i)
{
struct object_t *obj = obj_read(fd);
@@ -163,6 +157,7 @@ struct userdata_t *userdb_add(struct userdata_t *data)
/* don't overwrite their inventory */
struct userdata_t *old = userdb_lookup(new->username);
+
if(old && old->objects)
new->objects = hash_dup(old->objects);
else
@@ -188,7 +183,6 @@ void userdb_shutdown(void)
if(map)
{
- hash_setfreedata_cb(map, free_userdata_and_objs);
hash_free(map);
map = NULL;
}
diff --git a/src/util.c b/src/util.c
index 85914ca..ffeae79 100644
--- a/src/util.c
+++ b/src/util.c
@@ -132,3 +132,17 @@ void write_uint32(int fd, uint32_t b)
if(write(fd, &b, sizeof(b)) != sizeof(b))
error("write failed");
}
+
+size_t read_size(int fd)
+{
+ size_t ret;
+ if(read(fd, &ret, sizeof(ret)) != sizeof(ret))
+ error("unexpected EOF");
+ return ret;
+}
+
+void write_size(int fd, size_t b)
+{
+ if(write(fd, &b, sizeof(b)) != sizeof(b))
+ error("write failed");
+}
diff --git a/src/util.h b/src/util.h
index 02030c2..4c4824e 100644
--- a/src/util.h
+++ b/src/util.h
@@ -39,3 +39,6 @@ bool read_bool(int fd);
void write_uint32(int fd, uint32_t i);
uint32_t read_uint32(int fd);
+
+void write_size(int fd, size_t);
+size_t read_size(int fd);
diff --git a/src/verb.c b/src/verb.c
new file mode 100644
index 0000000..50af46f
--- /dev/null
+++ b/src/verb.c
@@ -0,0 +1,82 @@
+/*
+ * NetCosm - a MUD server
+ * Copyright (C) 2016 Franklin Wei
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "globals.h"
+
+#include "hash.h"
+#include "verb.h"
+#include "world.h"
+
+static void *map = NULL;
+
+struct verb_t *verb_new(const char *class_name)
+{
+ if(!map)
+ {
+ map = hash_init(netcosm_verb_classes_sz,
+ hash_djb,
+ compare_strings);
+
+ for(unsigned i = 0; i < netcosm_verb_classes_sz; ++i)
+ {
+ if(hash_insert(map, netcosm_verb_classes[i].class_name,
+ netcosm_verb_classes + i))
+ error("duplicate verb class '%s'", netcosm_verb_classes[i].class_name);
+ }
+ }
+
+ struct verb_t *new = calloc(1, sizeof(struct verb_t));
+
+ new->class = hash_lookup(map, class_name);
+ if(!new->class)
+ error("world module attempted to instantiate a verb of unknown class '%s'", class_name);
+
+ return new;
+}
+
+void verb_write(int fd, struct verb_t *verb)
+{
+ write_string(fd, verb->class->class_name);
+ write_string(fd, verb->name);
+}
+
+struct verb_t *verb_read(int fd)
+{
+ char *class_name = read_string(fd);
+ struct verb_t *ret = verb_new(class_name);
+ free(class_name);
+
+ ret->name = read_string(fd);
+ return ret;
+}
+
+void verb_free(void *ptr)
+{
+ struct verb_t *verb = ptr;
+ free(verb->name);
+ free(verb);
+}
+
+void verb_shutdown(void)
+{
+ if(map)
+ {
+ hash_free(map);
+ map = NULL;
+ }
+}
diff --git a/src/verb.h b/src/verb.h
new file mode 100644
index 0000000..cdfbe98
--- /dev/null
+++ b/src/verb.h
@@ -0,0 +1,55 @@
+/*
+ * NetCosm - a MUD server
+ * Copyright (C) 2016 Franklin Wei
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "globals.h"
+
+#include "room.h"
+
+/* the verb API is modeled after that of obj_*, this allows for
+ * dynamic creation/deletion of verbs, but is also easily
+ * serializable.
+ *
+ * so, all verbs are part of a verb class, which has all of its
+ * callbacks.
+ */
+
+struct verb_t;
+struct verb_class_t {
+ const char *class_name;
+
+ void (*hook_exec)(struct verb_t*, char *args, user_t *user);
+};
+
+struct verb_t {
+ char *name;
+
+ struct verb_class_t *class;
+};
+
+struct verb_t *verb_new(const char *class);
+
+void verb_write(int fd, struct verb_t*);
+
+struct verb_t *verb_read(int fd);
+
+void verb_free(void *verb);
+
+/* free the verb_ module's internal data structures */
+void verb_shutdown(void);
diff --git a/src/world.c b/src/world.c
new file mode 100644
index 0000000..cd46c4d
--- /dev/null
+++ b/src/world.c
@@ -0,0 +1,340 @@
+/*
+ * NetCosm - a MUD server
+ * Copyright (C) 2016 Franklin Wei
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "globals.h"
+
+#include "hash.h"
+#include "room.h"
+#include "world.h"
+#include "userdb.h"
+
+/* processed world data */
+
+static struct room_t *world;
+static size_t world_sz;
+static char *world_name;
+
+struct room_t *room_get(room_id id)
+{
+ return world + id;
+}
+
+void world_save(const char *fname)
+{
+ int fd = open(fname, O_CREAT | O_WRONLY, 0644);
+ write_uint32(fd, WORLD_MAGIC);
+ write(fd, &world_sz, sizeof(world_sz));
+ write_string(fd, world_name);
+
+ /* write all the global verbs */
+ void *global_verbs = world_verb_map();
+ size_t n_global_verbs = hash_size(global_verbs);
+ write(fd, &n_global_verbs, sizeof(n_global_verbs));
+
+ if(n_global_verbs)
+ {
+ void *save;
+
+ while(1)
+ {
+ struct verb_t *verb = hash_iterate(global_verbs, &save, NULL);
+ if(!verb)
+ break;
+ global_verbs = NULL;
+ verb_write(fd, verb);
+ }
+ }
+
+ for(unsigned i = 0; i < world_sz; ++i)
+ {
+ write_roomid(fd, &world[i].id);
+ /* unique ID never changes */
+ //write_string(fd, world[i].data.uniq_id);
+ write_string(fd, world[i].data.name);
+ write_string(fd, world[i].data.desc);
+ /* adjacency strings not serialized, only adjacent IDs */
+
+ /* callbacks are static, so are not serialized */
+
+ write(fd, world[i].adjacent, sizeof(world[i].adjacent));
+
+ /* now we serialize all the objects in this room */
+
+ size_t n_objects = room_obj_count(i);
+ write(fd, &n_objects, sizeof(n_objects));
+
+ room_id id = i;
+ void *save;
+ while(1)
+ {
+ struct object_t *obj = room_obj_iterate(id, &save);
+ if(!obj)
+ break;
+ id = ROOM_NONE;
+ obj_write(fd, obj);
+ }
+
+ /* and now all the verbs... */
+
+ void *verb_map = room_verb_map(i);
+ size_t n_verbs = hash_size(verb_map);
+ write_size(fd, n_verbs);
+ while(1)
+ {
+ struct verb_t *verb = hash_iterate(verb_map, &save, NULL);
+ if(!verb)
+ break;
+ verb_map = NULL;
+ verb_write(fd, verb);
+ }
+ }
+ close(fd);
+}
+
+void world_free(void)
+{
+ if(world)
+ {
+ if(world_name)
+ free(world_name);
+ for(unsigned i = 0; i < world_sz; ++i)
+ {
+ room_free(world + i);
+ }
+
+ hash_free(world_verb_map());
+
+ free(world);
+ world = NULL;
+ }
+}
+
+/**
+ * Loads a world using data on disk and in memory.
+ *
+ * @param fname world file
+ * @param data world module data
+ * @param data_sz number of rooms in world module
+ */
+bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz, const char *name)
+{
+ int fd = open(fname, O_RDONLY);
+ if(fd < 0)
+ return false;
+
+ if(read_uint32(fd) != WORLD_MAGIC)
+ return false;
+
+ if(world)
+ world_free();
+
+ read(fd, &world_sz, sizeof(world_sz));
+
+ if(world_sz != data_sz)
+ return false;
+
+ world = calloc(world_sz, sizeof(struct room_t));
+
+ world_name = read_string(fd);
+ if(strcmp(name, world_name))
+ {
+ free(world_name);
+ debugf("Incompatible world state.\n");
+ return false;
+ }
+
+ size_t n_global_verbs = read_size(fd);
+ for(unsigned i = 0; i < n_global_verbs; ++i)
+ {
+ struct verb_t *verb = verb_read(fd);
+ if(!world_verb_add(verb))
+ error("read duplicate global verb '%s'", verb->name);
+ }
+
+ for(unsigned i = 0; i < world_sz; ++i)
+ {
+ room_init_maps(world + i);
+
+ world[i].id = read_roomid(fd);
+ memcpy(&world[i].data, data + i, sizeof(struct roomdata_t));
+ world[i].data.name = read_string(fd);
+ world[i].data.desc = read_string(fd);
+ if(read(fd, world[i].adjacent, sizeof(world[i].adjacent)) < 0)
+ return false;
+
+ size_t n_objects;
+ if(read(fd, &n_objects, sizeof(n_objects)) != sizeof(n_objects))
+ error("world file corrupt");
+
+ for(unsigned j = 0; j < n_objects; ++j)
+ {
+ struct object_t *obj = obj_read(fd);
+
+ if(!room_obj_add(i, obj))
+ error("duplicate object name in room '%s'", world[i].data.name);
+ }
+
+ /* read room-local verbs */
+ size_t n_verbs = read_size(fd);
+ for(unsigned j = 0; j < n_verbs; ++j)
+ {
+ struct verb_t *verb = verb_read(fd);
+ if(!room_verb_add(i, verb))
+ error("duplicate verb '%s' in room '%s'", verb->name,
+ world[i].data.name);
+ }
+ }
+
+ close(fd);
+ return true;
+}
+
+static SIMP_HASH(enum direction_t, dir_hash);
+static SIMP_EQUAL(enum direction_t, dir_equal);
+
+/* loads room data (supplied by the world module) into our internal format */
+void world_init(const struct roomdata_t *data, size_t sz, const char *name)
+{
+ debugf("Loading world with %lu rooms.\n", sz);
+ world = calloc(sz, sizeof(struct room_t));
+ world_sz = 0;
+ world_name = strdup(name);
+
+ void *map = hash_init(sz / 2 + 1, hash_djb, compare_strings);
+
+ for(size_t i = 0; i < sz; ++i)
+ {
+ world[i].id = i;
+ memcpy(&world[i].data, &data[i], sizeof(data[i]));
+
+ /* have to strdup these strings so they can be freed later */
+ //world[i].uniq_id = strdup(world[i].uniq_id);
+ world[i].data.name = strdup(world[i].data.name);
+ world[i].data.desc = strdup(world[i].data.desc);
+ debugf("Loading room '%s'\n", world[i].data.uniq_id);
+
+ if(hash_insert(map, world[i].data.uniq_id, world + i))
+ error("Duplicate room ID '%s'", world[i].data.uniq_id);
+
+ for(int dir = 0; dir < NUM_DIRECTIONS; ++dir)
+ {
+ const char *adjacent_room = world[i].data.adjacent[dir];
+ if(adjacent_room)
+ {
+ struct room_t *room = hash_lookup(map, adjacent_room);
+ if(room)
+ world[i].adjacent[dir] = room->id;
+ }
+ }
+ room_init_maps(world + i);
+
+ world_sz = i + 1;
+ }
+
+ /* second pass to fill in missing references */
+ for(size_t i = 0; i < sz; ++i)
+ {
+ for(int dir = 0; dir < NUM_DIRECTIONS; ++dir)
+ {
+ const char *adjacent_room = world[i].data.adjacent[dir];
+ if(adjacent_room)
+ {
+ struct room_t *room = hash_lookup(map, adjacent_room);
+ if(room)
+ world[i].adjacent[dir] = room->id;
+ else
+ error("unknown room '%s' referenced from '%s'",
+ adjacent_room, world[i].data.uniq_id);
+ }
+ else
+ world[i].adjacent[dir] = ROOM_NONE;
+ }
+ }
+
+ struct direction_pair {
+ enum direction_t dir, opp;
+ } pairs[] = {
+ { DIR_N, DIR_S },
+ { DIR_NE, DIR_SW },
+ { DIR_E, DIR_W },
+ { DIR_SE, DIR_NW },
+ { DIR_UP, DIR_DN },
+ { DIR_IN, DIR_OT },
+ };
+
+ void *dir_map = hash_init(ARRAYLEN(pairs) * 2, dir_hash, dir_equal);
+ for(int n = 0; n < 2; ++n)
+ {
+ for(unsigned i = 0; i < ARRAYLEN(pairs); ++i)
+ {
+ if(!n)
+ hash_insert(dir_map, &pairs[i].dir, &pairs[i].opp);
+ else
+ hash_insert(dir_map, &pairs[i].opp, &pairs[i].dir);
+ }
+ }
+
+ /* third pass to call all the init handlers and check accessibility */
+ for(room_id i = 0; i < (int)world_sz; ++i)
+ {
+ if(world[i].data.hook_init)
+ world[i].data.hook_init(world[i].id);
+ /* check that all rooms are accessible */
+ for(enum direction_t j = 0; j < NUM_DIRECTIONS; ++j)
+ {
+ if(world[i].adjacent[j] != ROOM_NONE)
+ {
+ enum direction_t *opp = hash_lookup(dir_map, &j);
+ struct room_t *adj = room_get(world[i].adjacent[j]);
+ if(adj->adjacent[*opp] != i)
+ debugf("WARNING: Rooms '%s' and '%s' are one-way\n",
+ world[i].data.uniq_id, adj->data.uniq_id);
+ }
+ }
+ }
+
+ hash_free(dir_map);
+
+ hash_free(map);
+}
+
+static void *verb_map = NULL;
+
+#define VERBMAP_SZ 32
+
+static void init_map(void)
+{
+ if(!verb_map)
+ {
+ verb_map = hash_init(VERBMAP_SZ, hash_djb, compare_strings);
+ hash_setfreedata_cb(verb_map, verb_free);
+ }
+}
+
+bool world_verb_add(struct verb_t *verb)
+{
+ init_map();
+ debugf("Added global verb %s\n", verb->name);
+ return !hash_insert(verb_map, verb->name, verb);
+}
+
+void *world_verb_map(void)
+{
+ init_map();
+ return verb_map;
+}
diff --git a/src/world.h b/src/world.h
new file mode 100644
index 0000000..5d9ccae
--- /dev/null
+++ b/src/world.h
@@ -0,0 +1,61 @@
+/*
+ * NetCosm - a MUD server
+ * Copyright (C) 2016 Franklin Wei
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "globals.h"
+
+#include "obj.h"
+#include "room.h"
+#include "verb.h"
+
+/* the world module MUST define all of the following: */
+
+/* verb classes */
+extern const struct verb_class_t netcosm_verb_classes[];
+extern const size_t netcosm_verb_classes_sz;
+
+/* object classes */
+extern const struct obj_class_t netcosm_obj_classes[];
+extern const size_t netcosm_obj_classes_sz;
+
+/* rooms */
+extern const struct roomdata_t netcosm_world[];
+extern const size_t netcosm_world_sz;
+
+extern const char *netcosm_world_name;
+
+/*** loads the world into RAM for the first time, resets the game state ***/
+void world_init(const struct roomdata_t *data, size_t sz, const char *name);
+
+void world_save(const char *fname);
+
+/* loads the world from disk */
+bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz, const char *world_name);
+
+/** verbs **/
+bool world_verb_add(struct verb_t*);
+bool world_verb_del(struct verb_t*);
+
+/* gets the map of verbs */
+void *world_verb_map(void);
+
+void world_free(void);
+
+/* this goes in world_ and not room_ */
+struct room_t *room_get(room_id id);