From 639a12151eeb7ce0d18de330b4bb8d9d8094c4d3 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Tue, 13 May 2014 06:01:02 +0200 Subject: [PATCH] Server: Add ENEMY_FOV option (default: off) to force FOV on enemies. --- README | 15 ++++-- TODO | 2 + confserver/world | 1 + src/server/ai.c | 29 +++++++---- src/server/configfile.c | 23 ++++++++- src/server/field_of_view.c | 98 +++++--------------------------------- src/server/field_of_view.h | 34 +++++++++++-- src/server/io.c | 61 ++++++++++++++++++++---- src/server/map.c | 8 ++++ src/server/map.h | 7 ++- src/server/world.h | 1 + 11 files changed, 166 insertions(+), 113 deletions(-) diff --git a/README b/README index ff6a396..1923471 100644 --- a/README +++ b/README @@ -13,7 +13,9 @@ numbers of turns to finish. Enemies' AI is very dumb so far: Each turn, they try to move towards their shortest-path-wise nearest enemy. If no enemy is found in their surroundings, -they just wait. +they just wait. Contrary to the player, they by default see the whole map. (To +make them see only what is in their line of sight, enable ENEMY_FOV in the +server config file; see below "Hacking / server internals and configuration".) 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 @@ -78,8 +80,9 @@ Hacking / server internals and configuration -------------------------------------------- The ./confserver/world file defines the map object types, actions available to -them and the map itself. Each definition consists of a multi-line block wherein -each line sets one attribute of the object type, action or the map. +them, the map itself, the map object type (species) of the player and whether +enemies see the whole map or only a line-of-sight field of view. Each definition +consists of a single- or multi-line block wherein each line sets one attribute. Here's a typical map definition block: @@ -130,6 +133,12 @@ yet: or if they are inanimate, but are otherwise crushed). Note that the after, it may even be the same). "START_NUMBER" sets the number of objects that are to appear of the given type on the map on game start. +A line of "PLAYER_TYPE" followed by a number sets the map object type (id) of +the player's creature. "ENEMY_FOV" followed by "0" or "1" sets whether enemies +see the whole map or only that to which they have an unobstructed line of sight. +Since plomrogue's FOV algorithm is currently very expensive, this is disabled by +default. + All these definition block members must be present within their blocks, but only "ACTION" / "OBJECT" / "MAP_TYPE" must be positioned at their respective blocks' first line; the others may appear in whatever order and even multiple times. If diff --git a/TODO b/TODO index 414e155..bb54d85 100644 --- a/TODO +++ b/TODO @@ -13,6 +13,8 @@ BOTH SERVER/CLIENT: 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 diff --git a/confserver/world b/confserver/world index 724d012..bf2488c 100644 --- a/confserver/world +++ b/confserver/world @@ -3,6 +3,7 @@ HEIGHT 64 WIDTH 64 PLAYER_TYPE 0 +ENEMY_FOV 0 ACTION 1 NAME wait diff --git a/src/server/ai.c b/src/server/ai.c index 9c31153..d099c57 100644 --- a/src/server/ai.c +++ b/src/server/ai.c @@ -5,6 +5,7 @@ #include /* uint8_t, uint16_t, uint32_t, UINT16_MAX */ #include /* free() */ #include "../common/try_malloc.h" /* try_malloc() */ +#include "field_of_view.h" /* build_fov_map() */ #include "map_object_actions.h" /* get_moa_id_by_name() */ #include "map_objects.h" /* struct MapObj */ #include "world.h" /* global world */ @@ -27,15 +28,16 @@ static void get_neighbor_scores(uint16_t * score_map, uint16_t pos_i, * each cell's score against the score of its immediate neighbors in N_DIRS * directions. If it's neighbors are low enough that the result would be lower * than the current value, re-set it to 1 point higher than its lowest-scored - * neighbor- Repeat this whole process until all cells have settled on their + * neighbor. Repeat this whole process until all cells have settled on their * final score. Ignore cells whose position in "score_map" fits cells of - * unreachable terrain in world.map.cells. Expect "max_score" to be the maximum - * score for cells, marking them as unreachable. + * unreachable terrain in world.map.cells or whose score is greater than + * "max_score". Expect "max_score" to be the maximum score for cells, marking + * them as unreachable. */ static void dijkstra_map(uint16_t * score_map, uint16_t max_score); /* Return numpad char of direction ("8", "6", "2", "4" etc.) of enemy with the - * shortest path to "mo_origin". If no enemy is around, return 0. + * shortest path visible to "mo_origin". If no enemy is around, return 0. */ static char get_dir_to_nearest_enemy(struct MapObj * mo_origin); @@ -93,7 +95,7 @@ static void dijkstra_map(uint16_t * score_map, uint16_t max_score) scores_still_changing = 0; for (pos = 0; pos < map_size; pos++) { - if ('.' == world.map.cells[pos]) + if ('.' == world.map.cells[pos] && score_map[pos] <= max_score) { get_neighbor_scores(score_map, pos, max_score, neighbors); min_neighbor = max_score; @@ -120,18 +122,27 @@ static char get_dir_to_nearest_enemy(struct MapObj * mo_origin) { char * f_name = "get_dir_to_nearest_enemy()"; - /* Calculate for each cell the distance to the nearest map actor that is - * not "mo_origin", with movement only possible in the directions of "dir". + /* Calculate for each cell the distance to the visibly nearest map actor not + * "mo_origin", with movement only possible in the directions of "dir". * (Actors' own cells start with a distance of 0 towards themselves.) */ uint32_t map_size = world.map.size.y * world.map.size.x; - uint16_t max_score = UINT16_MAX; + uint16_t max_score = UINT16_MAX - 1; uint16_t * score_map = try_malloc(map_size * sizeof(uint16_t), f_name); + uint8_t * fov_map = world.enemy_fov ? build_fov_map(mo_origin) : NULL; uint32_t i; for (i = 0; i < map_size; i++) { - score_map[i] = max_score; + if (world.enemy_fov) + { + score_map[i] = fov_map[i] & VISIBLE ? max_score : UINT16_MAX; + } + else + { + score_map[i] = max_score; + } } + free(fov_map); struct MapObj * mo = world.map_objs; for (; mo != NULL; mo = mo->next) { diff --git a/src/server/configfile.c b/src/server/configfile.c index fc2c771..1446439 100644 --- a/src/server/configfile.c +++ b/src/server/configfile.c @@ -93,6 +93,9 @@ static void test_corpse_ids(); /* set_members() helper specifically for editing world.map members. */ static uint8_t set_map_members(char * token0,char * token1,uint8_t * map_flags); +/* If "token0" matches "comparand", set world.enemy_fov to "token1". */ +static uint8_t set_enemy_fov(char * token0, char * comparand, char * token1); + /* If "token0" matches "comparand", set world.player_type to int in "token1". */ static uint8_t set_player_type(char * token0, char * comparand, char * token1); @@ -121,6 +124,7 @@ static void tokens_into_entries(char * token0, char * token1) char * str_obj = "OBJECT"; char * str_map = "MAP_TYPE"; char * str_player = "PLAYER_TYPE"; + char * str_enemyfov = "ENEMY_FOV"; static struct MapObjAct ** moa_p_p = &world.map_obj_acts; static struct MapObjDef ** mod_p_p = &world.map_obj_defs; static uint8_t action_flags = READY_ACT; @@ -129,7 +133,8 @@ static void tokens_into_entries(char * token0, char * token1) static struct EntryHead * moa = NULL; static struct EntryHead * mod = NULL; if (!token0 || !strcmp(token0, str_act) || !strcmp(token0, str_obj) - || !strcmp(token0, str_map) || !strcmp(token0, str_player)) + || !strcmp(token0, str_map) || !strcmp(token0, str_player) + || !strcmp(token0, str_enemyfov)) { parse_and_reduce_to_readyflag(&action_flags, READY_ACT); parse_and_reduce_to_readyflag(&object_flags, READY_OBJ); @@ -152,6 +157,7 @@ static void tokens_into_entries(char * token0, char * token1) (struct EntryHead *) world.map_obj_defs) || start_map(token0, str_map, &map_flags) || set_player_type(token0, str_player, token1) + || set_enemy_fov(token0, str_enemyfov, token1) || set_members(token0, token1, &object_flags, &action_flags, &map_flags, (struct MapObjDef *)mod, (struct MapObjAct *) moa))) @@ -255,6 +261,21 @@ static uint8_t set_map_members(char * token0, char * token1,uint8_t * map_flags) +static uint8_t set_enemy_fov(char * token0, char * comparand, char * token1) +{ + if (strcmp(token0, comparand)) + { + return 0; + } + parsetest_int(token1, '8'); + int test = atoi(token1) > 1; + err_line(test, "Value must be 0 or 1."); + world.enemy_fov = atoi(token1); + return 1; +} + + + static uint8_t set_player_type(char * token0, char * comparand, char * token1) { if (strcmp(token0, comparand)) diff --git a/src/server/field_of_view.c b/src/server/field_of_view.c index a48582a..c92c11a 100644 --- a/src/server/field_of_view.c +++ b/src/server/field_of_view.c @@ -7,22 +7,13 @@ #include /* memset(), strchr(), strdup() */ #include "../common/rexit.h" /* exit_trouble() */ #include "../common/try_malloc.h" /* try_malloc() */ -#include "map_objects.h" /* MapObj, MapObjDef, get_player() */ +#include "map.h" /* yx_to_map_pos() */ +#include "map_objects.h" /* MapObj */ #include "yx_uint8.h" /* yx_uint8 */ #include "world.h" /* global world */ -/* States that cells in the fov map may be in. */ -enum fov_cell_states { - VISIBLE = 0x01, - HIDDEN = 0x02, - SHADOW_LEFT = 0x04, - SHADOW_RIGHT = 0x08, - LIMIT = 0x10, - HIDE_LATER = 0x20 -}; - /* Values for mv_yx_in_dir_wrap()'s wrapping directory memory. */ enum wraps { @@ -33,7 +24,7 @@ enum wraps }; /* Transform "yx" to an index position in the world map. */ -static uint16_t yx_to_pos(struct yx_uint8 * yx); +//static uint16_t yx_to_pos(struct yx_uint8 * yx); /* Move "yx" into hex direction "d". If this moves "yx" beyond the minimal (0) * or maximal (UINT8_MAX) column or row, it wraps to the opposite side. Such @@ -148,31 +139,6 @@ static void set_view_of_cell_and_shadows(struct yx_uint8 * yx_cell, struct yx_uint8 * yx_eye, uint8_t * fov_map); -/* Return overlay of world map wherein all cell positions visible from player's - * positions have flag VISIBLE set. - * - * This is achieved by spiraling out clock-wise from the player position, - * flagging cells as VISIBLE unless they're already marked as HIDDEN, and, on - * running into obstacles for view that are not HIDDEN, casting shadows from - * these, i.e. drawing cells as HIDDEN that would be hidden by said obstacle, - * before continuing the original spiraling path. - * - * Shadowcasting during spiraling is initially lazy, flagging only the shadows' - * interior cells as HIDDEN and their border cells as HIDE_LATER. Only at the - * end are all cells flagged HIDE_LATER flagged as HIDDEN. This is to handle - * cases where obstacles to view sit right at the border of pre-estabilshed - * shadows, therefore might be ignored if HIDDEN and not cast shadows on their - * own that may slightly extend beyond the pre-established shadows they border. - */ -static uint8_t * build_fov_map(); - - - -static uint16_t yx_to_pos(struct yx_uint8 * yx) -{ - return (yx->y * world.map.size.x) + yx->x; -} - static uint8_t mv_yx_in_dir_wrap(char d, struct yx_uint8 * yx, uint8_t unwrap) @@ -290,7 +256,7 @@ extern void draw_border_circle(struct yx_uint8 yx, uint8_t radius, { if (mv_yx_in_dir_legal(dir, &yx)) { - uint16_t pos = yx_to_pos(&yx); + uint16_t pos = yx_to_map_pos(&yx); fov_map[pos] = LIMIT; } } @@ -518,7 +484,7 @@ static uint16_t shadow_arm(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start, uint8_t shift_right) { struct yx_uint8 yx_border = *yx_start; - uint16_t pos; + uint16_t pos = yx_to_map_pos(&yx_border); if (mv_yx_in_dir_legal(dir, &yx_border)) { uint8_t met_limit = 0; @@ -527,7 +493,7 @@ static uint16_t shadow_arm(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start, yx_border = *yx_start; while (!met_limit && mv_yx_in_dir_legal(dirs[i_dirs], &yx_border)) { - pos = yx_to_pos(&yx_border); + pos = yx_to_map_pos(&yx_border); met_limit = fov_map[pos] & LIMIT; fov_map[pos] = fov_map[pos] | flag; i_dirs = dirs[i_dirs + 1] ? i_dirs + 1 : 0; @@ -546,7 +512,7 @@ static void shadow(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start, uint16_t pos_a, pos_b, pos_start, i; pos_a = shadow_arm(yx_eye, yx_start, fov_map, dir_left, SHADOW_LEFT, 0); pos_b = shadow_arm(yx_eye, yx_start, fov_map, dir_right, SHADOW_RIGHT, 1); - pos_start = yx_to_pos(yx_start); + pos_start = yx_to_map_pos(yx_start); fov_map[pos_start] = fov_map[pos_start] | SHADOW_LEFT | SHADOW_RIGHT; fill_shadow(yx_eye, yx_start, fov_map, pos_a, pos_b); for (i = 0; i < world.map.size.y * world.map.size.x; i++) @@ -568,7 +534,7 @@ static void set_view_of_cell_and_shadows(struct yx_uint8 * yx_cell, uint8_t * fov_map) { char * dirs = "dcxswe"; - uint16_t pos = yx_to_pos(yx_cell); + uint16_t pos = yx_to_map_pos(yx_cell); if (!(fov_map[pos] & HIDDEN)) { fov_map[pos] = fov_map[pos] | VISIBLE; @@ -594,17 +560,16 @@ static void set_view_of_cell_and_shadows(struct yx_uint8 * yx_cell, -static uint8_t * build_fov_map() +extern uint8_t * build_fov_map(struct MapObj * eye) { char * f_name = "build_fov_map()"; uint8_t radius = 2 * world.map.size.y; uint32_t map_size = world.map.size.y * world.map.size.x; - struct MapObj * player = get_player(); - struct yx_uint8 yx = player->pos; + struct yx_uint8 yx = eye->pos; uint8_t * fov_map = try_malloc(map_size, f_name); memset(fov_map, 0, map_size); draw_border_circle(yx, radius, fov_map); - fov_map[yx_to_pos(&yx)] = VISIBLE; + fov_map[yx_to_map_pos(&yx)] = VISIBLE; uint8_t dist; for (dist = 1; dist <= radius; dist++) { @@ -617,7 +582,7 @@ static uint8_t * build_fov_map() first_round = 0; if (mv_yx_in_dir_legal(i_dir, &yx)) { - set_view_of_cell_and_shadows(&yx, &player->pos, fov_map); + set_view_of_cell_and_shadows(&yx, &eye->pos, fov_map); } } } @@ -631,42 +596,3 @@ static uint8_t * build_fov_map() } return fov_map; } - - - -extern char * build_visible_map() -{ - char * f_name = "build_visible_map()"; - uint8_t * fov_map = build_fov_map(); - uint32_t map_size = world.map.size.y * world.map.size.x; - 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 (fov_map[pos_i] & VISIBLE) - { - visible_map[pos_i] = world.map.cells[pos_i]; - } - } - struct MapObj * o; - struct MapObjDef * d; - char c; - uint8_t i; - for (i = 0; i < 2; i++) - { - for (o = world.map_objs; o != 0; o = o->next) - { - if ( fov_map[yx_to_pos(&o->pos)] & VISIBLE - && ( (0 == i && 0 == o->lifepoints) - || (1 == i && 0 < o->lifepoints))) - { - d = get_map_object_def(o->type); - c = d->char_on_map; - visible_map[yx_to_pos(&o->pos)] = c; - } - } - } - free(fov_map); - return visible_map; -} diff --git a/src/server/field_of_view.h b/src/server/field_of_view.h index eb117ae..ee337eb 100644 --- a/src/server/field_of_view.h +++ b/src/server/field_of_view.h @@ -1,6 +1,6 @@ /* src/server/field_of_view.h * - * Generate view of map as visible to player. + * Generate field of view maps. */ @@ -8,12 +8,38 @@ #ifndef FIELD_OF_VIEW_H #define FIELD_OF_VIEW_H +#include /* uint8_t */ +struct MapObj; -/* Return map cells sequence as visible to the player, with invisible cells as - * whitespace. Super-impose over visible map cells map objects positioned there. + +/* States that cells in the fov map may be in. */ +enum fov_cell_states { + VISIBLE = 0x01, + HIDDEN = 0x02, + SHADOW_LEFT = 0x04, + SHADOW_RIGHT = 0x08, + LIMIT = 0x10, + HIDE_LATER = 0x20 +}; + +/* Return overlay of world map wherein all cell positions visible from player's + * positions have flag VISIBLE set. + * + * This is achieved by spiraling out clock-wise from the player position, + * flagging cells as VISIBLE unless they're already marked as HIDDEN, and, on + * running into obstacles for view that are not HIDDEN, casting shadows from + * these, i.e. drawing cells as HIDDEN that would be hidden by said obstacle, + * before continuing the original spiraling path. + * + * Shadowcasting during spiraling is initially lazy, flagging only the shadows' + * interior cells as HIDDEN and their border cells as HIDE_LATER. Only at the + * end are all cells flagged HIDE_LATER flagged as HIDDEN. This is to handle + * cases where obstacles to view sit right at the border of pre-estabilshed + * shadows, therefore might be ignored if HIDDEN and not cast shadows on their + * own that may slightly extend beyond the pre-established shadows they border. */ -extern char * build_visible_map(); +extern uint8_t * build_fov_map(struct MapObj * eye); diff --git a/src/server/io.c b/src/server/io.c index 81251c1..7fbb511 100644 --- a/src/server/io.c +++ b/src/server/io.c @@ -5,10 +5,10 @@ #include /* global errno */ #include /* PIPE_BUF */ #include /* size_t, NULL */ -#include /* uint8_t, uint32_t */ +#include /* uint8_t, uint16_t, uint32_t */ #include /* defines EOF, FILE, sprintf() */ #include /* free() */ -#include /* strlen(), memcpy() */ +#include /* strlen(), memcpy(), memset() */ #include /* time_t */ #include /* time(), nanosleep() */ #include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(), @@ -16,7 +16,8 @@ */ #include "../common/try_malloc.h" /* try_malloc() */ #include "cleanup.h" /* set_cleanup_flag() */ -#include "field_of_view.h" /* build_visible_map() */ +#include "field_of_view.h" /* VISIBLE, build_fov_map() */ +#include "map.h" /* yx_to_map_pos() */ #include "map_objects.h" /* structs MapObj, MapObjDef, get_map_obj_def() */ #include "world.h" /* global world */ @@ -45,10 +46,15 @@ static void write_value_as_line(uint32_t value, FILE * file); /* Write to "file" player's inventory, one item name per line. End in "%\n". */ static void write_inventory(struct MapObj * player, FILE * file); -/* Write to "file" game map as visible to the player, build_visible_map()-drawn. +/* Return map cells sequence as visible to the "player", with invisible cells as + * whitespace. Super-impose over visible map cells map objects positioned there. + */ +static char * build_visible_map(struct MapObj * player); + +/* Write to "file" game map as visible to "player", build_visible_map()-drawn. * Write one row per \n-delimited line. */ -static void write_map(FILE * file); +static void write_map(struct MapObj * player, FILE * file); @@ -138,7 +144,7 @@ static void update_worldstate_file() write_value_as_line(player->pos.x, file); write_value_as_line(world.map.size.y, file); write_value_as_line(world.map.size.x, file); - write_map(file); + write_map(player, file); if (world.log) { try_fwrite(world.log, strlen(world.log), 1, file, f_name); @@ -188,10 +194,49 @@ static void write_inventory(struct MapObj * player, FILE * file) -static void write_map(FILE * file) +static char * build_visible_map(struct MapObj * player) +{ + char * f_name = "build_visible_map()"; + uint8_t * fov_map = build_fov_map(player); + uint32_t map_size = world.map.size.y * world.map.size.x; + 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 (fov_map[pos_i] & VISIBLE) + { + visible_map[pos_i] = world.map.cells[pos_i]; + } + } + struct MapObj * o; + struct MapObjDef * d; + char c; + uint8_t i; + for (i = 0; i < 2; i++) + { + for (o = world.map_objs; o != 0; o = o->next) + { + if ( fov_map[yx_to_map_pos(&o->pos)] & VISIBLE + && ( (0 == i && 0 == o->lifepoints) + || (1 == i && 0 < o->lifepoints))) + { + d = get_map_object_def(o->type); + c = d->char_on_map; + visible_map[yx_to_map_pos(&o->pos)] = c; + } + } + } + free(fov_map); + return visible_map; +} + + + +static void write_map(struct MapObj * player, FILE * file) { char * f_name = "write_map()"; - char * visible_map = build_visible_map(); + char * visible_map = build_visible_map(player); uint16_t x, y; for (y = 0; y < world.map.size.y; y++) { diff --git a/src/server/map.c b/src/server/map.c index 36c4465..52a9b0b 100644 --- a/src/server/map.c +++ b/src/server/map.c @@ -156,3 +156,11 @@ extern uint8_t is_passable(struct yx_uint8 pos) } return passable; } + + + +extern uint16_t yx_to_map_pos(struct yx_uint8 * yx) +{ + return (yx->y * world.map.size.x) + yx->x; +} + diff --git a/src/server/map.h b/src/server/map.h index 45ddeaf..9e3c548 100644 --- a/src/server/map.h +++ b/src/server/map.h @@ -1,12 +1,12 @@ /* src/server/map.h * - * Struct for the game map and routines to create and scroll on it. + * Struct for the game map and routines to create and navigate on it. */ #ifndef MAP_H #define MAP_H -#include /* uint8_t */ +#include /* uint8_t, uint16_t */ #include "../common/yx_uint8.h" /* yx_uint8 struct */ #include "../common/yx_uint16.h" /* yx_uint16 struct */ @@ -34,6 +34,9 @@ extern void init_map(); */ extern uint8_t is_passable(struct yx_uint8 pos); +/* Transform "yx" to an index position in the world map. */ +extern uint16_t yx_to_map_pos(struct yx_uint8 * yx); + #endif diff --git a/src/server/world.h b/src/server/world.h index ed019f2..be772fc 100644 --- a/src/server/world.h +++ b/src/server/world.h @@ -40,6 +40,7 @@ struct World uint8_t player_type; /* Map object type that player will start as. */ uint8_t is_verbose; /* Should server send debugging info to stdout? */ uint8_t map_obj_count; /* Counts map objects generated so far. */ + uint8_t enemy_fov; /* != 0 if non-player actors only see field of view. */ }; extern struct World world; -- 2.30.2