3 # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
4 # or any later version. For details on its copyright, license, and warranties,
5 # see the file NOTICE in the root directory of the PlomRogue source package.
19 """"Interface to libplomrogue's pseudo-randomness generator."""
21 def set_seed(self, seed):
22 libpr.seed_rrand(1, seed)
25 return libpr.seed_rrand(0, 0)
30 seed = property(get_seed, set_seed)
34 """Prepare ctypes library at ./libplomrogue.so"""
35 libpath = ("./libplomrogue.so")
36 if not os.access(libpath, os.F_OK):
37 raise SystemExit("No library " + libpath + ", run ./redo first?")
38 libpr = ctypes.cdll.LoadLibrary(libpath)
39 libpr.seed_rrand.restype = ctypes.c_uint32
43 def c_pointer_to_bytearray(ba):
44 """Return C char * pointer to ba."""
45 type = ctypes.c_char * len(ba)
46 return type.from_buffer(ba)
49 def strong_write(file, string):
50 """Apply write(string), then flush()."""
55 def setup_server_io():
56 """Fill IO files DB with proper file( path)s. Write process IO test string.
58 Ensure IO files directory at server/. Remove any old input file if found.
59 Set up new input file for reading, and new output file for writing. Start
60 output file with process hash line of format PID + " " + floated UNIX time
61 (io_db["teststring"]). Raise SystemExit if file is found at path of either
62 record or save file plus io_db["tmp_suffix"].
64 def detect_atomic_leftover(path, tmp_suffix):
65 path_tmp = path + tmp_suffix
66 msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
67 "aborted previous attempt to write '" + path + "'. Aborting " \
68 "until matter is resolved by removing it from its current path."
69 if os.access(path_tmp, os.F_OK):
71 io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
72 io_db["save_wait"] = 0
73 io_db["verbose"] = False
74 io_db["record_chunk"] = ""
75 os.makedirs(io_db["path_server"], exist_ok=True)
76 io_db["file_out"] = open(io_db["path_out"], "w")
77 strong_write(io_db["file_out"], io_db["teststring"] + "\n")
78 if os.access(io_db["path_in"], os.F_OK):
79 os.remove(io_db["path_in"])
80 io_db["file_in"] = open(io_db["path_in"], "w")
81 io_db["file_in"].close()
82 io_db["file_in"] = open(io_db["path_in"], "r")
83 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
84 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
87 def cleanup_server_io():
88 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
89 def helper(file_key, path_key):
91 io_db[file_key].close()
92 if not io_db["kicked_by_rival"] \
93 and os.access(io_db[path_key], os.F_OK):
94 os.remove(io_db[path_key])
95 helper("file_in", "path_in")
96 helper("file_out", "path_out")
97 helper("file_worldstate", "path_worldstate")
98 if "file_record" in io_db:
99 io_db["file_record"].close()
103 """Send "msg" to log."""
104 strong_write(io_db["file_out"], "LOG " + msg + "\n")
107 def obey(command, prefix, replay=False, do_record=False):
108 """Call function from commands_db mapped to command's first token.
110 Tokenize command string with shlex.split(comments=True). If replay is set,
111 a non-meta command from the commands_db merely triggers obey() on the next
112 command from the records file. If not, non-meta commands set
113 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
114 do_record is set, are recorded to io_db["record_chunk"], and save_world()
115 is called (and io_db["record_chunk"] written) if 15 seconds have passed
116 since the last time it was called. The prefix string is inserted into the
117 server's input message between its beginning 'input ' and ':'. All activity
118 is preceded by a server_test() call. Commands that start with a lowercase
119 letter are ignored when world_db["WORLD_ACTIVE"] is False/0.
123 print("input " + prefix + ": " + command)
125 tokens = shlex.split(command, comments=True)
126 except ValueError as err:
127 print("Can't tokenize command string: " + str(err) + ".")
129 if len(tokens) > 0 and tokens[0] in commands_db \
130 and len(tokens) == commands_db[tokens[0]][0] + 1:
131 if commands_db[tokens[0]][1]:
132 commands_db[tokens[0]][2](*tokens[1:])
133 elif tokens[0][0].islower() and not world_db["WORLD_ACTIVE"]:
134 print("Ignoring lowercase-starting commands when world inactive.")
136 print("Due to replay mode, reading command as 'go on in record'.")
137 line = io_db["file_record"].readline()
139 obey(line.rstrip(), io_db["file_record"].prefix
140 + str(io_db["file_record"].line_n))
141 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
143 print("Reached end of record file.")
145 commands_db[tokens[0]][2](*tokens[1:])
147 io_db["record_chunk"] += command + "\n"
148 if time.time() > io_db["save_wait"] + 300:
149 atomic_write(io_db["path_record"], io_db["record_chunk"],
151 if world_db["WORLD_ACTIVE"]:
153 io_db["record_chunk"] = ""
154 io_db["save_wait"] = time.time()
155 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
156 elif 0 != len(tokens):
157 print("Invalid command/argument, or bad number of tokens.")
160 def atomic_write(path, text, do_append=False, delete=True):
161 """Atomic write of text to file at path, appended if do_append is set."""
162 path_tmp = path + io_db["tmp_suffix"]
166 if os.access(path, os.F_OK):
167 shutil.copyfile(path, path_tmp)
168 file = open(path_tmp, mode)
169 strong_write(file, text)
171 if delete and os.access(path, os.F_OK):
173 os.rename(path_tmp, path)
177 """Save all commands needed to reconstruct current world state."""
180 string = string.replace("\u005C", '\u005C\u005C')
181 return '"' + string.replace('"', '\u005C"') + '"'
186 if key == "MAP" or world_db["Things"][id][key]:
187 map = world_db["MAP"] if key == "MAP" \
188 else world_db["Things"][id][key]
189 length = world_db["MAP_LENGTH"]
190 for i in range(length):
191 line = map[i * length:(i * length) + length].decode()
192 string = string + key + " " + str(i) + " " + quote(line) \
199 for memthing in world_db["Things"][id]["T_MEMTHING"]:
200 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
201 str(memthing[1]) + " " + str(memthing[2]) + "\n"
204 def helper(category, id_string, special_keys={}):
206 for id in world_db[category]:
207 string = string + id_string + " " + str(id) + "\n"
208 for key in world_db[category][id]:
209 if not key in special_keys:
210 x = world_db[category][id][key]
211 argument = quote(x) if str == type(x) else str(x)
212 string = string + key + " " + argument + "\n"
213 elif special_keys[key]:
214 string = string + special_keys[key](id)
219 if (dict != type(world_db[key])
220 and key != "altar" # #
221 and key != "MAP" and key != "WORLD_ACTIVE"):
222 string = string + key + " " + str(world_db[key]) + "\n"
223 string = string + mapsetter("MAP")()
224 string = string + helper("ThingActions", "TA_ID")
225 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
226 for id in world_db["ThingTypes"]:
227 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
228 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
229 string = string + helper("Things", "T_ID",
230 {"T_CARRIES": False, "carried": False,
231 "T_MEMMAP": mapsetter("T_MEMMAP"),
232 "T_MEMTHING": memthing, "fovmap": False,
233 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
234 for id in world_db["Things"]:
235 if [] != world_db["Things"][id]["T_CARRIES"]:
236 string = string + "T_ID " + str(id) + "\n"
237 for carried_id in world_db["Things"][id]["T_CARRIES"]:
238 string = string + "T_CARRIES " + str(carried_id) + "\n"
239 string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
240 "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
241 atomic_write(io_db["path_save"], string)
244 def obey_lines_in_file(path, name, do_record=False):
245 """Call obey() on each line of path's file, use name in input prefix."""
246 file = open(path, "r")
248 for line in file.readlines():
249 obey(line.rstrip(), name + "file line " + str(line_n),
255 def parse_command_line_arguments():
256 """Return settings values read from command line arguments."""
257 parser = argparse.ArgumentParser()
258 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
260 parser.add_argument('-l', nargs="?", const="save", dest='savefile',
262 parser.add_argument('-v', dest='verbose', action='store_true')
263 opts, unknown = parser.parse_known_args()
268 """Ensure valid server out file belonging to current process.
270 This is done by comparing io_db["teststring"] to what's found at the start
271 of the current file at io_db["path_out"]. On failure, set
272 io_db["kicked_by_rival"] and raise SystemExit.
274 if not os.access(io_db["path_out"], os.F_OK):
275 raise SystemExit("Server output file has disappeared.")
276 file = open(io_db["path_out"], "r")
277 test = file.readline().rstrip("\n")
279 if test != io_db["teststring"]:
280 io_db["kicked_by_rival"] = True
281 msg = "Server test string in server output file does not match. This" \
282 " indicates that the current server process has been " \
283 "superseded by another one."
284 raise SystemExit(msg)
288 """Return next newline-delimited command from server in file.
290 Keep building return string until a newline is encountered. Pause between
291 unsuccessful reads, and after too much waiting, run server_test().
293 wait_on_fail = 0.03333
298 add = io_db["file_in"].readline()
300 command = command + add
301 if len(command) > 0 and "\n" == command[-1]:
302 command = command[:-1]
305 time.sleep(wait_on_fail)
306 if now + max_wait < time.time():
312 def try_worldstate_update():
313 """Write worldstate file if io_db["worldstate_updateable"] is set."""
314 if io_db["worldstate_updateable"]:
316 def write_map(string, map):
317 for i in range(length):
318 line = map[i * length:(i * length) + length].decode()
319 string = string + line + "\n"
323 if [] == world_db["Things"][0]["T_CARRIES"]:
324 inventory = "(none)\n"
326 for id in world_db["Things"][0]["T_CARRIES"]:
327 type_id = world_db["Things"][id]["T_TYPE"]
328 name = world_db["ThingTypes"][type_id]["TT_NAME"]
329 inventory = inventory + name + "\n"
330 # 7DRL additions: GOD_FAVOR
331 string = str(world_db["TURN"]) + "\n" + \
332 str(world_db["GOD_FAVOR"]) + "\n" + \
333 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
334 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
335 inventory + "%\n" + \
336 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
337 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
338 str(world_db["MAP_LENGTH"]) + "\n"
339 length = world_db["MAP_LENGTH"]
341 fov = bytearray(b' ' * (length ** 2))
343 for pos in [pos for pos in range(length ** 2)
344 if ord_v == world_db["Things"][0]["fovmap"][pos]]:
345 fov[pos] = world_db["MAP"][pos]
346 for id in [id for tid in reversed(sorted(list(world_db["ThingTypes"])))
347 for id in world_db["Things"]
348 if not world_db["Things"][id]["carried"]
349 if world_db["Things"][id]["T_TYPE"] == tid
350 if world_db["Things"][0]["fovmap"][
351 world_db["Things"][id]["T_POSY"] * length
352 + world_db["Things"][id]["T_POSX"]] == ord_v]:
353 type = world_db["Things"][id]["T_TYPE"]
354 c = ord(world_db["ThingTypes"][type]["TT_SYMBOL"])
355 fov[world_db["Things"][id]["T_POSY"] * length
356 + world_db["Things"][id]["T_POSX"]] = c
357 string = write_map(string, fov)
359 mem = world_db["Things"][0]["T_MEMMAP"][:]
360 for mt in [mt for tid in reversed(sorted(list(world_db["ThingTypes"])))
361 for mt in world_db["Things"][0]["T_MEMTHING"]
363 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
364 mem[(mt[1] * length) + mt[2]] = ord(c)
365 string = write_map(string, mem)
367 metamapA = bytearray(b'0' * (length ** 2)) # #
368 for id in [id for id in world_db["Things"] # #
369 if not world_db["Things"][id]["carried"] # #
370 if world_db["Things"][id]["T_LIFEPOINTS"] # #
371 if world_db["Things"][0]["fovmap"][ # #
372 world_db["Things"][id]["T_POSY"] * length # #
373 + world_db["Things"][id]["T_POSX"]] == ord_v]: # #
374 pos = (world_db["Things"][id]["T_POSY"] * length # #
375 + world_db["Things"][id]["T_POSX"]) # #
376 if id == 0 or world_db["EMPATHY"]: # #
377 type = world_db["Things"][id]["T_TYPE"] # #
378 max_hp = world_db["ThingTypes"][type]["TT_LIFEPOINTS"] # #
379 third_of_hp = max_hp / 3 # #
380 hp = world_db["Things"][id]["T_LIFEPOINTS"] # #
382 if hp > 2 * third_of_hp: # #
384 elif hp > third_of_hp: # #
386 metamapA[pos] = ord('a') + add # #
388 metamapA[pos] = ord('X') # #
389 for mt in world_db["Things"][0]["T_MEMTHING"]: # #
390 pos = mt[1] * length + mt[2] # #
391 if metamapA[pos] < ord('2'): # #
392 metamapA[pos] += 1 # #
393 string = write_map(string, metamapA) # #
395 metamapB = bytearray(b' ' * (length ** 2)) # #
396 for id in [id for id in world_db["Things"] # #
397 if not world_db["Things"][id]["carried"] # #
398 if world_db["Things"][id]["T_LIFEPOINTS"] # #
399 if world_db["Things"][0]["fovmap"][ # #
400 world_db["Things"][id]["T_POSY"] * length # #
401 + world_db["Things"][id]["T_POSX"]] == ord_v]: # #
402 pos = (world_db["Things"][id]["T_POSY"] * length # #
403 + world_db["Things"][id]["T_POSX"]) # #
404 if id == 0 or world_db["EMPATHY"]: # #
405 action = world_db["Things"][id]["T_COMMAND"] # #
407 name = world_db["ThingActions"][action]["TA_NAME"] # #
410 metamapB[pos] = ord(name[0]) # #
411 string = write_map(string, metamapB) # #
413 atomic_write(io_db["path_worldstate"], string, delete=False)
414 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
415 io_db["worldstate_updateable"] = False
419 """Replay game from record file.
421 Use opts.replay as breakpoint turn to which to replay automatically before
422 switching to manual input by non-meta commands in server input file
423 triggering further reads of record file. Ensure opts.replay is at least 1.
424 Run try_worldstate_update() before each interactive obey()/read_command().
428 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
429 " (if so late a turn is to be found).")
430 if not os.access(io_db["path_record"], os.F_OK):
431 raise SystemExit("No record file found to replay.")
432 io_db["file_record"] = open(io_db["path_record"], "r")
433 io_db["file_record"].prefix = "record file line "
434 io_db["file_record"].line_n = 1
435 while world_db["TURN"] < opts.replay:
436 line = io_db["file_record"].readline()
439 obey(line.rstrip(), io_db["file_record"].prefix
440 + str(io_db["file_record"].line_n))
441 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
443 try_worldstate_update()
444 obey(read_command(), "in file", replay=True)
448 """Play game by server input file commands. Before, load save file found.
450 If no save file is found, a new world is generated from the commands in the
451 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
452 command and all that follow via the server input file. Run
453 try_worldstate_update() before each interactive obey()/read_command().
455 if os.access(io_db["path_save"], os.F_OK):
456 obey_lines_in_file(io_db["path_save"], "save")
458 if not os.access(io_db["path_worldconf"], os.F_OK):
459 msg = "No world config file from which to start a new world."
460 raise SystemExit(msg)
461 obey_lines_in_file(io_db["path_worldconf"], "world config ",
463 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
465 try_worldstate_update()
466 obey(read_command(), "in file", do_record=True)
470 """(Re-)make island map.
472 Let "~" represent water, "." land, "X" trees: Build island shape randomly,
473 start with one land cell in the middle, then go into cycle of repeatedly
474 selecting a random sea cell and transforming it into land if it is neighbor
475 to land. The cycle ends when a land cell is due to be created at the map's
476 border. Then put some trees on the map (TODO: more precise algorithm desc).
478 # 7DRL: Also add some ":" cells, and (not surrounded by trees!) "_" altar.
480 def is_neighbor(coordinates, type):
483 length = world_db["MAP_LENGTH"]
485 diag_west = x + (ind > 0)
486 diag_east = x + (ind < (length - 1))
487 pos = (y * length) + x
488 if (y > 0 and diag_east
489 and type == chr(world_db["MAP"][pos - length + ind])) \
491 and type == chr(world_db["MAP"][pos + 1])) \
492 or (y < (length - 1) and diag_east
493 and type == chr(world_db["MAP"][pos + length + ind])) \
494 or (y > 0 and diag_west
495 and type == chr(world_db["MAP"][pos - length - (not ind)])) \
497 and type == chr(world_db["MAP"][pos - 1])) \
498 or (y < (length - 1) and diag_west
499 and type == chr(world_db["MAP"][pos + length - (not ind)])):
503 world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
504 length = world_db["MAP_LENGTH"]
505 add_half_width = (not (length % 2)) * int(length / 2)
506 world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
508 y = rand.next() % length
509 x = rand.next() % length
510 pos = (y * length) + x
511 if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
512 if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
514 world_db["MAP"][pos] = ord(".")
515 n_trees = int((length ** 2) / 16)
517 while (i_trees <= n_trees):
518 single_allowed = rand.next() % 32
519 y = rand.next() % length
520 x = rand.next() % length
521 pos = (y * length) + x
522 if "." == chr(world_db["MAP"][pos]) \
523 and ((not single_allowed) or is_neighbor((y, x), "X")):
524 world_db["MAP"][pos] = ord("X")
526 # This all-too-precise replica of the original C code misses iter_limit().
527 n_colons = int((length ** 2) / 16) # #
529 while (i_colons <= n_colons): # #
530 single_allowed = rand.next() % 256 # #
531 y = rand.next() % length # #
532 x = rand.next() % length # #
533 pos = (y * length) + x # #
534 if ("." == chr(world_db["MAP"][pos]) # #
535 and ((not single_allowed) or is_neighbor((y, x), ":"))): # #
536 world_db["MAP"][pos] = ord(":") # #
538 altar_placed = False # #
539 while not altar_placed: # #
540 y = rand.next() % length # #
541 x = rand.next() % length # #
542 pos = (y * length) + x # #
543 if (("." == chr(world_db["MAP"][pos] # #
544 or ":" == chr(world_db["MAP"][pos]))
545 and not is_neighbor((y, x), "X"))): # #
546 world_db["MAP"][pos] = ord("_") # #
547 world_db["altar"] = (y, x) # #
548 altar_placed = True # #
551 def eat_vs_hunger_threshold(thingtype):
552 """Return satiation cost of eating for type. Good food for it must be >."""
553 hunger_unit = hunger_per_turn(thingtype)
554 actiontype = [id for id in world_db["ThingActions"]
555 if world_db["ThingActions"][id]["TA_NAME"] == "use"][0]
556 return world_db["ThingActions"][actiontype]["TA_EFFORT"] * hunger_unit
559 def update_map_memory(t, age_map=True):
560 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
562 def age_some_memdepthmap_on_nonfov_cells():
563 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
567 # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
568 # if not ord_v == t["fovmap"][pos]
569 # if ord_0 <= t["T_MEMDEPTHMAP"][pos]
570 # if ord_9 > t["T_MEMDEPTHMAP"][pos]
571 # if not rand.next() % (2 **
572 # (t["T_MEMDEPTHMAP"][pos] - 48))]:
573 # t["T_MEMDEPTHMAP"][pos] += 1
574 memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
575 fovmap = c_pointer_to_bytearray(t["fovmap"])
576 libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap)
578 def update_mem_and_memdepthmap_via_fovmap():
579 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
580 # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
581 # if ord_v == t["fovmap"][pos]]:
582 # t["T_MEMDEPTHMAP"][pos] = ord_0
583 # t["T_MEMMAP"][pos] = world_db["MAP"][pos]
584 memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
585 memmap = c_pointer_to_bytearray(t["T_MEMMAP"])
586 fovmap = c_pointer_to_bytearray(t["fovmap"])
587 map = c_pointer_to_bytearray(world_db["MAP"])
588 libpr.update_mem_and_memdepthmap_via_fovmap(map, fovmap, memdepthmap,
591 if not t["T_MEMMAP"]:
592 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
593 if not t["T_MEMDEPTHMAP"]:
594 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
595 update_mem_and_memdepthmap_via_fovmap()
597 age_some_memdepthmap_on_nonfov_cells()
599 t["T_MEMTHING"] = [mt for mt in t["T_MEMTHING"]
600 if ord_v != t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
602 maplength = world_db["MAP_LENGTH"]
603 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"]) # #
604 [t["T_MEMTHING"].append((world_db["Things"][id]["T_TYPE"],
605 world_db["Things"][id]["T_POSY"],
606 world_db["Things"][id]["T_POSX"]))
607 for id in world_db["Things"]
608 if not world_db["Things"][id]["carried"]
609 if not world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
611 if (t == world_db["Things"][0] or
612 (world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
613 ["TT_TOOL"] == "food"
615 world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
616 ["TT_TOOLPOWER"] > eat_cost
619 if ord_v == t["fovmap"][(world_db["Things"][id]["T_POSY"] * maplength)
620 + world_db["Things"][id]["T_POSX"]]]
623 def set_world_inactive():
624 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
626 if os.access(io_db["path_worldstate"], os.F_OK):
627 os.remove(io_db["path_worldstate"])
628 world_db["WORLD_ACTIVE"] = 0
631 def integer_test(val_string, min, max=None):
632 """Return val_string if possible integer >= min and <= max, else None."""
634 val = int(val_string)
635 if val < min or (max is not None and val > max):
639 msg = "Ignoring: Please use integer >= " + str(min)
641 msg += " and <= " + str(max)
647 def setter(category, key, min, max=None):
648 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
651 val = integer_test(val_string, min, max)
655 if category == "Thing":
656 id_store = command_tid
657 decorator = test_Thing_id
658 elif category == "ThingType":
659 id_store = command_ttid
660 decorator = test_ThingType_id
661 elif category == "ThingAction":
662 id_store = command_taid
663 decorator = test_ThingAction_id
667 val = integer_test(val_string, min, max)
669 world_db[category + "s"][id_store.id][key] = val
673 def build_fov_map(t):
674 """Build Thing's FOV map."""
675 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
676 fovmap = c_pointer_to_bytearray(t["fovmap"])
677 map = c_pointer_to_bytearray(world_db["MAP"])
678 if libpr.build_fov_map(t["T_POSY"], t["T_POSX"], fovmap, map):
679 raise RuntimeError("Malloc error in build_fov_Map().")
683 """Send quick usage info to log."""
684 log("Use 'w'/'e'/'s'/'d'/'x'/'c' to move, and 'w' to wait.")
685 log("Use 'p' to pick up objects, and 'D' to drop them.")
686 log("Some objects can be used (such as: eaten) by 'u' if they are in "
687 + "your inventory. Use 'Up'/'Down' to navigate the inventory.")
688 log("Use 'l' to toggle 'look' mode (move an exploration cursor instead of "
689 + "the player over the map).")
690 log("Use 'PgUp'/PgDn' to scroll the 'Things here' window.")
691 log("See README file for more details.")
694 def decrement_lifepoints(t):
695 """Decrement t's lifepoints by 1, and if to zero, corpse it.
697 If t is the player avatar, only blank its fovmap, so that the client may
698 still display memory data. On non-player things, erase fovmap and memory.
699 Dying actors drop all their things.
701 # 7DRL: Return 1 if death, else 0.
702 # 7DRL: Re-spawn died-out species.
703 t["T_LIFEPOINTS"] -= 1
704 live_type = t["T_TYPE"] # 7DRL
705 if 0 == t["T_LIFEPOINTS"]:
706 for id in t["T_CARRIES"]:
707 t["T_CARRIES"].remove(id)
708 world_db["Things"][id]["T_POSY"] = t["T_POSY"]
709 world_db["Things"][id]["T_POSX"] = t["T_POSX"]
710 world_db["Things"][id]["carried"] = False
711 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
712 if world_db["Things"][0] == t:
713 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
715 log("See README on how to start over.")
718 t["T_MEMMAP"] = False
719 t["T_MEMDEPTHMAP"] = False
721 n_species = len([id for id in world_db["Things"] # #
722 if world_db["Things"][id]["T_TYPE"] == live_type])
723 if 0 == n_species: # #
724 id = id_setter(-1, "Things")
725 world_db["Things"][id] = new_Thing(live_type,
728 + world_db["ThingTypes"][live_type]["TT_NAME"]
729 + " has temporarily died out. "
730 + "One new-born is spawned at the altar.")
731 return world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
735 def mv_yx_in_dir_legal(dir, y, x):
736 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
737 dir_c = dir.encode("ascii")[0]
738 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
740 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
741 return (test, libpr.result_y(), libpr.result_x())
744 def enter_altar(): # #
745 """What happens when the player enters the altar."""
746 if world_db["FAVOR_STAGE"] > 9000:
747 log("You step on a soul-less slab of stone.")
749 log("YOU ENTER SACRED GROUND.")
750 if world_db["FAVOR_STAGE"] == 0:
751 world_db["FAVOR_STAGE"] = 1
752 log("The Island God speaks to you: \"I don't trust you. You intrude "
753 + "on the island's affairs. I think you're a nuisance at best, "
754 + "and a danger to my children at worst. I will give you a "
755 + "chance to lighten my mood, however: For a while now, I've "
756 + "been trying to spread the plant "
757 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"] + " (\""
758 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_SYMBOL"]
759 + "\"). I have not been very successful so far. Maybe you can "
760 + "make yourself useful there. I will count each further "
761 + world_db["ThingTypes"][world_db["PLANT_0"]]["TT_NAME"]
762 + " that grows to your favor.\"")
763 elif world_db["FAVOR_STAGE"] == 1 and world_db["GOD_FAVOR"] >= 100:
764 world_db["FAVOR_STAGE"] = 2
765 log("The Island God speaks to you: \"You could have done worse so "
766 + "far. Maybe you are not the worst to happen to this island "
767 + "since the metal birds threw the great lightning ball. Maybe "
768 + "you can help me spread another plant. It multiplies faster, "
769 + "and it is highly nutritious: "
770 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"] + " (\""
771 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_SYMBOL"]
772 + "\"). It is new. I give you the only example. Be very careful "
773 + "with it! I also give you another tool that may be helpful.\"")
774 id = id_setter(-1, "Things")
775 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
777 id = id_setter(-1, "Things")
778 world_db["Things"][id] = new_Thing(world_db["TOOL_0"],
780 elif world_db["FAVOR_STAGE"] == 2 and \
781 0 == len([id for id in world_db["Things"]
782 if world_db["Things"][id]["T_TYPE"]
783 == world_db["PLANT_1"]]):
784 log("The Island God speaks to you: \"I am greatly disappointed that "
786 + world_db["ThingTypes"][world_db["PLANT_1"]]["TT_NAME"]
787 + " this island had. Here is another one. It cost me great work. "
788 + "Be more careful this time.\"")
789 id = id_setter(-1, "Things")
790 world_db["Things"][id] = new_Thing(world_db["PLANT_1"],
792 world_db["GOD_FAVOR"] -= 250
793 elif world_db["GOD_FAVOR"] > 9000:
794 world_db["FAVOR_STAGE"] = 9001
795 log("The Island God speaks to you: \"You have proven yourself worthy"
796 + " of my respect. You were a good citizen to the island, and "
797 + "sometimes a better steward to its inhabitants than me. The "
798 + "island shall miss you when you leave. But you have earned "
799 + "the right to do so. Take this "
800 + world_db["ThingTypes"][world_db["SLIPPERS"]]["TT_NAME"]
801 + " and USE it when you please. It will take you to where you "
802 + "came from. (But do feel free to stay here as long as you "
804 id = id_setter(-1, "Things")
805 world_db["Things"][id] = new_Thing(world_db["SLIPPERS"],
810 """Make t do nothing (but loudly, if player avatar)."""
811 if t == world_db["Things"][0]:
816 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
817 # 7DRL: Player wounding (worse: killing) others will lower God's favor.
818 # 7DRL: Player entering the altar triggers enter_altar().
819 # 7DRL: Player with axe chops down trees.
821 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
822 t["T_POSY"], t["T_POSX"])
823 if 1 == move_result[0]:
824 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
825 hitted = [id for id in world_db["Things"]
826 if world_db["Things"][id] != t
827 if world_db["Things"][id]["T_LIFEPOINTS"]
828 if world_db["Things"][id]["T_POSY"] == move_result[1]
829 if world_db["Things"][id]["T_POSX"] == move_result[2]]
832 if t == world_db["Things"][0]:
833 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
834 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
835 log("You wound " + hitted_name + ".")
836 world_db["GOD_FAVOR"] -= -1 # #
838 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
839 log(hitter_name +" wounds you.")
840 test = decrement_lifepoints(world_db["Things"][hit_id]) # #(test=)
841 if test and t == world_db["Things"][0]: # #
842 world_db["GOD_FAVOR"] -test # #
844 if (ord("X") == world_db["MAP"][pos] # #
845 or ord("|") == world_db["MAP"][pos]): # #
846 carries_axe = False # #
847 for id in t["T_CARRIES"]: # #
848 type = world_db["Things"][id]["T_TYPE"] # #
849 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
850 carries_axe = True # #
853 axe_name = world_db["ThingTypes"][type]["TT_NAME"] # #
854 if t == world_db["Things"][0]: # #
855 log("With your " + axe_name + ", you chop!\n") # #
856 if ord("X") == world_db["MAP"][pos]: # #
857 world_db["GOD_FAVOR"] = -1 # #
858 chop_power = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
859 case_X = world_db["MAP"][pos] == ord("X") # #
860 if (chop_power > 0 # #
862 0 == int(rand.next() / chop_power)) # #
863 or (not case_X and # #
864 0 == int(rand.next() / (3 * chop_power))))): # #
865 if t == world_db["Things"][0]: # #
866 log("You chop it down.") # #
867 if world_db["MAP"][pos] == ord("X"): # #
868 world_db["GOD_FAVOR"] = -10 # #
869 world_db["MAP"][pos] = ord(".") # #
870 i = 3 if case_X else 1 # #
871 for i in range(i): # #
872 id = id_setter(-1, "Things") # #
873 world_db["Things"][id] = \
874 new_Thing(world_db["LUMBER"], # #
875 (move_result[1], move_result[2])) # #
878 passable = ("." == chr(world_db["MAP"][pos]) or
879 ":" == chr(world_db["MAP"][pos]) or # #
880 "_" == chr(world_db["MAP"][pos])) # #
881 dir = [dir for dir in directions_db
882 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
884 t["T_POSY"] = move_result[1]
885 t["T_POSX"] = move_result[2]
886 for id in t["T_CARRIES"]:
887 world_db["Things"][id]["T_POSY"] = move_result[1]
888 world_db["Things"][id]["T_POSX"] = move_result[2]
890 if t == world_db["Things"][0]:
891 log("You move " + dir + ".")
892 if (move_result[1] == world_db["altar"][0] and # #
893 move_result[2] == world_db["altar"][1]): # #
895 elif t == world_db["Things"][0]:
896 log("You fail to move " + dir + ".")
899 def actor_pick_up(t):
900 """Make t pick up (topmost?) Thing from ground into inventory.
902 Define topmostness by how low the thing's type ID is.
904 # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
905 # 7DRL: Non-players pick up nothing but food of good value to them.
906 used_slots = len(t["T_CARRIES"]) # #
907 if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
908 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
909 if not world_db["Things"][id]["carried"]
910 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
911 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
914 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"]) # #
916 tid = world_db["Things"][iid]["T_TYPE"]
917 if lowest_tid == -1 or tid < lowest_tid:
918 if (t != world_db["Things"][0] and # #
919 (world_db["ThingTypes"][tid]["TT_TOOL"] != "food" # #
920 or (world_db["ThingTypes"][tid]["TT_TOOLPOWER"] # #
925 world_db["Things"][id]["carried"] = True
926 type = world_db["Things"][id]["T_TYPE"] # #
927 if (t != world_db["Things"][0] # #
928 and world_db["Things"][id]["T_PLAYERDROP"] # #
929 and world_db["ThingTypes"][type]["TT_TOOL"] == "food"): # #
930 score = world_db["ThingTypes"][type]["TT_TOOLPOWER"] / 32 # #
931 world_db["GOD_FAVOR"] += score # #
932 world_db["Things"][id]["T_PLAYERDROP"] = 0 # #
933 t["T_CARRIES"].append(id)
934 if t == world_db["Things"][0]:
935 log("You pick up an object.")
936 elif t == world_db["Things"][0]:
937 log("You try to pick up an object, but there is none.")
938 elif t == world_db["Things"][0]: # #
939 log("Can't pick up object: No storage room to carry more.") # #
943 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
944 # TODO: Handle case where T_ARGUMENT matches nothing.
945 if len(t["T_CARRIES"]):
946 id = t["T_CARRIES"][t["T_ARGUMENT"]]
947 t["T_CARRIES"].remove(id)
948 world_db["Things"][id]["carried"] = False
949 if t == world_db["Things"][0]:
950 log("You drop an object.")
951 world_db["Things"][id]["T_PLAYERDROP"] = 1 # #
952 elif t == world_db["Things"][0]:
953 log("You try to drop an object, but you own none.")
957 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
958 # TODO: Handle case where T_ARGUMENT matches nothing.
959 # 7DLR: Handle SLIPPERS-type Thing use.
960 # 7DRL: Player with fertilizer fertilizes
961 if len(t["T_CARRIES"]):
962 id = t["T_CARRIES"][t["T_ARGUMENT"]]
963 type = world_db["Things"][id]["T_TYPE"]
964 if type == world_db["SLIPPERS"]: # #
965 if t == world_db["Things"][0]: # #
966 log("You use the " + world_db["ThingTypes"][type]["TT_NAME"] # #
967 + ". It glows in wondrous colors, and emits a sound as " # #
968 + "if from a dying cat. The Island God laughs.\n") # #
969 t["T_LIFEPOINTS"] = 1 # #
970 decrement_lifepoints(t) # #
971 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "axe" # #
972 and t == world_db["Things"][0]): # #
973 log("To use this item for chopping, move towards a tree while "
974 + "carrying it in your inventory.") # #
975 elif (world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry"): # #
976 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
977 if (world_db["MAP"][pos] == ord("X") # #
978 or world_db["MAP"][pos] == ord("|")): # #
979 log("Can't build when standing on barrier.") # #
981 for id in [id for id in world_db["Things"]
982 if not world_db["Things"][id] == t
983 if not world_db["Things"][id]["carried"]
984 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
985 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]:
986 log("Can't build when standing objects.") # #
988 for id in t["T_CARRIES"]: # #
989 type_tool = world_db["Things"][id]["T_TYPE"] # #
990 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
994 for id in t["T_CARRIES"]: # #
995 type_material = world_db["Things"][id]["T_TYPE"] # #
996 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1000 if wood_id != None: # #
1001 t["T_CARRIES"].remove(wood_id) # #
1002 del world_db["Things"][wood_id] # #
1003 world_db["MAP"][pos] = ord("|") # #
1004 log("With your " # #
1005 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1006 + " you build a wooden barrier from your " # #
1007 + world_db["ThingTypes"][type_material]["TT_NAME"] # #
1010 log("You can't use a " # #
1011 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1012 + " without some wood in your inventory.") # #
1013 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1014 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1015 if world_db["MAP"][pos] == ord("."):
1016 log("You create soil.")
1017 world_db["MAP"][pos] = ord(":")
1019 log("Can only make soil out of non-soil earth.")
1020 elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
1021 t["T_CARRIES"].remove(id)
1022 del world_db["Things"][id]
1023 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1024 if t == world_db["Things"][0]:
1025 log("You consume this object.")
1026 elif t == world_db["Things"][0]:
1027 log("You try to use this object, but fail.")
1028 elif t == world_db["Things"][0]:
1029 log("You try to use an object, but you own none.")
1032 def thingproliferation(t, prol_map):
1033 """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1035 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1036 marked "." in prol_map. If there are several map cell candidates, one is
1039 # 7DRL: success increments God's mood
1040 # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1041 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1042 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
1044 for dir in [directions_db[key] for key in directions_db]:
1045 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1046 pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1047 if mv_result[0] and \
1048 (ord(":") == prol_map[pos] # #
1049 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1050 and ord(".") == prol_map[pos])):
1051 # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1052 # * world_db["MAP_LENGTH"]
1054 candidates.append((mv_result[1], mv_result[2]))
1056 i = rand.next() % len(candidates)
1057 id = id_setter(-1, "Things")
1058 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1059 world_db["Things"][id] = newT
1060 if (world_db["FAVOR_STAGE"] > 0 # #
1061 and t["T_TYPE"] == world_db["PLANT_0"]): # #
1062 world_db["GOD_FAVOR"] += 5 # #
1063 elif t["T_TYPE"] == world_db["PLANT_1"]: # #
1064 world_db["GOD_FAVOR"] += 25 # #
1068 """If t's HP < max, increment them if well-nourished, maybe waiting."""
1069 # 7DRL: Successful heals increment God's mood.
1070 if t["T_LIFEPOINTS"] < \
1071 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1072 wait_id = [id for id in world_db["ThingActions"]
1073 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1074 wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
1075 testval = int(abs(t["T_SATIATION"]) / wait_divider)
1076 if (testval <= 1 or 1 == (rand.next() % testval)):
1077 t["T_LIFEPOINTS"] += 1
1078 if t == world_db["Things"][0]:
1082 def hunger_per_turn(type_id):
1083 """The amount of satiation score lost per turn for things of given type."""
1084 return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
1088 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1089 if t["T_SATIATION"] > -32768:
1090 #max_hp = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]
1091 t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"]) # int(math.sqrt(max_hp))
1092 if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
1093 if t == world_db["Things"][0]:
1094 if t["T_SATIATION"] < 0:
1095 log("You suffer from hunger.")
1097 log("You suffer from over-eating.")
1098 decrement_lifepoints(t)
1101 def get_dir_to_target(t, filter):
1102 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1104 The path-wise nearest target is chosen, via the shortest available path.
1105 Target must not be t. On succcess, return positive value, else False.
1107 "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
1108 is, and its corpse would be healthy food for t
1109 "f": move away from an enemy – any visible actor whose thing type has more
1110 TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
1111 food – if it is closer than n steps, where n will shrink as t's hunger
1112 grows; if enemy is too close, move towards (attack) the enemy instead;
1113 if no fleeing is possible, nor attacking useful, wait; don't tread on
1114 non-enemies for fleeing
1115 "c": Thing in memorized map is consumable of sufficient nutrition for t
1116 "s": memory map cell with greatest-reachable degree of unexploredness
1119 def zero_score_map_where_char_on_memdepthmap(c):
1120 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1121 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1122 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1123 # set_map_score(i, 0)
1124 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1125 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1126 raise RuntimeError("No score map allocated for "
1127 "zero_score_map_where_char_on_memdepthmap().")
1129 def set_map_score_at_thingpos(id, score):
1130 pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
1131 + world_db["Things"][id]["T_POSX"]
1132 set_map_score(pos, score)
1134 def set_map_score(pos, score):
1135 test = libpr.set_map_score(pos, score)
1137 raise RuntimeError("No score map allocated for set_map_score().")
1139 def get_map_score(pos):
1140 result = libpr.get_map_score(pos)
1142 raise RuntimeError("No score map allocated for get_map_score().")
1145 def animate_in_fov(Thing, maplength): # maplength needed for optimization?
1146 if not Thing["T_LIFEPOINTS"] or Thing["carried"] or Thing == t:
1148 pos = Thing["T_POSY"] * maplength + Thing["T_POSX"]
1149 if 118 == t["fovmap"][pos]: # optimization: 118 = ord("v")
1152 def good_attack_target(v):
1153 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1154 type = world_db["ThingTypes"][v["T_TYPE"]]
1155 type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
1156 if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
1157 and type_corpse["TT_TOOL"] == "food" \
1158 and type_corpse["TT_TOOLPOWER"] > eat_cost:
1162 def good_flee_target(m):
1163 own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
1164 corpse_type = world_db["ThingTypes"][own_corpse_id]
1165 targetness = 0 if corpse_type["TT_TOOL"] != "food" \
1166 else corpse_type["TT_TOOLPOWER"]
1167 type = world_db["ThingTypes"][m["T_TYPE"]]
1168 if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
1169 and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
1174 maplength = world_db["MAP_LENGTH"]
1175 if t["fovmap"] and "a" == filter:
1176 for id in world_db["Things"]:
1177 if animate_in_fov(world_db["Things"][id], maplength):
1178 if good_attack_target(world_db["Things"][id]):
1180 elif t["fovmap"] and "f" == filter:
1181 for id in world_db["Things"]:
1182 if animate_in_fov(world_db["Things"][id], maplength):
1183 if good_flee_target(world_db["Things"][id]):
1185 elif t["T_MEMMAP"] and "c" == filter:
1186 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1187 ord_blank = ord(" ")
1188 for mt in t["T_MEMTHING"]:
1189 if ord_blank != t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1191 (t != world_db["Things"][0] or \
1192 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"
1193 and world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"]
1198 def set_cells_passable_on_memmap_to_65534_on_scoremap():
1199 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1200 # ord_dot = ord(".")
1201 # memmap = t["T_MEMMAP"]
1202 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1203 # if ord_dot == memmap[i]]:
1204 # set_map_score(i, 65534) # i.e. 65535-1
1205 map = c_pointer_to_bytearray(t["T_MEMMAP"])
1206 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1207 raise RuntimeError("No score map allocated for "
1208 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1210 def init_score_map():
1211 test = libpr.init_score_map()
1213 raise RuntimeError("Malloc error in init_score_map().")
1214 set_cells_passable_on_memmap_to_65534_on_scoremap()
1215 maplength = world_db["MAP_LENGTH"]
1217 [set_map_score_at_thingpos(id, 0)
1218 for id in world_db["Things"]
1219 if animate_in_fov(world_db["Things"][id], maplength)
1220 if good_attack_target(world_db["Things"][id])]
1222 [set_map_score_at_thingpos(id, 0)
1223 for id in world_db["Things"]
1224 if animate_in_fov(world_db["Things"][id], maplength)
1225 if good_flee_target(world_db["Things"][id])]
1227 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1228 ord_blank = ord(" ")
1229 [set_map_score(mt[1] * maplength + mt[2], 0)
1230 for mt in t["T_MEMTHING"]
1231 if ord_blank != t["T_MEMMAP"][mt[1] * maplength + mt[2]]
1232 if t != world_db["Things"][0] or
1233 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food" and
1234 world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"] > eat_cost)]
1236 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1238 [set_map_score_at_thingpos(id, 65535)
1239 for id in world_db["Things"]
1240 if animate_in_fov(world_db["Things"][id], maplength)
1241 if get_map_score(world_db["Things"][id]["T_POSY"] * maplength
1242 + world_db["Things"][id]["T_POSX"])]
1244 [set_map_score_at_thingpos(id, 65535)
1245 for id in world_db["Things"]
1246 if animate_in_fov(world_db["Things"][id], maplength)]
1248 def rand_target_dir(neighbors, cmp, dirs):
1251 for i in range(len(dirs)):
1252 if cmp == neighbors[i]:
1253 candidates.append(dirs[i])
1255 return candidates[rand.next() % n_candidates] if n_candidates else 0
1257 def get_neighbor_scores(dirs, eye_pos):
1259 if libpr.ready_neighbor_scores(eye_pos):
1260 raise RuntimeError("No score map allocated for " +
1261 "ready_neighbor_scores.()")
1262 for i in range(len(dirs)):
1263 scores.append(libpr.get_neighbor_score(i))
1266 def get_dir_from_neighbors():
1267 dir_to_target = False
1269 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1270 neighbors = get_neighbor_scores(dirs, eye_pos)
1271 minmax_start = 0 if "f" == filter else 65535 - 1
1272 minmax_neighbor = minmax_start
1273 for i in range(len(dirs)):
1274 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1275 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1276 or ("f" != filter and minmax_neighbor > neighbors[i]):
1277 minmax_neighbor = neighbors[i]
1278 if minmax_neighbor != minmax_start:
1279 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1281 distance = get_map_score(eye_pos)
1282 fear_distance = world_db["MAP_LENGTH"]
1283 if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
1284 fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
1286 if not dir_to_target:
1287 if attack_distance >= distance:
1288 dir_to_target = rand_target_dir(neighbors,
1290 elif fear_distance >= distance:
1291 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1293 world_db["ThingActions"][id]["TA_NAME"]
1296 elif dir_to_target and fear_distance < distance:
1298 return dir_to_target
1300 dir_to_target = False
1302 run_i = 9 + 1 if "s" == filter else 1
1303 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1306 mem_depth_c = b'9' if b' ' == mem_depth_c \
1307 else bytes([mem_depth_c[0] - 1])
1308 if libpr.dijkstra_map():
1309 raise RuntimeError("No score map allocated for dijkstra_map().")
1310 dir_to_target = get_dir_from_neighbors()
1311 libpr.free_score_map()
1312 if dir_to_target and str == type(dir_to_target):
1313 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1314 if world_db["ThingActions"][id]["TA_NAME"]
1316 t["T_ARGUMENT"] = ord(dir_to_target)
1317 return dir_to_target
1320 def standing_on_food(t):
1321 """Return True/False whether t is standing on healthy consumable."""
1322 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1323 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1324 if not world_db["Things"][id]["carried"]
1325 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1326 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1327 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1328 ["TT_TOOL"] == "food"
1329 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1330 ["TT_TOOLPOWER"] > eat_cost]:
1335 def get_inventory_slot_to_consume(t):
1336 """Return invent. slot of healthiest consumable(if any healthy),else -1."""
1340 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1341 for id in t["T_CARRIES"]:
1342 type = world_db["Things"][id]["T_TYPE"]
1343 if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1344 and world_db["ThingTypes"][type]["TT_TOOLPOWER"]:
1345 nutvalue = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1346 tmp_cmp = abs(t["T_SATIATION"] + nutvalue - eat_cost)
1347 if (cmp_food < 0 and tmp_cmp < abs(t["T_SATIATION"])) \
1348 or tmp_cmp < cmp_food:
1356 """Determine next command/argment for actor t via AI algorithms."""
1357 # 7DRL add: Don't pick up or search things when inventory is full.
1358 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1359 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1360 if get_dir_to_target(t, "f"):
1362 sel = get_inventory_slot_to_consume(t)
1364 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1365 if world_db["ThingActions"][id]["TA_NAME"]
1367 t["T_ARGUMENT"] = sel
1368 elif standing_on_food(t):
1369 if (len(t["T_CARRIES"]) < # #
1370 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1371 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1372 if world_db["ThingActions"][id]["TA_NAME"]
1375 going_to_known_food_spot = get_dir_to_target(t, "c")
1376 if not going_to_known_food_spot:
1377 aiming_for_walking_food = get_dir_to_target(t, "a")
1378 if not aiming_for_walking_food:
1379 get_dir_to_target(t, "s")
1383 """Run game world and its inhabitants until new player input expected."""
1384 # 7DRL: effort of move action is TA_EFFORT / sqrt(TT_LIFEPOINTS)
1386 whilebreaker = False
1387 while world_db["Things"][0]["T_LIFEPOINTS"]:
1388 proliferable_map = world_db["MAP"][:]
1389 for id in [id for id in world_db["Things"]
1390 if not world_db["Things"][id]["carried"]]:
1391 y = world_db["Things"][id]["T_POSY"]
1392 x = world_db["Things"][id]["T_POSX"]
1393 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1394 for id in [id for id in world_db["Things"]]: # Only what's from start!
1395 if not id in world_db["Things"] or \
1396 world_db["Things"][id]["carried"]: # May have been consumed or
1397 continue # picked up during turn …
1398 Thing = world_db["Things"][id]
1399 if Thing["T_LIFEPOINTS"]:
1400 if not Thing["T_COMMAND"]:
1401 update_map_memory(Thing)
1408 if Thing["T_LIFEPOINTS"]:
1409 Thing["T_PROGRESS"] += 1
1410 taid = [a for a in world_db["ThingActions"]
1411 if a == Thing["T_COMMAND"]][0]
1412 ThingAction = world_db["ThingActions"][taid]
1413 #if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1414 effort = ThingAction["TA_EFFORT"] # #
1415 if ThingAction["TA_NAME"] == "move": # #
1416 type = Thing["T_TYPE"] # #
1417 max_hp = (world_db["ThingTypes"][type] # #
1418 ["TT_LIFEPOINTS"]) # #
1419 effort = int(effort / math.sqrt(max_hp)) # #
1420 if Thing["T_PROGRESS"] == effort: # #
1421 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1422 Thing["T_COMMAND"] = 0
1423 Thing["T_PROGRESS"] = 0
1424 thingproliferation(Thing, proliferable_map)
1427 world_db["TURN"] += 1
1430 def new_Thing(type, pos=(0, 0)):
1431 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1433 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1438 "T_PLAYERDROP": 0, # #
1446 "T_MEMDEPTHMAP": False,
1449 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1450 build_fov_map(thing)
1454 def id_setter(id, category, id_store=False, start_at_1=False):
1455 """Set ID of object of category to manipulate ID unused? Create new one.
1457 The ID is stored as id_store.id (if id_store is set). If the integer of the
1458 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1459 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1460 always returned when no new object is created, otherwise the new object's
1463 min = 0 if start_at_1 else -1
1465 id = integer_test(id, min)
1467 if id in world_db[category]:
1472 if (start_at_1 and 0 == id) \
1473 or ((not start_at_1) and (id < 0)):
1474 id = 0 if start_at_1 else -1
1477 if id not in world_db[category]:
1485 """Send PONG line to server output file."""
1486 strong_write(io_db["file_out"], "PONG\n")
1490 """Abort server process."""
1491 if None == opts.replay:
1492 if world_db["WORLD_ACTIVE"]:
1494 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1495 raise SystemExit("received QUIT command")
1498 def command_thingshere(str_y, str_x):
1499 """Write to out file list of Things known to player at coordinate y, x."""
1500 # 7DRL: terrain, too
1501 if world_db["WORLD_ACTIVE"]:
1502 y = integer_test(str_y, 0, 255)
1503 x = integer_test(str_x, 0, 255)
1504 length = world_db["MAP_LENGTH"]
1505 if None != y and None != x and y < length and x < length:
1506 pos = (y * world_db["MAP_LENGTH"]) + x
1507 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1508 pos = y * world_db["MAP_LENGTH"] + x; # #
1509 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1510 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1511 for id in world_db["Things"]
1512 if not world_db["Things"][id]["carried"]
1513 if world_db["Things"][id]["T_TYPE"] == tid
1514 if y == world_db["Things"][id]["T_POSY"]
1515 if x == world_db["Things"][id]["T_POSX"]]:
1516 type = world_db["Things"][id]["T_TYPE"]
1517 name = world_db["ThingTypes"][type]["TT_NAME"]
1518 strong_write(io_db["file_out"], name + "\n")
1520 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1521 for mt in world_db["Things"][0]["T_MEMTHING"]
1522 if mt[0] == tid if y == mt[1] if x == mt[2]]:
1523 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1524 strong_write(io_db["file_out"], name + "\n")
1525 if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"): # #
1526 name = "(terrain: SEA)" # #
1527 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."): # #
1528 name = "(terrain: EARTH)" # #
1529 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"): # #
1530 name = "(terrain: SOIL)" # #
1531 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"): # #
1532 name = "(terrain: TREE)" # #
1533 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"): # #
1534 name = "(terrain: WALL)" # #
1535 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"): # #
1536 name = "(terrain: ALTAR)" # #
1539 strong_write(io_db["file_out"], name + "\n") # #
1540 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1542 print("Ignoring: Invalid map coordinates.")
1544 print("Ignoring: Command only works on existing worlds.")
1547 def play_commander(action, args=False):
1548 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1550 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1555 if action == "drop":
1556 t = world_db["Things"][0]
1557 if 0 == len(t["T_CARRIES"]):
1558 log("You have nothing to drop in your inventory.")
1561 elif action == "pick_up":
1562 t = world_db["Things"][0]
1563 ids = [id for id in world_db["Things"] if id
1564 if not world_db["Things"][id]["carried"]
1565 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1566 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
1568 log("No object to pick up.")
1570 used_slots = len(t["T_CARRIES"]) # #
1571 if used_slots >= world_db["ThingTypes"][t["T_TYPE"]] \
1573 log("Can't pick up: No storage room to carry anything more.")
1576 elif action == "move":
1577 t = world_db["Things"][0]
1579 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
1580 t["T_POSY"], t["T_POSX"])
1581 if 1 == move_result[0]:
1582 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
1583 if (ord("X") == world_db["MAP"][pos] # #
1584 or ord("|") == world_db["MAP"][pos]): # #
1585 carries_axe = False # #
1586 for id in t["T_CARRIES"]: # #
1587 type = world_db["Things"][id]["T_TYPE"] # #
1588 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe": # #
1589 carries_axe = True # #
1591 if not carries_axe: # #
1592 log("You can't move there.")
1595 log("You can't move there.")
1598 elif action == "use":
1599 t = world_db["Things"][0]
1600 if len(t["T_CARRIES"]):
1601 id = t["T_CARRIES"][t["T_ARGUMENT"]]
1602 type = world_db["Things"][id]["T_TYPE"]
1603 if world_db["ThingTypes"][type]["TT_TOOL"] == "axe":
1604 log("To use this item for chopping, move towards a tree "
1605 + "while carrying it in your inventory.")
1607 elif world_db["ThingTypes"][type]["TT_TOOL"] == "carpentry":
1608 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1609 if (world_db["MAP"][pos] == ord("X") # #
1610 or world_db["MAP"][pos] == ord("|")): # #
1611 log("Can't build when standing on barrier.") # #
1613 for id in [id for id in world_db["Things"]
1614 if not world_db["Things"][id] == t
1615 if not world_db["Things"][id]["carried"]
1616 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1617 if world_db["Things"][id]["T_POSX"] ==t["T_POSX"]]:
1618 log("Can't build when standing objects.") # #
1620 for id in t["T_CARRIES"]: # #
1621 type_tool = world_db["Things"][id]["T_TYPE"] # #
1622 if (world_db["ThingTypes"][type_tool]["TT_TOOL"] # #
1623 == "carpentry"): # #
1626 for id in t["T_CARRIES"]: # #
1627 type_material = world_db["Things"][id]["T_TYPE"] # #
1628 if (world_db["ThingTypes"][type_material]["TT_TOOL"] # #
1632 if wood_id == None: # #
1633 log("You can't use a " # #
1634 + world_db["ThingTypes"][type_tool]["TT_NAME"] # #
1635 + " without some wood in your inventory.") # #
1637 elif world_db["ThingTypes"][type]["TT_TOOL"] == "fertilizer": # #
1638 pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1639 if not world_db["MAP"][pos] == ord("."):
1640 log("Can only make soil out of non-soil earth.")
1642 elif world_db["ThingTypes"][type]["TT_TOOL"] == "wood": # #
1643 log("To use wood, you need a carpentry tool.") # #
1646 log("You have nothing to use in your inventory.")
1650 id = [x for x in world_db["ThingActions"]
1651 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1652 world_db["Things"][0]["T_COMMAND"] = id
1655 def set_command_and_argument_int(str_arg):
1656 val = integer_test(str_arg, 0, 255)
1658 world_db["Things"][0]["T_ARGUMENT"] = val
1661 def set_command_and_argument_movestring(str_arg):
1662 if str_arg in directions_db:
1663 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1666 print("Ignoring: Argument must be valid direction string.")
1668 if action == "move":
1669 return set_command_and_argument_movestring
1671 return set_command_and_argument_int
1676 def command_seedrandomness(seed_string):
1677 """Set rand seed to int(seed_string)."""
1678 val = integer_test(seed_string, 0, 4294967295)
1683 def command_makeworld(seed_string):
1684 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1686 Seed rand with seed. Do more only with a "wait" ThingAction and
1687 world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1688 world_db["Things"] emptied, call make_map() and set
1689 world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1690 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1691 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1692 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1696 # def free_pos(plant=False):
1697 def free_pos(plant=False): # #
1700 err = "Space to put thing on too hard to find. Map too small?"
1702 y = rand.next() % world_db["MAP_LENGTH"]
1703 x = rand.next() % world_db["MAP_LENGTH"]
1704 pos = y * world_db["MAP_LENGTH"] + x;
1706 and "." == chr(world_db["MAP"][pos])) \
1707 or ":" == chr(world_db["MAP"][pos]): # #
1711 raise SystemExit(err)
1712 # Replica of C code, wrongly ignores animatedness of new Thing.
1713 pos_clear = (0 == len([id for id in world_db["Things"]
1714 if world_db["Things"][id]["T_LIFEPOINTS"]
1715 if world_db["Things"][id]["T_POSY"] == y
1716 if world_db["Things"][id]["T_POSX"] == x]))
1721 val = integer_test(seed_string, 0, 4294967295)
1725 player_will_be_generated = False
1726 playertype = world_db["PLAYER_TYPE"]
1727 for ThingType in world_db["ThingTypes"]:
1728 if playertype == ThingType:
1729 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1730 player_will_be_generated = True
1732 if not player_will_be_generated:
1733 print("Ignoring: No player type with start number >0 defined.")
1736 for ThingAction in world_db["ThingActions"]:
1737 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1740 print("Ignoring: No thing action with name 'wait' defined.")
1742 for name in specials: # #
1743 if world_db[name] not in world_db["ThingTypes"]: # #
1744 print("Ignoring: No valid " + name + " set.") # #
1746 world_db["Things"] = {}
1748 world_db["WORLD_ACTIVE"] = 1
1749 world_db["TURN"] = 1
1750 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1751 id = id_setter(-1, "Things")
1752 world_db["Things"][id] = new_Thing(playertype, free_pos())
1753 if not world_db["Things"][0]["fovmap"]:
1754 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1755 world_db["Things"][0]["fovmap"] = empty_fovmap
1756 update_map_memory(world_db["Things"][0])
1757 for type in world_db["ThingTypes"]:
1758 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1759 if type != playertype:
1760 id = id_setter(-1, "Things")
1761 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"] # #
1762 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1763 strong_write(io_db["file_out"], "NEW_WORLD\n")
1767 def command_maplength(maplength_string):
1768 """Redefine map length. Invalidate map, therefore lose all things on it."""
1769 val = integer_test(maplength_string, 1, 256)
1771 world_db["MAP_LENGTH"] = val
1772 world_db["MAP"] = False
1773 set_world_inactive()
1774 world_db["Things"] = {}
1775 libpr.set_maplength(val)
1778 def command_worldactive(worldactive_string):
1779 """Toggle world_db["WORLD_ACTIVE"] if possible.
1781 An active world can always be set inactive. An inactive world can only be
1782 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1783 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1784 Also call log_help().
1786 # 7DRL: altar must be on map, and specials must be set for active world.
1787 val = integer_test(worldactive_string, 0, 1)
1789 if 0 != world_db["WORLD_ACTIVE"]:
1791 set_world_inactive()
1793 print("World already active.")
1794 elif 0 == world_db["WORLD_ACTIVE"]:
1796 for ThingAction in world_db["ThingActions"]:
1797 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1800 player_exists = False
1801 for Thing in world_db["Things"]:
1803 player_exists = True
1805 specials_set = True # #
1806 for name in specials: # #
1807 if world_db[name] not in world_db["ThingTypes"]: # #
1808 specials_set = False # #
1809 altar_found = False # #
1810 if world_db["MAP"]: # #
1811 pos = world_db["MAP"].find(b'_') # #
1813 y = int(pos / world_db["MAP_LENGTH"]) # #
1814 x = pos % world_db["MAP_LENGTH"] # #
1815 world_db["altar"] = (y, x) # #
1816 altar_found = True # #
1817 if wait_exists and player_exists and world_db["MAP"] \
1818 and specials_set: # #
1819 for id in world_db["Things"]:
1820 if world_db["Things"][id]["T_LIFEPOINTS"]:
1821 build_fov_map(world_db["Things"][id])
1823 update_map_memory(world_db["Things"][id], False)
1824 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1825 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1826 world_db["Things"][0]["fovmap"] = empty_fovmap
1827 world_db["WORLD_ACTIVE"] = 1
1830 print("Ignoring: Not all conditions for world activation met.")
1833 def specialtypesetter(name): # #
1834 """Setter world_db[name], deactivating world if set int no ThingType."""
1835 def helper(str_int):
1836 val = integer_test(str_int, 0)
1838 world_db[name] = val
1839 if world_db["WORLD_ACTIVE"] \
1840 and world_db[name] not in world_db["ThingTypes"]:
1841 world_db["WORLD_ACTIVE"] = 0
1842 print(name + " fits no known ThingType, deactivating world.")
1846 def test_for_id_maker(object, category):
1847 """Return decorator testing for object having "id" attribute."""
1850 if hasattr(object, "id"):
1853 print("Ignoring: No " + category +
1854 " defined to manipulate yet.")
1859 def command_tid(id_string):
1860 """Set ID of Thing to manipulate. ID unused? Create new one.
1862 Default new Thing's type to the first available ThingType, others: zero.
1864 id = id_setter(id_string, "Things", command_tid)
1866 if world_db["ThingTypes"] == {}:
1867 print("Ignoring: No ThingType to settle new Thing in.")
1869 type = list(world_db["ThingTypes"].keys())[0]
1870 world_db["Things"][id] = new_Thing(type)
1873 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1877 def command_tcommand(str_int):
1878 """Set T_COMMAND of selected Thing."""
1879 val = integer_test(str_int, 0)
1881 if 0 == val or val in world_db["ThingActions"]:
1882 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1884 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1888 def command_ttype(str_int):
1889 """Set T_TYPE of selected Thing."""
1890 val = integer_test(str_int, 0)
1892 if val in world_db["ThingTypes"]:
1893 world_db["Things"][command_tid.id]["T_TYPE"] = val
1895 print("Ignoring: ThingType ID belongs to no known ThingType.")
1899 def command_tcarries(str_int):
1900 """Append int(str_int) to T_CARRIES of selected Thing.
1902 The ID int(str_int) must not be of the selected Thing, and must belong to a
1903 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1905 val = integer_test(str_int, 0)
1907 if val == command_tid.id:
1908 print("Ignoring: Thing cannot carry itself.")
1909 elif val in world_db["Things"] \
1910 and not world_db["Things"][val]["carried"]:
1911 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1912 world_db["Things"][val]["carried"] = True
1914 print("Ignoring: Thing not available for carrying.")
1915 # Note that the whole carrying structure is different from the C version:
1916 # Carried-ness is marked by a "carried" flag, not by Things containing
1917 # Things internally.
1921 def command_tmemthing(str_t, str_y, str_x):
1922 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1924 The type must fit to an existing ThingType, and the position into the map.
1926 type = integer_test(str_t, 0)
1927 posy = integer_test(str_y, 0, 255)
1928 posx = integer_test(str_x, 0, 255)
1929 if None != type and None != posy and None != posx:
1930 if type not in world_db["ThingTypes"] \
1931 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1932 print("Ignoring: Illegal value for thing type or position.")
1934 memthing = (type, posy, posx)
1935 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1938 def setter_map(maptype):
1939 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1941 If no map of maptype exists yet, initialize it with ' ' bytes first.
1944 def valid_map_line(str_int, mapline):
1945 val = integer_test(str_int, 0, 255)
1947 if val >= world_db["MAP_LENGTH"]:
1948 print("Illegal value for map line number.")
1949 elif len(mapline) != world_db["MAP_LENGTH"]:
1950 print("Map line length is unequal map width.")
1955 def nonThingMap_helper(str_int, mapline):
1956 val = valid_map_line(str_int, mapline)
1958 length = world_db["MAP_LENGTH"]
1959 if not world_db["MAP"]:
1960 map = bytearray(b' ' * (length ** 2))
1962 map = world_db["MAP"]
1963 map[val * length:(val * length) + length] = mapline.encode()
1964 if not world_db["MAP"]:
1965 world_db["MAP"] = map
1968 def ThingMap_helper(str_int, mapline):
1969 val = valid_map_line(str_int, mapline)
1971 length = world_db["MAP_LENGTH"]
1972 if not world_db["Things"][command_tid.id][maptype]:
1973 map = bytearray(b' ' * (length ** 2))
1975 map = world_db["Things"][command_tid.id][maptype]
1976 map[val * length:(val * length) + length] = mapline.encode()
1977 if not world_db["Things"][command_tid.id][maptype]:
1978 world_db["Things"][command_tid.id][maptype] = map
1980 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
1983 def setter_tpos(axis):
1984 """Generate setter for T_POSX or T_POSY of selected Thing.
1986 If world is active, rebuilds animate things' fovmap, player's memory map.
1989 def helper(str_int):
1990 val = integer_test(str_int, 0, 255)
1992 if val < world_db["MAP_LENGTH"]:
1993 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1994 if world_db["WORLD_ACTIVE"] \
1995 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1996 build_fov_map(world_db["Things"][command_tid.id])
1997 if 0 == command_tid.id:
1998 update_map_memory(world_db["Things"][command_tid.id])
2000 print("Ignoring: Position is outside of map.")
2004 def command_ttid(id_string):
2005 """Set ID of ThingType to manipulate. ID unused? Create new one.
2007 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
2010 id = id_setter(id_string, "ThingTypes", command_ttid)
2012 world_db["ThingTypes"][id] = {
2013 "TT_NAME": "(none)",
2016 "TT_PROLIFERATE": 0,
2017 "TT_START_NUMBER": 0,
2018 "TT_STORAGE": 0, # #
2025 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
2029 def command_ttname(name):
2030 """Set TT_NAME of selected ThingType."""
2031 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
2035 def command_tttool(name):
2036 """Set TT_TOOL of selected ThingType."""
2037 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
2041 def command_ttsymbol(char):
2042 """Set TT_SYMBOL of selected ThingType. """
2044 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
2046 print("Ignoring: Argument must be single character.")
2050 def command_ttcorpseid(str_int):
2051 """Set TT_CORPSE_ID of selected ThingType."""
2052 val = integer_test(str_int, 0)
2054 if val in world_db["ThingTypes"]:
2055 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
2057 print("Ignoring: Corpse ID belongs to no known ThignType.")
2060 def command_taid(id_string):
2061 """Set ID of ThingAction to manipulate. ID unused? Create new one.
2063 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
2065 id = id_setter(id_string, "ThingActions", command_taid, True)
2067 world_db["ThingActions"][id] = {
2073 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
2076 @test_ThingAction_id
2077 def command_taname(name):
2078 """Set TA_NAME of selected ThingAction.
2080 The name must match a valid thing action function. If after the name
2081 setting no ThingAction with name "wait" remains, call set_world_inactive().
2083 if name == "wait" or name == "move" or name == "use" or name == "drop" \
2084 or name == "pick_up":
2085 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
2086 if 1 == world_db["WORLD_ACTIVE"]:
2087 wait_defined = False
2088 for id in world_db["ThingActions"]:
2089 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
2092 if not wait_defined:
2093 set_world_inactive()
2095 print("Ignoring: Invalid action name.")
2096 # In contrast to the original,naming won't map a function to a ThingAction.
2100 """Call ai() on player Thing, then turn_over()."""
2101 ai(world_db["Things"][0])
2105 """Commands database.
2107 Map command start tokens to ([0]) number of expected command arguments, ([1])
2108 the command's meta-ness (i.e. is it to be written to the record file, is it to
2109 be ignored in replay mode if read from server input file), and ([2]) a function
2113 "QUIT": (0, True, command_quit),
2114 "PING": (0, True, command_ping),
2115 "THINGS_HERE": (2, True, command_thingshere),
2116 "MAKE_WORLD": (1, False, command_makeworld),
2117 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
2118 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
2119 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
2120 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
2121 "MAP_LENGTH": (1, False, command_maplength),
2122 "WORLD_ACTIVE": (1, False, command_worldactive),
2123 "MAP": (2, False, setter_map("MAP")),
2124 "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)), # #
2125 "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")), # #
2126 "PLANT_0": (1, False, specialtypesetter("PLANT_0")), # #
2127 "PLANT_1": (1, False, specialtypesetter("PLANT_1")), # #
2128 "LUMBER": (1, False, specialtypesetter("LUMBER")), # #
2129 "TOOL_0": (1, False, specialtypesetter("TOOL_0")), # #
2130 "TOOL_1": (1, False, specialtypesetter("TOOL_1")), # #
2131 "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)), # #
2132 "TA_ID": (1, False, command_taid),
2133 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
2134 "TA_NAME": (1, False, command_taname),
2135 "TT_ID": (1, False, command_ttid),
2136 "TT_NAME": (1, False, command_ttname),
2137 "TT_TOOL": (1, False, command_tttool),
2138 "TT_SYMBOL": (1, False, command_ttsymbol),
2139 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
2140 "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2141 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2143 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2145 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2146 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
2147 "T_ID": (1, False, command_tid),
2148 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2149 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2150 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2151 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2152 "T_COMMAND": (1, False, command_tcommand),
2153 "T_TYPE": (1, False, command_ttype),
2154 "T_CARRIES": (1, False, command_tcarries),
2155 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2156 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2157 "T_MEMTHING": (3, False, command_tmemthing),
2158 "T_POSY": (1, False, setter_tpos("Y")),
2159 "T_POSX": (1, False, setter_tpos("X")),
2160 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
2161 "wait": (0, False, play_commander("wait")),
2162 "move": (1, False, play_commander("move")),
2163 "pick_up": (0, False, play_commander("pick_up")),
2164 "drop": (1, False, play_commander("drop", True)),
2165 "use": (1, False, play_commander("use", True)),
2166 "ai": (0, False, command_ai)
2168 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2171 """World state database. With sane default values. (Randomness is in rand.)"""
2179 "FAVOR_STAGE": 0, # #
2193 """Special type settings."""
2194 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0", "TOOL_1"] # #
2196 """Mapping of direction names to internal direction chars."""
2197 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2198 "west": "s", "north-west": "w", "north-east": "e"}
2200 """File IO database."""
2202 "path_save": "save",
2203 "path_record": "record_save",
2204 "path_worldconf": "confserver/world",
2205 "path_server": "server/",
2206 "path_in": "server/in",
2207 "path_out": "server/out",
2208 "path_worldstate": "server/worldstate",
2209 "tmp_suffix": "_tmp",
2210 "kicked_by_rival": False,
2211 "worldstate_updateable": False
2216 libpr = prep_library()
2217 rand = RandomnessIO()
2218 opts = parse_command_line_arguments()
2220 io_db["path_save"] = opts.savefile
2221 io_db["path_record"] = "record_" + opts.savefile
2224 io_db["verbose"] = True
2225 if None != opts.replay:
2229 except SystemExit as exit:
2230 print("ABORTING: " + exit.args[0])
2232 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")