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