-# redo build file to build executables "roguelike-server", "roguelike-client".
+# redo build file to build "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 libplomrogue.so
redo-ifchange roguelike-client
# 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'
+++ /dev/null
-#!/bin/sh
-gcc -shared -fPIC -std=c11 -pedantic-errors -Wall -Werror -Wextra -Wformat-security -O3 -o libplomrogue.so libplomrogue.c -lm
+++ /dev/null
-#!/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_EXISTS"], 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 world_db[category]:
- string = string + id_string + " " + str(id) + "\n"
- for key in world_db[category][id]:
- 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 world_db:
- 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 world_db["ThingTypes"]:
- 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 world_db["Things"]:
- if [] != world_db["Things"][id]["T_CARRIES"]:
- string = string + "T_ID " + str(id) + "\n"
- for carried_id in world_db["Things"][id]["T_CARRIES"]:
- string = string + "T_CARRIES " + str(carried_id) + "\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 possible integer >= min and <= 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, otherwise 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
- 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()
# <https://github.com/plomlompom/plomrogue/issues/2#issuecomment-50972436> 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 "$@"
# 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
--- /dev/null
+#!/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_EXISTS"], 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 world_db[category]:
+ string = string + id_string + " " + str(id) + "\n"
+ for key in world_db[category][id]:
+ 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 world_db:
+ 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 world_db["ThingTypes"]:
+ 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 world_db["Things"]:
+ if [] != world_db["Things"][id]["T_CARRIES"]:
+ string = string + "T_ID " + str(id) + "\n"
+ for carried_id in world_db["Things"][id]["T_CARRIES"]:
+ string = string + "T_CARRIES " + str(carried_id) + "\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 possible integer >= min and <= 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, otherwise 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
+ 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()
+++ /dev/null
-# 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
+++ /dev/null
-#!/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
+++ /dev/null
-/* 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 <stdint.h> /* uint8_t, uint16_t, uint32_t, int16_t, UINT16_MAX */
-#include <stdlib.h> /* 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');
- }
- }
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 <stdint.h> /* uint32_t */
-#include <stdlib.h> /* free() */
-#include <unistd.h> /* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 <math.h> /* pow() */
-#include <stddef.h> /* NULL */
-#include <stdint.h> /* uint8_t, uint16_t, uint32_t, int32_t, UINT8_MAX */
-#include <stdlib.h> /* free() */
-#include <string.h> /* 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);
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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 <stddef.h> /* NULL */
-#include <stdint.h> /* uint8_t */
-#include <stdlib.h> /* atoi(), free() */
-#include <string.h> /* strcmp(), memset(), memcpy() */
-#include <unistd.h> /* 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;
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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";
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 <errno.h> /* global errno, EEXIST */
-#include <stddef.h> /* NULL */
-#include <stdint.h> /* uint32_t */
-#include <stdio.h> /* FILE, sprintf(), fflush() */
-#include <stdlib.h> /* exit(), free(), atoi() */
-#include <string.h> /* strlen() */
-#include <sys/stat.h> /* mkdir() */
-#include <sys/types.h> /* defines pid_t, time_t */
-#include <time.h> /* time() */
-#include <unistd.h> /* 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);
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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 <errno.h> /* global errno */
-#include <limits.h> /* PIPE_BUF */
-#include <stddef.h> /* NULL */
-#include <stdint.h> /* uint8_t, uint16_t, uint32_t, int32_t, UINT8_MAX */
-#include <stdio.h> /* defines FILE, sprintf(), fprintf() */
-#include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), snprintf(), memcpy(), strchr() */
-#include <sys/types.h> /* time_t */
-#include <time.h> /* 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);
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 <stdio.h> /* printf() */
-#include <stdlib.h> /* 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);
-}
+++ /dev/null
-/* 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 <stdint.h> /* uint8_t, int8_t, uint16_t, uint32_t, (U)INT*_(MIN|MAX) */
-#include <stdlib.h> /* free() */
-#include <string.h> /* 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);
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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 <stdint.h> /* 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. */
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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 <stddef.h> /* NULL */
-#include <stdint.h> /* uint8_t, uint16_t, uint32_t, int16_t */
-#include <stdio.h> /* FILE, printf(), fflush() */
-#include <stdlib.h> /* atoi(), free() */
-#include <string.h> /* strlen(), strcmp(), strncmp(), strdup() */
-#include <time.h> /* time_t, time() */
-#include <unistd.h> /* 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;
- }
- }
- }
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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 <stddef.h> /* NULL */
-#include <stdint.h> /* uint8_t, INT16_MIN, INT16_MAX */
-#include <stdio.h> /* sprintf() */
-#include <stdlib.h> /* free() */
-#include <string.h> /* 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);
- }
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 <stddef.h> /* NULL, size_t */
-#include <stdint.h> /* uint8_t, uint16_t, int16_t, UINT8_MAX, UINT16_MAX */
-#include <stdlib.h> /* free() */
-#include <string.h> /* 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);
-}
+++ /dev/null
-/* 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 <stdint.h> /* 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
+++ /dev/null
-/* 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 <stdint.h> /* uint8_t, uint16_t, uint32_t */
-#include <stdio.h> /* 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
fi
# Give helpful message to players that want to start without compiling first.
-if [ ! -e ./roguelike-server ]
-then
- echo 'No ./roguelike-server file found to execute. Try "./redo" first?'
- false
-fi
if [ ! -e ./roguelike-client ]
then
echo 'No ./roguelike-client file found to execute. Try "./redo" first?'
+++ /dev/null
-#!/bin/sh
-
-# Abort the script on error.
-set -e
-
-# Don't let any log leftovers from before interfere.
-if [ -e ./log ]
-then
- rm log
-fi
-
-# Give helpful message to players that want to start without compiling first.
-if [ ! -e ./roguelike-client ]
-then
- echo 'No ./roguelike-client file found to execute. Try "./redo" first?'
- false
-fi
-
-# Use shell script's arguments for server and pipe server output to log file.
-# This script's wrapper script will read it out on exit.
-python3 ./plomrogue-server.py "$@" > log 2>&1 &
-
-# Give server some time to start up and exit on error.
-sleep 0.01
-
-# The client should not start if the server is not running. (If the server was
-# running in the foreground, any error exit of it so far would be caught by "set
-# -e" above. But "set -e" is blind to error codes generated in the background.)
-kill -0 $! 2> /dev/null
-
-# Give server some time (max. 10 seconds) to generate its worldstate file.
-i=0
-while [ ! -e server/worldstate ] && [ $i -le 1000 ]
-do
- sleep 0.01
- i=`expr $i + 1`
-done
-if [ ! -e server/worldstate ]
-then
- echo "Server failed generating worldstate file within given time limit."
- false
-fi
-
-# Only start the interface when everything else went well.
-kill -0 $! 2> /dev/null
-./roguelike-client