diff options
| author | Franklin Wei <frankhwei536@gmail.com> | 2016-01-31 19:53:45 -0500 |
|---|---|---|
| committer | Franklin Wei <frankhwei536@gmail.com> | 2016-01-31 19:53:45 -0500 |
| commit | 0730fc3924dd4e04efbe51287d1d69850404d05f (patch) | |
| tree | 495d79d0dc26e39c9065c6ceb7d16b9a3e76561d /src | |
| parent | 8405274a91e3652ee98a423608a8496ead1edc05 (diff) | |
| download | netcosm-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.c | 26 | ||||
| -rw-r--r-- | src/globals.h | 2 | ||||
| -rw-r--r-- | src/hash.c | 9 | ||||
| -rw-r--r-- | src/hash.h | 3 | ||||
| -rw-r--r-- | src/obj.c | 21 | ||||
| -rw-r--r-- | src/obj.h | 12 | ||||
| -rw-r--r-- | src/room.c | 286 | ||||
| -rw-r--r-- | src/room.h | 26 | ||||
| -rw-r--r-- | src/server.c | 31 | ||||
| -rw-r--r-- | src/server_reqs.c | 46 | ||||
| -rw-r--r-- | src/server_reqs.h | 3 | ||||
| -rw-r--r-- | src/userdb.c | 16 | ||||
| -rw-r--r-- | src/util.c | 14 | ||||
| -rw-r--r-- | src/util.h | 3 | ||||
| -rw-r--r-- | src/verb.c | 82 | ||||
| -rw-r--r-- | src/verb.h | 55 | ||||
| -rw-r--r-- | src/world.c | 340 | ||||
| -rw-r--r-- | src/world.h | 61 |
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 @@ -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; +} @@ -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*)); @@ -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) @@ -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 */ @@ -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); } @@ -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; } @@ -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"); +} @@ -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); |