From ed95a5621ac9c4f5002e68a981f8b24d5caaedf4 Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Mon, 28 Mar 2016 14:11:22 -0400 Subject: kludge things to compile on old linux --- Makefile | 4 +- README.md | 12 +- export/include/world_api.h | 1 + src/auth.c | 5 +- src/auth.h | 6 - src/client.c | 70 +++++++++-- src/client.h | 4 - src/client_reqs.c | 14 ++- src/client_reqs.h | 2 + src/globals.h | 5 +- src/hash.c | 1 + src/obj.c | 6 +- src/obj.h | 5 +- src/room.h | 22 ++-- src/server.c | 48 ++++++-- src/server.h | 7 +- src/server_reqs.c | 56 +++++---- src/server_reqs.h | 7 +- src/userdb.c | 5 +- src/util.c | 90 ++++++++++++++ src/util.h | 7 ++ src/verb.h | 4 +- src/world.c | 50 ++++++-- src/world.h | 6 +- tests/all.sh | 0 tests/gen_data.sh | 0 worlds/test.c | 289 +++++++++++++++++++++++++++++++++++++++++---- 27 files changed, 602 insertions(+), 124 deletions(-) mode change 100644 => 100755 tests/all.sh mode change 100644 => 100755 tests/gen_data.sh diff --git a/Makefile b/Makefile index 692a0ce..f9fc032 100644 --- a/Makefile +++ b/Makefile @@ -34,12 +34,12 @@ INCLUDES = -I src/ -I export/include/ WARNFLAGS = -Wall -Wextra -Wshadow -fno-strict-aliasing -OPTFLAGS = -Og +OPTFLAGS = -O2 DEBUGFLAGS = -g CFLAGS = $(OPTFLAGS) $(DEBUGFLAGS) $(WARNFLAGS) -std=c99 $(INCLUDES) -LDFLAGS = -lev -lcrypto -lbsd +LDFLAGS = -lev -lcrypto HEADERS = src/*.h export/include/*.h diff --git a/README.md b/README.md index f7144b8..f459f72 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ number. #### 0.4.0 (skipped) -* Object support [DONE, needs testing] +* Object support [DONE] * User inventory support [DONE] @@ -110,7 +110,15 @@ number. * Verb support [DONE] -* World hooks/scripting [PARTIAL] +* World hooks/scripting [DONE] + +##### 0.5.1 + +* Work on dunnet clone + +* API enhancements + +* Many bugfixes #### 0.6.0 diff --git a/export/include/world_api.h b/export/include/world_api.h index d97d9cb..9537c68 100644 --- a/export/include/world_api.h +++ b/export/include/world_api.h @@ -21,6 +21,7 @@ #include "globals.h" #include "server.h" +#include "server_reqs.h" #include "hash.h" #include "multimap.h" #include "userdb.h" diff --git a/src/auth.c b/src/auth.c index 069e9fe..c7b53d3 100644 --- a/src/auth.c +++ b/src/auth.c @@ -193,11 +193,12 @@ struct userdata_t *auth_check(const char *name2, const char *pass2) { debugf("auth module: user %s found\n", name2); - /* hashes are in HEX to avoid the Trucha bug */ + /* hashes are in lowercase hex to avoid the Trucha bug + * but still allow comparison with strcmp() */ char *new_hash_hex = hash_pass_hex(pass, salt); bool success = true; - /* constant-time comparison to a timing attack */ + /* constant-time comparison to hopefully prevent a timing attack */ for(int i = 0; i < AUTH_HASHLEN; ++i) { if(new_hash_hex[i] != hash[i]) diff --git a/src/auth.h b/src/auth.h index ba24595..c5c9dc1 100644 --- a/src/auth.h +++ b/src/auth.h @@ -26,12 +26,6 @@ //#define HASH_ITERS 500000 #define HASH_ITERS 1 -struct authinfo_t { - bool success; - const char *user; - int authlevel; -}; - /* makes admin account */ void first_run_setup(void); diff --git a/src/client.c b/src/client.c index a93ae0e..d66836e 100644 --- a/src/client.c +++ b/src/client.c @@ -20,10 +20,12 @@ #include "auth.h" #include "client.h" +#include "client_reqs.h" #include "hash.h" #include "server.h" #include "room.h" #include "telnet.h" +#include "userdb.h" #include "util.h" bool are_admin = false; @@ -100,6 +102,8 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...) if(is_newline) ++ptr; /* skip the newline */ + + /* skip following spaces */ while(*ptr == ' ') ++ptr; last_space = 0; @@ -184,7 +188,7 @@ tryagain: } } -/* still not encrypted, but a bit more secure than echoing the password! */ +/* still not encrypted, but a bit better than echoing the password! */ char *client_read_password(void) { telnet_echo_off(); @@ -194,8 +198,6 @@ char *client_read_password(void) return ret; } -#define WSPACE " \t\r\n" - #define CMD_OK 0 #define CMD_LOGOUT 1 #define CMD_QUIT 2 @@ -206,7 +208,10 @@ int user_cb(char **save) { char *what = strtok_r(NULL, WSPACE, save); if(!what) + { + out("Usage: USER \n"); return CMD_OK; + } all_upper(what); @@ -304,9 +309,9 @@ int client_cb(char **save) else if(!strcmp(what, "KICK")) { char *pid_s = strtok_r(NULL, WSPACE, save); - all_upper(pid_s); if(pid_s) { + all_upper(pid_s); if(!strcmp(pid_s, "ALL")) { const char *msg = "Kicking everyone...\n"; @@ -336,7 +341,7 @@ int client_cb(char **save) debugf("Success.\n"); } else - out("Usage: CLIENT KICK \n"); + out("Usage: CLIENT KICK \n"); } return CMD_OK; } @@ -402,6 +407,7 @@ int take_cb(char **save) int wait_cb(char **save) { (void) save; + /* debugging */ send_master(REQ_WAIT, NULL, 0); return CMD_OK; } @@ -416,14 +422,61 @@ int go_cb(char **save) client_look(); } else - out("Expected direction after GO.\n"); + out("I don't understand where you want me to go.\n"); return CMD_OK; } int drop_cb(char **save) { char *what = strtok_r(NULL, "", save); - client_drop(what); + if(what) + client_drop(what); + else + out("You must supply an object.\n"); + return CMD_OK; +} + +int chpass_cb(char **save) +{ + out("Enter current password: "); + char *current = client_read_password(); + + struct userdata_t *current_data = auth_check(current_user, current); + + memset(current, 0, strlen(current)); + free(current); + + if(!current_data) + { + out("Password mismatch.\n"); + return CMD_OK; + } + + out("Enter new password: "); + char *pass1 = client_read_password(); + + out("Retype new password: "); + + char *pass2 = client_read_password(); + + if(strcmp(pass1, pass2)) + { + memset(pass1, 0, strlen(pass1)); + memset(pass2, 0, strlen(pass2)); + free(pass1); + free(pass2); + + out("Passwords do not match.\n"); + return CMD_OK; + } + + memset(pass2, 0, strlen(pass2)); + free(pass2); + + auth_user_add(current_user, pass1, current_data->priv); + + memset(pass1, 0, strlen(pass1)); + return CMD_OK; } @@ -442,9 +495,10 @@ static const struct client_cmd { { "LOOK", look_cb, false }, { "INVENTORY", inventory_cb, false }, { "TAKE", take_cb, false }, - { "WAIT", wait_cb, false }, + { "WAIT", wait_cb, true }, { "GO", go_cb, false }, { "DROP", drop_cb, false }, + { "CHPASS", chpass_cb, false }, }; static void *cmd_map = NULL; diff --git a/src/client.h b/src/client.h index 5b5c5ff..223bf86 100644 --- a/src/client.h +++ b/src/client.h @@ -21,10 +21,6 @@ #include "globals.h" -#include "client_reqs.h" -#include "room.h" -#include "userdb.h" - extern int client_fd, to_parent, from_parent; extern bool are_admin; diff --git a/src/client_reqs.c b/src/client_reqs.c index 39a67f9..21174c4 100644 --- a/src/client_reqs.c +++ b/src/client_reqs.c @@ -19,6 +19,7 @@ #include "globals.h" #include "client.h" +#include "client_reqs.h" #include "hash.h" enum reqdata_typespec reqdata_type = TYPE_NONE; @@ -81,9 +82,7 @@ bool poll_requests(void) int status = *((int*)data); reqdata_type = TYPE_BOOLEAN; - returned_reqdata.boolean = status; - if(!status) - out("You cannot go that way.\n"); + returned_reqdata.boolean = (status == 1); break; } case REQ_GETUSERDATA: @@ -266,8 +265,13 @@ void client_look_at(char *obj) void client_take(char *obj) { - all_lower(obj); - send_master(REQ_TAKE, obj, strlen(obj) + 1); + if(obj) + { + all_lower(obj); + send_master(REQ_TAKE, obj, strlen(obj) + 1); + } + else + out("You must supply an object.\n"); } void client_inventory(void) diff --git a/src/client_reqs.h b/src/client_reqs.h index 979da9a..0d2a63c 100644 --- a/src/client_reqs.h +++ b/src/client_reqs.h @@ -24,6 +24,8 @@ #include "server_reqs.h" #include "userdb.h" +enum room_id; + enum reqdata_typespec { TYPE_NONE = 0, TYPE_USERDATA, TYPE_BOOLEAN } reqdata_type; union reqdata_t { diff --git a/src/globals.h b/src/globals.h index 987d382..5a164ba 100644 --- a/src/globals.h +++ b/src/globals.h @@ -22,16 +22,17 @@ #define _GNU_SOURCE #include + #include #include #include #include #include -#include // for strlcat #include #include #include +#include #include #include #include @@ -66,7 +67,7 @@ #define WORLD_MAGIC 0x31415926 #define USERDB_MAGIC 0x27182818 #define MAX_FAILURES 3 -#define NETCOSM_VERSION "0.5.0" +#define NETCOSM_VERSION "0.5.1" /* username length */ #define MAX_NAME_LEN 32 diff --git a/src/hash.c b/src/hash.c index 290c668..ce91ceb 100644 --- a/src/hash.c +++ b/src/hash.c @@ -357,6 +357,7 @@ struct hash_export_node hash_get_internal_node(void *ptr, const void *key) } struct hash_export_node ret; + memset(&ret, 0, sizeof(ret)); ret.node = NULL; return ret; } diff --git a/src/obj.c b/src/obj.c index deeb30d..296fbb5 100644 --- a/src/obj.c +++ b/src/obj.c @@ -144,7 +144,7 @@ struct object_t *obj_copy(struct object_t *obj) struct object_t *obj_dup(struct object_t *obj) { - debugf("Adding an object reference to #%lu.\n", obj->id); + debugf("Adding an object reference to #%" PRI_OBJID ".\n", obj->id); ++obj->refcount; return obj; } @@ -154,11 +154,11 @@ void obj_free(void *ptr) struct object_t *obj = ptr; --obj->refcount; - debugf("Freeing an object reference for #%lu (%s, %d).\n", obj->id, obj->name, obj->refcount); + debugf("Freeing an object reference for #%" PRI_OBJID" (%s, %d).\n", obj->id, obj->name, obj->refcount); if(!obj->refcount) { - debugf("Freeing object #%lu\n", obj->id); + debugf("Freeing object #%"PRI_OBJID"\n", obj->id); if(obj->class->hook_destroy) obj->class->hook_destroy(obj); diff --git a/src/obj.h b/src/obj.h index 121e3ab..3858d78 100644 --- a/src/obj.h +++ b/src/obj.h @@ -21,14 +21,13 @@ #include "globals.h" #include "room.h" +#include "server.h" /* Objects belong to an object class. Objects define their object * class through the class name, which is converted to a class ID * internally. */ -typedef struct child_data user_t; - struct object_t; struct obj_class_t { @@ -55,6 +54,8 @@ struct obj_class_t { typedef uint64_t obj_id; +#define PRI_OBJID PRId64 + struct obj_alias_t { char *alias; struct obj_alias_t *next; diff --git a/src/room.h b/src/room.h index e9e1953..c0304c7 100644 --- a/src/room.h +++ b/src/room.h @@ -20,15 +20,14 @@ #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 - are added by hooks in rooms, which are provided by the world - module. */ + * them, as well as actions that can be performed in them + * (verbs). Objects are added by hooks in rooms, which are provided as + * function pointers by the world module. */ -typedef struct child_data user_t; // definition of child_data in server.h +struct child_data; +struct object_t; +struct verb_t; typedef enum room_id { ROOM_NONE = -1 } room_id; @@ -43,6 +42,7 @@ struct roomdata_t { char *name; char *desc; + /* unmutable properties, changes will have no effect */ const char * const adjacent[NUM_DIRECTIONS]; void (* const hook_init)(room_id id); @@ -50,8 +50,12 @@ struct roomdata_t { /* return values indicate permission to enter/leave, * setting to NULL defaults to true. */ - bool (* const hook_enter)(room_id room, user_t *user); - bool (* const hook_leave)(room_id room, user_t *user); + + /* NOTE: struct child_data is aliased as "user_t"!!! */ + + /* return false to deny entering/leaving a room */ + bool (* const hook_enter)(room_id room, struct child_data *user); + bool (* const hook_leave)(room_id room, struct child_data *user); void (* const hook_serialize)(room_id room, int fd); void (* const hook_deserialize)(room_id room, int fd); void (* const hook_destroy)(room_id room); diff --git a/src/server.c b/src/server.c index e42483c..6d90c61 100644 --- a/src/server.c +++ b/src/server.c @@ -21,6 +21,7 @@ #include "client.h" #include "hash.h" #include "server.h" +#include "server_reqs.h" #include "userdb.h" #include "util.h" #include "world.h" @@ -254,10 +255,34 @@ static void new_connection_cb(EV_P_ ev_io *w, int revents) int readpipe[2]; /* child->parent */ int outpipe [2]; /* parent->child */ + /* try several methods to create a packet pipe between the child and master: */ + + /* first try creating a pipe in "packet mode": see pipe(2) */ if(pipe2(readpipe, O_DIRECT) < 0) - error("error creating pipe, need linux kernel >= 3.4"); + { + /* then try a SOCK_SEQPACKET socket pair: see unix(7) */ + if(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, readpipe) < 0) + { + /* if that failed, try a SOCK_DGRAM socket as a last resort */ + if(socketpair(AF_UNIX, SOCK_DGRAM, 0, readpipe) < 0) + error("couldn't create child-master communication pipe"); + else + debugf("WARNING: Using a SOCK_DGRAM socket pair for IPC, performance may be degraded.\n"); + } + else + debugf("Using a SOCK_SEQPACKET socket pair for IPC.\n"); + } + else + debugf("Using a packet-mode pipe for IPC.\n"); + if(pipe2(outpipe, O_DIRECT) < 0) - error("error creating pipe, need linux kernel >= 3.4"); + { + if(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, outpipe) < 0) + { + if(socketpair(AF_UNIX, SOCK_DGRAM, 0, outpipe) < 0) + error("error creating pipe, need linux kernel >= 3.4"); + } + } pid_t pid = fork(); if(pid < 0) @@ -328,6 +353,7 @@ static void init_signals(void) { struct sigaction sa; + /* SIGINT and SIGTERM cause graceful shutdown */ sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGINT); sa.sa_handler = sigint_handler; @@ -337,6 +363,7 @@ static void init_signals(void) if(sigaction(SIGTERM, &sa, NULL) < 0) error("sigaction"); + /* ignore SIGPIPE */ sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; @@ -344,7 +371,7 @@ static void init_signals(void) error("sigaction"); /* libev's default SIGCHLD handler exhibits some really strange - * behavior, which we don't like, so we use our own ;) */ + * behavior, which we don't like, so we use our own */ sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGCHLD); sa.sa_handler = sigchld_handler; @@ -366,7 +393,7 @@ static void parse_args(int argc, char *argv[]) switch(c) { case 'h': /* help */ - debugf("FIXME: usage message"); + debugf("Usage: %s [-d PREFIX] [-a ]\n", argv[0]); exit(0); case 'a': /* automatic first-run config */ autoconfig = true; @@ -392,7 +419,7 @@ static SIMP_EQUAL(pid_t, pid_equal); static void check_libs(void) { - debugf("*** Starting NetCosm %s (libev %d.%d, %s) ***\n", + debugf("*** NetCosm %s (libev %d.%d, %s) ***\n", NETCOSM_VERSION, EV_VERSION_MAJOR, EV_VERSION_MINOR, OPENSSL_VERSION_TEXT); assert(ev_version_major() == EV_VERSION_MAJOR && ev_version_minor() >= EV_VERSION_MINOR); @@ -404,29 +431,32 @@ int server_main(int argc, char *argv[]) parse_args(argc, argv); - server_socket = server_bind(); - userdb_init(USERFILE); + /* also performs first-time setup: */ check_userfile(); + load_worldfile(); + /* initialize request map */ reqmap_init(); /* save some time after a fork() */ client_init(); - /* this initial size very low to make iteration faster */ + /* this initial size is set very low to make iteration faster */ child_map = hash_init(16, pid_hash, pid_equal); hash_setfreedata_cb(child_map, free_child_data); hash_setfreekey_cb(child_map, free); debugf("Listening on port %d\n", port); + server_socket = server_bind(); + struct ev_loop *loop = ev_default_loop(0); /* we initialize signals after creating the default event loop - * because libev grabs SIGCHLD */ + * because libev grabs SIGCHLD in the process */ init_signals(); ev_io server_watcher; diff --git a/src/server.h b/src/server.h index 95e537a..6d3d029 100644 --- a/src/server.h +++ b/src/server.h @@ -19,12 +19,9 @@ /* You should use #pragma once everywhere. */ #pragma once -#include -#include -#include +#include "globals.h" -#include "server_reqs.h" -#include "room.h" +enum room_id; /* everything the server needs to manage its children */ struct child_data { diff --git a/src/server_reqs.c b/src/server_reqs.c index 4d89ca4..21787f4 100644 --- a/src/server_reqs.c +++ b/src/server_reqs.c @@ -21,10 +21,11 @@ #include "hash.h" #include "multimap.h" #include "server.h" +#include "server_reqs.h" #include "userdb.h" #include "world.h" -/* sends a single packet to a child, virtually guarantees receipt */ +/* sends a single packet to a child, mostly reliable */ static void send_packet(struct child_data *child, unsigned char cmd, const void *data, size_t datalen) { @@ -188,7 +189,7 @@ static void req_send_desc(unsigned char *data, size_t datalen, struct child_data { strlcat(buf, "There are ", sizeof(buf)); char n[32]; - snprintf(n, sizeof(n), "%lu ", n_objs); + snprintf(n, sizeof(n), "%zu ", n_objs); strlcat(buf, n, sizeof(buf)); strlcat(buf, name, sizeof(buf)); strlcat(buf, "s here.\n", sizeof(buf)); @@ -229,18 +230,33 @@ static void req_set_room(unsigned char *data, size_t datalen, struct child_data static void req_move_room(unsigned char *data, size_t datalen, struct child_data *sender) { (void) data; (void) datalen; (void) sender; + + int status = 0; + enum direction_t dir = *((enum direction_t*)data); struct room_t *current = room_get(sender->room); - room_user_del(sender->room, sender); - - /* TODO: checking */ + /* TODO: bounds checking on `dir' */ room_id new = current->adjacent[dir]; - int status = 0; - if(new != ROOM_NONE) + + if(new == ROOM_NONE) { - child_set_room(sender, new); - status = 1; + send_msg(sender, "You cannot go that way.\n"); + } + else + { + struct room_t *new_room = room_get(new); + + if((!new_room->data.hook_enter || + (new_room->data.hook_enter && new_room->data.hook_enter(new, sender))) && + (!current->data.hook_leave || + (current->data.hook_leave && current->data.hook_leave(sender->room, sender)))) + { + room_user_del(sender->room, sender); + + child_set_room(sender, new); + status = 1; + } } send_packet(sender, REQ_MOVE, &status, sizeof(status)); @@ -403,26 +419,8 @@ static void req_inventory(unsigned char *data, size_t datalen, struct child_data struct object_t *obj = iter->val; if(!strcmp(name, obj->name)) { - if(n_objs == 1) - { - if(obj->default_article) - { - char *article = (is_vowel(name[0])?"An":"A"); - strlcat(buf, article, sizeof(buf)); - strlcat(buf, " ", sizeof(buf)); - } - strlcat(buf, name, sizeof(buf)); - strlcat(buf, "\n", sizeof(buf)); - } - else - { - char n[32]; - snprintf(n, sizeof(n), "%lu", n_objs); - strlcat(buf, n, sizeof(buf)); - strlcat(buf, " ", sizeof(buf)); - strlcat(buf, name, sizeof(buf)); - strlcat(buf, "s\n", sizeof(buf)); - } + format_noun(buf, sizeof(buf), name, n_objs, obj->default_article, true); + strlcat(buf, "\n", sizeof(buf)); send_packet(sender, REQ_BCASTMSG, buf, strlen(buf)); } diff --git a/src/server_reqs.h b/src/server_reqs.h index c77c53a..10c1755 100644 --- a/src/server_reqs.h +++ b/src/server_reqs.h @@ -20,10 +20,10 @@ #include "globals.h" -typedef struct child_data user_t; +#include "server.h" /* child<->master commands */ -/* children might not implement all of these */ +/* not all of these are implemented by both parties */ /* meanings might be different for the server and child, see comments */ #define REQ_NOP 0 /* server, child: do nothing (used for acknowledgement) */ #define REQ_BCASTMSG 1 /* server: broadcast text; child: print following text */ @@ -32,7 +32,7 @@ typedef struct child_data user_t; #define REQ_CHANGEUSER 4 /* server: change child login name */ #define REQ_HANG 5 /* server: loop forever */ #define REQ_KICK 6 /* server: kick PID with message; child: print message, quit */ -#define REQ_WAIT 7 /* server: sleep 10s */ +#define REQ_WAIT 7 /* server: sleep 10s */ #define REQ_GETROOMDESC 8 /* server: send child room description */ #define REQ_SETROOM 9 /* server: set child room */ #define REQ_MOVE 10 /* server: move child based on direction; child: success or failure */ @@ -50,6 +50,7 @@ typedef struct child_data user_t; #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 */ +#define REQ_RAWMODE 25 /* child: toggle the child's processing of commands and instead sending input directly to master */ /* child states, sent as an int to the master */ #define STATE_INIT 0 /* initial state */ diff --git a/src/userdb.c b/src/userdb.c index 101b6d0..0a13319 100644 --- a/src/userdb.c +++ b/src/userdb.c @@ -19,9 +19,12 @@ #include "globals.h" #include "client.h" +#include "client_reqs.h" #include "hash.h" #include "multimap.h" +#include "obj.h" #include "server.h" +#include "server_reqs.h" #include "userdb.h" static void *map = NULL; @@ -229,7 +232,7 @@ void userdb_dump(void) while(iter) { struct object_t *obj = iter->val; - debugf(" - Obj #%lu class %s: name %s\n", obj->id, obj->class->class_name, obj->name); + debugf(" - Obj #%"PRI_OBJID" class %s: name %s\n", obj->id, obj->class->class_name, obj->name); iter = iter->next; } } diff --git a/src/util.c b/src/util.c index 29a5399..9c4b447 100644 --- a/src/util.c +++ b/src/util.c @@ -187,3 +187,93 @@ bool is_vowel(char c) return false; } } + +/* $NetBSD: strlcat.c,v 1.4 2005/05/16 06:55:48 lukem Exp $ */ +/* from NetBSD: strlcat.c,v 1.16 2003/10/27 00:12:42 lukem Exp */ +/* from OpenBSD: strlcat.c,v 1.10 2003/04/12 21:56:39 millert Exp */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} + +char *format_noun(char *buf, size_t len, const char *name, size_t count, bool default_article, bool capitalize) +{ + assert(len > 1); + buf[0] = '\0'; + if(count == 1) + { + if(default_article) + { + char *article = capitalize?(is_vowel(name[0])? "An" : "A"):(is_vowel(name[0])? "an" : "a"); + strlcat(buf, article, len); + strlcat(buf, " ", len); + strlcat(buf, name, len); + } + else + { + char tmp[2]; + tmp[0] = toupper(name[0]); + tmp[1] = '\0'; + strlcat(buf, tmp, len); + strlcat(buf, name + 1, len); + } + } + else + { + char n[32]; + snprintf(n, sizeof(n), "%zu", count); + strlcat(buf, n, len); + strlcat(buf, " ", len); + strlcat(buf, name, len); + strlcat(buf, "s", len); + } + + return buf; +} diff --git a/src/util.h b/src/util.h index 6de6945..8007ec8 100644 --- a/src/util.h +++ b/src/util.h @@ -21,6 +21,8 @@ #include "room.h" +#define WSPACE " \t\r\n" + /* utility functions */ void __attribute__((noreturn,format(printf,1,2))) error(const char *fmt, ...); void __attribute__((format(printf,4,5))) debugf_real(const char*, int, const char*, const char *fmt, ...); @@ -47,3 +49,8 @@ void write_size(int fd, size_t); size_t read_size(int fd); bool is_vowel(char c); + +size_t strlcat(char *dst, const char *src, size_t siz); + +/* formats a noun's name */ +char *format_noun(char *buf, size_t len, const char *name, size_t count, bool default_article, bool capitalize); diff --git a/src/verb.h b/src/verb.h index 348da49..8913962 100644 --- a/src/verb.h +++ b/src/verb.h @@ -21,6 +21,7 @@ #include "globals.h" #include "room.h" +#include "server.h" /* the verb API is modeled after that of obj_*, this allows for * dynamic creation/deletion of verbs, but is also easily @@ -30,9 +31,6 @@ * callbacks. */ -struct child_data; -typedef struct child_data user_t; - struct verb_t; struct verb_class_t { const char *class_name; diff --git a/src/world.c b/src/world.c index a854e49..8c25d7d 100644 --- a/src/world.c +++ b/src/world.c @@ -29,6 +29,9 @@ static struct room_t *world; static size_t world_sz; static char *world_name; +/* map of room names -> rooms */ +static void *world_map = NULL; + struct room_t *room_get(room_id id) { return world + id; @@ -124,8 +127,22 @@ void world_save(const char *fname) world[i].data.hook_serialize(i, fd); } + /* write the object counter so future objects will have sequential ids */ write_uint64(fd, obj_get_idcounter()); + /* now write the map of room names to ids */ + void *ptr = world_map, *save; + for(unsigned int i = 0; i < world_sz; ++i) + { + void *key; + struct room_t *room = hash_iterate(ptr, &save, &key); + if(!room) + break; + ptr = NULL; + write_string(fd, key); + write_roomid(fd, &room->id); + } + close(fd); } @@ -230,6 +247,18 @@ bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz obj_set_idcounter(read_uint64(fd)); + /* read in the room name -> room map */ + + world_map = hash_init(world_sz * 2, hash_djb, compare_strings); + + for(unsigned int i = 0; i < world_sz; ++i) + { + const char *key = read_string(fd); + room_id id = read_roomid(fd); + debugf("'%s' -> %d\n", key, id); + hash_insert(world_map, key, world + id); + } + close(fd); return true; } @@ -240,12 +269,12 @@ 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); + debugf("Loading world with %zu 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); + world_map = hash_init(sz * 2, hash_djb, compare_strings); for(size_t i = 0; i < sz; ++i) { @@ -258,7 +287,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *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)) + if(hash_insert(world_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) @@ -266,7 +295,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name) const char *adjacent_room = world[i].data.adjacent[dir]; if(adjacent_room) { - struct room_t *room = hash_lookup(map, adjacent_room); + struct room_t *room = hash_lookup(world_map, adjacent_room); if(room) world[i].adjacent[dir] = room->id; } @@ -284,7 +313,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name) const char *adjacent_room = world[i].data.adjacent[dir]; if(adjacent_room) { - struct room_t *room = hash_lookup(map, adjacent_room); + struct room_t *room = hash_lookup(world_map, adjacent_room); if(room) world[i].adjacent[dir] = room->id; else @@ -339,8 +368,6 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name) } hash_free(dir_map); - - hash_free(map); } static void *verb_map = NULL; @@ -368,3 +395,12 @@ void *world_verb_map(void) init_map(); return verb_map; } + +room_id room_get_id(const char *uniq_id) +{ + struct room_t *room = hash_lookup(world_map, uniq_id); + if(!room) + return ROOM_NONE; + else + return room->id; +} diff --git a/src/world.h b/src/world.h index 5d9ccae..50f9769 100644 --- a/src/world.h +++ b/src/world.h @@ -48,14 +48,16 @@ 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 **/ +/** global verbs **/ bool world_verb_add(struct verb_t*); bool world_verb_del(struct verb_t*); -/* gets the map of verbs */ +/* gets the map of global 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); + +room_id room_get_id(const char *uniq_id); diff --git a/tests/all.sh b/tests/all.sh old mode 100644 new mode 100755 diff --git a/tests/gen_data.sh b/tests/gen_data.sh old mode 100644 new mode 100755 diff --git a/worlds/test.c b/worlds/test.c index c99fa33..f662c1f 100644 --- a/worlds/test.c +++ b/worlds/test.c @@ -1,5 +1,7 @@ #include +/* implements dunnet in NetCosm */ + /************ ROOM DEFINITIONS ************/ static void deadend_init(room_id id) @@ -7,12 +9,35 @@ static void deadend_init(room_id id) struct object_t *new = obj_new("/generic"); new->name = strdup("shovel"); new->userdata = strdup("It is a normal shovel with a price tag attached that says $19.99."); + room_obj_add(id, new); -#if 0 - new = obj_copy(new); + new = obj_new("/generic/notake"); + new->name = strdup("trees"); + new->userdata = strdup("They are palm trees with a bountiful supply of coconuts in them."); + new->hidden = true; + room_obj_add(id, new); -#endif + room_obj_add_alias(id, new, "tree"); + room_obj_add_alias(id, new, "palm"); + room_obj_add_alias(id, new, "palm tree"); + + /* add global verbs */ + struct verb_t *verb = verb_new("dig"); + verb->name = strdup("dig"); + world_verb_add(verb); + + verb = verb_new("put"); + verb->name = strdup("put"); + world_verb_add(verb); + + verb = verb_new("eat"); + verb->name = strdup("eat"); + world_verb_add(verb); + + verb = verb_new("shake"); + verb->name = strdup("shake"); + world_verb_add(verb); } static void ew_road_init(room_id id) @@ -27,30 +52,26 @@ static void ew_road_init(room_id id) static void fork_init(room_id id) { - struct verb_t *new = verb_new("dig"); - new->name = strdup("dig"); - room_verb_add(id, new); - room_get(id)->userdata = calloc(1, sizeof(bool)); /* flag for whether the user has already dug */ bool *b = room_get(id)->userdata; *b = false; } -static void fork_ser(room_id id, int fd) +static void bool_ser(room_id id, int fd) { bool *b = room_get(id)->userdata; write_bool(fd, *b); } -static void fork_deser(room_id id, int fd) +static void bool_deser(room_id id, int fd) { bool *b = calloc(1, sizeof(bool)); *b = read_bool(fd); room_get(id)->userdata = b; } -static void fork_destroy(room_id id) +static void bool_destroy(room_id id) { free(room_get(id)->userdata); } @@ -84,6 +105,43 @@ static void hidden_init(room_id id) room_obj_add_alias(id, new, "bracelet"); } +static bool building_enter(room_id id, user_t *user) +{ + (void) id; + if(multimap_lookup(userdb_lookup(user->user)->objects, "shiny brass key", NULL)) + return true; + else + { + send_msg(user, "You don't have a key that can open this door.\n"); + return false; + } +} + +static void mailroom_init(room_id id) +{ + struct object_t *new = obj_new("/generic/notake"); + new->name = strdup("bins"); + new->hidden = true; + + /* insert IAC NOP to prevent the extra whitespace from being dropped */ + new->userdata = strdup("All of the bins are empty. Looking closely you can see that there are names written at the bottom of each bin, but most of them are faded away so that you cannot read them. You can only make out three names:\n\377\361 Jeffrey Collier\n\377\361 Robert Toukmond\n\377\361 Thomas Stock\n"); + room_obj_add(id, new); +} + +static void computer_room_init(room_id id) +{ + struct object_t *new = obj_new("/generic/notake"); + new->name = strdup("computer"); + new->userdata = strdup("I see nothing special about that."); + new->hidden = true; + + room_obj_add(id, new); + room_obj_add_alias(id, new, "vax"); + + /* flag for whether computer is active */ + room_get(id)->userdata = malloc(sizeof(bool)); +} + const struct roomdata_t netcosm_world[] = { { "dead_end", @@ -119,9 +177,9 @@ const struct roomdata_t netcosm_world[] = { fork_init, NULL, NULL, - fork_ser, - fork_deser, - fork_destroy, + bool_ser, + bool_deser, + bool_destroy, }, { @@ -180,14 +238,53 @@ const struct roomdata_t netcosm_world[] = { "building_front", "Building Front", "You are at the end of the road. There is a building in front of you to the northeast, and the road leads back to the southwest.", - { NONE_N, NONE_NE, NONE_E, NONE_SE, NONE_S, "nesw_road", NONE_W, NONE_NW, NONE_UP, NONE_DN, NONE_IN, NONE_OT }, + { NONE_N, "building_hallway", NONE_E, NONE_SE, NONE_S, "nesw_road", NONE_W, NONE_NW, NONE_UP, NONE_DN, "building_hallway", NONE_OT }, + NULL, + NULL, NULL, NULL, NULL, NULL, + }, + + { + "building_hallway", + "Old Building hallway", + "You are in the hallway of an old building. There are rooms to the east and west, and doors leading out to the north and south.", + { NONE_N, NONE_NE, "mailroom", NONE_SE, "building_front", NONE_SW, "computer_room", NONE_NW, NONE_UP, NONE_DN, NONE_IN, "building_front" }, + NULL, + building_enter, + NULL, + NULL, NULL, NULL, }, + + { + "mailroom", + "Mailroom", + "You are in a mailroom. There are many bins where the mail is usually kept. The exit is to the west.", + { NONE_N, NONE_NE, NONE_E, NONE_SE, NONE_S, NONE_SW, "building_hallway", NONE_NW, NONE_UP, NONE_DN, NONE_IN, NONE_OT }, + mailroom_init, + NULL, + NULL, + NULL, + NULL, + NULL, + }, + + { + "computer_room", + "Computer room", + "You are in a computer room. It seems like most of the equipment has been removed. There is a VAX 11/780 in front of you, however, with one of the cabinets wide open. A sign on the front of the machine says: This VAX is named 'pokey'. To type on the console, use the 'type' command. The exit is to the east.\nThe panel lights are steady and motionless.", + { NONE_N, NONE_NE, "building_hallway", NONE_SE, NONE_S, NONE_SW, NONE_W, NONE_NW, NONE_UP, NONE_DN, NONE_IN, NONE_OT }, + computer_room_init, + NULL, + NULL, + bool_ser, + bool_deser, + bool_destroy, + }, }; const size_t netcosm_world_sz = ARRAYLEN(netcosm_world); @@ -323,6 +420,8 @@ static void dig_exec(struct verb_t *verb, char *args, user_t *user) goto nothing; } } + else + send_msg(user, "Digging here reveals nothing.\n"); return; @@ -330,19 +429,169 @@ nothing: send_msg(user, "Digging here reveals nothing.\n"); } +static void put_exec(struct verb_t *verb, char *args, user_t *user) +{ + (void) verb; + char *save; + const char *obj_name = strtok_r(args, WSPACE, &save); + + if(!obj_name) + { + send_msg(user, "You must supply an object\n"); + return; + } + + args = NULL; + const struct multimap_list *list = multimap_lookup(userdb_lookup(user->user)->objects, + obj_name, NULL); + if(!list) + { + send_msg(user, "You don't have that.\n"); + return; + } + + struct object_t *obj = list->val; + + /* original dunnet ignores the preposition */ + const char *prep = strtok_r(args, WSPACE, &save); + (void) prep; + + const char *ind_obj_name = strtok_r(args, WSPACE, &save); + + if(!ind_obj_name) + { + send_msg(user, "You must supply an indirect object.\n"); + return; + } + + list = room_obj_get(user->room, ind_obj_name); + + if(!list) + { + send_msg(user, "I don't know what that indirect object is.\n"); + return; + } + + struct object_t *ind_obj = list->val; + + /* now execute the verb */ + if(!strcmp(obj->name, "CPU card") && !strcmp(ind_obj->name, "computer") && user->room == room_get_id("computer_room")) + { + userdb_del_obj_by_ptr(user->user, obj); + send_msg(user, "As you put the CPU board in the computer, it immediately springs to life. The lights start flashing, and the fans seem to startup.\n"); + bool *b = room_get(user->room)->userdata; + *b = true; + + room_get(user->room)->data.desc = strdup("You are in a computer room. It seems like most of the equipment has been removed. There is a VAX 11/780 in front of you, however, with one of the cabinets wide open. A sign on the front of the machine says: This VAX is named 'pokey'. To type on the console, use the 'type' command. The exit is to the east.\nThe panel lights are flashing in a seemingly organized pattern."); + } + else + { + send_msg(user, "I don't know how to combine those objects. Perhaps you should just try dropping it.\n"); + } +} + +static void eat_exec(struct verb_t *verb, char *args, user_t *user) +{ + (void) verb; + char *save; + char *obj_name = strtok_r(args, WSPACE, &save); + if(!obj_name) + { + send_msg(user, "You must supply an object.\n"); + return; + } + + size_t n_objs; + const struct multimap_list *list = multimap_lookup(userdb_lookup(user->user)->objects, obj_name, &n_objs); + + if(!list) + { + if(!room_obj_get(user->room, obj_name)) + send_msg(user, "I don't know what that is.\n"); + else + send_msg(user, "You don't have that.\n"); + return; + } + + struct object_t *obj = list->val; + + if(!strcmp(obj->name, "some food")) + { + send_msg(user, "That tasted horrible.\n"); + } + else + { + char buf[MSG_MAX]; + send_msg(user, "You forcibly shove %s down your throat, and start choking.\n", + format_noun(buf, sizeof(buf), obj->name, n_objs, obj->default_article, false)); + + /* TODO: kill player */ + } + + userdb_del_obj(user->user, obj_name); +} + +static void shake_exec(struct verb_t *verb, char *args, user_t *user) +{ + (void) verb; + char *save; + char *obj_name = strtok_r(args, WSPACE, &save); + + if(!obj_name) + { + send_msg(user, "You must supply an object.\n"); + return; + } + + size_t n_objs_room, n_objs_inv; + const struct multimap_list *list_room = room_obj_get_size(user->room, obj_name, &n_objs_room); + + const struct multimap_list *list_inv = multimap_lookup(userdb_lookup(user->user)->objects, obj_name, &n_objs_inv); + + if(!list_room && !list_inv) + { + send_msg(user, "I don't know what that is.\n"); + return; + } + + if(list_room) + { + struct object_t *obj = list_room->val; + if(!strcmp(obj->name, "trees")) + send_msg(user, "You begin to shake a tree, and notice a coconut begin to fall from the air. As you try to get your hand up to block it, you feel the impact as it lands on your head.\n"); + else + send_msg(user, "You don't have that.\n"); + } + else if(list_inv) + { + struct object_t *obj = list_inv->val; + char buf[MSG_MAX]; + send_msg(user, "Shaking %s seems to have no effect.\n", + format_noun(buf, sizeof(buf), obj->name, + n_objs_room, obj->default_article, + false)); + } +} + +/* verb classes */ + const struct verb_class_t netcosm_verb_classes[] = { { "dig", dig_exec }, - /* - { "shake", - shake_exec }, - { "climb", - climb_exec }, { "put", put_exec }, { "eat", eat_exec }, - { "feed", + { "shake", + shake_exec }, + /* + { "type", + type_exec }, + { "climb", + climb_exec }, + */ + /* + { "feed", feed_exec }, */ }; -- cgit v1.1