From: Christian Heller Date: Fri, 4 Jul 2014 21:18:05 +0000 (+0200) Subject: Load last world state from save file, not from re-stepping record file. X-Git-Tag: tce~741 X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/static/%27%29;%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20chunks.push%28escapeHTML%28span%5B2%5D%29%29;%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20chunks.push%28%27?a=commitdiff_plain;h=1452d43c6d7c89219cda91362da53ac8e4acb887;p=plomrogue Load last world state from save file, not from re-stepping record file. Re-wrote large chunks of code dealing with server message parsing and file parsing in general. Introduced "god commands" (manipulating the game state beyond player actor control) that direct the re-generation of the game state when loading the save file. Introduced a module "hardcoded_strings" to store strings re-used and expected among various modules. --- diff --git a/README b/README index 8b79cf2..9775dd8 100644 --- a/README +++ b/README @@ -15,10 +15,9 @@ Enemies' AI is very dumb so far: Each turn, they try to move towards their shortest-path-wise nearest enemy visible to them. If they see no enemy, they just wait. -Once you start a new world, every move of yours is recorded in a file called -"record". Once you re-start the game, all of your previous moves are replayed -automatically up to the point wherere you left the game. To start over in a new -world, simply delete this file. +Every move of yours re-writes a file "savefile" that describes the new state of +the world. Once you re-start the game, the game state is recreated from the +"savefile" file. To start over in a new world, simply delete this file. System requirements / installation / running the game ----------------------------------------------------- @@ -67,12 +66,15 @@ in-client window management. Replay game recording --------------------- -Run "./roguelike -s" to watch a recording of the current game from the -beginning. Hit any player action key to increment turns (they will not trigger -the actions usually mapped to them, only repeat the actions done at that point -in the game as defined in the "record" file). Keys to manage windows, scroll on -the map and quit the program do their usual thing. Append a number to the -s -option (like "-s100") to start the recording at the respective turn number. +Once you start a new world, every move of yours is recorded in a file called +"record". It gets overwritten when a new game world is started after deletion +of the "savefile" file. Run "./roguelike -s" to watch the current game's +recording from the beginning. Hit any player action key to increment turns (they +will not trigger the actions usually mapped to them, only repeat the actions +done at that point in the game as defined in the "record" file). Keys to manage +windows, scroll on the map and quit the program do their usual thing. Append a +number to the -s option (like "-s100") to start the recording at the respective +turn number. Hacking / server internals and configuration -------------------------------------------- diff --git a/TODO b/TODO index c6623da..00b66b2 100644 --- a/TODO +++ b/TODO @@ -1,22 +1,24 @@ Next planned steps in plomrogue development: -BOTH SERVER/CLIENT: - -- make server and client communicate by specific world state info requests - in server/out, replacing server/worldstate +IN GENERAL: - check for return values of *printf() - be more strict and humble when allocating memory from the stack +- expand use of hardcoded_strings module(s) + +BOTH SERVER/CLIENT: + +- make server and client communicate by specific world state info requests + in server/out, replacing server/worldstate + SERVER: - optimize too-slow AI / FOV algorithms -- is it actually useful to define map object action ids in the config file? - -- for game continuation, replace re-playing of whole record files with loading - game state snapshots / save files +- save confserver/world data in record and save file, too; handle them like god + commands CLIENT: diff --git a/src/client/command_db.c b/src/client/command_db.c index 29ef83d..b9d10a6 100644 --- a/src/client/command_db.c +++ b/src/client/command_db.c @@ -9,7 +9,8 @@ #include "../common/parse_file.h" /* EDIT_STARTED,parse_init_entry(), * parse_id_uniq(), parse_unknown_arg(), * parsetest_too_many_values(), parse_file(), - * parse_and_reduce_to_readyflag(),parse_val() + * parse_and_reduce_to_readyflag(), + * parse_flagval() */ #include "array_append.h" /* array_append() */ #include "world.h" /* global world */ @@ -66,12 +67,12 @@ static void tokens_into_entries(char * token0, char * token1) cmd->dsc_short = strdup(token1); parse_id_uniq(NULL != get_command(cmd->dsc_short)); } - else if (!( parse_val(token0, token1, "DESCRIPTION", &cmd_flags, - DESC_SET, 's', (char *) &cmd->dsc_long) - || parse_val(token0, token1, "SERVER_COMMAND", &cmd_flags, - SERVERCMD_SET, 's', (char *) &cmd->server_msg) - || parse_val(token0, token1, "SERVER_ARGUMENT", &cmd_flags, - SERVERARG_SET, 'c', (char *) &cmd->arg))) + else if (!( parse_flagval(token0, token1, "DESCRIPTION", &cmd_flags, + DESC_SET, 's', (char *) &cmd->dsc_long) + || parse_flagval(token0, token1,"SERVER_COMMAND", &cmd_flags, + SERVERCMD_SET, 's',(char *)&cmd->server_msg) + || parse_flagval(token0, token1,"SERVER_ARGUMENT",&cmd_flags, + SERVERARG_SET, 'c', (char *) &cmd->arg))) { parse_unknown_arg(); } diff --git a/src/client/interface_conf.c b/src/client/interface_conf.c index 6d4d65a..e3bfd02 100644 --- a/src/client/interface_conf.c +++ b/src/client/interface_conf.c @@ -9,7 +9,7 @@ #include /* FILE, sprintf() */ #include /* strchr(), strcmp(), strdup(), strlen() */ #include /* optarg, getopt() */ -#include "../common/parse_file.h" /* EDIT_STARTED, parse_file(), parse_val(), +#include "../common/parse_file.h" /* EDIT_STARTED, parse_file(),parse_flagval(), * token_from_line(), parsetest_singlechar(), * parse_and_reduce_to_readyflag(), * parsetest_defcontext(),parse_unknown_arg(), @@ -311,19 +311,19 @@ static uint8_t set_members(char * token0, char * token1, uint8_t * win_flags, uint8_t * ord_flags,uint8_t kbd_flags,char * str_key, struct Win * win, struct KeyBindingDB * kbdb) { - if ( parse_val(token0, token1, "NAME", win_flags, - NAME_SET, 's', (char *) &win->title) - || parse_val(token0, token1, "WIDTH", win_flags, - WIDTH_SET, 'i', (char *) &win->target_width) - || parse_val(token0, token1, "HEIGHT", win_flags, - HEIGHT_SET, 'i', (char *) &win->target_height)); - else if (parse_val(token0, token1, "BREAK", win_flags, - BREAK_SET, '8', (char *) &win->linebreak)) + if ( parse_flagval(token0, token1, "NAME", win_flags, + NAME_SET, 's', (char *) &win->title) + || parse_flagval(token0, token1, "WIDTH", win_flags, + WIDTH_SET, 'i', (char *) &win->target_width) + || parse_flagval(token0, token1, "HEIGHT", win_flags, + HEIGHT_SET, 'i', (char *) &win->target_height)); + else if (parse_flagval(token0, token1, "BREAK", win_flags, + BREAK_SET, '8', (char *) &win->linebreak)) { err_line(2 < win->linebreak, "Value must be 0, 1 or 2."); } - else if (parse_val(token0, token1, "WIN_FOCUS", ord_flags, - FOCUS_SET, 'c', &tmp_active)) + else if (parse_flagval(token0, token1, "WIN_FOCUS", ord_flags, + FOCUS_SET, 'c', &tmp_active)) { char * err_null = "Value not empty as it should be."; char * err_outside = "ID not found in WIN_ORDER ID series."; diff --git a/src/common/parse_file.c b/src/common/parse_file.c index 50fc30d..8ce20ad 100644 --- a/src/common/parse_file.c +++ b/src/common/parse_file.c @@ -14,10 +14,11 @@ -/* Set by parse_file(), used by err_line() for more informative messages. */ +/* Set by parse_file(), helps err_line() deciding what to do/output on error. */ static uint32_t err_line_count = 0; static char * err_line_line = NULL; static char * err_line_intro = NULL; +static uint8_t err_line_exit = 1; @@ -54,23 +55,23 @@ extern void parse_file(char * path, void (* token_to_entry) (char *, char *)) char * prefix = "Failed reading config file: \""; char * affix = "\". "; size_t size = strlen(prefix) + strlen(path) + strlen(affix) + 1; - err_line_intro = try_malloc(size, f_name); - int test = snprintf(err_line_intro, size, "%s%s%s", prefix, path, affix); + char * errline_intro = try_malloc(size, f_name); + int test = snprintf(errline_intro, size, "%s%s%s", prefix, path, affix); exit_trouble(test < 0, f_name, "snprintf()"); - exit_err(access(path, F_OK), err_line_intro); + exit_err(access(path, F_OK), errline_intro); FILE * file = try_fopen(path, "r", f_name); uint32_t linemax = textfile_width(file); - err_line_line = try_malloc(linemax + 1, f_name); - err_line_count = 0; + char * errline_line = try_malloc(linemax + 1, f_name); + set_err_line_options(errline_intro, errline_line, 0, 1); err_line(0 == linemax, "File is empty."); char * token0 = NULL; /* For final token_to_entry() if while() stagnates. */ char * token1 = NULL; char * err_val = "No value given."; - while (try_fgets(err_line_line, linemax + 1, file, f_name)) + while (try_fgets(errline_line, linemax + 1, file, f_name)) { err_line_count++; err_line(UINT32_MAX == err_line_count, "Line reaches max lines limit."); - char * line_copy = strdup(err_line_line); + char * line_copy = strdup(errline_line); token0 = token_from_line(line_copy); if (token0) { @@ -82,17 +83,28 @@ extern void parse_file(char * path, void (* token_to_entry) (char *, char *)) } token_to_entry(token0, token1); try_fclose(file, f_name); - free(err_line_line); - free(err_line_intro); + free(errline_line); + free(errline_intro); } -extern void err_line(uint8_t test, char * msg) +extern void set_err_line_options(char * intro, char * line, uint32_t count, + uint8_t exit) +{ + err_line_count = count; + err_line_line = line; + err_line_intro = intro; + err_line_exit = exit; +} + + + +extern uint8_t err_line(uint8_t test, char * msg) { if (!test) { - return; + return 0; } char * f_name = "err_line()"; char * prefix = " Offending line "; @@ -104,7 +116,14 @@ extern void err_line(uint8_t test, char * msg) int ret = snprintf(err, size, "%s%s%s%d%s%s", err_line_intro, msg, prefix, err_line_count, affix, err_line_line); exit_trouble(ret < 0, f_name, "snprintf()"); - exit_err(1, err); + if (err_line_exit) + { + exit_err(1, err); + } + exit_trouble(0 > printf("%s\n", err), f_name, "printf()"); + exit_trouble(EOF == fflush(stdout), f_name, "fflush()"); + free(err); + return 1; } @@ -124,6 +143,10 @@ extern char * token_from_line(char * line) *(--final_char) = '\0'; } } + if (final_char < start) + { + return NULL; + } uint8_t empty = 1; uint32_t i; for (i = 0; '\0' != start[i]; i++) @@ -137,7 +160,7 @@ extern char * token_from_line(char * line) } if (empty) { - return start = NULL; + return NULL; } set_token_end(&start, &limit_char); return start; @@ -145,32 +168,42 @@ extern char * token_from_line(char * line) -extern void parsetest_int(char * string, char type) +extern uint8_t parsetest_int(char * string, char type) { - char * err; - if ('8' == type) - { - err = "Value must be proper representation of unsigned 8 bit integer."; - } - if ('i' == type) - { - err = "Value must be proper representation of signed 16 bit integer."; - } - err_line(strlen(string) < 1, err); + char * err_8 = "Value must represent proper unsigned 8 bit integer."; + char * err_i = "Value must represent proper signed 16 bit integer."; + char * err_u = "Value must represent proper unsigned 16 bit integer."; + char * err_U = "Value must represent proper unsigned 32 bit integer."; + char * err = ('8' == type) ? err_8 : err_U; + err = ('i' == type) ? err_i : err; + err = ('u' == type) ? err_u : err; + uint8_t ret = err_line(strlen(string) < 1, err); uint8_t i; uint8_t test; for (i = 0; '\0' != string[i]; i++) { char * err_many = "Value of too many characters."; - err_line(string[i + 1] && UINT8_MAX == i, err_many); + ret = ret + err_line(string[i + 1] && UINT8_MAX == i, err_many); test = ( (0 == i && ('-' == string[i] || '+' == string[i])) || ('0' <= string[i] && string[i] <= '9')); - err_line(!test, err); + ret = ret + err_line(!test, err); } - err_line(strlen(string) < 2 && ('-' == string[i] || '+' == string[i]), err); - err_line('8'==type && (atoi(string) < 0 || atoi(string) > UINT8_MAX), err); - test = 'i'==type && (atoi(string) < INT16_MIN || atoi(string) > INT16_MAX); - err_line(test, err); + ret = ret + err_line( strlen(string) < 2 + && ('-' == string[i] || '+' == string[i]), err); + test = ( '8' == type + && ( strlen(string) > 4 + || atoi(string) < 0 || atoi(string) > UINT8_MAX)) + || ( 'i' == type + && ( strlen(string) > 6 + || atol(string) < INT16_MIN || atol(string) > INT16_MAX)) + || ( 'u' == type + && ( strlen(string) > 6 + || atoll(string) < 0 || atol(string) > UINT16_MAX)) + || ( 'U' == type + && ( strlen(string) > 11 + || atoll(string) < 0 || atoll(string) > UINT32_MAX)); + ret = ret + err_line(test, err); + return ret; } @@ -182,9 +215,9 @@ extern void parsetest_defcontext(uint8_t flags) -extern void parsetest_singlechar(char * string) +extern uint8_t parsetest_singlechar(char * string) { - err_line(1 != strlen(string), "Value must be single ASCII character."); + return err_line(1 !=strlen(string),"Value must be single ASCII character."); } @@ -222,31 +255,36 @@ extern char * parse_init_entry(uint8_t * flags, size_t size) extern uint8_t parse_val(char * token0, char * token1, char * comparand, - uint8_t * flags, uint8_t set_flag, char type, - char * element) + char type, char * element) { if (!strcmp(token0, comparand)) { - parsetest_defcontext(*flags); - *flags = *flags | set_flag; if ('s' == type) { * (char **) element = strdup(token1); } - else if ('c' == type) + else if ('c' == type && !parsetest_singlechar(token1)) { - parsetest_singlechar(token1); *element = (token1)[0]; } - else if ('8' == type) - { - parsetest_int(token1, '8'); - * (uint8_t *) element = atoi(token1); - } - else if ('i' == type) + else if (!parsetest_int(token1, type)) { - parsetest_int(token1, 'i'); - * (int16_t *) element = atoi(token1); + if ('8' == type) + { + * (uint8_t *) element = atoi(token1); + } + else if ('i' == type) + { + * (int16_t *) element = atoi(token1); + } + else if ('u' == type) + { + * (uint16_t *) element = atol(token1); + } + else if ('U' == type) + { + * (uint32_t *) element = atoll(token1); + } } return 1; } @@ -255,6 +293,21 @@ extern uint8_t parse_val(char * token0, char * token1, char * comparand, +extern uint8_t parse_flagval(char * token0, char * token1, char * comparand, + uint8_t * flags, uint8_t set_flag, char type, + char * element) +{ + if (parse_val(token0, token1, comparand, type, element)) + { + parsetest_defcontext(*flags); + *flags = *flags | set_flag; + return 1; + } + return 0; +} + + + extern void parse_and_reduce_to_readyflag(uint8_t * flags, uint8_t ready_flag) { char * err_fin = "Last definition block not finished yet."; diff --git a/src/common/parse_file.h b/src/common/parse_file.h index 29e7ae1..4192dac 100644 --- a/src/common/parse_file.h +++ b/src/common/parse_file.h @@ -19,17 +19,23 @@ enum parse_flags /* Parse file at "path" by passing each line's first two tokens to - * "token_to_entry". Ignore empty line. Non-empty lines must feature at least + * "token_to_entry". Ignore empty lines. Non-empty lines must feature at least * two tokens as delimited either be whitespace or single quotes (to allow for * tokens featuring whitespaces). When EOF is reached, token_to_entry() is * called a last time with a first token of NULL. */ extern void parse_file(char * path, void ( *token_to_entry) (char *, char *)); -/* If "test" != 0, exit on output of "msg" and faulty line and line number as - * parsed by parse_file(). (Ought to be called as offspring to parse_file().) +/* Set err_line() options: "intro" message, char array used to store analyzed + * lines ("line"), line start "count", whether to "exit" on error message. */ -extern void err_line(uint8_t test, char * msg); +extern void set_err_line_options(char * intro, char * line, uint32_t count, + uint8_t exit); + +/* If "test", output "msg", faulty line, its number and exit if so defined by + * set_err_line_options(), else return 1 on "test" and 0 if "test" is 0. + */ +extern uint8_t err_line(uint8_t test, char * msg); /* Return next token from "line" or NULL if none is found. Tokens either a) * start at the first non-whitespace character and end before the next @@ -42,11 +48,13 @@ extern void err_line(uint8_t test, char * msg); * */ extern char * token_from_line(char * line); -/* Test for "string" to represent proper int16 (type: "i") or uint8 ("8"). */ -extern void parsetest_int(char * string, char type); +/* Test for "string" to represent proper int16 (type: "i"), uint8 ("8"), uint16 + * ("u") or uint32 ("U"). Returns 0 if proper value, else >0. + */ +extern uint8_t parsetest_int(char * string, char type); -/* Test for "string" to be of length 1 (excluding "\0" terminator). */ -extern void parsetest_singlechar(char * string); +/* Test for "string" to be of length 1 (excluding "\0"). Return 1 on failure. */ +extern uint8_t parsetest_singlechar(char * string); /* Calls err_line() with fitting message if EDIT_STARTED not set in "flags". */ extern void parsetest_defcontext(uint8_t flags); @@ -64,12 +72,16 @@ extern void parse_id_uniq(int test); extern char * parse_init_entry(uint8_t * flags, size_t size); /* If "token0" fits "comparand", set "element" to value read from "token1" as - * string (type: "s"), char ("c") uint8 ("8") or int16 ("i"), set that element's - * flag to "flags" and return 1; else return 0. + * string (type: "s"), char ("c") uint8 ("8"), uint16 ("u"), uint32 ("U") or + * int16 ("i"), and return 1; else 0. */ extern uint8_t parse_val(char * token0, char * token1, char * comparand, - uint8_t * flags, uint8_t set_flag, char type, - char * element); + char type, char * element); + +/* Wrapper to parse_val() that sets "flags" to "flags"|"set_flag" on success. */ +extern uint8_t parse_flagval(char * token0, char * token1, char * comparand, + uint8_t * flags, uint8_t set_flag, char type, + char * element); /* Check "ready_flag" is set in "flags", re-set "flags" to "ready_flag" only. */ extern void parse_and_reduce_to_readyflag(uint8_t * flags, uint8_t ready_flag); diff --git a/src/server/ai.c b/src/server/ai.c index 54db84b..ff883dd 100644 --- a/src/server/ai.c +++ b/src/server/ai.c @@ -6,6 +6,7 @@ #include /* free() */ #include "../common/try_malloc.h" /* try_malloc() */ #include "field_of_view.h" /* VISIBLE */ +#include "hardcoded_strings.h" /* s */ #include "thing_actions.h" /* get_thing_action_id_by_name() */ #include "things.h" /* struct Thing */ #include "world.h" /* global world */ @@ -168,11 +169,11 @@ static char get_dir_to_nearest_enemy(struct Thing * t_origin) extern void ai(struct Thing * t) { - t->command = get_thing_action_id_by_name("wait"); - char sel = get_dir_to_nearest_enemy(t); - if (0 != sel) - { - t->command = get_thing_action_id_by_name("move"); + t->command = get_thing_action_id_by_name(s[CMD_WAIT]); + char sel = t->fov_map ? get_dir_to_nearest_enemy(t) : 0;/* t->fov_map may */ + if (0 != sel) /* be absent due */ + { /* to god command.*/ + t->command = get_thing_action_id_by_name(s[CMD_MOVE]); t->arg = sel; } } diff --git a/src/server/cleanup.c b/src/server/cleanup.c index 1959c1c..02f52b3 100644 --- a/src/server/cleanup.c +++ b/src/server/cleanup.c @@ -5,6 +5,7 @@ #include /* free() */ #include /* unlink() */ #include "../common/readwrite.h" /* try_fclose() */ +#include "hardcoded_strings.h" /* s */ #include "thing_actions.h" /* free_thing_actions() */ #include "things.h" /* free_things(), free_thing_types() */ #include "world.h" /* global world */ @@ -25,7 +26,7 @@ extern void cleanup() free(world.map.cells); if (cleanup_flags & CLEANUP_WORLDSTATE) { - unlink(world.path_worldstate); + unlink(s[PATH_WORLDSTATE]); } if (cleanup_flags & CLEANUP_THINGS) { @@ -42,13 +43,13 @@ extern void cleanup() if (cleanup_flags & CLEANUP_IN) { try_fclose(world.file_in, f_name); - unlink(world.path_in); + unlink(s[PATH_IN]); } if (cleanup_flags & CLEANUP_OUT) { try_fclose(world.file_out, f_name); free(world.server_test); - unlink(world.path_out); + unlink(s[PATH_OUT]); } } diff --git a/src/server/configfile.c b/src/server/configfile.c index b53b85e..f6561aa 100644 --- a/src/server/configfile.c +++ b/src/server/configfile.c @@ -8,13 +8,15 @@ #include "../common/parse_file.h" /* EDIT_STARTED, parsetest_int(),parse_file(), * parsetest_too_many_values(),parse_id_uniq() * parse_unknown_arg(), parse_init_entry(), - * parse_and_reduce_to_readyflag(),parse_val() + * parse_and_reduce_to_readyflag(), + * parse_flagval() */ #include "../common/rexit.h" /* exit_err(), exit_trouble() */ #include "../common/try_malloc.h" /* try_malloc() */ #include "cleanup.h" /* set_cleanup_flag(), CLEANUP_THING_TYPES, * CLEANUP_THING_ACTIONS */ +#include "hardcoded_strings.h" /* s */ #include "thing_actions.h" /* ThingAction */ #include "things.h" /* Thing, ThingType */ #include "world.h" /* world global */ @@ -252,33 +254,33 @@ static uint8_t set_members(char * token0, char * token1, uint8_t * thing_flags, struct ThingType * tt, struct ThingAction * ta) { if ( *action_flags & EDIT_STARTED - && parse_val(token0, token1, "NAME", action_flags, - NAME_SET, 's', (char *) &ta->name)) + && parse_flagval(token0, token1, "NAME", action_flags, + NAME_SET, 's', (char *) &ta->name)) { - if (!( try_func_name(ta, "move", actor_move) - || try_func_name(ta, "pick_up", actor_pick) - || try_func_name(ta, "drop", actor_drop) - || try_func_name(ta, "use", actor_use))) + if (!( try_func_name(ta, s[CMD_MOVE], actor_move) + || try_func_name(ta, s[CMD_PICKUP], actor_pick) + || try_func_name(ta, s[CMD_DROP], actor_drop) + || try_func_name(ta, s[CMD_USE], actor_use))) { ta->func = actor_wait; } *action_flags = *action_flags | NAME_SET; return 1; } - else if ( parse_val(token0, token1, "NAME", thing_flags, - NAME_SET, 's', (char *) &tt->name) - || parse_val(token0, token1, "SYMBOL", thing_flags, - SYMBOL_SET, 'c', (char *) &tt->char_on_map) - || parse_val(token0, token1, "EFFORT", action_flags, - EFFORT_SET, '8', (char *) &ta->effort) - || parse_val(token0, token1, "START_NUMBER", thing_flags, - START_N_SET, '8', (char *) &tt->start_n) - || parse_val(token0, token1, "LIFEPOINTS", thing_flags, - LIFEPOINTS_SET, '8', (char *) &tt->lifepoints) - || parse_val(token0, token1, "CONSUMABLE", thing_flags, - CONSUMABLE_SET, '8', (char *) &tt->consumable) - || parse_val(token0, token1, "CORPSE_ID", thing_flags, - CORPSE_ID_SET, '8', (char *) &tt->corpse_id)) + else if ( parse_flagval(token0, token1, "NAME", thing_flags, + NAME_SET, 's', (char *) &tt->name) + || parse_flagval(token0, token1, "SYMBOL", thing_flags, + SYMBOL_SET, 'c', (char *) &tt->char_on_map) + || parse_flagval(token0, token1, "EFFORT", action_flags, + EFFORT_SET, '8', (char *) &ta->effort) + || parse_flagval(token0, token1, "START_NUMBER", thing_flags, + START_N_SET, '8', (char *) &tt->start_n) + || parse_flagval(token0, token1, "LIFEPOINTS", thing_flags, + LIFEPOINTS_SET, '8', (char *) &tt->lifepoints) + || parse_flagval(token0, token1, "CONSUMABLE", thing_flags, + CONSUMABLE_SET, '8', (char *) &tt->consumable) + || parse_flagval(token0, token1, "CORPSE_ID", thing_flags, + CORPSE_ID_SET, '8', (char *) &tt->corpse_id)) { return 1; } @@ -302,7 +304,7 @@ static uint8_t try_func_name(struct ThingAction * ta, char * name, extern void read_config_file() { - parse_file(world.path_config, tokens_into_entries); + parse_file(s[PATH_CONFIG], tokens_into_entries); exit_err(!world.map.length, "Map size not defined in config file."); uint8_t player_type_is_valid = 0; struct ThingType * tt; diff --git a/src/server/configfile.h b/src/server/configfile.h index 901834a..d80a2ba 100644 --- a/src/server/configfile.h +++ b/src/server/configfile.h @@ -8,8 +8,8 @@ -/* Parse file at world.path_config into thing type and thing action definitions - * at world.thing_types and world.thing_actions. +/* Parse thing type / action definitons file nto thing type and thing action + * definitions at world.thing_types and world.thing_actions. */ extern void read_config_file(); diff --git a/src/server/hardcoded_strings.c b/src/server/hardcoded_strings.c new file mode 100644 index 0000000..e007547 --- /dev/null +++ b/src/server/hardcoded_strings.c @@ -0,0 +1,39 @@ +/* hardcoded_strings.c */ + +#include "hardcoded_strings.h" + + + +char * s[26]; + + + +extern void init_strings() +{ + s[PATH_CONFIG] = "confserver/world"; + s[PATH_WORLDSTATE] = "server/worldstate"; + s[PATH_OUT] = "server/out"; + s[PATH_IN] = "server/in"; + s[PATH_RECORD] = "record"; + s[PATH_SUFFIX_TMP] = "_tmp"; + s[PATH_SAVE] = "savefile"; + s[CMD_MAKE_WORLD] = "MAKE_WORLD"; + s[CMD_DO_FOV] = "BUILD_FOVS"; + s[CMD_SEED_MAP] = "SEED_MAP"; + s[CMD_SEED_RAND] = "SEED_RANDOMNESS"; + s[CMD_TURN] = "TURN"; + s[CMD_THING] = "THING"; + s[CMD_TYPE] = "TYPE"; + s[CMD_POS_Y] = "POS_Y"; + s[CMD_POS_X] = "POS_X"; + s[CMD_COMMAND] = "COMMAND"; + s[CMD_ARGUMENT] = "ARGUMENT"; + s[CMD_PROGRESS] = "PROGRESS"; + s[CMD_LIFEPOINTS] = "LIFEPOINTS"; + s[CMD_CARRIES] = "CARRIES"; + s[CMD_WAIT] = "wait"; + s[CMD_MOVE] = "move"; + s[CMD_PICKUP] = "pick_up"; + s[CMD_DROP] = "drop"; + s[CMD_USE] = "use"; +} diff --git a/src/server/hardcoded_strings.h b/src/server/hardcoded_strings.h new file mode 100644 index 0000000..3fda2ac --- /dev/null +++ b/src/server/hardcoded_strings.h @@ -0,0 +1,47 @@ +/* hardcoded_strings.h + * + * For re-used hardcoded strings. + */ + +#ifndef STRINGS_H +#define STRINGS_H + + + +enum string_num +{ + PATH_CONFIG, + PATH_WORLDSTATE, + PATH_OUT, + PATH_IN, + PATH_RECORD, + PATH_SUFFIX_TMP, + PATH_SAVE, + CMD_MAKE_WORLD, + CMD_DO_FOV, + CMD_SEED_MAP, + CMD_SEED_RAND, + CMD_TURN, + CMD_THING, + CMD_TYPE, + CMD_POS_Y, + CMD_POS_X, + CMD_COMMAND, + CMD_ARGUMENT, + CMD_PROGRESS, + CMD_LIFEPOINTS, + CMD_CARRIES, + CMD_WAIT, + CMD_MOVE, + CMD_PICKUP, + CMD_DROP, + CMD_USE +}; + +extern void init_strings(); + +extern char * s[26]; + + + +#endif diff --git a/src/server/init.c b/src/server/init.c index a3c1589..219223b 100644 --- a/src/server/init.c +++ b/src/server/init.c @@ -19,7 +19,8 @@ #include "../common/try_malloc.h" /* try_malloc() */ #include "cleanup.h" /* set_cleanup_flag() */ #include "field_of_view.h" /* build_fov_map() */ -#include "map.h" /* init_map() */ +#include "hardcoded_strings.h" /* s */ +#include "map.h" /* remake_map() */ #include "things.h" /* Thing, ThingType, free_things(), add_things(), * get_player() */ @@ -28,6 +29,43 @@ + +/* Replay game from record file up to the turn named in world.replay, then turn + * over to manual replay via io_loop(). + */ +static void replay_game(); + + + +static void replay_game() +{ + char * f_name = "replay_game()"; + exit_err(access(s[PATH_RECORD], F_OK), "No record found to replay."); + FILE * file = try_fopen(s[PATH_RECORD], "r", f_name); + uint32_t linemax = textfile_width(file); + char line[linemax + 1]; + while ( world.turn < world.replay + && NULL != try_fgets(line, linemax + 1, file, f_name)) + { + obey_msg(line, 0); + } + uint8_t end = 0; + while (!io_loop()) + { + if (!end) + { + end = (NULL == try_fgets(line, linemax + 1, file, f_name)); + if (!end) + { + obey_msg(line, 0); + } + } + } + try_fclose(file, f_name); +} + + + extern void obey_argv(int argc, char * argv[]) { int opt; @@ -59,36 +97,35 @@ extern void setup_server_io() char * f_name = "setup_server_io()"; int test = mkdir("server", 0700); exit_trouble(test && EEXIST != errno, f_name, "mkdir()"); - world.file_out = try_fopen(world.path_out, "w", f_name); + world.file_out = try_fopen(s[PATH_OUT], "w", f_name); world.server_test = try_malloc(10 + 1 + 10 + 1 + 1, f_name); sprintf(world.server_test, "%d %d\n", getpid(), (int) time(0)); try_fwrite(world.server_test, strlen(world.server_test), 1, world.file_out, f_name); fflush(world.file_out); set_cleanup_flag(CLEANUP_OUT); - if (!access(world.path_in, F_OK)) /* This keeps out input from old input */ + char * path_in = s[PATH_IN]; + if (!access(path_in, F_OK)) /* This keeps out input from old input */ { /* file streams of clients */ - unlink(world.path_in); /* communicating with server processes */ + unlink(path_in) ; /* communicating with server processes */ } /* superseded by this current one. */ - world.file_in = try_fopen(world.path_in, "w", f_name); + world.file_in = try_fopen(path_in, "w", f_name); try_fclose(world.file_in, f_name); - world.file_in = try_fopen(world.path_in, "r", f_name); + world.file_in = try_fopen(path_in, "r", f_name); set_cleanup_flag(CLEANUP_IN); } -extern void remake_world(uint32_t seed) +extern void remake_world() { char * f_name = "remake_world()"; free(world.log); - world.log = NULL; /* thing_actions.c's update_log() checks for this. */ - world.seed = seed; - world.thing_count = 0; - free(world.map.cells); + world.log = NULL; /* thing_actions.c's update_log() checks for this. */ + world.seed_map = world.seed; free_things(world.things); world.last_update_turn = 0; - init_map(); + remake_map(); struct ThingType * tt; for (tt = world.thing_types; NULL != tt; tt = tt->next) { @@ -111,9 +148,9 @@ extern void remake_world(uint32_t seed) { t->fov_map = t->lifepoints ? build_fov_map(t) : NULL; } - if (world.turn) + if (!access(s[PATH_RECORD], F_OK)) { - exit_trouble(unlink(world.path_record), f_name, "unlink()"); + exit_trouble(unlink(s[PATH_RECORD]), f_name, "unlink()"); } world.turn = 1; } @@ -123,41 +160,32 @@ extern void remake_world(uint32_t seed) extern void run_game() { char * f_name = "run_game()"; - if (!access(world.path_record, F_OK)) + if (world.replay) { - FILE * file = try_fopen(world.path_record, "r", f_name); + replay_game(); + return; + } + char * path_savefile = s[PATH_SAVE]; + if (!access(path_savefile, F_OK)) + { + FILE * file = try_fopen(path_savefile, "r", f_name); uint32_t linemax = textfile_width(file); char line[linemax + 1]; - while ( (!world.replay || (world.turn < world.replay)) - && NULL != try_fgets(line, linemax + 1, file, f_name)) - { - obey_msg(line, 0); - } - if (!world.replay) - { - try_fclose(file, f_name); - io_loop(); - return; - } - uint8_t end = 0; - while (!io_loop()) + while (NULL != try_fgets(line, linemax + 1, file, f_name)) { - if (!end) + if (strlen(line) && strcmp("\n", line)) { - end = (NULL == try_fgets(line, linemax + 1, file, f_name)); - if (!end) - { - obey_msg(line, 0); - } + obey_msg(line, 0); } } try_fclose(file, f_name); - return; } - exit_err(world.replay, "No record file found to replay."); - char * command = "seed"; - char msg[strlen(command) + 1 + 11 + 1]; - sprintf(msg, "%s %d", command, (int) time(NULL)); - obey_msg(msg, 1); + else + { + char * command = s[CMD_MAKE_WORLD]; + char msg[strlen(command) + 1 + 11 + 1]; + sprintf(msg, "%s %d", command, (int) time(NULL)); + obey_msg(msg, 1); + } io_loop(); } diff --git a/src/server/init.h b/src/server/init.h index 0fbc79b..e563374 100644 --- a/src/server/init.h +++ b/src/server/init.h @@ -16,24 +16,24 @@ extern void obey_argv(int argc, char * argv[]); /* Start server in file and out file, latter with server process test string. */ extern void setup_server_io(); -/* Dissolves old game world if it exists, and generates a new one from "seed". - * Unlinks a pre-existing file at world.path_record if called on a world.turn>0, - * i.e. if called after iterating through an already established game world. +/* Dissolves old game world if it exists, generates a new one from world.seed. + * Unlinks any pre-existing record file. * * Thing (action) definitions read in from server config directory are not * affected. The map is populated accordingly. world.last_update_turn is set to * 0 and world.turn to 1, so that io_round()'s criteria for updating the output * file are triggered even when this function is called during a round 1. */ -extern void remake_world(uint32_t seed); +extern void remake_world(); -/* Create a game state from which to play or replay, then enter io_loop(). +/* Create a game world state, then enter play or replay mode. * - * If no record file exists at world.path_record, generate new world (by a - * "seed" command calling remake_world()) in play mode, or error-exit in replay - * mode. If a record file exists, in play mode auto-replay it up to the last - * game state before turning over to the player; in replay mode, auto-replay it - * up to the turn named in world.replay and then turn over to manual replay. + * If replay mode is called for, try for the record file and follow its commands + + up to the turn specified by the user, then enter manual replay. Otherwise, + * start into play mode after having either recreated a game world state from + * the savefile, or, if none exists, having created a new world with the + * MAKE_WORLD command. Manual replay as well as play mode take place inside + * io_loop(). */ extern void run_game(); diff --git a/src/server/io.c b/src/server/io.c index 8b09ec0..b2d8005 100644 --- a/src/server/io.c +++ b/src/server/io.c @@ -14,15 +14,23 @@ #include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(), * try_fwrite(), try_fputc(), try_fgetc() */ +#include "../common/rexit.h" /* exit_trouble() */ #include "../common/try_malloc.h" /* try_malloc() */ #include "cleanup.h" /* set_cleanup_flag() */ #include "field_of_view.h" /* VISIBLE */ +#include "hardcoded_strings.h" /* s */ #include "map.h" /* yx_to_map_pos() */ #include "things.h" /* Thing, ThingType, get_thing_type(), get_player() */ #include "world.h" /* global world */ +/* Write to "file" god commands (one per line) to recreate thing "t". */ +static void write_key_value(FILE * file, char * key, uint32_t value); + +/* Write to "file" \n-delimited line of "key" + space + "value" as string. */ +static void write_thing(FILE * file, struct Thing * t); + /* Cut out and return first \0-terminated string from world.queue and * appropriately reduce world.queue_size. Return NULL if queue is empty. * Superfluous \0 bytes after the string are also cut out. Should the queue @@ -58,6 +66,45 @@ static void write_map(struct Thing * player, FILE * file); +static void write_key_value(FILE * file, char * key, uint32_t value) +{ + char * f_name = "write_key_value()"; + try_fwrite(key, strlen(key), 1, file, f_name); + try_fputc(' ', file, f_name); + char * line = try_malloc(11, f_name); + exit_trouble(-1 == sprintf(line, "%u", value), f_name, "sprintf()"); + try_fwrite(line, strlen(line), 1, file, f_name); + free(line); + try_fputc('\n', file, f_name); +} + + + +static void write_thing(FILE * file, struct Thing * t) +{ + char * f_name = "write_thing()"; + struct Thing * o; + for (o = t->owns; o; o = o->next) + { + write_thing(file, o); + } + write_key_value(file, s[CMD_THING], t->id); + write_key_value(file, s[CMD_TYPE], t->type); + write_key_value(file, s[CMD_POS_Y], t->pos.y); + write_key_value(file, s[CMD_POS_X], t->pos.x); + write_key_value(file, s[CMD_COMMAND], t->command); + write_key_value(file, s[CMD_ARGUMENT], t->arg); + write_key_value(file, s[CMD_PROGRESS], t->progress); + write_key_value(file, s[CMD_LIFEPOINTS], t->lifepoints); + for (o = t->owns; o; o = o->next) + { + write_key_value(file, s[CMD_CARRIES], o->id); + } + try_fputc('\n', file, f_name); +} + + + static char * get_message_from_queue() { char * f_name = "get_message_from_queue()"; @@ -133,8 +180,8 @@ static void read_file_into_queue() static void update_worldstate_file() { char * f_name = "update_worldstate_file()"; - char path_tmp[strlen(world.path_worldstate) + strlen(world.tmp_suffix) + 1]; - sprintf(path_tmp, "%s%s", world.path_worldstate, world.tmp_suffix); + char path_tmp[strlen(s[PATH_WORLDSTATE]) + strlen(s[PATH_SUFFIX_TMP]) + 1]; + sprintf(path_tmp, "%s%s", s[PATH_WORLDSTATE], s[PATH_SUFFIX_TMP]); FILE * file = try_fopen(path_tmp, "w", f_name); struct Thing * player = get_player(); write_value_as_line(world.turn, file); @@ -148,7 +195,7 @@ static void update_worldstate_file() { try_fwrite(world.log, strlen(world.log), 1, file, f_name); } - try_fclose_unlink_rename(file, path_tmp, world.path_worldstate, f_name); + try_fclose_unlink_rename(file, path_tmp, s[PATH_WORLDSTATE], f_name); set_cleanup_flag(CLEANUP_WORLDSTATE); char * dot = ".\n";; try_fwrite(dot, strlen(dot), 1, world.file_out, f_name); @@ -199,29 +246,32 @@ static char * build_visible_map(struct Thing * player) uint32_t map_size = world.map.length * world.map.length; char * visible_map = try_malloc(map_size, f_name); memset(visible_map, ' ', map_size); - uint16_t pos_i; - for (pos_i = 0; pos_i < map_size; pos_i++) - { - if (player->fov_map[pos_i] & VISIBLE) + if (player->fov_map) /* May fail if player thing was created / positioned */ + { /* by god command after turning off FOV building. */ + uint16_t pos_i; + for (pos_i = 0; pos_i < map_size; pos_i++) { - visible_map[pos_i] = world.map.cells[pos_i]; + if (player->fov_map[pos_i] & VISIBLE) + { + visible_map[pos_i] = world.map.cells[pos_i]; + } } - } - struct Thing * t; - struct ThingType * tt; - char c; - uint8_t i; - for (i = 0; i < 2; i++) - { - for (t = world.things; t != 0; t = t->next) + struct Thing * t; + struct ThingType * tt; + char c; + uint8_t i; + for (i = 0; i < 2; i++) { - if ( player->fov_map[yx_to_map_pos(&t->pos)] & VISIBLE - && ( (0 == i && 0 == t->lifepoints) - || (1 == i && 0 < t->lifepoints))) + for (t = world.things; t != 0; t = t->next) { - tt = get_thing_type(t->type); - c = tt->char_on_map; - visible_map[yx_to_map_pos(&t->pos)] = c; + if ( player->fov_map[yx_to_map_pos(&t->pos)] & VISIBLE + && ( (0 == i && 0 == t->lifepoints) + || (1 == i && 0 < t->lifepoints))) + { + tt = get_thing_type(t->type); + c = tt->char_on_map; + visible_map[yx_to_map_pos(&t->pos)] = c; + } } } } @@ -272,3 +322,25 @@ extern char * io_round() } return get_message_from_queue(); } + + + +extern void save_world() +{ + char * f_name = "save_world()"; + char * path = s[PATH_SAVE]; + FILE * file = try_fopen(path, "w", f_name); + write_key_value(file, s[CMD_DO_FOV], 0); + try_fputc('\n', file, f_name); + write_key_value(file, s[CMD_SEED_MAP], world.seed_map); + write_key_value(file, s[CMD_SEED_RAND], world.seed); + write_key_value(file, s[CMD_TURN], world.turn); + try_fputc('\n', file, f_name); + struct Thing * t; + for (t = world.things; t; t = t->next) + { + write_thing(file, t); + } + write_key_value(file, s[CMD_DO_FOV], 1); + try_fclose(file, f_name); +} diff --git a/src/server/io.h b/src/server/io.h index 02f8e90..0e04d80 100644 --- a/src/server/io.h +++ b/src/server/io.h @@ -1,4 +1,4 @@ -/* io.h: +/* io.h * * Communication of the server with the outside world and its client via input, * output and world state files. @@ -11,21 +11,26 @@ /* Return single \0-terminated string read from input queue (world.queue); or, * if queue is empty and world.turn is unequal world.last_update_turn, update - * world state file at world.path_worldstate (and update world.last_update_turn - * and write a single dot line to output file at world.path_out), then read file - * at world.path_in for the next load of bytes to put onto the input queue. + * world state file (and world.last_update_turn) and write a single dot line to + * server out file, then read server in file for the next load of bytes to put + * onto the input queue. * - * Reading the file at world.path_in may put many \0-terminated strings on the - * queue at once. Successive calls of io_round() will make these available one - * by one. Each such call cuts off bytes from the beginning of world.queue, up - * to and including the last \0 byte that is followed by a non-\0 byte or ends - * the queue. If the queue starts with a \0 byte, it and its \0 followers are - * cut and a NULL pointer is returned. Reading from the input file stops only - * when one or more byte were read and the next read returns 0 bytes. If the + * Reading the server in file may put many \0-terminated strings on the queue at + * once. Successive calls of io_round() will make these available one by one. + * Each such call cuts off bytes from the beginning of world.queue, up to and + * including the last \0 byte that is followed by a non-\0 byte or ends the + * queue. If the queue starts with a \0 byte, it and its \0 followers are cut + * and a NULL pointer is returned. Reading from the input file stops only when + * one or more byte were read and the next read returns 0 bytes. If the * re-filled queue does not end in a \0 byte, a \0 byte is appended to it. */ extern char * io_round(); +/* Write to savefile god commands (one per line) to rebuild the current world + * state. + */ +extern void save_world(); + #endif diff --git a/src/server/main.c b/src/server/main.c index 366fb17..33e5391 100644 --- a/src/server/main.c +++ b/src/server/main.c @@ -5,6 +5,7 @@ #include "../common/rexit.h" /* exit_err, set_cleanup_func() */ #include "cleanup.h" /* set_cleanup_flag(), cleanup() */ #include "configfile.h" /* read_config_file() */ +#include "hardcoded_strings.h" /* s */ #include "init.h" /* run_game(), obey_argv(), obey_argv(), setup_server_io() */ #include "world.h" /* struct World */ @@ -20,6 +21,7 @@ int main(int argc, char ** argv) set_cleanup_func(cleanup); /* Init settings from command line / hard-coded values. Print start info. */ + init_strings(); obey_argv(argc, argv); if (world.is_verbose) { @@ -33,12 +35,6 @@ int main(int argc, char ** argv) exit_err(-1 == test, printf_err); } } - world.path_config = "confserver/world"; - world.path_worldstate = "server/worldstate"; - world.path_out = "server/out"; - world.path_in = "server/in"; - world.path_record = "record"; - world.tmp_suffix = "_tmp"; /* Init config file and server i/o files. */ read_config_file(); diff --git a/src/server/map.c b/src/server/map.c index 1c9a2f1..059a713 100644 --- a/src/server/map.c +++ b/src/server/map.c @@ -2,6 +2,7 @@ #include "map.h" #include /* uint8_t, uint16_t, uint32_t, UINT16_MAX */ +#include /* free() */ #include "../common/rexit.h" /* exit_err() */ #include "../common/try_malloc.h" /* try_malloc() */ #include "../common/yx_uint8.h" /* struct yx_uint8 */ @@ -136,13 +137,17 @@ static void make_trees() -extern void init_map() +extern void remake_map() { char * f_name = "init_map()"; + free(world.map.cells); world.map.cells = try_malloc(world.map.length * world.map.length, f_name); + uint32_t store_seed = world.seed; + world.seed = world.seed_map; make_sea(); make_island(); make_trees(); + world.seed = store_seed; } diff --git a/src/server/map.h b/src/server/map.h index f74d85d..4ff6f68 100644 --- a/src/server/map.h +++ b/src/server/map.h @@ -11,14 +11,14 @@ -/* Initialize island map "~" cells representing water and "." cells representing - * land. The island shape is built randomly by starting with a sea of one land - * cell in the middle, then going into a cycle of repeatedly selecting a random - * seal cell and transforming it into land if it is neighbor to land; the cycle - * ends when a land cell is due to be created right at the border of the map. - * Lots of 'X' cells representing trees are put on the island, too. +/* (Re-)make island map "~" cells representing water and "." cells representing + * land. The island shape is built randomly from world.seed_map by starting with + * a sea of one land cell in the middle, then going into a cycle of repeatedly + * selecting a random sea cell and transforming it into land if it is neighbor + * to land; the cycle ends when a land cell is due to be created right at the + * border of the map. Lots of 'X' cells representing trees are put on the island. */ -extern void init_map(); +extern void remake_map(); /* Check if coordinate "pos" on (or beyond) world.map is accessible to thing * movement. diff --git a/src/server/run.c b/src/server/run.c index 952cebb..ec018c5 100644 --- a/src/server/run.c +++ b/src/server/run.c @@ -1,45 +1,265 @@ /* src/server/run.c */ +#define _POSIX_C_SOURCE 200809L #include "run.h" #include /* NULL */ #include /* uint8_t, uint16_t, uint32_t */ #include /* FILE, sprintf(), fflush() */ -#include /* free() */ -#include /* strlen(), strcmp() strncmp(), atoi() */ +#include /* free(), atoi() */ +#include /* strlen(), strcmp() strncmp(), strdup() */ #include /* access() */ +#include "../common/parse_file.h" /* set_err_line_options(), token_from_line(), + * err_line() + */ #include "../common/readwrite.h" /* try_fopen(), try_fcose(), try_fwrite(), * try_fgets(), try_fclose_unlink_rename(), * textfile_width(), try_fputc() */ #include "../common/rexit.h" /* exit_trouble(), exit_err() */ #include "ai.h" /* ai() */ -#include "cleanup.h" /* unset_cleanup_flag() */ +#include "cleanup.h" /* set_cleanup_flag(), unset_cleanup_flag() */ +#include "field_of_view.h" /* build_fov_map() */ +#include "hardcoded_strings.h" /* s */ #include "init.h" /* remake_world() */ -#include "io.h" /* io_round() */ -#include "thing_actions.h" /* get_thing_action_id_by_name() */ -#include "things.h" /* Thing, get_player() */ +#include "io.h" /* io_round(), save_world() */ +#include "map.h" /* remake_map() */ +#include "thing_actions.h" /* ThingAction */ +#include "things.h" /* Thing, get_thing(), own_thing(), add_thing(), + * get_thing_action_id_by_name(), get_player() + */ #include "world.h" /* global world */ +/* Parse/apply god command in "tok0"/"tok1" on "t" owning another thing. */ +static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t); + +/* Parse/apply god commansd in "tok0"/"tok1" on positioning a thing "t". */ +static uint8_t parse_position(char* tok0, char * tok1, struct Thing * t); + +/* Parse/apply god command in "tok0"/"tok1" oo setting "t"'s thing type. */ +static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t); + +/* Parse/apply god command in "tok0"/"tok1" on setting up thing "t". */ +static uint8_t parse_thing_command(char * tok0, char * tok1, struct Thing * t); + +/* Parse/apply god command on enabling/disabling generation of fields of view on + * god commands that may affect them, via static global "do_fov". On enabling, + * (re-)generate all animate things' fields of view. + */ +static uint8_t parse_do_fov(char * tok0, char * tok1); + +/* Parse/apply god command in "tok0"/"tok1" manipulating a thing's state. */ +static uint8_t parse_thing_manipulation(char * tok0, char * tok1); + +/* Parse player command in "tok0"/"tok1" to action in player thing. */ +static uint8_t parse_player_command(char * tok0, char * tok1); + +/* Compares first line of server out file to world.server_test, aborts if they + * don't match, but not before unsetting the flags deleting files in the server + * directory, for in that case those must be assumed to belong to another server + * process. + */ +static void server_test(); + /* Run the game world and its inhabitants (and their actions) until the player * avatar is free to receive new commands (or is dead). */ static void turn_over(); -/* If "msg"'s first part matches "command_name", set player's Thing's .command - * to the command's id and its .arg to a numerical value following in the latter - * part of "msg" (if no digits are found, use 0); then finish player's turn and - * turn game over to the NPCs via turn_over(); then return 1. Else, return 0. - */ -static uint8_t apply_player_command(char * msg, char * command_name); -/* Compares first line of file at world.path_out to world.server_test, aborts if - * they don't match, but not before unsetting the flags deleting files in the - * server directory, for in that case those must be assumed to belong to another - * server process. - */ -static void server_test(); + +/* Do god commands to create / position things generate their fields of view? */ +static uint8_t do_fov = 0; + + + +static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t) +{ + uint8_t id; + if (parse_val(tok0, tok1, s[CMD_CARRIES], '8', (char *) &id)) + { + if (!err_line(id == t->id, "Thing cannot carry itself.")) + { + struct Thing * o = get_thing(world.things, id, 0); + if (!err_line(!o, "Thing cannot carry thing that does not exist.")) + { + own_thing(&(t->owns), &world.things, id); + o->pos = t->pos; + } + } + return 1; + } + return 0; +} + + + +static uint8_t parse_position(char* tok0, char * tok1, struct Thing * t) +{ + char axis = 0; + if (!strcmp(tok0, s[CMD_POS_Y])) + { + axis = 'y'; + } + else if (!strcmp(tok0, s[CMD_POS_X])) + { + axis = 'x'; + } + if (axis && !parsetest_int(tok1, '8')) + { + uint8_t length = atoi(tok1); + char * err = "Position is outside of map."; + if (!err_line(length >= world.map.length, err)) + { + if ('y' == axis) + { + t->pos.y = length; + } + else if ('x' == axis) + { + t->pos.x = length; + } + free(t->fov_map); + t->fov_map = do_fov && t->lifepoints ? build_fov_map(t) : t->fov_map; + } + return 1; + } + return 0; +} + + + +static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t) +{ + uint8_t type; + if (parse_val(tok0, tok1, s[CMD_TYPE], '8', (char *) &type)) + { + struct ThingType * tt = world.thing_types; + for (; NULL != tt && type != tt->id; tt = tt->next); + if (!err_line(!tt, "Thing type does not exist.")) + { + t->type = type; + } + return 1; + } + return 0; +} + + + +static uint8_t parse_thing_command(char * tok0, char * tok1, struct Thing * t) +{ + uint8_t command; + if (parse_val(tok0, tok1, s[CMD_COMMAND], '8', (char *) &command)) + { + if (!command) + { + t->command = command; + return 1; + } + struct ThingAction * ta = world.thing_actions; + for (; NULL != ta && command != ta->id; ta = ta->next); + if (!err_line(!ta, "Thing action does not exist.")) + { + t->command = command; + } + return 1; + } + return 0; +} + + + +static uint8_t parse_do_fov(char * tok0, char * tok1) +{ + if (parse_val(tok0, tok1, s[CMD_DO_FOV], '8', (char *) &do_fov)) + { + if (do_fov) + { + struct Thing * ti; + for (ti = world.things; ti; ti = ti->next) + { + ti->fov_map = ti->lifepoints ? build_fov_map(ti) : ti->fov_map; + } + } + return 1; + } + return 0; +} + + + +static uint8_t parse_thing_manipulation(char * tok0, char * tok1) +{ + uint8_t id; + static struct Thing * t = NULL; + if (t && ( parse_thing_type(tok0, tok1, t) + || parse_thing_command(tok0, tok1, t) + || parse_val(tok0, tok1, s[CMD_ARGUMENT], '8', (char *) &t->arg) + || parse_val(tok0, tok1, s[CMD_PROGRESS], '8', (char *) &t->progress) + + || parse_val(tok0, tok1, s[CMD_LIFEPOINTS],'8',(char *) &t->lifepoints) + || parse_position(tok0, tok1, t) + || parse_carry(tok0, tok1, t))); + else if (parse_val(tok0, tok1, s[CMD_THING], '8', (char *) &id)) + { + t = get_thing(world.things, id, 1); + if (!t) + { + t = add_thing(id, 0, 0); + set_cleanup_flag(CLEANUP_THINGS); + t->fov_map = do_fov && t->lifepoints ? build_fov_map(t) : t->fov_map; + } + } + else + { + return 0; + } + return 1; +} + + + +static uint8_t parse_player_command(char * tok0, char * tok1) +{ + struct Thing * player = get_player(); + if ( parse_val(tok0, tok1, s[CMD_WAIT], '8', (char *) &player->arg) + || parse_val(tok0, tok1, s[CMD_MOVE], '8', (char *) &player->arg) + || parse_val(tok0, tok1, s[CMD_PICKUP], '8', (char *) &player->arg) + || parse_val(tok0, tok1, s[CMD_DROP], '8', (char *) &player->arg) + || parse_val(tok0, tok1, s[CMD_USE], '8', (char *) &player->arg)) + { + player->command = get_thing_action_id_by_name(tok0); + turn_over(); + } + else + { + return 0; + } + return 1; +} + + + +static void server_test() +{ + char * f_name = "server_test()"; + char test[10 + 1 + 10 + 1 + 1]; + FILE * file = try_fopen(s[PATH_OUT], "r", f_name); + try_fgets(test, 10 + 10 + 1 + 1, file, f_name); + try_fclose(file, f_name); + if (strcmp(test, world.server_test)) + { + unset_cleanup_flag(CLEANUP_WORLDSTATE); + unset_cleanup_flag(CLEANUP_OUT); + unset_cleanup_flag(CLEANUP_IN); + char * msg = "Server test string in server output file does not match. " + "This indicates that the current server process has been " + "superseded by another one."; + exit_err(1, msg); + } +} @@ -85,77 +305,67 @@ static void turn_over() -static uint8_t apply_player_command(char * msg, char * command_name) +static void record_msg(char * msg) { - if (!strncmp(msg, command_name, strlen(command_name))) + char * f_name = "record_msg()"; + char path_tmp[strlen(s[PATH_RECORD]) + strlen(s[PATH_SUFFIX_TMP]) + 1]; + sprintf(path_tmp, "%s%s", s[PATH_RECORD], s[PATH_SUFFIX_TMP]); + FILE * file_tmp = try_fopen(path_tmp, "w", f_name); + if (!access(s[PATH_RECORD], F_OK)) { - struct Thing * player = get_player(); - player->arg = atoi(&(msg[strlen(command_name)])); - player->command = get_thing_action_id_by_name(command_name); - turn_over(); - return 1; + FILE * file_read = try_fopen(s[PATH_RECORD], "r", f_name); + uint32_t linemax = textfile_width(file_read); + char line[linemax + 1]; + while (try_fgets(line, linemax + 1, file_read, f_name)) + { + try_fwrite(line, strlen(line), 1, file_tmp, f_name); + } + try_fclose(file_read, f_name); } - return 0; + try_fwrite(msg, strlen(msg), 1, file_tmp, f_name); + try_fputc('\n', file_tmp, f_name); + try_fclose_unlink_rename(file_tmp, path_tmp, s[PATH_RECORD], f_name); } -static void server_test() +extern void obey_msg(char * msg, uint8_t do_record) { - char * f_name = "server_test()"; - char test[10 + 1 + 10 + 1 + 1]; - FILE * file = try_fopen(world.path_out, "r", f_name); - try_fgets(test, 10 + 10 + 1 + 1, file, f_name); - try_fclose(file, f_name); - if (strcmp(test, world.server_test)) + set_err_line_options("Trouble with message: ", msg, 0, 0); + char * msg_copy = strdup(msg); + char * tok0 = token_from_line(msg_copy); + char * tok1 = token_from_line(NULL); + char * tok2 = token_from_line(NULL); + if (err_line(!(tok0 && tok1) || tok2, "Bad number of tokens.")) { - unset_cleanup_flag(CLEANUP_WORLDSTATE); - unset_cleanup_flag(CLEANUP_OUT); - unset_cleanup_flag(CLEANUP_IN); - char * msg = "Server test string in server output file does not match. " - "This indicates that the current server process has been " - "superseded by another one."; - exit_err(1, msg); + return; } -} + if ( parse_thing_manipulation(tok0, tok1) + || parse_player_command(tok0, tok1) + || parse_val(tok0, tok1, s[CMD_SEED_RAND], 'U', (char *) &world.seed) + || parse_val(tok0, tok1, s[CMD_TURN], 'u', (char *) &world.turn) + || parse_do_fov(tok0, tok1)); + else if (parse_val(tok0, tok1, s[CMD_SEED_MAP], 'U', (char *) &world.seed_map)) - - -extern void obey_msg(char * msg, uint8_t do_record) -{ - char * f_name = "obey_msg()"; - if ( apply_player_command(msg, "wait") /* TODO: Check for non-error */ - || apply_player_command(msg, "move") /* return value of a modified */ - || apply_player_command(msg, "pick_up")/*get_thing_action_id_by_name()*/ - || apply_player_command(msg, "drop") /* and if id found, execute on */ - || apply_player_command(msg, "use")); /* it what's in */ - else /* apply_player_command(). */ - { - char * seed_command = "seed"; - if (!strncmp(msg, seed_command, strlen(seed_command))) - { - remake_world(atoi(&(msg[strlen(seed_command)]))); - } + { + remake_map(); + } + else if (parse_val(tok0, tok1, s[CMD_MAKE_WORLD], 'U', (char *) &world.seed)) + { + remake_world(); + } + else + { + err_line(1, "Unknown command."); + free(msg_copy); + return; } + world.last_update_turn = 0; + free(msg_copy); if (do_record) { - char path_tmp[strlen(world.path_record) + strlen(world.tmp_suffix) + 1]; - sprintf(path_tmp, "%s%s", world.path_record, world.tmp_suffix); - FILE * file_tmp = try_fopen(path_tmp, "w", f_name); - if (!access(world.path_record, F_OK)) - { - FILE * file_read = try_fopen(world.path_record, "r", f_name); - uint32_t linemax = textfile_width(file_read); - char line[linemax + 1]; - while (try_fgets(line, linemax + 1, file_read, f_name)) - { - try_fwrite(line, strlen(line), 1, file_tmp, f_name); - } - try_fclose(file_read, f_name); - } - try_fwrite(msg, strlen(msg), 1, file_tmp, f_name); - try_fputc('\n', file_tmp, f_name); - try_fclose_unlink_rename(file_tmp, path_tmp, world.path_record, f_name); + save_world(); + record_msg(msg); } } diff --git a/src/server/run.h b/src/server/run.h index 9fa526f..07ca902 100644 --- a/src/server/run.h +++ b/src/server/run.h @@ -10,8 +10,8 @@ -/* Try parsing "msg" into a server or player command to run. Player commands are - * are recorded into the record file at world.path_record if "do_record" is set. +/* Try parsing "msg" into a command to apply, and apply it. Record commands to + * the file at world.path_record if "do_record" is set. */ extern void obey_msg(char * msg, uint8_t do_record); @@ -19,8 +19,8 @@ extern void obey_msg(char * msg, uint8_t do_record); * on "QUIT" command. In replay mode, exits with 0 on each non-"QUIT" command. * Writes a "PONG" line to server output file on "PING" command. In play mode, * processes further incomming commands via obey_msg(). Compares the first line - * of the file at world.path_out with world.server_test to ensure that the - * current server process has not been superseded by a new one. + * of the server out file with world.server_test to ensure that the current + * server process has not been superseded by a new one. */ extern uint8_t io_loop(); diff --git a/src/server/things.c b/src/server/things.c index d5aff96..e0aa674 100644 --- a/src/server/things.c +++ b/src/server/things.c @@ -2,12 +2,12 @@ #include "things.h" #include /* NULL */ -#include /* uint8_t, uint16_t, UINT16_MAX */ +#include /* uint8_t, uint16_t, UINT8_MAX, UINT16_MAX */ #include /* free() */ #include /* memset(), strlen() */ #include "../common/rexit.h" /* exit_err() */ #include "../common/try_malloc.h" /* try_malloc() */ -#include "../common/yx_uint8.h" /* yx_uint8 struct */ +#include "../common/yx_uint8.h" /* yx_uint8 */ #include "map.h" /* is_passable() */ #include "rrand.h" /* rrand() */ #include "world.h" /* global world */ @@ -15,15 +15,29 @@ -/* Return pointer to thing of "id" in chain starting at "ptr". */ -static struct Thing * get_thing(struct Thing * ptr, uint8_t id); -/* Add thing of "type" to map on passable position. Don't put actor on actor. */ -static void add_thing(uint8_t type); +/* Return lowest unused id for new thing. */ +static uint8_t get_lowest_unused_id(); -static struct Thing * get_thing(struct Thing * ptr, uint8_t id) +static uint8_t get_lowest_unused_id() +{ + uint8_t i = 0; + while (1) + { + if (!get_thing(world.things, i, 1)) + { + return i; + } + exit_err(i == UINT8_MAX, "No unused ID available to add new thing."); + i++; + } +} + + + +extern struct Thing * get_thing(struct Thing * ptr, uint8_t id, uint8_t deep) { while (1) { @@ -31,10 +45,13 @@ static struct Thing * get_thing(struct Thing * ptr, uint8_t id) { return ptr; } - struct Thing * owned_thing = get_thing(ptr->owns, id); - if (NULL != owned_thing) + if (deep) { - return ptr; + struct Thing * owned_thing = get_thing(ptr->owns, id, 1); + if (NULL != owned_thing) + { + return ptr; + } } ptr = ptr->next; } @@ -42,18 +59,32 @@ static struct Thing * get_thing(struct Thing * ptr, uint8_t id) -static void add_thing(uint8_t type) +extern void free_thing_types(struct ThingType * tt_start) +{ + if (NULL == tt_start) + { + return; + } + free_thing_types(tt_start->next); + free(tt_start->name); + free(tt_start); +} + + + +extern struct Thing * add_thing(int16_t id, uint8_t type, uint8_t find_pos) { char * f_name = "add_thing()"; struct ThingType * tt = get_thing_type(type); struct Thing * t = try_malloc(sizeof(struct Thing), f_name); memset(t, 0, sizeof(struct Thing)); - t->id = world.thing_count++; + t->id = (0 <= id && id <= UINT8_MAX) ? id : get_lowest_unused_id(); t->type = tt->id; t->lifepoints = tt->lifepoints; char * err = "Space to put thing on too hard to find. Map too small?"; uint16_t i = 0; - while (1) + memset(&(t->pos), 0, sizeof(struct yx_uint8)); + while (find_pos) { struct yx_uint8 pos; for (pos.y = pos.x = 0; 0 == is_passable(pos); i++) @@ -81,19 +112,7 @@ static void add_thing(uint8_t type) struct Thing ** t_ptr_ptr = &world.things; for (; NULL != * t_ptr_ptr; t_ptr_ptr = &(*t_ptr_ptr)->next); * t_ptr_ptr = t; -} - - - -extern void free_thing_types(struct ThingType * tt_start) -{ - if (NULL == tt_start) - { - return; - } - free_thing_types(tt_start->next); - free(tt_start->name); - free(tt_start); + return t; } @@ -103,7 +122,7 @@ extern void add_things(uint8_t type, uint8_t n) uint8_t i; for (i = 0; i < n; i++) { - add_thing(type); + add_thing(-1, type, 1); } } @@ -160,7 +179,7 @@ extern void own_thing(struct Thing ** target, struct Thing ** source, extern struct Thing * get_player() { - return get_thing(world.things, 0); + return get_thing(world.things, 0, 1); } diff --git a/src/server/things.h b/src/server/things.h index 3040d04..889374a 100644 --- a/src/server/things.h +++ b/src/server/things.h @@ -14,8 +14,8 @@ struct Thing { - struct Thing * next; /* pointer to next one in things chain */ - struct Thing * owns; /* chain of things owned / in inventory */ + struct Thing * next; /* pointer to next one in things chain */ + struct Thing * owns; /* chain of things owned / in inventory */ struct yx_uint8 pos; /* coordinate on map */ uint8_t * fov_map; /* map of the thing's field of view */ uint8_t id; /* individual thing's unique identifier */ @@ -40,9 +40,18 @@ struct ThingType +/* Return thing of "id" in chain at "ptr", search inventories too if "deep". */ +extern struct Thing * get_thing(struct Thing * ptr, uint8_t id, uint8_t deep); + /* Free thing types chain starting at "tt_start". */ extern void free_thing_types(struct ThingType * tt_start); +/* Add thing of "id" and "type" to map on random passable position (positions + * which contain an actor are not deemed passable) if "find_pos", else on y=0, + * x=0. If "id" is >= 0 and <= UINT8_MAX, use lowest unused id. Return thing. + */ +extern struct Thing * add_thing(int16_t id, uint8_t type, uint8_t find_pos); + /* Add thing(s) ("n": how many?) of "type" to map on random position(s). New * animate things are never placed in the same square with other animate ones. */ diff --git a/src/server/world.h b/src/server/world.h index 91a32a7..4318562 100644 --- a/src/server/world.h +++ b/src/server/world.h @@ -19,27 +19,21 @@ struct World { FILE * file_in; /* Input stream on file at .path_in. */ FILE * file_out; /* Output stream on file at .path_out. */ - struct Map map; + struct Map map; /* Game map. */ struct ThingType * thing_types; /* Thing type definitions. */ struct ThingAction * thing_actions; /* Thing action definitions. */ struct Thing * things; /* All physical things of the game world. */ char * log; /* Logs the game events from the player's view. */ char * server_test; /* String uniquely identifying server process. */ - char * path_in; /* File to write client messages into. */ - char * path_out; /* File to write server messages into. */ - char * path_worldstate; /* File to represent world state to clients.*/ - char * path_record; /* Record file from which to read the game history. */ - char * path_config; /* Path for thing type / action definitions file. */ - char * tmp_suffix; /* Appended to paths of files for their tmp versions. */ char * queue; /* Stores un-processed messages read from the input file. */ uint32_t queue_size;/* Length of .queue sequence of \0-terminated strings.*/ uint32_t seed; /* Randomness seed. */ + uint32_t seed_map; /* Map seed. */ uint16_t replay; /* Turn up to which to replay game. No replay if zero. */ uint16_t turn; /* Current game turn. */ uint16_t last_update_turn; /* Last turn the .path_out file was updated. */ uint8_t player_type; /* Thing type that player will start as. */ uint8_t is_verbose; /* Should server send debugging info to stdout? */ - uint8_t thing_count; /* Counts things generated so far. */ uint8_t enemy_fov; /* != 0 if non-player actors only see field of view. */ };