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("The stats window: T=TURN, H=HEALTH, S=SATIATION, G=GOD'S FAVOR.")
692 log("See README file for more details.")
695 def decrement_lifepoints(t):
696 """Decrement t's lifepoints by 1, and if to zero, corpse it.
698 If t is the player avatar, only blank its fovmap, so that the client may
699 still display memory data. On non-player things, erase fovmap and memory.
700 Dying actors drop all their things.
702 # 7DRL: Return TT_LIFEPOINTS if death, else 0.
703 # 7DRL: Re-spawn died-out species.
704 t["T_LIFEPOINTS"] -= 1
705 live_type = t["T_TYPE"] # 7DRL
706 if 0 == t["T_LIFEPOINTS"]:
707 for id in t["T_CARRIES"]:
708 t["T_CARRIES"].remove(id)
709 world_db["Things"][id]["T_POSY"] = t["T_POSY"]
710 world_db["Things"][id]["T_POSX"] = t["T_POSX"]
711 world_db["Things"][id]["carried"] = False
712 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
713 if world_db["Things"][0] == t:
714 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
716 log("See README on how to start over.")
719 t["T_MEMMAP"] = False
720 t["T_MEMDEPTHMAP"] = False
722 n_species = len([id for id in world_db["Things"] # #
723 if world_db["Things"][id]["T_TYPE"] == live_type])
724 if 0 == n_species: # #
725 if world_db["FAVOR_STAGE"] >= 3 and \
726 live_type == world_db["ANIMAL_0"]:
727 world_db["GOD_FAVOR"] += 3000
728 log("Congratulations! The "
729 + world_db["ThingTypes"][live_type]["TT_NAME"]
730 + " species has died out. The Island God is pleased.")
732 id = id_setter(-1, "Things")
733 world_db["Things"][id] = new_Thing(live_type,
736 + world_db["ThingTypes"][live_type]["TT_NAME"]
737 + " species has temporarily died out. "
738 + "One new-born is spawned at the altar.")
739 return world_db["ThingTypes"][live_type]["TT_LIFEPOINTS"] # #
743 def mv_yx_in_dir_legal(dir, y, x):
744 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
745 dir_c = dir.encode("ascii")[0]
746 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
748 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
749 return (test, libpr.result_y(), libpr.result_x())
752 def enter_altar(): # #
753 """What happens when the player enters the altar."""
754 if world_db["FAVOR_STAGE"] > 9000:
755 log("You step on a soul-less slab of stone.")
757 log("YOU ENTER SACRED GROUND.")
758 if world_db["FAVOR_STAGE"] == 0:
759 world_db["FAVOR_STAGE"] = 1
760 log("The Island God speaks to you: \"I don't trust you. You intrude "
761 + "on the island's affairs. I think you're a nuisance at best, "
762 + "and a danger to my children at worst. I will give you a "
763 + "chance to lighten my mood, however: For a while now, I've "
764 + "been trying to spread the plant "
765 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"] + " (\""
766 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_SYMBOL"]
767 + "\"). I have not been very successful so far. Maybe you can "
768 + "make yourself useful there. I will count each further "
769 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
770 + " that grows to your favor.\"")
771 elif world_db["FAVOR_STAGE"] == 1 and world_db["GOD_FAVOR"] >= 100:
772 world_db["FAVOR_STAGE"] = 2
773 log("The Island God speaks to you: \"You could have done worse so "
774 + "far. Maybe you are not the worst to happen to this island "
775 + "since the metal birds threw the great lightning ball. Maybe "
776 + "you can help me spread another plant. It multiplies faster, "
777 + "and it is highly nutritious: "
778 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"] + " (\""
779 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_SYMBOL"]
780 + "\"). It is new. I give you the only example. Be very careful "
781 + "with it! I also give you another tool that may be helpful.\"")
782 id = id_setter(-1, "Things")
783 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
785 id = id_setter(-1, "Things")
786 world_db["Things"][id] = new_Thing(world_db["TOOL_0"],
788 elif world_db["FAVOR_STAGE"] == 2 and \
789 0 == len([id for id in world_db["Things"]
790 if world_db["Things"][id]["T_TYPE"]
791 == world_db["PLANT_1"]]):
792 log("The Island God speaks to you: \"I am greatly disappointed that "
794 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
795 + " this island had. Here is another one. It cost me great work. "
796 + "Be more careful this time when planting it.\"")
797 id = id_setter(-1, "Things")
798 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
800 world_db["GOD_FAVOR"] -= 250
801 elif world_db["FAVOR_STAGE"] == 2 and world_db["GOD_FAVOR"] >= 500:
802 world_db["FAVOR_STAGE"] = 3
803 log("The Island God speaks to you: \"The "
804 + world_db["ThingTypes"][world_db["ANIMAL_0"]]["TT_NAME"]
805 + " has lately become a pest. These creatures do not please me "
806 + "as much as they used to do. Exterminate them all. I will count "
807 + "each kill to your favor. To help you with the hunting, I grant "
808 + "you the empathy and knowledge to read animals.\"")
809 log("You will now see animals' health bars, and activities (\"m\": "
810 + "moving (maybe for an attack), \"u\": eating, \"p\": picking "
811 + "something up; no letter: waiting).")
812 world_db["EMPATHY"] = 1
813 elif world_db["FAVOR_STAGE"] == 3 and world_db["GOD_FAVOR"] >= 5000:
814 world_db["FAVOR_STAGE"] = 4
815 log("The Island God speaks to you: \"You know what animal I find the "
817 + world_db["ThingTypes"][world_db["ANIMAL_1"]]["TT_NAME"]
818 + "! I think what this islands clearly needs more of is "
819 + world_db["ThingTypes"][world_db["ANIMAL_1"]]["TT_NAME"]
820 + "s. Why don't you help? Support thm. Make sure they are well, "
821 + "and they will multiply faster. From now on, I will "
822 + "count each new-born "
823 + world_db["ThingTypes"][world_db["ANIMAL_1"]]["TT_NAME"]
824 + " (not spawned by me due to undo an extinction event) "
825 + "greatly to your favor. To help you with the feeding, here is "
826 + "something to make the ground bear more consumables.")
827 id = id_setter(-1, "Things")
828 world_db["Things"][id] = new_Thing(world_db["TOOL_1"],
830 elif world_db["GOD_FAVOR"] > 20000:
831 log("The Island God speaks to you: \"You have proven yourself worthy"
832 + " of my respect. You were a good citizen to the island, and "
833 + "sometimes a better steward to its inhabitants than me. The "
834 + "island shall miss you when you leave. But you have earned "
835 + "the right to do so. Take this "
836 + world_db["ThingTypes"][world_db["SLIPPERS"]]["TT_NAME"]
837 + " and USE it when you please. It will take you to where you "
838 + "came from. (But do feel free to stay here as long as you "
840 id = id_setter(-1, "Things")
841 world_db["Things"][id] = new_Thing(world_db["SLIPPERS"],
846 """Make t do nothing (but loudly, if player avatar)."""
847 if t == world_db["Things"][0]:
852 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
853 # 7DRL: Player wounding (worse: killing) others will lower God's favor.
854 # 7DRL: Player entering the altar triggers enter_altar().
855 # 7DRL: Player with axe chops down trees.
857 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
858 t["T_POSY"], t["T_POSX"])
859 if 1 == move_result[0]:
860 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
861 hitted = [id for id in world_db["Things"]
862 if world_db["Things"][id] != t
863 if world_db["Things"][id]["T_LIFEPOINTS"]
864 if world_db["Things"][id]["T_POSY"] == move_result[1]
865 if world_db["Things"][id]["T_POSX"] == move_result[2]]
868 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
869 if t == world_db["Things"][0]:
870 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
871 log("You wound " + hitted_name + ".")
872 world_db["GOD_FAVOR"] -= 1 # #
874 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
875 log(hitter_name +" wounds you.")
876 test = decrement_lifepoints(world_db["Things"][hit_id]) # #(test=)
877 if test and world_db["FAVOR_STAGE"] >= 3 and \
878 hitted_type == world_db["ANIMAL_0"]: # #
879 world_db["GOD_FAVOR"] += 125
880 elif test and t == world_db["Things"][0]: # #
881 world_db["GOD_FAVOR"] -= 2 * test # #
883 if (ord("X") == world_db["MAP"][pos] # #
884 or ord("|") == world_db["MAP"][pos]): # #
885 carries_axe = False # #
886 for id in t["T_CARRIES"]: # #
887 type = world_db["Things"][id]["T_TYPE"] # #
888 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
889 carries_axe = True # #
892 axe_name = world_db["ThingTypes"][type]["TT_NAME"] # #
893 if t == world_db["Things"][0]: # #
894 log("With your " + axe_name + ", you chop!\n") # #
895 if ord("X") == world_db["MAP"][pos]: # #
896 world_db["GOD_FAVOR"] = -1 # #
897 chop_power = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
898 case_X = world_db["MAP"][pos] == ord("X") # #
899 if (chop_power > 0 # #
901 0 == int(rand.next() / chop_power)) # #
902 or (not case_X and # #
903 0 == int(rand.next() / (3 * chop_power))))): # #
904 if t == world_db["Things"][0]: # #
905 log("You chop it down.") # #
906 if world_db["MAP"][pos] == ord("X"): # #
907 world_db["GOD_FAVOR"] -= 10 # #
908 world_db["MAP"][pos] = ord(".") # #
909 i = 3 if case_X else 1 # #
910 for i in range(i): # #
911 id = id_setter(-1, "Things") # #
912 world_db["Things"][id] = \
913 new_Thing(world_db["LUMBER"], # #
914 (move_result[1], move_result[2])) # #
917 passable = ("." == chr(world_db["MAP"][pos]) or
918 ":" == chr(world_db["MAP"][pos]) or # #
919 "_" == chr(world_db["MAP"][pos])) # #
920 dir = [dir for dir in directions_db
921 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
923 t["T_POSY"] = move_result[1]
924 t["T_POSX"] = move_result[2]
925 for id in t["T_CARRIES"]:
926 world_db["Things"][id]["T_POSY"] = move_result[1]
927 world_db["Things"][id]["T_POSX"] = move_result[2]
929 if t == world_db["Things"][0]:
930 log("You move " + dir + ".")
931 if (move_result[1] == world_db["altar"][0] and # #
932 move_result[2] == world_db["altar"][1]): # #
934 elif t == world_db["Things"][0]:
935 log("You fail to move " + dir + ".")
938 def actor_pick_up(t):
939 """Make t pick up (topmost?) Thing from ground into inventory.
941 Define topmostness by how low the thing's type ID is.
943 # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
944 # 7DRL: Non-players pick up nothing but food of good value to them.
945 used_slots = len(t["T_CARRIES"]) # #
946 if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
947 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
948 if not world_db["Things"][id]["carried"]
949 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
950 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
953 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"]) # #
955 tid = world_db["Things"][iid]["T_TYPE"]
956 if lowest_tid == -1 or tid < lowest_tid:
957 if (t != world_db["Things"][0] and # #
958 (world_db["ThingTypes"][tid]["TT_TOOL"] != "food" # #
959 or (world_db["ThingTypes"][tid]["TT_TOOLPOWER"] # #
964 world_db["Things"][id]["carried"] = True
965 type = world_db["Things"][id]["T_TYPE"] # #
966 if (t != world_db["Things"][0] # #
967 and world_db["Things"][id]["T_PLAYERDROP"] # #
968 and world_db["ThingTypes"][type]["TT_TOOL"] == "food"): # #
969 score = world_db["ThingTypes"][type]["TT_TOOLPOWER"] / 32 # #
970 world_db["GOD_FAVOR"] += score # #
971 world_db["Things"][id]["T_PLAYERDROP"] = 0 # #
972 t["T_CARRIES"].append(id)
973 if t == world_db["Things"][0]:
974 log("You pick up an object.")
975 elif t == world_db["Things"][0]:
976 log("You try to pick up an object, but there is none.")
977 elif t == world_db["Things"][0]: # #
978 log("Can't pick up object: No storage room to carry more.") # #
982 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
983 # TODO: Handle case where T_ARGUMENT matches nothing.
984 if len(t["T_CARRIES"]):
985 id = t["T_CARRIES"][t["T_ARGUMENT"]]
986 t["T_CARRIES"].remove(id)
987 world_db["Things"][id]["carried"] = False
988 if t == world_db["Things"][0]:
989 log("You drop an object.")
990 world_db["Things"][id]["T_PLAYERDROP"] = 1 # #
991 elif t == world_db["Things"][0]:
992 log("You try to drop an object, but you own none.")
996 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
997 # TODO: Handle case where T_ARGUMENT matches nothing.
998 # 7DLR: Handle SLIPPERS-type Thing use.
999 # 7DRL: Player with fertilizer fertilizes
1000 if len(t["T_CARRIES"]):
1001 id = t["T_CARRIES"][t["T_ARGUMENT"]]
1002 type = world_db["Things"][id]["T_TYPE"]
1003 if type == world_db["SLIPPERS"]: # #
1004 if t == world_db["Things"][0]: # #
1005 log("You use the " + world_db["ThingTypes"][type]["TT_NAME"] # #
1006 + ". It glows in wondrous colors, and emits a sound as " # #
1007 + "if from a dying cat. The Island God laughs.\n") # #
1008 t["T_LIFEPOINTS"] = 1 # #
1009 decrement_lifepoints(t) # #
1010 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "axe" # #
1011 and t == world_db["Things"][0]): # #
1012 log("To use this item for chopping, move towards a tree while "
1013 + "carrying it in your inventory.") # #
1014 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry"): # #
1015 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1016 if (world_db["MAP"][pos] == ord("X") # #
1017 or world_db["MAP"][pos] == ord("|")): # #
1018 log("Can't build when standing on barrier.") # #
1020 for id in [id for id in world_db["Things"]
1021 if not world_db["Things"][id] == t
1022 if not world_db["Things"][id]["carried"]
1023 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1024 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]:
1025 log("Can't build when standing objects.") # #
1027 for id in t["T_CARRIES"]: # #
1028 type_tool = world_db["Things"][id]["T_TYPE"] # #
1029 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
1030 == "carpentry"): # #
1033 for id in t["T_CARRIES"]: # #
1034 type_material = world_db["Things"][id]["T_TYPE"] # #
1035 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1039 if wood_id != None: # #
1040 t["T_CARRIES"].remove(wood_id) # #
1041 del world_db["Things"][wood_id] # #
1042 world_db["MAP"][pos] = ord("|") # #
1043 log("With your " # #
1044 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1045 + " you build a wooden barrier from your " # #
1046 + world_db["ThingTypes"][type_material]["TT_NAME"] # #
1049 log("You can't use a " # #
1050 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1051 + " without some wood in your inventory.") # #
1052 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1053 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1054 if world_db["MAP"][pos] == ord("."):
1055 log("You create soil.")
1056 world_db["MAP"][pos] = ord(":")
1058 log("Can only make soil out of non-soil earth.")
1059 elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
1060 t["T_CARRIES"].remove(id)
1061 del world_db["Things"][id]
1062 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1063 if t == world_db["Things"][0]:
1064 log("You consume this object.")
1065 elif t == world_db["Things"][0]:
1066 log("You try to use this object, but fail.")
1067 elif t == world_db["Things"][0]:
1068 log("You try to use an object, but you own none.")
1071 def thingproliferation(t, prol_map):
1072 """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1074 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1075 marked "." in prol_map. If there are several map cell candidates, one is
1078 # 7DRL: success increments God's mood
1079 # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1080 # 7DRL: Animals only proliferate when >= 0.9 their max HP.
1081 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1083 (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] == 0 or # #
1084 t["T_LIFEPOINTS"] >= 0.9 * # #
1085 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]) \
1087 (1 == prolscore or 1 == (rand.next() % prolscore)):
1089 for dir in [directions_db[key] for key in directions_db]:
1090 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1091 pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1092 if mv_result[0] and \
1093 (ord(":") == prol_map[pos] # #
1094 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1095 and ord(".") == prol_map[pos])):
1096 # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1097 # * world_db["MAP_LENGTH"]
1099 candidates.append((mv_result[1], mv_result[2]))
1101 i = rand.next() % len(candidates)
1102 id = id_setter(-1, "Things")
1103 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1104 world_db["Things"][id] = newT
1105 if (world_db["FAVOR_STAGE"] > 0 # #
1106 and t["T_TYPE"] == world_db["PLANT_0"]): # #
1107 world_db["GOD_FAVOR"] += 5 # #
1108 elif t["T_TYPE"] == world_db["PLANT_1"]: # #
1109 world_db["GOD_FAVOR"] += 25 # #
1110 elif world_db["FAVOR_STAGE"] >= 4 and \
1111 t["T_TYPE"] == world_db["ANIMAL_1"]:
1112 log("The Island God smiles upon a new-born bear baby.")
1113 world_db["GOD_FAVOR"] += 750
1117 """If t's HP < max, increment them if well-nourished, maybe waiting."""
1118 # 7DRL: Successful heals increment God's mood.
1119 if t["T_LIFEPOINTS"] < \
1120 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1121 wait_id = [id for id in world_db["ThingActions"]
1122 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1123 wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
1124 testval = int(abs(t["T_SATIATION"]) / wait_divider)
1125 if (testval <= 1 or 1 == (rand.next() % testval)):
1126 t["T_LIFEPOINTS"] += 1
1127 if t == world_db["Things"][0]:
1131 def hunger_per_turn(type_id):
1132 """The amount of satiation score lost per turn for things of given type."""
1133 return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
1137 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1138 if t["T_SATIATION"] > -32768:
1139 t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"])
1140 if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
1141 if t == world_db["Things"][0]:
1142 if t["T_SATIATION"] < 0:
1143 log("You suffer from hunger.")
1145 log("You suffer from over-eating.")
1146 decrement_lifepoints(t)
1149 def get_dir_to_target(t, filter):
1150 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1152 The path-wise nearest target is chosen, via the shortest available path.
1153 Target must not be t. On succcess, return positive value, else False.
1155 "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
1156 is, and its corpse would be healthy food for t
1157 "f": move away from an enemy – any visible actor whose thing type has more
1158 TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
1159 food – if it is closer than n steps, where n will shrink as t's hunger
1160 grows; if enemy is too close, move towards (attack) the enemy instead;
1161 if no fleeing is possible, nor attacking useful, wait; don't tread on
1162 non-enemies for fleeing
1163 "c": Thing in memorized map is consumable of sufficient nutrition for t
1164 "s": memory map cell with greatest-reachable degree of unexploredness
1167 def zero_score_map_where_char_on_memdepthmap(c):
1168 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1169 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1170 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1171 # set_map_score(i, 0)
1172 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1173 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1174 raise RuntimeError("No score map allocated for "
1175 "zero_score_map_where_char_on_memdepthmap().")
1177 def set_map_score_at_thingpos(id, score):
1178 pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
1179 + world_db["Things"][id]["T_POSX"]
1180 set_map_score(pos, score)
1182 def set_map_score(pos, score):
1183 test = libpr.set_map_score(pos, score)
1185 raise RuntimeError("No score map allocated for set_map_score().")
1187 def get_map_score(pos):
1188 result = libpr.get_map_score(pos)
1190 raise RuntimeError("No score map allocated for get_map_score().")
1193 def animate_in_fov(Thing, maplength): # maplength needed for optimization?
1194 if not Thing["T_LIFEPOINTS"] or Thing["carried"] or Thing == t:
1196 pos = Thing["T_POSY"] * maplength + Thing["T_POSX"]
1197 if 118 == t["fovmap"][pos]: # optimization: 118 = ord("v")
1200 def good_attack_target(v):
1201 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1202 type = world_db["ThingTypes"][v["T_TYPE"]]
1203 type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
1204 if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
1205 and type_corpse["TT_TOOL"] == "food" \
1206 and type_corpse["TT_TOOLPOWER"] > eat_cost:
1210 def good_flee_target(m):
1211 own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
1212 corpse_type = world_db["ThingTypes"][own_corpse_id]
1213 targetness = 0 if corpse_type["TT_TOOL"] != "food" \
1214 else corpse_type["TT_TOOLPOWER"]
1215 type = world_db["ThingTypes"][m["T_TYPE"]]
1216 if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
1217 and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
1222 maplength = world_db["MAP_LENGTH"]
1223 if t["fovmap"] and "a" == filter:
1224 for id in world_db["Things"]:
1225 if animate_in_fov(world_db["Things"][id], maplength):
1226 if good_attack_target(world_db["Things"][id]):
1228 elif t["fovmap"] and "f" == filter:
1229 for id in world_db["Things"]:
1230 if animate_in_fov(world_db["Things"][id], maplength):
1231 if good_flee_target(world_db["Things"][id]):
1233 elif t["T_MEMMAP"] and "c" == filter:
1234 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1235 ord_blank = ord(" ")
1236 for mt in t["T_MEMTHING"]:
1237 if ord_blank != t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1239 (t != world_db["Things"][0] or \
1240 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"
1241 and world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"]
1246 def set_cells_passable_on_memmap_to_65534_on_scoremap():
1247 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1248 # ord_dot = ord(".")
1249 # memmap = t["T_MEMMAP"]
1250 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1251 # if ord_dot == memmap[i]]:
1252 # set_map_score(i, 65534) # i.e. 65535-1
1253 map = c_pointer_to_bytearray(t["T_MEMMAP"])
1254 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1255 raise RuntimeError("No score map allocated for "
1256 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1258 def init_score_map():
1259 test = libpr.init_score_map()
1261 raise RuntimeError("Malloc error in init_score_map().")
1262 set_cells_passable_on_memmap_to_65534_on_scoremap()
1263 maplength = world_db["MAP_LENGTH"]
1265 [set_map_score_at_thingpos(id, 0)
1266 for id in world_db["Things"]
1267 if animate_in_fov(world_db["Things"][id], maplength)
1268 if good_attack_target(world_db["Things"][id])]
1270 [set_map_score_at_thingpos(id, 0)
1271 for id in world_db["Things"]
1272 if animate_in_fov(world_db["Things"][id], maplength)
1273 if good_flee_target(world_db["Things"][id])]
1275 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1276 ord_blank = ord(" ")
1277 [set_map_score(mt[1] * maplength + mt[2], 0)
1278 for mt in t["T_MEMTHING"]
1279 if ord_blank != t["T_MEMMAP"][mt[1] * maplength + mt[2]]
1280 if t != world_db["Things"][0] or
1281 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food" and
1282 world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"] > eat_cost)]
1284 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1286 [set_map_score_at_thingpos(id, 65535)
1287 for id in world_db["Things"]
1288 if animate_in_fov(world_db["Things"][id], maplength)
1289 if get_map_score(world_db["Things"][id]["T_POSY"] * maplength
1290 + world_db["Things"][id]["T_POSX"])]
1292 [set_map_score_at_thingpos(id, 65535)
1293 for id in world_db["Things"]
1294 if animate_in_fov(world_db["Things"][id], maplength)]
1296 def rand_target_dir(neighbors, cmp, dirs):
1299 for i in range(len(dirs)):
1300 if cmp == neighbors[i]:
1301 candidates.append(dirs[i])
1303 return candidates[rand.next() % n_candidates] if n_candidates else 0
1305 def get_neighbor_scores(dirs, eye_pos):
1307 if libpr.ready_neighbor_scores(eye_pos):
1308 raise RuntimeError("No score map allocated for " +
1309 "ready_neighbor_scores.()")
1310 for i in range(len(dirs)):
1311 scores.append(libpr.get_neighbor_score(i))
1314 def get_dir_from_neighbors():
1315 dir_to_target = False
1317 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1318 neighbors = get_neighbor_scores(dirs, eye_pos)
1319 minmax_start = 0 if "f" == filter else 65535 - 1
1320 minmax_neighbor = minmax_start
1321 for i in range(len(dirs)):
1322 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1323 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1324 or ("f" != filter and minmax_neighbor > neighbors[i]):
1325 minmax_neighbor = neighbors[i]
1326 if minmax_neighbor != minmax_start:
1327 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1329 distance = get_map_score(eye_pos)
1330 fear_distance = world_db["MAP_LENGTH"]
1331 if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
1332 fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
1334 if not dir_to_target:
1335 if attack_distance >= distance:
1336 dir_to_target = rand_target_dir(neighbors,
1338 elif fear_distance >= distance:
1339 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1341 world_db["ThingActions"][id]["TA_NAME"]
1344 elif dir_to_target and fear_distance < distance:
1346 return dir_to_target
1348 dir_to_target = False
1350 run_i = 9 + 1 if "s" == filter else 1
1351 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1354 mem_depth_c = b'9' if b' ' == mem_depth_c \
1355 else bytes([mem_depth_c[0] - 1])
1356 if libpr.dijkstra_map():
1357 raise RuntimeError("No score map allocated for dijkstra_map().")
1358 dir_to_target = get_dir_from_neighbors()
1359 libpr.free_score_map()
1360 if dir_to_target and str == type(dir_to_target):
1361 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1362 if world_db["ThingActions"][id]["TA_NAME"]
1364 t["T_ARGUMENT"] = ord(dir_to_target)
1365 return dir_to_target
1368 def standing_on_food(t):
1369 """Return True/False whether t is standing on healthy consumable."""
1370 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1371 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1372 if not world_db["Things"][id]["carried"]
1373 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1374 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1375 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1376 ["TT_TOOL"] == "food"
1377 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1378 ["TT_TOOLPOWER"] > eat_cost]:
1383 def get_inventory_slot_to_consume(t):
1384 """Return invent. slot of healthiest consumable(if any healthy),else -1."""
1388 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1389 for id in t["T_CARRIES"]:
1390 type = world_db["Things"][id]["T_TYPE"]
1391 if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1392 and world_db["ThingTypes"][type]["TT_TOOLPOWER"]:
1393 nutvalue = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1394 tmp_cmp = abs(t["T_SATIATION"] + nutvalue - eat_cost)
1395 if (cmp_food < 0 and tmp_cmp < abs(t["T_SATIATION"])) \
1396 or tmp_cmp < cmp_food:
1404 """Determine next command/argment for actor t via AI algorithms."""
1405 # 7DRL add: Don't pick up or search things when inventory is full.
1406 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1407 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1408 if get_dir_to_target(t, "f"):
1410 sel = get_inventory_slot_to_consume(t)
1412 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1413 if world_db["ThingActions"][id]["TA_NAME"]
1415 t["T_ARGUMENT"] = sel
1416 elif standing_on_food(t):
1417 if (len(t["T_CARRIES"]) < # #
1418 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1419 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1420 if world_db["ThingActions"][id]["TA_NAME"]
1423 going_to_known_food_spot = get_dir_to_target(t, "c")
1424 if not going_to_known_food_spot:
1425 aiming_for_walking_food = get_dir_to_target(t, "a")
1426 if not aiming_for_walking_food:
1427 get_dir_to_target(t, "s")
1431 """Run game world and its inhabitants until new player input expected."""
1432 # 7DRL: effort of move action is TA_EFFORT / sqrt(TT_LIFEPOINTS)
1434 whilebreaker = False
1435 while world_db["Things"][0]["T_LIFEPOINTS"]:
1436 proliferable_map = world_db["MAP"][:]
1437 for id in [id for id in world_db["Things"]
1438 if not world_db["Things"][id]["carried"]]:
1439 y = world_db["Things"][id]["T_POSY"]
1440 x = world_db["Things"][id]["T_POSX"]
1441 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1442 for id in [id for id in world_db["Things"]]: # Only what's from start!
1443 if not id in world_db["Things"] or \
1444 world_db["Things"][id]["carried"]: # May have been consumed or
1445 continue # picked up during turn …
1446 Thing = world_db["Things"][id]
1447 if Thing["T_LIFEPOINTS"]:
1448 if not Thing["T_COMMAND"]:
1449 update_map_memory(Thing)
1456 if Thing["T_LIFEPOINTS"]:
1457 Thing["T_PROGRESS"] += 1
1458 taid = [a for a in world_db["ThingActions"]
1459 if a == Thing["T_COMMAND"]][0]
1460 ThingAction = world_db["ThingActions"][taid]
1461 #if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1462 effort = ThingAction["TA_EFFORT"] # #
1463 if ThingAction["TA_NAME"] == "move": # #
1464 type = Thing["T_TYPE"] # #
1465 max_hp = (world_db["ThingTypes"][type] # #
1466 ["TT_LIFEPOINTS"]) # #
1467 effort = int(effort / math.sqrt(max_hp)) # #
1468 if Thing["T_PROGRESS"] == effort: # #
1469 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1470 Thing["T_COMMAND"] = 0
1471 Thing["T_PROGRESS"] = 0
1472 thingproliferation(Thing, proliferable_map)
1475 world_db["TURN"] += 1
1478 def new_Thing(type, pos=(0, 0)):
1479 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1481 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1486 "T_PLAYERDROP": 0, # #
1494 "T_MEMDEPTHMAP": False,
1497 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1498 build_fov_map(thing)
1502 def id_setter(id, category, id_store=False, start_at_1=False):
1503 """Set ID of object of category to manipulate ID unused? Create new one.
1505 The ID is stored as id_store.id (if id_store is set). If the integer of the
1506 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1507 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1508 always returned when no new object is created, otherwise the new object's
1511 min = 0 if start_at_1 else -1
1513 id = integer_test(id, min)
1515 if id in world_db[category]:
1520 if (start_at_1 and 0 == id) \
1521 or ((not start_at_1) and (id < 0)):
1522 id = 0 if start_at_1 else -1
1525 if id not in world_db[category]:
1533 """Send PONG line to server output file."""
1534 strong_write(io_db["file_out"], "PONG\n")
1538 """Abort server process."""
1539 if None == opts.replay:
1540 if world_db["WORLD_ACTIVE"]:
1542 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1543 raise SystemExit("received QUIT command")
1546 def command_thingshere(str_y, str_x):
1547 """Write to out file list of Things known to player at coordinate y, x."""
1548 # 7DRL: terrain, too
1549 if world_db["WORLD_ACTIVE"]:
1550 y = integer_test(str_y, 0, 255)
1551 x = integer_test(str_x, 0, 255)
1552 length = world_db["MAP_LENGTH"]
1553 if None != y and None != x and y < length and x < length:
1554 pos = (y * world_db["MAP_LENGTH"]) + x
1555 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1556 pos = y * world_db["MAP_LENGTH"] + x; # #
1557 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1558 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1559 for id in world_db["Things"]
1560 if not world_db["Things"][id]["carried"]
1561 if world_db["Things"][id]["T_TYPE"] == tid
1562 if y == world_db["Things"][id]["T_POSY"]
1563 if x == world_db["Things"][id]["T_POSX"]]:
1564 type = world_db["Things"][id]["T_TYPE"]
1565 name = world_db["ThingTypes"][type]["TT_NAME"]
1566 strong_write(io_db["file_out"], name + "\n")
1568 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1569 for mt in world_db["Things"][0]["T_MEMTHING"]
1570 if mt[0] == tid if y == mt[1] if x == mt[2]]:
1571 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1572 strong_write(io_db["file_out"], name + "\n")
1573 if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"): # #
1574 name = "(terrain: SEA)" # #
1575 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."): # #
1576 name = "(terrain: EARTH)" # #
1577 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"): # #
1578 name = "(terrain: SOIL)" # #
1579 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"): # #
1580 name = "(terrain: TREE)" # #
1581 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"): # #
1582 name = "(terrain: WALL)" # #
1583 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"): # #
1584 name = "(terrain: ALTAR)" # #
1587 strong_write(io_db["file_out"], name + "\n") # #
1588 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1590 print("Ignoring: Invalid map coordinates.")
1592 print("Ignoring: Command only works on existing worlds.")
1595 def play_commander(action, args=False):
1596 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1598 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1603 if action == "drop":
1604 t = world_db["Things"][0]
1605 if 0 == len(t["T_CARRIES"]):
1606 log("You have nothing to drop in your inventory.")
1609 elif action == "pick_up":
1610 t = world_db["Things"][0]
1611 ids = [id for id in world_db["Things"] if id
1612 if not world_db["Things"][id]["carried"]
1613 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1614 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
1616 log("No object to pick up.")
1618 used_slots = len(t["T_CARRIES"]) # #
1619 if used_slots >= world_db["ThingTypes"][t["T_TYPE"]] \
1621 log("Can't pick up: No storage room to carry anything more.")
1624 elif action == "move":
1625 t = world_db["Things"][0]
1627 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
1628 t["T_POSY"], t["T_POSX"])
1629 if 1 == move_result[0]:
1630 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
1631 if ord("~") == world_db["MAP"][pos]:
1632 log("You can't swim.")
1634 if (ord("X") == world_db["MAP"][pos] # #
1635 or ord("|") == world_db["MAP"][pos]): # #
1636 carries_axe = False # #
1637 for id in t["T_CARRIES"]: # #
1638 type = world_db["Things"][id]["T_TYPE"] # #
1639 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
1640 carries_axe = True # #
1642 if not carries_axe: # #
1643 log("You can't move there.")
1646 log("You can't move there.")
1649 elif action == "use":
1650 t = world_db["Things"][0]
1651 if len(t["T_CARRIES"]):
1652 id = t["T_CARRIES"][t["T_ARGUMENT"]]
1653 type = world_db["Things"][id]["T_TYPE"]
1654 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe":
1655 log("To use this item for chopping, move towards a tree "
1656 + "while carrying it in your inventory.")
1658 elif world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry":
1659 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1660 if (world_db["MAP"][pos] == ord("X") # #
1661 or world_db["MAP"][pos] == ord("|")): # #
1662 log("Can't build when standing on barrier.") # #
1664 for id in [id for id in world_db["Things"]
1665 if not world_db["Things"][id] == t
1666 if not world_db["Things"][id]["carried"]
1667 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1668 if world_db["Things"][id]["T_POSX"] ==t["T_POSX"]]:
1669 log("Can't build when standing objects.") # #
1671 for id in t["T_CARRIES"]: # #
1672 type_tool = world_db["Things"][id]["T_TYPE"] # #
1673 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
1674 == "carpentry"): # #
1677 for id in t["T_CARRIES"]: # #
1678 type_material = world_db["Things"][id]["T_TYPE"] # #
1679 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1683 if wood_id == None: # #
1684 log("You can't use a " # #
1685 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1686 + " without some wood in your inventory.") # #
1688 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1689 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1690 if not world_db["MAP"][pos] == ord("."):
1691 log("Can only make soil out of non-soil earth.")
1693 elif world_db["ThingTypes"][type]["TT_TOOL"] == "wood": # #
1694 log("To use wood, you need a carpentry tool.") # #
1697 log("You have nothing to use in your inventory.")
1701 id = [x for x in world_db["ThingActions"]
1702 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1703 world_db["Things"][0]["T_COMMAND"] = id
1706 def set_command_and_argument_int(str_arg):
1707 val = integer_test(str_arg, 0, 255)
1709 world_db["Things"][0]["T_ARGUMENT"] = val
1712 def set_command_and_argument_movestring(str_arg):
1713 if str_arg in directions_db:
1714 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1717 print("Ignoring: Argument must be valid direction string.")
1719 if action == "move":
1720 return set_command_and_argument_movestring
1722 return set_command_and_argument_int
1727 def command_seedrandomness(seed_string):
1728 """Set rand seed to int(seed_string)."""
1729 val = integer_test(seed_string, 0, 4294967295)
1734 def command_makeworld(seed_string):
1735 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1737 Seed rand with seed. Do more only with a "wait" ThingAction and
1738 world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1739 world_db["Things"] emptied, call make_map() and set
1740 world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1741 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1742 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1743 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1747 # def free_pos(plant=False):
1748 def free_pos(plant=False): # #
1751 err = "Space to put thing on too hard to find. Map too small?"
1753 y = rand.next() % world_db["MAP_LENGTH"]
1754 x = rand.next() % world_db["MAP_LENGTH"]
1755 pos = y * world_db["MAP_LENGTH"] + x;
1757 and "." == chr(world_db["MAP"][pos])) \
1758 or ":" == chr(world_db["MAP"][pos]): # #
1762 raise SystemExit(err)
1763 # Replica of C code, wrongly ignores animatedness of new Thing.
1764 pos_clear = (0 == len([id for id in world_db["Things"]
1765 if world_db["Things"][id]["T_LIFEPOINTS"]
1766 if world_db["Things"][id]["T_POSY"] == y
1767 if world_db["Things"][id]["T_POSX"] == x]))
1772 val = integer_test(seed_string, 0, 4294967295)
1776 player_will_be_generated = False
1777 playertype = world_db["PLAYER_TYPE"]
1778 for ThingType in world_db["ThingTypes"]:
1779 if playertype == ThingType:
1780 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1781 player_will_be_generated = True
1783 if not player_will_be_generated:
1784 print("Ignoring: No player type with start number >0 defined.")
1787 for ThingAction in world_db["ThingActions"]:
1788 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1791 print("Ignoring: No thing action with name 'wait' defined.")
1793 for name in specials: # #
1794 if world_db[name] not in world_db["ThingTypes"]: # #
1795 print("Ignoring: No valid " + name + " set.") # #
1797 world_db["Things"] = {}
1799 world_db["WORLD_ACTIVE"] = 1
1800 world_db["TURN"] = 1
1801 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1802 id = id_setter(-1, "Things")
1803 world_db["Things"][id] = new_Thing(playertype, free_pos())
1804 if not world_db["Things"][0]["fovmap"]:
1805 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1806 world_db["Things"][0]["fovmap"] = empty_fovmap
1807 update_map_memory(world_db["Things"][0])
1808 for type in world_db["ThingTypes"]:
1809 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1810 if type != playertype:
1811 id = id_setter(-1, "Things")
1812 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"] # #
1813 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1814 strong_write(io_db["file_out"], "NEW_WORLD\n")
1818 def command_maplength(maplength_string):
1819 """Redefine map length. Invalidate map, therefore lose all things on it."""
1820 val = integer_test(maplength_string, 1, 256)
1822 world_db["MAP_LENGTH"] = val
1823 world_db["MAP"] = False
1824 set_world_inactive()
1825 world_db["Things"] = {}
1826 libpr.set_maplength(val)
1829 def command_worldactive(worldactive_string):
1830 """Toggle world_db["WORLD_ACTIVE"] if possible.
1832 An active world can always be set inactive. An inactive world can only be
1833 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1834 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1835 Also call log_help().
1837 # 7DRL: altar must be on map, and specials must be set for active world.
1838 val = integer_test(worldactive_string, 0, 1)
1840 if 0 != world_db["WORLD_ACTIVE"]:
1842 set_world_inactive()
1844 print("World already active.")
1845 elif 0 == world_db["WORLD_ACTIVE"]:
1847 for ThingAction in world_db["ThingActions"]:
1848 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1851 player_exists = False
1852 for Thing in world_db["Things"]:
1854 player_exists = True
1856 specials_set = True # #
1857 for name in specials: # #
1858 if world_db[name] not in world_db["ThingTypes"]: # #
1859 specials_set = False # #
1860 altar_found = False # #
1861 if world_db["MAP"]: # #
1862 pos = world_db["MAP"].find(b'_') # #
1864 y = int(pos / world_db["MAP_LENGTH"]) # #
1865 x = pos % world_db["MAP_LENGTH"] # #
1866 world_db["altar"] = (y, x) # #
1867 altar_found = True # #
1868 if wait_exists and player_exists and world_db["MAP"] \
1869 and specials_set: # #
1870 for id in world_db["Things"]:
1871 if world_db["Things"][id]["T_LIFEPOINTS"]:
1872 build_fov_map(world_db["Things"][id])
1874 update_map_memory(world_db["Things"][id], False)
1875 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1876 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1877 world_db["Things"][0]["fovmap"] = empty_fovmap
1878 world_db["WORLD_ACTIVE"] = 1
1881 print("Ignoring: Not all conditions for world activation met.")
1884 def specialtypesetter(name): # #
1885 """Setter world_db[name], deactivating world if set int no ThingType."""
1886 def helper(str_int):
1887 val = integer_test(str_int, 0)
1889 world_db[name] = val
1890 if world_db["WORLD_ACTIVE"] \
1891 and world_db[name] not in world_db["ThingTypes"]:
1892 world_db["WORLD_ACTIVE"] = 0
1893 print(name + " fits no known ThingType, deactivating world.")
1897 def test_for_id_maker(object, category):
1898 """Return decorator testing for object having "id" attribute."""
1901 if hasattr(object, "id"):
1904 print("Ignoring: No " + category +
1905 " defined to manipulate yet.")
1910 def command_tid(id_string):
1911 """Set ID of Thing to manipulate. ID unused? Create new one.
1913 Default new Thing's type to the first available ThingType, others: zero.
1915 id = id_setter(id_string, "Things", command_tid)
1917 if world_db["ThingTypes"] == {}:
1918 print("Ignoring: No ThingType to settle new Thing in.")
1920 type = list(world_db["ThingTypes"].keys())[0]
1921 world_db["Things"][id] = new_Thing(type)
1924 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1928 def command_tcommand(str_int):
1929 """Set T_COMMAND of selected Thing."""
1930 val = integer_test(str_int, 0)
1932 if 0 == val or val in world_db["ThingActions"]:
1933 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1935 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1939 def command_ttype(str_int):
1940 """Set T_TYPE of selected Thing."""
1941 val = integer_test(str_int, 0)
1943 if val in world_db["ThingTypes"]:
1944 world_db["Things"][command_tid.id]["T_TYPE"] = val
1946 print("Ignoring: ThingType ID belongs to no known ThingType.")
1950 def command_tcarries(str_int):
1951 """Append int(str_int) to T_CARRIES of selected Thing.
1953 The ID int(str_int) must not be of the selected Thing, and must belong to a
1954 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1956 val = integer_test(str_int, 0)
1958 if val == command_tid.id:
1959 print("Ignoring: Thing cannot carry itself.")
1960 elif val in world_db["Things"] \
1961 and not world_db["Things"][val]["carried"]:
1962 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1963 world_db["Things"][val]["carried"] = True
1965 print("Ignoring: Thing not available for carrying.")
1966 # Note that the whole carrying structure is different from the C version:
1967 # Carried-ness is marked by a "carried" flag, not by Things containing
1968 # Things internally.
1972 def command_tmemthing(str_t, str_y, str_x):
1973 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1975 The type must fit to an existing ThingType, and the position into the map.
1977 type = integer_test(str_t, 0)
1978 posy = integer_test(str_y, 0, 255)
1979 posx = integer_test(str_x, 0, 255)
1980 if None != type and None != posy and None != posx:
1981 if type not in world_db["ThingTypes"] \
1982 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1983 print("Ignoring: Illegal value for thing type or position.")
1985 memthing = (type, posy, posx)
1986 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1989 def setter_map(maptype):
1990 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1992 If no map of maptype exists yet, initialize it with ' ' bytes first.
1995 def valid_map_line(str_int, mapline):
1996 val = integer_test(str_int, 0, 255)
1998 if val >= world_db["MAP_LENGTH"]:
1999 print("Illegal value for map line number.")
2000 elif len(mapline) != world_db["MAP_LENGTH"]:
2001 print("Map line length is unequal map width.")
2006 def nonThingMap_helper(str_int, mapline):
2007 val = valid_map_line(str_int, mapline)
2009 length = world_db["MAP_LENGTH"]
2010 if not world_db["MAP"]:
2011 map = bytearray(b' ' * (length ** 2))
2013 map = world_db["MAP"]
2014 map[val * length:(val * length) + length] = mapline.encode()
2015 if not world_db["MAP"]:
2016 world_db["MAP"] = map
2019 def ThingMap_helper(str_int, mapline):
2020 val = valid_map_line(str_int, mapline)
2022 length = world_db["MAP_LENGTH"]
2023 if not world_db["Things"][command_tid.id][maptype]:
2024 map = bytearray(b' ' * (length ** 2))
2026 map = world_db["Things"][command_tid.id][maptype]
2027 map[val * length:(val * length) + length] = mapline.encode()
2028 if not world_db["Things"][command_tid.id][maptype]:
2029 world_db["Things"][command_tid.id][maptype] = map
2031 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
2034 def setter_tpos(axis):
2035 """Generate setter for T_POSX or T_POSY of selected Thing.
2037 If world is active, rebuilds animate things' fovmap, player's memory map.
2040 def helper(str_int):
2041 val = integer_test(str_int, 0, 255)
2043 if val < world_db["MAP_LENGTH"]:
2044 world_db["Things"][command_tid.id]["T_POS" + axis] = val
2045 if world_db["WORLD_ACTIVE"] \
2046 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
2047 build_fov_map(world_db["Things"][command_tid.id])
2048 if 0 == command_tid.id:
2049 update_map_memory(world_db["Things"][command_tid.id])
2051 print("Ignoring: Position is outside of map.")
2055 def command_ttid(id_string):
2056 """Set ID of ThingType to manipulate. ID unused? Create new one.
2058 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
2061 id = id_setter(id_string, "ThingTypes", command_ttid)
2063 world_db["ThingTypes"][id] = {
2064 "TT_NAME": "(none)",
2067 "TT_PROLIFERATE": 0,
2068 "TT_START_NUMBER": 0,
2069 "TT_STORAGE": 0, # #
2076 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
2080 def command_ttname(name):
2081 """Set TT_NAME of selected ThingType."""
2082 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
2086 def command_tttool(name):
2087 """Set TT_TOOL of selected ThingType."""
2088 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
2092 def command_ttsymbol(char):
2093 """Set TT_SYMBOL of selected ThingType. """
2095 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
2097 print("Ignoring: Argument must be single character.")
2101 def command_ttcorpseid(str_int):
2102 """Set TT_CORPSE_ID of selected ThingType."""
2103 val = integer_test(str_int, 0)
2105 if val in world_db["ThingTypes"]:
2106 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
2108 print("Ignoring: Corpse ID belongs to no known ThignType.")
2111 def command_taid(id_string):
2112 """Set ID of ThingAction to manipulate. ID unused? Create new one.
2114 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
2116 id = id_setter(id_string, "ThingActions", command_taid, True)
2118 world_db["ThingActions"][id] = {
2124 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
2127 @test_ThingAction_id
2128 def command_taname(name):
2129 """Set TA_NAME of selected ThingAction.
2131 The name must match a valid thing action function. If after the name
2132 setting no ThingAction with name "wait" remains, call set_world_inactive().
2134 if name == "wait" or name == "move" or name == "use" or name == "drop" \
2135 or name == "pick_up":
2136 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
2137 if 1 == world_db["WORLD_ACTIVE"]:
2138 wait_defined = False
2139 for id in world_db["ThingActions"]:
2140 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
2143 if not wait_defined:
2144 set_world_inactive()
2146 print("Ignoring: Invalid action name.")
2147 # In contrast to the original,naming won't map a function to a ThingAction.
2151 """Call ai() on player Thing, then turn_over()."""
2152 ai(world_db["Things"][0])
2156 """Commands database.
2158 Map command start tokens to ([0]) number of expected command arguments, ([1])
2159 the command's meta-ness (i.e. is it to be written to the record file, is it to
2160 be ignored in replay mode if read from server input file), and ([2]) a function
2164 "QUIT": (0, True, command_quit),
2165 "PING": (0, True, command_ping),
2166 "THINGS_HERE": (2, True, command_thingshere),
2167 "MAKE_WORLD": (1, False, command_makeworld),
2168 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
2169 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
2170 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
2171 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
2172 "MAP_LENGTH": (1, False, command_maplength),
2173 "WORLD_ACTIVE": (1, False, command_worldactive),
2174 "MAP": (2, False, setter_map("MAP")),
2175 "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)), # #
2176 "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")), # #
2177 "PLANT_0": (1, False, specialtypesetter("PLANT_0")), # #
2178 "PLANT_1": (1, False, specialtypesetter("PLANT_1")), # #
2179 "LUMBER": (1, False, specialtypesetter("LUMBER")), # #
2180 "TOOL_0": (1, False, specialtypesetter("TOOL_0")), # #
2181 "TOOL_1": (1, False, specialtypesetter("TOOL_1")), # #
2182 "ANIMAL_0": (1, False, specialtypesetter("ANIMAL_0")), # #
2183 "ANIMAL_1": (1, False, specialtypesetter("ANIMAL_1")), # #
2184 "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)), # #
2185 "TA_ID": (1, False, command_taid),
2186 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
2187 "TA_NAME": (1, False, command_taname),
2188 "TT_ID": (1, False, command_ttid),
2189 "TT_NAME": (1, False, command_ttname),
2190 "TT_TOOL": (1, False, command_tttool),
2191 "TT_SYMBOL": (1, False, command_ttsymbol),
2192 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
2193 "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2194 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2196 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2198 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2199 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
2200 "T_ID": (1, False, command_tid),
2201 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2202 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2203 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2204 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2205 "T_COMMAND": (1, False, command_tcommand),
2206 "T_TYPE": (1, False, command_ttype),
2207 "T_CARRIES": (1, False, command_tcarries),
2208 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2209 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2210 "T_MEMTHING": (3, False, command_tmemthing),
2211 "T_POSY": (1, False, setter_tpos("Y")),
2212 "T_POSX": (1, False, setter_tpos("X")),
2213 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
2214 "wait": (0, False, play_commander("wait")),
2215 "move": (1, False, play_commander("move")),
2216 "pick_up": (0, False, play_commander("pick_up")),
2217 "drop": (1, False, play_commander("drop", True)),
2218 "use": (1, False, play_commander("use", True)),
2219 "ai": (0, False, command_ai)
2221 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2224 """World state database. With sane default values. (Randomness is in rand.)"""
2232 "FAVOR_STAGE": 0, # #
2248 """Special type settings."""
2249 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0", "TOOL_1",
2250 "ANIMAL_0", "ANIMAL_1"]
2252 """Mapping of direction names to internal direction chars."""
2253 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2254 "west": "s", "north-west": "w", "north-east": "e"}
2256 """File IO database."""
2258 "path_save": "save",
2259 "path_record": "record_save",
2260 "path_worldconf": "confserver/world",
2261 "path_server": "server/",
2262 "path_in": "server/in",
2263 "path_out": "server/out",
2264 "path_worldstate": "server/worldstate",
2265 "tmp_suffix": "_tmp",
2266 "kicked_by_rival": False,
2267 "worldstate_updateable": False
2272 libpr = prep_library()
2273 rand = RandomnessIO()
2274 opts = parse_command_line_arguments()
2276 io_db["path_save"] = opts.savefile
2277 io_db["path_record"] = "record_" + opts.savefile
2280 io_db["verbose"] = True
2281 if None != opts.replay:
2285 except SystemExit as exit:
2286 print("ABORTING: " + exit.args[0])
2288 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")