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
--------------------------------------------
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:
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
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
WIDTH 64
PLAYER_TYPE 0
+ENEMY_FOV 0
ACTION 1
NAME wait
#include <stdint.h> /* uint8_t, uint16_t, uint32_t, UINT16_MAX */
#include <stdlib.h> /* 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 */
* 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);
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;
{
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)
{
/* 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);
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;
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);
(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)))
+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))
#include <string.h> /* 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
{
};
/* 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
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)
{
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;
}
}
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;
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;
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++)
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;
-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++)
{
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);
}
}
}
}
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;
-}
/* src/server/field_of_view.h
*
- * Generate view of map as visible to player.
+ * Generate field of view maps.
*/
#ifndef FIELD_OF_VIEW_H
#define FIELD_OF_VIEW_H
+#include <stdint.h> /* 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);
#include <errno.h> /* global errno */
#include <limits.h> /* PIPE_BUF */
#include <stddef.h> /* size_t, NULL */
-#include <stdint.h> /* uint8_t, uint32_t */
+#include <stdint.h> /* uint8_t, uint16_t, uint32_t */
#include <stdio.h> /* defines EOF, FILE, sprintf() */
#include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), memcpy() */
+#include <string.h> /* strlen(), memcpy(), memset() */
#include <sys/types.h> /* time_t */
#include <time.h> /* time(), nanosleep() */
#include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(),
*/
#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 */
/* 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);
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);
-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++)
{
}
return passable;
}
+
+
+
+extern uint16_t yx_to_map_pos(struct yx_uint8 * yx)
+{
+ return (yx->y * world.map.size.x) + yx->x;
+}
+
/* 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 <stdint.h> /* uint8_t */
+#include <stdint.h> /* uint8_t, uint16_t */
#include "../common/yx_uint8.h" /* yx_uint8 struct */
#include "../common/yx_uint16.h" /* yx_uint16 struct */
*/
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
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;