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 t["T_LIFEPOINTS"] -= 1
703 if 0 == t["T_LIFEPOINTS"]:
704 for id in t["T_CARRIES"]:
705 t["T_CARRIES"].remove(id)
706 world_db["Things"][id]["T_POSY"] = t["T_POSY"]
707 world_db["Things"][id]["T_POSX"] = t["T_POSX"]
708 world_db["Things"][id]["carried"] = False
709 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
710 if world_db["Things"][0] == t:
711 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
713 log("See README on how to start over.")
716 t["T_MEMMAP"] = False
717 t["T_MEMDEPTHMAP"] = False
719 return world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
723 def mv_yx_in_dir_legal(dir, y, x):
724 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
725 dir_c = dir.encode("ascii")[0]
726 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
728 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
729 return (test, libpr.result_y(), libpr.result_x())
732 def enter_altar(): # #
733 """What happens when the player enters the altar."""
734 if world_db["FAVOR_STAGE"] > 9000:
735 log("You step on a soul-less slab of stone.")
737 log("YOU ENTER SACRED GROUND.")
738 if world_db["FAVOR_STAGE"] == 0:
739 world_db["FAVOR_STAGE"] = 1
740 log("The Island God speaks to you: \"I don't trust you. You intrude "
741 + "on the island's affairs. I think you're a nuisance at best, "
742 + "and a danger to my children at worst. I will give you a "
743 + "chance to lighten my mood, however: For a while now, I've "
744 + "been trying to spread the plant "
745 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"] + " (\""
746 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_SYMBOL"]
747 + "\"). I have not been very successful so far. Maybe you can "
748 + "make yourself useful there. I will count each further "
749 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
750 + " that grows to your favor.\"")
751 elif world_db["FAVOR_STAGE"] == 1 and world_db["GOD_FAVOR"] >= 100:
752 world_db["FAVOR_STAGE"] = 2
753 log("The Island God speaks to you: \"You could have done worse so "
754 + "far. Maybe you are not the worst to happen to this island "
755 + "since the metal birds threw the great lightning ball. Maybe "
756 + "you can help me spread another plant. It multiplies faster, "
757 + "and it is highly nutritious: "
758 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"] + " (\""
759 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_SYMBOL"]
760 + "\"). It is new. I give you the only example. Be very careful "
761 + "with it! I also give you another tool that may be helpful.\"")
762 id = id_setter(-1, "Things")
763 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
765 id = id_setter(-1, "Things")
766 world_db["Things"][id] = new_Thing(world_db["TOOL_0"],
768 elif world_db["FAVOR_STAGE"] == 2 and \
769 0 == len([id for id in world_db["Things"]
770 if world_db["Things"][id]["T_TYPE"]
771 == world_db["PLANT_1"]]):
772 log("The Island God speaks to you: \"I am greatly disappointed that "
774 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
775 + " this island had. Here is another one. It cost me great work. "
776 + "Be more careful this time.\"")
777 id = id_setter(-1, "Things")
778 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
780 world_db["GOD_FAVOR"] -= 250
781 elif world_db["GOD_FAVOR"] > 9000:
782 world_db["FAVOR_STAGE"] = 9001
783 log("The Island God speaks to you: \"You have proven yourself worthy"
784 + " of my respect. You were a good citizen to the island, and "
785 + "sometimes a better steward to its inhabitants than me. The "
786 + "island shall miss you when you leave. But you have earned "
787 + "the right to do so. Take this "
788 + world_db["ThingTypes"][world_db["SLIPPERS"]]["TT_NAME"]
789 + " and USE it when you please. It will take you to where you "
790 + "came from. (But do feel free to stay here as long as you "
792 id = id_setter(-1, "Things")
793 world_db["Things"][id] = new_Thing(world_db["SLIPPERS"],
798 """Make t do nothing (but loudly, if player avatar)."""
799 if t == world_db["Things"][0]:
804 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
805 # 7DRL: Player wounding (worse: killing) others will lower God's favor.
806 # 7DRL: Player entering the altar triggers enter_altar().
807 # 7DRL: Player with axe chops down trees.
809 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
810 t["T_POSY"], t["T_POSX"])
811 if 1 == move_result[0]:
812 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
813 hitted = [id for id in world_db["Things"]
814 if world_db["Things"][id] != t
815 if world_db["Things"][id]["T_LIFEPOINTS"]
816 if world_db["Things"][id]["T_POSY"] == move_result[1]
817 if world_db["Things"][id]["T_POSX"] == move_result[2]]
820 if t == world_db["Things"][0]:
821 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
822 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
823 log("You wound " + hitted_name + ".")
824 world_db["GOD_FAVOR"] -= -1 # #
826 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
827 log(hitter_name +" wounds you.")
828 test = decrement_lifepoints(world_db["Things"][hit_id]) # #(test=)
829 if test and t == world_db["Things"][0]: # #
830 world_db["GOD_FAVOR"] -test # #
832 if (ord("X") == world_db["MAP"][pos] # #
833 or ord("|") == world_db["MAP"][pos]): # #
834 carries_axe = False # #
835 for id in t["T_CARRIES"]: # #
836 type = world_db["Things"][id]["T_TYPE"] # #
837 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
838 carries_axe = True # #
841 axe_name = world_db["ThingTypes"][type]["TT_NAME"] # #
842 if t == world_db["Things"][0]: # #
843 log("With your " + axe_name + ", you chop!\n") # #
844 if ord("X") == world_db["MAP"][pos]: # #
845 world_db["GOD_FAVOR"] = -1 # #
846 chop_power = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
847 case_X = world_db["MAP"][pos] == ord("X") # #
848 if (chop_power > 0 # #
850 0 == int(rand.next() / chop_power)) # #
851 or (not case_X and # #
852 0 == int(rand.next() / (3 * chop_power))))): # #
853 if t == world_db["Things"][0]: # #
854 log("You chop it down.") # #
855 if world_db["MAP"][pos] == ord("X"): # #
856 world_db["GOD_FAVOR"] = -10 # #
857 world_db["MAP"][pos] = ord(".") # #
858 i = 3 if case_X else 1 # #
859 for i in range(i): # #
860 id = id_setter(-1, "Things") # #
861 world_db["Things"][id] = \
862 new_Thing(world_db["LUMBER"], # #
863 (move_result[1], move_result[2])) # #
866 passable = ("." == chr(world_db["MAP"][pos]) or
867 ":" == chr(world_db["MAP"][pos]) or # #
868 "_" == chr(world_db["MAP"][pos])) # #
869 dir = [dir for dir in directions_db
870 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
872 t["T_POSY"] = move_result[1]
873 t["T_POSX"] = move_result[2]
874 for id in t["T_CARRIES"]:
875 world_db["Things"][id]["T_POSY"] = move_result[1]
876 world_db["Things"][id]["T_POSX"] = move_result[2]
878 if t == world_db["Things"][0]:
879 log("You move " + dir + ".")
880 if (move_result[1] == world_db["altar"][0] and # #
881 move_result[2] == world_db["altar"][1]): # #
883 elif t == world_db["Things"][0]:
884 log("You fail to move " + dir + ".")
887 def actor_pick_up(t):
888 """Make t pick up (topmost?) Thing from ground into inventory.
890 Define topmostness by how low the thing's type ID is.
892 # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
893 # 7DRL: Non-players pick up nothing but food of good value to them.
894 used_slots = len(t["T_CARRIES"]) # #
895 if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
896 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
897 if not world_db["Things"][id]["carried"]
898 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
899 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
902 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"]) # #
904 tid = world_db["Things"][iid]["T_TYPE"]
905 if lowest_tid == -1 or tid < lowest_tid:
906 if (t != world_db["Things"][0] and # #
907 (world_db["ThingTypes"][tid]["TT_TOOL"] != "food" # #
908 or (world_db["ThingTypes"][tid]["TT_TOOLPOWER"] # #
913 world_db["Things"][id]["carried"] = True
914 type = world_db["Things"][id]["T_TYPE"] # #
915 if (t != world_db["Things"][0] # #
916 and world_db["Things"][id]["T_PLAYERDROP"] # #
917 and world_db["ThingTypes"][type]["TT_TOOL"] == "food"): # #
918 score = world_db["ThingTypes"][type]["TT_TOOLPOWER"] / 32 # #
919 world_db["GOD_FAVOR"] += score # #
920 world_db["Things"][id]["T_PLAYERDROP"] = 0 # #
921 t["T_CARRIES"].append(id)
922 if t == world_db["Things"][0]:
923 log("You pick up an object.")
924 elif t == world_db["Things"][0]:
925 log("You try to pick up an object, but there is none.")
926 elif t == world_db["Things"][0]: # #
927 log("Can't pick up object: No storage room to carry more.") # #
931 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
932 # TODO: Handle case where T_ARGUMENT matches nothing.
933 if len(t["T_CARRIES"]):
934 id = t["T_CARRIES"][t["T_ARGUMENT"]]
935 t["T_CARRIES"].remove(id)
936 world_db["Things"][id]["carried"] = False
937 if t == world_db["Things"][0]:
938 log("You drop an object.")
939 world_db["Things"][id]["T_PLAYERDROP"] = 1 # #
940 elif t == world_db["Things"][0]:
941 log("You try to drop an object, but you own none.")
945 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
946 # TODO: Handle case where T_ARGUMENT matches nothing.
947 # 7DLR: Handle SLIPPERS-type Thing use.
948 # 7DRL: Player with fertilizer fertilizes
949 if len(t["T_CARRIES"]):
950 id = t["T_CARRIES"][t["T_ARGUMENT"]]
951 type = world_db["Things"][id]["T_TYPE"]
952 if type == world_db["SLIPPERS"]: # #
953 if t == world_db["Things"][0]: # #
954 log("You use the " + world_db["ThingTypes"][type]["TT_NAME"] # #
955 + ". It glows in wondrous colors, and emits a sound as " # #
956 + "if from a dying cat. The Island God laughs.\n") # #
957 t["T_LIFEPOINTS"] = 1 # #
958 decrement_lifepoints(t) # #
959 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "axe" # #
960 and t == world_db["Things"][0]): # #
961 log("To use this item for chopping, move towards a tree while "
962 + "carrying it in your inventory.") # #
963 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry"): # #
964 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
965 if (world_db["MAP"][pos] == ord("X") # #
966 or world_db["MAP"][pos] == ord("|")): # #
967 log("Can't build when standing on barrier.") # #
969 for id in [id for id in world_db["Things"]
970 if not world_db["Things"][id] == t
971 if not world_db["Things"][id]["carried"]
972 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
973 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]:
974 log("Can't build when standing objects.") # #
976 for id in t["T_CARRIES"]: # #
977 type_tool = world_db["Things"][id]["T_TYPE"] # #
978 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
982 for id in t["T_CARRIES"]: # #
983 type_material = world_db["Things"][id]["T_TYPE"] # #
984 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
988 if wood_id != None: # #
989 t["T_CARRIES"].remove(wood_id) # #
990 del world_db["Things"][wood_id] # #
991 world_db["MAP"][pos] = ord("|") # #
993 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
994 + " you build a wooden barrier from your " # #
995 + world_db["ThingTypes"][type_material]["TT_NAME"] # #
998 log("You can't use a " # #
999 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1000 + " without some wood in your inventory.") # #
1001 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1002 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1003 if world_db["MAP"][pos] == ord("."):
1004 log("You create soil.")
1005 world_db["MAP"][pos] = ord(":")
1007 log("Can only make soil out of non-soil earth.")
1008 elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
1009 t["T_CARRIES"].remove(id)
1010 del world_db["Things"][id]
1011 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1012 if t == world_db["Things"][0]:
1013 log("You consume this object.")
1014 elif t == world_db["Things"][0]:
1015 log("You try to use this object, but fail.")
1016 elif t == world_db["Things"][0]:
1017 log("You try to use an object, but you own none.")
1020 def thingproliferation(t, prol_map):
1021 """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1023 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1024 marked "." in prol_map. If there are several map cell candidates, one is
1027 # 7DRL: success increments God's mood
1028 # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1029 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1030 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
1032 for dir in [directions_db[key] for key in directions_db]:
1033 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1034 pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1035 if mv_result[0] and \
1036 (ord(":") == prol_map[pos] # #
1037 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1038 and ord(".") == prol_map[pos])):
1039 # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1040 # * world_db["MAP_LENGTH"]
1042 candidates.append((mv_result[1], mv_result[2]))
1044 i = rand.next() % len(candidates)
1045 id = id_setter(-1, "Things")
1046 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1047 world_db["Things"][id] = newT
1048 if (world_db["FAVOR_STAGE"] > 0 # #
1049 and t["T_TYPE"] == world_db["PLANT_0"]): # #
1050 world_db["GOD_FAVOR"] += 5 # #
1051 elif t["T_TYPE"] == world_db["PLANT_1"]: # #
1052 world_db["GOD_FAVOR"] += 25 # #
1056 """If t's HP < max, increment them if well-nourished, maybe waiting."""
1057 # 7DRL: Successful heals increment God's mood.
1058 if t["T_LIFEPOINTS"] < \
1059 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1060 wait_id = [id for id in world_db["ThingActions"]
1061 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1062 wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
1063 testval = int(abs(t["T_SATIATION"]) / wait_divider)
1064 if (testval <= 1 or 1 == (rand.next() % testval)):
1065 t["T_LIFEPOINTS"] += 1
1066 if t == world_db["Things"][0]:
1070 def hunger_per_turn(type_id):
1071 """The amount of satiation score lost per turn for things of given type."""
1072 return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
1076 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1077 if t["T_SATIATION"] > -32768:
1078 #max_hp = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]
1079 t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"]) # int(math.sqrt(max_hp))
1080 if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
1081 if t == world_db["Things"][0]:
1082 if t["T_SATIATION"] < 0:
1083 log("You suffer from hunger.")
1085 log("You suffer from over-eating.")
1086 decrement_lifepoints(t)
1089 def get_dir_to_target(t, filter):
1090 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1092 The path-wise nearest target is chosen, via the shortest available path.
1093 Target must not be t. On succcess, return positive value, else False.
1095 "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
1096 is, and its corpse would be healthy food for t
1097 "f": move away from an enemy – any visible actor whose thing type has more
1098 TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
1099 food – if it is closer than n steps, where n will shrink as t's hunger
1100 grows; if enemy is too close, move towards (attack) the enemy instead;
1101 if no fleeing is possible, nor attacking useful, wait; don't tread on
1102 non-enemies for fleeing
1103 "c": Thing in memorized map is consumable of sufficient nutrition for t
1104 "s": memory map cell with greatest-reachable degree of unexploredness
1107 def zero_score_map_where_char_on_memdepthmap(c):
1108 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1109 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1110 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1111 # set_map_score(i, 0)
1112 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1113 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1114 raise RuntimeError("No score map allocated for "
1115 "zero_score_map_where_char_on_memdepthmap().")
1117 def set_map_score_at_thingpos(id, score):
1118 pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
1119 + world_db["Things"][id]["T_POSX"]
1120 set_map_score(pos, score)
1122 def set_map_score(pos, score):
1123 test = libpr.set_map_score(pos, score)
1125 raise RuntimeError("No score map allocated for set_map_score().")
1127 def get_map_score(pos):
1128 result = libpr.get_map_score(pos)
1130 raise RuntimeError("No score map allocated for get_map_score().")
1133 def animate_in_fov(Thing, maplength): # maplength needed for optimization?
1134 if not Thing["T_LIFEPOINTS"] or Thing["carried"] or Thing == t:
1136 pos = Thing["T_POSY"] * maplength + Thing["T_POSX"]
1137 if 118 == t["fovmap"][pos]: # optimization: 118 = ord("v")
1140 def good_attack_target(v):
1141 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1142 type = world_db["ThingTypes"][v["T_TYPE"]]
1143 type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
1144 if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
1145 and type_corpse["TT_TOOL"] == "food" \
1146 and type_corpse["TT_TOOLPOWER"] > eat_cost:
1150 def good_flee_target(m):
1151 own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
1152 corpse_type = world_db["ThingTypes"][own_corpse_id]
1153 targetness = 0 if corpse_type["TT_TOOL"] != "food" \
1154 else corpse_type["TT_TOOLPOWER"]
1155 type = world_db["ThingTypes"][m["T_TYPE"]]
1156 if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
1157 and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
1162 maplength = world_db["MAP_LENGTH"]
1163 if t["fovmap"] and "a" == filter:
1164 for id in world_db["Things"]:
1165 if animate_in_fov(world_db["Things"][id], maplength):
1166 if good_attack_target(world_db["Things"][id]):
1168 elif t["fovmap"] and "f" == filter:
1169 for id in world_db["Things"]:
1170 if animate_in_fov(world_db["Things"][id], maplength):
1171 if good_flee_target(world_db["Things"][id]):
1173 elif t["T_MEMMAP"] and "c" == filter:
1174 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1175 ord_blank = ord(" ")
1176 for mt in t["T_MEMTHING"]:
1177 if ord_blank != t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1179 (t != world_db["Things"][0] or \
1180 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"
1181 and world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"]
1186 def set_cells_passable_on_memmap_to_65534_on_scoremap():
1187 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1188 # ord_dot = ord(".")
1189 # memmap = t["T_MEMMAP"]
1190 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1191 # if ord_dot == memmap[i]]:
1192 # set_map_score(i, 65534) # i.e. 65535-1
1193 map = c_pointer_to_bytearray(t["T_MEMMAP"])
1194 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1195 raise RuntimeError("No score map allocated for "
1196 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1198 def init_score_map():
1199 test = libpr.init_score_map()
1201 raise RuntimeError("Malloc error in init_score_map().")
1202 set_cells_passable_on_memmap_to_65534_on_scoremap()
1203 maplength = world_db["MAP_LENGTH"]
1205 [set_map_score_at_thingpos(id, 0)
1206 for id in world_db["Things"]
1207 if animate_in_fov(world_db["Things"][id], maplength)
1208 if good_attack_target(world_db["Things"][id])]
1210 [set_map_score_at_thingpos(id, 0)
1211 for id in world_db["Things"]
1212 if animate_in_fov(world_db["Things"][id], maplength)
1213 if good_flee_target(world_db["Things"][id])]
1215 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1216 ord_blank = ord(" ")
1217 [set_map_score(mt[1] * maplength + mt[2], 0)
1218 for mt in t["T_MEMTHING"]
1219 if ord_blank != t["T_MEMMAP"][mt[1] * maplength + mt[2]]
1220 if t != world_db["Things"][0] or
1221 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food" and
1222 world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"] > eat_cost)]
1224 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1226 [set_map_score_at_thingpos(id, 65535)
1227 for id in world_db["Things"]
1228 if animate_in_fov(world_db["Things"][id], maplength)
1229 if get_map_score(world_db["Things"][id]["T_POSY"] * maplength
1230 + world_db["Things"][id]["T_POSX"])]
1232 [set_map_score_at_thingpos(id, 65535)
1233 for id in world_db["Things"]
1234 if animate_in_fov(world_db["Things"][id], maplength)]
1236 def rand_target_dir(neighbors, cmp, dirs):
1239 for i in range(len(dirs)):
1240 if cmp == neighbors[i]:
1241 candidates.append(dirs[i])
1243 return candidates[rand.next() % n_candidates] if n_candidates else 0
1245 def get_neighbor_scores(dirs, eye_pos):
1247 if libpr.ready_neighbor_scores(eye_pos):
1248 raise RuntimeError("No score map allocated for " +
1249 "ready_neighbor_scores.()")
1250 for i in range(len(dirs)):
1251 scores.append(libpr.get_neighbor_score(i))
1254 def get_dir_from_neighbors():
1255 dir_to_target = False
1257 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1258 neighbors = get_neighbor_scores(dirs, eye_pos)
1259 minmax_start = 0 if "f" == filter else 65535 - 1
1260 minmax_neighbor = minmax_start
1261 for i in range(len(dirs)):
1262 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1263 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1264 or ("f" != filter and minmax_neighbor > neighbors[i]):
1265 minmax_neighbor = neighbors[i]
1266 if minmax_neighbor != minmax_start:
1267 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1269 distance = get_map_score(eye_pos)
1270 fear_distance = world_db["MAP_LENGTH"]
1271 if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
1272 fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
1274 if not dir_to_target:
1275 if attack_distance >= distance:
1276 dir_to_target = rand_target_dir(neighbors,
1278 elif fear_distance >= distance:
1279 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1281 world_db["ThingActions"][id]["TA_NAME"]
1284 elif dir_to_target and fear_distance < distance:
1286 return dir_to_target
1288 dir_to_target = False
1290 run_i = 9 + 1 if "s" == filter else 1
1291 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1294 mem_depth_c = b'9' if b' ' == mem_depth_c \
1295 else bytes([mem_depth_c[0] - 1])
1296 if libpr.dijkstra_map():
1297 raise RuntimeError("No score map allocated for dijkstra_map().")
1298 dir_to_target = get_dir_from_neighbors()
1299 libpr.free_score_map()
1300 if dir_to_target and str == type(dir_to_target):
1301 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1302 if world_db["ThingActions"][id]["TA_NAME"]
1304 t["T_ARGUMENT"] = ord(dir_to_target)
1305 return dir_to_target
1308 def standing_on_food(t):
1309 """Return True/False whether t is standing on healthy consumable."""
1310 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1311 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1312 if not world_db["Things"][id]["carried"]
1313 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1314 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1315 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1316 ["TT_TOOL"] == "food"
1317 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1318 ["TT_TOOLPOWER"] > eat_cost]:
1323 def get_inventory_slot_to_consume(t):
1324 """Return invent. slot of healthiest consumable(if any healthy),else -1."""
1328 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1329 for id in t["T_CARRIES"]:
1330 type = world_db["Things"][id]["T_TYPE"]
1331 if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1332 and world_db["ThingTypes"][type]["TT_TOOLPOWER"]:
1333 nutvalue = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1334 tmp_cmp = abs(t["T_SATIATION"] + nutvalue - eat_cost)
1335 if (cmp_food < 0 and tmp_cmp < abs(t["T_SATIATION"])) \
1336 or tmp_cmp < cmp_food:
1344 """Determine next command/argment for actor t via AI algorithms."""
1345 # 7DRL add: Don't pick up or search things when inventory is full.
1346 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1347 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1348 if get_dir_to_target(t, "f"):
1350 sel = get_inventory_slot_to_consume(t)
1352 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1353 if world_db["ThingActions"][id]["TA_NAME"]
1355 t["T_ARGUMENT"] = sel
1356 elif standing_on_food(t):
1357 if (len(t["T_CARRIES"]) < # #
1358 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1359 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1360 if world_db["ThingActions"][id]["TA_NAME"]
1363 going_to_known_food_spot = get_dir_to_target(t, "c")
1364 if not going_to_known_food_spot:
1365 aiming_for_walking_food = get_dir_to_target(t, "a")
1366 if not aiming_for_walking_food:
1367 get_dir_to_target(t, "s")
1371 """Run game world and its inhabitants until new player input expected."""
1372 # 7DRL: effort of move action is TA_EFFORT / sqrt(TT_LIFEPOINTS)
1374 whilebreaker = False
1375 while world_db["Things"][0]["T_LIFEPOINTS"]:
1376 proliferable_map = world_db["MAP"][:]
1377 for id in [id for id in world_db["Things"]
1378 if not world_db["Things"][id]["carried"]]:
1379 y = world_db["Things"][id]["T_POSY"]
1380 x = world_db["Things"][id]["T_POSX"]
1381 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1382 for id in [id for id in world_db["Things"]]: # Only what's from start!
1383 if not id in world_db["Things"] or \
1384 world_db["Things"][id]["carried"]: # May have been consumed or
1385 continue # picked up during turn …
1386 Thing = world_db["Things"][id]
1387 if Thing["T_LIFEPOINTS"]:
1388 if not Thing["T_COMMAND"]:
1389 update_map_memory(Thing)
1396 if Thing["T_LIFEPOINTS"]:
1397 Thing["T_PROGRESS"] += 1
1398 taid = [a for a in world_db["ThingActions"]
1399 if a == Thing["T_COMMAND"]][0]
1400 ThingAction = world_db["ThingActions"][taid]
1401 #if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1402 effort = ThingAction["TA_EFFORT"] # #
1403 if ThingAction["TA_NAME"] == "move": # #
1404 type = Thing["T_TYPE"] # #
1405 max_hp = (world_db["ThingTypes"][type] # #
1406 ["TT_LIFEPOINTS"]) # #
1407 effort = int(effort / math.sqrt(max_hp)) # #
1408 if Thing["T_PROGRESS"] == effort: # #
1409 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1410 Thing["T_COMMAND"] = 0
1411 Thing["T_PROGRESS"] = 0
1412 thingproliferation(Thing, proliferable_map)
1415 world_db["TURN"] += 1
1418 def new_Thing(type, pos=(0, 0)):
1419 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1421 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1426 "T_PLAYERDROP": 0, # #
1434 "T_MEMDEPTHMAP": False,
1437 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1438 build_fov_map(thing)
1442 def id_setter(id, category, id_store=False, start_at_1=False):
1443 """Set ID of object of category to manipulate ID unused? Create new one.
1445 The ID is stored as id_store.id (if id_store is set). If the integer of the
1446 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1447 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1448 always returned when no new object is created, otherwise the new object's
1451 min = 0 if start_at_1 else -1
1453 id = integer_test(id, min)
1455 if id in world_db[category]:
1460 if (start_at_1 and 0 == id) \
1461 or ((not start_at_1) and (id < 0)):
1462 id = 0 if start_at_1 else -1
1465 if id not in world_db[category]:
1473 """Send PONG line to server output file."""
1474 strong_write(io_db["file_out"], "PONG\n")
1478 """Abort server process."""
1479 if None == opts.replay:
1480 if world_db["WORLD_ACTIVE"]:
1482 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1483 raise SystemExit("received QUIT command")
1486 def command_thingshere(str_y, str_x):
1487 """Write to out file list of Things known to player at coordinate y, x."""
1488 # 7DRL: terrain, too
1489 if world_db["WORLD_ACTIVE"]:
1490 y = integer_test(str_y, 0, 255)
1491 x = integer_test(str_x, 0, 255)
1492 length = world_db["MAP_LENGTH"]
1493 if None != y and None != x and y < length and x < length:
1494 pos = (y * world_db["MAP_LENGTH"]) + x
1495 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1496 pos = y * world_db["MAP_LENGTH"] + x; # #
1497 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1498 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1499 for id in world_db["Things"]
1500 if not world_db["Things"][id]["carried"]
1501 if world_db["Things"][id]["T_TYPE"] == tid
1502 if y == world_db["Things"][id]["T_POSY"]
1503 if x == world_db["Things"][id]["T_POSX"]]:
1504 type = world_db["Things"][id]["T_TYPE"]
1505 name = world_db["ThingTypes"][type]["TT_NAME"]
1506 strong_write(io_db["file_out"], name + "\n")
1508 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1509 for mt in world_db["Things"][0]["T_MEMTHING"]
1510 if mt[0] == tid if y == mt[1] if x == mt[2]]:
1511 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1512 strong_write(io_db["file_out"], name + "\n")
1513 if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"): # #
1514 name = "(terrain: SEA)" # #
1515 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."): # #
1516 name = "(terrain: EARTH)" # #
1517 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"): # #
1518 name = "(terrain: SOIL)" # #
1519 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"): # #
1520 name = "(terrain: TREE)" # #
1521 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"): # #
1522 name = "(terrain: WALL)" # #
1523 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"): # #
1524 name = "(terrain: ALTAR)" # #
1527 strong_write(io_db["file_out"], name + "\n") # #
1528 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1530 print("Ignoring: Invalid map coordinates.")
1532 print("Ignoring: Command only works on existing worlds.")
1535 def play_commander(action, args=False):
1536 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1538 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1543 if action == "drop":
1544 t = world_db["Things"][0]
1545 if 0 == len(t["T_CARRIES"]):
1546 log("You have nothing to drop in your inventory.")
1549 elif action == "pick_up":
1550 t = world_db["Things"][0]
1551 ids = [id for id in world_db["Things"] if id
1552 if not world_db["Things"][id]["carried"]
1553 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1554 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
1556 log("No object to pick up.")
1558 used_slots = len(t["T_CARRIES"]) # #
1559 if used_slots >= world_db["ThingTypes"][t["T_TYPE"]] \
1561 log("Can't pick up: No storage room to carry anything more.")
1564 elif action == "move":
1565 t = world_db["Things"][0]
1567 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
1568 t["T_POSY"], t["T_POSX"])
1569 if 1 == move_result[0]:
1570 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
1571 if (ord("X") == world_db["MAP"][pos] # #
1572 or ord("|") == world_db["MAP"][pos]): # #
1573 carries_axe = False # #
1574 for id in t["T_CARRIES"]: # #
1575 type = world_db["Things"][id]["T_TYPE"] # #
1576 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
1577 carries_axe = True # #
1579 if not carries_axe: # #
1580 log("You can't move there.")
1583 log("You can't move there.")
1586 elif action == "use":
1587 t = world_db["Things"][0]
1588 if len(t["T_CARRIES"]):
1589 id = t["T_CARRIES"][t["T_ARGUMENT"]]
1590 type = world_db["Things"][id]["T_TYPE"]
1591 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe":
1592 log("To use this item for chopping, move towards a tree "
1593 + "while carrying it in your inventory.")
1595 elif world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry":
1596 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1597 if (world_db["MAP"][pos] == ord("X") # #
1598 or world_db["MAP"][pos] == ord("|")): # #
1599 log("Can't build when standing on barrier.") # #
1601 for id in [id for id in world_db["Things"]
1602 if not world_db["Things"][id] == t
1603 if not world_db["Things"][id]["carried"]
1604 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1605 if world_db["Things"][id]["T_POSX"] ==t["T_POSX"]]:
1606 log("Can't build when standing objects.") # #
1608 for id in t["T_CARRIES"]: # #
1609 type_tool = world_db["Things"][id]["T_TYPE"] # #
1610 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
1611 == "carpentry"): # #
1614 for id in t["T_CARRIES"]: # #
1615 type_material = world_db["Things"][id]["T_TYPE"] # #
1616 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1620 if wood_id == None: # #
1621 log("You can't use a " # #
1622 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1623 + " without some wood in your inventory.") # #
1625 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1626 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1627 if not world_db["MAP"][pos] == ord("."):
1628 log("Can only make soil out of non-soil earth.")
1630 elif world_db["ThingTypes"][type]["TT_TOOL"] == "wood": # #
1631 log("To use wood, you need a carpentry tool.") # #
1634 log("You have nothing to use in your inventory.")
1638 id = [x for x in world_db["ThingActions"]
1639 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1640 world_db["Things"][0]["T_COMMAND"] = id
1643 def set_command_and_argument_int(str_arg):
1644 val = integer_test(str_arg, 0, 255)
1646 world_db["Things"][0]["T_ARGUMENT"] = val
1649 def set_command_and_argument_movestring(str_arg):
1650 if str_arg in directions_db:
1651 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1654 print("Ignoring: Argument must be valid direction string.")
1656 if action == "move":
1657 return set_command_and_argument_movestring
1659 return set_command_and_argument_int
1664 def command_seedrandomness(seed_string):
1665 """Set rand seed to int(seed_string)."""
1666 val = integer_test(seed_string, 0, 4294967295)
1671 def command_makeworld(seed_string):
1672 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1674 Seed rand with seed. Do more only with a "wait" ThingAction and
1675 world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1676 world_db["Things"] emptied, call make_map() and set
1677 world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1678 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1679 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1680 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1684 # def free_pos(plant=False):
1685 def free_pos(plant=False): # #
1688 err = "Space to put thing on too hard to find. Map too small?"
1690 y = rand.next() % world_db["MAP_LENGTH"]
1691 x = rand.next() % world_db["MAP_LENGTH"]
1692 pos = y * world_db["MAP_LENGTH"] + x;
1694 and "." == chr(world_db["MAP"][pos])) \
1695 or ":" == chr(world_db["MAP"][pos]): # #
1699 raise SystemExit(err)
1700 # Replica of C code, wrongly ignores animatedness of new Thing.
1701 pos_clear = (0 == len([id for id in world_db["Things"]
1702 if world_db["Things"][id]["T_LIFEPOINTS"]
1703 if world_db["Things"][id]["T_POSY"] == y
1704 if world_db["Things"][id]["T_POSX"] == x]))
1709 val = integer_test(seed_string, 0, 4294967295)
1713 player_will_be_generated = False
1714 playertype = world_db["PLAYER_TYPE"]
1715 for ThingType in world_db["ThingTypes"]:
1716 if playertype == ThingType:
1717 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1718 player_will_be_generated = True
1720 if not player_will_be_generated:
1721 print("Ignoring: No player type with start number >0 defined.")
1724 for ThingAction in world_db["ThingActions"]:
1725 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1728 print("Ignoring: No thing action with name 'wait' defined.")
1730 for name in specials: # #
1731 if world_db[name] not in world_db["ThingTypes"]: # #
1732 print("Ignoring: No valid " + name + " set.") # #
1734 world_db["Things"] = {}
1736 world_db["WORLD_ACTIVE"] = 1
1737 world_db["TURN"] = 1
1738 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1739 id = id_setter(-1, "Things")
1740 world_db["Things"][id] = new_Thing(playertype, free_pos())
1741 if not world_db["Things"][0]["fovmap"]:
1742 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1743 world_db["Things"][0]["fovmap"] = empty_fovmap
1744 update_map_memory(world_db["Things"][0])
1745 for type in world_db["ThingTypes"]:
1746 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1747 if type != playertype:
1748 id = id_setter(-1, "Things")
1749 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"] # #
1750 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1751 strong_write(io_db["file_out"], "NEW_WORLD\n")
1755 def command_maplength(maplength_string):
1756 """Redefine map length. Invalidate map, therefore lose all things on it."""
1757 val = integer_test(maplength_string, 1, 256)
1759 world_db["MAP_LENGTH"] = val
1760 world_db["MAP"] = False
1761 set_world_inactive()
1762 world_db["Things"] = {}
1763 libpr.set_maplength(val)
1766 def command_worldactive(worldactive_string):
1767 """Toggle world_db["WORLD_ACTIVE"] if possible.
1769 An active world can always be set inactive. An inactive world can only be
1770 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1771 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1772 Also call log_help().
1774 # 7DRL: altar must be on map, and specials must be set for active world.
1775 val = integer_test(worldactive_string, 0, 1)
1777 if 0 != world_db["WORLD_ACTIVE"]:
1779 set_world_inactive()
1781 print("World already active.")
1782 elif 0 == world_db["WORLD_ACTIVE"]:
1784 for ThingAction in world_db["ThingActions"]:
1785 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1788 player_exists = False
1789 for Thing in world_db["Things"]:
1791 player_exists = True
1793 specials_set = True # #
1794 for name in specials: # #
1795 if world_db[name] not in world_db["ThingTypes"]: # #
1796 specials_set = False # #
1797 altar_found = False # #
1798 if world_db["MAP"]: # #
1799 pos = world_db["MAP"].find(b'_') # #
1801 y = int(pos / world_db["MAP_LENGTH"]) # #
1802 x = pos % world_db["MAP_LENGTH"] # #
1803 world_db["altar"] = (y, x) # #
1804 altar_found = True # #
1805 if wait_exists and player_exists and world_db["MAP"] \
1806 and specials_set: # #
1807 for id in world_db["Things"]:
1808 if world_db["Things"][id]["T_LIFEPOINTS"]:
1809 build_fov_map(world_db["Things"][id])
1811 update_map_memory(world_db["Things"][id], False)
1812 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1813 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1814 world_db["Things"][0]["fovmap"] = empty_fovmap
1815 world_db["WORLD_ACTIVE"] = 1
1818 print("Ignoring: Not all conditions for world activation met.")
1821 def specialtypesetter(name): # #
1822 """Setter world_db[name], deactivating world if set int no ThingType."""
1823 def helper(str_int):
1824 val = integer_test(str_int, 0)
1826 world_db[name] = val
1827 if world_db["WORLD_ACTIVE"] \
1828 and world_db[name] not in world_db["ThingTypes"]:
1829 world_db["WORLD_ACTIVE"] = 0
1830 print(name + " fits no known ThingType, deactivating world.")
1834 def test_for_id_maker(object, category):
1835 """Return decorator testing for object having "id" attribute."""
1838 if hasattr(object, "id"):
1841 print("Ignoring: No " + category +
1842 " defined to manipulate yet.")
1847 def command_tid(id_string):
1848 """Set ID of Thing to manipulate. ID unused? Create new one.
1850 Default new Thing's type to the first available ThingType, others: zero.
1852 id = id_setter(id_string, "Things", command_tid)
1854 if world_db["ThingTypes"] == {}:
1855 print("Ignoring: No ThingType to settle new Thing in.")
1857 type = list(world_db["ThingTypes"].keys())[0]
1858 world_db["Things"][id] = new_Thing(type)
1861 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1865 def command_tcommand(str_int):
1866 """Set T_COMMAND of selected Thing."""
1867 val = integer_test(str_int, 0)
1869 if 0 == val or val in world_db["ThingActions"]:
1870 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1872 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1876 def command_ttype(str_int):
1877 """Set T_TYPE of selected Thing."""
1878 val = integer_test(str_int, 0)
1880 if val in world_db["ThingTypes"]:
1881 world_db["Things"][command_tid.id]["T_TYPE"] = val
1883 print("Ignoring: ThingType ID belongs to no known ThingType.")
1887 def command_tcarries(str_int):
1888 """Append int(str_int) to T_CARRIES of selected Thing.
1890 The ID int(str_int) must not be of the selected Thing, and must belong to a
1891 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1893 val = integer_test(str_int, 0)
1895 if val == command_tid.id:
1896 print("Ignoring: Thing cannot carry itself.")
1897 elif val in world_db["Things"] \
1898 and not world_db["Things"][val]["carried"]:
1899 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1900 world_db["Things"][val]["carried"] = True
1902 print("Ignoring: Thing not available for carrying.")
1903 # Note that the whole carrying structure is different from the C version:
1904 # Carried-ness is marked by a "carried" flag, not by Things containing
1905 # Things internally.
1909 def command_tmemthing(str_t, str_y, str_x):
1910 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1912 The type must fit to an existing ThingType, and the position into the map.
1914 type = integer_test(str_t, 0)
1915 posy = integer_test(str_y, 0, 255)
1916 posx = integer_test(str_x, 0, 255)
1917 if None != type and None != posy and None != posx:
1918 if type not in world_db["ThingTypes"] \
1919 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1920 print("Ignoring: Illegal value for thing type or position.")
1922 memthing = (type, posy, posx)
1923 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1926 def setter_map(maptype):
1927 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1929 If no map of maptype exists yet, initialize it with ' ' bytes first.
1932 def valid_map_line(str_int, mapline):
1933 val = integer_test(str_int, 0, 255)
1935 if val >= world_db["MAP_LENGTH"]:
1936 print("Illegal value for map line number.")
1937 elif len(mapline) != world_db["MAP_LENGTH"]:
1938 print("Map line length is unequal map width.")
1943 def nonThingMap_helper(str_int, mapline):
1944 val = valid_map_line(str_int, mapline)
1946 length = world_db["MAP_LENGTH"]
1947 if not world_db["MAP"]:
1948 map = bytearray(b' ' * (length ** 2))
1950 map = world_db["MAP"]
1951 map[val * length:(val * length) + length] = mapline.encode()
1952 if not world_db["MAP"]:
1953 world_db["MAP"] = map
1956 def ThingMap_helper(str_int, mapline):
1957 val = valid_map_line(str_int, mapline)
1959 length = world_db["MAP_LENGTH"]
1960 if not world_db["Things"][command_tid.id][maptype]:
1961 map = bytearray(b' ' * (length ** 2))
1963 map = world_db["Things"][command_tid.id][maptype]
1964 map[val * length:(val * length) + length] = mapline.encode()
1965 if not world_db["Things"][command_tid.id][maptype]:
1966 world_db["Things"][command_tid.id][maptype] = map
1968 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
1971 def setter_tpos(axis):
1972 """Generate setter for T_POSX or T_POSY of selected Thing.
1974 If world is active, rebuilds animate things' fovmap, player's memory map.
1977 def helper(str_int):
1978 val = integer_test(str_int, 0, 255)
1980 if val < world_db["MAP_LENGTH"]:
1981 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1982 if world_db["WORLD_ACTIVE"] \
1983 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1984 build_fov_map(world_db["Things"][command_tid.id])
1985 if 0 == command_tid.id:
1986 update_map_memory(world_db["Things"][command_tid.id])
1988 print("Ignoring: Position is outside of map.")
1992 def command_ttid(id_string):
1993 """Set ID of ThingType to manipulate. ID unused? Create new one.
1995 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
1998 id = id_setter(id_string, "ThingTypes", command_ttid)
2000 world_db["ThingTypes"][id] = {
2001 "TT_NAME": "(none)",
2004 "TT_PROLIFERATE": 0,
2005 "TT_START_NUMBER": 0,
2006 "TT_STORAGE": 0, # #
2013 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
2017 def command_ttname(name):
2018 """Set TT_NAME of selected ThingType."""
2019 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
2023 def command_tttool(name):
2024 """Set TT_TOOL of selected ThingType."""
2025 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
2029 def command_ttsymbol(char):
2030 """Set TT_SYMBOL of selected ThingType. """
2032 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
2034 print("Ignoring: Argument must be single character.")
2038 def command_ttcorpseid(str_int):
2039 """Set TT_CORPSE_ID of selected ThingType."""
2040 val = integer_test(str_int, 0)
2042 if val in world_db["ThingTypes"]:
2043 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
2045 print("Ignoring: Corpse ID belongs to no known ThignType.")
2048 def command_taid(id_string):
2049 """Set ID of ThingAction to manipulate. ID unused? Create new one.
2051 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
2053 id = id_setter(id_string, "ThingActions", command_taid, True)
2055 world_db["ThingActions"][id] = {
2061 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
2064 @test_ThingAction_id
2065 def command_taname(name):
2066 """Set TA_NAME of selected ThingAction.
2068 The name must match a valid thing action function. If after the name
2069 setting no ThingAction with name "wait" remains, call set_world_inactive().
2071 if name == "wait" or name == "move" or name == "use" or name == "drop" \
2072 or name == "pick_up":
2073 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
2074 if 1 == world_db["WORLD_ACTIVE"]:
2075 wait_defined = False
2076 for id in world_db["ThingActions"]:
2077 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
2080 if not wait_defined:
2081 set_world_inactive()
2083 print("Ignoring: Invalid action name.")
2084 # In contrast to the original,naming won't map a function to a ThingAction.
2088 """Call ai() on player Thing, then turn_over()."""
2089 ai(world_db["Things"][0])
2093 """Commands database.
2095 Map command start tokens to ([0]) number of expected command arguments, ([1])
2096 the command's meta-ness (i.e. is it to be written to the record file, is it to
2097 be ignored in replay mode if read from server input file), and ([2]) a function
2101 "QUIT": (0, True, command_quit),
2102 "PING": (0, True, command_ping),
2103 "THINGS_HERE": (2, True, command_thingshere),
2104 "MAKE_WORLD": (1, False, command_makeworld),
2105 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
2106 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
2107 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
2108 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
2109 "MAP_LENGTH": (1, False, command_maplength),
2110 "WORLD_ACTIVE": (1, False, command_worldactive),
2111 "MAP": (2, False, setter_map("MAP")),
2112 "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)), # #
2113 "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")), # #
2114 "PLANT_0": (1, False, specialtypesetter("PLANT_0")), # #
2115 "PLANT_1": (1, False, specialtypesetter("PLANT_1")), # #
2116 "LUMBER": (1, False, specialtypesetter("LUMBER")), # #
2117 "TOOL_0": (1, False, specialtypesetter("TOOL_0")), # #
2118 "TOOL_1": (1, False, specialtypesetter("TOOL_1")), # #
2119 "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)), # #
2120 "TA_ID": (1, False, command_taid),
2121 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
2122 "TA_NAME": (1, False, command_taname),
2123 "TT_ID": (1, False, command_ttid),
2124 "TT_NAME": (1, False, command_ttname),
2125 "TT_TOOL": (1, False, command_tttool),
2126 "TT_SYMBOL": (1, False, command_ttsymbol),
2127 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
2128 "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2129 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2131 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2133 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2134 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
2135 "T_ID": (1, False, command_tid),
2136 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2137 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2138 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2139 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2140 "T_COMMAND": (1, False, command_tcommand),
2141 "T_TYPE": (1, False, command_ttype),
2142 "T_CARRIES": (1, False, command_tcarries),
2143 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2144 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2145 "T_MEMTHING": (3, False, command_tmemthing),
2146 "T_POSY": (1, False, setter_tpos("Y")),
2147 "T_POSX": (1, False, setter_tpos("X")),
2148 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
2149 "wait": (0, False, play_commander("wait")),
2150 "move": (1, False, play_commander("move")),
2151 "pick_up": (0, False, play_commander("pick_up")),
2152 "drop": (1, False, play_commander("drop", True)),
2153 "use": (1, False, play_commander("use", True)),
2154 "ai": (0, False, command_ai)
2156 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2159 """World state database. With sane default values. (Randomness is in rand.)"""
2167 "FAVOR_STAGE": 0, # #
2181 """Special type settings."""
2182 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0", "TOOL_1"] # #
2184 """Mapping of direction names to internal direction chars."""
2185 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2186 "west": "s", "north-west": "w", "north-east": "e"}
2188 """File IO database."""
2190 "path_save": "save",
2191 "path_record": "record_save",
2192 "path_worldconf": "confserver/world",
2193 "path_server": "server/",
2194 "path_in": "server/in",
2195 "path_out": "server/out",
2196 "path_worldstate": "server/worldstate",
2197 "tmp_suffix": "_tmp",
2198 "kicked_by_rival": False,
2199 "worldstate_updateable": False
2204 libpr = prep_library()
2205 rand = RandomnessIO()
2206 opts = parse_command_line_arguments()
2208 io_db["path_save"] = opts.savefile
2209 io_db["path_record"] = "record_" + opts.savefile
2212 io_db["verbose"] = True
2213 if None != opts.replay:
2217 except SystemExit as exit:
2218 print("ABORTING: " + exit.args[0])
2220 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")