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 world_db["MAP"][pos] = ord(":")
1006 log("Can only fertilize on unfertilized earth.")
1007 elif world_db["ThingTypes"][type]["TT_TOOL"] == "food":
1008 t["T_CARRIES"].remove(id)
1009 del world_db["Things"][id]
1010 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1011 if t == world_db["Things"][0]:
1012 log("You consume this object.")
1013 elif t == world_db["Things"][0]:
1014 log("You try to use this object, but fail.")
1015 elif t == world_db["Things"][0]:
1016 log("You try to use an object, but you own none.")
1019 def thingproliferation(t, prol_map):
1020 """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
1022 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
1023 marked "." in prol_map. If there are several map cell candidates, one is
1026 # 7DRL: success increments God's mood
1027 # 7DRL: Plants (no TT_LIFEPOINTS) proliferate only on ":" ground.
1028 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
1029 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
1031 for dir in [directions_db[key] for key in directions_db]:
1032 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
1033 pos = mv_result[1] * world_db["MAP_LENGTH"] + mv_result[2]
1034 if mv_result[0] and \
1035 (ord(":") == prol_map[pos] # #
1036 or (world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
1037 and ord(".") == prol_map[pos])):
1038 # if mv_result[0] and ord(".") == prol_map[mv_result[1]
1039 # * world_db["MAP_LENGTH"]
1041 candidates.append((mv_result[1], mv_result[2]))
1043 i = rand.next() % len(candidates)
1044 id = id_setter(-1, "Things")
1045 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
1046 world_db["Things"][id] = newT
1047 if (world_db["FAVOR_STAGE"] > 0 # #
1048 and t["T_TYPE"] == world_db["PLANT_0"]): # #
1049 world_db["GOD_FAVOR"] += 5 # #
1050 elif t["T_TYPE"] == world_db["PLANT_1"]: # #
1051 world_db["GOD_FAVOR"] += 25 # #
1055 """If t's HP < max, increment them if well-nourished, maybe waiting."""
1056 # 7DRL: Successful heals increment God's mood.
1057 if t["T_LIFEPOINTS"] < \
1058 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
1059 wait_id = [id for id in world_db["ThingActions"]
1060 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1061 wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
1062 testval = int(abs(t["T_SATIATION"]) / wait_divider)
1063 if (testval <= 1 or 1 == (rand.next() % testval)):
1064 t["T_LIFEPOINTS"] += 1
1065 if t == world_db["Things"][0]:
1069 def hunger_per_turn(type_id):
1070 """The amount of satiation score lost per turn for things of given type."""
1071 return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
1075 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
1076 if t["T_SATIATION"] > -32768:
1077 #max_hp = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]
1078 t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"]) # int(math.sqrt(max_hp))
1079 if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
1080 if t == world_db["Things"][0]:
1081 if t["T_SATIATION"] < 0:
1082 log("You suffer from hunger.")
1084 log("You suffer from over-eating.")
1085 decrement_lifepoints(t)
1088 def get_dir_to_target(t, filter):
1089 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
1091 The path-wise nearest target is chosen, via the shortest available path.
1092 Target must not be t. On succcess, return positive value, else False.
1094 "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
1095 is, and its corpse would be healthy food for t
1096 "f": move away from an enemy – any visible actor whose thing type has more
1097 TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
1098 food – if it is closer than n steps, where n will shrink as t's hunger
1099 grows; if enemy is too close, move towards (attack) the enemy instead;
1100 if no fleeing is possible, nor attacking useful, wait; don't tread on
1101 non-enemies for fleeing
1102 "c": Thing in memorized map is consumable of sufficient nutrition for t
1103 "s": memory map cell with greatest-reachable degree of unexploredness
1106 def zero_score_map_where_char_on_memdepthmap(c):
1107 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1108 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1109 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
1110 # set_map_score(i, 0)
1111 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
1112 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
1113 raise RuntimeError("No score map allocated for "
1114 "zero_score_map_where_char_on_memdepthmap().")
1116 def set_map_score_at_thingpos(id, score):
1117 pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
1118 + world_db["Things"][id]["T_POSX"]
1119 set_map_score(pos, score)
1121 def set_map_score(pos, score):
1122 test = libpr.set_map_score(pos, score)
1124 raise RuntimeError("No score map allocated for set_map_score().")
1126 def get_map_score(pos):
1127 result = libpr.get_map_score(pos)
1129 raise RuntimeError("No score map allocated for get_map_score().")
1132 def animate_in_fov(Thing, maplength): # maplength needed for optimization?
1133 if not Thing["T_LIFEPOINTS"] or Thing["carried"] or Thing == t:
1135 pos = Thing["T_POSY"] * maplength + Thing["T_POSX"]
1136 if 118 == t["fovmap"][pos]: # optimization: 118 = ord("v")
1139 def good_attack_target(v):
1140 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1141 type = world_db["ThingTypes"][v["T_TYPE"]]
1142 type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
1143 if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
1144 and type_corpse["TT_TOOL"] == "food" \
1145 and type_corpse["TT_TOOLPOWER"] > eat_cost:
1149 def good_flee_target(m):
1150 own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
1151 corpse_type = world_db["ThingTypes"][own_corpse_id]
1152 targetness = 0 if corpse_type["TT_TOOL"] != "food" \
1153 else corpse_type["TT_TOOLPOWER"]
1154 type = world_db["ThingTypes"][m["T_TYPE"]]
1155 if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
1156 and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
1161 maplength = world_db["MAP_LENGTH"]
1162 if t["fovmap"] and "a" == filter:
1163 for id in world_db["Things"]:
1164 if animate_in_fov(world_db["Things"][id], maplength):
1165 if good_attack_target(world_db["Things"][id]):
1167 elif t["fovmap"] and "f" == filter:
1168 for id in world_db["Things"]:
1169 if animate_in_fov(world_db["Things"][id], maplength):
1170 if good_flee_target(world_db["Things"][id]):
1172 elif t["T_MEMMAP"] and "c" == filter:
1173 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1174 ord_blank = ord(" ")
1175 for mt in t["T_MEMTHING"]:
1176 if ord_blank != t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
1178 (t != world_db["Things"][0] or \
1179 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food"
1180 and world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"]
1185 def set_cells_passable_on_memmap_to_65534_on_scoremap():
1186 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
1187 # ord_dot = ord(".")
1188 # memmap = t["T_MEMMAP"]
1189 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
1190 # if ord_dot == memmap[i]]:
1191 # set_map_score(i, 65534) # i.e. 65535-1
1192 map = c_pointer_to_bytearray(t["T_MEMMAP"])
1193 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
1194 raise RuntimeError("No score map allocated for "
1195 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
1197 def init_score_map():
1198 test = libpr.init_score_map()
1200 raise RuntimeError("Malloc error in init_score_map().")
1201 set_cells_passable_on_memmap_to_65534_on_scoremap()
1202 maplength = world_db["MAP_LENGTH"]
1204 [set_map_score_at_thingpos(id, 0)
1205 for id in world_db["Things"]
1206 if animate_in_fov(world_db["Things"][id], maplength)
1207 if good_attack_target(world_db["Things"][id])]
1209 [set_map_score_at_thingpos(id, 0)
1210 for id in world_db["Things"]
1211 if animate_in_fov(world_db["Things"][id], maplength)
1212 if good_flee_target(world_db["Things"][id])]
1214 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1215 ord_blank = ord(" ")
1216 [set_map_score(mt[1] * maplength + mt[2], 0)
1217 for mt in t["T_MEMTHING"]
1218 if ord_blank != t["T_MEMMAP"][mt[1] * maplength + mt[2]]
1219 if t != world_db["Things"][0] or
1220 (world_db["ThingTypes"][mt[0]]["TT_TOOL"] == "food" and
1221 world_db["ThingTypes"][mt[0]]["TT_TOOLPOWER"] > eat_cost)]
1223 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
1225 [set_map_score_at_thingpos(id, 65535)
1226 for id in world_db["Things"]
1227 if animate_in_fov(world_db["Things"][id], maplength)
1228 if get_map_score(world_db["Things"][id]["T_POSY"] * maplength
1229 + world_db["Things"][id]["T_POSX"])]
1231 [set_map_score_at_thingpos(id, 65535)
1232 for id in world_db["Things"]
1233 if animate_in_fov(world_db["Things"][id], maplength)]
1235 def rand_target_dir(neighbors, cmp, dirs):
1238 for i in range(len(dirs)):
1239 if cmp == neighbors[i]:
1240 candidates.append(dirs[i])
1242 return candidates[rand.next() % n_candidates] if n_candidates else 0
1244 def get_neighbor_scores(dirs, eye_pos):
1246 if libpr.ready_neighbor_scores(eye_pos):
1247 raise RuntimeError("No score map allocated for " +
1248 "ready_neighbor_scores.()")
1249 for i in range(len(dirs)):
1250 scores.append(libpr.get_neighbor_score(i))
1253 def get_dir_from_neighbors():
1254 dir_to_target = False
1256 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
1257 neighbors = get_neighbor_scores(dirs, eye_pos)
1258 minmax_start = 0 if "f" == filter else 65535 - 1
1259 minmax_neighbor = minmax_start
1260 for i in range(len(dirs)):
1261 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
1262 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
1263 or ("f" != filter and minmax_neighbor > neighbors[i]):
1264 minmax_neighbor = neighbors[i]
1265 if minmax_neighbor != minmax_start:
1266 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
1268 distance = get_map_score(eye_pos)
1269 fear_distance = world_db["MAP_LENGTH"]
1270 if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
1271 fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
1273 if not dir_to_target:
1274 if attack_distance >= distance:
1275 dir_to_target = rand_target_dir(neighbors,
1277 elif fear_distance >= distance:
1278 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1280 world_db["ThingActions"][id]["TA_NAME"]
1283 elif dir_to_target and fear_distance < distance:
1285 return dir_to_target
1287 dir_to_target = False
1289 run_i = 9 + 1 if "s" == filter else 1
1290 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1293 mem_depth_c = b'9' if b' ' == mem_depth_c \
1294 else bytes([mem_depth_c[0] - 1])
1295 if libpr.dijkstra_map():
1296 raise RuntimeError("No score map allocated for dijkstra_map().")
1297 dir_to_target = get_dir_from_neighbors()
1298 libpr.free_score_map()
1299 if dir_to_target and str == type(dir_to_target):
1300 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1301 if world_db["ThingActions"][id]["TA_NAME"]
1303 t["T_ARGUMENT"] = ord(dir_to_target)
1304 return dir_to_target
1307 def standing_on_food(t):
1308 """Return True/False whether t is standing on healthy consumable."""
1309 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1310 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1311 if not world_db["Things"][id]["carried"]
1312 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1313 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1314 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1315 ["TT_TOOL"] == "food"
1316 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1317 ["TT_TOOLPOWER"] > eat_cost]:
1322 def get_inventory_slot_to_consume(t):
1323 """Return invent. slot of healthiest consumable(if any healthy),else -1."""
1327 eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
1328 for id in t["T_CARRIES"]:
1329 type = world_db["Things"][id]["T_TYPE"]
1330 if world_db["ThingTypes"][type]["TT_TOOL"] == "food" \
1331 and world_db["ThingTypes"][type]["TT_TOOLPOWER"]:
1332 nutvalue = world_db["ThingTypes"][type]["TT_TOOLPOWER"]
1333 tmp_cmp = abs(t["T_SATIATION"] + nutvalue - eat_cost)
1334 if (cmp_food < 0 and tmp_cmp < abs(t["T_SATIATION"])) \
1335 or tmp_cmp < cmp_food:
1343 """Determine next command/argment for actor t via AI algorithms."""
1344 # 7DRL add: Don't pick up or search things when inventory is full.
1345 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1346 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1347 if get_dir_to_target(t, "f"):
1349 sel = get_inventory_slot_to_consume(t)
1351 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1352 if world_db["ThingActions"][id]["TA_NAME"]
1354 t["T_ARGUMENT"] = sel
1355 elif standing_on_food(t):
1356 if (len(t["T_CARRIES"]) < # #
1357 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1358 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1359 if world_db["ThingActions"][id]["TA_NAME"]
1362 going_to_known_food_spot = get_dir_to_target(t, "c")
1363 if not going_to_known_food_spot:
1364 aiming_for_walking_food = get_dir_to_target(t, "a")
1365 if not aiming_for_walking_food:
1366 get_dir_to_target(t, "s")
1370 """Run game world and its inhabitants until new player input expected."""
1371 # 7DRL: effort of move action is TA_EFFORT / sqrt(TT_LIFEPOINTS)
1373 whilebreaker = False
1374 while world_db["Things"][0]["T_LIFEPOINTS"]:
1375 proliferable_map = world_db["MAP"][:]
1376 for id in [id for id in world_db["Things"]
1377 if not world_db["Things"][id]["carried"]]:
1378 y = world_db["Things"][id]["T_POSY"]
1379 x = world_db["Things"][id]["T_POSX"]
1380 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord("X")
1381 for id in [id for id in world_db["Things"]]: # Only what's from start!
1382 if not id in world_db["Things"] or \
1383 world_db["Things"][id]["carried"]: # May have been consumed or
1384 continue # picked up during turn …
1385 Thing = world_db["Things"][id]
1386 if Thing["T_LIFEPOINTS"]:
1387 if not Thing["T_COMMAND"]:
1388 update_map_memory(Thing)
1395 if Thing["T_LIFEPOINTS"]:
1396 Thing["T_PROGRESS"] += 1
1397 taid = [a for a in world_db["ThingActions"]
1398 if a == Thing["T_COMMAND"]][0]
1399 ThingAction = world_db["ThingActions"][taid]
1400 #if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1401 effort = ThingAction["TA_EFFORT"] # #
1402 if ThingAction["TA_NAME"] == "move": # #
1403 type = Thing["T_TYPE"] # #
1404 max_hp = (world_db["ThingTypes"][type] # #
1405 ["TT_LIFEPOINTS"]) # #
1406 effort = int(effort / math.sqrt(max_hp)) # #
1407 if Thing["T_PROGRESS"] == effort: # #
1408 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1409 Thing["T_COMMAND"] = 0
1410 Thing["T_PROGRESS"] = 0
1411 thingproliferation(Thing, proliferable_map)
1414 world_db["TURN"] += 1
1417 def new_Thing(type, pos=(0, 0)):
1418 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1420 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1425 "T_PLAYERDROP": 0, # #
1433 "T_MEMDEPTHMAP": False,
1436 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1437 build_fov_map(thing)
1441 def id_setter(id, category, id_store=False, start_at_1=False):
1442 """Set ID of object of category to manipulate ID unused? Create new one.
1444 The ID is stored as id_store.id (if id_store is set). If the integer of the
1445 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1446 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1447 always returned when no new object is created, otherwise the new object's
1450 min = 0 if start_at_1 else -1
1452 id = integer_test(id, min)
1454 if id in world_db[category]:
1459 if (start_at_1 and 0 == id) \
1460 or ((not start_at_1) and (id < 0)):
1461 id = 0 if start_at_1 else -1
1464 if id not in world_db[category]:
1472 """Send PONG line to server output file."""
1473 strong_write(io_db["file_out"], "PONG\n")
1477 """Abort server process."""
1478 if None == opts.replay:
1479 if world_db["WORLD_ACTIVE"]:
1481 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1482 raise SystemExit("received QUIT command")
1485 def command_thingshere(str_y, str_x):
1486 """Write to out file list of Things known to player at coordinate y, x."""
1487 # 7DRL: terrain, too
1488 if world_db["WORLD_ACTIVE"]:
1489 y = integer_test(str_y, 0, 255)
1490 x = integer_test(str_x, 0, 255)
1491 length = world_db["MAP_LENGTH"]
1492 if None != y and None != x and y < length and x < length:
1493 pos = (y * world_db["MAP_LENGTH"]) + x
1494 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1495 pos = y * world_db["MAP_LENGTH"] + x; # #
1496 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1497 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
1498 for id in world_db["Things"]
1499 if not world_db["Things"][id]["carried"]
1500 if world_db["Things"][id]["T_TYPE"] == tid
1501 if y == world_db["Things"][id]["T_POSY"]
1502 if x == world_db["Things"][id]["T_POSX"]]:
1503 type = world_db["Things"][id]["T_TYPE"]
1504 name = world_db["ThingTypes"][type]["TT_NAME"]
1505 strong_write(io_db["file_out"], name + "\n")
1507 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
1508 for mt in world_db["Things"][0]["T_MEMTHING"]
1509 if mt[0] == tid if y == mt[1] if x == mt[2]]:
1510 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1511 strong_write(io_db["file_out"], name + "\n")
1512 if world_db["Things"][0]["T_MEMMAP"][pos] == ord("~"): # #
1513 name = "(terrain: SEA)" # #
1514 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("."): # #
1515 name = "(terrain: EARTH)" # #
1516 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord(":"): # #
1517 name = "(terrain: SOIL)" # #
1518 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("X"): # #
1519 name = "(terrain: TREE)" # #
1520 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("|"): # #
1521 name = "(terrain: WALL)" # #
1522 elif world_db["Things"][0]["T_MEMMAP"][pos] == ord("_"): # #
1523 name = "(terrain: ALTAR)" # #
1526 strong_write(io_db["file_out"], name + "\n") # #
1527 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1529 print("Ignoring: Invalid map coordinates.")
1531 print("Ignoring: Command only works on existing worlds.")
1534 def play_commander(action, args=False):
1535 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1537 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1541 id = [x for x in world_db["ThingActions"]
1542 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1543 world_db["Things"][0]["T_COMMAND"] = id
1546 def set_command_and_argument_int(str_arg):
1547 val = integer_test(str_arg, 0, 255)
1549 world_db["Things"][0]["T_ARGUMENT"] = val
1552 def set_command_and_argument_movestring(str_arg):
1553 if str_arg in directions_db:
1554 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1557 print("Ignoring: Argument must be valid direction string.")
1559 if action == "move":
1560 return set_command_and_argument_movestring
1562 return set_command_and_argument_int
1567 def command_seedrandomness(seed_string):
1568 """Set rand seed to int(seed_string)."""
1569 val = integer_test(seed_string, 0, 4294967295)
1574 def command_makeworld(seed_string):
1575 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1577 Seed rand with seed. Do more only with a "wait" ThingAction and
1578 world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
1579 world_db["Things"] emptied, call make_map() and set
1580 world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1581 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1582 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1583 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1587 # def free_pos(plant=False):
1588 def free_pos(plant=False): # #
1591 err = "Space to put thing on too hard to find. Map too small?"
1593 y = rand.next() % world_db["MAP_LENGTH"]
1594 x = rand.next() % world_db["MAP_LENGTH"]
1595 pos = y * world_db["MAP_LENGTH"] + x;
1597 and "." == chr(world_db["MAP"][pos])) \
1598 or ":" == chr(world_db["MAP"][pos]): # #
1602 raise SystemExit(err)
1603 # Replica of C code, wrongly ignores animatedness of new Thing.
1604 pos_clear = (0 == len([id for id in world_db["Things"]
1605 if world_db["Things"][id]["T_LIFEPOINTS"]
1606 if world_db["Things"][id]["T_POSY"] == y
1607 if world_db["Things"][id]["T_POSX"] == x]))
1612 val = integer_test(seed_string, 0, 4294967295)
1616 player_will_be_generated = False
1617 playertype = world_db["PLAYER_TYPE"]
1618 for ThingType in world_db["ThingTypes"]:
1619 if playertype == ThingType:
1620 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1621 player_will_be_generated = True
1623 if not player_will_be_generated:
1624 print("Ignoring: No player type with start number >0 defined.")
1627 for ThingAction in world_db["ThingActions"]:
1628 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1631 print("Ignoring: No thing action with name 'wait' defined.")
1633 for name in specials: # #
1634 if world_db[name] not in world_db["ThingTypes"]: # #
1635 print("Ignoring: No valid " + name + " set.") # #
1637 world_db["Things"] = {}
1639 world_db["WORLD_ACTIVE"] = 1
1640 world_db["TURN"] = 1
1641 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1642 id = id_setter(-1, "Things")
1643 world_db["Things"][id] = new_Thing(playertype, free_pos())
1644 if not world_db["Things"][0]["fovmap"]:
1645 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1646 world_db["Things"][0]["fovmap"] = empty_fovmap
1647 update_map_memory(world_db["Things"][0])
1648 for type in world_db["ThingTypes"]:
1649 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1650 if type != playertype:
1651 id = id_setter(-1, "Things")
1652 plantness = world_db["ThingTypes"][type]["TT_PROLIFERATE"] # #
1653 world_db["Things"][id] = new_Thing(type, free_pos(plantness))
1654 strong_write(io_db["file_out"], "NEW_WORLD\n")
1658 def command_maplength(maplength_string):
1659 """Redefine map length. Invalidate map, therefore lose all things on it."""
1660 val = integer_test(maplength_string, 1, 256)
1662 world_db["MAP_LENGTH"] = val
1663 world_db["MAP"] = False
1664 set_world_inactive()
1665 world_db["Things"] = {}
1666 libpr.set_maplength(val)
1669 def command_worldactive(worldactive_string):
1670 """Toggle world_db["WORLD_ACTIVE"] if possible.
1672 An active world can always be set inactive. An inactive world can only be
1673 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1674 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1675 Also call log_help().
1677 # 7DRL: altar must be on map, and specials must be set for active world.
1678 val = integer_test(worldactive_string, 0, 1)
1680 if 0 != world_db["WORLD_ACTIVE"]:
1682 set_world_inactive()
1684 print("World already active.")
1685 elif 0 == world_db["WORLD_ACTIVE"]:
1687 for ThingAction in world_db["ThingActions"]:
1688 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1691 player_exists = False
1692 for Thing in world_db["Things"]:
1694 player_exists = True
1696 specials_set = True # #
1697 for name in specials: # #
1698 if world_db[name] not in world_db["ThingTypes"]: # #
1699 specials_set = False # #
1700 altar_found = False # #
1701 if world_db["MAP"]: # #
1702 pos = world_db["MAP"].find(b'_') # #
1704 y = int(pos / world_db["MAP_LENGTH"]) # #
1705 x = pos % world_db["MAP_LENGTH"] # #
1706 world_db["altar"] = (y, x) # #
1707 altar_found = True # #
1708 if wait_exists and player_exists and world_db["MAP"] \
1709 and specials_set: # #
1710 for id in world_db["Things"]:
1711 if world_db["Things"][id]["T_LIFEPOINTS"]:
1712 build_fov_map(world_db["Things"][id])
1714 update_map_memory(world_db["Things"][id], False)
1715 if not world_db["Things"][0]["T_LIFEPOINTS"]:
1716 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
1717 world_db["Things"][0]["fovmap"] = empty_fovmap
1718 world_db["WORLD_ACTIVE"] = 1
1721 print("Ignoring: Not all conditions for world activation met.")
1724 def specialtypesetter(name): # #
1725 """Setter world_db[name], deactivating world if set int no ThingType."""
1726 def helper(str_int):
1727 val = integer_test(str_int, 0)
1729 world_db[name] = val
1730 if world_db["WORLD_ACTIVE"] \
1731 and world_db[name] not in world_db["ThingTypes"]:
1732 world_db["WORLD_ACTIVE"] = 0
1733 print(name + " fits no known ThingType, deactivating world.")
1737 def test_for_id_maker(object, category):
1738 """Return decorator testing for object having "id" attribute."""
1741 if hasattr(object, "id"):
1744 print("Ignoring: No " + category +
1745 " defined to manipulate yet.")
1750 def command_tid(id_string):
1751 """Set ID of Thing to manipulate. ID unused? Create new one.
1753 Default new Thing's type to the first available ThingType, others: zero.
1755 id = id_setter(id_string, "Things", command_tid)
1757 if world_db["ThingTypes"] == {}:
1758 print("Ignoring: No ThingType to settle new Thing in.")
1760 type = list(world_db["ThingTypes"].keys())[0]
1761 world_db["Things"][id] = new_Thing(type)
1764 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1768 def command_tcommand(str_int):
1769 """Set T_COMMAND of selected Thing."""
1770 val = integer_test(str_int, 0)
1772 if 0 == val or val in world_db["ThingActions"]:
1773 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1775 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1779 def command_ttype(str_int):
1780 """Set T_TYPE of selected Thing."""
1781 val = integer_test(str_int, 0)
1783 if val in world_db["ThingTypes"]:
1784 world_db["Things"][command_tid.id]["T_TYPE"] = val
1786 print("Ignoring: ThingType ID belongs to no known ThingType.")
1790 def command_tcarries(str_int):
1791 """Append int(str_int) to T_CARRIES of selected Thing.
1793 The ID int(str_int) must not be of the selected Thing, and must belong to a
1794 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1796 val = integer_test(str_int, 0)
1798 if val == command_tid.id:
1799 print("Ignoring: Thing cannot carry itself.")
1800 elif val in world_db["Things"] \
1801 and not world_db["Things"][val]["carried"]:
1802 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1803 world_db["Things"][val]["carried"] = True
1805 print("Ignoring: Thing not available for carrying.")
1806 # Note that the whole carrying structure is different from the C version:
1807 # Carried-ness is marked by a "carried" flag, not by Things containing
1808 # Things internally.
1812 def command_tmemthing(str_t, str_y, str_x):
1813 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1815 The type must fit to an existing ThingType, and the position into the map.
1817 type = integer_test(str_t, 0)
1818 posy = integer_test(str_y, 0, 255)
1819 posx = integer_test(str_x, 0, 255)
1820 if None != type and None != posy and None != posx:
1821 if type not in world_db["ThingTypes"] \
1822 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1823 print("Ignoring: Illegal value for thing type or position.")
1825 memthing = (type, posy, posx)
1826 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1829 def setter_map(maptype):
1830 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
1832 If no map of maptype exists yet, initialize it with ' ' bytes first.
1835 def valid_map_line(str_int, mapline):
1836 val = integer_test(str_int, 0, 255)
1838 if val >= world_db["MAP_LENGTH"]:
1839 print("Illegal value for map line number.")
1840 elif len(mapline) != world_db["MAP_LENGTH"]:
1841 print("Map line length is unequal map width.")
1846 def nonThingMap_helper(str_int, mapline):
1847 val = valid_map_line(str_int, mapline)
1849 length = world_db["MAP_LENGTH"]
1850 if not world_db["MAP"]:
1851 map = bytearray(b' ' * (length ** 2))
1853 map = world_db["MAP"]
1854 map[val * length:(val * length) + length] = mapline.encode()
1855 if not world_db["MAP"]:
1856 world_db["MAP"] = map
1859 def ThingMap_helper(str_int, mapline):
1860 val = valid_map_line(str_int, mapline)
1862 length = world_db["MAP_LENGTH"]
1863 if not world_db["Things"][command_tid.id][maptype]:
1864 map = bytearray(b' ' * (length ** 2))
1866 map = world_db["Things"][command_tid.id][maptype]
1867 map[val * length:(val * length) + length] = mapline.encode()
1868 if not world_db["Things"][command_tid.id][maptype]:
1869 world_db["Things"][command_tid.id][maptype] = map
1871 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
1874 def setter_tpos(axis):
1875 """Generate setter for T_POSX or T_POSY of selected Thing.
1877 If world is active, rebuilds animate things' fovmap, player's memory map.
1880 def helper(str_int):
1881 val = integer_test(str_int, 0, 255)
1883 if val < world_db["MAP_LENGTH"]:
1884 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1885 if world_db["WORLD_ACTIVE"] \
1886 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1887 build_fov_map(world_db["Things"][command_tid.id])
1888 if 0 == command_tid.id:
1889 update_map_memory(world_db["Things"][command_tid.id])
1891 print("Ignoring: Position is outside of map.")
1895 def command_ttid(id_string):
1896 """Set ID of ThingType to manipulate. ID unused? Create new one.
1898 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
1901 id = id_setter(id_string, "ThingTypes", command_ttid)
1903 world_db["ThingTypes"][id] = {
1904 "TT_NAME": "(none)",
1907 "TT_PROLIFERATE": 0,
1908 "TT_START_NUMBER": 0,
1909 "TT_STORAGE": 0, # #
1916 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1920 def command_ttname(name):
1921 """Set TT_NAME of selected ThingType."""
1922 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1926 def command_tttool(name):
1927 """Set TT_TOOL of selected ThingType."""
1928 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
1932 def command_ttsymbol(char):
1933 """Set TT_SYMBOL of selected ThingType. """
1935 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1937 print("Ignoring: Argument must be single character.")
1941 def command_ttcorpseid(str_int):
1942 """Set TT_CORPSE_ID of selected ThingType."""
1943 val = integer_test(str_int, 0)
1945 if val in world_db["ThingTypes"]:
1946 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1948 print("Ignoring: Corpse ID belongs to no known ThignType.")
1951 def command_taid(id_string):
1952 """Set ID of ThingAction to manipulate. ID unused? Create new one.
1954 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
1956 id = id_setter(id_string, "ThingActions", command_taid, True)
1958 world_db["ThingActions"][id] = {
1964 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
1967 @test_ThingAction_id
1968 def command_taname(name):
1969 """Set TA_NAME of selected ThingAction.
1971 The name must match a valid thing action function. If after the name
1972 setting no ThingAction with name "wait" remains, call set_world_inactive().
1974 if name == "wait" or name == "move" or name == "use" or name == "drop" \
1975 or name == "pick_up":
1976 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
1977 if 1 == world_db["WORLD_ACTIVE"]:
1978 wait_defined = False
1979 for id in world_db["ThingActions"]:
1980 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
1983 if not wait_defined:
1984 set_world_inactive()
1986 print("Ignoring: Invalid action name.")
1987 # In contrast to the original,naming won't map a function to a ThingAction.
1991 """Call ai() on player Thing, then turn_over()."""
1992 ai(world_db["Things"][0])
1996 """Commands database.
1998 Map command start tokens to ([0]) number of expected command arguments, ([1])
1999 the command's meta-ness (i.e. is it to be written to the record file, is it to
2000 be ignored in replay mode if read from server input file), and ([2]) a function
2004 "QUIT": (0, True, command_quit),
2005 "PING": (0, True, command_ping),
2006 "THINGS_HERE": (2, True, command_thingshere),
2007 "MAKE_WORLD": (1, False, command_makeworld),
2008 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
2009 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
2010 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
2011 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
2012 "MAP_LENGTH": (1, False, command_maplength),
2013 "WORLD_ACTIVE": (1, False, command_worldactive),
2014 "MAP": (2, False, setter_map("MAP")),
2015 "FAVOR_STAGE": (1, False, setter(None, "FAVOR_STAGE", 0, 65535)), # #
2016 "SLIPPERS": (1, False, specialtypesetter("SLIPPERS")), # #
2017 "PLANT_0": (1, False, specialtypesetter("PLANT_0")), # #
2018 "PLANT_1": (1, False, specialtypesetter("PLANT_1")), # #
2019 "LUMBER": (1, False, specialtypesetter("LUMBER")), # #
2020 "TOOL_0": (1, False, specialtypesetter("TOOL_0")), # #
2021 "TOOL_1": (1, False, specialtypesetter("TOOL_1")), # #
2022 "EMPATHY": (1, False, setter(None, "EMPATHY", 0, 1)), # #
2023 "TA_ID": (1, False, command_taid),
2024 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
2025 "TA_NAME": (1, False, command_taname),
2026 "TT_ID": (1, False, command_ttid),
2027 "TT_NAME": (1, False, command_ttname),
2028 "TT_TOOL": (1, False, command_tttool),
2029 "TT_SYMBOL": (1, False, command_ttsymbol),
2030 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
2031 "TT_TOOLPOWER": (1, False, setter("ThingType", "TT_TOOLPOWER", 0, 65535)),
2032 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
2034 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
2036 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
2037 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
2038 "T_ID": (1, False, command_tid),
2039 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
2040 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
2041 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
2042 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
2043 "T_COMMAND": (1, False, command_tcommand),
2044 "T_TYPE": (1, False, command_ttype),
2045 "T_CARRIES": (1, False, command_tcarries),
2046 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
2047 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
2048 "T_MEMTHING": (3, False, command_tmemthing),
2049 "T_POSY": (1, False, setter_tpos("Y")),
2050 "T_POSX": (1, False, setter_tpos("X")),
2051 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
2052 "wait": (0, False, play_commander("wait")),
2053 "move": (1, False, play_commander("move")),
2054 "pick_up": (0, False, play_commander("pick_up")),
2055 "drop": (1, False, play_commander("drop", True)),
2056 "use": (1, False, play_commander("use", True)),
2057 "ai": (0, False, command_ai)
2059 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.
2062 """World state database. With sane default values. (Randomness is in rand.)"""
2070 "FAVOR_STAGE": 0, # #
2084 """Special type settings."""
2085 specials = ["SLIPPERS", "PLANT_0", "PLANT_1", "LUMBER", "TOOL_0", "TOOL_1"] # #
2087 """Mapping of direction names to internal direction chars."""
2088 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
2089 "west": "s", "north-west": "w", "north-east": "e"}
2091 """File IO database."""
2093 "path_save": "save",
2094 "path_record": "record_save",
2095 "path_worldconf": "confserver/world",
2096 "path_server": "server/",
2097 "path_in": "server/in",
2098 "path_out": "server/out",
2099 "path_worldstate": "server/worldstate",
2100 "tmp_suffix": "_tmp",
2101 "kicked_by_rival": False,
2102 "worldstate_updateable": False
2107 libpr = prep_library()
2108 rand = RandomnessIO()
2109 opts = parse_command_line_arguments()
2111 io_db["path_save"] = opts.savefile
2112 io_db["path_record"] = "record_" + opts.savefile
2115 io_db["verbose"] = True
2116 if None != opts.replay:
2120 except SystemExit as exit:
2121 print("ABORTING: " + exit.args[0])
2123 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")