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()
103 """Send "msg" to log."""
104 strong_write(io_db["file_out"], "LOG " + msg + "\n")
107 def obey(command, prefix, replay=False, do_record=False):
108 """Call function from commands_db mapped to command's first token.
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.
123 print("input " + prefix + ": " + command)
125 tokens = shlex.split(command, comments=True)
126 except ValueError as err:
127 print("Can't tokenize command string: " + str(err) + ".")
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.")
136 print("Due to replay mode, reading command as 'go on in record'.")
137 line = io_db["file_record"].readline()
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
143 print("Reached end of record file.")
145 commands_db[tokens[0]][2](*tokens[1:])
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"],
151 if world_db["WORLD_ACTIVE"]:
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.")
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"]
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)
171 if delete and os.access(path, os.F_OK):
173 os.rename(path_tmp, path)
177 """Save all commands needed to reconstruct current world state."""
180 string = string.replace("\u005C", '\u005C\u005C')
181 return '"' + string.replace('"', '\u005C"') + '"'
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) \
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"
204 def helper(category, id_string, special_keys={}):
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)
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)
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")
248 for line in file.readlines():
249 obey(line.rstrip(), name + "file line " + str(line_n),
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,
260 parser.add_argument('-l', nargs="?", const="save", dest='savefile',
262 parser.add_argument('-v', dest='verbose', action='store_true')
263 opts, unknown = parser.parse_known_args()
268 """Ensure valid server out file belonging to current process.
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.
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")
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)
288 """Return next newline-delimited command from server in file.
290 Keep building return string until a newline is encountered. Pause between
291 unsuccessful reads, and after too much waiting, run server_test().
293 wait_on_fail = 0.03333
298 add = io_db["file_in"].readline()
300 command = command + add
301 if len(command) > 0 and "\n" == command[-1]:
302 command = command[:-1]
305 time.sleep(wait_on_fail)
306 if now + max_wait < time.time():
312 def try_worldstate_update():
313 """Write worldstate file if io_db["worldstate_updateable"] is set."""
314 if io_db["worldstate_updateable"]:
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"
323 if [] == world_db["Things"][0]["T_CARRIES"]:
324 inventory = "(none)\n"
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"]
341 fov = bytearray(b' ' * (length ** 2))
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)
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"]
363 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
364 mem[(mt[1] * length) + mt[2]] = ord(c)
365 string = write_map(string, mem)
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"] # #
382 if hp > 2 * third_of_hp: # #
384 elif hp > third_of_hp: # #
386 metamapA[pos] = ord('a') + add # #
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) # #
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"] # #
407 name = world_db["ThingActions"][action]["TA_NAME"] # #
410 metamapB[pos] = ord(name[0]) # #
411 string = write_map(string, metamapB) # #
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
419 """Replay game from record file.
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().
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()
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
443 try_worldstate_update()
444 obey(read_command(), "in file", replay=True)
448 """Play game by server input file commands. Before, load save file found.
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().
455 if os.access(io_db["path_save"], os.F_OK):
456 obey_lines_in_file(io_db["path_save"], "save")
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 ",
463 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
465 try_worldstate_update()
466 obey(read_command(), "in file", do_record=True)
470 """(Re-)make island map.
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).
478 # 7DRL: Also add some ":" cells, and (not surrounded by trees!) "_" altar.
480 def is_neighbor(coordinates, type):
483 length = world_db["MAP_LENGTH"]
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])) \
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)])) \
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)])):
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(".")
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):
514 world_db["MAP"][pos] = ord(".")
515 n_trees = int((length ** 2) / 16)
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")
526 # This all-too-precise replica of the original C code misses iter_limit().
527 n_colons = int((length ** 2) / 16) # #
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(":") # #
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 # #
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
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."""
562 def age_some_memdepthmap_on_nonfov_cells():
563 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
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)
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,
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()
597 age_some_memdepthmap_on_nonfov_cells()
599 t["T_MEMTHING"] = [mt for mt in t["T_MEMTHING"]
600 if ord_v != t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
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"]]
611 if (t == world_db["Things"][0] or
612 (world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
613 ["TT_TOOL"] == "food"
615 world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
616 ["TT_TOOLPOWER"] > eat_cost
619 if ord_v == t["fovmap"][(world_db["Things"][id]["T_POSY"] * maplength)
620 + world_db["Things"][id]["T_POSX"]]]
623 def set_world_inactive():
624 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
626 if os.access(io_db["path_worldstate"], os.F_OK):
627 os.remove(io_db["path_worldstate"])
628 world_db["WORLD_ACTIVE"] = 0
631 def integer_test(val_string, min, max=None):
632 """Return val_string if possible integer >= min and <= max, else None."""
634 val = int(val_string)
635 if val < min or (max is not None and val > max):
639 msg = "Ignoring: Please use integer >= " + str(min)
641 msg += " and <= " + str(max)
647 def setter(category, key, min, max=None):
648 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
651 val = integer_test(val_string, min, max)
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
667 val = integer_test(val_string, min, max)
669 world_db[category + "s"][id_store.id][key] = val
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().")
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.")
694 def decrement_lifepoints(t):
695 """Decrement t's lifepoints by 1, and if to zero, corpse it.
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.
701 # 7DRL: Return 1 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))
715 log("See README on how to start over.")
718 t["T_MEMMAP"] = False
719 t["T_MEMDEPTHMAP"] = False
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 id = id_setter(-1, "Things")
725 world_db["Things"][id] = new_Thing(live_type,
728 + world_db["ThingTypes"][live_type]["TT_NAME"]
729 + " has temporarily died out. "
730 + "One new-born is spawned at the altar.")
731 return world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
735 def mv_yx_in_dir_legal(dir, y, x):
736 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
737 dir_c = dir.encode("ascii")[0]
738 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
740 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
741 return (test, libpr.result_y(), libpr.result_x())
744 def enter_altar(): # #
745 """What happens when the player enters the altar."""
746 if world_db["FAVOR_STAGE"] > 9000:
747 log("You step on a soul-less slab of stone.")
749 log("YOU ENTER SACRED GROUND.")
750 if world_db["FAVOR_STAGE"] == 0:
751 world_db["FAVOR_STAGE"] = 1
752 log("The Island God speaks to you: \"I don't trust you. You intrude "
753 + "on the island's affairs. I think you're a nuisance at best, "
754 + "and a danger to my children at worst. I will give you a "
755 + "chance to lighten my mood, however: For a while now, I've "
756 + "been trying to spread the plant "
757 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"] + " (\""
758 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_SYMBOL"]
759 + "\"). I have not been very successful so far. Maybe you can "
760 + "make yourself useful there. I will count each further "
761 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
762 + " that grows to your favor.\"")
763 elif world_db["FAVOR_STAGE"] == 1 and world_db["GOD_FAVOR"] >= 100:
764 world_db["FAVOR_STAGE"] = 2
765 log("The Island God speaks to you: \"You could have done worse so "
766 + "far. Maybe you are not the worst to happen to this island "
767 + "since the metal birds threw the great lightning ball. Maybe "
768 + "you can help me spread another plant. It multiplies faster, "
769 + "and it is highly nutritious: "
770 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"] + " (\""
771 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_SYMBOL"]
772 + "\"). It is new. I give you the only example. Be very careful "
773 + "with it! I also give you another tool that may be helpful.\"")
774 id = id_setter(-1, "Things")
775 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
777 id = id_setter(-1, "Things")
778 world_db["Things"][id] = new_Thing(world_db["TOOL_0"],
780 elif world_db["FAVOR_STAGE"] == 2 and \
781 0 == len([id for id in world_db["Things"]
782 if world_db["Things"][id]["T_TYPE"]
783 == world_db["PLANT_1"]]):
784 log("The Island God speaks to you: \"I am greatly disappointed that "
786 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
787 + " this island had. Here is another one. It cost me great work. "
788 + "Be more careful this time.\"")
789 id = id_setter(-1, "Things")
790 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
792 world_db["GOD_FAVOR"] -= 250
793 elif world_db["GOD_FAVOR"] > 9000:
794 world_db["FAVOR_STAGE"] = 9001
795 log("The Island God speaks to you: \"You have proven yourself worthy"
796 + " of my respect. You were a good citizen to the island, and "
797 + "sometimes a better steward to its inhabitants than me. The "
798 + "island shall miss you when you leave. But you have earned "
799 + "the right to do so. Take this "
800 + world_db["ThingTypes"][world_db["SLIPPERS"]]["TT_NAME"]
801 + " and USE it when you please. It will take you to where you "
802 + "came from. (But do feel free to stay here as long as you "
804 id = id_setter(-1, "Things")
805 world_db["Things"][id] = new_Thing(world_db["SLIPPERS"],
810 """Make t do nothing (but loudly, if player avatar)."""
811 if t == world_db["Things"][0]:
816 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
817 # 7DRL: Player wounding (worse: killing) others will lower God's favor.
818 # 7DRL: Player entering the altar triggers enter_altar().
819 # 7DRL: Player with axe chops down trees.
821 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
822 t["T_POSY"], t["T_POSX"])
823 if 1 == move_result[0]:
824 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
825 hitted = [id for id in world_db["Things"]
826 if world_db["Things"][id] != t
827 if world_db["Things"][id]["T_LIFEPOINTS"]
828 if world_db["Things"][id]["T_POSY"] == move_result[1]
829 if world_db["Things"][id]["T_POSX"] == move_result[2]]
832 if t == world_db["Things"][0]:
833 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
834 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
835 log("You wound " + hitted_name + ".")
836 world_db["GOD_FAVOR"] -= -1 # #
838 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
839 log(hitter_name +" wounds you.")
840 test = decrement_lifepoints(world_db["Things"][hit_id]) # #(test=)
841 if test and t == world_db["Things"][0]: # #
842 world_db["GOD_FAVOR"] -test # #
844 if (ord("X") == world_db["MAP"][pos] # #
845 or ord("|") == world_db["MAP"][pos]): # #
846 carries_axe = False # #
847 for id in t["T_CARRIES"]: # #
848 type = world_db["Things"][id]["T_TYPE"] # #
849 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
850 carries_axe = True # #
853 axe_name = world_db["ThingTypes"][type]["TT_NAME"] # #
854 if t == world_db["Things"][0]: # #
855 log("With your " + axe_name + ", you chop!\n") # #
856 if ord("X") == world_db["MAP"][pos]: # #
857 world_db["GOD_FAVOR"] = -1 # #
858 chop_power = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
859 case_X = world_db["MAP"][pos] == ord("X") # #
860 if (chop_power > 0 # #
862 0 == int(rand.next() / chop_power)) # #
863 or (not case_X and # #
864 0 == int(rand.next() / (3 * chop_power))))): # #
865 if t == world_db["Things"][0]: # #
866 log("You chop it down.") # #
867 if world_db["MAP"][pos] == ord("X"): # #
868 world_db["GOD_FAVOR"] = -10 # #
869 world_db["MAP"][pos] = ord(".") # #
870 i = 3 if case_X else 1 # #
871 for i in range(i): # #
872 id = id_setter(-1, "Things") # #
873 world_db["Things"][id] = \
874 new_Thing(world_db["LUMBER"], # #
875 (move_result[1], move_result[2])) # #
878 passable = ("." == chr(world_db["MAP"][pos]) or
879 ":" == chr(world_db["MAP"][pos]) or # #
880 "_" == chr(world_db["MAP"][pos])) # #
881 dir = [dir for dir in directions_db
882 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
884 t["T_POSY"] = move_result[1]
885 t["T_POSX"] = move_result[2]
886 for id in t["T_CARRIES"]:
887 world_db["Things"][id]["T_POSY"] = move_result[1]
888 world_db["Things"][id]["T_POSX"] = move_result[2]
890 if t == world_db["Things"][0]:
891 log("You move " + dir + ".")
892 if (move_result[1] == world_db["altar"][0] and # #
893 move_result[2] == world_db["altar"][1]): # #
895 elif t == world_db["Things"][0]:
896 log("You fail to move " + dir + ".")
899 def actor_pick_up(t):
900 """Make t pick up (topmost?) Thing from ground into inventory.
902 Define topmostness by how low the thing's type ID is.
904 # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
905 # 7DRL: Non-players pick up nothing but food of good value to them.
906 used_slots = len(t["T_CARRIES"]) # #
907 if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
908 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
909 if not world_db["Things"][id]["carried"]
910 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
911 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
914 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"]) # #
916 tid = world_db["Things"][iid]["T_TYPE"]
917 if lowest_tid == -1 or tid < lowest_tid:
918 if (t != world_db["Things"][0] and # #
919 (world_db["ThingTypes"][tid]["TT_TOOL"] != "food" # #
920 or (world_db["ThingTypes"][tid]["TT_TOOLPOWER"] # #
925 world_db["Things"][id]["carried"] = True
926 type = world_db["Things"][id]["T_TYPE"] # #
927 if (t != world_db["Things"][0] # #
928 and world_db["Things"][id]["T_PLAYERDROP"] # #
929 and world_db["ThingTypes"][type]["TT_TOOL"] == "food"): # #
930 score = world_db["ThingTypes"][type]["TT_TOOLPOWER"] / 32 # #
931 world_db["GOD_FAVOR"] += score # #
932 world_db["Things"][id]["T_PLAYERDROP"] = 0 # #
933 t["T_CARRIES"].append(id)
934 if t == world_db["Things"][0]:
935 log("You pick up an object.")
936 elif t == world_db["Things"][0]:
937 log("You try to pick up an object, but there is none.")
938 elif t == world_db["Things"][0]: # #
939 log("Can't pick up object: No storage room to carry more.") # #
943 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
944 # TODO: Handle case where T_ARGUMENT matches nothing.
945 if len(t["T_CARRIES"]):
946 id = t["T_CARRIES"][t["T_ARGUMENT"]]
947 t["T_CARRIES"].remove(id)
948 world_db["Things"][id]["carried"] = False
949 if t == world_db["Things"][0]:
950 log("You drop an object.")
951 world_db["Things"][id]["T_PLAYERDROP"] = 1 # #
952 elif t == world_db["Things"][0]:
953 log("You try to drop an object, but you own none.")
957 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
958 # TODO: Handle case where T_ARGUMENT matches nothing.
959 # 7DLR: Handle SLIPPERS-type Thing use.
960 # 7DRL: Player with fertilizer fertilizes
961 if len(t["T_CARRIES"]):
962 id = t["T_CARRIES"][t["T_ARGUMENT"]]
963 type = world_db["Things"][id]["T_TYPE"]
964 if type == world_db["SLIPPERS"]: # #
965 if t == world_db["Things"][0]: # #
966 log("You use the " + world_db["ThingTypes"][type]["TT_NAME"] # #
967 + ". It glows in wondrous colors, and emits a sound as " # #
968 + "if from a dying cat. The Island God laughs.\n") # #
969 t["T_LIFEPOINTS"] = 1 # #
970 decrement_lifepoints(t) # #
971 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "axe" # #
972 and t == world_db["Things"][0]): # #
973 log("To use this item for chopping, move towards a tree while "
974 + "carrying it in your inventory.") # #
975 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry"): # #
976 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
977 if (world_db["MAP"][pos] == ord("X") # #
978 or world_db["MAP"][pos] == ord("|")): # #
979 log("Can't build when standing on barrier.") # #
981 for id in [id for id in world_db["Things"]
982 if not world_db["Things"][id] == t
983 if not world_db["Things"][id]["carried"]
984 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
985 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]:
986 log("Can't build when standing objects.") # #
988 for id in t["T_CARRIES"]: # #
989 type_tool = world_db["Things"][id]["T_TYPE"] # #
990 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
994 for id in t["T_CARRIES"]: # #
995 type_material = world_db["Things"][id]["T_TYPE"] # #
996 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1000 if wood_id != None: # #
1001 t["T_CARRIES"].remove(wood_id) # #
1002 del world_db["Things"][wood_id] # #
1003 world_db["MAP"][pos] = ord("|") # #
1004 log("With your " # #
1005 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1006 + " you build a wooden barrier from your " # #
1007 + world_db["ThingTypes"][type_material]["TT_NAME"] # #
1010 log("You can't use a " # #
1011 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1012 + " without some wood in your inventory.") # #
1013 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1014 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1015 if world_db["MAP"][pos] == ord("."):
1016 log("You create soil.")
1017 world_db["MAP"][pos] = ord(":")
1019 log("Can only make soil out of non-soil earth.")
1020 elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
1021 t["T_CARRIES"].remove(id)
1022 del world_db["Things"][id]
1023 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1024 if t == world_db["Things"][0]:
1025 log("You consume this object.")
1026 elif t == world_db["Things"][0]:
1027 log("You try to use this object, but fail.")
1028 elif t == world_db["Things"][0]:
1029 log("You try to use an object, but you own none.")
1032 def thingproliferation(t, prol_map):
1033 """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1035 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1036 marked "." in prol_map. If there are several map cell candidates, one is
1039 # 7DRL: success increments God's mood
1040 # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1041 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1042 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
1044 for dir in [directions_db[key] for key in directions_db]:
1045 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1046 pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1047 if mv_result[0] and \
1048 (ord(":") == prol_map[pos] # #
1049 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1050 and ord(".") == prol_map[pos])):
1051 # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1052 # * world_db["MAP_LENGTH"]
1054 candidates.append((mv_result[1], mv_result[2]))
1056 i = rand.next() % len(candidates)
1057 id = id_setter(-1, "Things")
1058 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1059 world_db["Things"][id] = newT
1060 if (world_db["FAVOR_STAGE"] > 0 # #
1061 and t["T_TYPE"] == world_db["PLANT_0"]): # #
1062 world_db["GOD_FAVOR"] += 5 # #
1063 elif t["T_TYPE"] == world_db["PLANT_1"]: # #
1064 world_db["GOD_FAVOR"] += 25 # #
1068 """If t's HP < max, increment them if well-nourished, maybe waiting."""
1069 # 7DRL: Successful heals increment God's mood.
1070 if t["T_LIFEPOINTS"] < \
1071 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1072 wait_id = [id for id in world_db["ThingActions"]
1073 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1074 wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
1075 testval = int(abs(t["T_SATIATION"]) / wait_divider)
1076 if (testval <= 1 or 1 == (rand.next() % testval)):
1077 t["T_LIFEPOINTS"] += 1
1078 if t == world_db["Things"][0]:
1082 def hunger_per_turn(type_id):
1083 """The amount of satiation score lost per turn for things of given type."""
1084 return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
1088 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1089 if t["T_SATIATION"] > -32768:
1090 #max_hp = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]
1091 t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"]) # int(math.sqrt(max_hp))
1092 if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
1093 if t == world_db["Things"][0]:
1094 if t["T_SATIATION"] < 0:
1095 log("You suffer from hunger.")
1097 log("You suffer from over-eating.")
1098 decrement_lifepoints(t)
1101 def get_dir_to_target(t, filter):
1102 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1104 The path-wise nearest target is chosen, via the shortest available path.
1105 Target must not be t. On succcess, return positive value, else False.
1107 "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
1108 is, and its corpse would be healthy food for t
1109 "f": move away from an enemy – any visible actor whose thing type has more
1110 TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
1111 food – if it is closer than n steps, where n will shrink as t's hunger
1112 grows; if enemy is too close, move towards (attack) the enemy instead;
1113 if no fleeing is possible, nor attacking useful, wait; don't tread on
1114 non-enemies for fleeing
1115 "c": Thing in memorized map is consumable of sufficient nutrition for t
1116 "s": memory map cell with greatest-reachable degree of unexploredness
1119 def zero_score_map_where_char_on_memdepthmap(c):
1120 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1121 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1122 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1123 # set_map_score(i, 0)
1124 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1125 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1126 raise RuntimeError("No score map allocated for "
1127 "zero_score_map_where_char_on_memdepthmap().")
1129 def set_map_score_at_thingpos(id, score):
1130 pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
1131 + world_db["Things"][id]["T_POSX"]
1132 set_map_score(pos, score)
1134 def set_map_score(pos, score):
1135 test = libpr.set_map_score(pos, score)
1137 raise RuntimeError("No score map allocated for set_map_score().")
1139 def get_map_score(pos):
1140 result = libpr.get_map_score(pos)
1142 raise RuntimeError("No score map allocated for get_map_score().")
1145 def animate_in_fov(Thing, maplength): # maplength needed for optimization?
1146 if not Thing["T_LIFEPOINTS"] or Thing["carried"] or Thing == t:
1148 pos = Thing["T_POSY"] * maplength + Thing["T_POSX"]
1149 if 118 == t["fovmap"][pos]: # optimization: 118 = ord("v")
1152 def good_attack_target(v):
1153 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1154 type = world_db["ThingTypes"][v["T_TYPE"]]
1155 type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
1156 if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
1157 and type_corpse["TT_TOOL"] == "food" \
1158 and type_corpse["TT_TOOLPOWER"] > eat_cost:
1162 def good_flee_target(m):
1163 own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
1164 corpse_type = world_db["ThingTypes"][own_corpse_id]
1165 targetness = 0 if corpse_type["TT_TOOL"] != "food" \
1166 else corpse_type["TT_TOOLPOWER"]
1167 type = world_db["ThingTypes"][m["T_TYPE"]]
1168 if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
1169 and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
1174 maplength = world_db["MAP_LENGTH"]
1175 if t["fovmap"] and "a" == filter:
1176 for id in world_db["Things"]:
1177 if animate_in_fov(world_db["Things"][id], maplength):
1178 if good_attack_target(world_db["Things"][id]):
1180 elif t["fovmap"] and "f" == filter:
1181 for id in world_db["Things"]:
1182 if animate_in_fov(world_db["Things"][id], maplength):
1183 if good_flee_target(world_db["Things"][id]):
1185 elif t["T_MEMMAP"] and "c" == filter:
1186 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1187 ord_blank = ord(" ")
1188 for mt in t["T_MEMTHING"]:
1189 if ord_blank != t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1191 (t != world_db["Things"][0] or \
1192 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"
1193 and world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"]
1198 def set_cells_passable_on_memmap_to_65534_on_scoremap():
1199 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1200 # ord_dot = ord(".")
1201 # memmap = t["T_MEMMAP"]
1202 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1203 # if ord_dot == memmap[i]]:
1204 # set_map_score(i, 65534) # i.e. 65535-1
1205 map = c_pointer_to_bytearray(t["T_MEMMAP"])
1206 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1207 raise RuntimeError("No score map allocated for "
1208 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1210 def init_score_map():
1211 test = libpr.init_score_map()
1213 raise RuntimeError("Malloc error in init_score_map().")
1214 set_cells_passable_on_memmap_to_65534_on_scoremap()
1215 maplength = world_db["MAP_LENGTH"]
1217 [set_map_score_at_thingpos(id, 0)
1218 for id in world_db["Things"]
1219 if animate_in_fov(world_db["Things"][id], maplength)
1220 if good_attack_target(world_db["Things"][id])]
1222 [set_map_score_at_thingpos(id, 0)
1223 for id in world_db["Things"]
1224 if animate_in_fov(world_db["Things"][id], maplength)
1225 if good_flee_target(world_db["Things"][id])]
1227 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1228 ord_blank = ord(" ")
1229 [set_map_score(mt[1] * maplength + mt[2], 0)
1230 for mt in t["T_MEMTHING"]
1231 if ord_blank != t["T_MEMMAP"][mt[1] * maplength + mt[2]]
1232 if t != world_db["Things"][0] or
1233 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food" and
1234 world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"] > eat_cost)]
1236 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1238 [set_map_score_at_thingpos(id, 65535)
1239 for id in world_db["Things"]
1240 if animate_in_fov(world_db["Things"][id], maplength)
1241 if get_map_score(world_db["Things"][id]["T_POSY"] * maplength
1242 + world_db["Things"][id]["T_POSX"])]
1244 [set_map_score_at_thingpos(id, 65535)
1245 for id in world_db["Things"]
1246 if animate_in_fov(world_db["Things"][id], maplength)]
1248 def rand_target_dir(neighbors, cmp, dirs):
1251 for i in range(len(dirs)):
1252 if cmp == neighbors[i]:
1253 candidates.append(dirs[i])
1255 return candidates[rand.next() % n_candidates] if n_candidates else 0
1257 def get_neighbor_scores(dirs, eye_pos):
1259 if libpr.ready_neighbor_scores(eye_pos):
1260 raise RuntimeError("No score map allocated for " +
1261 "ready_neighbor_scores.()")
1262 for i in range(len(dirs)):
1263 scores.append(libpr.get_neighbor_score(i))
1266 def get_dir_from_neighbors():
1267 dir_to_target = False
1269 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1270 neighbors = get_neighbor_scores(dirs, eye_pos)
1271 minmax_start = 0 if "f" == filter else 65535 - 1
1272 minmax_neighbor = minmax_start
1273 for i in range(len(dirs)):
1274 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1275 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1276 or ("f" != filter and minmax_neighbor > neighbors[i]):
1277 minmax_neighbor = neighbors[i]
1278 if minmax_neighbor != minmax_start:
1279 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1281 distance = get_map_score(eye_pos)
1282 fear_distance = world_db["MAP_LENGTH"]
1283 if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
1284 fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
1286 if not dir_to_target:
1287 if attack_distance >= distance:
1288 dir_to_target = rand_target_dir(neighbors,
1290 elif fear_distance >= distance:
1291 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1293 world_db["ThingActions"][id]["TA_NAME"]
1296 elif dir_to_target and fear_distance < distance:
1298 return dir_to_target
1300 dir_to_target = False
1302 run_i = 9 + 1 if "s" == filter else 1
1303 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1306 mem_depth_c = b'9' if b' ' == mem_depth_c \
1307 else bytes([mem_depth_c[0] - 1])
1308 if libpr.dijkstra_map():
1309 raise RuntimeError("No score map allocated for dijkstra_map().")
1310 dir_to_target = get_dir_from_neighbors()
1311 libpr.free_score_map()
1312 if dir_to_target and str == type(dir_to_target):
1313 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1314 if world_db["ThingActions"][id]["TA_NAME"]
1316 t["T_ARGUMENT"] = ord(dir_to_target)
1317 return dir_to_target
1320 def standing_on_food(t):
1321 """Return True/False whether t is standing on healthy consumable."""
1322 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1323 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1324 if not world_db["Things"][id]["carried"]
1325 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1326 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1327 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1328 ["TT_TOOL"] == "food"
1329 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1330 ["TT_TOOLPOWER"] > eat_cost]:
1335 def get_inventory_slot_to_consume(t):
1336 """Return invent. slot of healthiest consumable(if any healthy),else -1."""
1340 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1341 for id in t["T_CARRIES"]:
1342 type = world_db["Things"][id]["T_TYPE"]
1343 if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1344 and world_db["ThingTypes"][type]["TT_TOOLPOWER"]:
1345 nutvalue = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1346 tmp_cmp = abs(t["T_SATIATION"] + nutvalue - eat_cost)
1347 if (cmp_food < 0 and tmp_cmp < abs(t["T_SATIATION"])) \
1348 or tmp_cmp < cmp_food:
1356 """Determine next command/argment for actor t via AI algorithms."""
1357 # 7DRL add: Don't pick up or search things when inventory is full.
1358 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1359 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1360 if get_dir_to_target(t, "f"):
1362 sel = get_inventory_slot_to_consume(t)
1364 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1365 if world_db["ThingActions"][id]["TA_NAME"]
1367 t["T_ARGUMENT"] = sel
1368 elif standing_on_food(t):
1369 if (len(t["T_CARRIES"]) < # #
1370 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1371 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1372 if world_db["ThingActions"][id]["TA_NAME"]
1375 going_to_known_food_spot = get_dir_to_target(t, "c")
1376 if not going_to_known_food_spot:
1377 aiming_for_walking_food = get_dir_to_target(t, "a")
1378 if not aiming_for_walking_food:
1379 get_dir_to_target(t, "s")
1383 """Run game world and its inhabitants until new player input expected."""
1384 # 7DRL: effort of move action is TA_EFFORT / sqrt(TT_LIFEPOINTS)
1386 whilebreaker = False
1387 while world_db["Things"][0]["T_LIFEPOINTS"]:
1388 proliferable_map = world_db["MAP"][:]
1389 for id in [id for id in world_db["Things"]
1390 if not world_db["Things"][id]["carried"]]:
1391 y = world_db["Things"][id]["T_POSY"]
1392 x = world_db["Things"][id]["T_POSX"]
1393 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1394 for id in [id for id in world_db["Things"]]: # Only what's from start!
1395 if not id in world_db["Things"] or \
1396 world_db["Things"][id]["carried"]: # May have been consumed or
1397 continue # picked up during turn …
1398 Thing = world_db["Things"][id]
1399 if Thing["T_LIFEPOINTS"]:
1400 if not Thing["T_COMMAND"]:
1401 update_map_memory(Thing)
1408 if Thing["T_LIFEPOINTS"]:
1409 Thing["T_PROGRESS"] += 1
1410 taid = [a for a in world_db["ThingActions"]
1411 if a == Thing["T_COMMAND"]][0]
1412 ThingAction = world_db["ThingActions"][taid]
1413 #if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1414 effort = ThingAction["TA_EFFORT"] # #
1415 if ThingAction["TA_NAME"] == "move": # #
1416 type = Thing["T_TYPE"] # #
1417 max_hp = (world_db["ThingTypes"][type] # #
1418 ["TT_LIFEPOINTS"]) # #
1419 effort = int(effort / math.sqrt(max_hp)) # #
1420 if Thing["T_PROGRESS"] == effort: # #
1421 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1422 Thing["T_COMMAND"] = 0
1423 Thing["T_PROGRESS"] = 0
1424 thingproliferation(Thing, proliferable_map)
1427 world_db["TURN"] += 1
1430 def new_Thing(type, pos=(0, 0)):
1431 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1433 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1438 "T_PLAYERDROP": 0, # #
1446 "T_MEMDEPTHMAP": False,
1449 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1450 build_fov_map(thing)
1454 def id_setter(id, category, id_store=False, start_at_1=False):
1455 """Set ID of object of category to manipulate ID unused? Create new one.
1457 The ID is stored as id_store.id (if id_store is set). If the integer of the
1458 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1459 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1460 always returned when no new object is created, otherwise the new object's
1463 min = 0 if start_at_1 else -1
1465 id = integer_test(id, min)
1467 if id in world_db[category]:
1472 if (start_at_1 and 0 == id) \
1473 or ((not start_at_1) and (id < 0)):
1474 id = 0 if start_at_1 else -1
1477 if id not in world_db[category]:
1485 """Send PONG line to server output file."""
1486 strong_write(io_db["file_out"], "PONG\n")
1490 """Abort server process."""
1491 if None == opts.replay:
1492 if world_db["WORLD_ACTIVE"]:
1494 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1495 raise SystemExit("received QUIT command")
1498 def command_thingshere(str_y, str_x):
1499 """Write to out file list of Things known to player at coordinate y, x."""
1500 # 7DRL: terrain, too
1501 if world_db["WORLD_ACTIVE"]:
1502 y = integer_test(str_y, 0, 255)
1503 x = integer_test(str_x, 0, 255)
1504 length = world_db["MAP_LENGTH"]
1505 if None != y and None != x and y < length and x < length:
1506 pos = (y * world_db["MAP_LENGTH"]) + x
1507 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1508 pos = y * world_db["MAP_LENGTH"] + x; # #
1509 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1510 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1511 for id in world_db["Things"]
1512 if not world_db["Things"][id]["carried"]
1513 if world_db["Things"][id]["T_TYPE"] == tid
1514 if y == world_db["Things"][id]["T_POSY"]
1515 if x == world_db["Things"][id]["T_POSX"]]:
1516 type = world_db["Things"][id]["T_TYPE"]
1517 name = world_db["ThingTypes"][type]["TT_NAME"]
1518 strong_write(io_db["file_out"], name + "\n")
1520 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1521 for mt in world_db["Things"][0]["T_MEMTHING"]
1522 if mt[0] == tid if y == mt[1] if x == mt[2]]:
1523 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1524 strong_write(io_db["file_out"], name + "\n")
1525 if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"): # #
1526 name = "(terrain: SEA)" # #
1527 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."): # #
1528 name = "(terrain: EARTH)" # #
1529 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"): # #
1530 name = "(terrain: SOIL)" # #
1531 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"): # #
1532 name = "(terrain: TREE)" # #
1533 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"): # #
1534 name = "(terrain: WALL)" # #
1535 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"): # #
1536 name = "(terrain: ALTAR)" # #
1539 strong_write(io_db["file_out"], name + "\n") # #
1540 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1542 print("Ignoring: Invalid map coordinates.")
1544 print("Ignoring: Command only works on existing worlds.")
1547 def play_commander(action, args=False):
1548 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1550 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1555 if action == "drop":
1556 t = world_db["Things"][0]
1557 if 0 == len(t["T_CARRIES"]):
1558 log("You have nothing to drop in your inventory.")
1561 elif action == "pick_up":
1562 t = world_db["Things"][0]
1563 ids = [id for id in world_db["Things"] if id
1564 if not world_db["Things"][id]["carried"]
1565 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1566 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
1568 log("No object to pick up.")
1570 used_slots = len(t["T_CARRIES"]) # #
1571 if used_slots >= world_db["ThingTypes"][t["T_TYPE"]] \
1573 log("Can't pick up: No storage room to carry anything more.")
1576 elif action == "move":
1577 t = world_db["Things"][0]
1579 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
1580 t["T_POSY"], t["T_POSX"])
1581 if 1 == move_result[0]:
1582 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
1583 if ord("~") == world_db["MAP"][pos]:
1584 log("You can't swim.")
1586 if (ord("X") == world_db["MAP"][pos] # #
1587 or ord("|") == world_db["MAP"][pos]): # #
1588 carries_axe = False # #
1589 for id in t["T_CARRIES"]: # #
1590 type = world_db["Things"][id]["T_TYPE"] # #
1591 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
1592 carries_axe = True # #
1594 if not carries_axe: # #
1595 log("You can't move there.")
1598 log("You can't move there.")
1601 elif action == "use":
1602 t = world_db["Things"][0]
1603 if len(t["T_CARRIES"]):
1604 id = t["T_CARRIES"][t["T_ARGUMENT"]]
1605 type = world_db["Things"][id]["T_TYPE"]
1606 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe":
1607 log("To use this item for chopping, move towards a tree "
1608 + "while carrying it in your inventory.")
1610 elif world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry":
1611 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1612 if (world_db["MAP"][pos] == ord("X") # #
1613 or world_db["MAP"][pos] == ord("|")): # #
1614 log("Can't build when standing on barrier.") # #
1616 for id in [id for id in world_db["Things"]
1617 if not world_db["Things"][id] == t
1618 if not world_db["Things"][id]["carried"]
1619 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1620 if world_db["Things"][id]["T_POSX"] ==t["T_POSX"]]:
1621 log("Can't build when standing objects.") # #
1623 for id in t["T_CARRIES"]: # #
1624 type_tool = world_db["Things"][id]["T_TYPE"] # #
1625 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
1626 == "carpentry"): # #
1629 for id in t["T_CARRIES"]: # #
1630 type_material = world_db["Things"][id]["T_TYPE"] # #
1631 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1635 if wood_id == None: # #
1636 log("You can't use a " # #
1637 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1638 + " without some wood in your inventory.") # #
1640 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1641 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1642 if not world_db["MAP"][pos] == ord("."):
1643 log("Can only make soil out of non-soil earth.")
1645 elif world_db["ThingTypes"][type]["TT_TOOL"] == "wood": # #
1646 log("To use wood, you need a carpentry tool.") # #
1649 log("You have nothing to use in your inventory.")
1653 id = [x for x in world_db["ThingActions"]
1654 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1655 world_db["Things"][0]["T_COMMAND"] = id
1658 def set_command_and_argument_int(str_arg):
1659 val = integer_test(str_arg, 0, 255)
1661 world_db["Things"][0]["T_ARGUMENT"] = val
1664 def set_command_and_argument_movestring(str_arg):
1665 if str_arg in directions_db:
1666 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1669 print("Ignoring: Argument must be valid direction string.")
1671 if action == "move":
1672 return set_command_and_argument_movestring
1674 return set_command_and_argument_int
1679 def command_seedrandomness(seed_string):
1680 """Set rand seed to int(seed_string)."""
1681 val = integer_test(seed_string, 0, 4294967295)
1686 def command_makeworld(seed_string):
1687 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1689 Seed rand with seed. Do more only with a "wait" ThingAction and
1690 world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1691 world_db["Things"] emptied, call make_map() and set
1692 world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1693 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1694 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1695 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1699 # def free_pos(plant=False):
1700 def free_pos(plant=False): # #
1703 err = "Space to put thing on too hard to find. Map too small?"
1705 y = rand.next() % world_db["MAP_LENGTH"]
1706 x = rand.next() % world_db["MAP_LENGTH"]
1707 pos = y * world_db["MAP_LENGTH"] + x;
1709 and "." == chr(world_db["MAP"][pos])) \
1710 or ":" == chr(world_db["MAP"][pos]): # #
1714 raise SystemExit(err)
1715 # Replica of C code, wrongly ignores animatedness of new Thing.
1716 pos_clear = (0 == len([id for id in world_db["Things"]
1717 if world_db["Things"][id]["T_LIFEPOINTS"]
1718 if world_db["Things"][id]["T_POSY"] == y
1719 if world_db["Things"][id]["T_POSX"] == x]))
1724 val = integer_test(seed_string, 0, 4294967295)
1728 player_will_be_generated = False
1729 playertype = world_db["PLAYER_TYPE"]
1730 for ThingType in world_db["ThingTypes"]:
1731 if playertype == ThingType:
1732 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1733 player_will_be_generated = True
1735 if not player_will_be_generated:
1736 print("Ignoring: No player type with start number >0 defined.")
1739 for ThingAction in world_db["ThingActions"]:
1740 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1743 print("Ignoring: No thing action with name 'wait' defined.")
1745 for name in specials: # #
1746 if world_db[name] not in world_db["ThingTypes"]: # #
1747 print("Ignoring: No valid " + name + " set.") # #
1749 world_db["Things"] = {}
1751 world_db["WORLD_ACTIVE"] = 1
1752 world_db["TURN"] = 1
1753 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1754 id = id_setter(-1, "Things")
1755 world_db["Things"][id] = new_Thing(playertype, free_pos())
1756 if not world_db["Things"][0]["fovmap"]:
1757 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1758 world_db["Things"][0]["fovmap"] = empty_fovmap
1759 update_map_memory(world_db["Things"][0])
1760 for type in world_db["ThingTypes"]:
1761 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1762 if type != playertype:
1763 id = id_setter(-1, "Things")
1764 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"] # #
1765 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1766 strong_write(io_db["file_out"], "NEW_WORLD\n")
1770 def command_maplength(maplength_string):
1771 """Redefine map length. Invalidate map, therefore lose all things on it."""
1772 val = integer_test(maplength_string, 1, 256)
1774 world_db["MAP_LENGTH"] = val
1775 world_db["MAP"] = False
1776 set_world_inactive()
1777 world_db["Things"] = {}
1778 libpr.set_maplength(val)
1781 def command_worldactive(worldactive_string):
1782 """Toggle world_db["WORLD_ACTIVE"] if possible.
1784 An active world can always be set inactive. An inactive world can only be
1785 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1786 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1787 Also call log_help().
1789 # 7DRL: altar must be on map, and specials must be set for active world.
1790 val = integer_test(worldactive_string, 0, 1)
1792 if 0 != world_db["WORLD_ACTIVE"]:
1794 set_world_inactive()
1796 print("World already active.")
1797 elif 0 == world_db["WORLD_ACTIVE"]:
1799 for ThingAction in world_db["ThingActions"]:
1800 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1803 player_exists = False
1804 for Thing in world_db["Things"]:
1806 player_exists = True
1808 specials_set = True # #
1809 for name in specials: # #
1810 if world_db[name] not in world_db["ThingTypes"]: # #
1811 specials_set = False # #
1812 altar_found = False # #
1813 if world_db["MAP"]: # #
1814 pos = world_db["MAP"].find(b'_') # #
1816 y = int(pos / world_db["MAP_LENGTH"]) # #
1817 x = pos % world_db["MAP_LENGTH"] # #
1818 world_db["altar"] = (y, x) # #
1819 altar_found = True # #
1820 if wait_exists and player_exists and world_db["MAP"] \
1821 and specials_set: # #
1822 for id in world_db["Things"]:
1823 if world_db["Things"][id]["T_LIFEPOINTS"]:
1824 build_fov_map(world_db["Things"][id])
1826 update_map_memory(world_db["Things"][id], False)
1827 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1828 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1829 world_db["Things"][0]["fovmap"] = empty_fovmap
1830 world_db["WORLD_ACTIVE"] = 1
1833 print("Ignoring: Not all conditions for world activation met.")
1836 def specialtypesetter(name): # #
1837 """Setter world_db[name], deactivating world if set int no ThingType."""
1838 def helper(str_int):
1839 val = integer_test(str_int, 0)
1841 world_db[name] = val
1842 if world_db["WORLD_ACTIVE"] \
1843 and world_db[name] not in world_db["ThingTypes"]:
1844 world_db["WORLD_ACTIVE"] = 0
1845 print(name + " fits no known ThingType, deactivating world.")
1849 def test_for_id_maker(object, category):
1850 """Return decorator testing for object having "id" attribute."""
1853 if hasattr(object, "id"):
1856 print("Ignoring: No " + category +
1857 " defined to manipulate yet.")
1862 def command_tid(id_string):
1863 """Set ID of Thing to manipulate. ID unused? Create new one.
1865 Default new Thing's type to the first available ThingType, others: zero.
1867 id = id_setter(id_string, "Things", command_tid)
1869 if world_db["ThingTypes"] == {}:
1870 print("Ignoring: No ThingType to settle new Thing in.")
1872 type = list(world_db["ThingTypes"].keys())[0]
1873 world_db["Things"][id] = new_Thing(type)
1876 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1880 def command_tcommand(str_int):
1881 """Set T_COMMAND of selected Thing."""
1882 val = integer_test(str_int, 0)
1884 if 0 == val or val in world_db["ThingActions"]:
1885 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1887 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1891 def command_ttype(str_int):
1892 """Set T_TYPE of selected Thing."""
1893 val = integer_test(str_int, 0)
1895 if val in world_db["ThingTypes"]:
1896 world_db["Things"][command_tid.id]["T_TYPE"] = val
1898 print("Ignoring: ThingType ID belongs to no known ThingType.")
1902 def command_tcarries(str_int):
1903 """Append int(str_int) to T_CARRIES of selected Thing.
1905 The ID int(str_int) must not be of the selected Thing, and must belong to a
1906 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1908 val = integer_test(str_int, 0)
1910 if val == command_tid.id:
1911 print("Ignoring: Thing cannot carry itself.")
1912 elif val in world_db["Things"] \
1913 and not world_db["Things"][val]["carried"]:
1914 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1915 world_db["Things"][val]["carried"] = True
1917 print("Ignoring: Thing not available for carrying.")
1918 # Note that the whole carrying structure is different from the C version:
1919 # Carried-ness is marked by a "carried" flag, not by Things containing
1920 # Things internally.
1924 def command_tmemthing(str_t, str_y, str_x):
1925 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1927 The type must fit to an existing ThingType, and the position into the map.
1929 type = integer_test(str_t, 0)
1930 posy = integer_test(str_y, 0, 255)
1931 posx = integer_test(str_x, 0, 255)
1932 if None != type and None != posy and None != posx:
1933 if type not in world_db["ThingTypes"] \
1934 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1935 print("Ignoring: Illegal value for thing type or position.")
1937 memthing = (type, posy, posx)
1938 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1941 def setter_map(maptype):
1942 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1944 If no map of maptype exists yet, initialize it with ' ' bytes first.
1947 def valid_map_line(str_int, mapline):
1948 val = integer_test(str_int, 0, 255)
1950 if val >= world_db["MAP_LENGTH"]:
1951 print("Illegal value for map line number.")
1952 elif len(mapline) != world_db["MAP_LENGTH"]:
1953 print("Map line length is unequal map width.")
1958 def nonThingMap_helper(str_int, mapline):
1959 val = valid_map_line(str_int, mapline)
1961 length = world_db["MAP_LENGTH"]
1962 if not world_db["MAP"]:
1963 map = bytearray(b' ' * (length ** 2))
1965 map = world_db["MAP"]
1966 map[val * length:(val * length) + length] = mapline.encode()
1967 if not world_db["MAP"]:
1968 world_db["MAP"] = map
1971 def ThingMap_helper(str_int, mapline):
1972 val = valid_map_line(str_int, mapline)
1974 length = world_db["MAP_LENGTH"]
1975 if not world_db["Things"][command_tid.id][maptype]:
1976 map = bytearray(b' ' * (length ** 2))
1978 map = world_db["Things"][command_tid.id][maptype]
1979 map[val * length:(val * length) + length] = mapline.encode()
1980 if not world_db["Things"][command_tid.id][maptype]:
1981 world_db["Things"][command_tid.id][maptype] = map
1983 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
1986 def setter_tpos(axis):
1987 """Generate setter for T_POSX or T_POSY of selected Thing.
1989 If world is active, rebuilds animate things' fovmap, player's memory map.
1992 def helper(str_int):
1993 val = integer_test(str_int, 0, 255)
1995 if val < world_db["MAP_LENGTH"]:
1996 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1997 if world_db["WORLD_ACTIVE"] \
1998 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1999 build_fov_map(world_db["Things"][command_tid.id])
2000 if 0 == command_tid.id:
2001 update_map_memory(world_db["Things"][command_tid.id])
2003 print("Ignoring: Position is outside of map.")
2007 def command_ttid(id_string):
2008 """Set ID of ThingType to manipulate. ID unused? Create new one.
2010 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
2013 id = id_setter(id_string, "ThingTypes", command_ttid)
2015 world_db["ThingTypes"][id] = {
2016 "TT_NAME": "(none)",
2019 "TT_PROLIFERATE": 0,
2020 "TT_START_NUMBER": 0,
2021 "TT_STORAGE": 0, # #
2028 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
2032 def command_ttname(name):
2033 """Set TT_NAME of selected ThingType."""
2034 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
2038 def command_tttool(name):
2039 """Set TT_TOOL of selected ThingType."""
2040 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
2044 def command_ttsymbol(char):
2045 """Set TT_SYMBOL of selected ThingType. """
2047 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
2049 print("Ignoring: Argument must be single character.")
2053 def command_ttcorpseid(str_int):
2054 """Set TT_CORPSE_ID of selected ThingType."""
2055 val = integer_test(str_int, 0)
2057 if val in world_db["ThingTypes"]:
2058 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
2060 print("Ignoring: Corpse ID belongs to no known ThignType.")
2063 def command_taid(id_string):
2064 """Set ID of ThingAction to manipulate. ID unused? Create new one.
2066 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
2068 id = id_setter(id_string, "ThingActions", command_taid, True)
2070 world_db["ThingActions"][id] = {
2076 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
2079 @test_ThingAction_id
2080 def command_taname(name):
2081 """Set TA_NAME of selected ThingAction.
2083 The name must match a valid thing action function. If after the name
2084 setting no ThingAction with name "wait" remains, call set_world_inactive().
2086 if name == "wait" or name == "move" or name == "use" or name == "drop" \
2087 or name == "pick_up":
2088 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
2089 if 1 == world_db["WORLD_ACTIVE"]:
2090 wait_defined = False
2091 for id in world_db["ThingActions"]:
2092 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
2095 if not wait_defined:
2096 set_world_inactive()
2098 print("Ignoring: Invalid action name.")
2099 # In contrast to the original,naming won't map a function to a ThingAction.
2103 """Call ai() on player Thing, then turn_over()."""
2104 ai(world_db["Things"][0])
2108 """Commands database.
2110 Map command start tokens to ([0]) number of expected command arguments, ([1])
2111 the command's meta-ness (i.e. is it to be written to the record file, is it to
2112 be ignored in replay mode if read from server input file), and ([2]) a function
2116 "QUIT": (0, True, command_quit),
2117 "PING": (0, True, command_ping),
2118 "THINGS_HERE": (2, True, command_thingshere),
2119 "MAKE_WORLD": (1, False, command_makeworld),
2120 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
2121 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
2122 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
2123 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
2124 "MAP_LENGTH": (1, False, command_maplength),
2125 "WORLD_ACTIVE": (1, False, command_worldactive),
2126 "MAP": (2, False, setter_map("MAP")),
2127 "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)), # #
2128 "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")), # #
2129 "PLANT_0": (1, False, specialtypesetter("PLANT_0")), # #
2130 "PLANT_1": (1, False, specialtypesetter("PLANT_1")), # #
2131 "LUMBER": (1, False, specialtypesetter("LUMBER")), # #
2132 "TOOL_0": (1, False, specialtypesetter("TOOL_0")), # #
2133 "TOOL_1": (1, False, specialtypesetter("TOOL_1")), # #
2134 "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)), # #
2135 "TA_ID": (1, False, command_taid),
2136 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
2137 "TA_NAME": (1, False, command_taname),
2138 "TT_ID": (1, False, command_ttid),
2139 "TT_NAME": (1, False, command_ttname),
2140 "TT_TOOL": (1, False, command_tttool),
2141 "TT_SYMBOL": (1, False, command_ttsymbol),
2142 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
2143 "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2144 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2146 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2148 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2149 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
2150 "T_ID": (1, False, command_tid),
2151 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2152 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2153 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2154 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2155 "T_COMMAND": (1, False, command_tcommand),
2156 "T_TYPE": (1, False, command_ttype),
2157 "T_CARRIES": (1, False, command_tcarries),
2158 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2159 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2160 "T_MEMTHING": (3, False, command_tmemthing),
2161 "T_POSY": (1, False, setter_tpos("Y")),
2162 "T_POSX": (1, False, setter_tpos("X")),
2163 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
2164 "wait": (0, False, play_commander("wait")),
2165 "move": (1, False, play_commander("move")),
2166 "pick_up": (0, False, play_commander("pick_up")),
2167 "drop": (1, False, play_commander("drop", True)),
2168 "use": (1, False, play_commander("use", True)),
2169 "ai": (0, False, command_ai)
2171 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2174 """World state database. With sane default values. (Randomness is in rand.)"""
2182 "FAVOR_STAGE": 0, # #
2196 """Special type settings."""
2197 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0", "TOOL_1"] # #
2199 """Mapping of direction names to internal direction chars."""
2200 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2201 "west": "s", "north-west": "w", "north-east": "e"}
2203 """File IO database."""
2205 "path_save": "save",
2206 "path_record": "record_save",
2207 "path_worldconf": "confserver/world",
2208 "path_server": "server/",
2209 "path_in": "server/in",
2210 "path_out": "server/out",
2211 "path_worldstate": "server/worldstate",
2212 "tmp_suffix": "_tmp",
2213 "kicked_by_rival": False,
2214 "worldstate_updateable": False
2219 libpr = prep_library()
2220 rand = RandomnessIO()
2221 opts = parse_command_line_arguments()
2223 io_db["path_save"] = opts.savefile
2224 io_db["path_record"] = "record_" + opts.savefile
2227 io_db["verbose"] = True
2228 if None != opts.replay:
2232 except SystemExit as exit:
2233 print("ABORTING: " + exit.args[0])
2235 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")