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.
19 """"Interface to libplomrogue's pseudo-randomness generator."""
21 def set_seed(self, seed):
22 libpr.seed_rrand(1, seed)
25 return libpr.seed_rrand(0, 0)
30 seed = property(get_seed, set_seed)
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
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)
49 def strong_write(file, string):
50 """Apply write(string), then flush()."""
55 def setup_server_io():
56 """Fill IO files DB with proper file( path)s. Write process IO test string.
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"].
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):
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"])
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):
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()
102 def obey(command, prefix, replay=False, do_record=False):
103 """Call function from commands_db mapped to command's first token.
105 Tokenize command string with shlex.split(comments=True). If replay is set,
106 a non-meta command from the commands_db merely triggers obey() on the next
107 command from the records file. If not, non-meta commands set
108 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
109 do_record is set, are recorded to io_db["record_chunk"], and save_world()
110 is called (and io_db["record_chunk"] written) if 15 seconds have passed
111 since the last time it was called. The prefix string is inserted into the
112 server's input message between its beginning 'input ' and ':'. All activity
113 is preceded by a server_test() call. Commands that start with a lowercase
114 letter are ignored when world_db["WORLD_ACTIVE"] is False/0.
118 print("input " + prefix + ": " + command)
120 tokens = shlex.split(command, comments=True)
121 except ValueError as err:
122 print("Can't tokenize command string: " + str(err) + ".")
124 if len(tokens) > 0 and tokens[0] in commands_db \
125 and len(tokens) == commands_db[tokens[0]][0] + 1:
126 if commands_db[tokens[0]][1]:
127 commands_db[tokens[0]][2](*tokens[1:])
128 elif tokens[0][0].islower() and not world_db["WORLD_ACTIVE"]:
129 print("Ignoring lowercase-starting commands when world inactive.")
131 print("Due to replay mode, reading command as 'go on in record'.")
132 line = io_db["file_record"].readline()
134 obey(line.rstrip(), io_db["file_record"].prefix
135 + str(io_db["file_record"].line_n))
136 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
138 print("Reached end of record file.")
140 commands_db[tokens[0]][2](*tokens[1:])
142 io_db["record_chunk"] += command + "\n"
143 if time.time() > io_db["save_wait"] + 15:
144 atomic_write(io_db["path_record"], io_db["record_chunk"],
146 if world_db["WORLD_ACTIVE"]:
148 io_db["record_chunk"] = ""
149 io_db["save_wait"] = time.time()
150 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
151 elif 0 != len(tokens):
152 print("Invalid command/argument, or bad number of tokens.")
155 def atomic_write(path, text, do_append=False, delete=True):
156 """Atomic write of text to file at path, appended if do_append is set."""
157 path_tmp = path + io_db["tmp_suffix"]
161 if os.access(path, os.F_OK):
162 shutil.copyfile(path, path_tmp)
163 file = open(path_tmp, mode)
164 strong_write(file, text)
166 if delete and os.access(path, os.F_OK):
168 os.rename(path_tmp, path)
172 """Save all commands needed to reconstruct current world state."""
175 string = string.replace("\u005C", '\u005C\u005C')
176 return '"' + string.replace('"', '\u005C"') + '"'
181 if key == "MAP" or world_db["Things"][id][key]:
182 map = world_db["MAP"] if key == "MAP" \
183 else world_db["Things"][id][key]
184 length = world_db["MAP_LENGTH"]
185 for i in range(length):
186 line = map[i * length:(i * length) + length].decode()
187 string = string + key + " " + str(i) + " " + quote(line) \
194 for memthing in world_db["Things"][id]["T_MEMTHING"]:
195 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
196 str(memthing[1]) + " " + str(memthing[2]) + "\n"
199 def helper(category, id_string, special_keys={}):
201 for id in world_db[category]:
202 string = string + id_string + " " + str(id) + "\n"
203 for key in world_db[category][id]:
204 if not key in special_keys:
205 x = world_db[category][id][key]
206 argument = quote(x) if str == type(x) else str(x)
207 string = string + key + " " + argument + "\n"
208 elif special_keys[key]:
209 string = string + special_keys[key](id)
214 if (dict != type(world_db[key])
215 and key != "altar" # #
216 and key != "MAP" and key != "WORLD_ACTIVE"):
217 string = string + key + " " + str(world_db[key]) + "\n"
218 string = string + mapsetter("MAP")()
219 string = string + helper("ThingActions", "TA_ID")
220 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
221 for id in world_db["ThingTypes"]:
222 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
223 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
224 string = string + helper("Things", "T_ID",
225 {"T_CARRIES": False, "carried": False,
226 "T_MEMMAP": mapsetter("T_MEMMAP"),
227 "T_MEMTHING": memthing, "fovmap": False,
228 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
229 for id in world_db["Things"]:
230 if [] != world_db["Things"][id]["T_CARRIES"]:
231 string = string + "T_ID " + str(id) + "\n"
232 for carried_id in world_db["Things"][id]["T_CARRIES"]:
233 string = string + "T_CARRIES " + str(carried_id) + "\n"
234 string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
235 "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
236 atomic_write(io_db["path_save"], string)
239 def obey_lines_in_file(path, name, do_record=False):
240 """Call obey() on each line of path's file, use name in input prefix."""
241 file = open(path, "r")
243 for line in file.readlines():
244 obey(line.rstrip(), name + "file line " + str(line_n),
250 def parse_command_line_arguments():
251 """Return settings values read from command line arguments."""
252 parser = argparse.ArgumentParser()
253 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
255 parser.add_argument('-l', nargs="?", const="save", dest='savefile',
257 parser.add_argument('-v', dest='verbose', action='store_true')
258 opts, unknown = parser.parse_known_args()
263 """Ensure valid server out file belonging to current process.
265 This is done by comparing io_db["teststring"] to what's found at the start
266 of the current file at io_db["path_out"]. On failure, set
267 io_db["kicked_by_rival"] and raise SystemExit.
269 if not os.access(io_db["path_out"], os.F_OK):
270 raise SystemExit("Server output file has disappeared.")
271 file = open(io_db["path_out"], "r")
272 test = file.readline().rstrip("\n")
274 if test != io_db["teststring"]:
275 io_db["kicked_by_rival"] = True
276 msg = "Server test string in server output file does not match. This" \
277 " indicates that the current server process has been " \
278 "superseded by another one."
279 raise SystemExit(msg)
283 """Return next newline-delimited command from server in file.
285 Keep building return string until a newline is encountered. Pause between
286 unsuccessful reads, and after too much waiting, run server_test().
288 wait_on_fail = 0.03333
293 add = io_db["file_in"].readline()
295 command = command + add
296 if len(command) > 0 and "\n" == command[-1]:
297 command = command[:-1]
300 time.sleep(wait_on_fail)
301 if now + max_wait < time.time():
307 def try_worldstate_update():
308 """Write worldstate file if io_db["worldstate_updateable"] is set."""
309 if io_db["worldstate_updateable"]:
311 def write_map(string, map):
312 for i in range(length):
313 line = map[i * length:(i * length) + length].decode()
314 string = string + line + "\n"
318 if [] == world_db["Things"][0]["T_CARRIES"]:
319 inventory = "(none)\n"
321 for id in world_db["Things"][0]["T_CARRIES"]:
322 type_id = world_db["Things"][id]["T_TYPE"]
323 name = world_db["ThingTypes"][type_id]["TT_NAME"]
324 inventory = inventory + name + "\n"
325 # 7DRL additions: GOD_MOOD, GOD_FAVOR
326 string = str(world_db["TURN"]) + "\n" + \
327 str(world_db["GOD_MOOD"]) + "\n" + \
328 str(world_db["GOD_FAVOR"]) + "\n" + \
329 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
330 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
331 inventory + "%\n" + \
332 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
333 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
334 str(world_db["MAP_LENGTH"]) + "\n"
335 length = world_db["MAP_LENGTH"]
337 fov = bytearray(b' ' * (length ** 2))
339 for pos in [pos for pos in range(length ** 2)
340 if ord_v == world_db["Things"][0]["fovmap"][pos]]:
341 fov[pos] = world_db["MAP"][pos]
342 for id in [id for tid in reversed(sorted(list(world_db["ThingTypes"])))
343 for id in world_db["Things"]
344 if not world_db["Things"][id]["carried"]
345 if world_db["Things"][id]["T_TYPE"] == tid
346 if world_db["Things"][0]["fovmap"][
347 world_db["Things"][id]["T_POSY"] * length
348 + world_db["Things"][id]["T_POSX"]] == ord_v]:
349 type = world_db["Things"][id]["T_TYPE"]
350 c = ord(world_db["ThingTypes"][type]["TT_SYMBOL"])
351 fov[world_db["Things"][id]["T_POSY"] * length
352 + world_db["Things"][id]["T_POSX"]] = c
353 string = write_map(string, fov)
355 mem = world_db["Things"][0]["T_MEMMAP"][:]
356 for mt in [mt for tid in reversed(sorted(list(world_db["ThingTypes"])))
357 for mt in world_db["Things"][0]["T_MEMTHING"]
359 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
360 mem[(mt[1] * length) + mt[2]] = ord(c)
361 string = write_map(string, mem)
363 stacksmap = bytearray(b'0' * (length ** 2)) # #
364 for id in [id for id in world_db["Things"] # #
365 if not world_db["Things"][id]["carried"] # #
366 if world_db["Things"][id]["T_LIFEPOINTS"] # #
367 if world_db["Things"][0]["fovmap"][ # #
368 world_db["Things"][id]["T_POSY"] * length # #
369 + world_db["Things"][id]["T_POSX"]] == ord_v]: # #
370 pos = (world_db["Things"][id]["T_POSY"] * length # #
371 + world_db["Things"][id]["T_POSX"]) # #
372 if id == 0 or world_db["EMPATHY"]: # #
373 type = world_db["Things"][id]["T_TYPE"] # #
374 max_hp = world_db["ThingTypes"][type]["TT_LIFEPOINTS"] # #
375 third_of_hp = max_hp / 3 # #
376 hp = world_db["Things"][id]["T_LIFEPOINTS"] # #
378 if hp > 2 * third_of_hp: # #
380 elif hp > third_of_hp: # #
382 stacksmap[pos] = ord('a') + add # #
384 stacksmap[pos] = ord('X') # #
385 for mt in world_db["Things"][0]["T_MEMTHING"]: # #
386 pos = mt[1] * length + mt[2] # #
387 if stacksmap[pos] < ord('2'): # #
388 stacksmap[pos] += 1 # #
389 string = write_map(string, stacksmap) # #
391 atomic_write(io_db["path_worldstate"], string, delete=False)
392 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
393 io_db["worldstate_updateable"] = False
397 """Replay game from record file.
399 Use opts.replay as breakpoint turn to which to replay automatically before
400 switching to manual input by non-meta commands in server input file
401 triggering further reads of record file. Ensure opts.replay is at least 1.
402 Run try_worldstate_update() before each interactive obey()/read_command().
406 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
407 " (if so late a turn is to be found).")
408 if not os.access(io_db["path_record"], os.F_OK):
409 raise SystemExit("No record file found to replay.")
410 io_db["file_record"] = open(io_db["path_record"], "r")
411 io_db["file_record"].prefix = "record file line "
412 io_db["file_record"].line_n = 1
413 while world_db["TURN"] < opts.replay:
414 line = io_db["file_record"].readline()
417 obey(line.rstrip(), io_db["file_record"].prefix
418 + str(io_db["file_record"].line_n))
419 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
421 try_worldstate_update()
422 obey(read_command(), "in file", replay=True)
426 """Play game by server input file commands. Before, load save file found.
428 If no save file is found, a new world is generated from the commands in the
429 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
430 command and all that follow via the server input file. Run
431 try_worldstate_update() before each interactive obey()/read_command().
433 if os.access(io_db["path_save"], os.F_OK):
434 obey_lines_in_file(io_db["path_save"], "save")
436 if not os.access(io_db["path_worldconf"], os.F_OK):
437 msg = "No world config file from which to start a new world."
438 raise SystemExit(msg)
439 obey_lines_in_file(io_db["path_worldconf"], "world config ",
441 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
443 try_worldstate_update()
444 obey(read_command(), "in file", do_record=True)
448 """(Re-)make island map.
450 Let "~" represent water, "." land, "X" trees: Build island shape randomly,
451 start with one land cell in the middle, then go into cycle of repeatedly
452 selecting a random sea cell and transforming it into land if it is neighbor
453 to land. The cycle ends when a land cell is due to be created at the map's
454 border. Then put some trees on the map (TODO: more precise algorithm desc).
456 # 7DRL: Also add some ":" cells, and (not surrounded by trees!) "_" altar.
458 def is_neighbor(coordinates, type):
461 length = world_db["MAP_LENGTH"]
463 diag_west = x + (ind > 0)
464 diag_east = x + (ind < (length - 1))
465 pos = (y * length) + x
466 if (y > 0 and diag_east
467 and type == chr(world_db["MAP"][pos - length + ind])) \
469 and type == chr(world_db["MAP"][pos + 1])) \
470 or (y < (length - 1) and diag_east
471 and type == chr(world_db["MAP"][pos + length + ind])) \
472 or (y > 0 and diag_west
473 and type == chr(world_db["MAP"][pos - length - (not ind)])) \
475 and type == chr(world_db["MAP"][pos - 1])) \
476 or (y < (length - 1) and diag_west
477 and type == chr(world_db["MAP"][pos + length - (not ind)])):
481 world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
482 length = world_db["MAP_LENGTH"]
483 add_half_width = (not (length % 2)) * int(length / 2)
484 world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
486 y = rand.next() % length
487 x = rand.next() % length
488 pos = (y * length) + x
489 if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
490 if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
492 world_db["MAP"][pos] = ord(".")
493 n_trees = int((length ** 2) / 16)
495 while (i_trees <= n_trees):
496 single_allowed = rand.next() % 32
497 y = rand.next() % length
498 x = rand.next() % length
499 pos = (y * length) + x
500 if "." == chr(world_db["MAP"][pos]) \
501 and ((not single_allowed) or is_neighbor((y, x), "X")):
502 world_db["MAP"][pos] = ord("X")
504 # This all-too-precise replica of the original C code misses iter_limit().
505 n_colons = int((length ** 2) / 16) # #
507 while (i_colons <= n_colons): # #
508 single_allowed = rand.next() % 256 # #
509 y = rand.next() % length # #
510 x = rand.next() % length # #
511 pos = (y * length) + x # #
512 if ("." == chr(world_db["MAP"][pos]) # #
513 and ((not single_allowed) or is_neighbor((y, x), ":"))): # #
514 world_db["MAP"][pos] = ord(":") # #
516 altar_placed = False # #
517 while not altar_placed: # #
518 y = rand.next() % length # #
519 x = rand.next() % length # #
520 pos = (y * length) + x # #
521 if (("." == chr(world_db["MAP"][pos] # #
522 or ":" == chr(world_db["MAP"][pos]))
523 and not is_neighbor((y, x), "X"))): # #
524 world_db["MAP"][pos] = ord("_") # #
525 world_db["altar"] = (y, x) # #
526 altar_placed = True # #
529 def update_map_memory(t, age_map=True):
530 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
532 def age_some_memdepthmap_on_nonfov_cells():
533 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
537 # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
538 # if not ord_v == t["fovmap"][pos]
539 # if ord_0 <= t["T_MEMDEPTHMAP"][pos]
540 # if ord_9 > t["T_MEMDEPTHMAP"][pos]
541 # if not rand.next() % (2 **
542 # (t["T_MEMDEPTHMAP"][pos] - 48))]:
543 # t["T_MEMDEPTHMAP"][pos] += 1
544 memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
545 fovmap = c_pointer_to_bytearray(t["fovmap"])
546 libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap)
548 if not t["T_MEMMAP"]:
549 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
550 if not t["T_MEMDEPTHMAP"]:
551 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
555 for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
556 if ord_v == t["fovmap"][pos]]:
557 t["T_MEMDEPTHMAP"][pos] = ord_0
558 t["T_MEMMAP"][pos] = world_db["MAP"][pos]
560 age_some_memdepthmap_on_nonfov_cells()
561 t["T_MEMTHING"] = [mt for mt in t["T_MEMTHING"]
562 if ord_v != t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
564 for id in [id for id in world_db["Things"]
565 if not world_db["Things"][id]["carried"]]:
566 type = world_db["Things"][id]["T_TYPE"]
567 if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
568 y = world_db["Things"][id]["T_POSY"]
569 x = world_db["Things"][id]["T_POSX"]
570 if ord_v == t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]:
571 t["T_MEMTHING"].append((type, y, x))
574 def set_world_inactive():
575 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
577 if os.access(io_db["path_worldstate"], os.F_OK):
578 os.remove(io_db["path_worldstate"])
579 world_db["WORLD_ACTIVE"] = 0
582 def integer_test(val_string, min, max=None):
583 """Return val_string if possible integer >= min and <= max, else None."""
585 val = int(val_string)
586 if val < min or (max is not None and val > max):
590 msg = "Ignoring: Please use integer >= " + str(min)
592 msg += " and <= " + str(max)
598 def setter(category, key, min, max=None):
599 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
602 val = integer_test(val_string, min, max)
606 if category == "Thing":
607 id_store = command_tid
608 decorator = test_Thing_id
609 elif category == "ThingType":
610 id_store = command_ttid
611 decorator = test_ThingType_id
612 elif category == "ThingAction":
613 id_store = command_taid
614 decorator = test_ThingAction_id
618 val = integer_test(val_string, min, max)
620 world_db[category + "s"][id_store.id][key] = val
624 def build_fov_map(t):
625 """Build Thing's FOV map."""
626 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
627 fovmap = c_pointer_to_bytearray(t["fovmap"])
628 map = c_pointer_to_bytearray(world_db["MAP"])
629 if libpr.build_fov_map(t["T_POSY"], t["T_POSX"], fovmap, map):
630 raise RuntimeError("Malloc error in build_fov_Map().")
634 """Send quick usage info to log."""
635 strong_write(io_db["file_out"], "LOG "
636 + "Use 'w'/'e'/'s'/'d'/'x'/'c' to move, and 'w' to wait.\n")
637 strong_write(io_db["file_out"], "LOG "
638 + "Use 'p' to pick up objects, and 'D' to drop them.\n")
639 strong_write(io_db["file_out"], "LOG "
640 + "Some objects can be used (such as: eaten) by 'u' if "
641 + "they are in your inventory. "
642 + "Use 'Up'/'Down' to navigate the inventory.\n")
643 strong_write(io_db["file_out"], "LOG "
644 + "Use 'l' to toggle 'look' mode (move an exploration cursor "
645 + "instead of the player over the map).\n")
646 strong_write(io_db["file_out"], "LOG "
647 + "Use 'PgUp'/PgDn' to scroll the 'Things here' window.\n")
648 strong_write(io_db["file_out"], "LOG See README file for more details.\n")
651 def decrement_lifepoints(t):
652 """Decrement t's lifepoints by 1, and if to zero, corpse it.
654 If t is the player avatar, only blank its fovmap, so that the client may
655 still display memory data. On non-player things, erase fovmap and memory.
656 Dying actors drop all their things.
658 # 7DRL: Also decrements God's mood; deaths heavily so.
659 # 7DRL: Return 1 if death, else 0.
660 t["T_LIFEPOINTS"] -= 1
661 world_db["GOD_MOOD"] -= 1 # #
662 if 0 == t["T_LIFEPOINTS"]:
663 sadness = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
664 world_db["GOD_MOOD"] -= sadness # #
665 for id in t["T_CARRIES"]:
666 t["T_CARRIES"].remove(id)
667 world_db["Things"][id]["T_POSY"] = t["T_POSY"]
668 world_db["Things"][id]["T_POSX"] = t["T_POSX"]
669 world_db["Things"][id]["carried"] = False
670 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
671 if world_db["Things"][0] == t:
672 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
673 strong_write(io_db["file_out"], "LOG You die.\n")
674 strong_write(io_db["file_out"],
675 "LOG See README on how to start over.\n")
678 t["T_MEMMAP"] = False
679 t["T_MEMDEPTHMAP"] = False
685 def add_gods_favor(i): # #
686 """"Add to GOD_FAVOR, multiplied with factor growing log. with GOD_MOOD."""
687 def favor_multiplier(i):
689 threshold = math.e * x
690 mood = world_db["GOD_MOOD"]
693 i = i * math.log(mood / x)
694 elif -mood > threshold:
695 i = i / math.log(-mood / x)
697 if -mood > threshold:
698 i = i * math.log(-mood / x)
700 i = i / math.log(mood / x)
702 world_db["GOD_FAVOR"] += favor_multiplier(i)
705 def mv_yx_in_dir_legal(dir, y, x):
706 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
707 dir_c = dir.encode("ascii")[0]
708 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
710 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
711 return (test, libpr.result_y(), libpr.result_x())
714 def enter_altar(): # #
715 """What happens when the player enters the altar."""
716 if world_db["FAVOR_STAGE"] > 9000:
717 strong_write(io_db["file_out"],
718 "LOG You step on a soul-less slab of stone.\n")
720 strong_write(io_db["file_out"], "LOG YOU ENTER SACRED GROUND.\n")
721 if world_db["FAVOR_STAGE"] == 0:
722 world_db["FAVOR_STAGE"] = 1
723 strong_write(io_db["file_out"], "LOG The Island God speaks to you: "
724 + "\"I don't trust you. You intrude on the island's "
725 + "affairs. I think you're a nuisance at best, and a "
726 + "danger to my children at worst. I will give you a "
727 + "chance to lighten my mood, however: For a while now, "
728 + "I've been trying to spread the plant "
729 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
731 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_SYMBOL"]
732 + "\"). I have not been very successful so far. Maybe "
733 + "you can make yourself useful there. I will count "
735 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
736 + " that grows to your favor.\n")
737 elif world_db["FAVOR_STAGE"] == 1 and world_db["GOD_FAVOR"] >= 100:
738 world_db["FAVOR_STAGE"] = 2
739 strong_write(io_db["file_out"], "LOG The Island God speaks to you: "
740 + "\"You could have done worse so far. Maybe you are not "
741 + "the worst to happen to this island since the metal "
742 + "birds threw the great lightning ball. Maybe you can "
743 + "help me spread another plant. It multiplies faster, "
744 + "and it is highly nutritious: "
745 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
747 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_SYMBOL"]
748 + "\"). It is new. I give you the only example. Be very "
749 + "careful with it! I also give you another tool that "
750 + "might be helpful.\n")
751 id = id_setter(-1, "Things")
752 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
754 id = id_setter(-1, "Things")
755 world_db["Things"][id] = new_Thing(world_db["TOOL_0"],
757 elif world_db["FAVOR_STAGE"] == 2 and \
758 0 == len([id for id in world_db["Things"]
759 if world_db["Things"][id]["T_TYPE"]
760 == world_db["PLANT_1"]]):
761 strong_write(io_db["file_out"], "LOG The Island God speaks to you: "
762 + "\"I am greatly disappointed that you lost all "
763 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
764 + " this island had. Here is another one. It cost me "
765 + " great work. Be more careful this time.\n")
766 id = id_setter(-1, "Things")
767 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
770 elif world_db["GOD_FAVOR"] > 9000:
771 world_db["FAVOR_STAGE"] = 9001
772 strong_write(io_db["file_out"], "LOG The Island God speaks to you: "
773 + "\"You have proven yourself worthy of my respect. "
774 + "You were a good citizen to the island, and sometimes "
775 + "a better steward to its inhabitants than me. The "
776 + "island shall miss you when you leave. But you have "
777 + "earned the right to do so. Take this "
778 + world_db["ThingTypes"][world_db["SLIPPERS"]]["TT_NAME"]
779 + " and USE it when you please. It will take you to "
780 + "where you came from. (But do feel free to stay here "
781 + "as long as you like.)\"\n")
782 id = id_setter(-1, "Things")
783 world_db["Things"][id] = new_Thing(world_db["SLIPPERS"],
788 """Make t do nothing (but loudly, if player avatar)."""
789 if t == world_db["Things"][0]:
790 strong_write(io_db["file_out"], "LOG You wait.\n")
794 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
795 # 7DRL: Player wounding (worse: killing) others will lower God's favor.
796 # 7DRL: Player entering the altar triggers enter_altar().
797 # 7DRL: Player with axe chops down trees.
799 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
800 t["T_POSY"], t["T_POSX"])
801 if 1 == move_result[0]:
802 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
803 hitted = [id for id in world_db["Things"]
804 if world_db["Things"][id] != t
805 if world_db["Things"][id]["T_LIFEPOINTS"]
806 if world_db["Things"][id]["T_POSY"] == move_result[1]
807 if world_db["Things"][id]["T_POSX"] == move_result[2]]
810 if t == world_db["Things"][0]:
811 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
812 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
813 strong_write(io_db["file_out"], "LOG You wound " + hitted_name
815 add_gods_favor(-1) # #
817 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
818 strong_write(io_db["file_out"], "LOG " + hitter_name +
820 test = decrement_lifepoints(world_db["Things"][hit_id]) # #(test=)
821 if test and t == world_db["Things"][0]: # #
822 add_gods_favor(-test) # #
824 if (ord("X") == world_db["MAP"][pos] # #
825 or ord("|") == world_db["MAP"][pos]): # #
826 carries_axe = False # #
827 for id in t["T_CARRIES"]: # #
828 type = world_db["Things"][id]["T_TYPE"] # #
829 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
830 carries_axe = True # #
833 axe_name = world_db["ThingTypes"][type]["TT_NAME"] # #
834 if t == world_db["Things"][0]: # #
835 strong_write(io_db["file_out"], "LOG With your " # #
837 + ", you chop!\n") # #
838 if ord("X") == world_db["MAP"][pos]: # #
839 add_gods_favor(-1) # #
840 chop_power = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
841 case_X = world_db["MAP"][pos] == ord("X") # #
842 if (chop_power > 0 # #
844 0 == int(rand.next() / chop_power)) # #
845 or (not case_X and # #
846 0 == int(rand.next() / (3 * chop_power))))): # #
847 if t == world_db["Things"][0]: # #
848 strong_write(io_db["file_out"], # #
849 "LOG You chop it down.\n") # #
850 if world_db["MAP"][pos] == ord("X"): # #
851 add_gods_favor(-10) # #
852 world_db["MAP"][pos] = ord(".") # #
853 i = 3 if case_X else 1 # #
854 for i in range(i): # #
855 id = id_setter(-1, "Things") # #
856 world_db["Things"][id] = \
857 new_Thing(world_db["LUMBER"], # #
858 (move_result[1], move_result[2])) # #
861 passable = ("." == chr(world_db["MAP"][pos]) or
862 ":" == chr(world_db["MAP"][pos]) or # #
863 "_" == chr(world_db["MAP"][pos])) # #
864 dir = [dir for dir in directions_db
865 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
867 t["T_POSY"] = move_result[1]
868 t["T_POSX"] = move_result[2]
869 for id in t["T_CARRIES"]:
870 world_db["Things"][id]["T_POSY"] = move_result[1]
871 world_db["Things"][id]["T_POSX"] = move_result[2]
873 if t == world_db["Things"][0]:
874 strong_write(io_db["file_out"], "LOG You move " + dir + ".\n")
875 if (move_result[1] == world_db["altar"][0] and # #
876 move_result[2] == world_db["altar"][1]): # #
878 elif t == world_db["Things"][0]:
879 strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n")
882 def actor_pick_up(t):
883 """Make t pick up (topmost?) Thing from ground into inventory.
885 Define topmostness by how low the thing's type ID is.
887 # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
888 used_slots = len(t["T_CARRIES"]) # #
889 if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
890 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
891 if not world_db["Things"][id]["carried"]
892 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
893 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
897 tid = world_db["Things"][iid]["T_TYPE"]
898 if lowest_tid == -1 or tid < lowest_tid:
901 world_db["Things"][id]["carried"] = True
902 type = world_db["Things"][id]["T_TYPE"] # #
903 if (t != world_db["Things"][0] # #
904 and world_db["Things"][id]["T_PLAYERDROP"] # #
905 and world_db["ThingTypes"][type]["TT_TOOL"] == "food"): # #
906 score = world_db["ThingTypes"][type]["TT_TOOLPOWER"] / 32 # #
907 add_gods_favor(score) # #
908 world_db["Things"][id]["T_PLAYERDROP"] = 0 # #
909 t["T_CARRIES"].append(id)
910 if t == world_db["Things"][0]:
911 strong_write(io_db["file_out"], "LOG You pick up an object.\n")
912 elif t == world_db["Things"][0]:
913 err = "You try to pick up an object, but there is none."
914 strong_write(io_db["file_out"], "LOG " + err + "\n")
915 elif t == world_db["Things"][0]: # #
916 strong_write(io_db["file_out"], "LOG Can't pick up object: " + # #
917 "No storage room to carry more.\n") # #
921 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
922 # TODO: Handle case where T_ARGUMENT matches nothing.
923 if len(t["T_CARRIES"]):
924 id = t["T_CARRIES"][t["T_ARGUMENT"]]
925 t["T_CARRIES"].remove(id)
926 world_db["Things"][id]["carried"] = False
927 if t == world_db["Things"][0]:
928 strong_write(io_db["file_out"], "LOG You drop an object.\n")
929 world_db["Things"][id]["T_PLAYERDROP"] = 1 # #
930 elif t == world_db["Things"][0]:
931 err = "You try to drop an object, but you own none."
932 strong_write(io_db["file_out"], "LOG " + err + "\n")
936 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
937 # TODO: Handle case where T_ARGUMENT matches nothing.
938 # 7DLR: Handle SLIPPERS-type Thing use.
939 if len(t["T_CARRIES"]):
940 id = t["T_CARRIES"][t["T_ARGUMENT"]]
941 type = world_db["Things"][id]["T_TYPE"]
942 if type == world_db["SLIPPERS"]: # #
943 if t == world_db["Things"][0]: # #
944 strong_write(io_db["file_out"], "LOG You use the " # #
945 + world_db["ThingTypes"][type]["TT_NAME"] # #
946 + ". It glows in wondrous colors, and emits " # #
947 + "a sound as if from a dying cat. The " # #
948 + "Island God laughs.\n") # #
949 t["T_LIFEPOINTS"] = 1 # #
950 decrement_lifepoints(t) # #
951 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "axe" # #
952 and t == world_db["Things"][0]): # #
953 strong_write(io_db["file_out"], # #
954 "LOG To use this item for chopping, move " # #
955 "towards a tree while carrying it in " # #
956 "your inventory.\n") # #
957 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry"): # #
958 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
959 if (world_db["MAP"][pos] == ord("X") # #
960 or world_db["MAP"][pos] == ord("|")): # #
961 strong_write(io_db["file_out"], # #
962 "LOG Can't build when standing on barrier.\n") # #
964 for id in [id for id in world_db["Things"]
965 if not world_db["Things"][id] == t
966 if not world_db["Things"][id]["carried"]
967 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
968 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]:
969 strong_write(io_db["file_out"],
970 "LOG Can't build when standing objects.\n") # #
972 for id in t["T_CARRIES"]: # #
973 type_tool = world_db["Things"][id]["T_TYPE"] # #
974 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
978 for id in t["T_CARRIES"]: # #
979 type_material = world_db["Things"][id]["T_TYPE"] # #
980 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
984 if wood_id != None: # #
985 t["T_CARRIES"].remove(wood_id) # #
986 del world_db["Things"][wood_id] # #
987 world_db["MAP"][pos] = ord("|") # #
988 strong_write(io_db["file_out"], "LOG With your " # #
989 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
990 + " you build a wooden barrier from your " # #
991 + world_db["ThingTypes"][type_material] # #
995 strong_write(io_db["file_out"], "LOG You can't use a " # #
996 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
997 + " without some wood in your inventory.\n") # #
998 elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
999 t["T_CARRIES"].remove(id)
1000 del world_db["Things"][id]
1001 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1002 if t == world_db["Things"][0]:
1003 strong_write(io_db["file_out"],
1004 "LOG You consume this object.\n")
1005 elif t == world_db["Things"][0]:
1006 strong_write(io_db["file_out"],
1007 "LOG You try to use this object, but fail.\n")
1008 elif t == world_db["Things"][0]:
1009 strong_write(io_db["file_out"],
1010 "LOG You try to use an object, but you own none.\n")
1013 def thingproliferation(t, prol_map):
1014 """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1016 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1017 marked "." in prol_map. If there are several map cell candidates, one is
1020 # 7DRL: success increments God's mood
1021 # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1022 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1023 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
1025 for dir in [directions_db[key] for key in directions_db]:
1026 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1027 pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1028 if mv_result[0] and \
1029 (ord(":") == prol_map[pos] # #
1030 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1031 and ord(".") == prol_map[pos])):
1032 # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1033 # * world_db["MAP_LENGTH"]
1035 candidates.append((mv_result[1], mv_result[2]))
1037 i = rand.next() % len(candidates)
1038 id = id_setter(-1, "Things")
1039 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1040 world_db["Things"][id] = newT
1041 animacy = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1043 world_db["GOD_MOOD"] += animacy # #
1045 world_db["GOD_MOOD"] += 1 # #
1046 if (world_db["FAVOR_STAGE"] > 0 # #
1047 and t["T_TYPE"] == world_db["PLANT_0"]): # #
1048 world_db["GOD_FAVOR"] += 5 # #
1049 elif t["T_TYPE"] == world_db["PLANT_1"]: # #
1050 world_db["GOD_FAVOR"] += 25 # #
1054 """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting.
1056 On success, decrease satiation score by 32.
1058 # 7DRL: Successful heals increment God's mood.
1059 if t["T_SATIATION"] > 0 \
1060 and t["T_LIFEPOINTS"] < \
1061 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \
1062 and 0 == (rand.next() % 31) \
1063 and t["T_COMMAND"] == [id for id in world_db["ThingActions"]
1064 if world_db["ThingActions"][id]["TA_NAME"] ==
1066 t["T_LIFEPOINTS"] += 1
1067 world_db["GOD_MOOD"] += 1 # #
1068 t["T_SATIATION"] -= 32
1069 if t == world_db["Things"][0]:
1070 strong_write(io_db["file_out"], "LOG You heal.\n")
1074 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1075 if t["T_SATIATION"] > -32768:
1076 t["T_SATIATION"] -= 1
1077 testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"]
1078 if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1079 raise RuntimeError("A thing that should not hunger is hungering.")
1080 stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"])
1081 if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)):
1082 if t == world_db["Things"][0]:
1083 strong_write(io_db["file_out"], "LOG You suffer from hunger.\n")
1084 decrement_lifepoints(t)
1087 def get_dir_to_target(t, filter):
1088 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1090 The path-wise nearest target is chosen, via the shortest available path.
1091 Target must not be t. On succcess, return positive value, else False.
1093 "a": Thing in FOV is below a certain distance, animate, but of ThingType
1094 that is not t's, and starts out weaker than t is; build path as
1095 avoiding things of t's ThingType
1096 "f": neighbor cell (not inhabited by any animate Thing) further away from
1097 animate Thing not further than x steps away and in FOV and of a
1098 ThingType that is not t's, and starts out stronger or as strong as t
1099 is currently; or (cornered), if no such flight cell, but Thing of
1100 above criteria is too near,1 a cell closer to it, or, if less near,
1102 "c": Thing in memorized map is consumable
1103 "s": memory map cell with greatest-reachable degree of unexploredness
1106 def zero_score_map_where_char_on_memdepthmap(c):
1107 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1108 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1109 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1110 # set_map_score(i, 0)
1111 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1112 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1113 raise RuntimeError("No score map allocated for "
1114 "zero_score_map_where_char_on_memdepthmap().")
1116 def set_map_score(pos, score):
1117 test = libpr.set_map_score(pos, score)
1119 raise RuntimeError("No score map allocated for set_map_score().")
1121 def get_map_score(pos):
1122 result = libpr.get_map_score(pos)
1124 raise RuntimeError("No score map allocated for get_map_score().")
1128 if t["fovmap"] and ("a" == filter or "f" == filter):
1129 for id in world_db["Things"]:
1130 Thing = world_db["Things"][id]
1131 if Thing != t and Thing["T_LIFEPOINTS"] and \
1132 t["T_TYPE"] != Thing["T_TYPE"] and \
1133 'v' == chr(t["fovmap"][(Thing["T_POSY"]
1134 * world_db["MAP_LENGTH"])
1135 + Thing["T_POSX"]]):
1136 ThingType = world_db["ThingTypes"][Thing["T_TYPE"]]
1137 if ("f" == filter and ThingType["TT_LIFEPOINTS"] >=
1138 t["T_LIFEPOINTS"]) \
1139 or ("a" == filter and ThingType["TT_LIFEPOINTS"] <
1142 elif t["T_MEMMAP"] and "c" == filter:
1143 for mt in t["T_MEMTHING"]:
1144 if ' ' != chr(t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1146 and world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food":
1150 def set_cells_passable_on_memmap_to_65534_on_scoremap():
1151 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1152 # ord_dot = ord(".")
1153 # memmap = t["T_MEMMAP"]
1154 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1155 # if ord_dot == memmap[i]]:
1156 # set_map_score(i, 65534) # i.e. 65535-1
1157 map = c_pointer_to_bytearray(t["T_MEMMAP"])
1158 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1159 raise RuntimeError("No score map allocated for "
1160 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1162 def init_score_map():
1163 test = libpr.init_score_map()
1165 raise RuntimeError("Malloc error in init_score_map().")
1167 ord_blank = ord(" ")
1168 set_cells_passable_on_memmap_to_65534_on_scoremap()
1170 for id in world_db["Things"]:
1171 Thing = world_db["Things"][id]
1172 pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
1174 if t != Thing and Thing["T_LIFEPOINTS"] and \
1175 t["T_TYPE"] != Thing["T_TYPE"] and \
1176 ord_v == t["fovmap"][pos] and \
1177 t["T_LIFEPOINTS"] > \
1178 world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
1179 set_map_score(pos, 0)
1180 elif t["T_TYPE"] == Thing["T_TYPE"]:
1181 set_map_score(pos, 65535)
1183 for id in [id for id in world_db["Things"]
1184 if world_db["Things"][id]["T_LIFEPOINTS"]]:
1185 Thing = world_db["Things"][id]
1186 pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
1188 if t["T_TYPE"] != Thing["T_TYPE"] and \
1189 ord_v == t["fovmap"][pos] and \
1190 t["T_LIFEPOINTS"] <= \
1191 world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
1192 set_map_score(pos, 0)
1194 for mt in [mt for mt in t["T_MEMTHING"]
1195 if ord_blank != t["T_MEMMAP"][mt[1]
1196 * world_db["MAP_LENGTH"]
1198 if world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"]:
1199 set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0)
1201 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1203 def rand_target_dir(neighbors, cmp, dirs):
1206 for i in range(len(dirs)):
1207 if cmp == neighbors[i]:
1208 candidates.append(dirs[i])
1210 return candidates[rand.next() % n_candidates] if n_candidates else 0
1212 def get_neighbor_scores(dirs, eye_pos):
1214 if libpr.ready_neighbor_scores(eye_pos):
1215 raise RuntimeError("No score map allocated for " +
1216 "ready_neighbor_scores.()")
1217 for i in range(len(dirs)):
1218 scores.append(libpr.get_neighbor_score(i))
1221 def get_dir_from_neighbors():
1222 dir_to_target = False
1224 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1225 neighbors = get_neighbor_scores(dirs, eye_pos)
1227 inhabited = [world_db["Things"][id]["T_POSY"]
1228 * world_db["MAP_LENGTH"]
1229 + world_db["Things"][id]["T_POSX"]
1230 for id in world_db["Things"]
1231 if world_db["Things"][id]["T_LIFEPOINTS"]]
1232 for i in range(len(dirs)):
1233 mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"])
1234 pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \
1236 for pos in [pos for pos in inhabited if pos == pos_cmp]:
1237 neighbors[i] = 65535
1239 minmax_start = 0 if "f" == filter else 65535 - 1
1240 minmax_neighbor = minmax_start
1241 for i in range(len(dirs)):
1242 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1243 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1244 or ("f" != filter and minmax_neighbor > neighbors[i]):
1245 minmax_neighbor = neighbors[i]
1246 if minmax_neighbor != minmax_start:
1247 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1249 if not dir_to_target:
1250 if 1 == get_map_score(eye_pos):
1251 dir_to_target = rand_target_dir(neighbors, 0, dirs)
1252 elif 3 >= get_map_score(eye_pos):
1253 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1255 world_db["ThingActions"][id]["TA_NAME"]
1258 elif dir_to_target and 3 < get_map_score(eye_pos):
1260 elif "a" == filter and 10 <= get_map_score(eye_pos):
1262 return dir_to_target
1264 dir_to_target = False
1266 run_i = 9 + 1 if "s" == filter else 1
1267 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1270 mem_depth_c = b'9' if b' ' == mem_depth_c \
1271 else bytes([mem_depth_c[0] - 1])
1272 if libpr.dijkstra_map():
1273 raise RuntimeError("No score map allocated for dijkstra_map().")
1274 dir_to_target = get_dir_from_neighbors()
1275 libpr.free_score_map()
1276 if dir_to_target and str == type(dir_to_target):
1277 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1278 if world_db["ThingActions"][id]["TA_NAME"]
1280 t["T_ARGUMENT"] = ord(dir_to_target)
1281 return dir_to_target
1284 def standing_on_food(t):
1285 """Return True/False whether t is standing on a consumable."""
1286 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1287 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1288 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1289 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1290 ["TT_TOOL"] == "food"]:
1295 def get_inventory_slot_to_consume(t):
1296 """Return slot Id of strongest consumable in t's inventory, else -1."""
1300 for id in t["T_CARRIES"]:
1301 type = world_db["Things"][id]["T_TYPE"]
1302 if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1303 and world_db["ThingTypes"][type]["TT_TOOLPOWER"] > cmp_food:
1304 cmp_food = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1311 """Determine next command/argment for actor t via AI algorithms.
1313 AI will look for, and move towards, enemies (animate Things not of their
1314 own ThingType); if they see none, they will consume consumables in their
1315 inventory; if there are none, they will pick up what they stand on if they
1316 stand on consumables; if they stand on none, they will move towards the
1317 next consumable they see or remember on the map; if they see or remember
1318 none, they will explore parts of the map unseen since ever or for at least
1319 one turn; if there is nothing to explore, they will simply wait.
1321 # 7DRL add: Don't pick up or search things when inventory is full.
1322 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1323 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1324 if not get_dir_to_target(t, "f"):
1325 sel = get_inventory_slot_to_consume(t)
1327 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1328 if world_db["ThingActions"][id]["TA_NAME"]
1330 t["T_ARGUMENT"] = sel
1331 elif standing_on_food(t) \
1332 and (len(t["T_CARRIES"]) < # #
1333 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1334 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1335 if world_db["ThingActions"][id]["TA_NAME"]
1338 (len(t["T_CARRIES"]) < # #
1339 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"] # #
1340 and get_dir_to_target(t, "c"))) and \
1341 (not get_dir_to_target(t, "a")):
1342 get_dir_to_target(t, "s")
1346 """Run game world and its inhabitants until new player input expected."""
1348 whilebreaker = False
1349 while world_db["Things"][0]["T_LIFEPOINTS"]:
1350 proliferable_map = world_db["MAP"][:]
1351 for id in [id for id in world_db["Things"]
1352 if not world_db["Things"][id]["carried"]]:
1353 y = world_db["Things"][id]["T_POSY"]
1354 x = world_db["Things"][id]["T_POSX"]
1355 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1356 for id in [id for id in world_db["Things"]]: # Only what's from start!
1357 if not id in world_db["Things"] or \
1358 world_db["Things"][id]["carried"]: # May have been consumed or
1359 continue # picked up during turn …
1360 Thing = world_db["Things"][id]
1361 if Thing["T_LIFEPOINTS"]:
1362 if not Thing["T_COMMAND"]:
1363 update_map_memory(Thing)
1370 if Thing["T_LIFEPOINTS"]:
1371 Thing["T_PROGRESS"] += 1
1372 taid = [a for a in world_db["ThingActions"]
1373 if a == Thing["T_COMMAND"]][0]
1374 ThingAction = world_db["ThingActions"][taid]
1375 if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1376 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1377 Thing["T_COMMAND"] = 0
1378 Thing["T_PROGRESS"] = 0
1379 thingproliferation(Thing, proliferable_map)
1382 world_db["TURN"] += 1
1385 def new_Thing(type, pos=(0, 0)):
1386 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1388 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1393 "T_PLAYERDROP": 0, # #
1401 "T_MEMDEPTHMAP": False,
1404 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1405 build_fov_map(thing)
1409 def id_setter(id, category, id_store=False, start_at_1=False):
1410 """Set ID of object of category to manipulate ID unused? Create new one.
1412 The ID is stored as id_store.id (if id_store is set). If the integer of the
1413 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1414 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1415 always returned when no new object is created, otherwise the new object's
1418 min = 0 if start_at_1 else -1
1420 id = integer_test(id, min)
1422 if id in world_db[category]:
1427 if (start_at_1 and 0 == id) \
1428 or ((not start_at_1) and (id < 0)):
1429 id = 0 if start_at_1 else -1
1432 if id not in world_db[category]:
1440 """Send PONG line to server output file."""
1441 strong_write(io_db["file_out"], "PONG\n")
1445 """Abort server process."""
1446 if None == opts.replay:
1447 if world_db["WORLD_ACTIVE"]:
1449 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1450 raise SystemExit("received QUIT command")
1453 def command_thingshere(str_y, str_x):
1454 """Write to out file list of Things known to player at coordinate y, x."""
1455 # 7DRL: terrain, too
1456 if world_db["WORLD_ACTIVE"]:
1457 y = integer_test(str_y, 0, 255)
1458 x = integer_test(str_x, 0, 255)
1459 length = world_db["MAP_LENGTH"]
1460 if None != y and None != x and y < length and x < length:
1461 pos = (y * world_db["MAP_LENGTH"]) + x
1462 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1463 pos = y * world_db["MAP_LENGTH"] + x; # #
1464 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1465 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1466 for id in world_db["Things"]
1467 if not world_db["Things"][id]["carried"]
1468 if world_db["Things"][id]["T_TYPE"] == tid
1469 if y == world_db["Things"][id]["T_POSY"]
1470 if x == world_db["Things"][id]["T_POSX"]]:
1471 type = world_db["Things"][id]["T_TYPE"]
1472 name = world_db["ThingTypes"][type]["TT_NAME"]
1473 strong_write(io_db["file_out"], name + "\n")
1475 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1476 for mt in world_db["Things"][0]["T_MEMTHING"]
1477 if mt[0] == tid if y == mt[1] if x == mt[2]]:
1478 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1479 strong_write(io_db["file_out"], name + "\n")
1480 if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"): # #
1481 name = "(terrain: SEA)" # #
1482 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."): # #
1483 name = "(terrain: EARTH)" # #
1484 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"): # #
1485 name = "(terrain: SOIL)" # #
1486 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"): # #
1487 name = "(terrain: TREE)" # #
1488 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"): # #
1489 name = "(terrain: WALL)" # #
1490 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"): # #
1491 name = "(terrain: ALTAR)" # #
1494 strong_write(io_db["file_out"], name + "\n") # #
1495 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1497 print("Ignoring: Invalid map coordinates.")
1499 print("Ignoring: Command only works on existing worlds.")
1502 def play_commander(action, args=False):
1503 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1505 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1509 id = [x for x in world_db["ThingActions"]
1510 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1511 world_db["Things"][0]["T_COMMAND"] = id
1514 def set_command_and_argument_int(str_arg):
1515 val = integer_test(str_arg, 0, 255)
1517 world_db["Things"][0]["T_ARGUMENT"] = val
1520 def set_command_and_argument_movestring(str_arg):
1521 if str_arg in directions_db:
1522 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1525 print("Ignoring: Argument must be valid direction string.")
1527 if action == "move":
1528 return set_command_and_argument_movestring
1530 return set_command_and_argument_int
1535 def command_seedrandomness(seed_string):
1536 """Set rand seed to int(seed_string)."""
1537 val = integer_test(seed_string, 0, 4294967295)
1542 def command_makeworld(seed_string):
1543 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1545 Seed rand with seed. Do more only with a "wait" ThingAction and
1546 world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1547 world_db["Things"] emptied, call make_map() and set
1548 world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1549 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1550 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1551 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1555 # def free_pos(plant=False):
1556 def free_pos(plant=False): # #
1559 err = "Space to put thing on too hard to find. Map too small?"
1561 y = rand.next() % world_db["MAP_LENGTH"]
1562 x = rand.next() % world_db["MAP_LENGTH"]
1563 pos = y * world_db["MAP_LENGTH"] + x;
1565 and "." == chr(world_db["MAP"][pos])) \
1566 or ":" == chr(world_db["MAP"][pos]): # #
1570 raise SystemExit(err)
1571 # Replica of C code, wrongly ignores animatedness of new Thing.
1572 pos_clear = (0 == len([id for id in world_db["Things"]
1573 if world_db["Things"][id]["T_LIFEPOINTS"]
1574 if world_db["Things"][id]["T_POSY"] == y
1575 if world_db["Things"][id]["T_POSX"] == x]))
1580 val = integer_test(seed_string, 0, 4294967295)
1584 player_will_be_generated = False
1585 playertype = world_db["PLAYER_TYPE"]
1586 for ThingType in world_db["ThingTypes"]:
1587 if playertype == ThingType:
1588 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1589 player_will_be_generated = True
1591 if not player_will_be_generated:
1592 print("Ignoring: No player type with start number >0 defined.")
1595 for ThingAction in world_db["ThingActions"]:
1596 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1599 print("Ignoring: No thing action with name 'wait' defined.")
1601 for name in specials: # #
1602 if world_db[name] not in world_db["ThingTypes"]: # #
1603 print("Ignoring: No valid " + name + " set.") # #
1605 world_db["Things"] = {}
1607 world_db["WORLD_ACTIVE"] = 1
1608 world_db["TURN"] = 1
1609 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1610 id = id_setter(-1, "Things")
1611 world_db["Things"][id] = new_Thing(playertype, free_pos())
1612 if not world_db["Things"][0]["fovmap"]:
1613 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1614 world_db["Things"][0]["fovmap"] = empty_fovmap
1615 update_map_memory(world_db["Things"][0])
1616 for type in world_db["ThingTypes"]:
1617 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1618 if type != playertype:
1619 id = id_setter(-1, "Things")
1620 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"] # #
1621 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1622 strong_write(io_db["file_out"], "NEW_WORLD\n")
1626 def command_maplength(maplength_string):
1627 """Redefine map length. Invalidate map, therefore lose all things on it."""
1628 val = integer_test(maplength_string, 1, 256)
1630 world_db["MAP_LENGTH"] = val
1631 world_db["MAP"] = False
1632 set_world_inactive()
1633 world_db["Things"] = {}
1634 libpr.set_maplength(val)
1637 def command_worldactive(worldactive_string):
1638 """Toggle world_db["WORLD_ACTIVE"] if possible.
1640 An active world can always be set inactive. An inactive world can only be
1641 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1642 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1643 Also call log_help().
1645 # 7DRL: altar must be on map, and specials must be set for active world.
1646 val = integer_test(worldactive_string, 0, 1)
1648 if 0 != world_db["WORLD_ACTIVE"]:
1650 set_world_inactive()
1652 print("World already active.")
1653 elif 0 == world_db["WORLD_ACTIVE"]:
1655 for ThingAction in world_db["ThingActions"]:
1656 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1659 player_exists = False
1660 for Thing in world_db["Things"]:
1662 player_exists = True
1664 specials_set = True # #
1665 for name in specials: # #
1666 if world_db[name] not in world_db["ThingTypes"]: # #
1667 specials_set = False # #
1668 altar_found = False # #
1669 if world_db["MAP"]: # #
1670 pos = world_db["MAP"].find(b'_') # #
1672 y = int(pos / world_db["MAP_LENGTH"]) # #
1673 x = pos % world_db["MAP_LENGTH"] # #
1674 world_db["altar"] = (y, x) # #
1675 altar_found = True # #
1676 if wait_exists and player_exists and world_db["MAP"] \
1677 and specials_set: # #
1678 for id in world_db["Things"]:
1679 if world_db["Things"][id]["T_LIFEPOINTS"]:
1680 build_fov_map(world_db["Things"][id])
1682 update_map_memory(world_db["Things"][id], False)
1683 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1684 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1685 world_db["Things"][0]["fovmap"] = empty_fovmap
1686 world_db["WORLD_ACTIVE"] = 1
1689 print("Ignoring: Not all conditions for world activation met.")
1692 def specialtypesetter(name): # #
1693 """Setter world_db[name], deactivating world if set int no ThingType."""
1694 def helper(str_int):
1695 val = integer_test(str_int, 0)
1697 world_db[name] = val
1698 if world_db["WORLD_ACTIVE"] \
1699 and world_db[name] not in world_db["ThingTypes"]:
1700 world_db["WORLD_ACTIVE"] = 0
1701 print(name + " fits no known ThingType, deactivating world.")
1705 def test_for_id_maker(object, category):
1706 """Return decorator testing for object having "id" attribute."""
1709 if hasattr(object, "id"):
1712 print("Ignoring: No " + category +
1713 " defined to manipulate yet.")
1718 def command_tid(id_string):
1719 """Set ID of Thing to manipulate. ID unused? Create new one.
1721 Default new Thing's type to the first available ThingType, others: zero.
1723 id = id_setter(id_string, "Things", command_tid)
1725 if world_db["ThingTypes"] == {}:
1726 print("Ignoring: No ThingType to settle new Thing in.")
1728 type = list(world_db["ThingTypes"].keys())[0]
1729 world_db["Things"][id] = new_Thing(type)
1732 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1736 def command_tcommand(str_int):
1737 """Set T_COMMAND of selected Thing."""
1738 val = integer_test(str_int, 0)
1740 if 0 == val or val in world_db["ThingActions"]:
1741 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1743 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1747 def command_ttype(str_int):
1748 """Set T_TYPE of selected Thing."""
1749 val = integer_test(str_int, 0)
1751 if val in world_db["ThingTypes"]:
1752 world_db["Things"][command_tid.id]["T_TYPE"] = val
1754 print("Ignoring: ThingType ID belongs to no known ThingType.")
1758 def command_tcarries(str_int):
1759 """Append int(str_int) to T_CARRIES of selected Thing.
1761 The ID int(str_int) must not be of the selected Thing, and must belong to a
1762 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1764 val = integer_test(str_int, 0)
1766 if val == command_tid.id:
1767 print("Ignoring: Thing cannot carry itself.")
1768 elif val in world_db["Things"] \
1769 and not world_db["Things"][val]["carried"]:
1770 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1771 world_db["Things"][val]["carried"] = True
1773 print("Ignoring: Thing not available for carrying.")
1774 # Note that the whole carrying structure is different from the C version:
1775 # Carried-ness is marked by a "carried" flag, not by Things containing
1776 # Things internally.
1780 def command_tmemthing(str_t, str_y, str_x):
1781 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1783 The type must fit to an existing ThingType, and the position into the map.
1785 type = integer_test(str_t, 0)
1786 posy = integer_test(str_y, 0, 255)
1787 posx = integer_test(str_x, 0, 255)
1788 if None != type and None != posy and None != posx:
1789 if type not in world_db["ThingTypes"] \
1790 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1791 print("Ignoring: Illegal value for thing type or position.")
1793 memthing = (type, posy, posx)
1794 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1797 def setter_map(maptype):
1798 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1800 If no map of maptype exists yet, initialize it with ' ' bytes first.
1803 def valid_map_line(str_int, mapline):
1804 val = integer_test(str_int, 0, 255)
1806 if val >= world_db["MAP_LENGTH"]:
1807 print("Illegal value for map line number.")
1808 elif len(mapline) != world_db["MAP_LENGTH"]:
1809 print("Map line length is unequal map width.")
1814 def nonThingMap_helper(str_int, mapline):
1815 val = valid_map_line(str_int, mapline)
1817 length = world_db["MAP_LENGTH"]
1818 if not world_db["MAP"]:
1819 map = bytearray(b' ' * (length ** 2))
1821 map = world_db["MAP"]
1822 map[val * length:(val * length) + length] = mapline.encode()
1823 if not world_db["MAP"]:
1824 world_db["MAP"] = map
1827 def ThingMap_helper(str_int, mapline):
1828 val = valid_map_line(str_int, mapline)
1830 length = world_db["MAP_LENGTH"]
1831 if not world_db["Things"][command_tid.id][maptype]:
1832 map = bytearray(b' ' * (length ** 2))
1834 map = world_db["Things"][command_tid.id][maptype]
1835 map[val * length:(val * length) + length] = mapline.encode()
1836 if not world_db["Things"][command_tid.id][maptype]:
1837 world_db["Things"][command_tid.id][maptype] = map
1839 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
1842 def setter_tpos(axis):
1843 """Generate setter for T_POSX or T_POSY of selected Thing.
1845 If world is active, rebuilds animate things' fovmap, player's memory map.
1848 def helper(str_int):
1849 val = integer_test(str_int, 0, 255)
1851 if val < world_db["MAP_LENGTH"]:
1852 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1853 if world_db["WORLD_ACTIVE"] \
1854 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1855 build_fov_map(world_db["Things"][command_tid.id])
1856 if 0 == command_tid.id:
1857 update_map_memory(world_db["Things"][command_tid.id])
1859 print("Ignoring: Position is outside of map.")
1863 def command_ttid(id_string):
1864 """Set ID of ThingType to manipulate. ID unused? Create new one.
1866 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
1869 id = id_setter(id_string, "ThingTypes", command_ttid)
1871 world_db["ThingTypes"][id] = {
1872 "TT_NAME": "(none)",
1875 "TT_PROLIFERATE": 0,
1876 "TT_START_NUMBER": 0,
1877 "TT_STORAGE": 0, # #
1884 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1888 def command_ttname(name):
1889 """Set TT_NAME of selected ThingType."""
1890 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1894 def command_tttool(name):
1895 """Set TT_TOOL of selected ThingType."""
1896 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
1900 def command_ttsymbol(char):
1901 """Set TT_SYMBOL of selected ThingType. """
1903 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1905 print("Ignoring: Argument must be single character.")
1909 def command_ttcorpseid(str_int):
1910 """Set TT_CORPSE_ID of selected ThingType."""
1911 val = integer_test(str_int, 0)
1913 if val in world_db["ThingTypes"]:
1914 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1916 print("Ignoring: Corpse ID belongs to no known ThignType.")
1919 def command_taid(id_string):
1920 """Set ID of ThingAction to manipulate. ID unused? Create new one.
1922 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
1924 id = id_setter(id_string, "ThingActions", command_taid, True)
1926 world_db["ThingActions"][id] = {
1932 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
1935 @test_ThingAction_id
1936 def command_taname(name):
1937 """Set TA_NAME of selected ThingAction.
1939 The name must match a valid thing action function. If after the name
1940 setting no ThingAction with name "wait" remains, call set_world_inactive().
1942 if name == "wait" or name == "move" or name == "use" or name == "drop" \
1943 or name == "pick_up":
1944 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
1945 if 1 == world_db["WORLD_ACTIVE"]:
1946 wait_defined = False
1947 for id in world_db["ThingActions"]:
1948 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
1951 if not wait_defined:
1952 set_world_inactive()
1954 print("Ignoring: Invalid action name.")
1955 # In contrast to the original,naming won't map a function to a ThingAction.
1959 """Call ai() on player Thing, then turn_over()."""
1960 ai(world_db["Things"][0])
1964 """Commands database.
1966 Map command start tokens to ([0]) number of expected command arguments, ([1])
1967 the command's meta-ness (i.e. is it to be written to the record file, is it to
1968 be ignored in replay mode if read from server input file), and ([2]) a function
1972 "QUIT": (0, True, command_quit),
1973 "PING": (0, True, command_ping),
1974 "THINGS_HERE": (2, True, command_thingshere),
1975 "MAKE_WORLD": (1, False, command_makeworld),
1976 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
1977 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
1978 "GOD_MOOD": (1, False, setter(None, "GOD_MOOD", -32768, 32767)), # #
1979 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
1980 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
1981 "MAP_LENGTH": (1, False, command_maplength),
1982 "WORLD_ACTIVE": (1, False, command_worldactive),
1983 "MAP": (2, False, setter_map("MAP")),
1984 "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)), # #
1985 "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")), # #
1986 "PLANT_0": (1, False, specialtypesetter("PLANT_0")), # #
1987 "PLANT_1": (1, False, specialtypesetter("PLANT_1")), # #
1988 "LUMBER": (1, False, specialtypesetter("LUMBER")), # #
1989 "TOOL_0": (1, False, specialtypesetter("TOOL_0")), # #
1990 "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)), # #
1991 "TA_ID": (1, False, command_taid),
1992 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
1993 "TA_NAME": (1, False, command_taname),
1994 "TT_ID": (1, False, command_ttid),
1995 "TT_NAME": (1, False, command_ttname),
1996 "TT_TOOL": (1, False, command_tttool),
1997 "TT_SYMBOL": (1, False, command_ttsymbol),
1998 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
1999 "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2000 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2002 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2004 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2005 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
2006 "T_ID": (1, False, command_tid),
2007 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2008 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2009 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2010 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2011 "T_COMMAND": (1, False, command_tcommand),
2012 "T_TYPE": (1, False, command_ttype),
2013 "T_CARRIES": (1, False, command_tcarries),
2014 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2015 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2016 "T_MEMTHING": (3, False, command_tmemthing),
2017 "T_POSY": (1, False, setter_tpos("Y")),
2018 "T_POSX": (1, False, setter_tpos("X")),
2019 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
2020 "wait": (0, False, play_commander("wait")),
2021 "move": (1, False, play_commander("move")),
2022 "pick_up": (0, False, play_commander("pick_up")),
2023 "drop": (1, False, play_commander("drop", True)),
2024 "use": (1, False, play_commander("use", True)),
2025 "ai": (0, False, command_ai)
2027 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2030 """World state database. With sane default values. (Randomness is in rand.)"""
2039 "FAVOR_STAGE": 0, # #
2052 """Special type settings."""
2053 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0"] # #
2055 """Mapping of direction names to internal direction chars."""
2056 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2057 "west": "s", "north-west": "w", "north-east": "e"}
2059 """File IO database."""
2061 "path_save": "save",
2062 "path_record": "record_save",
2063 "path_worldconf": "confserver/world",
2064 "path_server": "server/",
2065 "path_in": "server/in",
2066 "path_out": "server/out",
2067 "path_worldstate": "server/worldstate",
2068 "tmp_suffix": "_tmp",
2069 "kicked_by_rival": False,
2070 "worldstate_updateable": False
2075 libpr = prep_library()
2076 rand = RandomnessIO()
2077 opts = parse_command_line_arguments()
2079 io_db["path_save"] = opts.savefile
2080 io_db["path_record"] = "record_" + opts.savefile
2083 io_db["verbose"] = True
2084 if None != opts.replay:
2088 except SystemExit as exit:
2089 print("ABORTING: " + exit.args[0])
2091 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")