From: Christian Heller Date: Tue, 25 Aug 2015 03:08:00 +0000 (+0200) Subject: Remove C variant of server, redefine build system to match this change. X-Git-Tag: tce~339 X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/todo?a=commitdiff_plain;h=dcdb99eb5863af512f8f079c320216bfeec5d9e2;p=plomrogue Remove C variant of server, redefine build system to match this change. --- diff --git a/all.do b/all.do index 96e8536..337d4c2 100644 --- a/all.do +++ b/all.do @@ -1,8 +1,8 @@ -# redo build file to build executables "roguelike-server", "roguelike-client". +# redo build file to build executables "roguelike-server", "libplomrogue.so". # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 # or any later version. For details on its copyright, license, and warranties, # see the file NOTICE in the root directory of the PlomRogue source package. -redo-ifchange roguelike-server +redo-ifchange roguelike-server redo-ifchange roguelike-client diff --git a/build/compiler_flags b/build/compiler_flags index 59fcb18..198da87 100644 --- a/build/compiler_flags +++ b/build/compiler_flags @@ -2,4 +2,4 @@ # or any later version. For details on its copyright, license, and warranties, # see the file NOTICE in the root directory of the PlomRogue source package. -CFLAGS='-std=c11 -pedantic-errors -Wall -Werror -Wextra -Wformat-security -g' +CFLAGS='-std=c11 -pedantic-errors -Wall -Werror -Wextra -Wformat-security -O3' diff --git a/compile-server.sh b/compile-server.sh deleted file mode 100755 index 5fdc7b2..0000000 --- a/compile-server.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -gcc -shared -fPIC -std=c11 -pedantic-errors -Wall -Werror -Wextra -Wformat-security -O3 -o libplomrogue.so libplomrogue.c -lm diff --git a/plomrogue-server.py b/plomrogue-server.py deleted file mode 100755 index ef3fdbc..0000000 --- a/plomrogue-server.py +++ /dev/null @@ -1,1629 +0,0 @@ -#!/usr/bin/python3 - -# This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 -# or any later version. For details on its copyright, license, and warranties, -# see the file NOTICE in the root directory of the PlomRogue source package. - - -import argparse -import errno -import os -import shlex -import shutil -import time -import ctypes - - -class RandomnessIO: - """"Interface to libplomrogue's pseudo-randomness generator.""" - - def set_seed(self, seed): - libpr.seed_rrand(1, seed) - - def get_seed(self): - return libpr.seed_rrand(0, 0) - - def next(self): - return libpr.rrand() - - seed = property(get_seed, set_seed) - - -def prep_library(): - """Prepare ctypes library at ./libplomrogue.so""" - libpath = ("./libplomrogue.so") - if not os.access(libpath, os.F_OK): - raise SystemExit("No library " + libpath + - ", run ./compile-server.sh first?") - libpr = ctypes.cdll.LoadLibrary(libpath) - libpr.seed_rrand.restype = ctypes.c_uint32 - return libpr - - -def strong_write(file, string): - """Apply write(string), then flush().""" - file.write(string) - file.flush() - - -def setup_server_io(): - """Fill IO files DB with proper file( path)s. Write process IO test string. - - Ensure IO files directory at server/. Remove any old input file if found. - Set up new input file for reading, and new output file for writing. Start - output file with process hash line of format PID + " " + floated UNIX time - (io_db["teststring"]). Raise SystemExit if file is found at path of either - record or save file plus io_db["tmp_suffix"]. - """ - def detect_atomic_leftover(path, tmp_suffix): - path_tmp = path + tmp_suffix - msg = "Found file '" + path_tmp + "' that may be a leftover from an " \ - "aborted previous attempt to write '" + path + "'. Aborting " \ - "until matter is resolved by removing it from its current path." - if os.access(path_tmp, os.F_OK): - raise SystemExit(msg) - io_db["teststring"] = str(os.getpid()) + " " + str(time.time()) - io_db["save_wait"] = 0 - io_db["verbose"] = False - io_db["record_chunk"] = "" - os.makedirs(io_db["path_server"], exist_ok=True) - io_db["file_out"] = open(io_db["path_out"], "w") - strong_write(io_db["file_out"], io_db["teststring"] + "\n") - if os.access(io_db["path_in"], os.F_OK): - os.remove(io_db["path_in"]) - io_db["file_in"] = open(io_db["path_in"], "w") - io_db["file_in"].close() - io_db["file_in"] = open(io_db["path_in"], "r") - detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"]) - detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"]) - - -def cleanup_server_io(): - """Close and (if io_db["kicked_by_rival"] false) remove files in io_db.""" - def helper(file_key, path_key): - if file_key in io_db: - io_db[file_key].close() - if not io_db["kicked_by_rival"] \ - and os.access(io_db[path_key], os.F_OK): - os.remove(io_db[path_key]) - helper("file_in", "path_in") - helper("file_out", "path_out") - helper("file_worldstate", "path_worldstate") - if "file_record" in io_db: - io_db["file_record"].close() - - -def obey(command, prefix, replay=False, do_record=False): - """Call function from commands_db mapped to command's first token. - - Tokenize command string with shlex.split(comments=True). If replay is set, - a non-meta command from the commands_db merely triggers obey() on the next - command from the records file. If not, non-meta commands set - io_db["worldstate_updateable"] to world_db["WORLD_ACTIVE"], and, if - do_record is set, are recorded to io_db["record_chunk"], and save_world() - is called (and io_db["record_chunk"] written) if 15 seconds have passed - since the last time it was called. The prefix string is inserted into the - server's input message between its beginning 'input ' and ':'. All activity - is preceded by a server_test() call. - """ - server_test() - if io_db["verbose"]: - print("input " + prefix + ": " + command) - try: - tokens = shlex.split(command, comments=True) - except ValueError as err: - print("Can't tokenize command string: " + str(err) + ".") - return - if len(tokens) > 0 and tokens[0] in commands_db \ - and len(tokens) == commands_db[tokens[0]][0] + 1: - if commands_db[tokens[0]][1]: - commands_db[tokens[0]][2](*tokens[1:]) - elif replay: - print("Due to replay mode, reading command as 'go on in record'.") - line = io_db["file_record"].readline() - if len(line) > 0: - obey(line.rstrip(), io_db["file_record"].prefix - + str(io_db["file_record"].line_n)) - io_db["file_record"].line_n = io_db["file_record"].line_n + 1 - else: - print("Reached end of record file.") - else: - commands_db[tokens[0]][2](*tokens[1:]) - if do_record: - io_db["record_chunk"] += command + "\n" - if time.time() > io_db["save_wait"] + 15: - atomic_write(io_db["path_record"], io_db["record_chunk"], - do_append=True) - save_world() - io_db["record_chunk"] = "" - io_db["save_wait"] = time.time() - io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"] - elif 0 != len(tokens): - print("Invalid command/argument, or bad number of tokens.") - - -def atomic_write(path, text, do_append=False, delete=True): - """Atomic write of text to file at path, appended if do_append is set.""" - path_tmp = path + io_db["tmp_suffix"] - mode = "w" - if do_append: - mode = "a" - if os.access(path, os.F_OK): - shutil.copyfile(path, path_tmp) - file = open(path_tmp, mode) - strong_write(file, text) - file.close() - if delete and os.access(path, os.F_OK): - os.remove(path) - os.rename(path_tmp, path) - - -def save_world(): - """Save all commands needed to reconstruct current world state.""" - - def quote(string): - string = string.replace("\u005C", '\u005C\u005C') - return '"' + string.replace('"', '\u005C"') + '"' - - def mapsetter(key): - def helper(id): - string = "" - if world_db["Things"][id][key]: - map = world_db["Things"][id][key] - length = world_db["MAP_LENGTH"] - for i in range(length): - line = map[i * length:(i * length) + length].decode() - string = string + key + " " + str(i) + " " + quote(line) \ - + "\n" - return string - return helper - - def memthing(id): - string = "" - for memthing in world_db["Things"][id]["T_MEMTHING"]: - string = string + "T_MEMTHING " + str(memthing[0]) + " " + \ - str(memthing[1]) + " " + str(memthing[2]) + "\n" - return string - - def helper(category, id_string, special_keys={}): - string = "" - for id in sorted(world_db[category].keys()): - string = string + id_string + " " + str(id) + "\n" - for key in sorted(world_db[category][id].keys()): - if not key in special_keys: - x = world_db[category][id][key] - argument = quote(x) if str == type(x) else str(x) - string = string + key + " " + argument + "\n" - elif special_keys[key]: - string = string + special_keys[key](id) - return string - - string = "" - for key in sorted(world_db.keys()): - if dict != type(world_db[key]) and key != "MAP" and \ - key != "WORLD_ACTIVE" and key != "SEED_MAP": - string = string + key + " " + str(world_db[key]) + "\n" - string = string + "SEED_MAP " + str(world_db["SEED_MAP"]) + "\n" - string = string + helper("ThingActions", "TA_ID") - string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False}) - for id in sorted(world_db["ThingTypes"].keys()): - string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \ - str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n" - string = string + helper("Things", "T_ID", - {"T_CARRIES": False, "carried": False, - "T_MEMMAP": mapsetter("T_MEMMAP"), - "T_MEMTHING": memthing, "fovmap": False, - "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")}) - for id in sorted(world_db["Things"].keys()): - if [] != world_db["Things"][id]["T_CARRIES"]: - string = string + "T_ID " + str(id) + "\n" - for carried in sorted(world_db["Things"][id]["T_CARRIES"].keys()): - string = string + "T_CARRIES " + str(carried) + "\n" - string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \ - "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"]) - atomic_write(io_db["path_save"], string) - - -def obey_lines_in_file(path, name, do_record=False): - """Call obey() on each line of path's file, use name in input prefix.""" - file = open(path, "r") - line_n = 1 - for line in file.readlines(): - obey(line.rstrip(), name + "file line " + str(line_n), - do_record=do_record) - line_n = line_n + 1 - file.close() - - -def parse_command_line_arguments(): - """Return settings values read from command line arguments.""" - parser = argparse.ArgumentParser() - parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1, - action='store') - parser.add_argument('-l', nargs="?", const="save", dest='savefile', - action="store") - parser.add_argument('-v', dest='verbose', action='store_true') - opts, unknown = parser.parse_known_args() - return opts - - -def server_test(): - """Ensure valid server out file belonging to current process. - - This is done by comparing io_db["teststring"] to what's found at the start - of the current file at io_db["path_out"]. On failure, set - io_db["kicked_by_rival"] and raise SystemExit. - """ - if not os.access(io_db["path_out"], os.F_OK): - raise SystemExit("Server output file has disappeared.") - file = open(io_db["path_out"], "r") - test = file.readline().rstrip("\n") - file.close() - if test != io_db["teststring"]: - io_db["kicked_by_rival"] = True - msg = "Server test string in server output file does not match. This" \ - " indicates that the current server process has been " \ - "superseded by another one." - raise SystemExit(msg) - - -def read_command(): - """Return next newline-delimited command from server in file. - - Keep building return string until a newline is encountered. Pause between - unsuccessful reads, and after too much waiting, run server_test(). - """ - wait_on_fail = 0.03333 - max_wait = 5 - now = time.time() - command = "" - while True: - add = io_db["file_in"].readline() - if len(add) > 0: - command = command + add - if len(command) > 0 and "\n" == command[-1]: - command = command[:-1] - break - else: - time.sleep(wait_on_fail) - if now + max_wait < time.time(): - server_test() - now = time.time() - return command - - -def try_worldstate_update(): - """Write worldstate file if io_db["worldstate_updateable"] is set.""" - if io_db["worldstate_updateable"]: - - def draw_visible_Things(map, run): - for id in world_db["Things"]: - type = world_db["Things"][id]["T_TYPE"] - consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"] - alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"] - if (0 == run and not consumable and not alive) \ - or (1 == run and consumable and not alive) \ - or (2 == run and alive): - y = world_db["Things"][id]["T_POSY"] - x = world_db["Things"][id]["T_POSX"] - fovflag = world_db["Things"][0]["fovmap"][(y * length) + x] - if 'v' == chr(fovflag): - c = world_db["ThingTypes"][type]["TT_SYMBOL"] - map[(y * length) + x] = ord(c) - - def write_map(string, map): - for i in range(length): - line = map[i * length:(i * length) + length].decode() - string = string + line + "\n" - return string - - inventory = "" - if [] == world_db["Things"][0]["T_CARRIES"]: - inventory = "(none)\n" - else: - for id in world_db["Things"][0]["T_CARRIES"]: - type_id = world_db["Things"][id]["T_TYPE"] - name = world_db["ThingTypes"][type_id]["TT_NAME"] - inventory = inventory + name + "\n" - string = str(world_db["TURN"]) + "\n" + \ - str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \ - str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \ - inventory + "%\n" + \ - str(world_db["Things"][0]["T_POSY"]) + "\n" + \ - str(world_db["Things"][0]["T_POSX"]) + "\n" + \ - str(world_db["MAP_LENGTH"]) + "\n" - length = world_db["MAP_LENGTH"] - fov = bytearray(b' ' * (length ** 2)) - for pos in range(length ** 2): - if 'v' == chr(world_db["Things"][0]["fovmap"][pos]): - fov[pos] = world_db["MAP"][pos] - for i in range(3): - draw_visible_Things(fov, i) - string = write_map(string, fov) - mem = world_db["Things"][0]["T_MEMMAP"][:] - for i in range(2): - for mt in world_db["Things"][0]["T_MEMTHING"]: - consumable = world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"] - if (i == 0 and not consumable) or (i == 1 and consumable): - c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"] - mem[(mt[1] * length) + mt[2]] = ord(c) - string = write_map(string, mem) - atomic_write(io_db["path_worldstate"], string, delete=False) - strong_write(io_db["file_out"], "WORLD_UPDATED\n") - io_db["worldstate_updateable"] = False - - -def replay_game(): - """Replay game from record file. - - Use opts.replay as breakpoint turn to which to replay automatically before - switching to manual input by non-meta commands in server input file - triggering further reads of record file. Ensure opts.replay is at least 1. - Run try_worldstate_update() before each interactive obey()/read_command(). - """ - if opts.replay < 1: - opts.replay = 1 - print("Replay mode. Auto-replaying up to turn " + str(opts.replay) + - " (if so late a turn is to be found).") - if not os.access(io_db["path_record"], os.F_OK): - raise SystemExit("No record file found to replay.") - io_db["file_record"] = open(io_db["path_record"], "r") - io_db["file_record"].prefix = "record file line " - io_db["file_record"].line_n = 1 - while world_db["TURN"] < opts.replay: - line = io_db["file_record"].readline() - if "" == line: - break - obey(line.rstrip(), io_db["file_record"].prefix - + str(io_db["file_record"].line_n)) - io_db["file_record"].line_n = io_db["file_record"].line_n + 1 - while True: - try_worldstate_update() - obey(read_command(), "in file", replay=True) - - -def play_game(): - """Play game by server input file commands. Before, load save file found. - - If no save file is found, a new world is generated from the commands in the - world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this - command and all that follow via the server input file. Run - try_worldstate_update() before each interactive obey()/read_command(). - """ - if os.access(io_db["path_save"], os.F_OK): - obey_lines_in_file(io_db["path_save"], "save") - else: - if not os.access(io_db["path_worldconf"], os.F_OK): - msg = "No world config file from which to start a new world." - raise SystemExit(msg) - obey_lines_in_file(io_db["path_worldconf"], "world config ", - do_record=True) - obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True) - while True: - try_worldstate_update() - obey(read_command(), "in file", do_record=True) - - -def remake_map(): - """(Re-)make island map. - - Let "~" represent water, "." land, "X" trees: Build island shape randomly, - start with one land cell in the middle, then go into 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 at the map's - border. Then put some trees on the map (TODO: more precise algorithm desc). - """ - def is_neighbor(coordinates, type): - y = coordinates[0] - x = coordinates[1] - length = world_db["MAP_LENGTH"] - ind = y % 2 - diag_west = x + (ind > 0) - diag_east = x + (ind < (length - 1)) - pos = (y * length) + x - if (y > 0 and diag_east - and type == chr(world_db["MAP"][pos - length + ind])) \ - or (x < (length - 1) - and type == chr(world_db["MAP"][pos + 1])) \ - or (y < (length - 1) and diag_east - and type == chr(world_db["MAP"][pos + length + ind])) \ - or (y > 0 and diag_west - and type == chr(world_db["MAP"][pos - length - (not ind)])) \ - or (x > 0 - and type == chr(world_db["MAP"][pos - 1])) \ - or (y < (length - 1) and diag_west - and type == chr(world_db["MAP"][pos + length - (not ind)])): - return True - return False - store_seed = rand.seed - rand.seed = world_db["SEED_MAP"] - world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2)) - length = world_db["MAP_LENGTH"] - add_half_width = (not (length % 2)) * int(length / 2) - world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".") - while (1): - y = rand.next() % length - x = rand.next() % length - pos = (y * length) + x - if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."): - if y == 0 or y == (length - 1) or x == 0 or x == (length - 1): - break - world_db["MAP"][pos] = ord(".") - n_trees = int((length ** 2) / 16) - i_trees = 0 - while (i_trees <= n_trees): - single_allowed = rand.next() % 32 - y = rand.next() % length - x = rand.next() % length - pos = (y * length) + x - if "." == chr(world_db["MAP"][pos]) \ - and ((not single_allowed) or is_neighbor((y, x), "X")): - world_db["MAP"][pos] = ord("X") - i_trees += 1 - rand.seed = store_seed - # This all-too-precise replica of the original C code misses iter_limit(). - - -def update_map_memory(t, age_map=True): - """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP.""" - if not t["T_MEMMAP"]: - t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) - if not t["T_MEMDEPTHMAP"]: - t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) - ord_v = ord("v") - ord_0 = ord("0") - ord_space = ord(" ") - for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2) - if ord_v == t["fovmap"][pos]]: - t["T_MEMDEPTHMAP"][pos] = ord_0 - if ord_space == t["T_MEMMAP"][pos]: - t["T_MEMMAP"][pos] = world_db["MAP"][pos] - if age_map: - maptype = ctypes.c_char * len(t["T_MEMDEPTHMAP"]) - memdepthmap = maptype.from_buffer(t["T_MEMDEPTHMAP"]) - fovmap = maptype.from_buffer(t["fovmap"]) - libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap) - for mt in [mt for mt in t["T_MEMTHING"] - if "v" == chr(t["fovmap"][(mt[1] * world_db["MAP_LENGTH"]) - + mt[2]])]: - t["T_MEMTHING"].remove(mt) - for id in [id for id in world_db["Things"] - if not world_db["Things"][id]["carried"]]: - type = world_db["Things"][id]["T_TYPE"] - if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]: - y = world_db["Things"][id]["T_POSY"] - x = world_db["Things"][id]["T_POSX"] - if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]): - t["T_MEMTHING"].append((type, y, x)) - - -def set_world_inactive(): - """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file.""" - server_test() - if os.access(io_db["path_worldstate"], os.F_OK): - os.remove(io_db["path_worldstate"]) - world_db["WORLD_ACTIVE"] = 0 - - -def integer_test(val_string, min, max=None): - """Return val_string if integer >= min & (if max set) <= max, else None.""" - try: - val = int(val_string) - if val < min or (max != None and val > max): - raise ValueError - return val - except ValueError: - msg = "Ignoring: Please use integer >= " + str(min) - if max != None: - msg += " and <= " + str(max) - msg += "." - print(msg) - return None - - -def setter(category, key, min, max=None): - """Build setter for world_db([category + "s"][id])[key] to >=min/<=max.""" - if category is None: - def f(val_string): - val = integer_test(val_string, min, max) - if None != val: - world_db[key] = val - else: - if category == "Thing": - id_store = command_tid - decorator = test_Thing_id - elif category == "ThingType": - id_store = command_ttid - decorator = test_ThingType_id - elif category == "ThingAction": - id_store = command_taid - decorator = test_ThingAction_id - - @decorator - def f(val_string): - val = integer_test(val_string, min, max) - if None != val: - world_db[category + "s"][id_store.id][key] = val - return f - - -def build_fov_map(t): - """Build Thing's FOV map.""" - t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2)) - maptype = ctypes.c_char * len(world_db["MAP"]) - test = libpr.build_fov_map(t["T_POSY"], t["T_POSX"], - maptype.from_buffer(t["fovmap"]), - maptype.from_buffer(world_db["MAP"])) - if test: - raise RuntimeError("Malloc error in build_fov_Map().") - - -def decrement_lifepoints(t): - """Decrement t's lifepoints by 1, and if to zero, corpse it. - - If t is the player avatar, only blank its fovmap, so that the client may - still display memory data. On non-player things, erase fovmap and memory. - """ - t["T_LIFEPOINTS"] -= 1 - if 0 == t["T_LIFEPOINTS"]: - t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"] - if world_db["Things"][0] == t: - t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) - strong_write(io_db["file_out"], "LOG You die.\n") - else: - t["fovmap"] = False - t["T_MEMMAP"] = False - t["T_MEMDEPTHMAP"] = False - t["T_MEMTHING"] = [] - strong_write(io_db["file_out"], "LOG It dies.\n") - - -def mv_yx_in_dir_legal(dir, y, x): - """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use.""" - dir_c = dir.encode("ascii")[0] - test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x) - if -1 == test: - raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!") - return (test, libpr.result_y(), libpr.result_x()) - - -def actor_wait(t): - """Make t do nothing (but loudly, if player avatar).""" - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You wait.\n") - - -def actor_move(t): - """If passable, move/collide(=attack) thing into T_ARGUMENT's direction.""" - passable = False - move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]), - t["T_POSY"], t["T_POSX"]) - if 1 == move_result[0]: - pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2] - passable = "." == chr(world_db["MAP"][pos]) - hitted = [id for id in world_db["Things"] - if world_db["Things"][id] != t - if world_db["Things"][id]["T_LIFEPOINTS"] - if world_db["Things"][id]["T_POSY"] == move_result[1] - if world_db["Things"][id]["T_POSX"] == move_result[2]] - if len(hitted): - hit_id = hitted[0] - hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"] - hitter = "You" if t == world_db["Things"][0] else hitter_name - hitted_type = world_db["Things"][hit_id]["T_TYPE"] - hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"] - hitted = "you" if hit_id == 0 else hitted_name - verb = " wound " if hitter == "You" else " wounds " - strong_write(io_db["file_out"], "LOG " + hitter + verb + hitted + - ".\n") - decrement_lifepoints(world_db["Things"][hit_id]) - return - dir = [dir for dir in directions_db - if directions_db[dir] == chr(t["T_ARGUMENT"])][0] - if passable: - t["T_POSY"] = move_result[1] - t["T_POSX"] = move_result[2] - for id in t["T_CARRIES"]: - world_db["Things"][id]["T_POSY"] = move_result[1] - world_db["Things"][id]["T_POSX"] = move_result[2] - build_fov_map(t) - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You move " + dir + ".\n") - elif t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n") - - -def actor_pick_up(t): - """Make t pick up (topmost?) Thing from ground into inventory.""" - # Topmostness is actually not defined so far. Picks Thing with highest ID. - ids = [id for id in world_db["Things"] if world_db["Things"][id] != t - if not world_db["Things"][id]["carried"] - if world_db["Things"][id]["T_POSY"] == t["T_POSY"] - if world_db["Things"][id]["T_POSX"] == t["T_POSX"]] - if len(ids): - highest_id = 0 - for id in ids: - if id > highest_id: - highest_id = id - world_db["Things"][highest_id]["carried"] = True - t["T_CARRIES"].append(highest_id) - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You pick up an object.\n") - elif t == world_db["Things"][0]: - err = "You try to pick up an object, but there is none." - strong_write(io_db["file_out"], "LOG " + err + "\n") - - -def actor_drop(t): - """Make t rop Thing from inventory to ground indexed by T_ARGUMENT.""" - # TODO: Handle case where T_ARGUMENT matches nothing. - if len(t["T_CARRIES"]): - id = t["T_CARRIES"][t["T_ARGUMENT"]] - t["T_CARRIES"].remove(id) - world_db["Things"][id]["carried"] = False - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You drop an object.\n") - elif t == world_db["Things"][0]: - err = "You try to drop an object, but you own none." - strong_write(io_db["file_out"], "LOG " + err + "\n") - - -def actor_use(t): - """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory.""" - # TODO: Handle case where T_ARGUMENT matches nothing. - if len(t["T_CARRIES"]): - id = t["T_CARRIES"][t["T_ARGUMENT"]] - type = world_db["Things"][id]["T_TYPE"] - if world_db["ThingTypes"][type]["TT_CONSUMABLE"]: - t["T_CARRIES"].remove(id) - del world_db["Things"][id] - t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"] - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], - "LOG You consume this object.\n") - elif t == world_db["Things"][0]: - strong_write(io_db["file_out"], - "LOG You try to use this object, but fail.\n") - elif t == world_db["Things"][0]: - strong_write(io_db["file_out"], - "LOG You try to use an object, but you own none.\n") - - -def thingproliferation(t): - """To chance of 1/TT_PROLIFERATE, create t offspring in neighbor cell. - - Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be - passable and not be inhabited by a Thing of the same type, or, if Thing is - animate, any other animate Thing. If there are several map cell candidates, - one is selected randomly. - """ - def test_cell(t, y, x): - if "." == chr(world_db["MAP"][(y * world_db["MAP_LENGTH"]) + x]): - for id in [id for id in world_db["Things"] - if y == world_db["Things"][id]["T_POSY"] - if x == world_db["Things"][id]["T_POSX"] - if (t["T_TYPE"] == world_db["Things"][id]["T_TYPE"]) - or (t["T_LIFEPOINTS"] and - world_db["Things"][id]["T_LIFEPOINTS"])]: - return False - return True - return False - prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"] - if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)): - candidates = [] - for dir in [directions_db[key] for key in directions_db]: - mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"]) - if mv_result[0] and test_cell(t, mv_result[1], mv_result[2]): - candidates.append((mv_result[1], mv_result[2])) - if len(candidates): - i = rand.next() % len(candidates) - id = id_setter(-1, "Things") - newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1])) - world_db["Things"][id] = newT - - -def try_healing(t): - """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting. - - On success, decrease satiation score by 32. - """ - if t["T_SATIATION"] > 0 \ - and t["T_LIFEPOINTS"] < \ - world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \ - and 0 == (rand.next() % 31) \ - and t["T_COMMAND"] == [id for id in world_db["ThingActions"] - if world_db["ThingActions"][id]["TA_NAME"] == - "wait"][0]: - t["T_LIFEPOINTS"] += 1 - t["T_SATIATION"] -= 32 - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You heal.\n") - else: - name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"] - strong_write(io_db["file_out"], "LOG " + name + "heals.\n") - - -def hunger(t): - """Decrement t's satiation,dependent on it trigger lifepoint dec chance.""" - if t["T_SATIATION"] > -32768: - t["T_SATIATION"] -= 1 - testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"] - if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]: - raise RuntimeError("A thing that should not hunger is hungering.") - stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]) - if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)): - if t == world_db["Things"][0]: - strong_write(io_db["file_out"], "LOG You suffer from hunger.\n") - else: - name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"] - strong_write(io_db["file_out"], "LOG " + name + - " suffers from hunger.\n") - decrement_lifepoints(t) - - -def get_dir_to_target(t, filter): - """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target. - - The path-wise nearest target is chosen, via the shortest available path. - Target must not be t. On succcess, return positive value, else False. - Filters: - "a": Thing in FOV is below a certain distance, animate, but of ThingType - that is not t's, and starts out weaker than t is; build path as - avoiding things of t's ThingType - "f": neighbor cell (not inhabited by any animate Thing) further away from - animate Thing not further than x steps away and in FOV and of a - ThingType that is not t's, and starts out stronger or as strong as t - is currently; or (cornered), if no such flight cell, but Thing of - above criteria is too near,1 a cell closer to it, or, if less near, - just wait - "c": Thing in memorized map is consumable - "s": memory map cell with greatest-reachable degree of unexploredness - """ - - def zero_score_map_where_char_on_memdepthmap(c): - maptype = ctypes.c_char * len(t["T_MEMDEPTHMAP"]) - map = maptype.from_buffer(t["T_MEMDEPTHMAP"]) - test = libpr.zero_score_map_where_char_on_memdepthmap(c, map) - if test: - raise RuntimeError("No score map allocated for " - "zero_score_map_where_char_on_memdepthmap().") - - def set_map_score(pos, score): - test = libpr.set_map_score(pos, score) - if test: - raise RuntimeError("No score map allocated for set_map_score().") - - def get_map_score(pos): - result = libpr.get_map_score(pos) - if result < 0: - raise RuntimeError("No score map allocated for get_map_score().") - return result - - def seeing_thing(): - if t["fovmap"] and ("a" == filter or "f" == filter): - for id in world_db["Things"]: - Thing = world_db["Things"][id] - if Thing != t and Thing["T_LIFEPOINTS"] and \ - t["T_TYPE"] != Thing["T_TYPE"] and \ - 'v' == chr(t["fovmap"][(Thing["T_POSY"] - * world_db["MAP_LENGTH"]) - + Thing["T_POSX"]]): - ThingType = world_db["ThingTypes"][Thing["T_TYPE"]] - if ("f" == filter and ThingType["TT_LIFEPOINTS"] >= - t["T_LIFEPOINTS"]) \ - or ("a" == filter and ThingType["TT_LIFEPOINTS"] < - t["T_LIFEPOINTS"]): - return True - elif t["T_MEMMAP"] and "c" == filter: - for mt in t["T_MEMTHING"]: - if ' ' != chr(t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"]) - + mt[2]]) \ - and world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]: - return True - return False - - def init_score_map(): - test = libpr.init_score_map() - if test: - raise RuntimeError("Malloc error in init_score_map().") - ord_dot = ord(".") - ord_v = ord("v") - ord_blank = ord(" ") - for i in [i for i in range(world_db["MAP_LENGTH"] ** 2) - if ord_dot == t["T_MEMMAP"][i]]: - set_map_score(i, 65535 - 1) - if "a" == filter: - for id in world_db["Things"]: - Thing = world_db["Things"][id] - pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \ - + Thing["T_POSX"] - if t != Thing and Thing["T_LIFEPOINTS"] and \ - t["T_TYPE"] != Thing["T_TYPE"] and \ - ord_v == t["fovmap"][pos] and \ - t["T_LIFEPOINTS"] > \ - world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]: - set_map_score(pos, 0) - elif t["T_TYPE"] == Thing["T_TYPE"]: - set_map_score(pos, 65535) - elif "f" == filter: - for id in [id for id in world_db["Things"] - if world_db["Things"][id]["T_LIFEPOINTS"]]: - Thing = world_db["Things"][id] - pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \ - + Thing["T_POSX"] - if t["T_TYPE"] != Thing["T_TYPE"] and \ - ord_v == t["fovmap"][pos] and \ - t["T_LIFEPOINTS"] <= \ - world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]: - set_map_score(pos, 0) - elif "c" == filter: - for mt in [mt for mt in t["T_MEMTHING"] - if ord_blank != t["T_MEMMAP"][mt[1] - * world_db["MAP_LENGTH"] - + mt[2]] - if world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]]: - set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0) - elif "s" == filter: - zero_score_map_where_char_on_memdepthmap(mem_depth_c[0]) - - def rand_target_dir(neighbors, cmp, dirs): - candidates = [] - n_candidates = 0 - for i in range(len(dirs)): - if cmp == neighbors[i]: - candidates.append(dirs[i]) - n_candidates += 1 - return candidates[rand.next() % n_candidates] if n_candidates else 0 - - def get_neighbor_scores(dirs, eye_pos): - scores = [] - if libpr.ready_neighbor_scores(eye_pos): - raise RuntimeError("No score map allocated for " + - "ready_neighbor_scores.()") - for i in range(len(dirs)): - scores.append(libpr.get_neighbor_score(i)) - return scores - - def get_dir_from_neighbors(): - dir_to_target = False - dirs = "edcxsw" - eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"] - neighbors = get_neighbor_scores(dirs, eye_pos) - if "f" == filter: - inhabited = [world_db["Things"][id]["T_POSY"] - * world_db["MAP_LENGTH"] - + world_db["Things"][id]["T_POSX"] - for id in world_db["Things"] - if world_db["Things"][id]["T_LIFEPOINTS"]] - for i in range(len(dirs)): - mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"]) - pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \ - + libpr.result_x() - for pos in [pos for pos in inhabited if pos == pos_cmp]: - neighbors[i] = 65535 - break - minmax_start = 0 if "f" == filter else 65535 - 1 - minmax_neighbor = minmax_start - for i in range(len(dirs)): - if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and - minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \ - or ("f" != filter and minmax_neighbor > neighbors[i]): - minmax_neighbor = neighbors[i] - if minmax_neighbor != minmax_start: - dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs) - if "f" == filter: - if not dir_to_target: - if 1 == get_map_score(eye_pos): - dir_to_target = rand_target_dir(neighbors, 0, dirs) - elif 3 >= get_map_score(eye_pos): - t["T_COMMAND"] = [id for id in world_db["ThingActions"] - if - world_db["ThingActions"][id]["TA_NAME"] - == "wait"][0] - return 1 - elif dir_to_target and 3 < get_map_score(eye_pos): - dir_to_target = 0 - elif "a" == filter and 10 <= get_map_score(eye_pos): - dir_to_target = 0 - return dir_to_target - - dir_to_target = False - mem_depth_c = b' ' - run_i = 9 + 1 if "s" == filter else 1 - while run_i and not dir_to_target and ("s" == filter or seeing_thing()): - run_i -= 1 - init_score_map() - mem_depth_c = b'9' if b' ' == mem_depth_c \ - else bytes([mem_depth_c[0] - 1]) - if libpr.dijkstra_map(): - raise RuntimeError("No score map allocated for dijkstra_map().") - dir_to_target = get_dir_from_neighbors() - libpr.free_score_map() - if dir_to_target and str == type(dir_to_target): - t["T_COMMAND"] = [id for id in world_db["ThingActions"] - if world_db["ThingActions"][id]["TA_NAME"] - == "move"][0] - t["T_ARGUMENT"] = ord(dir_to_target) - return dir_to_target - - -def standing_on_consumable(t): - """Return True/False whether t is standing on a consumable.""" - for id in [id for id in world_db["Things"] if world_db["Things"][id] != t - if world_db["Things"][id]["T_POSY"] == t["T_POSY"] - if world_db["Things"][id]["T_POSX"] == t["T_POSX"] - if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]] - ["TT_CONSUMABLE"]]: - return True - return False - - -def get_inventory_slot_to_consume(t): - """Return slot Id of strongest consumable in t's inventory, else -1.""" - cmp_consumability = 0 - selection = -1 - i = 0 - for id in t["T_CARRIES"]: - type = world_db["Things"][id]["T_TYPE"] - if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability: - cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"] - selection = i - i += 1 - return selection - - -def ai(t): - """Determine next command/argment for actor t via AI algorithms. - - AI will look for, and move towards, enemies (animate Things not of their - own ThingType); if they see none, they will consume consumables in their - inventory; if there are none, they will pick up what they stand on if they - stand on consumables; if they stand on none, they will move towards the - next consumable they see or remember on the map; if they see or remember - none, they will explore parts of the map unseen since ever or for at least - one turn; if there is nothing to explore, they will simply wait. - """ - t["T_COMMAND"] = [id for id in world_db["ThingActions"] - if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0] - if not get_dir_to_target(t, "f"): - sel = get_inventory_slot_to_consume(t) - if -1 != sel: - t["T_COMMAND"] = [id for id in world_db["ThingActions"] - if world_db["ThingActions"][id]["TA_NAME"] - == "use"][0] - t["T_ARGUMENT"] = sel - elif standing_on_consumable(t): - t["T_COMMAND"] = [id for id in world_db["ThingActions"] - if world_db["ThingActions"][id]["TA_NAME"] - == "pick_up"][0] - elif (not get_dir_to_target(t, "c")) and \ - (not get_dir_to_target(t, "a")): - get_dir_to_target(t, "s") - - -def turn_over(): - """Run game world and its inhabitants until new player input expected.""" - id = 0 - whilebreaker = False - while world_db["Things"][0]["T_LIFEPOINTS"]: - for id in [id for id in world_db["Things"]]: # Only what's from start! - if not id in world_db["Things"] or \ - world_db["Things"][id]["carried"]: # May have been consumed or - continue # picked up during turn … - Thing = world_db["Things"][id] - if Thing["T_LIFEPOINTS"]: - if not Thing["T_COMMAND"]: - update_map_memory(Thing) - if 0 == id: - whilebreaker = True - break - ai(Thing) - try_healing(Thing) - Thing["T_PROGRESS"] += 1 - taid = [a for a in world_db["ThingActions"] - if a == Thing["T_COMMAND"]][0] - ThingAction = world_db["ThingActions"][taid] - if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]: - eval("actor_" + ThingAction["TA_NAME"])(Thing) - Thing["T_COMMAND"] = 0 - Thing["T_PROGRESS"] = 0 - hunger(Thing) - thingproliferation(Thing) - if whilebreaker: - break - world_db["TURN"] += 1 - - -def new_Thing(type, pos=(0, 0)): - """Return Thing of type T_TYPE, with fovmap if alive and world active.""" - thing = { - "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"], - "T_ARGUMENT": 0, - "T_PROGRESS": 0, - "T_SATIATION": 0, - "T_COMMAND": 0, - "T_TYPE": type, - "T_POSY": pos[0], - "T_POSX": pos[1], - "T_CARRIES": [], - "carried": False, - "T_MEMTHING": [], - "T_MEMMAP": False, - "T_MEMDEPTHMAP": False, - "fovmap": False - } - if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]: - build_fov_map(thing) - return thing - - -def id_setter(id, category, id_store=False, start_at_1=False): - """Set ID of object of category to manipulate ID unused? Create new one. - The ID is stored as id_store.id (if id_store is set). If the integer of the - input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1) - <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is - always returned when no new object is created, else the new object's ID. - """ - min = 0 if start_at_1 else -1 - if str == type(id): - id = integer_test(id, min) - if None != id: - if id in world_db[category]: - if id_store: - id_store.id = id - return None - else: - if (start_at_1 and 0 == id) \ - or ((not start_at_1) and (id < 0)): - id = 0 if start_at_1 else -1 - while 1: - id = id + 1 - if id not in world_db[category]: - break - return None - if id_store: - id_store.id = id - return id - - -def command_ping(): - """Send PONG line to server output file.""" - strong_write(io_db["file_out"], "PONG\n") - - -def command_quit(): - """Abort server process.""" - save_world() - atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True) - raise SystemExit("received QUIT command") - - -def command_thingshere(str_y, str_x): - """Write to out file list of Things known to player at coordinate y, x.""" - if world_db["WORLD_ACTIVE"]: - y = integer_test(str_y, 0, 255) - x = integer_test(str_x, 0, 255) - length = world_db["MAP_LENGTH"] - if None != y and None != x and y < length and x < length: - pos = (y * world_db["MAP_LENGTH"]) + x - strong_write(io_db["file_out"], "THINGS_HERE START\n") - if "v" == chr(world_db["Things"][0]["fovmap"][pos]): - for id in world_db["Things"]: - if y == world_db["Things"][id]["T_POSY"] \ - and x == world_db["Things"][id]["T_POSX"] \ - and not world_db["Things"][id]["carried"]: - type = world_db["Things"][id]["T_TYPE"] - name = world_db["ThingTypes"][type]["TT_NAME"] - strong_write(io_db["file_out"], name + "\n") - else: - for mt in world_db["Things"][0]["T_MEMTHING"]: - if y == mt[1] and x == mt[2]: - name = world_db["ThingTypes"][mt[0]]["TT_NAME"] - strong_write(io_db["file_out"], name + "\n") - strong_write(io_db["file_out"], "THINGS_HERE END\n") - else: - print("Ignoring: Invalid map coordinates.") - else: - print("Ignoring: Command only works on existing worlds.") - - -def play_commander(action, args=False): - """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over(). - - T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args. - """ - - def set_command(): - id = [x for x in world_db["ThingActions"] - if world_db["ThingActions"][x]["TA_NAME"] == action][0] - world_db["Things"][0]["T_COMMAND"] = id - turn_over() - - def set_command_and_argument_int(str_arg): - val = integer_test(str_arg, 0, 255) - if None != val: - world_db["Things"][0]["T_ARGUMENT"] = val - set_command() - - def set_command_and_argument_movestring(str_arg): - if str_arg in directions_db: - world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg]) - set_command() - else: - print("Ignoring: Argument must be valid direction string.") - - if action == "move": - return set_command_and_argument_movestring - elif args: - return set_command_and_argument_int - else: - return set_command - - -def command_seedrandomness(seed_string): - """Set rand seed to int(seed_string).""" - val = integer_test(seed_string, 0, 4294967295) - if None != val: - rand.seed = val - - -def command_seedmap(seed_string): - """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map.""" - setter(None, "SEED_MAP", 0, 4294967295)(seed_string) - remake_map() - - -def command_makeworld(seed_string): - """(Re-)build game world, i.e. map, things, to a new turn 1 from seed. - - Seed rand with seed, fill it into world_db["SEED_MAP"]. Do more only with a - "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType of - TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map() - and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things - according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType - of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each - other. Init player's memory map. Write "NEW_WORLD" line to out file. - """ - - def free_pos(): - i = 0 - while 1: - err = "Space to put thing on too hard to find. Map too small?" - while 1: - y = rand.next() % world_db["MAP_LENGTH"] - x = rand.next() % world_db["MAP_LENGTH"] - if "." == chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]): - break - i += 1 - if i == 65535: - raise SystemExit(err) - # Replica of C code, wrongly ignores animatedness of new Thing. - pos_clear = (0 == len([id for id in world_db["Things"] - if world_db["Things"][id]["T_LIFEPOINTS"] - if world_db["Things"][id]["T_POSY"] == y - if world_db["Things"][id]["T_POSX"] == x])) - if pos_clear: - break - return (y, x) - - val = integer_test(seed_string, 0, 4294967295) - if None == val: - return - rand.seed = val - world_db["SEED_MAP"] = val - player_will_be_generated = False - playertype = world_db["PLAYER_TYPE"] - for ThingType in world_db["ThingTypes"]: - if playertype == ThingType: - if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]: - player_will_be_generated = True - break - if not player_will_be_generated: - print("Ignoring beyond SEED_MAP: " + - "No player type with start number >0 defined.") - return - wait_action = False - for ThingAction in world_db["ThingActions"]: - if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]: - wait_action = True - if not wait_action: - print("Ignoring beyond SEED_MAP: " + - "No thing action with name 'wait' defined.") - return - world_db["Things"] = {} - remake_map() - world_db["WORLD_ACTIVE"] = 1 - world_db["TURN"] = 1 - for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]): - id = id_setter(-1, "Things") - world_db["Things"][id] = new_Thing(playertype, free_pos()) - update_map_memory(world_db["Things"][0]) - for type in world_db["ThingTypes"]: - for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]): - if type != playertype: - id = id_setter(-1, "Things") - world_db["Things"][id] = new_Thing(type, free_pos()) - strong_write(io_db["file_out"], "NEW_WORLD\n") - - -def command_maplength(maplength_string): - """Redefine map length. Invalidate map, therefore lose all things on it.""" - val = integer_test(maplength_string, 1, 256) - if None != val: - world_db["MAP_LENGTH"] = val - set_world_inactive() - world_db["Things"] = {} - libpr.set_maplength(val) - - -def command_worldactive(worldactive_string): - """Toggle world_db["WORLD_ACTIVE"] if possible. - - An active world can always be set inactive. An inactive world can only be - set active with a "wait" ThingAction, and a player Thing (of ID 0). On - activation, rebuild all Things' FOVs, and the player's map memory. - """ - # In original version, map existence was also tested (unnecessarily?). - val = integer_test(worldactive_string, 0, 1) - if val: - if 0 != world_db["WORLD_ACTIVE"]: - if 0 == val: - set_world_inactive() - else: - print("World already active.") - elif 0 == world_db["WORLD_ACTIVE"]: - wait_exists = False - for ThingAction in world_db["ThingActions"]: - if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]: - wait_exists = True - break - player_exists = False - for Thing in world_db["Things"]: - if 0 == Thing: - player_exists = True - break - if wait_exists and player_exists: - for id in world_db["Things"]: - if world_db["Things"][id]["T_LIFEPOINTS"]: - build_fov_map(world_db["Things"][id]) - if 0 == id: - update_map_memory(world_db["Things"][id], False) - world_db["WORLD_ACTIVE"] = 1 - - -def test_for_id_maker(object, category): - """Return decorator testing for object having "id" attribute.""" - def decorator(f): - def helper(*args): - if hasattr(object, "id"): - f(*args) - else: - print("Ignoring: No " + category + - " defined to manipulate yet.") - return helper - return decorator - - -def command_tid(id_string): - """Set ID of Thing to manipulate. ID unused? Create new one. - - Default new Thing's type to the first available ThingType, others: zero. - """ - id = id_setter(id_string, "Things", command_tid) - if None != id: - if world_db["ThingTypes"] == {}: - print("Ignoring: No ThingType to settle new Thing in.") - return - type = list(world_db["ThingTypes"].keys())[0] - world_db["Things"][id] = new_Thing(type) - - -test_Thing_id = test_for_id_maker(command_tid, "Thing") - - -@test_Thing_id -def command_tcommand(str_int): - """Set T_COMMAND of selected Thing.""" - val = integer_test(str_int, 0) - if None != val: - if 0 == val or val in world_db["ThingActions"]: - world_db["Things"][command_tid.id]["T_COMMAND"] = val - else: - print("Ignoring: ThingAction ID belongs to no known ThingAction.") - - -@test_Thing_id -def command_ttype(str_int): - """Set T_TYPE of selected Thing.""" - val = integer_test(str_int, 0) - if None != val: - if val in world_db["ThingTypes"]: - world_db["Things"][command_tid.id]["T_TYPE"] = val - else: - print("Ignoring: ThingType ID belongs to no known ThingType.") - - -@test_Thing_id -def command_tcarries(str_int): - """Append int(str_int) to T_CARRIES of selected Thing. - - The ID int(str_int) must not be of the selected Thing, and must belong to a - Thing with unset "carried" flag. Its "carried" flag will be set on owning. - """ - val = integer_test(str_int, 0) - if None != val: - if val == command_tid.id: - print("Ignoring: Thing cannot carry itself.") - elif val in world_db["Things"] \ - and not world_db["Things"][val]["carried"]: - world_db["Things"][command_tid.id]["T_CARRIES"].append(val) - world_db["Things"][val]["carried"] = True - else: - print("Ignoring: Thing not available for carrying.") - # Note that the whole carrying structure is different from the C version: - # Carried-ness is marked by a "carried" flag, not by Things containing - # Things internally. - - -@test_Thing_id -def command_tmemthing(str_t, str_y, str_x): - """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING. - - The type must fit to an existing ThingType, and the position into the map. - """ - type = integer_test(str_t, 0) - posy = integer_test(str_y, 0, 255) - posx = integer_test(str_x, 0, 255) - if None != type and None != posy and None != posx: - if type not in world_db["ThingTypes"] \ - or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]: - print("Ignoring: Illegal value for thing type or position.") - else: - memthing = (type, posy, posx) - world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing) - - -def setter_map(maptype): - """Set selected Thing's map of maptype's int(str_int)-th line to mapline. - - If Thing has no map of maptype yet, initialize it with ' ' bytes first. - """ - @test_Thing_id - def helper(str_int, mapline): - val = integer_test(str_int, 0, 255) - if None != val: - if val >= world_db["MAP_LENGTH"]: - print("Illegal value for map line number.") - elif len(mapline) != world_db["MAP_LENGTH"]: - print("Map line length is unequal map width.") - else: - length = world_db["MAP_LENGTH"] - map = None - if not world_db["Things"][command_tid.id][maptype]: - map = bytearray(b' ' * (length ** 2)) - else: - map = world_db["Things"][command_tid.id][maptype] - map[val * length:(val * length) + length] = mapline.encode() - world_db["Things"][command_tid.id][maptype] = map - return helper - - -def setter_tpos(axis): - """Generate setter for T_POSX or T_POSY of selected Thing. - - If world is active, rebuilds animate things' fovmap, player's memory map. - """ - @test_Thing_id - def helper(str_int): - val = integer_test(str_int, 0, 255) - if None != val: - if val < world_db["MAP_LENGTH"]: - world_db["Things"][command_tid.id]["T_POS" + axis] = val - if world_db["WORLD_ACTIVE"] \ - and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]: - build_fov_map(world_db["Things"][command_tid.id]) - if 0 == command_tid.id: - update_map_memory(world_db["Things"][command_tid.id]) - else: - print("Ignoring: Position is outside of map.") - return helper - - -def command_ttid(id_string): - """Set ID of ThingType to manipulate. ID unused? Create new one. - - Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0. - """ - id = id_setter(id_string, "ThingTypes", command_ttid) - if None != id: - world_db["ThingTypes"][id] = { - "TT_NAME": "(none)", - "TT_CONSUMABLE": 0, - "TT_LIFEPOINTS": 0, - "TT_PROLIFERATE": 0, - "TT_START_NUMBER": 0, - "TT_SYMBOL": "?", - "TT_CORPSE_ID": id - } - - -test_ThingType_id = test_for_id_maker(command_ttid, "ThingType") - - -@test_ThingType_id -def command_ttname(name): - """Set TT_NAME of selected ThingType.""" - world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name - - -@test_ThingType_id -def command_ttsymbol(char): - """Set TT_SYMBOL of selected ThingType. """ - if 1 == len(char): - world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char - else: - print("Ignoring: Argument must be single character.") - - -@test_ThingType_id -def command_ttcorpseid(str_int): - """Set TT_CORPSE_ID of selected ThingType.""" - val = integer_test(str_int, 0) - if None != val: - if val in world_db["ThingTypes"]: - world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val - else: - print("Ignoring: Corpse ID belongs to no known ThignType.") - - -def command_taid(id_string): - """Set ID of ThingAction to manipulate. ID unused? Create new one. - - Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait". - """ - id = id_setter(id_string, "ThingActions", command_taid, True) - if None != id: - world_db["ThingActions"][id] = { - "TA_EFFORT": 1, - "TA_NAME": "wait" - } - - -test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction") - - -@test_ThingAction_id -def command_taname(name): - """Set TA_NAME of selected ThingAction. - - The name must match a valid thing action function. If after the name - setting no ThingAction with name "wait" remains, call set_world_inactive(). - """ - if name == "wait" or name == "move" or name == "use" or name == "drop" \ - or name == "pick_up": - world_db["ThingActions"][command_taid.id]["TA_NAME"] = name - if 1 == world_db["WORLD_ACTIVE"]: - wait_defined = False - for id in world_db["ThingActions"]: - if "wait" == world_db["ThingActions"][id]["TA_NAME"]: - wait_defined = True - break - if not wait_defined: - set_world_inactive() - else: - print("Ignoring: Invalid action name.") - # In contrast to the original,naming won't map a function to a ThingAction. - - -def command_ai(): - """Call ai() on player Thing, then turn_over().""" - ai(world_db["Things"][0]) - turn_over() - - -"""Commands database. - -Map command start tokens to ([0]) number of expected command arguments, ([1]) -the command's meta-ness (i.e. is it to be written to the record file, is it to -be ignored in replay mode if read from server input file), and ([2]) a function -to be called on it. -""" -commands_db = { - "QUIT": (0, True, command_quit), - "PING": (0, True, command_ping), - "THINGS_HERE": (2, True, command_thingshere), - "MAKE_WORLD": (1, False, command_makeworld), - "SEED_MAP": (1, False, command_seedmap), - "SEED_RANDOMNESS": (1, False, command_seedrandomness), - "TURN": (1, False, setter(None, "TURN", 0, 65535)), - "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)), - "MAP_LENGTH": (1, False, command_maplength), - "WORLD_ACTIVE": (1, False, command_worldactive), - "TA_ID": (1, False, command_taid), - "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)), - "TA_NAME": (1, False, command_taname), - "TT_ID": (1, False, command_ttid), - "TT_NAME": (1, False, command_ttname), - "TT_SYMBOL": (1, False, command_ttsymbol), - "TT_CORPSE_ID": (1, False, command_ttcorpseid), - "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE", - 0, 65535)), - "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER", - 0, 255)), - "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE", - 0, 255)), - "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)), - "T_ID": (1, False, command_tid), - "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)), - "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)), - "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)), - "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)), - "T_COMMAND": (1, False, command_tcommand), - "T_TYPE": (1, False, command_ttype), - "T_CARRIES": (1, False, command_tcarries), - "T_MEMMAP": (2, False, setter_map("T_MEMMAP")), - "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")), - "T_MEMTHING": (3, False, command_tmemthing), - "T_POSY": (1, False, setter_tpos("Y")), - "T_POSX": (1, False, setter_tpos("X")), - "wait": (0, False, play_commander("wait")), - "move": (1, False, play_commander("move")), - "pick_up": (0, False, play_commander("pick_up")), - "drop": (1, False, play_commander("drop", True)), - "use": (1, False, play_commander("use", True)), - "ai": (0, False, command_ai) -} - - -"""World state database. With sane default values. (Randomness is in rand.)""" -world_db = { - "TURN": 0, - "MAP_LENGTH": 64, - "SEED_MAP": 0, - "PLAYER_TYPE": 0, - "WORLD_ACTIVE": 0, - "ThingActions": {}, - "ThingTypes": {}, - "Things": {} -} - -"""Mapping of direction names to internal direction chars.""" -directions_db = {"east": "d", "south-east": "c", "south-west": "x", - "west": "s", "north-west": "w", "north-east": "e"} - -"""File IO database.""" -io_db = { - "path_save": "save", - "path_record": "record_save", - "path_worldconf": "confserver/world", - "path_server": "server/", - "path_in": "server/in", - "path_out": "server/out", - "path_worldstate": "server/worldstate", - "tmp_suffix": "_tmp", - "kicked_by_rival": False, - "worldstate_updateable": False -} - - -try: - libpr = prep_library() - rand = RandomnessIO() - opts = parse_command_line_arguments() - if opts.savefile: - io_db["path_save"] = opts.savefile - io_db["path_record"] = "record_" + opts.savefile - setup_server_io() - if opts.verbose: - io_db["verbose"] = True - if None != opts.replay: - replay_game() - else: - play_game() -except SystemExit as exit: - print("ABORTING: " + exit.args[0]) -except: - print("SOMETHING WENT WRONG IN UNEXPECTED WAYS") - raise -finally: - cleanup_server_io() diff --git a/redo b/redo index 8f459fb..4081117 100755 --- a/redo +++ b/redo @@ -18,9 +18,5 @@ # for a # workaround. -echo "non-redo stuff (preparing for future Python port sans redo needs):" -echo "Building library for server's Python variant with mere shell one-liner." -./compile-server.sh - export PATH=$PATH:$PWD/build/redo_scripts redo "$@" diff --git a/roguelike b/roguelike index a0be8cc..b002721 100755 --- a/roguelike +++ b/roguelike @@ -2,7 +2,10 @@ # Wrapper to the script so that its suppressed server messages get read on exit. ./start_server_client_union.sh "$@" + +# For some reason, mere sync won't ensure a log is written out, so wait a while. sync +sleep 0.5 if [ -e ./log ] then cat log diff --git a/roguelike-server b/roguelike-server new file mode 100755 index 0000000..ef3fdbc --- /dev/null +++ b/roguelike-server @@ -0,0 +1,1629 @@ +#!/usr/bin/python3 + +# This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 +# or any later version. For details on its copyright, license, and warranties, +# see the file NOTICE in the root directory of the PlomRogue source package. + + +import argparse +import errno +import os +import shlex +import shutil +import time +import ctypes + + +class RandomnessIO: + """"Interface to libplomrogue's pseudo-randomness generator.""" + + def set_seed(self, seed): + libpr.seed_rrand(1, seed) + + def get_seed(self): + return libpr.seed_rrand(0, 0) + + def next(self): + return libpr.rrand() + + seed = property(get_seed, set_seed) + + +def prep_library(): + """Prepare ctypes library at ./libplomrogue.so""" + libpath = ("./libplomrogue.so") + if not os.access(libpath, os.F_OK): + raise SystemExit("No library " + libpath + + ", run ./compile-server.sh first?") + libpr = ctypes.cdll.LoadLibrary(libpath) + libpr.seed_rrand.restype = ctypes.c_uint32 + return libpr + + +def strong_write(file, string): + """Apply write(string), then flush().""" + file.write(string) + file.flush() + + +def setup_server_io(): + """Fill IO files DB with proper file( path)s. Write process IO test string. + + Ensure IO files directory at server/. Remove any old input file if found. + Set up new input file for reading, and new output file for writing. Start + output file with process hash line of format PID + " " + floated UNIX time + (io_db["teststring"]). Raise SystemExit if file is found at path of either + record or save file plus io_db["tmp_suffix"]. + """ + def detect_atomic_leftover(path, tmp_suffix): + path_tmp = path + tmp_suffix + msg = "Found file '" + path_tmp + "' that may be a leftover from an " \ + "aborted previous attempt to write '" + path + "'. Aborting " \ + "until matter is resolved by removing it from its current path." + if os.access(path_tmp, os.F_OK): + raise SystemExit(msg) + io_db["teststring"] = str(os.getpid()) + " " + str(time.time()) + io_db["save_wait"] = 0 + io_db["verbose"] = False + io_db["record_chunk"] = "" + os.makedirs(io_db["path_server"], exist_ok=True) + io_db["file_out"] = open(io_db["path_out"], "w") + strong_write(io_db["file_out"], io_db["teststring"] + "\n") + if os.access(io_db["path_in"], os.F_OK): + os.remove(io_db["path_in"]) + io_db["file_in"] = open(io_db["path_in"], "w") + io_db["file_in"].close() + io_db["file_in"] = open(io_db["path_in"], "r") + detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"]) + detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"]) + + +def cleanup_server_io(): + """Close and (if io_db["kicked_by_rival"] false) remove files in io_db.""" + def helper(file_key, path_key): + if file_key in io_db: + io_db[file_key].close() + if not io_db["kicked_by_rival"] \ + and os.access(io_db[path_key], os.F_OK): + os.remove(io_db[path_key]) + helper("file_in", "path_in") + helper("file_out", "path_out") + helper("file_worldstate", "path_worldstate") + if "file_record" in io_db: + io_db["file_record"].close() + + +def obey(command, prefix, replay=False, do_record=False): + """Call function from commands_db mapped to command's first token. + + Tokenize command string with shlex.split(comments=True). If replay is set, + a non-meta command from the commands_db merely triggers obey() on the next + command from the records file. If not, non-meta commands set + io_db["worldstate_updateable"] to world_db["WORLD_ACTIVE"], and, if + do_record is set, are recorded to io_db["record_chunk"], and save_world() + is called (and io_db["record_chunk"] written) if 15 seconds have passed + since the last time it was called. The prefix string is inserted into the + server's input message between its beginning 'input ' and ':'. All activity + is preceded by a server_test() call. + """ + server_test() + if io_db["verbose"]: + print("input " + prefix + ": " + command) + try: + tokens = shlex.split(command, comments=True) + except ValueError as err: + print("Can't tokenize command string: " + str(err) + ".") + return + if len(tokens) > 0 and tokens[0] in commands_db \ + and len(tokens) == commands_db[tokens[0]][0] + 1: + if commands_db[tokens[0]][1]: + commands_db[tokens[0]][2](*tokens[1:]) + elif replay: + print("Due to replay mode, reading command as 'go on in record'.") + line = io_db["file_record"].readline() + if len(line) > 0: + obey(line.rstrip(), io_db["file_record"].prefix + + str(io_db["file_record"].line_n)) + io_db["file_record"].line_n = io_db["file_record"].line_n + 1 + else: + print("Reached end of record file.") + else: + commands_db[tokens[0]][2](*tokens[1:]) + if do_record: + io_db["record_chunk"] += command + "\n" + if time.time() > io_db["save_wait"] + 15: + atomic_write(io_db["path_record"], io_db["record_chunk"], + do_append=True) + save_world() + io_db["record_chunk"] = "" + io_db["save_wait"] = time.time() + io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"] + elif 0 != len(tokens): + print("Invalid command/argument, or bad number of tokens.") + + +def atomic_write(path, text, do_append=False, delete=True): + """Atomic write of text to file at path, appended if do_append is set.""" + path_tmp = path + io_db["tmp_suffix"] + mode = "w" + if do_append: + mode = "a" + if os.access(path, os.F_OK): + shutil.copyfile(path, path_tmp) + file = open(path_tmp, mode) + strong_write(file, text) + file.close() + if delete and os.access(path, os.F_OK): + os.remove(path) + os.rename(path_tmp, path) + + +def save_world(): + """Save all commands needed to reconstruct current world state.""" + + def quote(string): + string = string.replace("\u005C", '\u005C\u005C') + return '"' + string.replace('"', '\u005C"') + '"' + + def mapsetter(key): + def helper(id): + string = "" + if world_db["Things"][id][key]: + map = world_db["Things"][id][key] + length = world_db["MAP_LENGTH"] + for i in range(length): + line = map[i * length:(i * length) + length].decode() + string = string + key + " " + str(i) + " " + quote(line) \ + + "\n" + return string + return helper + + def memthing(id): + string = "" + for memthing in world_db["Things"][id]["T_MEMTHING"]: + string = string + "T_MEMTHING " + str(memthing[0]) + " " + \ + str(memthing[1]) + " " + str(memthing[2]) + "\n" + return string + + def helper(category, id_string, special_keys={}): + string = "" + for id in sorted(world_db[category].keys()): + string = string + id_string + " " + str(id) + "\n" + for key in sorted(world_db[category][id].keys()): + if not key in special_keys: + x = world_db[category][id][key] + argument = quote(x) if str == type(x) else str(x) + string = string + key + " " + argument + "\n" + elif special_keys[key]: + string = string + special_keys[key](id) + return string + + string = "" + for key in sorted(world_db.keys()): + if dict != type(world_db[key]) and key != "MAP" and \ + key != "WORLD_ACTIVE" and key != "SEED_MAP": + string = string + key + " " + str(world_db[key]) + "\n" + string = string + "SEED_MAP " + str(world_db["SEED_MAP"]) + "\n" + string = string + helper("ThingActions", "TA_ID") + string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False}) + for id in sorted(world_db["ThingTypes"].keys()): + string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \ + str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n" + string = string + helper("Things", "T_ID", + {"T_CARRIES": False, "carried": False, + "T_MEMMAP": mapsetter("T_MEMMAP"), + "T_MEMTHING": memthing, "fovmap": False, + "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")}) + for id in sorted(world_db["Things"].keys()): + if [] != world_db["Things"][id]["T_CARRIES"]: + string = string + "T_ID " + str(id) + "\n" + for carried in sorted(world_db["Things"][id]["T_CARRIES"].keys()): + string = string + "T_CARRIES " + str(carried) + "\n" + string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \ + "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"]) + atomic_write(io_db["path_save"], string) + + +def obey_lines_in_file(path, name, do_record=False): + """Call obey() on each line of path's file, use name in input prefix.""" + file = open(path, "r") + line_n = 1 + for line in file.readlines(): + obey(line.rstrip(), name + "file line " + str(line_n), + do_record=do_record) + line_n = line_n + 1 + file.close() + + +def parse_command_line_arguments(): + """Return settings values read from command line arguments.""" + parser = argparse.ArgumentParser() + parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1, + action='store') + parser.add_argument('-l', nargs="?", const="save", dest='savefile', + action="store") + parser.add_argument('-v', dest='verbose', action='store_true') + opts, unknown = parser.parse_known_args() + return opts + + +def server_test(): + """Ensure valid server out file belonging to current process. + + This is done by comparing io_db["teststring"] to what's found at the start + of the current file at io_db["path_out"]. On failure, set + io_db["kicked_by_rival"] and raise SystemExit. + """ + if not os.access(io_db["path_out"], os.F_OK): + raise SystemExit("Server output file has disappeared.") + file = open(io_db["path_out"], "r") + test = file.readline().rstrip("\n") + file.close() + if test != io_db["teststring"]: + io_db["kicked_by_rival"] = True + msg = "Server test string in server output file does not match. This" \ + " indicates that the current server process has been " \ + "superseded by another one." + raise SystemExit(msg) + + +def read_command(): + """Return next newline-delimited command from server in file. + + Keep building return string until a newline is encountered. Pause between + unsuccessful reads, and after too much waiting, run server_test(). + """ + wait_on_fail = 0.03333 + max_wait = 5 + now = time.time() + command = "" + while True: + add = io_db["file_in"].readline() + if len(add) > 0: + command = command + add + if len(command) > 0 and "\n" == command[-1]: + command = command[:-1] + break + else: + time.sleep(wait_on_fail) + if now + max_wait < time.time(): + server_test() + now = time.time() + return command + + +def try_worldstate_update(): + """Write worldstate file if io_db["worldstate_updateable"] is set.""" + if io_db["worldstate_updateable"]: + + def draw_visible_Things(map, run): + for id in world_db["Things"]: + type = world_db["Things"][id]["T_TYPE"] + consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"] + alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"] + if (0 == run and not consumable and not alive) \ + or (1 == run and consumable and not alive) \ + or (2 == run and alive): + y = world_db["Things"][id]["T_POSY"] + x = world_db["Things"][id]["T_POSX"] + fovflag = world_db["Things"][0]["fovmap"][(y * length) + x] + if 'v' == chr(fovflag): + c = world_db["ThingTypes"][type]["TT_SYMBOL"] + map[(y * length) + x] = ord(c) + + def write_map(string, map): + for i in range(length): + line = map[i * length:(i * length) + length].decode() + string = string + line + "\n" + return string + + inventory = "" + if [] == world_db["Things"][0]["T_CARRIES"]: + inventory = "(none)\n" + else: + for id in world_db["Things"][0]["T_CARRIES"]: + type_id = world_db["Things"][id]["T_TYPE"] + name = world_db["ThingTypes"][type_id]["TT_NAME"] + inventory = inventory + name + "\n" + string = str(world_db["TURN"]) + "\n" + \ + str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \ + str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \ + inventory + "%\n" + \ + str(world_db["Things"][0]["T_POSY"]) + "\n" + \ + str(world_db["Things"][0]["T_POSX"]) + "\n" + \ + str(world_db["MAP_LENGTH"]) + "\n" + length = world_db["MAP_LENGTH"] + fov = bytearray(b' ' * (length ** 2)) + for pos in range(length ** 2): + if 'v' == chr(world_db["Things"][0]["fovmap"][pos]): + fov[pos] = world_db["MAP"][pos] + for i in range(3): + draw_visible_Things(fov, i) + string = write_map(string, fov) + mem = world_db["Things"][0]["T_MEMMAP"][:] + for i in range(2): + for mt in world_db["Things"][0]["T_MEMTHING"]: + consumable = world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"] + if (i == 0 and not consumable) or (i == 1 and consumable): + c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"] + mem[(mt[1] * length) + mt[2]] = ord(c) + string = write_map(string, mem) + atomic_write(io_db["path_worldstate"], string, delete=False) + strong_write(io_db["file_out"], "WORLD_UPDATED\n") + io_db["worldstate_updateable"] = False + + +def replay_game(): + """Replay game from record file. + + Use opts.replay as breakpoint turn to which to replay automatically before + switching to manual input by non-meta commands in server input file + triggering further reads of record file. Ensure opts.replay is at least 1. + Run try_worldstate_update() before each interactive obey()/read_command(). + """ + if opts.replay < 1: + opts.replay = 1 + print("Replay mode. Auto-replaying up to turn " + str(opts.replay) + + " (if so late a turn is to be found).") + if not os.access(io_db["path_record"], os.F_OK): + raise SystemExit("No record file found to replay.") + io_db["file_record"] = open(io_db["path_record"], "r") + io_db["file_record"].prefix = "record file line " + io_db["file_record"].line_n = 1 + while world_db["TURN"] < opts.replay: + line = io_db["file_record"].readline() + if "" == line: + break + obey(line.rstrip(), io_db["file_record"].prefix + + str(io_db["file_record"].line_n)) + io_db["file_record"].line_n = io_db["file_record"].line_n + 1 + while True: + try_worldstate_update() + obey(read_command(), "in file", replay=True) + + +def play_game(): + """Play game by server input file commands. Before, load save file found. + + If no save file is found, a new world is generated from the commands in the + world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this + command and all that follow via the server input file. Run + try_worldstate_update() before each interactive obey()/read_command(). + """ + if os.access(io_db["path_save"], os.F_OK): + obey_lines_in_file(io_db["path_save"], "save") + else: + if not os.access(io_db["path_worldconf"], os.F_OK): + msg = "No world config file from which to start a new world." + raise SystemExit(msg) + obey_lines_in_file(io_db["path_worldconf"], "world config ", + do_record=True) + obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True) + while True: + try_worldstate_update() + obey(read_command(), "in file", do_record=True) + + +def remake_map(): + """(Re-)make island map. + + Let "~" represent water, "." land, "X" trees: Build island shape randomly, + start with one land cell in the middle, then go into 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 at the map's + border. Then put some trees on the map (TODO: more precise algorithm desc). + """ + def is_neighbor(coordinates, type): + y = coordinates[0] + x = coordinates[1] + length = world_db["MAP_LENGTH"] + ind = y % 2 + diag_west = x + (ind > 0) + diag_east = x + (ind < (length - 1)) + pos = (y * length) + x + if (y > 0 and diag_east + and type == chr(world_db["MAP"][pos - length + ind])) \ + or (x < (length - 1) + and type == chr(world_db["MAP"][pos + 1])) \ + or (y < (length - 1) and diag_east + and type == chr(world_db["MAP"][pos + length + ind])) \ + or (y > 0 and diag_west + and type == chr(world_db["MAP"][pos - length - (not ind)])) \ + or (x > 0 + and type == chr(world_db["MAP"][pos - 1])) \ + or (y < (length - 1) and diag_west + and type == chr(world_db["MAP"][pos + length - (not ind)])): + return True + return False + store_seed = rand.seed + rand.seed = world_db["SEED_MAP"] + world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2)) + length = world_db["MAP_LENGTH"] + add_half_width = (not (length % 2)) * int(length / 2) + world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".") + while (1): + y = rand.next() % length + x = rand.next() % length + pos = (y * length) + x + if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."): + if y == 0 or y == (length - 1) or x == 0 or x == (length - 1): + break + world_db["MAP"][pos] = ord(".") + n_trees = int((length ** 2) / 16) + i_trees = 0 + while (i_trees <= n_trees): + single_allowed = rand.next() % 32 + y = rand.next() % length + x = rand.next() % length + pos = (y * length) + x + if "." == chr(world_db["MAP"][pos]) \ + and ((not single_allowed) or is_neighbor((y, x), "X")): + world_db["MAP"][pos] = ord("X") + i_trees += 1 + rand.seed = store_seed + # This all-too-precise replica of the original C code misses iter_limit(). + + +def update_map_memory(t, age_map=True): + """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP.""" + if not t["T_MEMMAP"]: + t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) + if not t["T_MEMDEPTHMAP"]: + t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) + ord_v = ord("v") + ord_0 = ord("0") + ord_space = ord(" ") + for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2) + if ord_v == t["fovmap"][pos]]: + t["T_MEMDEPTHMAP"][pos] = ord_0 + if ord_space == t["T_MEMMAP"][pos]: + t["T_MEMMAP"][pos] = world_db["MAP"][pos] + if age_map: + maptype = ctypes.c_char * len(t["T_MEMDEPTHMAP"]) + memdepthmap = maptype.from_buffer(t["T_MEMDEPTHMAP"]) + fovmap = maptype.from_buffer(t["fovmap"]) + libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap) + for mt in [mt for mt in t["T_MEMTHING"] + if "v" == chr(t["fovmap"][(mt[1] * world_db["MAP_LENGTH"]) + + mt[2]])]: + t["T_MEMTHING"].remove(mt) + for id in [id for id in world_db["Things"] + if not world_db["Things"][id]["carried"]]: + type = world_db["Things"][id]["T_TYPE"] + if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]: + y = world_db["Things"][id]["T_POSY"] + x = world_db["Things"][id]["T_POSX"] + if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]): + t["T_MEMTHING"].append((type, y, x)) + + +def set_world_inactive(): + """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file.""" + server_test() + if os.access(io_db["path_worldstate"], os.F_OK): + os.remove(io_db["path_worldstate"]) + world_db["WORLD_ACTIVE"] = 0 + + +def integer_test(val_string, min, max=None): + """Return val_string if integer >= min & (if max set) <= max, else None.""" + try: + val = int(val_string) + if val < min or (max != None and val > max): + raise ValueError + return val + except ValueError: + msg = "Ignoring: Please use integer >= " + str(min) + if max != None: + msg += " and <= " + str(max) + msg += "." + print(msg) + return None + + +def setter(category, key, min, max=None): + """Build setter for world_db([category + "s"][id])[key] to >=min/<=max.""" + if category is None: + def f(val_string): + val = integer_test(val_string, min, max) + if None != val: + world_db[key] = val + else: + if category == "Thing": + id_store = command_tid + decorator = test_Thing_id + elif category == "ThingType": + id_store = command_ttid + decorator = test_ThingType_id + elif category == "ThingAction": + id_store = command_taid + decorator = test_ThingAction_id + + @decorator + def f(val_string): + val = integer_test(val_string, min, max) + if None != val: + world_db[category + "s"][id_store.id][key] = val + return f + + +def build_fov_map(t): + """Build Thing's FOV map.""" + t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2)) + maptype = ctypes.c_char * len(world_db["MAP"]) + test = libpr.build_fov_map(t["T_POSY"], t["T_POSX"], + maptype.from_buffer(t["fovmap"]), + maptype.from_buffer(world_db["MAP"])) + if test: + raise RuntimeError("Malloc error in build_fov_Map().") + + +def decrement_lifepoints(t): + """Decrement t's lifepoints by 1, and if to zero, corpse it. + + If t is the player avatar, only blank its fovmap, so that the client may + still display memory data. On non-player things, erase fovmap and memory. + """ + t["T_LIFEPOINTS"] -= 1 + if 0 == t["T_LIFEPOINTS"]: + t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"] + if world_db["Things"][0] == t: + t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) + strong_write(io_db["file_out"], "LOG You die.\n") + else: + t["fovmap"] = False + t["T_MEMMAP"] = False + t["T_MEMDEPTHMAP"] = False + t["T_MEMTHING"] = [] + strong_write(io_db["file_out"], "LOG It dies.\n") + + +def mv_yx_in_dir_legal(dir, y, x): + """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use.""" + dir_c = dir.encode("ascii")[0] + test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x) + if -1 == test: + raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!") + return (test, libpr.result_y(), libpr.result_x()) + + +def actor_wait(t): + """Make t do nothing (but loudly, if player avatar).""" + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You wait.\n") + + +def actor_move(t): + """If passable, move/collide(=attack) thing into T_ARGUMENT's direction.""" + passable = False + move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]), + t["T_POSY"], t["T_POSX"]) + if 1 == move_result[0]: + pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2] + passable = "." == chr(world_db["MAP"][pos]) + hitted = [id for id in world_db["Things"] + if world_db["Things"][id] != t + if world_db["Things"][id]["T_LIFEPOINTS"] + if world_db["Things"][id]["T_POSY"] == move_result[1] + if world_db["Things"][id]["T_POSX"] == move_result[2]] + if len(hitted): + hit_id = hitted[0] + hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"] + hitter = "You" if t == world_db["Things"][0] else hitter_name + hitted_type = world_db["Things"][hit_id]["T_TYPE"] + hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"] + hitted = "you" if hit_id == 0 else hitted_name + verb = " wound " if hitter == "You" else " wounds " + strong_write(io_db["file_out"], "LOG " + hitter + verb + hitted + + ".\n") + decrement_lifepoints(world_db["Things"][hit_id]) + return + dir = [dir for dir in directions_db + if directions_db[dir] == chr(t["T_ARGUMENT"])][0] + if passable: + t["T_POSY"] = move_result[1] + t["T_POSX"] = move_result[2] + for id in t["T_CARRIES"]: + world_db["Things"][id]["T_POSY"] = move_result[1] + world_db["Things"][id]["T_POSX"] = move_result[2] + build_fov_map(t) + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You move " + dir + ".\n") + elif t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n") + + +def actor_pick_up(t): + """Make t pick up (topmost?) Thing from ground into inventory.""" + # Topmostness is actually not defined so far. Picks Thing with highest ID. + ids = [id for id in world_db["Things"] if world_db["Things"][id] != t + if not world_db["Things"][id]["carried"] + if world_db["Things"][id]["T_POSY"] == t["T_POSY"] + if world_db["Things"][id]["T_POSX"] == t["T_POSX"]] + if len(ids): + highest_id = 0 + for id in ids: + if id > highest_id: + highest_id = id + world_db["Things"][highest_id]["carried"] = True + t["T_CARRIES"].append(highest_id) + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You pick up an object.\n") + elif t == world_db["Things"][0]: + err = "You try to pick up an object, but there is none." + strong_write(io_db["file_out"], "LOG " + err + "\n") + + +def actor_drop(t): + """Make t rop Thing from inventory to ground indexed by T_ARGUMENT.""" + # TODO: Handle case where T_ARGUMENT matches nothing. + if len(t["T_CARRIES"]): + id = t["T_CARRIES"][t["T_ARGUMENT"]] + t["T_CARRIES"].remove(id) + world_db["Things"][id]["carried"] = False + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You drop an object.\n") + elif t == world_db["Things"][0]: + err = "You try to drop an object, but you own none." + strong_write(io_db["file_out"], "LOG " + err + "\n") + + +def actor_use(t): + """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory.""" + # TODO: Handle case where T_ARGUMENT matches nothing. + if len(t["T_CARRIES"]): + id = t["T_CARRIES"][t["T_ARGUMENT"]] + type = world_db["Things"][id]["T_TYPE"] + if world_db["ThingTypes"][type]["TT_CONSUMABLE"]: + t["T_CARRIES"].remove(id) + del world_db["Things"][id] + t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"] + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], + "LOG You consume this object.\n") + elif t == world_db["Things"][0]: + strong_write(io_db["file_out"], + "LOG You try to use this object, but fail.\n") + elif t == world_db["Things"][0]: + strong_write(io_db["file_out"], + "LOG You try to use an object, but you own none.\n") + + +def thingproliferation(t): + """To chance of 1/TT_PROLIFERATE, create t offspring in neighbor cell. + + Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be + passable and not be inhabited by a Thing of the same type, or, if Thing is + animate, any other animate Thing. If there are several map cell candidates, + one is selected randomly. + """ + def test_cell(t, y, x): + if "." == chr(world_db["MAP"][(y * world_db["MAP_LENGTH"]) + x]): + for id in [id for id in world_db["Things"] + if y == world_db["Things"][id]["T_POSY"] + if x == world_db["Things"][id]["T_POSX"] + if (t["T_TYPE"] == world_db["Things"][id]["T_TYPE"]) + or (t["T_LIFEPOINTS"] and + world_db["Things"][id]["T_LIFEPOINTS"])]: + return False + return True + return False + prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"] + if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)): + candidates = [] + for dir in [directions_db[key] for key in directions_db]: + mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"]) + if mv_result[0] and test_cell(t, mv_result[1], mv_result[2]): + candidates.append((mv_result[1], mv_result[2])) + if len(candidates): + i = rand.next() % len(candidates) + id = id_setter(-1, "Things") + newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1])) + world_db["Things"][id] = newT + + +def try_healing(t): + """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting. + + On success, decrease satiation score by 32. + """ + if t["T_SATIATION"] > 0 \ + and t["T_LIFEPOINTS"] < \ + world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \ + and 0 == (rand.next() % 31) \ + and t["T_COMMAND"] == [id for id in world_db["ThingActions"] + if world_db["ThingActions"][id]["TA_NAME"] == + "wait"][0]: + t["T_LIFEPOINTS"] += 1 + t["T_SATIATION"] -= 32 + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You heal.\n") + else: + name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"] + strong_write(io_db["file_out"], "LOG " + name + "heals.\n") + + +def hunger(t): + """Decrement t's satiation,dependent on it trigger lifepoint dec chance.""" + if t["T_SATIATION"] > -32768: + t["T_SATIATION"] -= 1 + testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"] + if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]: + raise RuntimeError("A thing that should not hunger is hungering.") + stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]) + if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)): + if t == world_db["Things"][0]: + strong_write(io_db["file_out"], "LOG You suffer from hunger.\n") + else: + name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"] + strong_write(io_db["file_out"], "LOG " + name + + " suffers from hunger.\n") + decrement_lifepoints(t) + + +def get_dir_to_target(t, filter): + """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target. + + The path-wise nearest target is chosen, via the shortest available path. + Target must not be t. On succcess, return positive value, else False. + Filters: + "a": Thing in FOV is below a certain distance, animate, but of ThingType + that is not t's, and starts out weaker than t is; build path as + avoiding things of t's ThingType + "f": neighbor cell (not inhabited by any animate Thing) further away from + animate Thing not further than x steps away and in FOV and of a + ThingType that is not t's, and starts out stronger or as strong as t + is currently; or (cornered), if no such flight cell, but Thing of + above criteria is too near,1 a cell closer to it, or, if less near, + just wait + "c": Thing in memorized map is consumable + "s": memory map cell with greatest-reachable degree of unexploredness + """ + + def zero_score_map_where_char_on_memdepthmap(c): + maptype = ctypes.c_char * len(t["T_MEMDEPTHMAP"]) + map = maptype.from_buffer(t["T_MEMDEPTHMAP"]) + test = libpr.zero_score_map_where_char_on_memdepthmap(c, map) + if test: + raise RuntimeError("No score map allocated for " + "zero_score_map_where_char_on_memdepthmap().") + + def set_map_score(pos, score): + test = libpr.set_map_score(pos, score) + if test: + raise RuntimeError("No score map allocated for set_map_score().") + + def get_map_score(pos): + result = libpr.get_map_score(pos) + if result < 0: + raise RuntimeError("No score map allocated for get_map_score().") + return result + + def seeing_thing(): + if t["fovmap"] and ("a" == filter or "f" == filter): + for id in world_db["Things"]: + Thing = world_db["Things"][id] + if Thing != t and Thing["T_LIFEPOINTS"] and \ + t["T_TYPE"] != Thing["T_TYPE"] and \ + 'v' == chr(t["fovmap"][(Thing["T_POSY"] + * world_db["MAP_LENGTH"]) + + Thing["T_POSX"]]): + ThingType = world_db["ThingTypes"][Thing["T_TYPE"]] + if ("f" == filter and ThingType["TT_LIFEPOINTS"] >= + t["T_LIFEPOINTS"]) \ + or ("a" == filter and ThingType["TT_LIFEPOINTS"] < + t["T_LIFEPOINTS"]): + return True + elif t["T_MEMMAP"] and "c" == filter: + for mt in t["T_MEMTHING"]: + if ' ' != chr(t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"]) + + mt[2]]) \ + and world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]: + return True + return False + + def init_score_map(): + test = libpr.init_score_map() + if test: + raise RuntimeError("Malloc error in init_score_map().") + ord_dot = ord(".") + ord_v = ord("v") + ord_blank = ord(" ") + for i in [i for i in range(world_db["MAP_LENGTH"] ** 2) + if ord_dot == t["T_MEMMAP"][i]]: + set_map_score(i, 65535 - 1) + if "a" == filter: + for id in world_db["Things"]: + Thing = world_db["Things"][id] + pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \ + + Thing["T_POSX"] + if t != Thing and Thing["T_LIFEPOINTS"] and \ + t["T_TYPE"] != Thing["T_TYPE"] and \ + ord_v == t["fovmap"][pos] and \ + t["T_LIFEPOINTS"] > \ + world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]: + set_map_score(pos, 0) + elif t["T_TYPE"] == Thing["T_TYPE"]: + set_map_score(pos, 65535) + elif "f" == filter: + for id in [id for id in world_db["Things"] + if world_db["Things"][id]["T_LIFEPOINTS"]]: + Thing = world_db["Things"][id] + pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \ + + Thing["T_POSX"] + if t["T_TYPE"] != Thing["T_TYPE"] and \ + ord_v == t["fovmap"][pos] and \ + t["T_LIFEPOINTS"] <= \ + world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]: + set_map_score(pos, 0) + elif "c" == filter: + for mt in [mt for mt in t["T_MEMTHING"] + if ord_blank != t["T_MEMMAP"][mt[1] + * world_db["MAP_LENGTH"] + + mt[2]] + if world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]]: + set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0) + elif "s" == filter: + zero_score_map_where_char_on_memdepthmap(mem_depth_c[0]) + + def rand_target_dir(neighbors, cmp, dirs): + candidates = [] + n_candidates = 0 + for i in range(len(dirs)): + if cmp == neighbors[i]: + candidates.append(dirs[i]) + n_candidates += 1 + return candidates[rand.next() % n_candidates] if n_candidates else 0 + + def get_neighbor_scores(dirs, eye_pos): + scores = [] + if libpr.ready_neighbor_scores(eye_pos): + raise RuntimeError("No score map allocated for " + + "ready_neighbor_scores.()") + for i in range(len(dirs)): + scores.append(libpr.get_neighbor_score(i)) + return scores + + def get_dir_from_neighbors(): + dir_to_target = False + dirs = "edcxsw" + eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"] + neighbors = get_neighbor_scores(dirs, eye_pos) + if "f" == filter: + inhabited = [world_db["Things"][id]["T_POSY"] + * world_db["MAP_LENGTH"] + + world_db["Things"][id]["T_POSX"] + for id in world_db["Things"] + if world_db["Things"][id]["T_LIFEPOINTS"]] + for i in range(len(dirs)): + mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"]) + pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \ + + libpr.result_x() + for pos in [pos for pos in inhabited if pos == pos_cmp]: + neighbors[i] = 65535 + break + minmax_start = 0 if "f" == filter else 65535 - 1 + minmax_neighbor = minmax_start + for i in range(len(dirs)): + if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and + minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \ + or ("f" != filter and minmax_neighbor > neighbors[i]): + minmax_neighbor = neighbors[i] + if minmax_neighbor != minmax_start: + dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs) + if "f" == filter: + if not dir_to_target: + if 1 == get_map_score(eye_pos): + dir_to_target = rand_target_dir(neighbors, 0, dirs) + elif 3 >= get_map_score(eye_pos): + t["T_COMMAND"] = [id for id in world_db["ThingActions"] + if + world_db["ThingActions"][id]["TA_NAME"] + == "wait"][0] + return 1 + elif dir_to_target and 3 < get_map_score(eye_pos): + dir_to_target = 0 + elif "a" == filter and 10 <= get_map_score(eye_pos): + dir_to_target = 0 + return dir_to_target + + dir_to_target = False + mem_depth_c = b' ' + run_i = 9 + 1 if "s" == filter else 1 + while run_i and not dir_to_target and ("s" == filter or seeing_thing()): + run_i -= 1 + init_score_map() + mem_depth_c = b'9' if b' ' == mem_depth_c \ + else bytes([mem_depth_c[0] - 1]) + if libpr.dijkstra_map(): + raise RuntimeError("No score map allocated for dijkstra_map().") + dir_to_target = get_dir_from_neighbors() + libpr.free_score_map() + if dir_to_target and str == type(dir_to_target): + t["T_COMMAND"] = [id for id in world_db["ThingActions"] + if world_db["ThingActions"][id]["TA_NAME"] + == "move"][0] + t["T_ARGUMENT"] = ord(dir_to_target) + return dir_to_target + + +def standing_on_consumable(t): + """Return True/False whether t is standing on a consumable.""" + for id in [id for id in world_db["Things"] if world_db["Things"][id] != t + if world_db["Things"][id]["T_POSY"] == t["T_POSY"] + if world_db["Things"][id]["T_POSX"] == t["T_POSX"] + if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]] + ["TT_CONSUMABLE"]]: + return True + return False + + +def get_inventory_slot_to_consume(t): + """Return slot Id of strongest consumable in t's inventory, else -1.""" + cmp_consumability = 0 + selection = -1 + i = 0 + for id in t["T_CARRIES"]: + type = world_db["Things"][id]["T_TYPE"] + if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability: + cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"] + selection = i + i += 1 + return selection + + +def ai(t): + """Determine next command/argment for actor t via AI algorithms. + + AI will look for, and move towards, enemies (animate Things not of their + own ThingType); if they see none, they will consume consumables in their + inventory; if there are none, they will pick up what they stand on if they + stand on consumables; if they stand on none, they will move towards the + next consumable they see or remember on the map; if they see or remember + none, they will explore parts of the map unseen since ever or for at least + one turn; if there is nothing to explore, they will simply wait. + """ + t["T_COMMAND"] = [id for id in world_db["ThingActions"] + if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0] + if not get_dir_to_target(t, "f"): + sel = get_inventory_slot_to_consume(t) + if -1 != sel: + t["T_COMMAND"] = [id for id in world_db["ThingActions"] + if world_db["ThingActions"][id]["TA_NAME"] + == "use"][0] + t["T_ARGUMENT"] = sel + elif standing_on_consumable(t): + t["T_COMMAND"] = [id for id in world_db["ThingActions"] + if world_db["ThingActions"][id]["TA_NAME"] + == "pick_up"][0] + elif (not get_dir_to_target(t, "c")) and \ + (not get_dir_to_target(t, "a")): + get_dir_to_target(t, "s") + + +def turn_over(): + """Run game world and its inhabitants until new player input expected.""" + id = 0 + whilebreaker = False + while world_db["Things"][0]["T_LIFEPOINTS"]: + for id in [id for id in world_db["Things"]]: # Only what's from start! + if not id in world_db["Things"] or \ + world_db["Things"][id]["carried"]: # May have been consumed or + continue # picked up during turn … + Thing = world_db["Things"][id] + if Thing["T_LIFEPOINTS"]: + if not Thing["T_COMMAND"]: + update_map_memory(Thing) + if 0 == id: + whilebreaker = True + break + ai(Thing) + try_healing(Thing) + Thing["T_PROGRESS"] += 1 + taid = [a for a in world_db["ThingActions"] + if a == Thing["T_COMMAND"]][0] + ThingAction = world_db["ThingActions"][taid] + if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]: + eval("actor_" + ThingAction["TA_NAME"])(Thing) + Thing["T_COMMAND"] = 0 + Thing["T_PROGRESS"] = 0 + hunger(Thing) + thingproliferation(Thing) + if whilebreaker: + break + world_db["TURN"] += 1 + + +def new_Thing(type, pos=(0, 0)): + """Return Thing of type T_TYPE, with fovmap if alive and world active.""" + thing = { + "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"], + "T_ARGUMENT": 0, + "T_PROGRESS": 0, + "T_SATIATION": 0, + "T_COMMAND": 0, + "T_TYPE": type, + "T_POSY": pos[0], + "T_POSX": pos[1], + "T_CARRIES": [], + "carried": False, + "T_MEMTHING": [], + "T_MEMMAP": False, + "T_MEMDEPTHMAP": False, + "fovmap": False + } + if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]: + build_fov_map(thing) + return thing + + +def id_setter(id, category, id_store=False, start_at_1=False): + """Set ID of object of category to manipulate ID unused? Create new one. + The ID is stored as id_store.id (if id_store is set). If the integer of the + input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1) + <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is + always returned when no new object is created, else the new object's ID. + """ + min = 0 if start_at_1 else -1 + if str == type(id): + id = integer_test(id, min) + if None != id: + if id in world_db[category]: + if id_store: + id_store.id = id + return None + else: + if (start_at_1 and 0 == id) \ + or ((not start_at_1) and (id < 0)): + id = 0 if start_at_1 else -1 + while 1: + id = id + 1 + if id not in world_db[category]: + break + return None + if id_store: + id_store.id = id + return id + + +def command_ping(): + """Send PONG line to server output file.""" + strong_write(io_db["file_out"], "PONG\n") + + +def command_quit(): + """Abort server process.""" + save_world() + atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True) + raise SystemExit("received QUIT command") + + +def command_thingshere(str_y, str_x): + """Write to out file list of Things known to player at coordinate y, x.""" + if world_db["WORLD_ACTIVE"]: + y = integer_test(str_y, 0, 255) + x = integer_test(str_x, 0, 255) + length = world_db["MAP_LENGTH"] + if None != y and None != x and y < length and x < length: + pos = (y * world_db["MAP_LENGTH"]) + x + strong_write(io_db["file_out"], "THINGS_HERE START\n") + if "v" == chr(world_db["Things"][0]["fovmap"][pos]): + for id in world_db["Things"]: + if y == world_db["Things"][id]["T_POSY"] \ + and x == world_db["Things"][id]["T_POSX"] \ + and not world_db["Things"][id]["carried"]: + type = world_db["Things"][id]["T_TYPE"] + name = world_db["ThingTypes"][type]["TT_NAME"] + strong_write(io_db["file_out"], name + "\n") + else: + for mt in world_db["Things"][0]["T_MEMTHING"]: + if y == mt[1] and x == mt[2]: + name = world_db["ThingTypes"][mt[0]]["TT_NAME"] + strong_write(io_db["file_out"], name + "\n") + strong_write(io_db["file_out"], "THINGS_HERE END\n") + else: + print("Ignoring: Invalid map coordinates.") + else: + print("Ignoring: Command only works on existing worlds.") + + +def play_commander(action, args=False): + """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over(). + + T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args. + """ + + def set_command(): + id = [x for x in world_db["ThingActions"] + if world_db["ThingActions"][x]["TA_NAME"] == action][0] + world_db["Things"][0]["T_COMMAND"] = id + turn_over() + + def set_command_and_argument_int(str_arg): + val = integer_test(str_arg, 0, 255) + if None != val: + world_db["Things"][0]["T_ARGUMENT"] = val + set_command() + + def set_command_and_argument_movestring(str_arg): + if str_arg in directions_db: + world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg]) + set_command() + else: + print("Ignoring: Argument must be valid direction string.") + + if action == "move": + return set_command_and_argument_movestring + elif args: + return set_command_and_argument_int + else: + return set_command + + +def command_seedrandomness(seed_string): + """Set rand seed to int(seed_string).""" + val = integer_test(seed_string, 0, 4294967295) + if None != val: + rand.seed = val + + +def command_seedmap(seed_string): + """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map.""" + setter(None, "SEED_MAP", 0, 4294967295)(seed_string) + remake_map() + + +def command_makeworld(seed_string): + """(Re-)build game world, i.e. map, things, to a new turn 1 from seed. + + Seed rand with seed, fill it into world_db["SEED_MAP"]. Do more only with a + "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType of + TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map() + and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things + according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType + of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each + other. Init player's memory map. Write "NEW_WORLD" line to out file. + """ + + def free_pos(): + i = 0 + while 1: + err = "Space to put thing on too hard to find. Map too small?" + while 1: + y = rand.next() % world_db["MAP_LENGTH"] + x = rand.next() % world_db["MAP_LENGTH"] + if "." == chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]): + break + i += 1 + if i == 65535: + raise SystemExit(err) + # Replica of C code, wrongly ignores animatedness of new Thing. + pos_clear = (0 == len([id for id in world_db["Things"] + if world_db["Things"][id]["T_LIFEPOINTS"] + if world_db["Things"][id]["T_POSY"] == y + if world_db["Things"][id]["T_POSX"] == x])) + if pos_clear: + break + return (y, x) + + val = integer_test(seed_string, 0, 4294967295) + if None == val: + return + rand.seed = val + world_db["SEED_MAP"] = val + player_will_be_generated = False + playertype = world_db["PLAYER_TYPE"] + for ThingType in world_db["ThingTypes"]: + if playertype == ThingType: + if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]: + player_will_be_generated = True + break + if not player_will_be_generated: + print("Ignoring beyond SEED_MAP: " + + "No player type with start number >0 defined.") + return + wait_action = False + for ThingAction in world_db["ThingActions"]: + if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]: + wait_action = True + if not wait_action: + print("Ignoring beyond SEED_MAP: " + + "No thing action with name 'wait' defined.") + return + world_db["Things"] = {} + remake_map() + world_db["WORLD_ACTIVE"] = 1 + world_db["TURN"] = 1 + for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]): + id = id_setter(-1, "Things") + world_db["Things"][id] = new_Thing(playertype, free_pos()) + update_map_memory(world_db["Things"][0]) + for type in world_db["ThingTypes"]: + for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]): + if type != playertype: + id = id_setter(-1, "Things") + world_db["Things"][id] = new_Thing(type, free_pos()) + strong_write(io_db["file_out"], "NEW_WORLD\n") + + +def command_maplength(maplength_string): + """Redefine map length. Invalidate map, therefore lose all things on it.""" + val = integer_test(maplength_string, 1, 256) + if None != val: + world_db["MAP_LENGTH"] = val + set_world_inactive() + world_db["Things"] = {} + libpr.set_maplength(val) + + +def command_worldactive(worldactive_string): + """Toggle world_db["WORLD_ACTIVE"] if possible. + + An active world can always be set inactive. An inactive world can only be + set active with a "wait" ThingAction, and a player Thing (of ID 0). On + activation, rebuild all Things' FOVs, and the player's map memory. + """ + # In original version, map existence was also tested (unnecessarily?). + val = integer_test(worldactive_string, 0, 1) + if val: + if 0 != world_db["WORLD_ACTIVE"]: + if 0 == val: + set_world_inactive() + else: + print("World already active.") + elif 0 == world_db["WORLD_ACTIVE"]: + wait_exists = False + for ThingAction in world_db["ThingActions"]: + if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]: + wait_exists = True + break + player_exists = False + for Thing in world_db["Things"]: + if 0 == Thing: + player_exists = True + break + if wait_exists and player_exists: + for id in world_db["Things"]: + if world_db["Things"][id]["T_LIFEPOINTS"]: + build_fov_map(world_db["Things"][id]) + if 0 == id: + update_map_memory(world_db["Things"][id], False) + world_db["WORLD_ACTIVE"] = 1 + + +def test_for_id_maker(object, category): + """Return decorator testing for object having "id" attribute.""" + def decorator(f): + def helper(*args): + if hasattr(object, "id"): + f(*args) + else: + print("Ignoring: No " + category + + " defined to manipulate yet.") + return helper + return decorator + + +def command_tid(id_string): + """Set ID of Thing to manipulate. ID unused? Create new one. + + Default new Thing's type to the first available ThingType, others: zero. + """ + id = id_setter(id_string, "Things", command_tid) + if None != id: + if world_db["ThingTypes"] == {}: + print("Ignoring: No ThingType to settle new Thing in.") + return + type = list(world_db["ThingTypes"].keys())[0] + world_db["Things"][id] = new_Thing(type) + + +test_Thing_id = test_for_id_maker(command_tid, "Thing") + + +@test_Thing_id +def command_tcommand(str_int): + """Set T_COMMAND of selected Thing.""" + val = integer_test(str_int, 0) + if None != val: + if 0 == val or val in world_db["ThingActions"]: + world_db["Things"][command_tid.id]["T_COMMAND"] = val + else: + print("Ignoring: ThingAction ID belongs to no known ThingAction.") + + +@test_Thing_id +def command_ttype(str_int): + """Set T_TYPE of selected Thing.""" + val = integer_test(str_int, 0) + if None != val: + if val in world_db["ThingTypes"]: + world_db["Things"][command_tid.id]["T_TYPE"] = val + else: + print("Ignoring: ThingType ID belongs to no known ThingType.") + + +@test_Thing_id +def command_tcarries(str_int): + """Append int(str_int) to T_CARRIES of selected Thing. + + The ID int(str_int) must not be of the selected Thing, and must belong to a + Thing with unset "carried" flag. Its "carried" flag will be set on owning. + """ + val = integer_test(str_int, 0) + if None != val: + if val == command_tid.id: + print("Ignoring: Thing cannot carry itself.") + elif val in world_db["Things"] \ + and not world_db["Things"][val]["carried"]: + world_db["Things"][command_tid.id]["T_CARRIES"].append(val) + world_db["Things"][val]["carried"] = True + else: + print("Ignoring: Thing not available for carrying.") + # Note that the whole carrying structure is different from the C version: + # Carried-ness is marked by a "carried" flag, not by Things containing + # Things internally. + + +@test_Thing_id +def command_tmemthing(str_t, str_y, str_x): + """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING. + + The type must fit to an existing ThingType, and the position into the map. + """ + type = integer_test(str_t, 0) + posy = integer_test(str_y, 0, 255) + posx = integer_test(str_x, 0, 255) + if None != type and None != posy and None != posx: + if type not in world_db["ThingTypes"] \ + or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]: + print("Ignoring: Illegal value for thing type or position.") + else: + memthing = (type, posy, posx) + world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing) + + +def setter_map(maptype): + """Set selected Thing's map of maptype's int(str_int)-th line to mapline. + + If Thing has no map of maptype yet, initialize it with ' ' bytes first. + """ + @test_Thing_id + def helper(str_int, mapline): + val = integer_test(str_int, 0, 255) + if None != val: + if val >= world_db["MAP_LENGTH"]: + print("Illegal value for map line number.") + elif len(mapline) != world_db["MAP_LENGTH"]: + print("Map line length is unequal map width.") + else: + length = world_db["MAP_LENGTH"] + map = None + if not world_db["Things"][command_tid.id][maptype]: + map = bytearray(b' ' * (length ** 2)) + else: + map = world_db["Things"][command_tid.id][maptype] + map[val * length:(val * length) + length] = mapline.encode() + world_db["Things"][command_tid.id][maptype] = map + return helper + + +def setter_tpos(axis): + """Generate setter for T_POSX or T_POSY of selected Thing. + + If world is active, rebuilds animate things' fovmap, player's memory map. + """ + @test_Thing_id + def helper(str_int): + val = integer_test(str_int, 0, 255) + if None != val: + if val < world_db["MAP_LENGTH"]: + world_db["Things"][command_tid.id]["T_POS" + axis] = val + if world_db["WORLD_ACTIVE"] \ + and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]: + build_fov_map(world_db["Things"][command_tid.id]) + if 0 == command_tid.id: + update_map_memory(world_db["Things"][command_tid.id]) + else: + print("Ignoring: Position is outside of map.") + return helper + + +def command_ttid(id_string): + """Set ID of ThingType to manipulate. ID unused? Create new one. + + Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0. + """ + id = id_setter(id_string, "ThingTypes", command_ttid) + if None != id: + world_db["ThingTypes"][id] = { + "TT_NAME": "(none)", + "TT_CONSUMABLE": 0, + "TT_LIFEPOINTS": 0, + "TT_PROLIFERATE": 0, + "TT_START_NUMBER": 0, + "TT_SYMBOL": "?", + "TT_CORPSE_ID": id + } + + +test_ThingType_id = test_for_id_maker(command_ttid, "ThingType") + + +@test_ThingType_id +def command_ttname(name): + """Set TT_NAME of selected ThingType.""" + world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name + + +@test_ThingType_id +def command_ttsymbol(char): + """Set TT_SYMBOL of selected ThingType. """ + if 1 == len(char): + world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char + else: + print("Ignoring: Argument must be single character.") + + +@test_ThingType_id +def command_ttcorpseid(str_int): + """Set TT_CORPSE_ID of selected ThingType.""" + val = integer_test(str_int, 0) + if None != val: + if val in world_db["ThingTypes"]: + world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val + else: + print("Ignoring: Corpse ID belongs to no known ThignType.") + + +def command_taid(id_string): + """Set ID of ThingAction to manipulate. ID unused? Create new one. + + Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait". + """ + id = id_setter(id_string, "ThingActions", command_taid, True) + if None != id: + world_db["ThingActions"][id] = { + "TA_EFFORT": 1, + "TA_NAME": "wait" + } + + +test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction") + + +@test_ThingAction_id +def command_taname(name): + """Set TA_NAME of selected ThingAction. + + The name must match a valid thing action function. If after the name + setting no ThingAction with name "wait" remains, call set_world_inactive(). + """ + if name == "wait" or name == "move" or name == "use" or name == "drop" \ + or name == "pick_up": + world_db["ThingActions"][command_taid.id]["TA_NAME"] = name + if 1 == world_db["WORLD_ACTIVE"]: + wait_defined = False + for id in world_db["ThingActions"]: + if "wait" == world_db["ThingActions"][id]["TA_NAME"]: + wait_defined = True + break + if not wait_defined: + set_world_inactive() + else: + print("Ignoring: Invalid action name.") + # In contrast to the original,naming won't map a function to a ThingAction. + + +def command_ai(): + """Call ai() on player Thing, then turn_over().""" + ai(world_db["Things"][0]) + turn_over() + + +"""Commands database. + +Map command start tokens to ([0]) number of expected command arguments, ([1]) +the command's meta-ness (i.e. is it to be written to the record file, is it to +be ignored in replay mode if read from server input file), and ([2]) a function +to be called on it. +""" +commands_db = { + "QUIT": (0, True, command_quit), + "PING": (0, True, command_ping), + "THINGS_HERE": (2, True, command_thingshere), + "MAKE_WORLD": (1, False, command_makeworld), + "SEED_MAP": (1, False, command_seedmap), + "SEED_RANDOMNESS": (1, False, command_seedrandomness), + "TURN": (1, False, setter(None, "TURN", 0, 65535)), + "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)), + "MAP_LENGTH": (1, False, command_maplength), + "WORLD_ACTIVE": (1, False, command_worldactive), + "TA_ID": (1, False, command_taid), + "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)), + "TA_NAME": (1, False, command_taname), + "TT_ID": (1, False, command_ttid), + "TT_NAME": (1, False, command_ttname), + "TT_SYMBOL": (1, False, command_ttsymbol), + "TT_CORPSE_ID": (1, False, command_ttcorpseid), + "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE", + 0, 65535)), + "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER", + 0, 255)), + "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE", + 0, 255)), + "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)), + "T_ID": (1, False, command_tid), + "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)), + "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)), + "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)), + "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)), + "T_COMMAND": (1, False, command_tcommand), + "T_TYPE": (1, False, command_ttype), + "T_CARRIES": (1, False, command_tcarries), + "T_MEMMAP": (2, False, setter_map("T_MEMMAP")), + "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")), + "T_MEMTHING": (3, False, command_tmemthing), + "T_POSY": (1, False, setter_tpos("Y")), + "T_POSX": (1, False, setter_tpos("X")), + "wait": (0, False, play_commander("wait")), + "move": (1, False, play_commander("move")), + "pick_up": (0, False, play_commander("pick_up")), + "drop": (1, False, play_commander("drop", True)), + "use": (1, False, play_commander("use", True)), + "ai": (0, False, command_ai) +} + + +"""World state database. With sane default values. (Randomness is in rand.)""" +world_db = { + "TURN": 0, + "MAP_LENGTH": 64, + "SEED_MAP": 0, + "PLAYER_TYPE": 0, + "WORLD_ACTIVE": 0, + "ThingActions": {}, + "ThingTypes": {}, + "Things": {} +} + +"""Mapping of direction names to internal direction chars.""" +directions_db = {"east": "d", "south-east": "c", "south-west": "x", + "west": "s", "north-west": "w", "north-east": "e"} + +"""File IO database.""" +io_db = { + "path_save": "save", + "path_record": "record_save", + "path_worldconf": "confserver/world", + "path_server": "server/", + "path_in": "server/in", + "path_out": "server/out", + "path_worldstate": "server/worldstate", + "tmp_suffix": "_tmp", + "kicked_by_rival": False, + "worldstate_updateable": False +} + + +try: + libpr = prep_library() + rand = RandomnessIO() + opts = parse_command_line_arguments() + if opts.savefile: + io_db["path_save"] = opts.savefile + io_db["path_record"] = "record_" + opts.savefile + setup_server_io() + if opts.verbose: + io_db["verbose"] = True + if None != opts.replay: + replay_game() + else: + play_game() +except SystemExit as exit: + print("ABORTING: " + exit.args[0]) +except: + print("SOMETHING WENT WRONG IN UNEXPECTED WAYS") + raise +finally: + cleanup_server_io() diff --git a/roguelike-server.do b/roguelike-server.do deleted file mode 100644 index ebe418d..0000000 --- a/roguelike-server.do +++ /dev/null @@ -1,10 +0,0 @@ -# redo build file to build the executable "roguelike-server". - -# This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 -# or any later version. For details on its copyright, license, and warranties, -# see the file NOTICE in the root directory of the PlomRogue source package. - -redo-ifchange build/build_template -TARGET=server -LIBRARY_LINKS=-lm -. ./build/build_template diff --git a/roguelike_python b/roguelike_python deleted file mode 100755 index 74f7f34..0000000 --- a/roguelike_python +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -# Wrapper to the script so that its suppressed server messages get read on exit. -./start_server_python_client_union.sh "$@" - -# For some reason, mere sync won't ensure a log is written out, so wait a while. -sync -sleep 0.5 -if [ -e ./log ] -then - cat log -fi diff --git a/src/server/ai.c b/src/server/ai.c deleted file mode 100644 index 749a385..0000000 --- a/src/server/ai.c +++ /dev/null @@ -1,486 +0,0 @@ -/* src/server/ai.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "ai.h" -#include /* uint8_t, uint16_t, uint32_t, int16_t, UINT16_MAX */ -#include /* free() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "hardcoded_strings.h" /* s */ -#include "rrand.h" /* rrand() */ -#include "thing_actions.h" /* get_thing_action_id_by_name() */ -#include "things.h" /* Thing, ThingType, ThingInMemory, get_thing_type() */ -#include "world.h" /* world */ - -#define N_DIRS 6 - - - -/* Return "score_map"["pos"] unless "check_inhabitant" and cell is inhabited. */ -static uint16_t set_neighbor_val(uint16_t * score_map, uint8_t check_inhabitant, - uint16_t kill_score, uint16_t pos); - -/* Write into "neighbors" scores of the N_DIRS immediate neighbors of the - * "score_map" cell at "pos_i" (array index), as found in the directions - * north-east, east, south-east etc. (clockwise order). Use "kill_score" for - * illegal neighborhoods (i.e. if direction would lead beyond the map's border, - * or, if "check_inhabitants" is non-zero, into animate-inhabited cell). - */ -static void get_neighbor_scores(uint16_t * score_map, uint16_t pos_i, - uint16_t kill_score, uint16_t * neighbors, - uint8_t check_inhabitants); - -/* Iterate over scored cells in "score_map" of world.map's geometry. Compare - * each cell's score against the score of its immediate neighbors in N_DIRS - * directions. If any neighbor's score is at least two points lower than the - * current cell's score, re-set it to 1 point higher than its lowest-scored - * neighbor. Repeat this whole process until all cells have settled on their - * final score. Ignore cells 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); - -/* Helpers to init_score_map(), realizing individual filters. */ -static uint8_t score_map_filter_attack(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye); -static uint8_t score_map_filter_flee(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye); -static uint8_t score_map_filter_consume(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye); -static uint8_t score_map_filter_search(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye); - -/* get_dir_to_nearest_target() helper: Prepare "score_map" for dijkstra_map(). */ -static void init_score_map(char filter, uint16_t * score_map, uint32_t map_size, - struct Thing * t_eye); - -/* From "targets" select random "cmp" match as directory by order in "dirs". */ -static char rand_target_dir(char * dirs, uint16_t cmp, uint16_t * targets); - -/* Helper to get_dir_to_nearest_target(). */ -static char get_dir_from_neighbors(char filter, struct Thing * t_eye, - uint16_t * score_map); - -/* Set (if possible) as "t_eye"'s command a move to the path to the path-wise - * nearest target that is not "t_eye" and fits criteria set by "filter". On - * success, return !0, else 0. Values for "filter": - * "a": thing in FOV is below a certain distance, animate, but of a type that is - * not "t_eye"'s, and starts out weaker than it is; build path as avoiding - * things of "t_eye"'s type - * "f": neighbor cell (not inhabited by any animate thing) further away from - * animate thing not further than x steps away and in FOV and of a type - * that is not "t_eye"'s, and starts out stronger or as strong as "t_eye" - * is currently; or (cornered), if no such flight cell, but thing of above - * criteria is too near, a cell closer to it, or, if less near, just wait - * "c": thing in memorized map is consumable - * "s": memory map cell with greatest-reachable degree of unexploredness - */ -static uint8_t get_dir_to_nearest_target(struct Thing * t_eye, char filter); - -/* Return 1 if any thing not "t_eye" is known and fulfills some criteria defined - * by "filter", else 0. Values for "filter": - * "a" or "f": thing in FOV is animate, but of type that not that of "t_eye", - * and starts out weaker ("a") / stronger ("f") than "t_eye" is - * "c" : thing in memorized map is consumable - */ -static uint8_t seeing_thing(struct Thing * t_eye, char filter); - -/* Return slot ID of strongest consumable in "t_owner"'s inventory, else -1. */ -static int16_t get_inventory_slot_to_consume(struct Thing * t_owner); - -/* Return 1 if "t_standing" is standing on a consumable, else 0. */ -static uint8_t standing_on_consumable(struct Thing * t_standing); - - - -static uint16_t set_neighbor_val(uint16_t * score_map, uint8_t check_inhabitant, - uint16_t kill_score, uint16_t pos) -{ - if (check_inhabitant) - { - struct Thing * t = world.things; - for (; t; t = t->next) - { - if (t->lifepoints && pos == t->pos.y * world.map.length + t->pos.x) - { - return kill_score; - } - } - } - return score_map[pos]; -} - - - -static void get_neighbor_scores(uint16_t * score_map, uint16_t pos_i, - uint16_t kill_score, uint16_t * neighbors, - uint8_t check_inhabitants) -{ - uint32_t map_size = world.map.length * world.map.length; - uint8_t open_north = pos_i >= world.map.length; - uint8_t open_east = pos_i + 1 % world.map.length; - uint8_t open_south = pos_i + world.map.length < map_size; - uint8_t open_west = pos_i % world.map.length; - uint8_t is_indented = (pos_i / world.map.length) % 2; - uint8_t open_diag_west = is_indented || open_west; - uint8_t open_diag_east = !is_indented || open_east; - neighbors[0] = !(open_north && open_diag_east) ? kill_score : - set_neighbor_val(score_map, check_inhabitants, kill_score, - pos_i - world.map.length + is_indented); - neighbors[1] = !(open_east) ? kill_score : - set_neighbor_val(score_map, check_inhabitants, kill_score, - pos_i + 1); - neighbors[2] = !(open_south && open_diag_east) ? kill_score : - set_neighbor_val(score_map, check_inhabitants, kill_score, - pos_i + world.map.length + is_indented); - neighbors[3] = !(open_south && open_diag_west) ? kill_score : - set_neighbor_val(score_map, check_inhabitants, kill_score, - pos_i + world.map.length - !is_indented); - neighbors[4] = !(open_west) ? kill_score : - set_neighbor_val(score_map, check_inhabitants, kill_score, - pos_i - 1); - neighbors[5] = !(open_north && open_diag_west) ? kill_score : - set_neighbor_val(score_map, check_inhabitants, kill_score, - pos_i - world.map.length - !is_indented); -} - - - -static void dijkstra_map(uint16_t * score_map, uint16_t max_score) -{ - uint32_t map_size = world.map.length * world.map.length; - uint32_t pos; - uint16_t i_scans, neighbors[N_DIRS], min_neighbor; - uint8_t scores_still_changing = 1; - uint8_t i_dirs; - for (i_scans = 0; scores_still_changing; i_scans++) - { - scores_still_changing = 0; - for (pos = 0; pos < map_size; pos++) - { - if (score_map[pos] <= max_score) - { - get_neighbor_scores(score_map, pos, max_score, neighbors, 0); - min_neighbor = max_score; - for (i_dirs = 0; i_dirs < N_DIRS; i_dirs++) - { - if (min_neighbor > neighbors[i_dirs]) - { - min_neighbor = neighbors[i_dirs]; - } - } - if (score_map[pos] > min_neighbor + 1) - { - score_map[pos] = min_neighbor + 1; - scores_still_changing = 1; - } - } - } - } -} - - - -static uint8_t score_map_filter_attack(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye) -{ - if ('a' != filter) - { - return 0; - } - struct Thing * t = world.things; - for (; t; t = t->next) - { - if ( t != t_eye && t->lifepoints && t->type != t_eye->type - && 'v' == t_eye->fov_map[t->pos.y*world.map.length + t->pos.x] - && get_thing_type(t->type)->lifepoints < t_eye->lifepoints) - { - score_map[t->pos.y * world.map.length + t->pos.x] = 0; - } - else if (t->type == t_eye->type) - { - score_map[t->pos.y * world.map.length + t->pos.x] = UINT16_MAX; - } - } - return 1; -} - - - -static uint8_t score_map_filter_flee(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye) -{ - if ('f' != filter) - { - return 0; - } - struct Thing * t = world.things; - for (; t; t = t->next) - { - if ( t->lifepoints && t->type != t_eye->type - && 'v' == t_eye->fov_map[t->pos.y*world.map.length + t->pos.x] - && get_thing_type(t->type)->lifepoints >= t_eye->lifepoints) - { - score_map[t->pos.y * world.map.length + t->pos.x] = 0; - } - } - return 1; -} - - - -static uint8_t score_map_filter_consume(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye) -{ - if ('c' != filter) - { - return 0; - } - struct ThingInMemory * tm = t_eye->t_mem; - for (; tm; tm = tm->next) - { - if ( ' ' != t_eye->mem_map[tm->pos.y * world.map.length + tm->pos.x] - && get_thing_type(tm->type)->consumable) - { - score_map[tm->pos.y * world.map.length + tm->pos.x] = 0; - } - } - return 1; -} - - - -static uint8_t score_map_filter_search(uint8_t filter, uint16_t * score_map, - struct Thing * t_eye) -{ - if (!(('0' < filter && '9' >= filter) || ' ' == filter)) - { - return 0; - } - uint32_t i; - for (i = 0; i < (uint32_t) (world.map.length * world.map.length); i++) - { - score_map[i] = filter == t_eye->mem_depth_map[i] ? 0 : score_map[i]; - } - return 1; -} - - - -static void init_score_map(char filter, uint16_t * score_map, uint32_t map_size, - struct Thing * t_eye) -{ - uint32_t i; - for (i = 0; i < map_size; i++) - { - score_map[i] = UINT16_MAX; - if ('.' == t_eye->mem_map[i]) - { - score_map[i] = UINT16_MAX-1; - } - } - if ( score_map_filter_attack(filter, score_map, t_eye) - || score_map_filter_flee(filter, score_map, t_eye) - || score_map_filter_consume(filter, score_map, t_eye) - || score_map_filter_search(filter, score_map, t_eye)) - { - } -} - - -static char rand_target_dir(char * dirs, uint16_t cmp, uint16_t * targets) -{ - char candidates[N_DIRS]; - uint8_t n_candidates = 0; - uint8_t i; - for (i = 0; i < N_DIRS; i++) - { - if (cmp == targets[i]) - { - candidates[n_candidates] = dirs[i]; - n_candidates++; - } - } - return n_candidates ? candidates[rrand() % n_candidates] : 0; -} - - - -static char get_dir_from_neighbors(char filter, struct Thing * t_eye, - uint16_t * score_map) -{ - char dir_to_nearest_target = 0; - uint16_t pos_i = (t_eye->pos.y * world.map.length) + t_eye->pos.x; - char * dirs = "edcxsw"; /* get_neighbor_scores()'s clockwise dir order. */ - uint16_t neighbors[N_DIRS]; - get_neighbor_scores(score_map, pos_i, UINT16_MAX, neighbors, 'f'==filter); - uint16_t minmax_start = 'f' == filter ? 0 : UINT16_MAX-1; - uint16_t minmax_neighbor = minmax_start; - uint8_t i; - for (i = 0; i < N_DIRS; i++) - { - if ( ( 'f' == filter && score_map[pos_i] < neighbors[i] - && minmax_neighbor < neighbors[i] && UINT16_MAX != neighbors[i]) - || ('f' != filter && minmax_neighbor > neighbors[i])) - { - minmax_neighbor = neighbors[i]; - } - } - if (minmax_neighbor != minmax_start) - { - dir_to_nearest_target = rand_target_dir(dirs,minmax_neighbor,neighbors); - } - if ('f' == filter) - { - if (!dir_to_nearest_target) - { - if (1 == score_map[pos_i]) /* Attack if cornered too closely. */ - { - dir_to_nearest_target = rand_target_dir(dirs, 0, neighbors); - } - else if (3 >= score_map[pos_i]) /* If less closely, just wait. */ - { - t_eye->command = get_thing_action_id_by_name(s[S_CMD_WAIT]); - return 1; - } - } - else if (dir_to_nearest_target && 3 < score_map[pos_i]) /* Don't flee */ - { /* enemy of */ - dir_to_nearest_target = 0; /* a certain */ - } /* distance. */ - } - else if ('a' == filter && 10 <= score_map[pos_i]) - { - dir_to_nearest_target = 0; - } - return dir_to_nearest_target; -} - - - -static uint8_t get_dir_to_nearest_target(struct Thing * t_eye, char filter) -{ - char dir_to_nearest_target = 0; - uint8_t mem_depth_char = ' '; - uint8_t run_i = 's' == filter ? 9 /* max explored mem depth age */ + 1 : 1; - while ( run_i && !dir_to_nearest_target - && ('s' == filter || seeing_thing(t_eye, filter))) - { - run_i--; - uint32_t map_size = world.map.length * world.map.length; - uint16_t * score_map = try_malloc(map_size * sizeof(uint16_t),__func__); - init_score_map('s' == filter ? mem_depth_char : filter, - score_map, map_size, t_eye); - mem_depth_char = ' ' == mem_depth_char ? '9' : mem_depth_char - 1; - dijkstra_map(score_map, UINT16_MAX-1); - dir_to_nearest_target = get_dir_from_neighbors(filter,t_eye,score_map); - free(score_map); - if (dir_to_nearest_target && 1 != dir_to_nearest_target) - { - t_eye->command = get_thing_action_id_by_name(s[S_CMD_MOVE]); - t_eye->arg = dir_to_nearest_target; - } - } - return dir_to_nearest_target; -} - - - -static uint8_t seeing_thing(struct Thing * t_eye, char filter) -{ - if (t_eye->fov_map && ('a' == filter || 'f' == filter)) - { - struct Thing * t = world.things; - for (; t; t = t->next) - { - if ( t != t_eye && t->lifepoints && t->type != t_eye->type - && 'v' == t_eye->fov_map[t->pos.y*world.map.length + t->pos.x]) - { - struct ThingType * tt = get_thing_type(t->type); - if ( ('f' == filter && tt->lifepoints >= t_eye->lifepoints) - || ('a' == filter && tt->lifepoints < t_eye->lifepoints)) - { - return 1; - } - } - } - } - else if (t_eye->mem_map && 'c' == filter) - { - struct ThingInMemory * tm = t_eye->t_mem; - for (; tm; tm = tm->next) - { - if ( ' ' != t_eye->mem_map[tm->pos.y*world.map.length+tm->pos.x] - && get_thing_type(tm->type)->consumable) - { - return 1; - } - } - } - return 0; -} - - - -static int16_t get_inventory_slot_to_consume(struct Thing * t_owner) -{ - uint8_t compare_consumability = 0; - int16_t selection = -1; - struct Thing * t = t_owner->owns;; - uint8_t i; - for (i = 0; t; t = t->next, i++) - { - struct ThingType * tt = get_thing_type(t->type); - if (tt->consumable > compare_consumability) - { - compare_consumability = tt->consumable; - selection = i; - } - } - return selection; -} - - - -static uint8_t standing_on_consumable(struct Thing * t_standing) -{ - struct Thing * t = world.things; - for (; t; t = t->next) - { - if ( t != t_standing - && t->pos.y == t_standing->pos.y && t->pos.x == t_standing->pos.x - && get_thing_type(t->type)->consumable) - { - return 1; - } - } - return 0; -} - - - -extern void ai(struct Thing * t) -{ - t->command = get_thing_action_id_by_name(s[S_CMD_WAIT]); - if (!get_dir_to_nearest_target(t, 'f')) - { - int16_t sel = get_inventory_slot_to_consume(t); - if (-1 != sel) - { - t->command = get_thing_action_id_by_name(s[S_CMD_USE]); - t->arg = (uint8_t) sel; - } - else if (standing_on_consumable(t)) - { - t->command = get_thing_action_id_by_name(s[S_CMD_PICKUP]); - } - else if ( !get_dir_to_nearest_target(t, 'c') - && !get_dir_to_nearest_target(t, 'a')) - { - get_dir_to_nearest_target(t, 's'); - } - } -} diff --git a/src/server/ai.h b/src/server/ai.h deleted file mode 100644 index 966edd5..0000000 --- a/src/server/ai.h +++ /dev/null @@ -1,30 +0,0 @@ -/* src/server/ai.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Pseudo AI for actor movement. - */ - -#ifndef AI_H -#define AI_H - -struct Thing; - - - -/* Determine next non-player actor command / arguments by the actor's AI. Actors - * will look for, and move towards, enemies (animate things not of their own - * type); if they see none, they will consume consumables in their inventory; if - * there are none, they will pick up any consumables they stand on; if they - * stand on none, they will move towards the next consumable they see or - * remember on the map; if they see or remember none, they'll explore parts of - * the map unseen since ever or for at least one turn; if there is nothing to - * explore, they will simply wait. - */ -extern void ai(struct Thing * t); - - - -#endif diff --git a/src/server/cleanup.c b/src/server/cleanup.c deleted file mode 100644 index ec8017d..0000000 --- a/src/server/cleanup.c +++ /dev/null @@ -1,70 +0,0 @@ -/* src/server/cleanup.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "cleanup.h" -#include /* uint32_t */ -#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 */ - - - - -/* The clean-up flags set by set_cleanup_flag(). */ -static uint32_t cleanup_flags = 0x0000; - - - -extern void cleanup() -{ - free(world.queue); - free(world.map.cells); - if (cleanup_flags & CLEANUP_WORLDSTATE) - { - unlink(s[S_PATH_WORLDSTATE]); - } - if (cleanup_flags & CLEANUP_THINGS) - { - free_things(world.things); - } - if (cleanup_flags & CLEANUP_THING_TYPES) - { - free_thing_types(world.thing_types); - } - if (cleanup_flags & CLEANUP_THING_ACTIONS) - { - free_thing_actions(world.thing_actions); - } - if (cleanup_flags & CLEANUP_IN) - { - try_fclose(world.file_in, __func__); - unlink(s[S_PATH_IN]); - } - if (cleanup_flags & CLEANUP_OUT) - { - try_fclose(world.file_out, __func__); - free(world.server_test); - unlink(s[S_PATH_OUT]); - } -} - - -extern void set_cleanup_flag(enum cleanup_flag flag) -{ - cleanup_flags = cleanup_flags | flag; -} - - - -extern void unset_cleanup_flag(enum cleanup_flag flag) -{ - cleanup_flags = cleanup_flags ^ flag; -} diff --git a/src/server/cleanup.h b/src/server/cleanup.h deleted file mode 100644 index 2047510..0000000 --- a/src/server/cleanup.h +++ /dev/null @@ -1,39 +0,0 @@ -/* src/server/cleanup.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Stuff defining / performing the cleanup called by rexit.h's exit functions. - */ - -#ifndef CLEANUP_H -#define CLEANUP_H - - - -/* set_cleanup_flag() sets any of the flags defined in cleanup_flag to announce - * the resources that need cleaning up upon program exit. It is to be called at - * the earliest moment possible after resource creation / initialization. - */ -enum cleanup_flag -{ - CLEANUP_FIFO = 0x0001, - CLEANUP_WORLDSTATE = 0x0002, - CLEANUP_THING_TYPES = 0x0004, - CLEANUP_THINGS = 0x0008, - CLEANUP_THING_ACTIONS = 0x0010, - CLEANUP_IN = 0x0020, - CLEANUP_OUT = 0x0040 -}; - -/* In addition, unset_cleanup_flag() may be used to unset flags. */ -extern void set_cleanup_flag(enum cleanup_flag flag); -extern void unset_cleanup_flag(enum cleanup_flag flag); - -/* Frees memory and unlinks some files. */ -extern void cleanup(); - - - -#endif diff --git a/src/server/field_of_view.c b/src/server/field_of_view.c deleted file mode 100644 index 45c90e6..0000000 --- a/src/server/field_of_view.c +++ /dev/null @@ -1,381 +0,0 @@ -/* src/server/field_of_view.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "field_of_view.h" -#include /* pow() */ -#include /* NULL */ -#include /* uint8_t, uint16_t, uint32_t, int32_t, UINT8_MAX */ -#include /* free() */ -#include /* memset() */ -#include "../common/rexit.h" /* exit_trouble() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "../common/yx_uint8.h" /* yx_uint8 */ -#include "map.h" /* mv_yx_in_dir_legal(), init_empty_map() */ -#include "rrand.h" /* rrand() */ -#include "things.h" /* Thing, ThingInMemory, add_thing_to_memory_map() */ -#include "world.h" /* world */ - - - -/* Number of degrees a circle is divided into. The greater it is, the greater - * the angle precision. But make it one whole zero larger and bizarre FOV bugs - * appear on large maps, probably due to value overflows (TODO: more research!). - */ -#define CIRCLE 3600000 - - - -/* Angle of a shadow. */ -struct shadow_angle -{ - struct shadow_angle * next; - uint32_t left_angle; - uint32_t right_angle; -}; - - - -/* Recalculate angle < 0 or > CIRCLE to a value between these two limits. */ -static uint32_t correct_angle(int32_t angle); - -/* Try merging the angle between "left_angle" and "right_angle" to "shadow" if - * it meets the shadow from the right or the left. Returns 1 on success, else 0. - */ -static uint8_t try_merge(struct shadow_angle * shadow, - uint32_t left_angle, uint32_t right_angle); - -/* Try merging the shadow angle between "left_angle" and "right_angle" into an - * existing shadow angle in "shadows". On success, see if this leads to any - * additional shadow angle overlaps and merge these accordingly. Return 1 on - * success, else 0. - */ -static uint8_t try_merging_angles(uint32_t left_angle, uint32_t right_angle, - struct shadow_angle ** shadows); - -/* Test whether angle between "left_angle" and "right_angle", or at least - * "middle_angle", is captured inside one of the shadow angles in "shadows". If - * so, set hex in "fov_map" indexed by "pos_in_map" to 'H'. If the whole angle - * and not just "middle_angle" is captured, return 1. Any other case: 0. - */ -static uint8_t shade_hex(uint32_t left_angle, uint32_t right_angle, - uint32_t middle_angle, struct shadow_angle ** shadows, - uint16_t pos_in_map, char * fov_map); - -/* To "shadows", add shadow defined by "left_angle" and "right_angle", either as - * new entry or as part of an existing shadow (swallowed whole or extending it). - */ -static void set_shadow(uint32_t left_angle, uint32_t right_angle, - struct shadow_angle ** shadows); - -/* Free shadow angles list "angles". */ -static void free_angles(struct shadow_angle * angles); - -/* Evaluate map position "test_pos" in distance "dist" to the view origin, and - * on the circle of that distance to the origin on hex "hex_i" (as counted from - * the circle's rightmost point), for setting shaded hexes in "fov_map" and - * potentially adding a new shadow to linked shadow angle list "shadows". - */ -static void eval_position(uint16_t dist, uint16_t hex_i, char * fov_map, - struct yx_uint8 * test_pos, - struct shadow_angle ** shadows); - -/* Update "t_eye"'s things-on-map memory by removing from its .t_mem all - * memorized thing in FOV, and adding inanimate things in FOV to it. - */ -static void add_things_to_map_memory(struct Thing * t_eye); - - - -static uint32_t correct_angle(int32_t angle) -{ - while (angle < 0) - { - angle = angle + CIRCLE; - } - while (angle > CIRCLE) - { - angle = angle - CIRCLE; - } - return angle; -} - - - -static uint8_t try_merge(struct shadow_angle * shadow, - uint32_t left_angle, uint32_t right_angle) -{ - if ( shadow->right_angle <= left_angle + 1 - && shadow->right_angle >= right_angle) - { - shadow->right_angle = right_angle; - } - else if ( shadow->left_angle + 1 >= right_angle - && shadow->left_angle <= left_angle) - { - shadow->left_angle = left_angle; - } - else - { - return 0; - } - return 1; -} - - - -static uint8_t try_merging_angles(uint32_t left_angle, uint32_t right_angle, - struct shadow_angle ** shadows) -{ - uint8_t angle_merge = 0; - struct shadow_angle * shadow; - for (shadow = *shadows; shadow; shadow = shadow->next) - { - if (try_merge(shadow, left_angle, right_angle)) - { - angle_merge = 1; - } - } - if (angle_merge) - { - struct shadow_angle * shadow1; - for (shadow1 = *shadows; shadow1; shadow1 = shadow1->next) - { - struct shadow_angle * last_shadow = NULL; - struct shadow_angle * shadow2; - for (shadow2 = *shadows; shadow2; shadow2 = shadow2->next) - { - if ( shadow1 != shadow2 - && try_merge(shadow1, shadow2->left_angle, - shadow2->right_angle)) - { - struct shadow_angle * to_free = shadow2; - if (last_shadow) - { - last_shadow->next = shadow2->next; - shadow2 = last_shadow; - } - else - { - *shadows = shadow2->next; - shadow2 = *shadows; - } - free(to_free); - } - last_shadow = shadow2; - } - } - } - return angle_merge; -} - - - -static uint8_t shade_hex(uint32_t left_angle, uint32_t right_angle, - uint32_t middle_angle, struct shadow_angle ** shadows, - uint16_t pos_in_map, char * fov_map) -{ - struct shadow_angle * shadow_i; - if (fov_map[pos_in_map] == 'v') - { - for (shadow_i = *shadows; shadow_i; shadow_i = shadow_i->next) - { - if ( left_angle <= shadow_i->left_angle - && right_angle >= shadow_i->right_angle) - { - fov_map[pos_in_map] = 'H'; - return 1; - } - if ( middle_angle < shadow_i->left_angle - && middle_angle > shadow_i->right_angle) - { - fov_map[pos_in_map] = 'H'; - } - } - } - return 0; -} - - - -static void set_shadow(uint32_t left_angle, uint32_t right_angle, - struct shadow_angle ** shadows) -{ - struct shadow_angle * shadow_i; - if (!try_merging_angles(left_angle, right_angle, shadows)) - { - struct shadow_angle * shadow; - shadow = try_malloc(sizeof(struct shadow_angle), __func__); - shadow->left_angle = left_angle; - shadow->right_angle = right_angle; - shadow->next = NULL; - if (*shadows) - { - for (shadow_i = *shadows; shadow_i; shadow_i = shadow_i->next) - { - if (!shadow_i->next) - { - shadow_i->next = shadow; - return; - } - } - } - *shadows = shadow; - } -} - - - -static void free_angles(struct shadow_angle * angles) -{ - if (angles->next) - { - free_angles(angles->next); - } - free(angles); -} - - - -static void eval_position(uint16_t dist, uint16_t hex_i, char * fov_map, - struct yx_uint8 * test_pos, - struct shadow_angle ** shadows) -{ - int32_t left_angle_uncorrected = ((CIRCLE / 12) / dist) - - (hex_i * (CIRCLE / 6) / dist); - int32_t right_angle_uncorrected = left_angle_uncorrected - - (CIRCLE / (6 * dist)); - uint32_t left_angle = correct_angle(left_angle_uncorrected); - uint32_t right_angle = correct_angle(right_angle_uncorrected); - uint32_t right_angle_1st = right_angle > left_angle ? 0 : right_angle; - uint32_t middle_angle = 0; - if (right_angle_1st) - { - middle_angle = right_angle + ((left_angle - right_angle) / 2); - } - uint16_t pos_in_map = test_pos->y * world.map.length + test_pos->x; - uint8_t all_shaded = shade_hex(left_angle, right_angle_1st, middle_angle, - shadows, pos_in_map, fov_map); - if (!all_shaded && 'X' == world.map.cells[pos_in_map]) - { - set_shadow(left_angle, right_angle_1st, shadows); - if (right_angle_1st != right_angle) - { - left_angle = CIRCLE; - set_shadow(left_angle, right_angle, shadows); - } - } -} - - - -static void add_things_to_map_memory(struct Thing * t_eye) -{ - struct ThingInMemory * tm = t_eye->t_mem; - struct ThingInMemory * tm_prev = NULL; - struct ThingInMemory * tm_next = NULL; - for (; tm; tm = tm_next) - { - tm_next = tm->next; - if ('v' == t_eye->fov_map[tm->pos.y * world.map.length + tm->pos.x]) - { - if (tm_prev) - { - tm_prev->next = tm->next; - } - else - { - t_eye->t_mem = tm->next; - } - free(tm); - continue; - } - tm_prev = tm; - } - struct Thing * t = world.things; - for (; t; t = t->next) - { - if ( !t->lifepoints - && 'v' == t_eye->fov_map[t->pos.y * world.map.length + t->pos.x]) - { - add_thing_to_memory_map(t_eye, t->type, t->pos.y, t->pos.x); - } - } -} - - - -extern void update_map_memory(struct Thing * t_eye, uint8_t age_map) -{ - if (!t_eye->mem_map) - { - init_empty_map(&(t_eye->mem_map)); - } - if (!t_eye->mem_depth_map) - { - init_empty_map(&(t_eye->mem_depth_map)); - } - uint32_t i; - for (i = 0; i < (uint32_t) (world.map.length * world.map.length); i++) - { - if ('v' == t_eye->fov_map[i]) - { - t_eye->mem_depth_map[i] = '0'; - if (' ' == t_eye->mem_map[i]) - { - t_eye->mem_map[i] = world.map.cells[i]; - } - continue; - } - if (age_map && - '0' <= t_eye->mem_depth_map[i] && '9' > t_eye->mem_depth_map[i] - && !(rrand() % (uint16_t) pow(2, t_eye->mem_depth_map[i] - 48))) - { - t_eye->mem_depth_map[i]++; - } - } - add_things_to_map_memory(t_eye); -} - - - -extern void build_fov_map(struct Thing * t) -{ - uint32_t map_size = world.map.length * world.map.length; - t->fov_map = t->fov_map ? t->fov_map : try_malloc(map_size, __func__); - memset(t->fov_map, 'v', map_size); - struct shadow_angle * shadows = NULL; - struct yx_uint8 test_pos = t->pos; - char * circledirs_string = "xswedc"; - uint16_t circle_i; - uint8_t circle_is_on_map; - for (circle_i = 1, circle_is_on_map = 1; circle_is_on_map; circle_i++) - { - circle_is_on_map = 0; - if (1 < circle_i) /* All circles but the 1st are */ - { /* moved into starting from a */ - mv_yx_in_dir_legal('c', &test_pos);/* previous circle's last hex, */ - } /* i.e. from the upper left. */ - char dir_char = 'd'; /* Circle's 1st hex is entered by rightward move.*/ - uint8_t dir_char_pos_in_circledirs_string = UINT8_MAX; - uint16_t dist_i, hex_i; - for (hex_i=0, dist_i=circle_i; hex_i < 6 * circle_i; dist_i++, hex_i++) - { - if (circle_i < dist_i) - { - dist_i = 1; - dir_char=circledirs_string[++dir_char_pos_in_circledirs_string]; - } - if (mv_yx_in_dir_legal(dir_char, &test_pos)) - { - eval_position(circle_i, hex_i, t->fov_map, &test_pos, &shadows); - circle_is_on_map = 1; - } - } - } - mv_yx_in_dir_legal(0, NULL); - free_angles(shadows); -} diff --git a/src/server/field_of_view.h b/src/server/field_of_view.h deleted file mode 100644 index be6324b..0000000 --- a/src/server/field_of_view.h +++ /dev/null @@ -1,28 +0,0 @@ -/* src/server/field_of_view.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Generate field of view maps. - */ - -#ifndef FIELD_OF_VIEW_H -#define FIELD_OF_VIEW_H - -#include /* uint8_t, uint32_t */ -struct Thing; - - - -/* Update "t_eye"'s .mem_map memory with what's in its current FOV, and update - * and age the .mem_depth_map. - */ -extern void update_map_memory(struct Thing * t_eye, uint8_t age_map); - -/* Build "t"'s field of view. */ -extern void build_fov_map(struct Thing * t); - - - -#endif diff --git a/src/server/god_commands.c b/src/server/god_commands.c deleted file mode 100644 index 0dbf1a3..0000000 --- a/src/server/god_commands.c +++ /dev/null @@ -1,524 +0,0 @@ -/* src/server/god_commands.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "god_commands.h" -#include /* NULL */ -#include /* uint8_t */ -#include /* atoi(), free() */ -#include /* strcmp(), memset(), memcpy() */ -#include /* F_OK, access(), unlink() */ -#include "../common/parse_file.h" /* err_line(), parse_val(), parsetest_int() */ -#include "../common/rexit.h" /* exit_trouble() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "cleanup.h" /* unset_cleanup_flag() */ -#include "field_of_view.h" /* build_fov_map(), update_map_memory() */ -#include "hardcoded_strings.h" /* s */ -#include "init.h" /* remake_world() */ -#include "map.h" /* init_empty_map(), remake_map() */ -#include "thing_actions.h" /* ThingAction, actor_wait(), actor_move(), - * actor_use(), actor_pickup(), actor_drop() - */ -#include "things.h" /* Thing, ThingType, add_thing(), get_thing(), own_thing(), - * free_things(), add_thing_to_memory_map(),get_thing_type(), - * get_player() - */ -#include "world.h" /* world */ - - - -/* Parse/apply god command in "tok0"/tok1" to manipulate a ThingType*/ -static uint8_t parse_thingtype_manipulation(char * tok0, char * tok1); - -/* If "name" fits "ta"->name, set "ta"->func to "func". (Derives ThingAction - * .func from .name for set_members(). - */ -static uint8_t try_func_name(struct ThingAction * ta, char * name, - void (* func) (struct Thing *)); - -/* Parse/apply god command in "tok0"/"tok1" to manipulate a ThingAction. */ -static uint8_t parse_thingaction_manipulation(char * tok0, char * tok1); - -/* 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 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" on "t" owning another thing. */ -static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t); - -/* Parse/apply god command in "tok0"/"tok1" to manipulate a Thing. */ -static uint8_t parse_thing_manipulation_1arg(char * tok0, char * tok1); - -/* Performs parse_world_active()'s world activation legality tests. */ -static uint8_t world_may_be_set_active(); - -/* Unlink worldstate file if it exists. */ -static void remove_worldstate_file(); - -/* Parse/apply god command in "tok0"/"tok1" on toggling world.exists. Unset if - * argument is 0 and unlink worldstate file, but only set it on positive - * argument if it is not already set and a thing action of name S_CMD_WAIT, a - * player thing and a map are defined. On setting it, rebuild all FOVs. - */ -static uint8_t parse_world_active(char * tok0, char * tok1); - -/* Parse/apply god command in "tok0"/"tok1" to reset world.map.length. On - * re-set, set world.exists to 0, remove all things and free world.map.cells - */ -static uint8_t set_map_length(char * tok0, char * tok1); - - - -/* Thing, ThingType or ThingAction selected to be manipulated. */ -static struct Thing * t = NULL; -static struct ThingType * tt = NULL; -static struct ThingAction * ta = NULL; - - - -static uint8_t parse_thingtype_manipulation(char * tok0, char * tok1) -{ - if (!tt && - ( !strcmp(tok0, s[S_CMD_TT_CONSUM]) || !strcmp(tok0, s[S_CMD_TT_SYMB]) - || !strcmp(tok0, s[S_CMD_TT_STARTN]) || !strcmp(tok0, s[S_CMD_TT_NAME]) - || !strcmp(tok0, s[S_CMD_TT_CORPS]) || !strcmp(tok0, s[S_CMD_TT_HP]) - || !strcmp(tok0, s[S_CMD_TT_PROL]))) - { - return err_line(1, "No thing type defined to manipulate yet."); - } - int16_t id; - if ( parse_val(tok0,tok1,s[S_CMD_TT_CONSUM],'u',(char *) &tt->consumable) - || parse_val(tok0,tok1,s[S_CMD_TT_HP],'8',(char *) &tt->lifepoints) - || parse_val(tok0,tok1,s[S_CMD_TT_STARTN],'8',(char *) &tt->start_n) - || parse_val(tok0,tok1,s[S_CMD_TT_SYMB],'c',(char *) &tt->char_on_map) - || parse_val(tok0,tok1,s[S_CMD_TT_PROL],'8',(char *) &tt->proliferate) - || parse_val(tok0,tok1,s[S_CMD_TT_NAME],'s',(char *) &tt->name)) - { - ; - } - else if (parse_val(tok0, tok1, s[S_CMD_TT_CORPS],'8',(char *)&id)) - { - if (!get_thing_type(id)) - { - return err_line(1, "Corpse ID belongs to no known thing type."); - } - tt->corpse_id = id; - } - else if (parse_val(tok0, tok1, s[S_CMD_TT_ID], 'i', (char *) &id)) - { - tt = get_thing_type(id); - if (!tt) - { - tt = add_thing_type(id); - } - } - else - { - return 0; - } - return 1; -} - - - -static uint8_t try_func_name(struct ThingAction * ta, char * name, - void (* func) (struct Thing *)) -{ - if (0 == strcmp(ta->name, name)) - { - ta->func = func; - return 1; - } - return 0; -} - - - -static uint8_t parse_thingaction_manipulation(char * tok0, char * tok1) -{ - if (!ta && - (!strcmp(tok0, s[S_CMD_TA_EFFORT]) || !strcmp(tok0, s[S_CMD_TA_NAME]))) - { - return err_line(1, "No thing action defined to manipulate yet."); - } - int16_t id; - if (parse_val(tok0, tok1, s[S_CMD_TA_EFFORT],'8',(char *)&ta->effort)); - else if (parse_val(tok0, tok1, s[S_CMD_TA_NAME], 's', (char *)&ta->name)) - { - if (!( try_func_name(ta, s[S_CMD_MOVE], actor_move) - || try_func_name(ta, s[S_CMD_PICKUP], actor_pick) - || try_func_name(ta, s[S_CMD_WAIT], actor_wait) - || try_func_name(ta, s[S_CMD_DROP], actor_drop) - || try_func_name(ta, s[S_CMD_USE], actor_use))) - { - return err_line(1, "Invalid action function name."); - } - if (world.exists) - { /* Legal worlds have at least one thing action for waiting. */ - world.exists = 0 != get_thing_action_id_by_name(s[S_CMD_WAIT]); - if (!world.exists) - { - remove_worldstate_file(); - } - } - } - else if (parse_val(tok0, tok1, s[S_CMD_TA_ID], '8', (char *) &id)) - { - ta = get_thing_action(id); - if (!ta) - { - ta = add_thing_action(id); - } - } - else - { - return 0; - } - return 1; -} - - - -static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t) -{ - uint8_t type; - if (parse_val(tok0, tok1, s[S_CMD_T_TYPE], '8', (char *) &type)) - { - struct ThingType * tt = get_thing_type(type); - 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[S_CMD_T_COMMAND], '8', (char *) &command)) - { - if (!command) - { - t->command = command; - return 1; - } - struct ThingAction * ta = world.thing_actions; - for (; 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_position(char* tok0, char * tok1, struct Thing * t) -{ - char axis = 0; - if (!strcmp(tok0, s[S_CMD_T_POSY])) - { - axis = 'y'; - } - else if (!strcmp(tok0, s[S_CMD_T_POSX])) - { - 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; - } - if (world.exists && t->lifepoints) - { - build_fov_map(t); - if (t == get_player()) - { - update_map_memory(t, 1); - } - } - } - return 1; - } - return 0; -} - - - -static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t) -{ - uint8_t id; - if (parse_val(tok0, tok1, s[S_CMD_T_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 not available for carrying.")) - { - own_thing(&(t->owns), &world.things, id); - o->pos = t->pos; - } - } - return 1; - } - return 0; -} - - - -static uint8_t parse_thing_manipulation_1arg(char * tok0, char * tok1) -{ - if (!t && - ( !strcmp(tok0, s[S_CMD_T_PROGRESS]) || !strcmp(tok0, s[S_CMD_T_TYPE]) - || !strcmp(tok0, s[S_CMD_T_CARRIES]) || !strcmp(tok0, s[S_CMD_T_POSY]) - || !strcmp(tok0, s[S_CMD_T_POSY]) || !strcmp(tok0, s[S_CMD_T_ARGUMENT]) - || !strcmp(tok0, s[S_CMD_T_HP]) || !strcmp(tok0, s[S_CMD_T_COMMAND]) - || !strcmp(tok0, s[S_CMD_T_SATIATION]))) - { - return err_line(1, "No thing defined to manipulate yet."); - } - int16_t id; - if ( parse_thing_type(tok0, tok1, t) - || parse_thing_command(tok0, tok1, t) - || parse_val(tok0,tok1, s[S_CMD_T_ARGUMENT], '8', (char *)&t->arg) - || parse_val(tok0,tok1, s[S_CMD_T_PROGRESS], '8', (char *)&t->progress) - || parse_val(tok0,tok1, s[S_CMD_T_HP], '8', (char *) &t->lifepoints) - || parse_val(tok0,tok1, s[S_CMD_T_SATIATION], 'i',(char *)&t->satiation) - || parse_position(tok0, tok1, t) - || parse_carry(tok0, tok1, t)); - else if (parse_val(tok0, tok1, s[S_CMD_T_ID], 'i', (char *) &id)) - { - t = get_thing(world.things, id, 1); - char * err = "No thing type found to initialize new thing."; - if (!t && !err_line(!world.thing_types, err)) - { - t = add_thing(id, world.thing_types->id, 0, 0); - } - } - else - { - return 0; - } - return 1; -} - - - -static uint8_t world_may_be_set_active() -{ - if (!get_thing_action_id_by_name(s[S_CMD_WAIT])) - { - err_line(1, "No thing action of name 'wait' found."); - return 0; - } - if (!get_player()) - { - err_line(1, "No un-owned player thing (of id=0) found."); - return 0; - } - if (!world.map.cells) - { - err_line(1, "No map found."); - return 0; - } - return 1; -} - - - -static void remove_worldstate_file() -{ - if (!access(s[S_PATH_WORLDSTATE], F_OK)) - { - int test = unlink(s[S_PATH_WORLDSTATE]); - exit_trouble(-1 == test, __func__, "unlink"); - } -} - - - -static uint8_t parse_world_active(char * tok0, char * tok1) -{ - if (!strcmp(tok0, s[S_CMD_WORLD_ACTIVE]) && !parsetest_int(tok1, '8')) - { - if (!parsetest_int(tok1, '8')) - { - uint8_t argument = atoi(tok1); - if (!argument) - { - remove_worldstate_file(); - world.exists = 0; - } - else if (world.exists) - { - err_line(1, "World already active."); - } - else if (world_may_be_set_active()) - { - struct Thing * ti; - for (ti = world.things; ti; ti = ti->next) - { - if (ti->lifepoints) - { - build_fov_map(ti); - if (ti == get_player()) - { - update_map_memory(ti, 0); - } - } - } - world.exists = 1; - } - return 1; - } - } - return 0; -} - - - -static uint8_t set_map_length(char * tok0, char * tok1) -{ - if (!strcmp(tok0, s[S_CMD_MAPLENGTH]) && !parsetest_int(tok1, 'u')) - { - uint16_t argument = atoi(tok1); - if (argument < 1 || argument > 256) - { - return err_line(1, "Value must be >= 1 and <= 256."); - } - world.exists = 0; - remove_worldstate_file(); - free_things(world.things); - free(world.map.cells); - world.map.cells = NULL; /* Since remake_map() runs free() on this. */ - world.map.length = argument; - return 1; - } - return 0; -} - - - -extern uint8_t parse_god_command_1arg(char * tok0, char * tok1) -{ - if ( parse_thingtype_manipulation(tok0, tok1) - || parse_thingaction_manipulation(tok0, tok1) - || parse_thing_manipulation_1arg(tok0, tok1) - || set_map_length(tok0,tok1) - || parse_val(tok0,tok1,s[S_CMD_SEED_RAND],'U', (char *)&world.seed) - || parse_val(tok0,tok1,s[S_CMD_TURN],'u',(char *)&world.turn) - || parse_val(tok0,tok1,s[S_CMD_PLAYTYPE],'8',(char *)&world.player_type) - || parse_world_active(tok0, tok1)); - else if (parse_val(tok0,tok1,s[S_CMD_SEED_MAP],'U',(char *)&world.seed_map)) - - { - remake_map(); - } - else if (parse_val(tok0, tok1, s[S_CMD_MAKE_WORLD],'U',(char *)&world.seed)) - { - uint8_t test = remake_world(); - err_line(1 == test, "No player type with start number of >0 defined."); - err_line(2 == test, "No thing action with name 'wait' defined."); - } - else - { - return 0; - } - return 1; -} - - - -extern uint8_t parse_god_command_2arg(char * tok0, char * tok1, char * tok2) -{ - if (!t && ( !strcmp(tok0, s[S_CMD_T_MEMMAP]) - || !strcmp(tok0, s[S_CMD_T_MEMDEPTHMAP]))) - { - return err_line(1, "No thing defined to manipulate yet."); - } - if (!strcmp(tok0,s[S_CMD_T_MEMMAP]) || !strcmp(tok0,s[S_CMD_T_MEMDEPTHMAP])) - { - uint8_t y = atoi(tok1); - if (parsetest_int(tok1, '8') || y >= world.map.length) - { - return err_line(1, "Illegal value for map line number."); - } - if (strlen(tok2) != world.map.length) - { - return err_line(1, "Map line length is unequal map width."); - } - if (!strcmp(tok0,s[S_CMD_T_MEMMAP])) - { - if (!t->mem_map) - { - init_empty_map(&(t->mem_map)); - } - memcpy(t->mem_map + y * world.map.length, tok2, world.map.length); - } - else - { - if (!t->mem_depth_map) - { - init_empty_map(&(t->mem_depth_map)); - } - memcpy(t->mem_depth_map+y*world.map.length, tok2, world.map.length); - } - } - else - { - return 0; - } - return 1; -} - - - -extern uint8_t parse_god_command_3arg(char * tok0, char * tok1, char * tok2, - char * tok3) -{ - if (!t && !strcmp(tok0, s[S_CMD_T_MEMTHING])) - { - return err_line(1, "No thing defined to manipulate yet."); - } - if (!strcmp(tok0, s[S_CMD_T_MEMTHING])) - { - uint8_t id = atoi(tok1); - uint8_t y = atoi(tok2); - uint8_t x = atoi(tok3); - if ( parsetest_int(tok1, '8') || !get_thing_type(id) - || parsetest_int(tok2, '8') || y >= world.map.length - || parsetest_int(tok3, '8') || x >= world.map.length) - { - return err_line(1, "Illegal value for thing type or position."); - } - add_thing_to_memory_map(t, id, y, x); - } - else - { - return 0; - } - return 1; -} diff --git a/src/server/god_commands.h b/src/server/god_commands.h deleted file mode 100644 index bfe7454..0000000 --- a/src/server/god_commands.h +++ /dev/null @@ -1,25 +0,0 @@ -/* src/server/god_commands.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * God commands and their interpretation by the server. - */ - -#ifndef GOD_COMMANDS_H -#define GOD_COMMANDS_H - -#include /* uint8_t */ - - - -/* Parse/apply god command "tok0" with argument "tok1", "tok2" etc. . */ -extern uint8_t parse_god_command_1arg(char * tok0, char * tok1); -extern uint8_t parse_god_command_2arg(char * tok0, char * tok1, char * tok2); -extern uint8_t parse_god_command_3arg(char * tok0, char * tok1, char * tok2, - char * tok3); - - - -#endif diff --git a/src/server/hardcoded_strings.c b/src/server/hardcoded_strings.c deleted file mode 100644 index dfb30e1..0000000 --- a/src/server/hardcoded_strings.c +++ /dev/null @@ -1,62 +0,0 @@ -/* hardcoded_strings.c * - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "hardcoded_strings.h" - - - -char * s[44]; - - - -extern void init_strings() -{ - s[S_PATH_CONFIG] = "confserver/world"; - s[S_PATH_WORLDSTATE] = "server/worldstate"; - s[S_PATH_OUT] = "server/out"; - s[S_PATH_IN] = "server/in"; - s[S_PATH_RECORD] = "record"; - s[S_PATH_SAVE] = "savefile"; - s[S_FCN_SPRINTF] = "sprintf"; - s[S_CMD_TA_ID] = "TA_ID"; - s[S_CMD_TA_EFFORT] = "TA_EFFORT"; - s[S_CMD_TA_NAME] = "TA_NAME"; - s[S_CMD_TT_ID] = "TT_ID"; - s[S_CMD_TT_CONSUM] = "TT_CONSUMABLE"; - s[S_CMD_TT_STARTN] = "TT_START_NUMBER"; - s[S_CMD_TT_HP] = "TT_LIFEPOINTS"; - s[S_CMD_TT_SYMB] = "TT_SYMBOL"; - s[S_CMD_TT_NAME] = "TT_NAME"; - s[S_CMD_TT_CORPS] = "TT_CORPSE_ID"; - s[S_CMD_TT_PROL] = "TT_PROLIFERATE"; - s[S_CMD_T_ID] = "T_ID"; - s[S_CMD_T_TYPE] = "T_TYPE"; - s[S_CMD_T_POSY] = "T_POSY"; - s[S_CMD_T_POSX] = "T_POSX"; - s[S_CMD_T_COMMAND] = "T_COMMAND"; - s[S_CMD_T_ARGUMENT] = "T_ARGUMENT"; - s[S_CMD_T_PROGRESS] = "T_PROGRESS"; - s[S_CMD_T_HP] = "T_LIFEPOINTS"; - s[S_CMD_T_SATIATION] = "T_SATIATION"; - s[S_CMD_T_CARRIES] = "T_CARRIES"; - s[S_CMD_T_MEMMAP] = "T_MEMMAP"; - s[S_CMD_T_MEMDEPTHMAP] = "T_MEMDEPTHMAP"; - s[S_CMD_T_MEMTHING] = "T_MEMTHING"; - s[S_CMD_AI] = "ai"; - s[S_CMD_WAIT] = "wait"; - s[S_CMD_MOVE] = "move"; - s[S_CMD_PICKUP] = "pick_up"; - s[S_CMD_DROP] = "drop"; - s[S_CMD_USE] = "use"; - s[S_CMD_MAKE_WORLD] = "MAKE_WORLD"; - s[S_CMD_WORLD_ACTIVE] = "WORLD_ACTIVE"; - s[S_CMD_SEED_MAP] = "SEED_MAP"; - s[S_CMD_SEED_RAND] = "SEED_RANDOMNESS"; - s[S_CMD_TURN] = "TURN"; - s[S_CMD_MAPLENGTH] = "MAP_LENGTH"; - s[S_CMD_PLAYTYPE] = "PLAYER_TYPE"; -} diff --git a/src/server/hardcoded_strings.h b/src/server/hardcoded_strings.h deleted file mode 100644 index 4725bc2..0000000 --- a/src/server/hardcoded_strings.h +++ /dev/null @@ -1,69 +0,0 @@ -/* hardcoded_strings.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * For re-used hardcoded strings. - */ - -#ifndef STRINGS_H -#define STRINGS_H - - - -enum string_num -{ - S_PATH_CONFIG, - S_PATH_WORLDSTATE, - S_PATH_OUT, - S_PATH_IN, - S_PATH_RECORD, - S_PATH_SAVE, - S_FCN_SPRINTF, - S_CMD_TA_ID, - S_CMD_TA_EFFORT, - S_CMD_TA_NAME, - S_CMD_TT_ID, - S_CMD_TT_CONSUM, - S_CMD_TT_STARTN, - S_CMD_TT_HP, - S_CMD_TT_SYMB, - S_CMD_TT_NAME, - S_CMD_TT_CORPS, - S_CMD_TT_PROL, - S_CMD_T_ID, - S_CMD_T_TYPE, - S_CMD_T_POSY, - S_CMD_T_POSX, - S_CMD_T_COMMAND, - S_CMD_T_ARGUMENT, - S_CMD_T_PROGRESS, - S_CMD_T_HP, - S_CMD_T_SATIATION, - S_CMD_T_CARRIES, - S_CMD_T_MEMMAP, - S_CMD_T_MEMDEPTHMAP, - S_CMD_T_MEMTHING, - S_CMD_AI, - S_CMD_WAIT, - S_CMD_MOVE, - S_CMD_PICKUP, - S_CMD_DROP, - S_CMD_USE, - S_CMD_MAKE_WORLD, - S_CMD_WORLD_ACTIVE, - S_CMD_SEED_MAP, - S_CMD_SEED_RAND, - S_CMD_TURN, - S_CMD_MAPLENGTH, - S_CMD_PLAYTYPE -}; - -extern void init_strings(); - -extern char * s[44]; - - - -#endif diff --git a/src/server/init.c b/src/server/init.c deleted file mode 100644 index f44c504..0000000 --- a/src/server/init.c +++ /dev/null @@ -1,257 +0,0 @@ -/* src/server/init.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#define _POSIX_C_SOURCE 2 /* getopt(), optarg */ -#include "init.h" -#include /* global errno, EEXIST */ -#include /* NULL */ -#include /* uint32_t */ -#include /* FILE, sprintf(), fflush() */ -#include /* exit(), free(), atoi() */ -#include /* strlen() */ -#include /* mkdir() */ -#include /* defines pid_t, time_t */ -#include /* time() */ -#include /* optarg, getopt(), access(), getpid() */ -#include "../common/parse_file.h" /* err_line_zero(), err_line_inc() */ -#include "../common/readwrite.h" /* try_fopen(), try_fclose(), textfile_width(), - * try_fgets(), try_fwrite(), - * detect_atomic_leftover() - */ -#include "../common/rexit.h" /* exit_err(), exit_trouble() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "cleanup.h" /* set_cleanup_flag() */ -#include "field_of_view.h" /* update_map_memory() */ -#include "hardcoded_strings.h" /* s */ -#include "map.h" /* remake_map() */ -#include "things.h" /* Thing, ThingType, free_things(), add_things(), - * get_thing_id_action_id_by_name(), get_player() - */ -#include "run.h" /* obey_msg(), io_loop(), record(), send_to_outfile() */ -#include "world.h" /* global world */ - - - - -/* Pass to obey_msg() lines from file at "path", on "record" write to same. Do - * not pass lines that consist only of a newline character. Transform newline - * in the line passed to \0. - */ -static void obey_lines_from_file(char * path, uint8_t record); - -/* 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(); - -/* Return 1 if the type defined by world.player_type has a .start_n of 0. - * Return 2 if no thing action with .name of s[S_CMD_WAIT] is defined. - * Else, return 0. - */ -static uint8_t world_cannot_be_made(); - - -static void obey_lines_from_file(char * path, uint8_t record) -{ - FILE * file = try_fopen(path, "r", __func__); - uint32_t linemax = textfile_width(file); - char * line = try_malloc(linemax + 1, __func__); - while (try_fgets(line, linemax + 1, file, __func__)) - { - if (strlen(line)) - { - if (strcmp("\n", line)) - { - char * nl = strchr(line, '\n'); - if (nl) - { - *nl = '\0'; - } - obey_msg(line, record); - } - err_line_inc(); - } - } - free(line); - try_fclose(file, __func__); -} - - - -static void replay_game() -{ - exit_err(access(s[S_PATH_RECORD], F_OK), "No record found to replay."); - FILE * file = try_fopen(s[S_PATH_RECORD], "r", __func__); - uint32_t linemax = textfile_width(file); - char * line = try_malloc(linemax + 1, __func__); - while ( world.turn < world.replay - && try_fgets(line, linemax + 1, file, __func__)) - { - obey_msg(line, 0); - err_line_inc(); - } - uint8_t end = 0; - while (3 == io_loop(2)) - { - if (!end) - { - end = (NULL == try_fgets(line, linemax + 1, file, __func__)); - if (!end) - { - obey_msg(line, 0); - err_line_inc(); - } - } - } - free(line); - try_fclose(file, __func__); -} - - - -static uint8_t world_cannot_be_made() -{ - uint8_t player_will_be_generated = 0; - struct ThingType * tt; - for (tt = world.thing_types; tt; tt = tt->next) - { - if (world.player_type == tt->id) - { - player_will_be_generated = 0 < tt->start_n; - break; - } - } - if (!player_will_be_generated) - { - return 1; - } - if (!get_thing_action_id_by_name(s[S_CMD_WAIT])) - { - return 2; - } - return 0; -} - - - -extern void obey_argv(int argc, char * argv[]) -{ - int opt; - while (-1 != (opt = getopt(argc, argv, "vs::"))) - { - if ('v' == opt) - { - world.is_verbose = 1; - } - else if ('s' == opt) - { - world.replay = 1; - if (optarg) - { - world.replay = atoi(optarg); - } - } - else - { - exit(EXIT_FAILURE); - } - } -} - - - -extern void setup_server_io() -{ - int test = mkdir("server", 0777); - exit_trouble(test && EEXIST != errno, __func__, "mkdir"); - world.file_out = try_fopen(s[S_PATH_OUT], "w", __func__); - world.server_test = try_malloc(10 + 1 + 10 + 1 + 1, __func__); - test = sprintf(world.server_test, "%d %d\n", getpid(), (int) time(0)); - exit_trouble(test < 0, __func__, s[S_FCN_SPRINTF]); - try_fwrite(world.server_test, strlen(world.server_test), 1, - world.file_out, __func__); - fflush(world.file_out); - set_cleanup_flag(CLEANUP_OUT); - char * path_in = s[S_PATH_IN]; - if (!access(path_in, F_OK)) /* This keeps out input from old input */ - { /* file streams of clients */ - unlink(path_in) ; /* communicating with server processes */ - } /* superseded by this current one. */ - world.file_in = try_fopen(path_in, "w", __func__); - try_fclose(world.file_in, __func__); - world.file_in = try_fopen(path_in, "r", __func__); - set_cleanup_flag(CLEANUP_IN); -} - - - -extern uint8_t remake_world() -{ - uint8_t test = world_cannot_be_made(); - if (test) - { - return test; - } - world.seed_map = world.seed; - free_things(world.things); - remake_map(); - world.exists = 1; - struct ThingType * tt; - for (tt = world.thing_types; tt; tt = tt->next) - { - if (world.player_type == tt->id) - { - add_things(tt->id, tt->start_n); - break; - } - } - update_map_memory(get_player(), 1); - for (tt = world.thing_types; tt; tt = tt->next) - { - if (world.player_type != tt->id) - { - add_things(tt->id, tt->start_n); - } - } - world.turn = 1; - send_to_outfile("NEW_WORLD\n", 1); - return 0; -} - - - -extern void run_game() -{ - detect_atomic_leftover(s[S_PATH_SAVE]); - detect_atomic_leftover(s[S_PATH_RECORD]); - err_line_zero(); - if (world.replay) - { - replay_game(); - return; - } - if (!access(s[S_PATH_SAVE], F_OK)) - { - obey_lines_from_file(s[S_PATH_SAVE], 0); - } - else - { - char * err = "No world config file from which to start a new world."; - exit_err(access(s[S_PATH_CONFIG], F_OK), err); - obey_lines_from_file(s[S_PATH_CONFIG], 1); - err_line_zero(); - char * command = s[S_CMD_MAKE_WORLD]; - char * msg = try_malloc(strlen(command) + 1 + 11 + 1, __func__); - int test = sprintf(msg, "%s %d", command, (int) time(NULL)); - exit_trouble(test < 0, __func__, s[S_FCN_SPRINTF]); - obey_msg(msg, 1); - free(msg); - } - err_line_zero(); - io_loop(1); - record(NULL, 1); -} diff --git a/src/server/init.h b/src/server/init.h deleted file mode 100644 index 6161200..0000000 --- a/src/server/init.h +++ /dev/null @@ -1,46 +0,0 @@ -/* src/server/init.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Server, world and game state initialization. - */ - -#ifndef INIT_H -#define INIT_H - -#include /* uint8_t */ - - - -/* Parses command line arguments -v and -s into server configuration. */ -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, generates a new one from world.seed. - * The map is populated according to world.thing_types start numbers. world.turn - * is set to 1, as is .exists and .do_update, so that io_round() is told to - * update the worldstate file. Returns 0 on success, and if the world cannot be - * generated 1 since there is no player type or it has .n_start of 0, 2 if no - * "wait" thing action is defined. - */ -extern uint8_t remake_world(); - -/* Create a game world state, then enter play or replay mode. - * - * 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 first - * following the commands from the world config file, then running the - * MAKE_WORLD command. Manual replay as well as manual play mode take place - * inside io_loop(). - */ -extern void run_game(); - - - -#endif diff --git a/src/server/io.c b/src/server/io.c deleted file mode 100644 index 06ee838..0000000 --- a/src/server/io.c +++ /dev/null @@ -1,441 +0,0 @@ -/* src/server/io.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#define _POSIX_C_SOURCE 200112L /* snrpintf() */ -#include "io.h" -#include /* global errno */ -#include /* PIPE_BUF */ -#include /* NULL */ -#include /* uint8_t, uint16_t, uint32_t, int32_t, UINT8_MAX */ -#include /* defines FILE, sprintf(), fprintf() */ -#include /* free() */ -#include /* strlen(), snprintf(), memcpy(), strchr() */ -#include /* time_t */ -#include /* time(), nanosleep() */ -#include "../common/readwrite.h" /* atomic_write_start(), atomic_write_finish(), - * get_message_from_queue(), try_fwrite(), - * read_file_into_queue(), try_fputc() - */ -#include "../common/rexit.h" /* exit_trouble() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "cleanup.h" /* set_cleanup_flag() */ -#include "hardcoded_strings.h" /* s */ -#include "map.h" /* init_empty_map() */ -#include "run.h" /* send_to_outfile() */ -#include "things.h" /* Thing, ThingType, ThingInMemory, ThingAction, - * get_thing_type(), get_player() - */ -#include "world.h" /* global world */ - - - -/* Helpers to write lines of god commands to recreate thing "t". */ -static void write_key_space(FILE * file, char * key); -static void write_uvalue(FILE * file, uint32_t value); -static void write_string(FILE * file, char * string); -static void write_key_space_uvalue(FILE * file, char * key, uint32_t value); -static void write_key_space_svalue(FILE * file, char * key, int32_t value); -static void write_key_space_string(FILE * file, char * key, char * string); - -/* Write to "file" game-map-sized "map" in "command"-prefixed numbered lines. */ -static void write_mem_map(FILE * file, char * map, char * command); - -/* Write to "file" \n-delimited line of "key" + space + "value" as string. */ -static void write_thing(FILE * file, struct Thing * t); - -/* Poll input file for world.queue input. Wait a few seconds until giving up; - * poll only every 0.03 seconds. Translate '\n' chars in input file into '\0'. - */ -static void try_growing_queue(); - -/* Write world state as visible to clients to its file. Write single dot line to - * server output file to satisfy client ping mechanisms. - */ -static void update_worldstate_file(); - -/* Write "value" to new \n-delimited line of "file". */ -static void write_value_as_line(int32_t value, FILE * file); - -/* Write to "file" player's inventory, one item name per line. End in "%\n". */ -static void write_inventory(struct Thing * player, FILE * file); - -/* Return map cells sequence as visible to the "player", with invisible cells as - * whitespace. Super-impose over visible map cells things positioned there, - * with animate things overwriting inanimate things, and inanimate consumable - * things overwriting inanimate non-consumable things. - */ -static char * build_visible_map(struct Thing * player); - -/* Write to "file" game map as visible to "player" right now, as drawn by - * build_visible_map(), and thereafter game map as memorized by player in its - * .mem_map and .t_mem. Write one row per \n-delimited line. - */ -static void write_map(struct Thing * player, FILE * file); - - - -static void write_key_space(FILE * file, char * key) -{ - try_fwrite(key, strlen(key), 1, file, __func__); - try_fputc(' ', file, __func__); -} - - - -static void write_uvalue(FILE * file, uint32_t value) -{ - char * line = try_malloc(11, __func__); - exit_trouble(-1 == sprintf(line, "%u", value), __func__, s[S_FCN_SPRINTF]); - try_fwrite(line, strlen(line), 1, file, __func__); - free(line); -} - - - -static void write_string(FILE * file, char * string) -{ - try_fputc('\'', file, __func__); - try_fwrite(string, strlen(string), 1, file, __func__); - try_fputc('\'', file, __func__); -} - - - -static void write_key_space_uvalue(FILE * file, char * key, uint32_t value) -{ - write_key_space(file, key); - write_uvalue(file, value); - try_fputc('\n', file, __func__); -} - - - -static void write_key_space_svalue(FILE * file, char * key, int32_t value) -{ - write_key_space(file, key); - char * line = try_malloc(11, __func__); - exit_trouble(-1 == sprintf(line, "%d", value), __func__, s[S_FCN_SPRINTF]); - try_fwrite(line, strlen(line), 1, file, __func__); - free(line); - try_fputc('\n', file, __func__); -} - - - -static void write_key_space_string(FILE * file, char * key, char * string) -{ - write_key_space(file, key); - write_string(file, string); - try_fputc('\n', file, __func__); -} - - - -static void write_mem_map(FILE * file, char * map, char * command) -{ - if (map) - { - uint32_t map_size = world.map.length * world.map.length;/* snprintf() */ - char * map_copy = try_malloc(map_size + 1, __func__); /* reads one */ - memcpy(map_copy, map, map_size); /* byte beyond map_size */ - map_copy[map_size] = '\0'; /* if string is not \0-terminated. */ - uint16_t y; - char string[UINT8_MAX + 1 + 1]; - for (y = 0; y < world.map.length; y++) - { - int test = snprintf(string, world.map.length + 1, "%s", - map_copy + (y * world.map.length)); - exit_trouble(test < 0, __func__, "snprintf()"); - write_key_space(file, command); - write_uvalue(file, y); - try_fputc(' ', file, __func__); - write_string(file, string); - try_fputc('\n', file, __func__); - } - free(map_copy); - } -} - - - -static void write_thing(FILE * file, struct Thing * t) -{ - struct Thing * o; - for (o = t->owns; o; o = o->next) - { - write_thing(file, o); - } - write_key_space_uvalue(file, s[S_CMD_T_ID], t->id); - struct ThingInMemory * tm = t->t_mem; - for (; tm; tm = tm->next) - { - write_key_space(file, s[S_CMD_T_MEMTHING]); - write_uvalue(file, tm->type); - try_fputc(' ', file, __func__); - write_uvalue(file, tm->pos.y); - try_fputc(' ', file, __func__); - write_uvalue(file, tm->pos.x); - try_fputc('\n', file, __func__); - } - write_key_space_uvalue(file, s[S_CMD_T_COMMAND], t->command); - write_key_space_uvalue(file, s[S_CMD_T_HP], t->lifepoints); - write_key_space_uvalue(file, s[S_CMD_T_TYPE], t->type); - write_key_space_uvalue(file, s[S_CMD_T_ARGUMENT], t->arg); - write_key_space_uvalue(file, s[S_CMD_T_POSY], t->pos.y); - write_key_space_uvalue(file, s[S_CMD_T_POSX], t->pos.x); - write_key_space_uvalue(file, s[S_CMD_T_PROGRESS], t->progress); - write_mem_map(file, t->mem_depth_map, s[S_CMD_T_MEMDEPTHMAP]); - write_key_space_svalue(file, s[S_CMD_T_SATIATION], t->satiation); - write_mem_map(file, t->mem_map, s[S_CMD_T_MEMMAP]); -} - - - -static void write_thing_carrying(FILE * file, struct Thing * t) -{ - if (t->owns) - { - write_key_space_uvalue(file, s[S_CMD_T_ID], t->id); - struct Thing * o; - for (o = t->owns; o; o = o->next) - { - write_key_space_uvalue(file, s[S_CMD_T_CARRIES], o->id); - } - } -} - - - -static void try_growing_queue() -{ - uint8_t wait_seconds = 5; - time_t now = time(0); - struct timespec dur; - dur.tv_sec = 0; - dur.tv_nsec = 33333333; - while (1) - { - if (read_file_into_queue(world.file_in, &world.queue)) - { - return; - } - nanosleep(&dur, NULL); - if (time(0) > now + wait_seconds) - { - return; - } - } -} - - - -static void update_worldstate_file() -{ - char * path_tmp; - FILE * file = atomic_write_start(s[S_PATH_WORLDSTATE], &path_tmp); - struct Thing * player = get_player(); - write_value_as_line(world.turn, file); - write_value_as_line(player->lifepoints, file); - write_value_as_line(player->satiation, file); - write_inventory(player, file); - write_value_as_line(player->pos.y, file); - write_value_as_line(player->pos.x, file); - write_value_as_line(world.map.length, file); - write_map(player, file); - atomic_write_finish(file, s[S_PATH_WORLDSTATE], path_tmp); - set_cleanup_flag(CLEANUP_WORLDSTATE); - char * dot = ".\n"; - try_fwrite(dot, strlen(dot), 1, world.file_out, __func__); - fflush(world.file_out); -} - - - -static void write_value_as_line(int32_t value, FILE * file) -{ - char write_buf[13]; /* Hold "+"/"-" + 10 digits of int32_t max + \n + \0. */ - exit_trouble(sprintf(write_buf,"%u\n",value) < 0,__func__,s[S_FCN_SPRINTF]); - try_fwrite(write_buf, strlen(write_buf), 1, file, __func__); -} - - - -static void write_inventory(struct Thing * player, FILE * file) -{ - struct Thing * owned = player->owns; - if (!owned) - { - char * empty = "(none)\n"; - try_fwrite(empty, strlen(empty), 1, file, __func__); - } - else - { - uint8_t q; - for (q = 0; owned; q++) - { - struct ThingType * tt = get_thing_type(owned->type); - try_fwrite(tt->name, strlen(tt->name), 1, file, __func__); - try_fputc('\n', file, __func__); - owned = owned->next; - } - } - try_fputc('%', file, __func__); - try_fputc('\n', file, __func__); -} - - - -static char * build_visible_map(struct Thing * player) -{ - char * visible_map; - init_empty_map(&visible_map); - if (player->fov_map) /* May fail if player thing was created / positioned */ - { /* by god command after turning off FOV building. */ - uint32_t pos_i = 0; - for (; pos_i < (uint32_t) world.map.length * world.map.length; pos_i++) - { - if (player->fov_map[pos_i] == 'v') - { - visible_map[pos_i] = world.map.cells[pos_i]; - } - } - struct Thing * t; - char c; - uint8_t i; - for (i = 0; i < 3; i++) - { - for (t = world.things; t != 0; t = t->next) - { - if ('v' == player->fov_map[t->pos.y*world.map.length+t->pos.x]) - { - struct ThingType * tt = get_thing_type(t->type); - if ( (0 == i && !t->lifepoints && !tt->consumable) - || (1 == i && !t->lifepoints && tt->consumable) - || (2 == i && t->lifepoints)) - { - c = tt->char_on_map; - visible_map[t->pos.y * world.map.length + t->pos.x] = c; - } - } - } - } - } - return visible_map; -} - - - -static void write_map(struct Thing * player, FILE * file) -{ - char * visible_map = build_visible_map(player); - uint16_t x, y; - for (y = 0; y < world.map.length; y++) - { - for (x = 0; x < world.map.length; x++) - { - try_fputc(visible_map[y * world.map.length + x], file, __func__); - } - try_fputc('\n', file, __func__); - } - free(visible_map); - uint32_t map_size = world.map.length * world.map.length; - char * mem_map = try_malloc(map_size, __func__); - memcpy(mem_map, player->mem_map, map_size); - uint8_t i; - struct ThingInMemory * tm; - for (i = 0; i < 2; i++) - { - for (tm = player->t_mem; tm; tm = tm->next) - { - if (' ' != player->mem_map[tm->pos.y*world.map.length+tm->pos.x]) - { - struct ThingType * tt = get_thing_type(tm->type); - if ( (0 == i && !tt->consumable) - || (1 == i && tt->consumable)) - { - char c = tt->char_on_map; - mem_map[tm->pos.y * world.map.length + tm->pos.x] = c; - } - } - } - } - for (y = 0; y < world.map.length; y++) - { - for (x = 0; x < world.map.length; x++) - { - try_fputc(mem_map[y * world.map.length + x], file, __func__); - } - try_fputc('\n', file, __func__); - } - free(mem_map); -} - - - -extern char * io_round() -{ - if (world.queue && strlen(world.queue)) - { - return get_message_from_queue(&world.queue); - } - if (world.do_update) - { - update_worldstate_file(); - send_to_outfile("WORLD_UPDATED\n", 1); - world.do_update = 0; - } - try_growing_queue(); - return get_message_from_queue(&world.queue); -} - - - -extern void save_world() -{ - char * path_tmp; - FILE * file = atomic_write_start(s[S_PATH_SAVE], &path_tmp); - write_key_space_uvalue(file, s[S_CMD_TURN], world.turn); - write_key_space_uvalue(file, s[S_CMD_PLAYTYPE], world.player_type); - write_key_space_uvalue(file, s[S_CMD_MAPLENGTH], world.map.length); - write_key_space_uvalue(file, s[S_CMD_SEED_MAP], world.seed_map); - struct ThingAction * ta; - for (ta = world.thing_actions; ta; ta = ta->next) - { - write_key_space_uvalue(file, s[S_CMD_TA_ID], ta->id); - write_key_space_uvalue(file, s[S_CMD_TA_EFFORT], ta->effort); - write_key_space_string(file, s[S_CMD_TA_NAME], ta->name); - } - struct ThingType * tt; - for (tt = world.thing_types; tt; tt = tt->next) - { - write_key_space_uvalue(file, s[S_CMD_TT_ID], tt->id); - write_key_space_uvalue(file, s[S_CMD_TT_STARTN], tt->start_n); - int test = fprintf(file, "%s '%c'\n", s[S_CMD_TT_SYMB], tt->char_on_map); - exit_trouble(test < 0, __func__, "fprintf"); - write_key_space_string(file, s[S_CMD_TT_NAME], tt->name); - write_key_space_uvalue(file, s[S_CMD_TT_PROL], tt->proliferate); - write_key_space_uvalue(file, s[S_CMD_TT_HP], tt->lifepoints); - write_key_space_uvalue(file, s[S_CMD_TT_CONSUM], tt->consumable); - } - for (tt = world.thing_types; tt; tt = tt->next) - { - write_key_space_uvalue(file, s[S_CMD_TT_ID], tt->id); - write_key_space_uvalue(file, s[S_CMD_TT_CORPS], tt->corpse_id); - } - struct Thing * t; - for (t = world.things; t; t = t->next) - { - write_thing(file, t); - } - for (t = world.things; t; t = t->next) - { - write_thing_carrying(file, t); - } - write_key_space_uvalue(file, s[S_CMD_SEED_RAND], world.seed); - write_key_space_uvalue(file, s[S_CMD_WORLD_ACTIVE], 1); - atomic_write_finish(file, s[S_PATH_SAVE], path_tmp); -} diff --git a/src/server/io.h b/src/server/io.h deleted file mode 100644 index 7c9148d..0000000 --- a/src/server/io.h +++ /dev/null @@ -1,31 +0,0 @@ -/* io.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Communication of the server with the outside world and its client via input, - * output and world state files. - */ - -#ifndef IO_H -#define IO_H - - - -/* Return single \0-terminated string read from input queue (world.queue); or, - * if queue is empty and world.do_update is set, update world state file (and - * unset world.do_update) 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. - * (Queueing ensures only complete messages are interpreted.) - */ -extern char * io_round(); - -/* Write to savefile (atomically) 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 deleted file mode 100644 index 1ae00f1..0000000 --- a/src/server/main.c +++ /dev/null @@ -1,49 +0,0 @@ -/* src/server/main.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include /* printf() */ -#include /* exit() */ -#include "../common/rexit.h" /* exit_err, set_cleanup_func() */ -#include "cleanup.h" /* set_cleanup_flag(), cleanup() */ -#include "hardcoded_strings.h" /* s */ -#include "init.h" /* run_game(), obey_argv(), obey_argv(), setup_server_io() */ -#include "world.h" /* struct World */ - - - -struct World world; - - - -int main(int argc, char ** argv) -{ - /* So error exits also go through the server's cleanup() function. */ - 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) - { - char * printf_err = "Trouble in main() with printf()."; - int test = printf("Starting plomrogue-server.\n"); - exit_err(-1 == test, printf_err); - if (world.replay) - { - test = printf("Replay mode. Auto-replaying up to turn %d.\n", - world.replay); - exit_err(-1 == test, printf_err); - } - } - world.map.length = 64; /* Just a sane default value. */ - - /* Init server i/o, Enter play or replay mode loops, then leave properly. */ - setup_server_io(); - run_game(); - cleanup(); - exit(EXIT_SUCCESS); -} diff --git a/src/server/map.c b/src/server/map.c deleted file mode 100644 index 3a5a0a2..0000000 --- a/src/server/map.c +++ /dev/null @@ -1,241 +0,0 @@ -/* src/server/map.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "map.h" -#include /* uint8_t, int8_t, uint16_t, uint32_t, (U)INT*_(MIN|MAX) */ -#include /* free() */ -#include /* memset() */ -#include "../common/rexit.h" /* exit_err() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "../common/yx_uint8.h" /* yx_uint8 */ -#include "rrand.h" /* rrand() */ -#include "world.h" /* global world */ - - - -/* Helper to mv_yx_in_dir_legal(). Move "yx" into hex direction "d". */ -static void mv_yx_in_dir(char d, struct yx_uint8 * yx); - -/* Call this too often with "init" of 0 and the game exits with an error message - * about reaching an iteration limit. An "init" of 1 sets the iteration counter - * to 0. Iteration limit is currently 256 * UINT16_MAX. - */ -static uint8_t iter_limit(uint8_t init); - -/* Return 1 if cell on "pos" is neighbor to a cell of "type", else return 0. */ -static uint8_t is_neighbor(struct yx_uint8 pos, char type); - -/* Fill map with '~' cells. */ -static void make_sea(); - -/* Put island of '.' cells inside map sea. */ -static void make_sea(); - -/* Put tree cells of 'X' on island. */ -static void make_trees(); - - - -static void mv_yx_in_dir(char d, struct yx_uint8 * yx) -{ - if (d == 'e') - { - yx->x = yx->x + (yx->y % 2); - yx->y--; - } - else if (d == 'd') - { - yx->x++; - } - else if (d == 'c') - { - yx->x = yx->x + (yx->y % 2); - yx->y++; - } - else if (d == 'x') - { - yx->x = yx->x - !(yx->y % 2); - yx->y++; - } - else if (d == 's') - { - yx->x--; - } - else if (d == 'w') - { - yx->x = yx->x - !(yx->y % 2); - yx->y--; - } -} - - - -static uint8_t iter_limit(uint8_t init) -{ - static uint32_t i = 0; - char * err = "Map generation reached iteration limit. Change map size?"; - if (init) - { - i = 0; - return 0; - } - i++; - exit_err(256 * UINT16_MAX == i, err); - return 1; -} - - - -static uint8_t is_neighbor(struct yx_uint8 pos, char type) -{ - uint8_t ind = pos.y % 2; - uint8_t diag_west = pos.x + ind > 0; - uint8_t diag_east = pos.x + ind <= world.map.length - 1; - uint16_t pos_i = (pos.y * world.map.length) + pos.x; - if ( ( pos.y > 0 && diag_east - && type == world.map.cells[pos_i - world.map.length + ind]) - || ( pos.x < world.map.length - 1 - && type == world.map.cells[pos_i + 1]) - || ( pos.y < world.map.length - 1 && diag_east - && type == world.map.cells[pos_i + world.map.length + ind]) - || ( pos.y > 0 && diag_west - && type == world.map.cells[pos_i - world.map.length - !ind]) - || ( pos.x > 0 - && type == world.map.cells[pos_i - 1]) - || ( pos.y < world.map.length - 1 && diag_west - && type == world.map.cells[pos_i + world.map.length - !ind])) - { - return 1; - } - return 0; -} - - - -static void make_sea() -{ - uint16_t y, x; - for (y = 0; y < world.map.length; y++) - { - for (x = 0; - x < world.map.length; - world.map.cells[(y * world.map.length) + x] = '~', x++); - } -} - - - -static void make_island() -{ - char type = '.'; - uint8_t add_half_width = !(world.map.length % 2) * (world.map.length / 2); - uint32_t size = world.map.length * world.map.length; - world.map.cells[(size / 2) + add_half_width] = type; - struct yx_uint8 pos; - iter_limit(1); - while (iter_limit(0)) - { - pos.y = rrand() % world.map.length; - pos.x = rrand() % world.map.length; - uint16_t pos_i = (pos.y * world.map.length) + pos.x; - if ('~' == world.map.cells[pos_i] && is_neighbor(pos, type)) - { - if ( pos.y == 0 || pos.y == world.map.length - 1 - || pos.x == 0 || pos.x == world.map.length - 1) - { - break; - } - world.map.cells[pos_i] = type; - } - } -} - - - -static void make_trees() -{ - char type = 'X'; - struct yx_uint8 pos; - uint16_t n_trees = (world.map.length * world.map.length) / 16; - uint16_t i_trees = 0; - iter_limit(1); - while (i_trees <= n_trees && iter_limit(0)) - { - uint8_t single_allowed = rrand() % 32; - pos.y = rrand() % world.map.length; - pos.x = rrand() % world.map.length; - uint16_t pos_i = (pos.y * world.map.length) + pos.x; - if ('.' == world.map.cells[pos_i] - && (!single_allowed || is_neighbor(pos, type))) - { - world.map.cells[pos_i] = type; - i_trees++; - } - } -} - - - -extern void remake_map() -{ - free(world.map.cells); - world.map.cells = try_malloc(world.map.length * world.map.length, __func__); - uint32_t store_seed = world.seed; - world.seed = world.seed_map; - make_sea(); - make_island(); - make_trees(); - world.seed = store_seed; -} - - - -extern uint8_t mv_yx_in_dir_legal(char dir, struct yx_uint8 * yx) -{ - static int8_t wrap_west_east = 0; - static int8_t wrap_north_south = 0; - if (!yx) - { - wrap_west_east = wrap_north_south = 0; - return 0; - } - char * err = "Too much wrapping in mv_yx_in_dir_legal()."; - exit_err( INT8_MIN == wrap_west_east || INT8_MIN == wrap_north_south - || INT8_MAX == wrap_west_east || INT8_MAX == wrap_north_south,err); - struct yx_uint8 original = *yx; - mv_yx_in_dir(dir, yx); - if (('e' == dir || 'd' == dir || 'c' == dir) && yx->x < original.x) - { - wrap_west_east++; - } - else if (('x' == dir || 's' == dir || 'w' == dir) && yx->x > original.x) - { - wrap_west_east--; - } - if (('w' == dir || 'e' == dir) && yx->y > original.y) - { - wrap_north_south--; - } - else if (('x' == dir || 'c' == dir) && yx->y < original.y) - { - wrap_north_south++; - } - if ( !wrap_west_east && !wrap_north_south - && yx->x < world.map.length && yx->y < world.map.length) - { - return 1; - } - return 0; -} - - - -extern void init_empty_map(char ** map) -{ - *map = try_malloc(world.map.length * world.map.length, __func__); - memset(*map, ' ', world.map.length * world.map.length); -} diff --git a/src/server/map.h b/src/server/map.h deleted file mode 100644 index d9ebf74..0000000 --- a/src/server/map.h +++ /dev/null @@ -1,47 +0,0 @@ -/* src/server/map.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Routines to create and navigate game map. - */ - -#ifndef MAP_H_SERVER -#define MAP_H_SERVER - -#include /* uint8_t */ -struct yx_uint8; - - - -/* (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 remake_map(); - -/* Move "yx" into hex direction "dir". Available hex directions are: 'e' - * (north-east), 'd' (east), 'c' (south-east), 'x' (south-west), 's' (west), 'w' - * (north-west). Returns 1 if the move was legal, else 0. - * - * A move is legal if "yx" ends up in the confines of the map and the original - * wrap space. The latter is left to a neighbor wrap space if "yx" moves beyond - * the minimal (0) or maximal (UINT8_MAX) column or row of possible map space – - * in which case "yx".y or "yx".x will snap to the respective opposite side. The - * current wrapping state is kept between successive calls until a "yx" of NULL - * is passed, in which case the function does nothing but zero the wrap state. - * Successive wrapping may move "yx" several wrap spaces into either direction, - * or return it into the original wrap space. - */ -extern uint8_t mv_yx_in_dir_legal(char dir, struct yx_uint8 * yx); - -/* Initialize (empty) map array at "map". */ -extern void init_empty_map(char ** map); - - -#endif diff --git a/src/server/rrand.c b/src/server/rrand.c deleted file mode 100644 index 034b0e8..0000000 --- a/src/server/rrand.c +++ /dev/null @@ -1,18 +0,0 @@ -/* src/server/rrand.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "rrand.h" -#include /* uint16_t */ -#include "world.h" /* global world */ - - - -extern uint16_t rrand() -{ /* Constants as recommended by POSIX.1-2001 (see man page rand(3)). */ - world.seed = ((world.seed * 1103515245) + 12345) % 4294967296; - return (world.seed >> 16); /* Ignore less random least significant bits. */ -} diff --git a/src/server/rrand.h b/src/server/rrand.h deleted file mode 100644 index 74f12fd..0000000 --- a/src/server/rrand.h +++ /dev/null @@ -1,25 +0,0 @@ -/* src/server/rrand.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Provides deterministic pseudo-randomness. - */ - -#ifndef RRAND_H -#define RRAND_H - -#include /* uint16_t */ - - - -/* Return 16-bit number pseudo-randomly generated via Linear Congruential - * Generator algorithm with some proven constants. Use instead of rand() to - * ensure portability of the same pseudo-randomness across systems. - */ -extern uint16_t rrand(); - - - -#endif diff --git a/src/server/run.c b/src/server/run.c deleted file mode 100644 index ad04f71..0000000 --- a/src/server/run.c +++ /dev/null @@ -1,467 +0,0 @@ -/* src/server/run.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#define _POSIX_C_SOURCE 200809L /* strdup() */ -#include "run.h" -#include /* NULL */ -#include /* uint8_t, uint16_t, uint32_t, int16_t */ -#include /* FILE, printf(), fflush() */ -#include /* atoi(), free() */ -#include /* strlen(), strcmp(), strncmp(), strdup() */ -#include /* time_t, time() */ -#include /* access() */ -#include "../common/parse_file.h" /* set_err_line_options(), token_from_line(), - * err_line(), err_line_inc(), parse_val(), - * parestest_int() - */ -#include "../common/readwrite.h" /* try_fopen(), try_fcose(), try_fwrite(), - * try_fgets(), textfile_width(), try_fputc(), - * atomic_write_finish(), build_temp_path() - */ -#include "../common/rexit.h" /* exit_trouble(), exit_err() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "ai.h" /* ai() */ -#include "cleanup.h" /* unset_cleanup_flag() */ -#include "field_of_view.h" /* update_map_memory() */ -#include "god_commands.h" /* parse_god_command_(1|2|3)arg() */ -#include "hardcoded_strings.h" /* s */ -#include "io.h" /* io_round(), save_world() */ -#include "thing_actions.h" /* hunger(), try_healing() */ -#include "things.h" /* Thing, ThingType, ThingInMemory, get_player(), - * get_thing_action_id_by_name(), try_thing_proliferation() - */ -#include "world.h" /* world */ - - - -/* If "string" and "comparand" match in string, set "c_to_set" to value." */ -static uint8_t set_char_by_string_comparison(char * string, char * comparand, - char * c_to_set, char value); - -/* Return 1 on world.exists, else 0 and err_line() appropriate error message. */ -static uint8_t player_commands_allowed(); - -/* Parse player commands "tok0" with optional argument "tok1" to player action. - * Return 1 on success, 0 on failure. - */ -static uint8_t parse_player_command_0arg(char * tok0); -static uint8_t parse_player_command_1arg(char * tok0, char * tok1); - -/* Parse/apply (non-)meta command "tok0" (read further tokens as necessary). - * Return 0 on failure, 1 on success, 2 on QUIT meta command. - */ -static uint8_t parse_command_nonmeta(char * tok0); -static uint8_t parse_command_meta(char * tok0); - -/* Compare 1st line of server out file to world.server_test, abort if they don't - * match, but not before unsetting 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(); - -/* Return array of IDs of non-owned things in game world, ended by non-ID -1. */ -static int16_t * build_whitelist(); - -/* Return 1 if value of "id" appears in "whitelist", else 0. */ -static uint8_t thing_in_whitelist(uint8_t id, int16_t * whitelist); - -/* Run the game world and its inhabitants (and their actions) until the player - * avatar is free to receive new commands (or is dead). Only actions and - * proliferations for non-owned things are performed that exist at the start of - * the turn jumped into, or started anew by the cycle. - */ -static void turn_over(); - - - -static uint8_t set_char_by_string_comparison(char * string, char * comparand, - char * c_to_set, char value) -{ - if (!strcmp(string, comparand)) - { - * c_to_set = value; - return 1; - } - return 0; -} - - - -static uint8_t player_commands_allowed() -{ - if (!world.exists) - { - return !err_line(1, "No world exists in which to run player commands."); - } - return 1; -} - - - -static uint8_t parse_player_command_0arg(char * tok0) -{ - struct Thing * player = get_player(); - if ( !strcmp(tok0, s[S_CMD_WAIT]) || !strcmp(tok0, s[S_CMD_PICKUP]) - || !strcmp(tok0, s[S_CMD_AI])) - { - if (player_commands_allowed()) - { - if (!strcmp(tok0, s[S_CMD_AI])) - { - ai(player); - } - else - { - player->command = get_thing_action_id_by_name(tok0); - } - turn_over(); - } - return 1; - } - return 0; -} - - - -static uint8_t parse_player_command_1arg(char * tok0, char * tok1) -{ - struct Thing * player = get_player(); - if ( ( parse_val(tok0, tok1, s[S_CMD_DROP], '8', (char *) &player->arg) - || parse_val(tok0, tok1, s[S_CMD_USE], '8', (char *) &player->arg)) - && player_commands_allowed()) - { - player->command = get_thing_action_id_by_name(tok0); - turn_over(); - } - else if (!strcmp(tok0, s[S_CMD_MOVE]) && player_commands_allowed()) - { - char dir = '\0'; - if (!( set_char_by_string_comparison(tok1, "east", &dir, 'd') - || set_char_by_string_comparison(tok1, "south-east", &dir, 'c') - || set_char_by_string_comparison(tok1, "south-west", &dir, 'x') - || set_char_by_string_comparison(tok1, "west", &dir, 's') - || set_char_by_string_comparison(tok1, "north-west", &dir, 'w') - || set_char_by_string_comparison(tok1, "north-east", &dir, 'e'))) - { - return 0; - } - player->arg = dir; - player->command = get_thing_action_id_by_name(tok0); - turn_over(); - } - else - { - return 0; - } - return 1; -} - - - -static uint8_t parse_command_nonmeta(char * tok0) -{ - if (parse_player_command_0arg(tok0)) - { - return 1; - } - else - { - char * tok1 = token_from_line(NULL); - if (tok1 && ( parse_player_command_1arg(tok0, tok1) - || parse_god_command_1arg(tok0, tok1))) - { - return 1; - } - else - { - char * tok2 = token_from_line(NULL); - if (tok2 && parse_god_command_2arg(tok0, tok1, tok2)) - { - return 1; - } - else - { - char * tok3 = token_from_line(NULL); - if (tok2 && parse_god_command_3arg(tok0, tok1, tok2, tok3)) - { - return 1; - } - } - } - } - return 0; -} - - - -static uint8_t parse_command_meta(char * tok0) -{ - if (!strcmp("QUIT", tok0)) - { - return 2; - } - if (!strcmp("PING", tok0)) - { - send_to_outfile("PONG\n", 1); - return 1; - } - if (!strcmp("THINGS_HERE", tok0)) - { - char * tok1 = token_from_line(NULL); - char * tok2 = token_from_line(NULL); - if (tok1&&tok2 && !parsetest_int(tok1, '8')&&!parsetest_int(tok2, '8')) - { - if (!world.exists) - { - err_line(1, "Command only works on existing worlds."); - return 0; - } - send_to_outfile("THINGS_HERE START\n", 1); - struct Thing * player = get_player(); - if (player->fov_map && - 'v' == player->fov_map[atoi(tok1)*world.map.length+atoi(tok2)]) - { - struct Thing * t; - for (t = world.things; t; t = t->next) - { - if (t->pos.y == atoi(tok1) && t->pos.x == atoi(tok2)) - { - struct ThingType * tt = get_thing_type(t->type); - send_to_outfile(tt->name, 0); - send_to_outfile("\n", 1); - } - } - } - else - { - struct ThingInMemory * t_mem; - for (t_mem = player->t_mem; t_mem; t_mem = t_mem->next) - { - if (t_mem->pos.y == atoi(tok1) && t_mem->pos.x == atoi(tok2)) - { - struct ThingType * tt = get_thing_type(t_mem->type); - send_to_outfile(tt->name, 0); - send_to_outfile("\n", 1); - } - } - } - send_to_outfile("THINGS_HERE END\n", 1); - return 1; - } - } - return 0; -} - - - -static void server_test() -{ - char test[10 + 1 + 10 + 1 + 1]; - FILE * file = try_fopen(s[S_PATH_OUT], "r", __func__); - try_fgets(test, 10 + 10 + 1 + 1, file, __func__); - try_fclose(file, __func__); - 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); - } -} - - - -static int16_t * build_whitelist() -{ - uint16_t i_things = NULL != world.things; - struct Thing * t = world.things; - for (; t; t = t->next, i_things++); - int16_t * whitelist = try_malloc(i_things * sizeof(int16_t), __func__); - for (i_things = 0, t = world.things; t; - whitelist[i_things] = t->id, t = t->next, i_things++); - whitelist[i_things] = -1; - return whitelist; -} - - - -static uint8_t thing_in_whitelist(uint8_t id, int16_t * whitelist) -{ - uint16_t i; - for (i = 0; -1 < whitelist[i]; i++) - { - if ((int16_t) id == whitelist[i]) - { - return 1; - } - } - return 0; -} - - - -static void turn_over() -{ - struct Thing * player = get_player(); - struct Thing * thing = player; - int16_t * whitelist = build_whitelist(); - while (0 < player->lifepoints) - { - if (!thing) - { - world.turn++; - thing = world.things; - free(whitelist); - whitelist = build_whitelist();/* The whitelist excludes things */ - } /* that appear only during the turn.*/ - if (thing_in_whitelist(thing->id, whitelist)) - { - if (0 < thing->lifepoints) - { - if (0 == thing->command) - { - update_map_memory(thing, 1); - if (thing == player) - { - break; - } - ai(thing); - } - try_healing(thing); - thing->progress++; - struct ThingAction * ta = get_thing_action(thing->command); - if (thing->progress == ta->effort) - { - ta->func(thing); - thing->command = 0; - thing->progress = 0; - } - hunger(thing); - } - try_thing_proliferation(thing); - } - thing = thing->next; - } - free(whitelist); -} - - - -extern void send_to_outfile(char * answer, uint8_t flush) -{ - try_fwrite(answer, strlen(answer), 1, world.file_out, __func__); - if (flush) - { - fflush(world.file_out); - } -} - - - -extern void record(char * msg, uint8_t force) -{ - static FILE * file_tmp = NULL; - static time_t save_wait = 0; - static char * path_tmp; - if (!file_tmp) - { - path_tmp = build_temp_path(s[S_PATH_RECORD]); - file_tmp = try_fopen(path_tmp, "w", __func__); - if (!access(s[S_PATH_RECORD], F_OK)) - { - FILE * file_read = try_fopen(s[S_PATH_RECORD], "r", __func__); - uint32_t linemax = textfile_width(file_read); - char * line = try_malloc(linemax + 1, __func__); - while (try_fgets(line, linemax + 1, file_read, __func__)) - { - try_fwrite(line, strlen(line), 1, file_tmp, __func__); - } - free(line); - try_fclose(file_read, __func__); - } - } - if (msg) - { - try_fwrite(msg, strlen(msg), 1, file_tmp, __func__); - try_fputc('\n', file_tmp, __func__); - } - if (force || time(NULL) > save_wait + 15) - { - save_wait = time(NULL); - save_world(); - atomic_write_finish(file_tmp, s[S_PATH_RECORD], path_tmp); - file_tmp = NULL; - } -} - - - -extern uint8_t obey_msg(char * msg, uint8_t obey_state) -{ - if (world.is_verbose) - { - exit_trouble(-1 == printf("Input: %s\n", msg), __func__, "printf"); - } - set_err_line_options("Trouble with message: ", msg, 0); - char * msg_copy = strdup(msg); - char * tok0 = token_from_line(msg_copy); - uint8_t ret = 0; - if (tok0) - { - ret = parse_command_meta(tok0); - if (ret || (obey_state < 2 && parse_command_nonmeta(tok0))) - { - if (!ret) - { - if (world.exists) - { - world.do_update = 1; - } - if (1 == obey_state) - { - record(msg, 0); - } - } - char * tokplus = token_from_line(NULL); - err_line(!(!tokplus), "Too many arguments, ignoring overflow."); - } - else if (world.replay) - { - ret = 3; - } - else if (!world.replay) - { - err_line(1, "Invalid command/argument or bad number of tokens."); - } - } - free(msg_copy); - return ret; -} - - - -extern uint8_t io_loop(uint8_t obey_state) -{ - while (1) - { - char * msg = io_round(); - server_test(); - if (msg) - { - uint8_t ret = obey_msg(msg, obey_state); - free(msg); - if ( (!world.replay && 2 == ret) - || ( world.replay && 2 <= ret)) - { - return ret; - } - } - } -} diff --git a/src/server/run.h b/src/server/run.h deleted file mode 100644 index 414df1b..0000000 --- a/src/server/run.h +++ /dev/null @@ -1,46 +0,0 @@ -/* src/server/run.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Process commands and act on them. Stuff that furthers the state of the game. - */ - -#ifndef RUN_H -#define RUN_H - -#include /* uint8_t */ - - - -/* Append "answer" to server output file, with instant fflush() if "flush". */ -extern void send_to_outfile(char * answer, uint8_t flush); - -/* Record save and record file data. Both are only written if "force" is set, or - * on the first run with unset "force", or if 15 seconds have passed since the - * last file writing. "msg" is appended to the record file if it is set. - */ -extern void record(char * msg, uint8_t force); - -/* Try parsing "msg" into a command to apply. Output "msg" if world.is_verbose. - * If "obey_state" is > 1 and world.replay is set, any non-meta command message - * is not executed, but merely returns 3. The QUIT meta command (if well-formed) - * always returns 2. Other meta commands and (with "obey_state" < 2) non-meta - * commands return 1 if well-formed. Malformed or empty command messages return - * 0. If "obey_state" is 1, "msg" is recorded via a non-forced record(). If a - * non-meta command is executed and world.exists, world.do_update is set. - */ -extern uint8_t obey_msg(char * msg, uint8_t obey_state); - -/* Loop to read commands via io_round() and call obey_msg() with "obey_state" - * set on them. If latter returns 2 and world.replay is not set, or returns > 1 - * and world.replay is set, abort loop to return that result. After io_round(), - * compares 1st line of the server out file is compared with world.server_test - * to ensure the server process hasn't been superseded by a new one. - */ -extern uint8_t io_loop(uint8_t obey_state); - - - -#endif diff --git a/src/server/thing_actions.c b/src/server/thing_actions.c deleted file mode 100644 index 6a8803d..0000000 --- a/src/server/thing_actions.c +++ /dev/null @@ -1,393 +0,0 @@ -/* src/server/thing_actions.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#include "thing_actions.h" -#include /* NULL */ -#include /* uint8_t, INT16_MIN, INT16_MAX */ -#include /* sprintf() */ -#include /* free() */ -#include /* strlen() */ -#include "../common/rexit.h" /* exit_err(), exit_trouble() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "../common/yx_uint8.h" /* yx_uint8 */ -#include "field_of_view.h" /* build_fov_map() */ -#include "hardcoded_strings.h" /* s */ -#include "things.h" /* Thing, ThingType, get_player(), free_things_in_memory(), - * own_thing(), set_thing_position(), get_thing_type(), - */ -#include "map.h" /* mv_yx_in_dir_legal() */ -#include "rrand.h" /* rrand() */ -#include "run.h" /* send_to_outfile() */ -#include "world.h" /* global world */ - - - -/* Send "text" as log message to server out file. */ -static void update_log(char * text); - -/* Decrement "t"'s lifepoints, and if to zero, kill it with log update. */ -static void decrement_lifepoints(struct Thing * t); - -/* One actor "wounds" another actor, decrementing his lifepoints. */ -static void actor_hits_actor(struct Thing * hitter, struct Thing * hitted); - -/* Bonus stuff to actor_*() to happen if actor==player. Mostly writing of log - * messages; _pick and _drop also decrement world.inventory_sel by 1 if >0. - * (match_dir() is just a little helper to playerbonus_move().) - */ -static void playerbonus_wait(); -static uint8_t match_dir(char d, char ** dsc_d, char match, char * dsc_match); -static void playerbonus_move(char d, uint8_t passable); -static void playerbonus_drop(uint8_t owns_none); -static void playerbonus_pick(uint8_t picked); -static void playerbonus_use(uint8_t no_thing, uint8_t wrong_thing); - - - -static void update_log(char * text) -{ - send_to_outfile("LOG ", 0); - send_to_outfile(text, 0); - send_to_outfile("\n", 1); -} - - - -static void decrement_lifepoints(struct Thing * t) -{ - struct Thing * player = get_player(); - t->lifepoints--; - if (0 == t->lifepoints) - { - t->type = get_thing_type(t->type)->corpse_id; - if (player == t) - { - update_log("You die."); - memset(t->fov_map, ' ', world.map.length * world.map.length); - return; - } - else - { - free(t->fov_map); - t->fov_map = NULL; - free(t->mem_map); - t->mem_map = NULL; - free(t->mem_depth_map); - t->mem_depth_map = NULL; - free_things_in_memory(t->t_mem); - t->t_mem = NULL; - } - update_log("It dies."); - } -} - - - -static void actor_hits_actor(struct Thing * hitter, struct Thing * hitted) -{ - struct ThingType * tt_hitter = get_thing_type(hitter->type); - struct ThingType * tt_hitted = get_thing_type(hitted->type); - struct Thing * player = get_player(); - char * msg1 = "You"; - char * msg2 = "wound"; - char * msg3 = "you"; - if (player != hitter) - { - msg1 = tt_hitter->name; - msg2 = "wounds"; - } - if (player != hitted) - { - msg3 = tt_hitted->name; - } - uint8_t len = strlen(msg1) + 1 + strlen(msg2) + 1 + strlen(msg3) + 2; - char * msg = try_malloc(len, __func__); - int test = sprintf(msg, "%s %s %s.", msg1, msg2, msg3); - exit_trouble(test < 0, __func__, s[S_FCN_SPRINTF]); - update_log(msg); - free(msg); - decrement_lifepoints(hitted); -} - - - -static void playerbonus_wait() -{ - update_log("You wait."); -} - - - -static uint8_t match_dir(char d, char ** dsc_d, char match, char * dsc_match) -{ - if (d == match) - { - * dsc_d = dsc_match; - return 1; - } - return 0; -} - - - -static void playerbonus_move(char d, uint8_t passable) -{ - char * dsc_dir = "north-east"; - if ( match_dir(d, &dsc_dir, 'd', "east") - || match_dir(d, &dsc_dir, 'c', "south-east") - || match_dir(d, &dsc_dir, 'x', "south-west") - || match_dir(d, &dsc_dir, 's', "west") - || match_dir(d, &dsc_dir, 'w', "north-west")) - { - ; - } - char * dsc_move = "You move "; - if (0 == passable) - { - dsc_move = "You fail to move "; - } - char * msg = try_malloc(strlen(dsc_move) + strlen (dsc_dir) + 2, __func__); - int test = sprintf(msg, "%s%s.", dsc_move, dsc_dir); - exit_trouble(test < 0, __func__, s[S_FCN_SPRINTF]); - update_log(msg); - free(msg); -} - - - -static void playerbonus_drop(uint8_t owns_none) -{ - if (0 != owns_none) - { - update_log("You try to drop an object, but you own none."); - return; - } - update_log("You drop an object."); -} - - - -static void playerbonus_pick(uint8_t picked) -{ - if (picked) - { - update_log("You pick up an object."); - return; - } - update_log("You try to pick up an object, but there is none."); -} - - - -static void playerbonus_use(uint8_t no_thing, uint8_t wrong_thing) -{ - if (no_thing) - { - update_log("You try to use an object, but you own none."); - return; - } - else if (wrong_thing) - { - update_log("You try to use this object, but fail."); - return; - } - update_log("You consume this object."); -} - - - -extern void actor_wait(struct Thing * t) -{ - if (t == get_player()) - { - playerbonus_wait(); - } -} - - - -extern void actor_move(struct Thing * t) -{ - char d = t->arg; - struct Thing * other_t; - struct yx_uint8 target = t->pos; - uint8_t legal_move = mv_yx_in_dir_legal(d, &target); - mv_yx_in_dir_legal(0, NULL); - uint8_t passable = 0; - if (legal_move) - { - passable = '.' == world.map.cells[target.y*world.map.length + target.x]; - for (other_t = world.things; other_t != 0; other_t = other_t->next) - { - if (0 == other_t->lifepoints || other_t == t) - { - continue; - } - if (target.y == other_t->pos.y && target.x == other_t->pos.x) - { - actor_hits_actor(t, other_t); - return; - } - } - } - if (passable) - { - set_thing_position(t, target); - build_fov_map(t); - } - if (t == get_player()) - { - playerbonus_move(d, passable); - } -} - - - -extern void actor_drop(struct Thing * t) -{ - uint8_t owns_none = (!t->owns); - if (!owns_none) - { - uint8_t select = t->arg; - struct Thing * owned = t->owns; - uint8_t i = 0; - for (; i != select; i++, owned = owned->next); - own_thing(&world.things, &t->owns, owned->id); - } - if (t == get_player()) - { - playerbonus_drop(owns_none); - } -} - - - -extern void actor_pick(struct Thing * t) -{ - struct Thing * picked = NULL; - struct Thing * t_i; - uint8_t highest_id = 0; - for (t_i = world.things; t_i; t_i = t_i->next) - { - if (t_i != t && t_i->pos.y == t->pos.y && t_i->pos.x == t->pos.x) - { - if (t_i->id >= highest_id) /* With several Things to pick, */ - { /* pick the one with the highest ID. */ - highest_id = t_i->id; - picked = t_i; - } - } - } - if (picked) - { - own_thing(&t->owns, &world.things, picked->id); - set_thing_position(picked, t->pos); - } - if (t == get_player()) - { - playerbonus_pick(!(!picked)); - } -} - - - -extern void actor_use(struct Thing * t) -{ - uint8_t wrong_thing = 1; - uint8_t no_thing = (!t->owns); - if (!no_thing) - { - uint8_t select = t->arg; - uint8_t i = 0; - struct Thing * selected = t->owns; - for (; i != select; i++, selected = selected->next); - struct ThingType * tt = get_thing_type(selected->type); - if (tt->consumable) - { - wrong_thing = 0; - struct Thing * next = selected->next; - free(selected); - if (0 < select) - { - select--; - selected = t->owns; - for (i = 0; i != select; i++, selected = selected->next); - selected->next = next; - } - else - { - t->owns = next; - } - t->satiation = t->satiation + tt->consumable > INT16_MAX ? - INT16_MAX : t->satiation + tt->consumable; - } - } - if (t == get_player()) - { - playerbonus_use(no_thing, wrong_thing); - } -} - - - -extern void try_healing(struct Thing * t) -{ - struct ThingType * tt = get_thing_type(t->type); - if ( t->satiation > 0 && t->lifepoints < tt->lifepoints - && 0 == (rrand() % 31) - && get_thing_action_id_by_name(s[S_CMD_WAIT]) == t->command) - { - t->lifepoints++; - t->satiation = t->satiation - 32; - if (get_player() == t) - { - update_log("You heal."); - } - else - { - char * msg_part = " heals."; - uint8_t len = strlen(tt->name) + strlen(msg_part) + 1; - char * msg = try_malloc(len, __func__); - int test = sprintf(msg, "%s%s", tt->name, msg_part); - exit_trouble(test < 0, __func__, s[S_FCN_SPRINTF]); - update_log(msg); - free(msg); - } - } -} - - - -extern void hunger(struct Thing * t) -{ - if (t->satiation > INT16_MIN) - { - t->satiation--; - } - struct ThingType * tt = get_thing_type(t->type); - uint16_t testbase = t->satiation < 0 ? -(t->satiation) : t->satiation; - exit_err(!(tt->lifepoints), "A thing that should not hunger is hungering."); - uint16_t endurance = INT16_MAX / tt->lifepoints; - if ((testbase / endurance) / ((rrand() % endurance) + 1)) - { - if (get_player() == t) - { - update_log("You suffer from hunger."); - } - else - { - char * msg_part = " suffers from hunger."; - uint8_t len = strlen(tt->name) + strlen(msg_part) + 1; - char * msg = try_malloc(len, __func__); - int test = sprintf(msg, "%s%s", tt->name, msg_part); - exit_trouble(test < 0, __func__, s[S_FCN_SPRINTF]); - update_log(msg); - free(msg); - } - decrement_lifepoints(t); - } -} diff --git a/src/server/thing_actions.h b/src/server/thing_actions.h deleted file mode 100644 index 97ab3cc..0000000 --- a/src/server/thing_actions.h +++ /dev/null @@ -1,54 +0,0 @@ -/* src/server/thing_actions.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Actions that can be performed by living things / "actors". Note that apart - * from the consequences described below, each action may also trigger log - * messages and other minor stuff if the actor is equal to the player. - */ - -#ifndef THING_ACTIONS_H -#define THING_ACTIONS_H - -struct Thing; - - - -/* Actor "t" does nothing. */ -extern void actor_wait(struct Thing * t); - -/* Actor "t" tries to move one step in direction described by char t->arg (where - * north-east is 'e', east 'd' etc.) Move either succeeds, or another actor is - * encountered and hit (which leads ot its lifepoint decreasing by one and - * eventually death), or the move fails due to an impassable target square. On - * success, update thing's field of view map. - */ -extern void actor_move(struct Thing * t); - -/* Actor "t" tries to drop from inventory thing indexed by number t->args. */ -extern void actor_drop(struct Thing * t); - -/* Actor "t" tries to pick up topmost thing from ground into its inventory. */ -extern void actor_pick(struct Thing * t); - -/* Actor "t" tries to use thing in inventory indexed by number t->args. - * (Currently the only valid use is consuming items defined as consumable.) - */ -extern void actor_use(struct Thing * t); - -/* Increment "t"'s lifepoints to a 1/32 chance if its .satiation is positive, - * its lifepoints are below "t"'s type's .lifepoints, and "t"'s .command is the - * ID of the waiting action. On success, also decrement .satiation by by 32. - */ -extern void try_healing(struct Thing * t); - -/* Decrement "t"'s satiation and trigger a chance (dependent on over-/under- - * satiation value) of lifepoint decrement. - */ -extern void hunger(struct Thing * t); - - - -#endif diff --git a/src/server/things.c b/src/server/things.c deleted file mode 100644 index f114ea4..0000000 --- a/src/server/things.c +++ /dev/null @@ -1,411 +0,0 @@ -/* src/server/things.c - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - */ - -#define _POSIX_C_SOURCE 200809L /* strdup() */ -#include "things.h" -#include /* NULL, size_t */ -#include /* uint8_t, uint16_t, int16_t, UINT8_MAX, UINT16_MAX */ -#include /* free() */ -#include /* memset(), strcmp(), strdup(), strlen() */ -#include "../common/rexit.h" /* exit_err() */ -#include "../common/try_malloc.h" /* try_malloc() */ -#include "../common/yx_uint8.h" /* yx_uint8 */ -#include "cleanup.h" /* set_cleanup_flag() */ -#include "hardcoded_strings.h" /* s */ -#include "field_of_view.h" /* build_fov_map() */ -#include "map.h" /* mv_yx_in_dir_legal() */ -#include "rrand.h" /* rrand() */ -#include "thing_actions.h" /* actor_wait */ -#include "world.h" /* world */ - - - -/* Used to treat structs Thing, ThingType and ThingAction the same. */ -struct NextAndId -{ - struct NextAndId * next; - uint8_t id; -}; - - - -/* To linked list of NextAndId structs (or rather structs whose start region is - * compatible to it) starting at "start", add newly allocated element of - * "n_size" and an ID that is either "id" or, if "id" is <= UINT8_MAX and >= - * "id_start", get lowest ID >= "start_id" and <= UINT8_MAX for new thing - * ("struct_id"=0), thing type ("struct_id"=1) or thing action ("struct_id"=2). - */ -static struct NextAndId * add_to_struct_list(size_t n_size, uint8_t start_id, - int16_t id, uint8_t struct_id, - struct NextAndId ** start); - -/* Return 1 if cell at "test_pos" is proliferable by "t", i.e. it is passable, - * it is not inhabited by another thing of "t"'s type, and, if "t" is animate, - * neither by any other animate thing; else return 0. - */ -static uint8_t cell_is_proliferable(struct yx_uint8 test_pos, struct Thing * t); - - -static struct NextAndId * add_to_struct_list(size_t n_size, uint8_t start_id, - int16_t id, uint8_t struct_id, - struct NextAndId ** start) -{ - struct NextAndId * nai = try_malloc(n_size, __func__); - memset(nai, 0, n_size); - if (start_id <= id && id <= UINT8_MAX) - { - nai->id = id; - } - else - { - while (1) - { - if ( (0 == struct_id && !get_thing(world.things, start_id, 1)) - || (1 == struct_id && !get_thing_type(start_id)) - || (2 == struct_id && !get_thing_action(start_id))) - { - nai->id = start_id; - break; - } - char * err = "No unused ID available to add to ID list."; - exit_err(start_id == UINT8_MAX, err); - start_id++; - } - } - struct NextAndId ** nai_ptr_ptr = start; - for (; * nai_ptr_ptr; nai_ptr_ptr = &(*nai_ptr_ptr)->next); - *nai_ptr_ptr = nai; - return nai; -} - - - -static uint8_t cell_is_proliferable(struct yx_uint8 test_pos, struct Thing * t) -{ - if ('.' == world.map.cells[test_pos.y * world.map.length + test_pos.x]) - { - struct Thing * t_test; - for (t_test = world.things; t_test; t_test = t_test->next) - { - if (t_test->pos.y == test_pos.y && t_test->pos.x == test_pos.x) - { - if (t_test->type == t->type) - { - return 0; - } - if (t_test->lifepoints && t->lifepoints) - { - return 0; - } - } - } - return 1; - } - return 0; -} - - - -extern struct ThingAction * add_thing_action(uint8_t id) -{ - struct ThingAction * ta; - ta = (struct ThingAction *) add_to_struct_list(sizeof(struct ThingAction), - 1, (int16_t) id, 2, - (struct NextAndId **) - &world.thing_actions); - set_cleanup_flag(CLEANUP_THING_ACTIONS); - ta->name = strdup(s[S_CMD_WAIT]); - ta->effort = 1; - ta->func = actor_wait; - return ta; -} - - - -extern struct ThingType * add_thing_type(int16_t id) -{ - struct ThingType * tt; - tt = (struct ThingType *) add_to_struct_list(sizeof(struct ThingType), - 0, id, 1, - (struct NextAndId **) - &world.thing_types); - set_cleanup_flag(CLEANUP_THING_TYPES); - tt->name = strdup("(none)"); - tt->corpse_id = tt->id; - return tt; -} - - - -extern struct Thing * add_thing(int16_t id, uint8_t type, uint8_t y, uint8_t x) -{ - struct Thing * t; - t = (struct Thing *) add_to_struct_list(sizeof(struct Thing), 0, id, 0, - (struct NextAndId **)&world.things); - struct ThingType * tt = get_thing_type(type); - set_cleanup_flag(CLEANUP_THINGS); - t->type = tt->id; - t->lifepoints = tt->lifepoints; - t->pos.y = y; - t->pos.x = x; - if (t->lifepoints && world.exists) - { - build_fov_map(t); - } - return t; -} - - - -extern void add_thing_to_memory_map(struct Thing * t, uint8_t type, - uint8_t y, uint8_t x) -{ - struct ThingInMemory * tm=try_malloc(sizeof(struct ThingInMemory),__func__); - tm->type = type; - tm->pos.y = y; - tm->pos.x = x; - tm->next = t->t_mem; - t->t_mem = tm; -} - - - -extern void free_thing_actions(struct ThingAction * ta) -{ - if (!ta) - { - return; - } - free_thing_actions(ta->next); - free(ta->name); - free(ta); -} - - - -extern void free_thing_types(struct ThingType * tt) -{ - if (!tt) - { - return; - } - free_thing_types(tt->next); - free(tt->name); - free(tt); -} - - - -extern void free_things(struct Thing * t) -{ - if (!t) - { - return; - } - free_things(t->owns); - free_things(t->next); - free(t->fov_map); - free(t->mem_map); - free(t->mem_depth_map); - free_things_in_memory(t->t_mem); - free(t); - if (t == world.things) /* So add_things()' NULL-delimited thing */ - { /* iteration loop does not iterate over */ - world.things = NULL; /* freed memory when called the first time */ - } /* after world re-seeding. */ -} - - - -extern void free_things_in_memory(struct ThingInMemory * tm) -{ - if (!tm) - { - return; - } - free_things_in_memory(tm->next); - free(tm); -} - - - -extern struct ThingAction * get_thing_action(uint8_t id) -{ - struct ThingAction * ta = world.thing_actions; - for (; ta && id != ta->id; ta = ta->next); - return ta; -} - - - -extern struct ThingType * get_thing_type(uint8_t id) -{ - struct ThingType * tt = world.thing_types; - for (; tt && id != tt->id; tt = tt->next); - return tt; -} - - - -extern uint8_t get_thing_action_id_by_name(char * name) -{ - struct ThingAction * ta = world.thing_actions; - while (ta) - { - if (0 == strcmp(ta->name, name)) - { - break; - } - ta = ta->next; - } - if (!ta) - { - return 0; - } - return ta->id; -} - - - -extern struct Thing * get_thing(struct Thing * ptr, uint8_t id, uint8_t deep) -{ - while (1) - { - if (!ptr || id == ptr->id) - { - return ptr; - } - if (deep) - { - struct Thing * owned_thing = get_thing(ptr->owns, id, 1); - if (owned_thing) - { - return ptr; - } - } - ptr = ptr->next; - } -} - - - -extern struct Thing * get_player() -{ - return get_thing(world.things, 0, 0); -} - - - -extern void try_thing_proliferation(struct Thing * t) -{ - struct ThingType * tt = get_thing_type(t->type); - if (tt->proliferate) - { - if (1 == tt->proliferate || 1 == (rrand() % tt->proliferate)) - { - struct yx_uint8 candidates[6]; - uint8_t n_candidates = 0; - char dirs[7] = "cxswed"; - struct yx_uint8 test = t->pos; - uint8_t i; - for (i = 0; i < strlen(dirs); i++) - { - if ( mv_yx_in_dir_legal(dirs[i], &test) - && cell_is_proliferable(test, t)) - { - candidates[n_candidates] = test; - n_candidates++; - } - } - if (!n_candidates) - { - return; - } - i = rrand() % n_candidates; - add_thing(-1, tt->id, candidates[i].y, candidates[i].x); - } - } -} - - - -extern void add_things(uint8_t type, uint8_t n) -{ - uint8_t i; - for (i = 0; i < n; i++) - { - struct yx_uint8 pos; - while (1) - { - char * err="Space to put thing on too hard to find. Map too small?"; - uint16_t i_pos = 0; - for (pos.y = pos.x = 0; - '.' != world.map.cells[pos.y * world.map.length + pos.x]; - i_pos++) - { - exit_err(UINT16_MAX == i_pos, err); - pos.y = rrand() % world.map.length; - pos.x = rrand() % world.map.length; - } - struct Thing * t; - uint8_t clear = 1; - for (t = world.things; t; t = t->next) - { - if (0 != t->lifepoints && pos.y==t->pos.y && pos.x==t->pos.x) - { - clear = 0; - break; - } - } - if (1 == clear) - { - break; - } - } - add_thing(-1, type, pos.y, pos.x); - } -} - - - -extern void own_thing(struct Thing ** target, struct Thing ** source, - uint8_t id) -{ - struct Thing * t; - if (id == (*source)->id) - { - t = * source; - * source = t->next; - } - else - { - struct Thing * penult = * source; - while (1) - { - if (id == penult->next->id) - { - break; - } - penult = penult->next; - } - t = penult->next; - penult->next = t->next; - } - struct Thing ** t_ptr_ptr = target; - for (; * t_ptr_ptr; t_ptr_ptr = &(*t_ptr_ptr)->next); - * t_ptr_ptr = t; - t->next = NULL; -} - - - -extern void set_thing_position(struct Thing * t, struct yx_uint8 pos) -{ - t->pos = pos; - struct Thing * owned = t->owns; - for (; owned; set_thing_position(owned, pos), owned = owned->next); -} diff --git a/src/server/things.h b/src/server/things.h deleted file mode 100644 index 33a07db..0000000 --- a/src/server/things.h +++ /dev/null @@ -1,136 +0,0 @@ -/* src/server/things.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Structs for things and their type and action definitions, and routines to - * initialize these. - */ - -#ifndef THINGS_H -#define THINGS_H - -#include /* uint8_t, int16_t, uint16_t */ -#include "../common/yx_uint8.h" /* yx_uint8 */ - - - -struct Thing -{ - struct Thing * next; - uint8_t id; /* individual thing's unique identifier */ - struct Thing * owns; /* chain of things owned / in inventory */ - struct ThingInMemory * t_mem; /* chain of things remembered */ - struct yx_uint8 pos; /* coordinate on map */ - char * fov_map; /* thing's FOV map; 'v':visible, 'H':hidden */ - char * mem_map; /* map knowledge of thing by FOV and memory */ - char * mem_depth_map; /* map of map memory up-to-dateness */ - int16_t satiation; /* negative: hungry; positive: over-fed */ - uint8_t type; /* ID of appropriate thing definition */ - uint8_t lifepoints; /* 0: thing is inanimate; >0: hitpoints */ - uint8_t command; /* thing's current action; 0 if none */ - uint8_t arg; /* optional field for .command argument */ - uint8_t progress; /* turns already passed to realize .command */ -}; - -struct ThingInMemory -{ - struct ThingInMemory * next; - struct yx_uint8 pos; /* position on memorized */ - uint8_t type; /* thing type identifier */ -}; - -struct ThingType -{ - struct ThingType * next; - uint8_t id; /* thing type identifier / sets .type */ - char char_on_map; /* thing symbol to appear on map */ - char * name; /* string to describe thing in game log */ - uint16_t consumable; /* can be eaten if !0, for so much .satiation win */ - uint8_t corpse_id; /* type to change thing into upon destruction */ - uint8_t lifepoints; /* default start value for thing's .lifepoints */ - uint8_t start_n; /* how many of these does the map start with? */ - uint8_t proliferate; /* if >0: inverse of chance to proliferate */ -}; - -struct ThingAction -{ - struct ThingAction * next; - uint8_t id; /* identifies action in Thing.command; therefore must be >0 */ - void (* func) (struct Thing *); /* function called after .effort turns */ - char * name; /* human-readable identifier */ - uint8_t effort; /* how many turns the action takes */ -}; - - - -/* Add thing action of "id" to world.thing_actions, with .name defaulting to - * s[S_CMD_WAIT], .func to actor_wait() and .effort to 1. If "id" is not >= 1 - * and <= UINT8_MAX, use lowest unused id. Return thing action. - */ -extern struct ThingAction * add_thing_action(uint8_t id); - -/* Add thing type of "id" to world.thing_types, with .corpse_id defaulting to - * the new thing type's .id, .name to "(none)" and the remaining values to 0. If - * "id" is not >= 0 and <= UINT8_MAX, use lowest unused id. Return thing type. - */ -extern struct ThingType * add_thing_type(int16_t id); - -/* Add thing of "id" and "type" on position of "y"/x" to world.things. If "id" - * is not >= 0 and <= UINT8_MAX, use lowest unused id. Build .fov_map if - * world.exists is non-zero. Return thing. - */ -extern struct Thing * add_thing(int16_t id, uint8_t type, uint8_t y, uint8_t x); - -/* Add to thing memory of "t" thing of type id "type" and position "y"/"x". */ -extern void add_thing_to_memory_map(struct Thing * t, uint8_t type, - uint8_t y, uint8_t x); - -/* Free ThingAction / ThingType / Thing / ThingInMemory chain starting at "ta" / - * "tt" / "t" / "tm". - */ -extern void free_thing_actions(struct ThingAction * ta); -extern void free_thing_types(struct ThingType * tt); -extern void free_things(struct Thing * t); -extern void free_things_in_memory(struct ThingInMemory * tm); - -/* Return pointer to ThingAction/ThingType of "id", or NULL if none found. */ -extern struct ThingAction * get_thing_action(uint8_t id); -extern struct ThingType * get_thing_type(uint8_t id); - -/* Return world.thing_actions ThingAction.id for "name" or 0 if none found. */ -extern uint8_t get_thing_action_id_by_name(char * name); - -/* Return thing of "id" in chain at "ptr", search inventories too if "deep". - * Return NULL if nothing found. - */ -extern struct Thing * get_thing(struct Thing * ptr, uint8_t id, uint8_t deep); - -/* Get pointer to the non-owend Thing struct that represents the player, or NULL - * if none found. - */ -extern struct Thing * get_player(); - -/* Try to create "t" offspring on random passable neighbor cell if available - * (and, if "t" is of animate thing type, not inhabited by animate thing) and - * "t"'s type's .proliferation is >0, with a chance of 1/.proliferation. - */ -extern void try_thing_proliferation(struct Thing * t); - -/* Add thing(s) ("n": how many?) of "type" to map on random passable - * position(s). New animate things are never placed in the same square with - * other animate ones. - */ -extern void add_things(uint8_t type, uint8_t n); - -/* Move thing of "id" from "source" inventory to "target" inventory. */ -extern void own_thing(struct Thing ** target, struct Thing ** source, - uint8_t id); - -/* Move not only "t" to "pos", but also all things owned by it. */ -extern void set_thing_position(struct Thing * t, struct yx_uint8 pos); - - - -#endif diff --git a/src/server/world.h b/src/server/world.h deleted file mode 100644 index f6deb10..0000000 --- a/src/server/world.h +++ /dev/null @@ -1,46 +0,0 @@ -/* src/server/world.h - * - * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3 - * or any later version. For details on its copyright, license, and warranties, - * see the file NOTICE in the root directory of the PlomRogue source package. - * - * Contains the World struct holding all game data together. - */ - -#ifndef MAIN_H -#define MAIN_H - -#include /* uint8_t, uint16_t, uint32_t */ -#include /* define FILE */ -#include "../common/map.h" /* struct Map */ -struct ThingType; -struct ThingAction; -struct Thing; - - - -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; /* 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 * server_test; /* String uniquely identifying server process. */ - char * queue; /* Stores un-processed messages read from the input file. */ - 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. */ - uint8_t do_update; /* Update worldstate file if !0. */ - uint8_t exists; /* If !0, remake_world() has been run successfully. */ - uint8_t player_type; /* Thing type that player will start as. */ - uint8_t is_verbose; /* Should server send debugging info to stdout? */ -}; - -extern struct World world; - - - -#endif