home · contact · privacy
7DRL: New tool: fertilizer.
[plomrogue] / roguelike-server
1 #!/usr/bin/python3
2
3 # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
4 # or any later version. For details on its copyright, license, and warranties,
5 # see the file NOTICE in the root directory of the PlomRogue source package.
6
7
8 import argparse
9 import errno
10 import os
11 import shlex
12 import shutil
13 import time
14 import ctypes
15 import math  # #
16
17
18 class RandomnessIO:
19     """"Interface to libplomrogue's pseudo-randomness generator."""
20
21     def set_seed(self, seed):
22         libpr.seed_rrand(1, seed)
23
24     def get_seed(self):
25         return libpr.seed_rrand(0, 0)
26
27     def next(self):
28         return libpr.rrand()
29
30     seed = property(get_seed, set_seed)
31
32
33 def prep_library():
34     """Prepare ctypes library at ./libplomrogue.so"""
35     libpath = ("./libplomrogue.so")
36     if not os.access(libpath, os.F_OK):
37         raise SystemExit("No library " + libpath + ", run ./redo first?")
38     libpr = ctypes.cdll.LoadLibrary(libpath)
39     libpr.seed_rrand.restype = ctypes.c_uint32
40     return libpr
41
42
43 def c_pointer_to_bytearray(ba):
44     """Return C char * pointer to ba."""
45     type = ctypes.c_char * len(ba)
46     return type.from_buffer(ba)
47
48
49 def strong_write(file, string):
50     """Apply write(string), then flush()."""
51     file.write(string)
52     file.flush()
53
54
55 def setup_server_io():
56     """Fill IO files DB with proper file( path)s. Write process IO test string.
57
58     Ensure IO files directory at server/. Remove any old input file if found.
59     Set up new input file for reading, and new output file for writing. Start
60     output file with process hash line of format PID + " " + floated UNIX time
61     (io_db["teststring"]). Raise SystemExit if file is found at path of either
62     record or save file plus io_db["tmp_suffix"].
63     """
64     def detect_atomic_leftover(path, tmp_suffix):
65         path_tmp = path + tmp_suffix
66         msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
67               "aborted previous attempt to write '" + path + "'. Aborting " \
68              "until matter is resolved by removing it from its current path."
69         if os.access(path_tmp, os.F_OK):
70             raise SystemExit(msg)
71     io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
72     io_db["save_wait"] = 0
73     io_db["verbose"] = False
74     io_db["record_chunk"] = ""
75     os.makedirs(io_db["path_server"], exist_ok=True)
76     io_db["file_out"] = open(io_db["path_out"], "w")
77     strong_write(io_db["file_out"], io_db["teststring"] + "\n")
78     if os.access(io_db["path_in"], os.F_OK):
79         os.remove(io_db["path_in"])
80     io_db["file_in"] = open(io_db["path_in"], "w")
81     io_db["file_in"].close()
82     io_db["file_in"] = open(io_db["path_in"], "r")
83     detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
84     detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
85
86
87 def cleanup_server_io():
88     """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
89     def helper(file_key, path_key):
90         if file_key in io_db:
91             io_db[file_key].close()
92         if not io_db["kicked_by_rival"] \
93            and os.access(io_db[path_key], os.F_OK):
94             os.remove(io_db[path_key])
95     helper("file_in", "path_in")
96     helper("file_out", "path_out")
97     helper("file_worldstate", "path_worldstate")
98     if "file_record" in io_db:
99         io_db["file_record"].close()
100
101
102 def log(msg):
103     """Send "msg" to log."""
104     strong_write(io_db["file_out"], "LOG " + msg + "\n")
105
106
107 def obey(command, prefix, replay=False, do_record=False):
108     """Call function from commands_db mapped to command's first token.
109
110     Tokenize command string with shlex.split(comments=True). If replay is set,
111     a non-meta command from the commands_db merely triggers obey() on the next
112     command from the records file. If not, non-meta commands set
113     io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
114     do_record is set, are recorded to io_db["record_chunk"], and save_world()
115     is called (and io_db["record_chunk"] written) if 15 seconds have passed
116     since the last time it was called. The prefix string is inserted into the
117     server's input message between its beginning 'input ' and ':'. All activity
118     is preceded by a server_test() call. Commands that start with a lowercase
119     letter are ignored when world_db["WORLD_ACTIVE"] is False/0.
120     """
121     server_test()
122     if io_db["verbose"]:
123         print("input " + prefix + ": " + command)
124     try:
125         tokens = shlex.split(command, comments=True)
126     except ValueError as err:
127         print("Can't tokenize command string: " + str(err) + ".")
128         return
129     if len(tokens) > 0 and tokens[0] in commands_db \
130        and len(tokens) == commands_db[tokens[0]][0] + 1:
131         if commands_db[tokens[0]][1]:
132             commands_db[tokens[0]][2](*tokens[1:])
133         elif tokens[0][0].islower() and not world_db["WORLD_ACTIVE"]:
134             print("Ignoring lowercase-starting commands when world inactive.")
135         elif replay:
136             print("Due to replay mode, reading command as 'go on in record'.")
137             line = io_db["file_record"].readline()
138             if len(line) > 0:
139                 obey(line.rstrip(), io_db["file_record"].prefix
140                      + str(io_db["file_record"].line_n))
141                 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
142             else:
143                 print("Reached end of record file.")
144         else:
145             commands_db[tokens[0]][2](*tokens[1:])
146             if do_record:
147                 io_db["record_chunk"] += command + "\n"
148                 if time.time() > io_db["save_wait"] + 300:
149                     atomic_write(io_db["path_record"], io_db["record_chunk"],
150                                  do_append=True)
151                     if world_db["WORLD_ACTIVE"]:
152                         save_world()
153                     io_db["record_chunk"] = ""
154                     io_db["save_wait"] = time.time()
155             io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
156     elif 0 != len(tokens):
157         print("Invalid command/argument, or bad number of tokens.")
158
159
160 def atomic_write(path, text, do_append=False, delete=True):
161     """Atomic write of text to file at path, appended if do_append is set."""
162     path_tmp = path + io_db["tmp_suffix"]
163     mode = "w"
164     if do_append:
165         mode = "a"
166         if os.access(path, os.F_OK):
167             shutil.copyfile(path, path_tmp)
168     file = open(path_tmp, mode)
169     strong_write(file, text)
170     file.close()
171     if delete and os.access(path, os.F_OK):
172         os.remove(path)
173     os.rename(path_tmp, path)
174
175
176 def save_world():
177     """Save all commands needed to reconstruct current world state."""
178
179     def quote(string):
180         string = string.replace("\u005C", '\u005C\u005C')
181         return '"' + string.replace('"', '\u005C"') + '"'
182
183     def mapsetter(key):
184         def helper(id=None):
185             string = ""
186             if key == "MAP" or world_db["Things"][id][key]:
187                 map = world_db["MAP"] if key == "MAP" \
188                                       else world_db["Things"][id][key]
189                 length = world_db["MAP_LENGTH"]
190                 for i in range(length):
191                     line = map[i * length:(i * length) + length].decode()
192                     string = string + key + " " + str(i) + " " + quote(line) \
193                              + "\n"
194             return string
195         return helper
196
197     def memthing(id):
198         string = ""
199         for memthing in world_db["Things"][id]["T_MEMTHING"]:
200             string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
201                      str(memthing[1]) + " " + str(memthing[2]) + "\n"
202         return string
203
204     def helper(category, id_string, special_keys={}):
205         string = ""
206         for id in world_db[category]:
207             string = string + id_string + " " + str(id) + "\n"
208             for key in world_db[category][id]:
209                 if not key in special_keys:
210                     x = world_db[category][id][key]
211                     argument = quote(x) if str == type(x) else str(x)
212                     string = string + key + " " + argument + "\n"
213                 elif special_keys[key]:
214                     string = string + special_keys[key](id)
215         return string
216
217     string = ""
218     for key in world_db:
219         if (dict != type(world_db[key])
220             and key != "altar"  # #
221             and key != "MAP" and key != "WORLD_ACTIVE"):
222             string = string + key + " " + str(world_db[key]) + "\n"
223     string = string + mapsetter("MAP")()
224     string = string + helper("ThingActions", "TA_ID")
225     string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
226     for id in world_db["ThingTypes"]:
227         string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
228                  str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
229     string = string + helper("Things", "T_ID",
230                              {"T_CARRIES": False, "carried": False,
231                               "T_MEMMAP": mapsetter("T_MEMMAP"),
232                               "T_MEMTHING": memthing, "fovmap": False,
233                               "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
234     for id in world_db["Things"]:
235         if [] != world_db["Things"][id]["T_CARRIES"]:
236             string = string + "T_ID " + str(id) + "\n"
237             for carried_id in world_db["Things"][id]["T_CARRIES"]:
238                 string = string + "T_CARRIES " + str(carried_id) + "\n"
239     string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
240              "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
241     atomic_write(io_db["path_save"], string)
242
243
244 def obey_lines_in_file(path, name, do_record=False):
245     """Call obey() on each line of path's file, use name in input prefix."""
246     file = open(path, "r")
247     line_n = 1
248     for line in file.readlines():
249         obey(line.rstrip(), name + "file line " + str(line_n),
250              do_record=do_record)
251         line_n = line_n + 1
252     file.close()
253
254
255 def parse_command_line_arguments():
256     """Return settings values read from command line arguments."""
257     parser = argparse.ArgumentParser()
258     parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
259                         action='store')
260     parser.add_argument('-l', nargs="?", const="save", dest='savefile',
261                         action="store")
262     parser.add_argument('-v', dest='verbose', action='store_true')
263     opts, unknown = parser.parse_known_args()
264     return opts
265
266
267 def server_test():
268     """Ensure valid server out file belonging to current process.
269
270     This is done by comparing io_db["teststring"] to what's found at the start
271     of the current file at io_db["path_out"]. On failure, set
272     io_db["kicked_by_rival"] and raise SystemExit.
273     """
274     if not os.access(io_db["path_out"], os.F_OK):
275         raise SystemExit("Server output file has disappeared.")
276     file = open(io_db["path_out"], "r")
277     test = file.readline().rstrip("\n")
278     file.close()
279     if test != io_db["teststring"]:
280         io_db["kicked_by_rival"] = True
281         msg = "Server test string in server output file does not match. This" \
282               " indicates that the current server process has been " \
283               "superseded by another one."
284         raise SystemExit(msg)
285
286
287 def read_command():
288     """Return next newline-delimited command from server in file.
289
290     Keep building return string until a newline is encountered. Pause between
291     unsuccessful reads, and after too much waiting, run server_test().
292     """
293     wait_on_fail = 0.03333
294     max_wait = 5
295     now = time.time()
296     command = ""
297     while True:
298         add = io_db["file_in"].readline()
299         if len(add) > 0:
300             command = command + add
301             if len(command) > 0 and "\n" == command[-1]:
302                 command = command[:-1]
303                 break
304         else:
305             time.sleep(wait_on_fail)
306             if now + max_wait < time.time():
307                 server_test()
308                 now = time.time()
309     return command
310
311
312 def try_worldstate_update():
313     """Write worldstate file if io_db["worldstate_updateable"] is set."""
314     if io_db["worldstate_updateable"]:
315
316         def write_map(string, map):
317             for i in range(length):
318                 line = map[i * length:(i * length) + length].decode()
319                 string = string + line + "\n"
320             return string
321
322         inventory = ""
323         if [] == world_db["Things"][0]["T_CARRIES"]:
324             inventory = "(none)\n"
325         else:
326             for id in world_db["Things"][0]["T_CARRIES"]:
327                 type_id = world_db["Things"][id]["T_TYPE"]
328                 name = world_db["ThingTypes"][type_id]["TT_NAME"]
329                 inventory = inventory + name + "\n"
330         # 7DRL additions:  GOD_MOOD, GOD_FAVOR
331         string = str(world_db["TURN"]) + "\n" + \
332                  str(world_db["GOD_MOOD"]) + "\n" + \
333                  str(world_db["GOD_FAVOR"]) + "\n" + \
334                  str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
335                  str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
336                  inventory + "%\n" + \
337                  str(world_db["Things"][0]["T_POSY"]) + "\n" + \
338                  str(world_db["Things"][0]["T_POSX"]) + "\n" + \
339                  str(world_db["MAP_LENGTH"]) + "\n"
340         length = world_db["MAP_LENGTH"]
341
342         fov = bytearray(b' ' * (length ** 2))
343         ord_v = ord("v")
344         for pos in [pos for pos in range(length ** 2)
345                         if ord_v == world_db["Things"][0]["fovmap"][pos]]:
346             fov[pos] = world_db["MAP"][pos]
347         for id in [id for tid in reversed(sorted(list(world_db["ThingTypes"])))
348                       for id in world_db["Things"]
349                       if not world_db["Things"][id]["carried"]
350                       if world_db["Things"][id]["T_TYPE"] == tid
351                       if world_db["Things"][0]["fovmap"][
352                            world_db["Things"][id]["T_POSY"] * length
353                            + world_db["Things"][id]["T_POSX"]] == ord_v]:
354             type = world_db["Things"][id]["T_TYPE"]
355             c = ord(world_db["ThingTypes"][type]["TT_SYMBOL"])
356             fov[world_db["Things"][id]["T_POSY"] * length
357                 + world_db["Things"][id]["T_POSX"]] = c
358         string = write_map(string, fov)
359
360         mem = world_db["Things"][0]["T_MEMMAP"][:]
361         for mt in [mt for tid in reversed(sorted(list(world_db["ThingTypes"])))
362                       for mt in world_db["Things"][0]["T_MEMTHING"]
363                       if mt[0] == tid]:
364              c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
365              mem[(mt[1] * length) + mt[2]] = ord(c)
366         string = write_map(string, mem)
367
368         metamapA = bytearray(b'0' * (length ** 2))  # #
369         for id in [id for id in world_db["Things"]  # #
370                       if not world_db["Things"][id]["carried"]  # #
371                       if world_db["Things"][id]["T_LIFEPOINTS"]  # #
372                       if world_db["Things"][0]["fovmap"][  # #
373                            world_db["Things"][id]["T_POSY"] * length  # #
374                            + world_db["Things"][id]["T_POSX"]] == ord_v]:  # #
375             pos = (world_db["Things"][id]["T_POSY"] * length  # #
376                   + world_db["Things"][id]["T_POSX"])  # #
377             if id == 0 or world_db["EMPATHY"]:  # #
378                 type = world_db["Things"][id]["T_TYPE"]  # #
379                 max_hp = world_db["ThingTypes"][type]["TT_LIFEPOINTS"]  # #
380                 third_of_hp = max_hp / 3  # #
381                 hp = world_db["Things"][id]["T_LIFEPOINTS"]  # #
382                 add = 0  # #
383                 if hp > 2 * third_of_hp:  # #
384                      add = 2  # #
385                 elif hp > third_of_hp:  # #
386                     add = 1  # #
387                 metamapA[pos] = ord('a') + add # #
388             else:  # #
389                 metamapA[pos] = ord('X')  # #
390         for mt in world_db["Things"][0]["T_MEMTHING"]:  # #
391             pos = mt[1] * length + mt[2]  # #
392             if metamapA[pos] < ord('2'):  # #
393                 metamapA[pos] += 1  # #
394         string = write_map(string, metamapA)  # #
395         
396         metamapB = bytearray(b' ' * (length ** 2))  # #
397         for id in [id for id in world_db["Things"]  # #
398                       if not world_db["Things"][id]["carried"]  # #
399                       if world_db["Things"][id]["T_LIFEPOINTS"]  # #
400                       if world_db["Things"][0]["fovmap"][  # #
401                            world_db["Things"][id]["T_POSY"] * length  # #
402                            + world_db["Things"][id]["T_POSX"]] == ord_v]:  # #
403             pos = (world_db["Things"][id]["T_POSY"] * length  # #
404                   + world_db["Things"][id]["T_POSX"])  # #
405             if id == 0 or world_db["EMPATHY"]:  # #
406                 action = world_db["Things"][id]["T_COMMAND"]  # #
407                 if 0 != action:  # #
408                     name = world_db["ThingActions"][action]["TA_NAME"]  # #
409                 else:  # #
410                     name = " "  # #
411                 metamapB[pos] = ord(name[0]) # #
412         string = write_map(string, metamapB)  # #
413
414         atomic_write(io_db["path_worldstate"], string, delete=False)
415         strong_write(io_db["file_out"], "WORLD_UPDATED\n")
416         io_db["worldstate_updateable"] = False
417
418
419 def replay_game():
420     """Replay game from record file.
421
422     Use opts.replay as breakpoint turn to which to replay automatically before
423     switching to manual input by non-meta commands in server input file
424     triggering further reads of record file. Ensure opts.replay is at least 1.
425     Run try_worldstate_update() before each interactive obey()/read_command().
426     """
427     if opts.replay < 1:
428         opts.replay = 1
429     print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
430           " (if so late a turn is to be found).")
431     if not os.access(io_db["path_record"], os.F_OK):
432         raise SystemExit("No record file found to replay.")
433     io_db["file_record"] = open(io_db["path_record"], "r")
434     io_db["file_record"].prefix = "record file line "
435     io_db["file_record"].line_n = 1
436     while world_db["TURN"] < opts.replay:
437         line = io_db["file_record"].readline()
438         if "" == line:
439             break
440         obey(line.rstrip(), io_db["file_record"].prefix
441              + str(io_db["file_record"].line_n))
442         io_db["file_record"].line_n = io_db["file_record"].line_n + 1
443     while True:
444         try_worldstate_update()
445         obey(read_command(), "in file", replay=True)
446
447
448 def play_game():
449     """Play game by server input file commands. Before, load save file found.
450
451     If no save file is found, a new world is generated from the commands in the
452     world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
453     command and all that follow via the server input file. Run
454     try_worldstate_update() before each interactive obey()/read_command().
455     """
456     if os.access(io_db["path_save"], os.F_OK):
457         obey_lines_in_file(io_db["path_save"], "save")
458     else:
459         if not os.access(io_db["path_worldconf"], os.F_OK):
460             msg = "No world config file from which to start a new world."
461             raise SystemExit(msg)
462         obey_lines_in_file(io_db["path_worldconf"], "world config ",
463                            do_record=True)
464         obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
465     while True:
466         try_worldstate_update()
467         obey(read_command(), "in file", do_record=True)
468
469
470 def make_map():
471     """(Re-)make island map.
472
473     Let "~" represent water, "." land, "X" trees: Build island shape randomly,
474     start with one land cell in the middle, then go into cycle of repeatedly
475     selecting a random sea cell and transforming it into land if it is neighbor
476     to land. The cycle ends when a land cell is due to be created at the map's
477     border. Then put some trees on the map (TODO: more precise algorithm desc).
478     """
479     # 7DRL: Also add some ":" cells, and (not surrounded by trees!) "_" altar.
480
481     def is_neighbor(coordinates, type):
482         y = coordinates[0]
483         x = coordinates[1]
484         length = world_db["MAP_LENGTH"]
485         ind = y % 2
486         diag_west = x + (ind > 0)
487         diag_east = x + (ind < (length - 1))
488         pos = (y * length) + x
489         if (y > 0 and diag_east
490             and type == chr(world_db["MAP"][pos - length + ind])) \
491            or (x < (length - 1)
492                and type == chr(world_db["MAP"][pos + 1])) \
493            or (y < (length - 1) and diag_east
494                and type == chr(world_db["MAP"][pos + length + ind])) \
495            or (y > 0 and diag_west
496                and type == chr(world_db["MAP"][pos - length - (not ind)])) \
497            or (x > 0
498                and type == chr(world_db["MAP"][pos - 1])) \
499            or (y < (length - 1) and diag_west
500                and type == chr(world_db["MAP"][pos + length - (not ind)])):
501             return True
502         return False
503
504     world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
505     length = world_db["MAP_LENGTH"]
506     add_half_width = (not (length % 2)) * int(length / 2)
507     world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
508     while (1):
509         y = rand.next() % length
510         x = rand.next() % length
511         pos = (y * length) + x
512         if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
513             if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
514                 break
515             world_db["MAP"][pos] = ord(".")
516     n_trees = int((length ** 2) / 16)
517     i_trees = 0
518     while (i_trees <= n_trees):
519         single_allowed = rand.next() % 32
520         y = rand.next() % length
521         x = rand.next() % length
522         pos = (y * length) + x
523         if "." == chr(world_db["MAP"][pos]) \
524           and ((not single_allowed) or is_neighbor((y, x), "X")):
525             world_db["MAP"][pos] = ord("X")
526             i_trees += 1
527     # This all-too-precise replica of the original C code misses iter_limit().
528     n_colons = int((length ** 2) / 16)  # #
529     i_colons = 0  # #
530     while (i_colons <= n_colons):  # #
531         single_allowed = rand.next() % 256  # #
532         y = rand.next() % length  # #
533         x = rand.next() % length  # #
534         pos = (y * length) + x  # #
535         if ("." == chr(world_db["MAP"][pos])  # #
536           and ((not single_allowed) or is_neighbor((y, x), ":"))):  # #
537             world_db["MAP"][pos] = ord(":")  # #
538             i_colons += 1  # #
539     altar_placed = False  # #
540     while not altar_placed:  # #
541         y = rand.next() % length  # #
542         x = rand.next() % length  # #
543         pos = (y * length) + x  # #
544         if (("." == chr(world_db["MAP"][pos]  # #
545              or ":" == chr(world_db["MAP"][pos]))
546             and not is_neighbor((y, x), "X"))):  # #
547             world_db["MAP"][pos] = ord("_")  # #
548             world_db["altar"] = (y, x)  # #
549             altar_placed = True  # #
550
551
552 def eat_vs_hunger_threshold(thingtype):
553     """Return satiation cost of eating for type. Good food for it must be >."""
554     hunger_unit = hunger_per_turn(thingtype)
555     actiontype = [id for id in world_db["ThingActions"]
556                if world_db["ThingActions"][id]["TA_NAME"] == "use"][0]
557     return world_db["ThingActions"][actiontype]["TA_EFFORT"] * hunger_unit
558
559
560 def update_map_memory(t, age_map=True):
561     """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
562
563     def age_some_memdepthmap_on_nonfov_cells():
564         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
565         # ord_v = ord("v")
566         # ord_0 = ord("0")
567         # ord_9 = ord("9")
568         # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
569         #             if not ord_v == t["fovmap"][pos]
570         #             if ord_0 <= t["T_MEMDEPTHMAP"][pos]
571         #             if ord_9 > t["T_MEMDEPTHMAP"][pos]
572         #             if not rand.next() % (2 **
573         #                                   (t["T_MEMDEPTHMAP"][pos] - 48))]:
574         #     t["T_MEMDEPTHMAP"][pos] += 1
575         memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
576         fovmap = c_pointer_to_bytearray(t["fovmap"])
577         libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap)
578
579     def update_mem_and_memdepthmap_via_fovmap():
580         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
581         # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
582         #             if ord_v == t["fovmap"][pos]]:
583         #     t["T_MEMDEPTHMAP"][pos] = ord_0
584         #     t["T_MEMMAP"][pos] = world_db["MAP"][pos]
585         memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
586         memmap = c_pointer_to_bytearray(t["T_MEMMAP"])
587         fovmap = c_pointer_to_bytearray(t["fovmap"])
588         map = c_pointer_to_bytearray(world_db["MAP"])
589         libpr.update_mem_and_memdepthmap_via_fovmap(map, fovmap, memdepthmap,
590                                                     memmap)
591
592     if not t["T_MEMMAP"]:
593         t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
594     if not t["T_MEMDEPTHMAP"]:
595         t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
596     update_mem_and_memdepthmap_via_fovmap()
597     if age_map:
598         age_some_memdepthmap_on_nonfov_cells()
599     ord_v = ord("v")
600     t["T_MEMTHING"] = [mt for mt in t["T_MEMTHING"]
601                        if ord_v != t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
602                                                + mt[2]]]
603     maplength = world_db["MAP_LENGTH"]
604     eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])  # #
605     [t["T_MEMTHING"].append((world_db["Things"][id]["T_TYPE"],
606                              world_db["Things"][id]["T_POSY"],
607                              world_db["Things"][id]["T_POSX"]))
608      for id in world_db["Things"]
609      if not world_db["Things"][id]["carried"]
610      if not world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
611                                                               ["TT_LIFEPOINTS"]
612      if (t == world_db["Things"][0] or
613          (world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
614                                                           ["TT_TOOL"] == "food"
615           and 
616           world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
617                                                     ["TT_TOOLPOWER"] > eat_cost
618          )
619         )
620      if ord_v == t["fovmap"][(world_db["Things"][id]["T_POSY"] * maplength)
621                                        + world_db["Things"][id]["T_POSX"]]]
622
623
624 def set_world_inactive():
625     """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
626     server_test()
627     if os.access(io_db["path_worldstate"], os.F_OK):
628         os.remove(io_db["path_worldstate"])
629     world_db["WORLD_ACTIVE"] = 0
630
631
632 def integer_test(val_string, min, max=None):
633     """Return val_string if possible integer >= min and <= max, else None."""
634     try:
635         val = int(val_string)
636         if val < min or (max is not None and val > max):
637             raise ValueError
638         return val
639     except ValueError:
640         msg = "Ignoring: Please use integer >= " + str(min)
641         if max is not None:
642             msg += " and <= " + str(max)
643         msg += "."
644         print(msg)
645         return None
646
647
648 def setter(category, key, min, max=None):
649     """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
650     if category is None:
651         def f(val_string):
652             val = integer_test(val_string, min, max)
653             if None != val:
654                 world_db[key] = val
655     else:
656         if category == "Thing":
657             id_store = command_tid
658             decorator = test_Thing_id
659         elif category == "ThingType":
660             id_store = command_ttid
661             decorator = test_ThingType_id
662         elif category == "ThingAction":
663             id_store = command_taid
664             decorator = test_ThingAction_id
665
666         @decorator
667         def f(val_string):
668             val = integer_test(val_string, min, max)
669             if None != val:
670                 world_db[category + "s"][id_store.id][key] = val
671     return f
672
673
674 def build_fov_map(t):
675     """Build Thing's FOV map."""
676     t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
677     fovmap = c_pointer_to_bytearray(t["fovmap"])
678     map = c_pointer_to_bytearray(world_db["MAP"])
679     if libpr.build_fov_map(t["T_POSY"], t["T_POSX"], fovmap, map):
680         raise RuntimeError("Malloc error in build_fov_Map().")
681
682
683 def log_help():
684     """Send quick usage info to log."""
685     log("Use 'w'/'e'/'s'/'d'/'x'/'c' to move, and 'w' to wait.")
686     log("Use 'p' to pick up objects, and 'D' to drop them.")
687     log("Some objects can be used (such as: eaten) by 'u' if they are in "
688         + "your inventory. Use 'Up'/'Down' to navigate the inventory.")
689     log("Use 'l' to toggle 'look' mode (move an exploration cursor instead of "
690         + "the player over the map).")
691     log("Use 'PgUp'/PgDn' to scroll the 'Things here' window.")
692     log("See README file for more details.")
693
694
695 def decrement_lifepoints(t):
696     """Decrement t's lifepoints by 1, and if to zero, corpse it.
697
698     If t is the player avatar, only blank its fovmap, so that the client may
699     still display memory data. On non-player things, erase fovmap and memory.
700     Dying actors drop all their things.
701     """
702     # 7DRL: Also decrements God's mood; deaths heavily so.
703     # 7DRL: Return 1 if death, else 0.
704     t["T_LIFEPOINTS"] -= 1
705     world_db["GOD_MOOD"] -= 1  # #
706     if 0 == t["T_LIFEPOINTS"]:
707         sadness = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]  # #
708         world_db["GOD_MOOD"] -= sadness  # #        
709         for id in t["T_CARRIES"]:
710             t["T_CARRIES"].remove(id)
711             world_db["Things"][id]["T_POSY"] = t["T_POSY"]
712             world_db["Things"][id]["T_POSX"] = t["T_POSX"]
713             world_db["Things"][id]["carried"] = False
714         t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
715         if world_db["Things"][0] == t:
716             t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
717             log("You die.")
718             log("See README on how to start over.")
719         else:
720             t["fovmap"] = False
721             t["T_MEMMAP"] = False
722             t["T_MEMDEPTHMAP"] = False
723             t["T_MEMTHING"] = []
724         return sadness  # #
725     return 0  # #
726
727
728 def add_gods_favor(i): # #
729     """"Add to GOD_FAVOR, multiplied with factor growing log. with GOD_MOOD."""
730     def favor_multiplier(i):
731         x = 100
732         threshold = math.e * x
733         mood = world_db["GOD_MOOD"]
734         if i > 0:
735             if mood > threshold:
736                 i = i * math.log(mood / x)
737             elif -mood > threshold:
738                 i = i / math.log(-mood / x)
739         elif i < 0:
740             if -mood > threshold:
741                 i = i * math.log(-mood / x)
742             if mood > threshold:
743                 i = i / math.log(mood / x)
744         return int(i)
745     world_db["GOD_FAVOR"] += favor_multiplier(i)
746
747
748 def mv_yx_in_dir_legal(dir, y, x):
749     """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
750     dir_c = dir.encode("ascii")[0]
751     test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
752     if -1 == test:
753         raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
754     return (test, libpr.result_y(), libpr.result_x())
755
756
757 def enter_altar():  # #
758      """What happens when the player enters the altar."""
759      if world_db["FAVOR_STAGE"] > 9000:
760         log("You step on a soul-less slab of stone.")
761         return
762      log("YOU ENTER SACRED GROUND.")
763      if world_db["FAVOR_STAGE"] == 0:
764          world_db["FAVOR_STAGE"] = 1
765          log("The Island God speaks to you: \"I don't trust you. You intrude "
766               + "on the island's affairs. I think you're a nuisance at best, "
767               + "and a danger to my children at worst. I will give you a "
768               + "chance to lighten my mood, however: For a while now, I've "
769               + "been trying to spread the plant "
770               + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"] + " (\""
771               + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_SYMBOL"]
772               + "\"). I have not been very successful so far. Maybe you can "
773               + "make yourself useful there. I will count each further "
774               + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
775               + " that grows to your favor.\"")
776      elif world_db["FAVOR_STAGE"] == 1 and world_db["GOD_FAVOR"] >= 100:
777          world_db["FAVOR_STAGE"] = 2
778          log("The Island God speaks to you: \"You could have done worse so "
779              + "far. Maybe you are not the worst to happen to this island "
780              + "since the metal birds threw the great lightning ball. Maybe "
781              + "you can help me spread another plant. It multiplies faster, "
782              + "and it is highly nutritious: "
783              + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"] + " (\""
784              + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_SYMBOL"]
785              + "\"). It is new. I give you the only example. Be very careful "
786              + "with it! I also give you another tool that may be helpful.\"")
787          id = id_setter(-1, "Things")
788          world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
789                                             world_db["altar"])
790          id = id_setter(-1, "Things")
791          world_db["Things"][id] = new_Thing(world_db["TOOL_0"],
792                                             world_db["altar"])
793      elif world_db["FAVOR_STAGE"] == 2 and \
794           0 == len([id for id in world_db["Things"]
795                    if world_db["Things"][id]["T_TYPE"]
796                       == world_db["PLANT_1"]]):
797          log("The Island God speaks to you: \"I am greatly disappointed that "
798              + "you lost all "
799              + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
800              + " this island had. Here is another one. It cost me great work. "
801              + "Be more careful this time.\"")
802          id = id_setter(-1, "Things")
803          world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
804                                             world_db["altar"])
805          add_gods_favor(-250)
806      elif world_db["GOD_FAVOR"] > 9000:
807          world_db["FAVOR_STAGE"] = 9001
808          log("The Island God speaks to you: \"You have proven yourself worthy"
809               + " of my respect. You were a good citizen to the island, and "
810               + "sometimes a better steward to its inhabitants than me. The "
811               + "island shall miss you when you leave. But you have earned "
812               + "the right to do so. Take this "
813               + world_db["ThingTypes"][world_db["SLIPPERS"]]["TT_NAME"]
814               + " and USE it when you please. It will take you to where you "
815               + "came from. (But do feel free to stay here as long as you "
816               + "like.)\"")
817          id = id_setter(-1, "Things")
818          world_db["Things"][id] = new_Thing(world_db["SLIPPERS"],
819                                             world_db["altar"])
820
821
822 def actor_wait(t):
823     """Make t do nothing (but loudly, if player avatar)."""
824     if t == world_db["Things"][0]:
825         log("You wait.")
826
827
828 def actor_move(t):
829     """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
830     # 7DRL: Player wounding (worse: killing) others will lower God's favor.
831     # 7DRL: Player entering the altar triggers enter_altar().
832     # 7DRL: Player with axe chops down trees.
833     passable = False
834     move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
835                                      t["T_POSY"], t["T_POSX"])
836     if 1 == move_result[0]:
837         pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
838         hitted = [id for id in world_db["Things"]
839                   if world_db["Things"][id] != t
840                   if world_db["Things"][id]["T_LIFEPOINTS"]
841                   if world_db["Things"][id]["T_POSY"] == move_result[1]
842                   if world_db["Things"][id]["T_POSX"] == move_result[2]]
843         if len(hitted):
844             hit_id = hitted[0]
845             if t == world_db["Things"][0]:
846                 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
847                 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
848                 log("You wound " + hitted_name + ".")
849                 add_gods_favor(-1)  # #
850             elif 0 == hit_id:
851                 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
852                 log(hitter_name +" wounds you.")
853             test = decrement_lifepoints(world_db["Things"][hit_id])  # #(test=)
854             if test and t == world_db["Things"][0]:  # #
855                 add_gods_favor(-test)  # #
856             return
857         if (ord("X") == world_db["MAP"][pos]  # #
858             or ord("|") == world_db["MAP"][pos]):  # #
859             carries_axe = False  # #
860             for id in t["T_CARRIES"]:  # # 
861                 type = world_db["Things"][id]["T_TYPE"]  # #
862                 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe":  # #
863                     carries_axe = True  # #
864                     break  # #
865             if carries_axe:  # #
866                 axe_name = world_db["ThingTypes"][type]["TT_NAME"]  # #
867                 if t == world_db["Things"][0]:  # #
868                     log("With your " + axe_name + ", you chop!\n")  # #
869                     if ord("X") == world_db["MAP"][pos]:  # #
870                         add_gods_favor(-1)  # #
871                 chop_power = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
872                 case_X = world_db["MAP"][pos] == ord("X")  # #
873                 if (chop_power > 0  # #
874                     and ((case_X and  # #
875                           0 == int(rand.next() / chop_power))  # #
876                     or (not case_X and  # #
877                              0 == int(rand.next() / (3 * chop_power))))):  # #
878                     if t == world_db["Things"][0]:  # #
879                         log("You chop it down.")  # #
880                     if world_db["MAP"][pos] == ord("X"):  # #
881                         add_gods_favor(-10)  # #
882                     world_db["MAP"][pos] = ord(".")   # #
883                     i = 3 if case_X else 1  # #
884                     for i in range(i):  # #
885                         id = id_setter(-1, "Things")  # #
886                         world_db["Things"][id] = \
887                           new_Thing(world_db["LUMBER"],  # #
888                                     (move_result[1], move_result[2]))  # # 
889                     build_fov_map(t)  # #
890                 return   # #
891         passable = ("." == chr(world_db["MAP"][pos]) or
892                     ":" == chr(world_db["MAP"][pos]) or # #
893                     "_" == chr(world_db["MAP"][pos])) # #
894     dir = [dir for dir in directions_db
895            if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
896     if passable:
897         t["T_POSY"] = move_result[1]
898         t["T_POSX"] = move_result[2]
899         for id in t["T_CARRIES"]:
900             world_db["Things"][id]["T_POSY"] = move_result[1]
901             world_db["Things"][id]["T_POSX"] = move_result[2]
902         build_fov_map(t)
903         if t == world_db["Things"][0]:
904             log("You move " + dir + ".")
905             if (move_result[1] == world_db["altar"][0] and  # #
906                 move_result[2] == world_db["altar"][1]):  # #
907                 enter_altar()  # #
908     elif t == world_db["Things"][0]:
909         log("You fail to move " + dir + ".")
910
911
912 def actor_pick_up(t):
913     """Make t pick up (topmost?) Thing from ground into inventory.
914
915     Define topmostness by how low the thing's type ID is.
916     """
917     # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
918     # 7DRL: Non-players pick up nothing but food of good value to them.
919     used_slots = len(t["T_CARRIES"]) # #
920     if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
921         ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
922                if not world_db["Things"][id]["carried"]
923                if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
924                if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
925         if len(ids):
926             lowest_tid = -1
927             eat_cost = eat_vs_hunger_threshold(t["T_TYPE"]) # #
928             for iid in ids:
929                 tid = world_db["Things"][iid]["T_TYPE"] 
930                 if lowest_tid == -1 or tid < lowest_tid:
931                     if (t != world_db["Things"][0] and  # #
932                         (world_db["ThingTypes"][tid]["TT_TOOL"] != "food"  # #
933                          or (world_db["ThingTypes"][tid]["TT_TOOLPOWER"]  # #
934                              <= eat_cost))):  # #
935                         continue  # #
936                     id = iid
937                     lowest_tid = tid
938             world_db["Things"][id]["carried"] = True
939             type = world_db["Things"][id]["T_TYPE"]  # #
940             if (t != world_db["Things"][0] # #
941                 and world_db["Things"][id]["T_PLAYERDROP"]  # #
942                 and world_db["ThingTypes"][type]["TT_TOOL"] == "food"):  # #
943                 score = world_db["ThingTypes"][type]["TT_TOOLPOWER"] / 32  # #
944                 add_gods_favor(score)  # #
945                 world_db["Things"][id]["T_PLAYERDROP"] = 0  # #
946             t["T_CARRIES"].append(id)
947             if t == world_db["Things"][0]:
948                 log("You pick up an object.")
949         elif t == world_db["Things"][0]:
950             log("You try to pick up an object, but there is none.")
951     elif t == world_db["Things"][0]: # #
952         log("Can't pick up object: No storage room to carry more.") # #
953
954
955 def actor_drop(t):
956     """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
957     # TODO: Handle case where T_ARGUMENT matches nothing.
958     if len(t["T_CARRIES"]):
959         id = t["T_CARRIES"][t["T_ARGUMENT"]]
960         t["T_CARRIES"].remove(id)
961         world_db["Things"][id]["carried"] = False
962         if t == world_db["Things"][0]:
963             log("You drop an object.")
964             world_db["Things"][id]["T_PLAYERDROP"] = 1  # #
965     elif t == world_db["Things"][0]:
966        log("You try to drop an object, but you own none.")
967
968
969 def actor_use(t):
970     """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
971     # TODO: Handle case where T_ARGUMENT matches nothing.
972     # 7DLR: Handle SLIPPERS-type Thing use.
973     if len(t["T_CARRIES"]):
974         id = t["T_CARRIES"][t["T_ARGUMENT"]]
975         type = world_db["Things"][id]["T_TYPE"]
976         if type == world_db["SLIPPERS"]:  # #
977             if t == world_db["Things"][0]:  # #
978                 log("You use the " + world_db["ThingTypes"][type]["TT_NAME"]  # #
979                     + ". It glows in wondrous colors, and emits a sound as "  # #
980                     + "if from a dying cat. The Island God laughs.\n")  # #
981             t["T_LIFEPOINTS"] = 1  # #
982             decrement_lifepoints(t)  # #
983         elif (world_db["ThingTypes"][type]["TT_TOOL"] == "axe"  # #
984               and t == world_db["Things"][0]):  # #
985                 log("To use this item for chopping, move towards a tree while "
986                      + "carrying it in your inventory.")  # #
987         elif (world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry"):  # #
988             pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
989             if (world_db["MAP"][pos] == ord("X")  # #
990                 or world_db["MAP"][pos] == ord("|")):  # #
991                 log("Can't build when standing on barrier.")  # #
992                 return
993             for id in [id for id in world_db["Things"]
994                        if not world_db["Things"][id] == t
995                        if not world_db["Things"][id]["carried"]
996                        if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
997                        if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]:
998                  log("Can't build when standing objects.")  # #
999                  return
1000             for id in t["T_CARRIES"]:  # #
1001                 type_tool = world_db["Things"][id]["T_TYPE"]  # #
1002                 if (world_db["ThingTypes"][type_tool]["TT_TOOL"]  # #
1003                     == "carpentry"):  # #
1004                     break  # #
1005             wood_id = None  # #
1006             for id in t["T_CARRIES"]:  # #
1007                 type_material = world_db["Things"][id]["T_TYPE"]  # #
1008                 if (world_db["ThingTypes"][type_material]["TT_TOOL"]  # #
1009                     == "wood"):  # #
1010                     wood_id = id  # #
1011                     break  # #
1012             if wood_id != None:  # #
1013                 t["T_CARRIES"].remove(wood_id)  # #
1014                 del world_db["Things"][wood_id]  # #
1015                 world_db["MAP"][pos] = ord("|")  # #
1016                 log("With your "  # #
1017                     + world_db["ThingTypes"][type_tool]["TT_NAME"]  # #
1018                     + " you build a wooden barrier from your "  # #
1019                     + world_db["ThingTypes"][type_material]["TT_NAME"]  # #
1020                     + ".")  # #
1021             else:  # #
1022                 log("You can't use a "  # #
1023                     + world_db["ThingTypes"][type_tool]["TT_NAME"]  # #
1024                     + " without some wood in your inventory.")  # #
1025         elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer":  # #
1026             pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1027             if world_db["MAP"][pos] == ord("."):
1028                 world_db["MAP"][pos] = ord(":")
1029             else:
1030                 log("Can only fertilize on unfertilized earth.")
1031         elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
1032             t["T_CARRIES"].remove(id)
1033             del world_db["Things"][id]
1034             t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1035             if t == world_db["Things"][0]:
1036                 log("You consume this object.")
1037         elif t == world_db["Things"][0]:
1038             log("You try to use this object, but fail.")
1039     elif t == world_db["Things"][0]:
1040         log("You try to use an object, but you own none.")
1041
1042
1043 def thingproliferation(t, prol_map):
1044     """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1045
1046     Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1047     marked "." in prol_map. If there are several map cell candidates, one is
1048     selected randomly.
1049     """
1050     # 7DRL: success increments God's mood
1051     # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1052     prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1053     if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
1054         candidates = []
1055         for dir in [directions_db[key] for key in directions_db]:
1056             mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1057             pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1058             if mv_result[0] and \
1059                (ord(":") == prol_map[pos]  # #
1060                 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]  # #
1061                     and ord(".") == prol_map[pos])):
1062             # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1063             #                                          * world_db["MAP_LENGTH"]
1064             #                                          + mv_result[2]]:
1065                 candidates.append((mv_result[1], mv_result[2]))
1066         if len(candidates):
1067             i = rand.next() % len(candidates)
1068             id = id_setter(-1, "Things")
1069             newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1070             world_db["Things"][id] = newT
1071             animacy = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]  # #
1072             if animacy:  # #
1073                 world_db["GOD_MOOD"] += animacy  # #
1074             else:  # #
1075                 world_db["GOD_MOOD"] += 1  # #
1076             if (world_db["FAVOR_STAGE"] > 0  # #
1077                 and t["T_TYPE"] == world_db["PLANT_0"]):  # #
1078                 world_db["GOD_FAVOR"] += 5  # #
1079             elif t["T_TYPE"] == world_db["PLANT_1"]:  # #
1080                 world_db["GOD_FAVOR"] += 25  # #
1081
1082
1083 def try_healing(t):
1084     """If t's HP < max, increment them if well-nourished, maybe waiting."""
1085     # 7DRL: Successful heals increment God's mood.
1086     if t["T_LIFEPOINTS"] < \
1087        world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1088         wait_id = [id for id in world_db["ThingActions"]
1089                       if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1090         wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
1091         testval = int(abs(t["T_SATIATION"]) / wait_divider)
1092         if (testval <= 1 or 1 == (rand.next() % testval)):
1093             t["T_LIFEPOINTS"] += 1
1094             if t != world_db["Things"][0]: # #
1095                  world_db["GOD_MOOD"] += 1  # #
1096             if t == world_db["Things"][0]:
1097                 log("You heal.")
1098
1099
1100 def hunger_per_turn(type_id):
1101     """The amount of satiation score lost per turn for things of given type."""
1102     return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
1103
1104
1105 def hunger(t):
1106     """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1107     if t["T_SATIATION"] > -32768:
1108         #max_hp = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]
1109         t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"]) # int(math.sqrt(max_hp))
1110     if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
1111         if t == world_db["Things"][0]:
1112             if t["T_SATIATION"] < 0:
1113                 log("You suffer from hunger.")
1114             else:
1115                 log("You suffer from over-eating.")
1116         decrement_lifepoints(t)
1117
1118
1119 def get_dir_to_target(t, filter):
1120     """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1121
1122     The path-wise nearest target is chosen, via the shortest available path.
1123     Target must not be t. On succcess, return positive value, else False.
1124     Filters:
1125     "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
1126          is, and its corpse would be healthy food for t
1127     "f": move away from an enemy – any visible actor whose thing type has more
1128          TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
1129          food – if it is closer than n steps, where n will shrink as t's hunger
1130          grows; if enemy is too close, move towards (attack) the enemy instead;
1131          if no fleeing is possible, nor attacking useful, wait; don't tread on
1132          non-enemies for fleeing
1133     "c": Thing in memorized map is consumable of sufficient nutrition for t
1134     "s": memory map cell with greatest-reachable degree of unexploredness
1135     """
1136
1137     def zero_score_map_where_char_on_memdepthmap(c):
1138         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1139         # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1140         #           if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1141         #     set_map_score(i, 0)
1142         map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1143         if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1144             raise RuntimeError("No score map allocated for "
1145                                "zero_score_map_where_char_on_memdepthmap().")
1146
1147     def set_map_score_at_thingpos(id, score):
1148         pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
1149                                      + world_db["Things"][id]["T_POSX"]
1150         set_map_score(pos, score)
1151
1152     def set_map_score(pos, score):
1153         test = libpr.set_map_score(pos, score)
1154         if test:
1155             raise RuntimeError("No score map allocated for set_map_score().")
1156
1157     def get_map_score(pos):
1158         result = libpr.get_map_score(pos)
1159         if result < 0:
1160             raise RuntimeError("No score map allocated for get_map_score().")
1161         return result
1162
1163     def animate_in_fov(Thing, maplength): # maplength needed for optimization?
1164         if not Thing["T_LIFEPOINTS"] or Thing["carried"] or Thing == t:
1165             return False
1166         pos = Thing["T_POSY"] * maplength + Thing["T_POSX"]
1167         if 118 == t["fovmap"][pos]: # optimization: 118 = ord("v")
1168             return True
1169
1170     def good_attack_target(v):
1171         eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1172         type = world_db["ThingTypes"][v["T_TYPE"]]
1173         type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
1174         if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
1175         and type_corpse["TT_TOOL"] == "food" \
1176         and type_corpse["TT_TOOLPOWER"] > eat_cost:
1177             return True
1178         return False
1179
1180     def good_flee_target(m):
1181         own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
1182         corpse_type = world_db["ThingTypes"][own_corpse_id]
1183         targetness = 0 if corpse_type["TT_TOOL"] != "food" \
1184                        else corpse_type["TT_TOOLPOWER"]
1185         type = world_db["ThingTypes"][m["T_TYPE"]]
1186         if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
1187         and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
1188             return True
1189         return False
1190
1191     def seeing_thing():
1192         maplength = world_db["MAP_LENGTH"]
1193         if t["fovmap"] and "a" == filter:
1194             for id in world_db["Things"]:
1195                 if animate_in_fov(world_db["Things"][id], maplength):
1196                     if good_attack_target(world_db["Things"][id]):
1197                         return True
1198         elif t["fovmap"] and "f" == filter:
1199             for id in world_db["Things"]:
1200                 if animate_in_fov(world_db["Things"][id], maplength):
1201                     if good_flee_target(world_db["Things"][id]):
1202                         return True
1203         elif t["T_MEMMAP"] and "c" == filter:
1204             eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1205             ord_blank = ord(" ")
1206             for mt in t["T_MEMTHING"]:
1207                 if ord_blank != t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1208                                             + mt[2]] and \
1209                    (t != world_db["Things"][0] or \
1210                     (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"
1211                       and world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"]
1212                        > eat_cost)):
1213                     return True
1214         return False
1215
1216     def set_cells_passable_on_memmap_to_65534_on_scoremap():
1217         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1218         # ord_dot = ord(".")
1219         # memmap = t["T_MEMMAP"]
1220         # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1221         #            if ord_dot == memmap[i]]:
1222         #     set_map_score(i, 65534) # i.e. 65535-1
1223         map = c_pointer_to_bytearray(t["T_MEMMAP"])
1224         if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1225             raise RuntimeError("No score map allocated for "
1226                         "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1227
1228     def init_score_map():
1229         test = libpr.init_score_map()
1230         if test:
1231             raise RuntimeError("Malloc error in init_score_map().")
1232         set_cells_passable_on_memmap_to_65534_on_scoremap()
1233         maplength = world_db["MAP_LENGTH"]
1234         if "a" == filter:
1235             [set_map_score_at_thingpos(id, 0)
1236              for id in world_db["Things"]
1237              if animate_in_fov(world_db["Things"][id], maplength)
1238              if good_attack_target(world_db["Things"][id])]
1239         elif "f" == filter:
1240             [set_map_score_at_thingpos(id, 0)
1241              for id in world_db["Things"]
1242              if animate_in_fov(world_db["Things"][id], maplength)
1243              if good_flee_target(world_db["Things"][id])]
1244         elif "c" == filter:
1245             eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1246             ord_blank = ord(" ")
1247             [set_map_score(mt[1] * maplength + mt[2], 0)
1248              for mt in t["T_MEMTHING"]
1249              if ord_blank != t["T_MEMMAP"][mt[1] * maplength + mt[2]]
1250              if t != world_db["Things"][0] or
1251                 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food" and
1252                  world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"] > eat_cost)]
1253         elif "s" == filter:
1254             zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1255         if "f" == filter:
1256             [set_map_score_at_thingpos(id, 65535)
1257              for id in world_db["Things"]
1258              if animate_in_fov(world_db["Things"][id], maplength)
1259              if get_map_score(world_db["Things"][id]["T_POSY"] * maplength
1260                               + world_db["Things"][id]["T_POSX"])]
1261         elif "a" != filter:
1262             [set_map_score_at_thingpos(id, 65535)
1263              for id in world_db["Things"]
1264              if animate_in_fov(world_db["Things"][id], maplength)]
1265
1266     def rand_target_dir(neighbors, cmp, dirs):
1267         candidates = []
1268         n_candidates = 0
1269         for i in range(len(dirs)):
1270             if cmp == neighbors[i]:
1271                 candidates.append(dirs[i])
1272                 n_candidates += 1
1273         return candidates[rand.next() % n_candidates] if n_candidates else 0
1274
1275     def get_neighbor_scores(dirs, eye_pos):
1276         scores = []
1277         if libpr.ready_neighbor_scores(eye_pos):
1278             raise RuntimeError("No score map allocated for " +
1279                                "ready_neighbor_scores.()")
1280         for i in range(len(dirs)):
1281             scores.append(libpr.get_neighbor_score(i))
1282         return scores
1283
1284     def get_dir_from_neighbors():
1285         dir_to_target = False
1286         dirs = "edcxsw"
1287         eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1288         neighbors = get_neighbor_scores(dirs, eye_pos)
1289         minmax_start = 0 if "f" == filter else 65535 - 1
1290         minmax_neighbor = minmax_start
1291         for i in range(len(dirs)):
1292             if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1293                 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1294                or ("f" != filter and minmax_neighbor > neighbors[i]):
1295                 minmax_neighbor = neighbors[i]
1296         if minmax_neighbor != minmax_start:
1297             dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1298         if "f" == filter:
1299             distance = get_map_score(eye_pos)
1300             fear_distance = world_db["MAP_LENGTH"]
1301             if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
1302                 fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
1303             attack_distance = 1
1304             if not dir_to_target:
1305                 if attack_distance >= distance:
1306                     dir_to_target = rand_target_dir(neighbors,
1307                                                     distance - 1, dirs)
1308                 elif fear_distance >= distance:
1309                     t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1310                                       if
1311                                       world_db["ThingActions"][id]["TA_NAME"]
1312                                          == "wait"][0]
1313                     return 1
1314             elif dir_to_target and fear_distance < distance:
1315                 dir_to_target = 0
1316         return dir_to_target
1317
1318     dir_to_target = False
1319     mem_depth_c = b' '
1320     run_i = 9 + 1 if "s" == filter else 1
1321     while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1322         run_i -= 1
1323         init_score_map()
1324         mem_depth_c = b'9' if b' ' == mem_depth_c \
1325                            else bytes([mem_depth_c[0] - 1])
1326         if libpr.dijkstra_map():
1327             raise RuntimeError("No score map allocated for dijkstra_map().")
1328         dir_to_target = get_dir_from_neighbors()
1329         libpr.free_score_map()
1330         if dir_to_target and str == type(dir_to_target):
1331             t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1332                               if world_db["ThingActions"][id]["TA_NAME"]
1333                                  == "move"][0]
1334             t["T_ARGUMENT"] = ord(dir_to_target)
1335     return dir_to_target
1336
1337
1338 def standing_on_food(t):
1339     """Return True/False whether t is standing on healthy consumable."""
1340     eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1341     for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1342                if not world_db["Things"][id]["carried"]
1343                if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1344                if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1345                if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1346                   ["TT_TOOL"] == "food"
1347                if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1348                   ["TT_TOOLPOWER"] > eat_cost]:
1349         return True
1350     return False
1351
1352
1353 def get_inventory_slot_to_consume(t):
1354     """Return invent. slot of healthiest consumable(if any healthy),else -1."""
1355     cmp_food = -1
1356     selection = -1
1357     i = 0
1358     eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1359     for id in t["T_CARRIES"]:
1360         type = world_db["Things"][id]["T_TYPE"]
1361         if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1362            and world_db["ThingTypes"][type]["TT_TOOLPOWER"]:
1363             nutvalue = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1364             tmp_cmp = abs(t["T_SATIATION"] + nutvalue - eat_cost)
1365             if (cmp_food < 0 and tmp_cmp < abs(t["T_SATIATION"])) \
1366             or tmp_cmp < cmp_food:
1367                 cmp_food = tmp_cmp
1368                 selection = i
1369         i += 1
1370     return selection
1371
1372
1373 def ai(t):
1374     """Determine next command/argment for actor t via AI algorithms."""
1375     # 7DRL add: Don't pick up or search things when inventory is full.
1376     if t == world_db["Things"][0]:
1377         log("%AI------")
1378     t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1379                       if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1380     if get_dir_to_target(t, "f"):
1381         if t == world_db["Things"][0]:
1382             log("%FLEE")
1383         return
1384     sel = get_inventory_slot_to_consume(t)
1385     if -1 != sel:
1386         if t == world_db["Things"][0]:
1387             log("%EAT")
1388         t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1389                           if world_db["ThingActions"][id]["TA_NAME"]
1390                              == "use"][0]
1391         t["T_ARGUMENT"] = sel
1392     elif standing_on_food(t):
1393         if t == world_db["Things"][0]:
1394             log("%STANDINGON")
1395         if  (len(t["T_CARRIES"]) <  # #
1396              world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]):  # #
1397             if t == world_db["Things"][0]:
1398                 log("%(pickingup)")
1399             t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1400                                  if world_db["ThingActions"][id]["TA_NAME"]
1401                                  == "pick_up"][0]
1402     else:
1403         if t == world_db["Things"][0]:
1404             log("%AIMING_FOR_FOOD")
1405         going_to_known_food_spot = get_dir_to_target(t, "c")
1406         if not going_to_known_food_spot:
1407             if t == world_db["Things"][0]:
1408                 log("%AIMING_FOR_WALKING_FOOD")
1409             aiming_for_walking_food = get_dir_to_target(t, "a")
1410             if not aiming_for_walking_food:
1411                 if t == world_db["Things"][0]:
1412                     log("%SEARCHING")
1413                 get_dir_to_target(t, "s")
1414
1415
1416 def turn_over():
1417     """Run game world and its inhabitants until new player input expected."""
1418     # 7DRL: effort of move action is TA_EFFORT / sqrt(TT_LIFEPOINTS)
1419     id = 0
1420     whilebreaker = False
1421     while world_db["Things"][0]["T_LIFEPOINTS"]:
1422         proliferable_map = world_db["MAP"][:]
1423         for id in [id for id in world_db["Things"]
1424                    if not world_db["Things"][id]["carried"]]:
1425             y = world_db["Things"][id]["T_POSY"]
1426             x = world_db["Things"][id]["T_POSX"]
1427             proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1428         for id in [id for id in world_db["Things"]]:  # Only what's from start!
1429             if not id in world_db["Things"] or \
1430                world_db["Things"][id]["carried"]:   # May have been consumed or
1431                 continue                            # picked up during turn …
1432             Thing = world_db["Things"][id]
1433             if Thing["T_LIFEPOINTS"]:
1434                 if not Thing["T_COMMAND"]:
1435                     update_map_memory(Thing)
1436                     if 0 == id:
1437                         whilebreaker = True
1438                         break
1439                     ai(Thing)
1440                 try_healing(Thing)
1441                 hunger(Thing)
1442                 if Thing["T_LIFEPOINTS"]:
1443                     Thing["T_PROGRESS"] += 1
1444                     taid = [a for a in world_db["ThingActions"]
1445                               if a == Thing["T_COMMAND"]][0]
1446                     ThingAction = world_db["ThingActions"][taid]
1447                     #if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1448                     effort = ThingAction["TA_EFFORT"]  # #
1449                     if ThingAction["TA_NAME"] == "move":  # #
1450                         type = Thing["T_TYPE"]  # #
1451                         max_hp = (world_db["ThingTypes"][type]  # #
1452                                  ["TT_LIFEPOINTS"])  # #
1453                         effort = int(effort / math.sqrt(max_hp))  # #
1454                     if Thing["T_PROGRESS"] == effort:  # #
1455                         eval("actor_" + ThingAction["TA_NAME"])(Thing)
1456                         Thing["T_COMMAND"] = 0
1457                         Thing["T_PROGRESS"] = 0
1458             thingproliferation(Thing, proliferable_map)
1459         if whilebreaker:
1460             break
1461         world_db["TURN"] += 1
1462
1463
1464 def new_Thing(type, pos=(0, 0)):
1465     """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1466     thing = {
1467         "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1468         "T_ARGUMENT": 0,
1469         "T_PROGRESS": 0,
1470         "T_SATIATION": 0,
1471         "T_COMMAND": 0,
1472         "T_PLAYERDROP": 0,  # #
1473         "T_TYPE": type,
1474         "T_POSY": pos[0],
1475         "T_POSX": pos[1],
1476         "T_CARRIES": [],
1477         "carried": False,
1478         "T_MEMTHING": [],
1479         "T_MEMMAP": False,
1480         "T_MEMDEPTHMAP": False,
1481         "fovmap": False
1482     }
1483     if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1484         build_fov_map(thing)
1485     return thing
1486
1487
1488 def id_setter(id, category, id_store=False, start_at_1=False):
1489     """Set ID of object of category to manipulate ID unused? Create new one.
1490
1491     The ID is stored as id_store.id (if id_store is set). If the integer of the
1492     input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1493     <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1494     always returned when no new object is created, otherwise the new object's
1495     ID.
1496     """
1497     min = 0 if start_at_1 else -1
1498     if str == type(id):
1499         id = integer_test(id, min)
1500     if None != id:
1501         if id in world_db[category]:
1502             if id_store:
1503                 id_store.id = id
1504             return None
1505         else:
1506             if (start_at_1 and 0 == id) \
1507                or ((not start_at_1) and (id < 0)):
1508                 id = 0 if start_at_1 else -1
1509                 while 1:
1510                     id = id + 1
1511                     if id not in world_db[category]:
1512                         break
1513             if id_store:
1514                 id_store.id = id
1515     return id
1516
1517
1518 def command_ping():
1519     """Send PONG line to server output file."""
1520     strong_write(io_db["file_out"], "PONG\n")
1521
1522
1523 def command_quit():
1524     """Abort server process."""
1525     if None == opts.replay:
1526         if world_db["WORLD_ACTIVE"]:
1527             save_world()
1528         atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1529     raise SystemExit("received QUIT command")
1530
1531
1532 def command_thingshere(str_y, str_x):
1533     """Write to out file list of Things known to player at coordinate y, x."""
1534     # 7DRL: terrain, too
1535     if world_db["WORLD_ACTIVE"]:
1536         y = integer_test(str_y, 0, 255)
1537         x = integer_test(str_x, 0, 255)
1538         length = world_db["MAP_LENGTH"]
1539         if None != y and None != x and y < length and x < length:
1540             pos = (y * world_db["MAP_LENGTH"]) + x
1541             strong_write(io_db["file_out"], "THINGS_HERE START\n")
1542             pos = y * world_db["MAP_LENGTH"] + x;  # #
1543             if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1544                 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1545                               for id in world_db["Things"]
1546                               if not world_db["Things"][id]["carried"]
1547                               if world_db["Things"][id]["T_TYPE"] == tid
1548                               if y == world_db["Things"][id]["T_POSY"]
1549                               if x == world_db["Things"][id]["T_POSX"]]:
1550                     type = world_db["Things"][id]["T_TYPE"]
1551                     name = world_db["ThingTypes"][type]["TT_NAME"]
1552                     strong_write(io_db["file_out"], name + "\n")
1553             else:
1554                 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1555                               for mt in world_db["Things"][0]["T_MEMTHING"]
1556                               if mt[0] == tid if y == mt[1] if x == mt[2]]:
1557                     name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1558                     strong_write(io_db["file_out"], name + "\n")
1559             if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"):  # #
1560                 name = "(terrain: SEA)"  # #
1561             elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."):  # #
1562                 name = "(terrain: EARTH)"  # #
1563             elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"):  # #
1564                 name = "(terrain: SOIL)"  # #
1565             elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"):  # #
1566                 name = "(terrain: TREE)"  # #
1567             elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"):  # #
1568                 name = "(terrain: WALL)"  # #
1569             elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"):  # #
1570                 name = "(terrain: ALTAR)"  # #
1571             else:  # #
1572                 name = "(?)"  # #
1573             strong_write(io_db["file_out"], name + "\n")  # #
1574             strong_write(io_db["file_out"], "THINGS_HERE END\n")
1575         else:
1576             print("Ignoring: Invalid map coordinates.")
1577     else:
1578         print("Ignoring: Command only works on existing worlds.")
1579
1580
1581 def play_commander(action, args=False):
1582     """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1583
1584     T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1585     """
1586
1587     def set_command():
1588         id = [x for x in world_db["ThingActions"]
1589                 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1590         world_db["Things"][0]["T_COMMAND"] = id
1591         turn_over()
1592
1593     def set_command_and_argument_int(str_arg):
1594         val = integer_test(str_arg, 0, 255)
1595         if None != val:
1596             world_db["Things"][0]["T_ARGUMENT"] = val
1597             set_command()
1598
1599     def set_command_and_argument_movestring(str_arg):
1600         if str_arg in directions_db:
1601             world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1602             set_command()
1603         else:
1604             print("Ignoring: Argument must be valid direction string.")
1605
1606     if action == "move":
1607         return set_command_and_argument_movestring
1608     elif args:
1609         return set_command_and_argument_int
1610     else:
1611         return set_command
1612
1613
1614 def command_seedrandomness(seed_string):
1615     """Set rand seed to int(seed_string)."""
1616     val = integer_test(seed_string, 0, 4294967295)
1617     if None != val:
1618         rand.seed = val
1619
1620
1621 def command_makeworld(seed_string):
1622     """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1623
1624     Seed rand with seed. Do more only with a "wait" ThingAction and
1625     world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1626     world_db["Things"] emptied, call make_map() and set
1627     world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1628     according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1629     of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1630     other. Init player's memory map. Write "NEW_WORLD" line to out file.
1631     Call log_help().
1632     """
1633
1634     # def free_pos(plant=False):
1635     def free_pos(plant=False):  # #
1636         i = 0
1637         while 1:
1638             err = "Space to put thing on too hard to find. Map too small?"
1639             while 1:
1640                 y = rand.next() % world_db["MAP_LENGTH"]
1641                 x = rand.next() % world_db["MAP_LENGTH"]
1642                 pos = y * world_db["MAP_LENGTH"] + x;
1643                 if (not plant  # #
1644                     and "." == chr(world_db["MAP"][pos])) \
1645                    or ":" == chr(world_db["MAP"][pos]):  # #
1646                     break
1647                 i += 1
1648                 if i == 65535:
1649                     raise SystemExit(err)
1650             # Replica of C code, wrongly ignores animatedness of new Thing.
1651             pos_clear = (0 == len([id for id in world_db["Things"]
1652                                    if world_db["Things"][id]["T_LIFEPOINTS"]
1653                                    if world_db["Things"][id]["T_POSY"] == y
1654                                    if world_db["Things"][id]["T_POSX"] == x]))
1655             if pos_clear:
1656                 break
1657         return (y, x)
1658
1659     val = integer_test(seed_string, 0, 4294967295)
1660     if None == val:
1661         return
1662     rand.seed = val
1663     player_will_be_generated = False
1664     playertype = world_db["PLAYER_TYPE"]
1665     for ThingType in world_db["ThingTypes"]:
1666         if playertype == ThingType:
1667             if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1668                 player_will_be_generated = True
1669             break
1670     if not player_will_be_generated:
1671         print("Ignoring: No player type with start number >0 defined.")
1672         return
1673     wait_action = False
1674     for ThingAction in world_db["ThingActions"]:
1675         if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1676             wait_action = True
1677     if not wait_action:
1678         print("Ignoring: No thing action with name 'wait' defined.")
1679         return
1680     for name in specials:  # #
1681         if world_db[name] not in world_db["ThingTypes"]:  # #
1682             print("Ignoring: No valid " + name + " set.")  # #
1683             return  # #
1684     world_db["Things"] = {}
1685     make_map()
1686     world_db["WORLD_ACTIVE"] = 1
1687     world_db["TURN"] = 1
1688     for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1689         id = id_setter(-1, "Things")
1690         world_db["Things"][id] = new_Thing(playertype, free_pos())
1691     if not world_db["Things"][0]["fovmap"]:
1692         empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1693         world_db["Things"][0]["fovmap"] = empty_fovmap
1694     update_map_memory(world_db["Things"][0])
1695     for type in world_db["ThingTypes"]:
1696         for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1697             if type != playertype:
1698                 id = id_setter(-1, "Things")
1699                 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"]  # #
1700                 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1701     strong_write(io_db["file_out"], "NEW_WORLD\n")
1702     log_help()
1703
1704
1705 def command_maplength(maplength_string):
1706     """Redefine map length. Invalidate map, therefore lose all things on it."""
1707     val = integer_test(maplength_string, 1, 256)
1708     if None != val:
1709         world_db["MAP_LENGTH"] = val
1710         world_db["MAP"] = False
1711         set_world_inactive()
1712         world_db["Things"] = {}
1713         libpr.set_maplength(val)
1714
1715
1716 def command_worldactive(worldactive_string):
1717     """Toggle world_db["WORLD_ACTIVE"] if possible.
1718
1719     An active world can always be set inactive. An inactive world can only be
1720     set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1721     map. On activation, rebuild all Things' FOVs, and the player's map memory.
1722     Also call log_help().
1723     """
1724     # 7DRL: altar must be on map, and specials must be set for active world.
1725     val = integer_test(worldactive_string, 0, 1)
1726     if None != val:
1727         if 0 != world_db["WORLD_ACTIVE"]:
1728             if 0 == val:
1729                 set_world_inactive()
1730             else:
1731                 print("World already active.")
1732         elif 0 == world_db["WORLD_ACTIVE"]:
1733             wait_exists = False
1734             for ThingAction in world_db["ThingActions"]:
1735                 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1736                     wait_exists = True
1737                     break
1738             player_exists = False
1739             for Thing in world_db["Things"]:
1740                 if 0 == Thing:
1741                     player_exists = True
1742                     break
1743             specials_set = True  # #
1744             for name in specials:  # #
1745                 if world_db[name] not in world_db["ThingTypes"]:  # #
1746                     specials_set = False  # #
1747             altar_found = False  # #
1748             if world_db["MAP"]:  # #
1749                 pos = world_db["MAP"].find(b'_')  # #
1750                 if pos > 0:  # #
1751                     y = int(pos / world_db["MAP_LENGTH"])  # #
1752                     x = pos % world_db["MAP_LENGTH"]  # #
1753                     world_db["altar"] = (y, x)  # #
1754                     altar_found = True  # #
1755             if wait_exists and player_exists and world_db["MAP"] \
1756                and specials_set:  # #
1757                 for id in world_db["Things"]:
1758                     if world_db["Things"][id]["T_LIFEPOINTS"]:
1759                         build_fov_map(world_db["Things"][id])
1760                         if 0 == id:
1761                             update_map_memory(world_db["Things"][id], False)
1762                 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1763                     empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1764                     world_db["Things"][0]["fovmap"] = empty_fovmap
1765                 world_db["WORLD_ACTIVE"] = 1
1766                 log_help()
1767             else:
1768                 print("Ignoring: Not all conditions for world activation met.")
1769
1770
1771 def specialtypesetter(name):  # #
1772     """Setter world_db[name], deactivating world if set int no ThingType."""
1773     def helper(str_int):
1774         val = integer_test(str_int, 0)
1775         if None != val:
1776             world_db[name] = val
1777             if world_db["WORLD_ACTIVE"] \
1778                and world_db[name] not in world_db["ThingTypes"]:
1779                 world_db["WORLD_ACTIVE"] = 0
1780                 print(name + " fits no known ThingType, deactivating world.")
1781     return helper
1782
1783
1784 def test_for_id_maker(object, category):
1785     """Return decorator testing for object having "id" attribute."""
1786     def decorator(f):
1787         def helper(*args):
1788             if hasattr(object, "id"):
1789                 f(*args)
1790             else:
1791                 print("Ignoring: No " + category +
1792                       " defined to manipulate yet.")
1793         return helper
1794     return decorator
1795
1796
1797 def command_tid(id_string):
1798     """Set ID of Thing to manipulate. ID unused? Create new one.
1799
1800     Default new Thing's type to the first available ThingType, others: zero.
1801     """
1802     id = id_setter(id_string, "Things", command_tid)
1803     if None != id:
1804         if world_db["ThingTypes"] == {}:
1805             print("Ignoring: No ThingType to settle new Thing in.")
1806             return
1807         type = list(world_db["ThingTypes"].keys())[0]
1808         world_db["Things"][id] = new_Thing(type)
1809
1810
1811 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1812
1813
1814 @test_Thing_id
1815 def command_tcommand(str_int):
1816     """Set T_COMMAND of selected Thing."""
1817     val = integer_test(str_int, 0)
1818     if None != val:
1819         if 0 == val or val in world_db["ThingActions"]:
1820             world_db["Things"][command_tid.id]["T_COMMAND"] = val
1821         else:
1822             print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1823
1824
1825 @test_Thing_id
1826 def command_ttype(str_int):
1827     """Set T_TYPE of selected Thing."""
1828     val = integer_test(str_int, 0)
1829     if None != val:
1830         if val in world_db["ThingTypes"]:
1831             world_db["Things"][command_tid.id]["T_TYPE"] = val
1832         else:
1833             print("Ignoring: ThingType ID belongs to no known ThingType.")
1834
1835
1836 @test_Thing_id
1837 def command_tcarries(str_int):
1838     """Append int(str_int) to T_CARRIES of selected Thing.
1839
1840     The ID int(str_int) must not be of the selected Thing, and must belong to a
1841     Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1842     """
1843     val = integer_test(str_int, 0)
1844     if None != val:
1845         if val == command_tid.id:
1846             print("Ignoring: Thing cannot carry itself.")
1847         elif val in world_db["Things"] \
1848              and not world_db["Things"][val]["carried"]:
1849             world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1850             world_db["Things"][val]["carried"] = True
1851         else:
1852             print("Ignoring: Thing not available for carrying.")
1853     # Note that the whole carrying structure is different from the C version:
1854     # Carried-ness is marked by a "carried" flag, not by Things containing
1855     # Things internally.
1856
1857
1858 @test_Thing_id
1859 def command_tmemthing(str_t, str_y, str_x):
1860     """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1861
1862     The type must fit to an existing ThingType, and the position into the map.
1863     """
1864     type = integer_test(str_t, 0)
1865     posy = integer_test(str_y, 0, 255)
1866     posx = integer_test(str_x, 0, 255)
1867     if None != type and None != posy and None != posx:
1868         if type not in world_db["ThingTypes"] \
1869            or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1870             print("Ignoring: Illegal value for thing type or position.")
1871         else:
1872             memthing = (type, posy, posx)
1873             world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1874
1875
1876 def setter_map(maptype):
1877     """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1878
1879     If no map of maptype exists yet, initialize it with ' ' bytes first.
1880     """
1881
1882     def valid_map_line(str_int, mapline):
1883         val = integer_test(str_int, 0, 255)
1884         if None != val:
1885             if val >= world_db["MAP_LENGTH"]:
1886                 print("Illegal value for map line number.")
1887             elif len(mapline) != world_db["MAP_LENGTH"]:
1888                 print("Map line length is unequal map width.")
1889             else:
1890                 return val
1891         return None
1892
1893     def nonThingMap_helper(str_int, mapline):
1894         val = valid_map_line(str_int, mapline)
1895         if None != val:
1896             length = world_db["MAP_LENGTH"]
1897             if not world_db["MAP"]:
1898                 map = bytearray(b' ' * (length ** 2))
1899             else:
1900                 map = world_db["MAP"]
1901             map[val * length:(val * length) + length] = mapline.encode()
1902             if not world_db["MAP"]:
1903                 world_db["MAP"] = map
1904
1905     @test_Thing_id
1906     def ThingMap_helper(str_int, mapline):
1907         val = valid_map_line(str_int, mapline)
1908         if None != val:
1909             length = world_db["MAP_LENGTH"]
1910             if not world_db["Things"][command_tid.id][maptype]:
1911                 map = bytearray(b' ' * (length ** 2))
1912             else:
1913                 map = world_db["Things"][command_tid.id][maptype]
1914             map[val * length:(val * length) + length] = mapline.encode()
1915             if not world_db["Things"][command_tid.id][maptype]:
1916                 world_db["Things"][command_tid.id][maptype] = map
1917
1918     return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
1919
1920
1921 def setter_tpos(axis):
1922     """Generate setter for T_POSX or  T_POSY of selected Thing.
1923
1924     If world is active, rebuilds animate things' fovmap, player's memory map.
1925     """
1926     @test_Thing_id
1927     def helper(str_int):
1928         val = integer_test(str_int, 0, 255)
1929         if None != val:
1930             if val < world_db["MAP_LENGTH"]:
1931                 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1932                 if world_db["WORLD_ACTIVE"] \
1933                    and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1934                     build_fov_map(world_db["Things"][command_tid.id])
1935                     if 0 == command_tid.id:
1936                         update_map_memory(world_db["Things"][command_tid.id])
1937             else:
1938                 print("Ignoring: Position is outside of map.")
1939     return helper
1940
1941
1942 def command_ttid(id_string):
1943     """Set ID of ThingType to manipulate. ID unused? Create new one.
1944
1945     Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
1946     "", others: 0.
1947     """
1948     id = id_setter(id_string, "ThingTypes", command_ttid)
1949     if None != id:
1950         world_db["ThingTypes"][id] = {
1951             "TT_NAME": "(none)",
1952             "TT_TOOLPOWER": 0,
1953             "TT_LIFEPOINTS": 0,
1954             "TT_PROLIFERATE": 0,
1955             "TT_START_NUMBER": 0,
1956             "TT_STORAGE": 0, # #
1957             "TT_SYMBOL": "?",
1958             "TT_CORPSE_ID": id,
1959             "TT_TOOL": ""
1960         }
1961
1962
1963 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1964
1965
1966 @test_ThingType_id
1967 def command_ttname(name):
1968     """Set TT_NAME of selected ThingType."""
1969     world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1970
1971
1972 @test_ThingType_id
1973 def command_tttool(name):
1974     """Set TT_TOOL of selected ThingType."""
1975     world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
1976
1977
1978 @test_ThingType_id
1979 def command_ttsymbol(char):
1980     """Set TT_SYMBOL of selected ThingType. """
1981     if 1 == len(char):
1982         world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1983     else:
1984         print("Ignoring: Argument must be single character.")
1985
1986
1987 @test_ThingType_id
1988 def command_ttcorpseid(str_int):
1989     """Set TT_CORPSE_ID of selected ThingType."""
1990     val = integer_test(str_int, 0)
1991     if None != val:
1992         if val in world_db["ThingTypes"]:
1993             world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1994         else:
1995             print("Ignoring: Corpse ID belongs to no known ThignType.")
1996
1997
1998 def command_taid(id_string):
1999     """Set ID of ThingAction to manipulate. ID unused? Create new one.
2000
2001     Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
2002     """
2003     id = id_setter(id_string, "ThingActions", command_taid, True)
2004     if None != id:
2005         world_db["ThingActions"][id] = {
2006             "TA_EFFORT": 1,
2007             "TA_NAME": "wait"
2008         }
2009
2010
2011 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
2012
2013
2014 @test_ThingAction_id
2015 def command_taname(name):
2016     """Set TA_NAME of selected ThingAction.
2017
2018     The name must match a valid thing action function. If after the name
2019     setting no ThingAction with name "wait" remains, call set_world_inactive().
2020     """
2021     if name == "wait" or name == "move" or name == "use" or name == "drop" \
2022        or name == "pick_up":
2023         world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
2024         if 1 == world_db["WORLD_ACTIVE"]:
2025             wait_defined = False
2026             for id in world_db["ThingActions"]:
2027                 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
2028                     wait_defined = True
2029                     break
2030             if not wait_defined:
2031                 set_world_inactive()
2032     else:
2033         print("Ignoring: Invalid action name.")
2034     # In contrast to the original,naming won't map a function to a ThingAction.
2035
2036
2037 def command_ai():
2038     """Call ai() on player Thing, then turn_over()."""
2039     ai(world_db["Things"][0])
2040     turn_over()
2041
2042
2043 """Commands database.
2044
2045 Map command start tokens to ([0]) number of expected command arguments, ([1])
2046 the command's meta-ness (i.e. is it to be written to the record file, is it to
2047 be ignored in replay mode if read from server input file), and ([2]) a function
2048 to be called on it.
2049 """
2050 commands_db = {
2051     "QUIT": (0, True, command_quit),
2052     "PING": (0, True, command_ping),
2053     "THINGS_HERE": (2, True, command_thingshere),
2054     "MAKE_WORLD": (1, False, command_makeworld),
2055     "SEED_RANDOMNESS": (1, False, command_seedrandomness),
2056     "TURN": (1, False, setter(None, "TURN", 0, 65535)),
2057     "GOD_MOOD": (1, False, setter(None, "GOD_MOOD", -32768, 32767)),  # #
2058     "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)),  # #
2059     "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
2060     "MAP_LENGTH": (1, False, command_maplength),
2061     "WORLD_ACTIVE": (1, False, command_worldactive),
2062     "MAP": (2, False, setter_map("MAP")),
2063     "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)),  # #
2064     "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")),  # #
2065     "PLANT_0": (1, False, specialtypesetter("PLANT_0")),  # #
2066     "PLANT_1": (1, False, specialtypesetter("PLANT_1")),  # #
2067     "LUMBER": (1, False, specialtypesetter("LUMBER")),  # #
2068     "TOOL_0": (1, False, specialtypesetter("TOOL_0")),  # #
2069     "TOOL_1": (1, False, specialtypesetter("TOOL_1")),  # #
2070     "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)),  # #
2071     "TA_ID": (1, False, command_taid),
2072     "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
2073     "TA_NAME": (1, False, command_taname),
2074     "TT_ID": (1, False, command_ttid),
2075     "TT_NAME": (1, False, command_ttname),
2076     "TT_TOOL": (1, False, command_tttool),
2077     "TT_SYMBOL": (1, False, command_ttsymbol),
2078     "TT_CORPSE_ID": (1, False, command_ttcorpseid),
2079     "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2080     "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2081                                          0, 255)),
2082     "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2083                                         0, 65535)),
2084     "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2085     "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)),  # #
2086     "T_ID": (1, False, command_tid),
2087     "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2088     "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2089     "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2090     "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2091     "T_COMMAND": (1, False, command_tcommand),
2092     "T_TYPE": (1, False, command_ttype),
2093     "T_CARRIES": (1, False, command_tcarries),
2094     "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2095     "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2096     "T_MEMTHING": (3, False, command_tmemthing),
2097     "T_POSY": (1, False, setter_tpos("Y")),
2098     "T_POSX": (1, False, setter_tpos("X")),
2099     "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)),  # #
2100     "wait": (0, False, play_commander("wait")),
2101     "move": (1, False, play_commander("move")),
2102     "pick_up": (0, False, play_commander("pick_up")),
2103     "drop": (1, False, play_commander("drop", True)),
2104     "use": (1, False, play_commander("use", True)),
2105     "ai": (0, False, command_ai)
2106 }
2107 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2108
2109
2110 """World state database. With sane default values. (Randomness is in rand.)"""
2111 world_db = {
2112     "TURN": 0,
2113     "MAP_LENGTH": 64,
2114     "PLAYER_TYPE": 0,
2115     "WORLD_ACTIVE": 0,
2116     "GOD_MOOD": 0,  # #
2117     "GOD_FAVOR": 0,  # #
2118     "MAP": False,
2119     "FAVOR_STAGE": 0,  # #
2120     "SLIPPERS": 0,  # #
2121     "PLANT_0": 0,  # #
2122     "PLANT_1": 0,  # #
2123     "LUMBER": 0,  # #
2124     "TOOL_0": 0,  # #
2125     "TOOL_1": 0,  # #
2126     "EMPATHY": 1,  # #
2127     "ThingActions": {},
2128     "ThingTypes": {},
2129     "Things": {}
2130 }
2131
2132 # 7DRL-specific!
2133 """Special type settings."""
2134 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0", "TOOL_1"]  # #
2135
2136 """Mapping of direction names to internal direction chars."""
2137 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2138                  "west": "s", "north-west": "w", "north-east": "e"}
2139
2140 """File IO database."""
2141 io_db = {
2142     "path_save": "save",
2143     "path_record": "record_save",
2144     "path_worldconf": "confserver/world",
2145     "path_server": "server/",
2146     "path_in": "server/in",
2147     "path_out": "server/out",
2148     "path_worldstate": "server/worldstate",
2149     "tmp_suffix": "_tmp",
2150     "kicked_by_rival": False,
2151     "worldstate_updateable": False
2152 }
2153
2154
2155 try:
2156     libpr = prep_library()
2157     rand = RandomnessIO()
2158     opts = parse_command_line_arguments()
2159     if opts.savefile:
2160         io_db["path_save"] = opts.savefile
2161         io_db["path_record"] = "record_" + opts.savefile
2162     setup_server_io()
2163     if opts.verbose:
2164         io_db["verbose"] = True
2165     if None != opts.replay:
2166         replay_game()
2167     else:
2168         play_game()
2169 except SystemExit as exit:
2170     print("ABORTING: " + exit.args[0])
2171 except:
2172     print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
2173     raise
2174 finally:
2175     cleanup_server_io()