home · contact · privacy
Let actor_pick_up pick up most nutritious Thing as topmost Thing.
[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 obey(command, prefix, replay=False, do_record=False):
103     """Call function from commands_db mapped to command's first token.
104
105     Tokenize command string with shlex.split(comments=True). If replay is set,
106     a non-meta command from the commands_db merely triggers obey() on the next
107     command from the records file. If not, non-meta commands set
108     io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
109     do_record is set, are recorded to io_db["record_chunk"], and save_world()
110     is called (and io_db["record_chunk"] written) if 15 seconds have passed
111     since the last time it was called. The prefix string is inserted into the
112     server's input message between its beginning 'input ' and ':'. All activity
113     is preceded by a server_test() call.
114     """
115     server_test()
116     if io_db["verbose"]:
117         print("input " + prefix + ": " + command)
118     try:
119         tokens = shlex.split(command, comments=True)
120     except ValueError as err:
121         print("Can't tokenize command string: " + str(err) + ".")
122         return
123     if len(tokens) > 0 and tokens[0] in commands_db \
124        and len(tokens) == commands_db[tokens[0]][0] + 1:
125         if commands_db[tokens[0]][1]:
126             commands_db[tokens[0]][2](*tokens[1:])
127         elif replay:
128             print("Due to replay mode, reading command as 'go on in record'.")
129             line = io_db["file_record"].readline()
130             if len(line) > 0:
131                 obey(line.rstrip(), io_db["file_record"].prefix
132                      + str(io_db["file_record"].line_n))
133                 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
134             else:
135                 print("Reached end of record file.")
136         else:
137             commands_db[tokens[0]][2](*tokens[1:])
138             if do_record:
139                 io_db["record_chunk"] += command + "\n"
140                 if time.time() > io_db["save_wait"] + 15:
141                     atomic_write(io_db["path_record"], io_db["record_chunk"],
142                                  do_append=True)
143                     save_world()
144                     io_db["record_chunk"] = ""
145                     io_db["save_wait"] = time.time()
146             io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
147     elif 0 != len(tokens):
148         print("Invalid command/argument, or bad number of tokens.")
149
150
151 def atomic_write(path, text, do_append=False, delete=True):
152     """Atomic write of text to file at path, appended if do_append is set."""
153     path_tmp = path + io_db["tmp_suffix"]
154     mode = "w"
155     if do_append:
156         mode = "a"
157         if os.access(path, os.F_OK):
158             shutil.copyfile(path, path_tmp)
159     file = open(path_tmp, mode)
160     strong_write(file, text)
161     file.close()
162     if delete and os.access(path, os.F_OK):
163         os.remove(path)
164     os.rename(path_tmp, path)
165
166
167 def save_world():
168     """Save all commands needed to reconstruct current world state."""
169
170     def quote(string):
171         string = string.replace("\u005C", '\u005C\u005C')
172         return '"' + string.replace('"', '\u005C"') + '"'
173
174     def mapsetter(key):
175         def helper(id):
176             string = ""
177             if world_db["Things"][id][key]:
178                 map = world_db["Things"][id][key]
179                 length = world_db["MAP_LENGTH"]
180                 for i in range(length):
181                     line = map[i * length:(i * length) + length].decode()
182                     string = string + key + " " + str(i) + " " + quote(line) \
183                              + "\n"
184             return string
185         return helper
186
187     def memthing(id):
188         string = ""
189         for memthing in world_db["Things"][id]["T_MEMTHING"]:
190             string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
191                      str(memthing[1]) + " " + str(memthing[2]) + "\n"
192         return string
193
194     def helper(category, id_string, special_keys={}):
195         string = ""
196         for id in world_db[category]:
197             string = string + id_string + " " + str(id) + "\n"
198             for key in world_db[category][id]:
199                 if not key in special_keys:
200                     x = world_db[category][id][key]
201                     argument = quote(x) if str == type(x) else str(x)
202                     string = string + key + " " + argument + "\n"
203                 elif special_keys[key]:
204                     string = string + special_keys[key](id)
205         return string
206
207     string = ""
208     for key in world_db:
209         if dict != type(world_db[key]) and key != "MAP" and \
210            key != "WORLD_ACTIVE" and key != "SEED_MAP":
211             string = string + key + " " + str(world_db[key]) + "\n"
212     string = string + "SEED_MAP " + str(world_db["SEED_MAP"]) + "\n"
213     string = string + helper("ThingActions", "TA_ID")
214     string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
215     for id in world_db["ThingTypes"]:
216         string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
217                  str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
218     string = string + helper("Things", "T_ID",
219                              {"T_CARRIES": False, "carried": False,
220                               "T_MEMMAP": mapsetter("T_MEMMAP"),
221                               "T_MEMTHING": memthing, "fovmap": False,
222                               "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
223     for id in world_db["Things"]:
224         if [] != world_db["Things"][id]["T_CARRIES"]:
225             string = string + "T_ID " + str(id) + "\n"
226             for carried_id in world_db["Things"][id]["T_CARRIES"]:
227                 string = string + "T_CARRIES " + str(carried_id) + "\n"
228     string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
229              "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
230     atomic_write(io_db["path_save"], string)
231
232
233 def obey_lines_in_file(path, name, do_record=False):
234     """Call obey() on each line of path's file, use name in input prefix."""
235     file = open(path, "r")
236     line_n = 1
237     for line in file.readlines():
238         obey(line.rstrip(), name + "file line " + str(line_n),
239              do_record=do_record)
240         line_n = line_n + 1
241     file.close()
242
243
244 def parse_command_line_arguments():
245     """Return settings values read from command line arguments."""
246     parser = argparse.ArgumentParser()
247     parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
248                         action='store')
249     parser.add_argument('-l', nargs="?", const="save", dest='savefile',
250                         action="store")
251     parser.add_argument('-v', dest='verbose', action='store_true')
252     opts, unknown = parser.parse_known_args()
253     return opts
254
255
256 def server_test():
257     """Ensure valid server out file belonging to current process.
258
259     This is done by comparing io_db["teststring"] to what's found at the start
260     of the current file at io_db["path_out"]. On failure, set
261     io_db["kicked_by_rival"] and raise SystemExit.
262     """
263     if not os.access(io_db["path_out"], os.F_OK):
264         raise SystemExit("Server output file has disappeared.")
265     file = open(io_db["path_out"], "r")
266     test = file.readline().rstrip("\n")
267     file.close()
268     if test != io_db["teststring"]:
269         io_db["kicked_by_rival"] = True
270         msg = "Server test string in server output file does not match. This" \
271               " indicates that the current server process has been " \
272               "superseded by another one."
273         raise SystemExit(msg)
274
275
276 def read_command():
277     """Return next newline-delimited command from server in file.
278
279     Keep building return string until a newline is encountered. Pause between
280     unsuccessful reads, and after too much waiting, run server_test().
281     """
282     wait_on_fail = 0.03333
283     max_wait = 5
284     now = time.time()
285     command = ""
286     while True:
287         add = io_db["file_in"].readline()
288         if len(add) > 0:
289             command = command + add
290             if len(command) > 0 and "\n" == command[-1]:
291                 command = command[:-1]
292                 break
293         else:
294             time.sleep(wait_on_fail)
295             if now + max_wait < time.time():
296                 server_test()
297                 now = time.time()
298     return command
299
300
301 def try_worldstate_update():
302     """Write worldstate file if io_db["worldstate_updateable"] is set."""
303     if io_db["worldstate_updateable"]:
304
305         def draw_visible_Things(map, run):
306             for id in world_db["Things"]:
307                 type = world_db["Things"][id]["T_TYPE"]
308                 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
309                 alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"]
310                 if (0 == run and not consumable and not alive) \
311                    or (1 == run and consumable and not alive) \
312                    or (2 == run and alive):
313                     y = world_db["Things"][id]["T_POSY"]
314                     x = world_db["Things"][id]["T_POSX"]
315                     fovflag = world_db["Things"][0]["fovmap"][(y * length) + x]
316                     if 'v' == chr(fovflag):
317                         c = world_db["ThingTypes"][type]["TT_SYMBOL"]
318                         map[(y * length) + x] = ord(c)
319
320         def write_map(string, map):
321             for i in range(length):
322                 line = map[i * length:(i * length) + length].decode()
323                 string = string + line + "\n"
324             return string
325
326         inventory = ""
327         if [] == world_db["Things"][0]["T_CARRIES"]:
328             inventory = "(none)\n"
329         else:
330             for id in world_db["Things"][0]["T_CARRIES"]:
331                 type_id = world_db["Things"][id]["T_TYPE"]
332                 name = world_db["ThingTypes"][type_id]["TT_NAME"]
333                 inventory = inventory + name + "\n"
334         # # 7DRL additions:  GOD_MOOD, GOD_FAVOR
335         string = str(world_db["TURN"]) + "\n" + \
336                  str(world_db["GOD_MOOD"]) + "\n" + \
337                  str(world_db["GOD_FAVOR"]) + "\n" + \
338                  str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
339                  str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
340                  inventory + "%\n" + \
341                  str(world_db["Things"][0]["T_POSY"]) + "\n" + \
342                  str(world_db["Things"][0]["T_POSX"]) + "\n" + \
343                  str(world_db["MAP_LENGTH"]) + "\n"
344         length = world_db["MAP_LENGTH"]
345         fov = bytearray(b' ' * (length ** 2))
346         for pos in range(length ** 2):
347             if 'v' == chr(world_db["Things"][0]["fovmap"][pos]):
348                 fov[pos] = world_db["MAP"][pos]
349         for i in range(3):
350             draw_visible_Things(fov, i)
351         string = write_map(string, fov)
352         mem = world_db["Things"][0]["T_MEMMAP"][:]
353         for i in range(2):
354             for mt in world_db["Things"][0]["T_MEMTHING"]:
355                 consumable = world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]
356                 if (i == 0 and not consumable) or (i == 1 and consumable):
357                     c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
358                     mem[(mt[1] * length) + mt[2]] = ord(c)
359         string = write_map(string, mem)
360         atomic_write(io_db["path_worldstate"], string, delete=False)
361         strong_write(io_db["file_out"], "WORLD_UPDATED\n")
362         io_db["worldstate_updateable"] = False
363
364
365 def replay_game():
366     """Replay game from record file.
367
368     Use opts.replay as breakpoint turn to which to replay automatically before
369     switching to manual input by non-meta commands in server input file
370     triggering further reads of record file. Ensure opts.replay is at least 1.
371     Run try_worldstate_update() before each interactive obey()/read_command().
372     """
373     if opts.replay < 1:
374         opts.replay = 1
375     print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
376           " (if so late a turn is to be found).")
377     if not os.access(io_db["path_record"], os.F_OK):
378         raise SystemExit("No record file found to replay.")
379     io_db["file_record"] = open(io_db["path_record"], "r")
380     io_db["file_record"].prefix = "record file line "
381     io_db["file_record"].line_n = 1
382     while world_db["TURN"] < opts.replay:
383         line = io_db["file_record"].readline()
384         if "" == line:
385             break
386         obey(line.rstrip(), io_db["file_record"].prefix
387              + str(io_db["file_record"].line_n))
388         io_db["file_record"].line_n = io_db["file_record"].line_n + 1
389     while True:
390         try_worldstate_update()
391         obey(read_command(), "in file", replay=True)
392
393
394 def play_game():
395     """Play game by server input file commands. Before, load save file found.
396
397     If no save file is found, a new world is generated from the commands in the
398     world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
399     command and all that follow via the server input file. Run
400     try_worldstate_update() before each interactive obey()/read_command().
401     """
402     if os.access(io_db["path_save"], os.F_OK):
403         obey_lines_in_file(io_db["path_save"], "save")
404     else:
405         if not os.access(io_db["path_worldconf"], os.F_OK):
406             msg = "No world config file from which to start a new world."
407             raise SystemExit(msg)
408         obey_lines_in_file(io_db["path_worldconf"], "world config ",
409                            do_record=True)
410         obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
411     while True:
412         try_worldstate_update()
413         obey(read_command(), "in file", do_record=True)
414
415
416 def remake_map():
417     """(Re-)make island map.
418
419     Let "~" represent water, "." land, "X" trees: Build island shape randomly,
420     start with one land cell in the middle, then go into cycle of repeatedly
421     selecting a random sea cell and transforming it into land if it is neighbor
422     to land. The cycle ends when a land cell is due to be created at the map's
423     border. Then put some trees on the map (TODO: more precise algorithm desc).
424     """
425     def is_neighbor(coordinates, type):
426         y = coordinates[0]
427         x = coordinates[1]
428         length = world_db["MAP_LENGTH"]
429         ind = y % 2
430         diag_west = x + (ind > 0)
431         diag_east = x + (ind < (length - 1))
432         pos = (y * length) + x
433         if (y > 0 and diag_east
434             and type == chr(world_db["MAP"][pos - length + ind])) \
435            or (x < (length - 1)
436                and type == chr(world_db["MAP"][pos + 1])) \
437            or (y < (length - 1) and diag_east
438                and type == chr(world_db["MAP"][pos + length + ind])) \
439            or (y > 0 and diag_west
440                and type == chr(world_db["MAP"][pos - length - (not ind)])) \
441            or (x > 0
442                and type == chr(world_db["MAP"][pos - 1])) \
443            or (y < (length - 1) and diag_west
444                and type == chr(world_db["MAP"][pos + length - (not ind)])):
445             return True
446         return False
447     store_seed = rand.seed
448     rand.seed = world_db["SEED_MAP"]
449     world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
450     length = world_db["MAP_LENGTH"]
451     add_half_width = (not (length % 2)) * int(length / 2)
452     world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
453     while (1):
454         y = rand.next() % length
455         x = rand.next() % length
456         pos = (y * length) + x
457         if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
458             if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
459                 break
460             world_db["MAP"][pos] = ord(".")
461     n_trees = int((length ** 2) / 16)
462     i_trees = 0
463     while (i_trees <= n_trees):
464         single_allowed = rand.next() % 32
465         y = rand.next() % length
466         x = rand.next() % length
467         pos = (y * length) + x
468         if "." == chr(world_db["MAP"][pos]) \
469           and ((not single_allowed) or is_neighbor((y, x), "X")):
470             world_db["MAP"][pos] = ord("X")
471             i_trees += 1
472     rand.seed = store_seed
473     # This all-too-precise replica of the original C code misses iter_limit().
474
475
476 def update_map_memory(t, age_map=True):
477     """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
478     def age_some_memdepthmap_on_nonfov_cells():
479         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
480         # ord_v = ord("v")
481         # ord_0 = ord("0")
482         # ord_9 = ord("9")
483         # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
484         #             if not ord_v == t["fovmap"][pos]
485         #             if ord_0 <= t["T_MEMDEPTHMAP"][pos]
486         #             if ord_9 > t["T_MEMDEPTHMAP"][pos]
487         #             if not rand.next() % (2 **
488         #                                   (t["T_MEMDEPTHMAP"][pos] - 48))]:
489         #     t["T_MEMDEPTHMAP"][pos] += 1
490         memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
491         fovmap = c_pointer_to_bytearray(t["fovmap"])
492         libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap)
493     if not t["T_MEMMAP"]:
494         t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
495     if not t["T_MEMDEPTHMAP"]:
496         t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
497     ord_v = ord("v")
498     ord_0 = ord("0")
499     ord_space = ord(" ")
500     for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
501                 if ord_v == t["fovmap"][pos]]:
502         t["T_MEMDEPTHMAP"][pos] = ord_0
503         if ord_space == t["T_MEMMAP"][pos]:
504             t["T_MEMMAP"][pos] = world_db["MAP"][pos]
505     if age_map:
506         age_some_memdepthmap_on_nonfov_cells()
507     for mt in [mt for mt in t["T_MEMTHING"]
508                if ord_v == t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
509                                        + mt[2]]]:
510         t["T_MEMTHING"].remove(mt)
511     for id in [id for id in world_db["Things"]
512                if not world_db["Things"][id]["carried"]]:
513         type = world_db["Things"][id]["T_TYPE"]
514         if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
515             y = world_db["Things"][id]["T_POSY"]
516             x = world_db["Things"][id]["T_POSX"]
517             if ord_v == t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]:
518                 t["T_MEMTHING"].append((type, y, x))
519
520
521 def set_world_inactive():
522     """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
523     server_test()
524     if os.access(io_db["path_worldstate"], os.F_OK):
525         os.remove(io_db["path_worldstate"])
526     world_db["WORLD_ACTIVE"] = 0
527
528
529 def integer_test(val_string, min, max=None):
530     """Return val_string if possible integer >= min and <= max, else None."""
531     try:
532         val = int(val_string)
533         if val < min or (max is not None and val > max):
534             raise ValueError
535         return val
536     except ValueError:
537         msg = "Ignoring: Please use integer >= " + str(min)
538         if max is not None:
539             msg += " and <= " + str(max)
540         msg += "."
541         print(msg)
542         return None
543
544
545 def setter(category, key, min, max=None):
546     """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
547     if category is None:
548         def f(val_string):
549             val = integer_test(val_string, min, max)
550             if None != val:
551                 world_db[key] = val
552     else:
553         if category == "Thing":
554             id_store = command_tid
555             decorator = test_Thing_id
556         elif category == "ThingType":
557             id_store = command_ttid
558             decorator = test_ThingType_id
559         elif category == "ThingAction":
560             id_store = command_taid
561             decorator = test_ThingAction_id
562
563         @decorator
564         def f(val_string):
565             val = integer_test(val_string, min, max)
566             if None != val:
567                 world_db[category + "s"][id_store.id][key] = val
568     return f
569
570
571 def build_fov_map(t):
572     """Build Thing's FOV map."""
573     t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
574     fovmap = c_pointer_to_bytearray(t["fovmap"])
575     map = c_pointer_to_bytearray(world_db["MAP"])
576     if libpr.build_fov_map(t["T_POSY"], t["T_POSX"], fovmap, map):
577         raise RuntimeError("Malloc error in build_fov_Map().")
578
579
580 def decrement_lifepoints(t):
581     """Decrement t's lifepoints by 1, and if to zero, corpse it.
582
583     If t is the player avatar, only blank its fovmap, so that the client may
584     still display memory data. On non-player things, erase fovmap and memory.
585     Dying actors drop all their things.
586     """
587     # # 7DRL: also decrements God's mood; deaths heavily so
588     # # 7DRL: return 1 if death, else 0
589     t["T_LIFEPOINTS"] -= 1
590     world_db["GOD_MOOD"] -= 1  # #
591     if 0 == t["T_LIFEPOINTS"]:
592         sadness = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]  # #
593         world_db["GOD_MOOD"] -= sadness  # #        
594         for id in t["T_CARRIES"]:
595             t["T_CARRIES"].remove(id)
596             world_db["Things"][id]["T_POSY"] = t["T_POSY"]
597             world_db["Things"][id]["T_POSX"] = t["T_POSX"]
598             world_db["Things"][id]["carried"] = False
599         t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
600         if world_db["Things"][0] == t:
601             t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
602             strong_write(io_db["file_out"], "LOG You die.\n")
603         else:
604             t["fovmap"] = False
605             t["T_MEMMAP"] = False
606             t["T_MEMDEPTHMAP"] = False
607             t["T_MEMTHING"] = []
608         return sadness  # #
609     return 0  # #
610
611
612 def add_gods_favor(i): # #
613     """"Add to GOD_FAVOR, multiplied with factor growing log. with GOD_MOOD."""
614     def favor_multiplier(i):
615         x = 100
616         threshold = math.e * x
617         mood = world_db["GOD_MOOD"]
618         if i > 0:
619             if mood > threshold:
620                 i = i * math.log(mood / x)
621             elif -mood > threshold:
622                 i = i / math.log(-mood / x)
623         elif i < 0:
624             if -mood > threshold:
625                 i = i * math.log(-mood / x)
626             if mood > threshold:
627                 i = i / math.log(mood / x)
628         return int(i)
629     world_db["GOD_FAVOR"] += favor_multiplier(i)
630
631
632 def mv_yx_in_dir_legal(dir, y, x):
633     """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
634     dir_c = dir.encode("ascii")[0]
635     test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
636     if -1 == test:
637         raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
638     return (test, libpr.result_y(), libpr.result_x())
639
640
641 def actor_wait(t):
642     """Make t do nothing (but loudly, if player avatar)."""
643     if t == world_db["Things"][0]:
644         strong_write(io_db["file_out"], "LOG You wait.\n")
645
646
647 def actor_move(t):
648     """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
649     # # 7DRL: Player wounding (worse: killing) others will lower God's favor.
650     passable = False
651     move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
652                                      t["T_POSY"], t["T_POSX"])
653     if 1 == move_result[0]:
654         pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
655         passable = "." == chr(world_db["MAP"][pos])
656         hitted = [id for id in world_db["Things"]
657                   if world_db["Things"][id] != t
658                   if world_db["Things"][id]["T_LIFEPOINTS"]
659                   if world_db["Things"][id]["T_POSY"] == move_result[1]
660                   if world_db["Things"][id]["T_POSX"] == move_result[2]]
661         if len(hitted):
662             hit_id = hitted[0]
663             if t == world_db["Things"][0]:
664                 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
665                 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
666                 strong_write(io_db["file_out"], "LOG You wound " + hitted_name
667                                                 + ".\n")
668                 add_gods_favor(-1)  # #
669             elif 0 == hit_id:
670                 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
671                 strong_write(io_db["file_out"], "LOG " + hitter_name +
672                                                 " wounds you.\n")
673             test = decrement_lifepoints(world_db["Things"][hit_id])  # #(test=)
674             if test and t == world_db["Things"][0]:  # #
675                 add_gods_favor(-test)  # #
676             return
677     dir = [dir for dir in directions_db
678            if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
679     if passable:
680         t["T_POSY"] = move_result[1]
681         t["T_POSX"] = move_result[2]
682         for id in t["T_CARRIES"]:
683             world_db["Things"][id]["T_POSY"] = move_result[1]
684             world_db["Things"][id]["T_POSX"] = move_result[2]
685         build_fov_map(t)
686         if t == world_db["Things"][0]:
687             strong_write(io_db["file_out"], "LOG You move " + dir + ".\n")
688     elif t == world_db["Things"][0]:
689         strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n")
690
691
692 def actor_pick_up(t):
693     """Make t pick up (topmost?) Thing from ground into inventory."""
694     # Topmostness is actually not defined so far. Picks most nutritious Thing.
695     used_slots = len(t["T_CARRIES"]) # #
696     if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
697         ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
698                if not world_db["Things"][id]["carried"]
699                if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
700                if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
701         if len(ids):
702             highest_id = ids[0]
703             nutritious = 0
704             for id in ids:
705                 type = world_db["Things"][id]["T_TYPE"]
706                 if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > nutritious:
707                     nutritious = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
708                     highest_id = id
709             world_db["Things"][highest_id]["carried"] = True
710             #if (t != world_db["Things"][0] and  # #
711             #    world_db["Things"][highest_id]["T_PLAYERDROP"]):  # #
712             #    x = world_db["Things"][highest_id]["T_TYPE"]
713             #    score = world_db["ThingTypes"][x]["TT_CONSUMABLE"] / 32  # #
714             #    add_gods_favor(score)  # #
715             #    world_db["Things"][highest_id]["T_PLAYERDROP"] = 0  # #
716             t["T_CARRIES"].append(highest_id)
717             if t == world_db["Things"][0]:
718                 strong_write(io_db["file_out"], "LOG You pick up an object.\n")
719         elif t == world_db["Things"][0]:
720             err = "You try to pick up an object, but there is none."
721             strong_write(io_db["file_out"], "LOG " + err + "\n")
722     elif t == world_db["Things"][0]: # #
723         strong_write(io_db["file_out"], "LOG Can't pick up object: " + # #
724                                         "No storage room to carry more.\n") # #
725
726
727 def actor_drop(t):
728     """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
729     # TODO: Handle case where T_ARGUMENT matches nothing.
730     if len(t["T_CARRIES"]):
731         id = t["T_CARRIES"][t["T_ARGUMENT"]]
732         t["T_CARRIES"].remove(id)
733         world_db["Things"][id]["carried"] = False
734         if t == world_db["Things"][0]:
735             strong_write(io_db["file_out"], "LOG You drop an object.\n")
736     elif t == world_db["Things"][0]:
737         err = "You try to drop an object, but you own none."
738         strong_write(io_db["file_out"], "LOG " + err + "\n")
739
740
741 def actor_use(t):
742     """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
743     # TODO: Handle case where T_ARGUMENT matches nothing.
744     if len(t["T_CARRIES"]):
745         id = t["T_CARRIES"][t["T_ARGUMENT"]]
746         type = world_db["Things"][id]["T_TYPE"]
747         if world_db["ThingTypes"][type]["TT_CONSUMABLE"]:
748             t["T_CARRIES"].remove(id)
749             del world_db["Things"][id]
750             t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"]
751             if t == world_db["Things"][0]:
752                 strong_write(io_db["file_out"],
753                              "LOG You consume this object.\n")
754         elif t == world_db["Things"][0]:
755             strong_write(io_db["file_out"],
756                          "LOG You try to use this object, but fail.\n")
757     elif t == world_db["Things"][0]:
758         strong_write(io_db["file_out"],
759                      "LOG You try to use an object, but you own none.\n")
760
761
762 def thingproliferation(t):
763     """To chance of 1/TT_PROLIFERATE, create t offspring in neighbor cell.
764
765     Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be
766     passable and not be inhabited by a Thing of the same type, or, if Thing is
767     animate, any other animate Thing. If there are several map cell candidates,
768     one is selected randomly.
769     """
770     # # 7DRL: success increments God's mood
771     def test_cell(t, y, x):
772         if "." == chr(world_db["MAP"][(y * world_db["MAP_LENGTH"]) + x]):
773             for id in [id for id in world_db["Things"]
774                        if y == world_db["Things"][id]["T_POSY"]
775                        if x == world_db["Things"][id]["T_POSX"]
776                        if (t["T_TYPE"] == world_db["Things"][id]["T_TYPE"])
777                        or (t["T_LIFEPOINTS"] and
778                            world_db["Things"][id]["T_LIFEPOINTS"])]:
779                 return False
780             return True
781         return False
782     prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
783     if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
784         candidates = []
785         for dir in [directions_db[key] for key in directions_db]:
786             mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
787             if mv_result[0] and test_cell(t, mv_result[1], mv_result[2]):
788                 candidates.append((mv_result[1], mv_result[2]))
789         if len(candidates):
790             i = rand.next() % len(candidates)
791             id = id_setter(-1, "Things")
792             newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
793             world_db["Things"][id] = newT
794             world_db["GOD_MOOD"] += 1  # #
795
796
797 def try_healing(t):
798     """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting.
799
800     On success, decrease satiation score by 32.
801     """
802     # # 7DRL: Successful heals increment God's mood.
803     if t["T_SATIATION"] > 0 \
804        and t["T_LIFEPOINTS"] < \
805            world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \
806        and 0 == (rand.next() % 31) \
807        and t["T_COMMAND"] == [id for id in world_db["ThingActions"]
808                               if world_db["ThingActions"][id]["TA_NAME"] ==
809                                  "wait"][0]:
810         t["T_LIFEPOINTS"] += 1
811         world_db["GOD_MOOD"] += 1  # #
812         t["T_SATIATION"] -= 32
813         if t == world_db["Things"][0]:
814             strong_write(io_db["file_out"], "LOG You heal.\n")
815
816
817 def hunger(t):
818     """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
819     if t["T_SATIATION"] > -32768:
820         t["T_SATIATION"] -= 1
821     testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"]
822     if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
823         raise RuntimeError("A thing that should not hunger is hungering.")
824     stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"])
825     if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)):
826         if t == world_db["Things"][0]:
827             strong_write(io_db["file_out"], "LOG You suffer from hunger.\n")
828         decrement_lifepoints(t)
829
830
831 def get_dir_to_target(t, filter):
832     """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
833
834     The path-wise nearest target is chosen, via the shortest available path.
835     Target must not be t. On succcess, return positive value, else False.
836     Filters:
837     "a": Thing in FOV is below a certain distance, animate, but of ThingType
838          that is not t's, and starts out weaker than t is; build path as
839          avoiding things of t's ThingType
840     "f": neighbor cell (not inhabited by any animate Thing) further away from
841          animate Thing not further than x steps away and in FOV and of a
842          ThingType that is not t's, and starts out stronger or as strong as t
843          is currently; or (cornered), if no such flight cell, but Thing of
844          above criteria is too near,1 a cell closer to it, or, if less near,
845          just wait
846     "c": Thing in memorized map is consumable
847     "s": memory map cell with greatest-reachable degree of unexploredness
848     """
849
850     def zero_score_map_where_char_on_memdepthmap(c):
851         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
852         # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
853         #           if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
854         #     set_map_score(i, 0)
855         map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
856         if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
857             raise RuntimeError("No score map allocated for "
858                                "zero_score_map_where_char_on_memdepthmap().")
859
860     def set_map_score(pos, score):
861         test = libpr.set_map_score(pos, score)
862         if test:
863             raise RuntimeError("No score map allocated for set_map_score().")
864
865     def get_map_score(pos):
866         result = libpr.get_map_score(pos)
867         if result < 0:
868             raise RuntimeError("No score map allocated for get_map_score().")
869         return result
870
871     def seeing_thing():
872         if t["fovmap"] and ("a" == filter or "f" == filter):
873             for id in world_db["Things"]:
874                 Thing = world_db["Things"][id]
875                 if Thing != t and Thing["T_LIFEPOINTS"] and \
876                    t["T_TYPE"] != Thing["T_TYPE"] and \
877                    'v' == chr(t["fovmap"][(Thing["T_POSY"]
878                                           * world_db["MAP_LENGTH"])
879                                           + Thing["T_POSX"]]):
880                     ThingType = world_db["ThingTypes"][Thing["T_TYPE"]]
881                     if ("f" == filter and ThingType["TT_LIFEPOINTS"] >=
882                                           t["T_LIFEPOINTS"]) \
883                        or ("a" == filter and ThingType["TT_LIFEPOINTS"] <
884                                              t["T_LIFEPOINTS"]):
885                         return True
886         elif t["T_MEMMAP"] and "c" == filter:
887             for mt in t["T_MEMTHING"]:
888                 if ' ' != chr(t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
889                                          + mt[2]]) \
890                    and world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]:
891                     return True
892         return False
893
894     def set_cells_passable_on_memmap_to_65534_on_scoremap():
895         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
896         # ord_dot = ord(".")
897         # memmap = t["T_MEMMAP"]
898         # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
899         #            if ord_dot == memmap[i]]:
900         #     set_map_score(i, 65534) # i.e. 65535-1
901         map = c_pointer_to_bytearray(t["T_MEMMAP"])
902         if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
903             raise RuntimeError("No score map allocated for "
904                         "set_cells_passable_on_memmap_to_65534_on_scoremap().")
905
906     def init_score_map():
907         test = libpr.init_score_map()
908         if test:
909             raise RuntimeError("Malloc error in init_score_map().")
910         ord_v = ord("v")
911         ord_blank = ord(" ")
912         set_cells_passable_on_memmap_to_65534_on_scoremap()
913         if "a" == filter:
914             for id in world_db["Things"]:
915                 Thing = world_db["Things"][id]
916                 pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
917                       + Thing["T_POSX"]
918                 if t != Thing and Thing["T_LIFEPOINTS"] and \
919                    t["T_TYPE"] != Thing["T_TYPE"] and \
920                    ord_v == t["fovmap"][pos] and \
921                    t["T_LIFEPOINTS"] > \
922                    world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
923                     set_map_score(pos, 0)
924                 elif t["T_TYPE"] == Thing["T_TYPE"]:
925                     set_map_score(pos, 65535)
926         elif "f" == filter:
927             for id in [id for id in world_db["Things"]
928                        if world_db["Things"][id]["T_LIFEPOINTS"]]:
929                 Thing = world_db["Things"][id]
930                 pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
931                       + Thing["T_POSX"]
932                 if t["T_TYPE"] != Thing["T_TYPE"] and \
933                    ord_v == t["fovmap"][pos] and \
934                    t["T_LIFEPOINTS"] <= \
935                    world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
936                     set_map_score(pos, 0)
937         elif "c" == filter:
938             for mt in [mt for mt in t["T_MEMTHING"]
939                        if ord_blank != t["T_MEMMAP"][mt[1]
940                                                     * world_db["MAP_LENGTH"]
941                                                     + mt[2]]
942                        if world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]]:
943                 set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0)
944         elif "s" == filter:
945             zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
946
947     def rand_target_dir(neighbors, cmp, dirs):
948         candidates = []
949         n_candidates = 0
950         for i in range(len(dirs)):
951             if cmp == neighbors[i]:
952                 candidates.append(dirs[i])
953                 n_candidates += 1
954         return candidates[rand.next() % n_candidates] if n_candidates else 0
955
956     def get_neighbor_scores(dirs, eye_pos):
957         scores = []
958         if libpr.ready_neighbor_scores(eye_pos):
959             raise RuntimeError("No score map allocated for " +
960                                "ready_neighbor_scores.()")
961         for i in range(len(dirs)):
962             scores.append(libpr.get_neighbor_score(i))
963         return scores
964
965     def get_dir_from_neighbors():
966         dir_to_target = False
967         dirs = "edcxsw"
968         eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
969         neighbors = get_neighbor_scores(dirs, eye_pos)
970         if "f" == filter:
971             inhabited = [world_db["Things"][id]["T_POSY"]
972                          * world_db["MAP_LENGTH"]
973                          + world_db["Things"][id]["T_POSX"]
974                          for id in world_db["Things"]
975                          if world_db["Things"][id]["T_LIFEPOINTS"]]
976             for i in range(len(dirs)):
977                 mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"])
978                 pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \
979                           + libpr.result_x()
980                 for pos in [pos for pos in inhabited if pos == pos_cmp]:
981                     neighbors[i] = 65535
982                     break
983         minmax_start = 0 if "f" == filter else 65535 - 1
984         minmax_neighbor = minmax_start
985         for i in range(len(dirs)):
986             if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
987                 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
988                or ("f" != filter and minmax_neighbor > neighbors[i]):
989                 minmax_neighbor = neighbors[i]
990         if minmax_neighbor != minmax_start:
991             dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
992         if "f" == filter:
993             if not dir_to_target:
994                 if 1 == get_map_score(eye_pos):
995                     dir_to_target = rand_target_dir(neighbors, 0, dirs)
996                 elif 3 >= get_map_score(eye_pos):
997                     t["T_COMMAND"] = [id for id in world_db["ThingActions"]
998                                       if
999                                       world_db["ThingActions"][id]["TA_NAME"]
1000                                          == "wait"][0]
1001                     return 1
1002             elif dir_to_target and 3 < get_map_score(eye_pos):
1003                 dir_to_target = 0
1004         elif "a" == filter and 10 <= get_map_score(eye_pos):
1005             dir_to_target = 0
1006         return dir_to_target
1007
1008     dir_to_target = False
1009     mem_depth_c = b' '
1010     run_i = 9 + 1 if "s" == filter else 1
1011     while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1012         run_i -= 1
1013         init_score_map()
1014         mem_depth_c = b'9' if b' ' == mem_depth_c \
1015                            else bytes([mem_depth_c[0] - 1])
1016         if libpr.dijkstra_map():
1017             raise RuntimeError("No score map allocated for dijkstra_map().")
1018         dir_to_target = get_dir_from_neighbors()
1019         libpr.free_score_map()
1020         if dir_to_target and str == type(dir_to_target):
1021             t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1022                               if world_db["ThingActions"][id]["TA_NAME"]
1023                                  == "move"][0]
1024             t["T_ARGUMENT"] = ord(dir_to_target)
1025     return dir_to_target
1026
1027
1028 def standing_on_consumable(t):
1029     """Return True/False whether t is standing on a consumable."""
1030     for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1031                if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1032                if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1033                if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1034                           ["TT_CONSUMABLE"]]:
1035         return True
1036     return False
1037
1038
1039 def get_inventory_slot_to_consume(t):
1040     """Return slot Id of strongest consumable in t's inventory, else -1."""
1041     cmp_consumability = 0
1042     selection = -1
1043     i = 0
1044     for id in t["T_CARRIES"]:
1045         type = world_db["Things"][id]["T_TYPE"]
1046         if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability:
1047             cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
1048             selection = i
1049         i += 1
1050     return selection
1051
1052
1053 def ai(t):
1054     """Determine next command/argment for actor t via AI algorithms.
1055
1056     AI will look for, and move towards, enemies (animate Things not of their
1057     own ThingType); if they see none, they will consume consumables in their
1058     inventory; if there are none, they will pick up what they stand on if they
1059     stand on consumables; if they stand on none, they will move towards the
1060     next consumable they see or remember on the map; if they see or remember
1061     none, they will explore parts of the map unseen since ever or for at least
1062     one turn; if there is nothing to explore, they will simply wait.
1063     """
1064     # # 7DRL add: Don't pick up or search things when inventory is full.
1065     t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1066                       if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1067     if not get_dir_to_target(t, "f"):
1068         sel = get_inventory_slot_to_consume(t)
1069         if -1 != sel:
1070             t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1071                               if world_db["ThingActions"][id]["TA_NAME"]
1072                                  == "use"][0]
1073             t["T_ARGUMENT"] = sel
1074         elif standing_on_consumable(t) \
1075              and (len(t["T_CARRIES"]) < # #
1076                  world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1077             t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1078                               if world_db["ThingActions"][id]["TA_NAME"]
1079                                  == "pick_up"][0]
1080         elif (not
1081               (len(t["T_CARRIES"]) < # #
1082                 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"] # #
1083                and get_dir_to_target(t, "c"))) and \
1084              (not get_dir_to_target(t, "a")):
1085             get_dir_to_target(t, "s")
1086
1087
1088 def turn_over():
1089     """Run game world and its inhabitants until new player input expected."""
1090     id = 0
1091     whilebreaker = False
1092     while world_db["Things"][0]["T_LIFEPOINTS"]:
1093         for id in [id for id in world_db["Things"]]:  # Only what's from start!
1094             if not id in world_db["Things"] or \
1095                world_db["Things"][id]["carried"]:   # May have been consumed or
1096                 continue                            # picked up during turn …
1097             Thing = world_db["Things"][id]
1098             if Thing["T_LIFEPOINTS"]:
1099                 if not Thing["T_COMMAND"]:
1100                     update_map_memory(Thing)
1101                     if 0 == id:
1102                         whilebreaker = True
1103                         break
1104                     ai(Thing)
1105                 try_healing(Thing)
1106                 Thing["T_PROGRESS"] += 1
1107                 taid = [a for a in world_db["ThingActions"]
1108                           if a == Thing["T_COMMAND"]][0]
1109                 ThingAction = world_db["ThingActions"][taid]
1110                 if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1111                     eval("actor_" + ThingAction["TA_NAME"])(Thing)
1112                     Thing["T_COMMAND"] = 0
1113                     Thing["T_PROGRESS"] = 0
1114                 hunger(Thing)
1115             thingproliferation(Thing)
1116         if whilebreaker:
1117             break
1118         world_db["TURN"] += 1
1119
1120
1121 def new_Thing(type, pos=(0, 0)):
1122     """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1123     thing = {
1124         "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1125         "T_ARGUMENT": 0,
1126         "T_PROGRESS": 0,
1127         "T_SATIATION": 0,
1128         "T_COMMAND": 0,
1129         "T_TYPE": type,
1130         "T_POSY": pos[0],
1131         "T_POSX": pos[1],
1132         "T_CARRIES": [],
1133         "carried": False,
1134         "T_MEMTHING": [],
1135         "T_MEMMAP": False,
1136         "T_MEMDEPTHMAP": False,
1137         "fovmap": False
1138     }
1139     if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1140         build_fov_map(thing)
1141     return thing
1142
1143
1144 def id_setter(id, category, id_store=False, start_at_1=False):
1145     """Set ID of object of category to manipulate ID unused? Create new one.
1146
1147     The ID is stored as id_store.id (if id_store is set). If the integer of the
1148     input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1149     <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1150     always returned when no new object is created, otherwise the new object's
1151     ID.
1152     """
1153     min = 0 if start_at_1 else -1
1154     if str == type(id):
1155         id = integer_test(id, min)
1156     if None != id:
1157         if id in world_db[category]:
1158             if id_store:
1159                 id_store.id = id
1160             return None
1161         else:
1162             if (start_at_1 and 0 == id) \
1163                or ((not start_at_1) and (id < 0)):
1164                 id = 0 if start_at_1 else -1
1165                 while 1:
1166                     id = id + 1
1167                     if id not in world_db[category]:
1168                         break
1169             if id_store:
1170                 id_store.id = id
1171     return id
1172
1173
1174 def command_ping():
1175     """Send PONG line to server output file."""
1176     strong_write(io_db["file_out"], "PONG\n")
1177
1178
1179 def command_quit():
1180     """Abort server process."""
1181     if None == opts.replay:
1182         save_world()
1183         atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1184     raise SystemExit("received QUIT command")
1185
1186
1187 def command_thingshere(str_y, str_x):
1188     """Write to out file list of Things known to player at coordinate y, x."""
1189     if world_db["WORLD_ACTIVE"]:
1190         y = integer_test(str_y, 0, 255)
1191         x = integer_test(str_x, 0, 255)
1192         length = world_db["MAP_LENGTH"]
1193         if None != y and None != x and y < length and x < length:
1194             pos = (y * world_db["MAP_LENGTH"]) + x
1195             strong_write(io_db["file_out"], "THINGS_HERE START\n")
1196             if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1197                 for id in world_db["Things"]:
1198                     if y == world_db["Things"][id]["T_POSY"] \
1199                        and x == world_db["Things"][id]["T_POSX"] \
1200                        and not world_db["Things"][id]["carried"]:
1201                         type = world_db["Things"][id]["T_TYPE"]
1202                         name = world_db["ThingTypes"][type]["TT_NAME"]
1203                         strong_write(io_db["file_out"], name + "\n")
1204             else:
1205                 for mt in world_db["Things"][0]["T_MEMTHING"]:
1206                     if y == mt[1] and x == mt[2]:
1207                         name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1208                         strong_write(io_db["file_out"], name + "\n")
1209             strong_write(io_db["file_out"], "THINGS_HERE END\n")
1210         else:
1211             print("Ignoring: Invalid map coordinates.")
1212     else:
1213         print("Ignoring: Command only works on existing worlds.")
1214
1215
1216 def play_commander(action, args=False):
1217     """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1218
1219     T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1220     """
1221
1222     def set_command():
1223         id = [x for x in world_db["ThingActions"]
1224                 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1225         world_db["Things"][0]["T_COMMAND"] = id
1226         turn_over()
1227
1228     def set_command_and_argument_int(str_arg):
1229         val = integer_test(str_arg, 0, 255)
1230         if None != val:
1231             world_db["Things"][0]["T_ARGUMENT"] = val
1232             set_command()
1233
1234     def set_command_and_argument_movestring(str_arg):
1235         if str_arg in directions_db:
1236             world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1237             set_command()
1238         else:
1239             print("Ignoring: Argument must be valid direction string.")
1240
1241     if action == "move":
1242         return set_command_and_argument_movestring
1243     elif args:
1244         return set_command_and_argument_int
1245     else:
1246         return set_command
1247
1248
1249 def command_seedrandomness(seed_string):
1250     """Set rand seed to int(seed_string)."""
1251     val = integer_test(seed_string, 0, 4294967295)
1252     if None != val:
1253         rand.seed = val
1254
1255
1256 def command_seedmap(seed_string):
1257     """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
1258     setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
1259     remake_map()
1260
1261
1262 def command_makeworld(seed_string):
1263     """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1264
1265     Seed rand with seed, fill it into world_db["SEED_MAP"]. Do more only with a
1266     "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType of
1267     TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
1268     and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1269     according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1270     of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1271     other. Init player's memory map. Write "NEW_WORLD" line to out file.
1272     """
1273
1274     def free_pos():
1275         i = 0
1276         while 1:
1277             err = "Space to put thing on too hard to find. Map too small?"
1278             while 1:
1279                 y = rand.next() % world_db["MAP_LENGTH"]
1280                 x = rand.next() % world_db["MAP_LENGTH"]
1281                 if "." == chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]):
1282                     break
1283                 i += 1
1284                 if i == 65535:
1285                     raise SystemExit(err)
1286             # Replica of C code, wrongly ignores animatedness of new Thing.
1287             pos_clear = (0 == len([id for id in world_db["Things"]
1288                                    if world_db["Things"][id]["T_LIFEPOINTS"]
1289                                    if world_db["Things"][id]["T_POSY"] == y
1290                                    if world_db["Things"][id]["T_POSX"] == x]))
1291             if pos_clear:
1292                 break
1293         return (y, x)
1294
1295     val = integer_test(seed_string, 0, 4294967295)
1296     if None == val:
1297         return
1298     rand.seed = val
1299     world_db["SEED_MAP"] = val
1300     player_will_be_generated = False
1301     playertype = world_db["PLAYER_TYPE"]
1302     for ThingType in world_db["ThingTypes"]:
1303         if playertype == ThingType:
1304             if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1305                 player_will_be_generated = True
1306             break
1307     if not player_will_be_generated:
1308         print("Ignoring beyond SEED_MAP: " +
1309               "No player type with start number >0 defined.")
1310         return
1311     wait_action = False
1312     for ThingAction in world_db["ThingActions"]:
1313         if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1314             wait_action = True
1315     if not wait_action:
1316         print("Ignoring beyond SEED_MAP: " +
1317               "No thing action with name 'wait' defined.")
1318         return
1319     world_db["Things"] = {}
1320     remake_map()
1321     world_db["WORLD_ACTIVE"] = 1
1322     world_db["TURN"] = 1
1323     for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1324         id = id_setter(-1, "Things")
1325         world_db["Things"][id] = new_Thing(playertype, free_pos())
1326     update_map_memory(world_db["Things"][0])
1327     for type in world_db["ThingTypes"]:
1328         for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1329             if type != playertype:
1330                 id = id_setter(-1, "Things")
1331                 world_db["Things"][id] = new_Thing(type, free_pos())
1332     strong_write(io_db["file_out"], "NEW_WORLD\n")
1333
1334
1335 def command_maplength(maplength_string):
1336     """Redefine map length. Invalidate map, therefore lose all things on it."""
1337     val = integer_test(maplength_string, 1, 256)
1338     if None != val:
1339         world_db["MAP_LENGTH"] = val
1340         set_world_inactive()
1341         world_db["Things"] = {}
1342         libpr.set_maplength(val)
1343
1344
1345 def command_worldactive(worldactive_string):
1346     """Toggle world_db["WORLD_ACTIVE"] if possible.
1347
1348     An active world can always be set inactive. An inactive world can only be
1349     set active with a "wait" ThingAction, and a player Thing (of ID 0). On
1350     activation, rebuild all Things' FOVs, and the player's map memory.
1351     """
1352     # In original version, map existence was also tested (unnecessarily?).
1353     val = integer_test(worldactive_string, 0, 1)
1354     if val:
1355         if 0 != world_db["WORLD_ACTIVE"]:
1356             if 0 == val:
1357                 set_world_inactive()
1358             else:
1359                 print("World already active.")
1360         elif 0 == world_db["WORLD_ACTIVE"]:
1361             wait_exists = False
1362             for ThingAction in world_db["ThingActions"]:
1363                 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1364                     wait_exists = True
1365                     break
1366             player_exists = False
1367             for Thing in world_db["Things"]:
1368                 if 0 == Thing:
1369                     player_exists = True
1370                     break
1371             if wait_exists and player_exists:
1372                 for id in world_db["Things"]:
1373                     if world_db["Things"][id]["T_LIFEPOINTS"]:
1374                         build_fov_map(world_db["Things"][id])
1375                         if 0 == id:
1376                             update_map_memory(world_db["Things"][id], False)
1377                 world_db["WORLD_ACTIVE"] = 1
1378
1379
1380 def test_for_id_maker(object, category):
1381     """Return decorator testing for object having "id" attribute."""
1382     def decorator(f):
1383         def helper(*args):
1384             if hasattr(object, "id"):
1385                 f(*args)
1386             else:
1387                 print("Ignoring: No " + category +
1388                       " defined to manipulate yet.")
1389         return helper
1390     return decorator
1391
1392
1393 def command_tid(id_string):
1394     """Set ID of Thing to manipulate. ID unused? Create new one.
1395
1396     Default new Thing's type to the first available ThingType, others: zero.
1397     """
1398     id = id_setter(id_string, "Things", command_tid)
1399     if None != id:
1400         if world_db["ThingTypes"] == {}:
1401             print("Ignoring: No ThingType to settle new Thing in.")
1402             return
1403         type = list(world_db["ThingTypes"].keys())[0]
1404         world_db["Things"][id] = new_Thing(type)
1405
1406
1407 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1408
1409
1410 @test_Thing_id
1411 def command_tcommand(str_int):
1412     """Set T_COMMAND of selected Thing."""
1413     val = integer_test(str_int, 0)
1414     if None != val:
1415         if 0 == val or val in world_db["ThingActions"]:
1416             world_db["Things"][command_tid.id]["T_COMMAND"] = val
1417         else:
1418             print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1419
1420
1421 @test_Thing_id
1422 def command_ttype(str_int):
1423     """Set T_TYPE of selected Thing."""
1424     val = integer_test(str_int, 0)
1425     if None != val:
1426         if val in world_db["ThingTypes"]:
1427             world_db["Things"][command_tid.id]["T_TYPE"] = val
1428         else:
1429             print("Ignoring: ThingType ID belongs to no known ThingType.")
1430
1431
1432 @test_Thing_id
1433 def command_tcarries(str_int):
1434     """Append int(str_int) to T_CARRIES of selected Thing.
1435
1436     The ID int(str_int) must not be of the selected Thing, and must belong to a
1437     Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1438     """
1439     val = integer_test(str_int, 0)
1440     if None != val:
1441         if val == command_tid.id:
1442             print("Ignoring: Thing cannot carry itself.")
1443         elif val in world_db["Things"] \
1444              and not world_db["Things"][val]["carried"]:
1445             world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1446             world_db["Things"][val]["carried"] = True
1447         else:
1448             print("Ignoring: Thing not available for carrying.")
1449     # Note that the whole carrying structure is different from the C version:
1450     # Carried-ness is marked by a "carried" flag, not by Things containing
1451     # Things internally.
1452
1453
1454 @test_Thing_id
1455 def command_tmemthing(str_t, str_y, str_x):
1456     """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1457
1458     The type must fit to an existing ThingType, and the position into the map.
1459     """
1460     type = integer_test(str_t, 0)
1461     posy = integer_test(str_y, 0, 255)
1462     posx = integer_test(str_x, 0, 255)
1463     if None != type and None != posy and None != posx:
1464         if type not in world_db["ThingTypes"] \
1465            or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1466             print("Ignoring: Illegal value for thing type or position.")
1467         else:
1468             memthing = (type, posy, posx)
1469             world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1470
1471
1472 def setter_map(maptype):
1473     """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
1474
1475     If Thing has no map of maptype yet, initialize it with ' ' bytes first.
1476     """
1477     @test_Thing_id
1478     def helper(str_int, mapline):
1479         val = integer_test(str_int, 0, 255)
1480         if None != val:
1481             if val >= world_db["MAP_LENGTH"]:
1482                 print("Illegal value for map line number.")
1483             elif len(mapline) != world_db["MAP_LENGTH"]:
1484                 print("Map line length is unequal map width.")
1485             else:
1486                 length = world_db["MAP_LENGTH"]
1487                 map = None
1488                 if not world_db["Things"][command_tid.id][maptype]:
1489                     map = bytearray(b' ' * (length ** 2))
1490                 else:
1491                     map = world_db["Things"][command_tid.id][maptype]
1492                 map[val * length:(val * length) + length] = mapline.encode()
1493                 world_db["Things"][command_tid.id][maptype] = map
1494     return helper
1495
1496
1497 def setter_tpos(axis):
1498     """Generate setter for T_POSX or  T_POSY of selected Thing.
1499
1500     If world is active, rebuilds animate things' fovmap, player's memory map.
1501     """
1502     @test_Thing_id
1503     def helper(str_int):
1504         val = integer_test(str_int, 0, 255)
1505         if None != val:
1506             if val < world_db["MAP_LENGTH"]:
1507                 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1508                 if world_db["WORLD_ACTIVE"] \
1509                    and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1510                     build_fov_map(world_db["Things"][command_tid.id])
1511                     if 0 == command_tid.id:
1512                         update_map_memory(world_db["Things"][command_tid.id])
1513             else:
1514                 print("Ignoring: Position is outside of map.")
1515     return helper
1516
1517
1518 def command_ttid(id_string):
1519     """Set ID of ThingType to manipulate. ID unused? Create new one.
1520
1521     Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
1522     """
1523     id = id_setter(id_string, "ThingTypes", command_ttid)
1524     if None != id:
1525         world_db["ThingTypes"][id] = {
1526             "TT_NAME": "(none)",
1527             "TT_CONSUMABLE": 0,
1528             "TT_LIFEPOINTS": 0,
1529             "TT_PROLIFERATE": 0,
1530             "TT_START_NUMBER": 0,
1531             "TT_STORAGE": 0, # #
1532             "TT_SYMBOL": "?",
1533             "TT_CORPSE_ID": id
1534         }
1535
1536
1537 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1538
1539
1540 @test_ThingType_id
1541 def command_ttname(name):
1542     """Set TT_NAME of selected ThingType."""
1543     world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1544
1545
1546 @test_ThingType_id
1547 def command_ttsymbol(char):
1548     """Set TT_SYMBOL of selected ThingType. """
1549     if 1 == len(char):
1550         world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1551     else:
1552         print("Ignoring: Argument must be single character.")
1553
1554
1555 @test_ThingType_id
1556 def command_ttcorpseid(str_int):
1557     """Set TT_CORPSE_ID of selected ThingType."""
1558     val = integer_test(str_int, 0)
1559     if None != val:
1560         if val in world_db["ThingTypes"]:
1561             world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1562         else:
1563             print("Ignoring: Corpse ID belongs to no known ThignType.")
1564
1565
1566 def command_taid(id_string):
1567     """Set ID of ThingAction to manipulate. ID unused? Create new one.
1568
1569     Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
1570     """
1571     id = id_setter(id_string, "ThingActions", command_taid, True)
1572     if None != id:
1573         world_db["ThingActions"][id] = {
1574             "TA_EFFORT": 1,
1575             "TA_NAME": "wait"
1576         }
1577
1578
1579 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
1580
1581
1582 @test_ThingAction_id
1583 def command_taname(name):
1584     """Set TA_NAME of selected ThingAction.
1585
1586     The name must match a valid thing action function. If after the name
1587     setting no ThingAction with name "wait" remains, call set_world_inactive().
1588     """
1589     if name == "wait" or name == "move" or name == "use" or name == "drop" \
1590        or name == "pick_up":
1591         world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
1592         if 1 == world_db["WORLD_ACTIVE"]:
1593             wait_defined = False
1594             for id in world_db["ThingActions"]:
1595                 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
1596                     wait_defined = True
1597                     break
1598             if not wait_defined:
1599                 set_world_inactive()
1600     else:
1601         print("Ignoring: Invalid action name.")
1602     # In contrast to the original,naming won't map a function to a ThingAction.
1603
1604
1605 def command_ai():
1606     """Call ai() on player Thing, then turn_over()."""
1607     ai(world_db["Things"][0])
1608     turn_over()
1609
1610
1611 """Commands database.
1612
1613 Map command start tokens to ([0]) number of expected command arguments, ([1])
1614 the command's meta-ness (i.e. is it to be written to the record file, is it to
1615 be ignored in replay mode if read from server input file), and ([2]) a function
1616 to be called on it.
1617 """
1618 commands_db = {
1619     "QUIT": (0, True, command_quit),
1620     "PING": (0, True, command_ping),
1621     "THINGS_HERE": (2, True, command_thingshere),
1622     "MAKE_WORLD": (1, False, command_makeworld),
1623     "SEED_MAP": (1, False, command_seedmap),
1624     "SEED_RANDOMNESS": (1, False, command_seedrandomness),
1625     "TURN": (1, False, setter(None, "TURN", 0, 65535)),
1626     "GOD_MOOD": (1, False, setter(None, "GOD_MOOD", -32768, 32767)),  # #
1627     "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)),  # #
1628     "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
1629     "MAP_LENGTH": (1, False, command_maplength),
1630     "WORLD_ACTIVE": (1, False, command_worldactive),
1631     "TA_ID": (1, False, command_taid),
1632     "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
1633     "TA_NAME": (1, False, command_taname),
1634     "TT_ID": (1, False, command_ttid),
1635     "TT_NAME": (1, False, command_ttname),
1636     "TT_SYMBOL": (1, False, command_ttsymbol),
1637     "TT_CORPSE_ID": (1, False, command_ttcorpseid),
1638     "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
1639                                        0, 65535)),
1640     "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
1641                                          0, 255)),
1642     "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
1643                                         0, 255)),
1644     "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
1645     "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
1646     "T_ID": (1, False, command_tid),
1647     "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
1648     "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
1649     "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
1650     "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
1651     "T_COMMAND": (1, False, command_tcommand),
1652     "T_TYPE": (1, False, command_ttype),
1653     "T_CARRIES": (1, False, command_tcarries),
1654     "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
1655     "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
1656     "T_MEMTHING": (3, False, command_tmemthing),
1657     "T_POSY": (1, False, setter_tpos("Y")),
1658     "T_POSX": (1, False, setter_tpos("X")),
1659     "wait": (0, False, play_commander("wait")),
1660     "move": (1, False, play_commander("move")),
1661     "pick_up": (0, False, play_commander("pick_up")),
1662     "drop": (1, False, play_commander("drop", True)),
1663     "use": (1, False, play_commander("use", True)),
1664     "ai": (0, False, command_ai)
1665 }
1666
1667
1668 """World state database. With sane default values. (Randomness is in rand.)"""
1669 world_db = {
1670     "TURN": 0,
1671     "MAP_LENGTH": 64,
1672     "SEED_MAP": 0,
1673     "PLAYER_TYPE": 0,
1674     "WORLD_ACTIVE": 0,
1675     "GOD_MOOD": 0,  # #
1676     "GOD_FAVOR": 0,  # #
1677     "ThingActions": {},
1678     "ThingTypes": {},
1679     "Things": {}
1680 }
1681
1682 """Mapping of direction names to internal direction chars."""
1683 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
1684                  "west": "s", "north-west": "w", "north-east": "e"}
1685
1686 """File IO database."""
1687 io_db = {
1688     "path_save": "save",
1689     "path_record": "record_save",
1690     "path_worldconf": "confserver/world",
1691     "path_server": "server/",
1692     "path_in": "server/in",
1693     "path_out": "server/out",
1694     "path_worldstate": "server/worldstate",
1695     "tmp_suffix": "_tmp",
1696     "kicked_by_rival": False,
1697     "worldstate_updateable": False
1698 }
1699
1700
1701 try:
1702     libpr = prep_library()
1703     rand = RandomnessIO()
1704     opts = parse_command_line_arguments()
1705     if opts.savefile:
1706         io_db["path_save"] = opts.savefile
1707         io_db["path_record"] = "record_" + opts.savefile
1708     setup_server_io()
1709     if opts.verbose:
1710         io_db["verbose"] = True
1711     if None != opts.replay:
1712         replay_game()
1713     else:
1714         play_game()
1715 except SystemExit as exit:
1716     print("ABORTING: " + exit.args[0])
1717 except:
1718     print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
1719     raise
1720 finally:
1721     cleanup_server_io()