9 def strong_write(file, string):
10 """Apply write(string), flush() and os.fsync() to file."""
16 def setup_server_io():
17 """Fill IO files DB with proper file( path)s. Write process IO test string.
19 Ensure IO files directory at server/. Remove any old input file if found.
20 Set up new input file for reading, and new output file for writing. Start
21 output file with process hash line of format PID + " " + floated UNIX time
22 (io_db["teststring"]). Raise SystemExit if file is found at path of either
23 record or save file plus io_db["tmp_suffix"].
25 def detect_atomic_leftover(path, tmp_suffix):
26 path_tmp = path + tmp_suffix
27 msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
28 "aborted previous attempt to write '" + path + "'. Aborting " \
29 "until matter is resolved by removing it from its current path."
30 if os.access(path_tmp, os.F_OK):
32 io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
33 os.makedirs(io_db["path_server"], exist_ok=True)
34 io_db["file_out"] = open(io_db["path_out"], "w")
35 strong_write(io_db["file_out"], io_db["teststring"] + "\n")
36 if os.access(io_db["path_in"], os.F_OK):
37 os.remove(io_db["path_in"])
38 io_db["file_in"] = open(io_db["path_in"], "w")
39 io_db["file_in"].close()
40 io_db["file_in"] = open(io_db["path_in"], "r")
41 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
42 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
45 def cleanup_server_io():
46 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
47 def helper(file_key, path_key):
49 io_db[file_key].close()
50 if not io_db["kicked_by_rival"] \
51 and os.access(io_db[path_key], os.F_OK):
52 os.remove(io_db[path_key])
53 helper("file_out", "path_out")
54 helper("file_in", "path_in")
55 helper("file_worldstate", "path_worldstate")
56 if "file_record" in io_db:
57 io_db["file_record"].close()
60 def obey(command, prefix, replay=False, do_record=False):
61 """Call function from commands_db mapped to command's first token.
63 Tokenize command string with shlex.split(comments=True). If replay is set,
64 a non-meta command from the commands_db merely triggers obey() on the next
65 command from the records file. If not, non-meta commands set
66 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
67 do_record is set, are recorded via record(), and save_world() is called.
68 The prefix string is inserted into the server's input message between its
69 beginning 'input ' & ':'. All activity is preceded by a server_test() call.
72 print("input " + prefix + ": " + command)
74 tokens = shlex.split(command, comments=True)
75 except ValueError as err:
76 print("Can't tokenize command string: " + str(err) + ".")
78 if len(tokens) > 0 and tokens[0] in commands_db \
79 and len(tokens) == commands_db[tokens[0]][0] + 1:
80 if commands_db[tokens[0]][1]:
81 commands_db[tokens[0]][2](*tokens[1:])
83 print("Due to replay mode, reading command as 'go on in record'.")
84 line = io_db["file_record"].readline()
86 obey(line.rstrip(), io_db["file_record"].prefix
87 + str(io_db["file_record"].line_n))
88 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
90 print("Reached end of record file.")
92 commands_db[tokens[0]][2](*tokens[1:])
96 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
97 elif 0 != len(tokens):
98 print("Invalid command/argument, or bad number of tokens.")
101 def atomic_write(path, text, do_append=False):
102 """Atomic write of text to file at path, appended if do_append is set."""
103 path_tmp = path + io_db["tmp_suffix"]
107 if os.access(path, os.F_OK):
108 shutil.copyfile(path, path_tmp)
109 file = open(path_tmp, mode)
110 strong_write(file, text)
112 if os.access(path, os.F_OK):
114 os.rename(path_tmp, path)
118 """Append command string plus newline to record file. (Atomic.)"""
119 # This misses some optimizations from the original record(), namely only
120 # finishing the atomic write with expensive flush() and fsync() every 15
121 # seconds unless explicitely forced. Implement as needed.
122 atomic_write(io_db["path_record"], command + "\n", do_append=True)
126 """Save all commands needed to reconstruct current world state."""
127 # TODO: Misses same optimizations as record() from the original record().
130 string = string.replace("\u005C", '\u005C\u005C')
131 return '"' + string.replace('"', '\u005C"') + '"'
136 if world_db["Things"][id][key]:
137 rmap = world_db["Things"][id][key]
138 length = world_db["MAP_LENGTH"]
139 for i in range(length):
140 line = rmap[i * length:(i * length) + length].decode()
141 string = string + key + " " + str(i) + quote(line) + "\n"
147 for memthing in world_db["Things"][id]["T_MEMTHING"]:
148 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
149 str(memthing[1]) + " " + str(memthing[2]) + "\n"
152 def helper(category, id_string, special_keys={}):
154 for id in world_db[category]:
155 string = string + id_string + " " + str(id) + "\n"
156 for key in world_db[category][id]:
157 if not key in special_keys:
158 x = world_db[category][id][key]
159 argument = quote(x) if str == type(x) else str(x)
160 string = string + key + " " + argument + "\n"
161 elif special_keys[key]:
162 string = string + special_keys[key](id)
167 if dict != type(world_db[key]) and key != "MAP":
168 string = string + key + " " + str(world_db[key]) + "\n"
169 string = string + helper("ThingActions", "TA_ID")
170 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
171 for id in world_db["ThingTypes"]:
172 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
173 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
174 string = string + helper("Things", "T_ID",
175 {"T_CARRIES": False, "carried": False,
176 "T_MEMMAP": mapsetter("T_MEMMAP"),
177 "T_MEMTHING": memthing,
178 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
179 for id in world_db["Things"]:
180 if [] != world_db["Things"][id]["T_CARRIES"]:
181 string = string + "T_ID " + str(id) + "\n"
182 for carried_id in world_db["Things"][id]["T_CARRIES"]:
183 string = string + "T_CARRIES " + str(carried_id) + "\n"
184 string = string + "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
185 atomic_write(io_db["path_save"], string)
188 def obey_lines_in_file(path, name, do_record=False):
189 """Call obey() on each line of path's file, use name in input prefix."""
190 file = open(path, "r")
192 for line in file.readlines():
193 obey(line.rstrip(), name + "file line " + str(line_n),
199 def parse_command_line_arguments():
200 """Return settings values read from command line arguments."""
201 parser = argparse.ArgumentParser()
202 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
204 opts, unknown = parser.parse_known_args()
209 """Ensure valid server out file belonging to current process.
211 This is done by comparing io_db["teststring"] to what's found at the start
212 of the current file at io_db["path_out"]. On failure, set
213 io_db["kicked_by_rival"] and raise SystemExit.
215 if not os.access(io_db["path_out"], os.F_OK):
216 raise SystemExit("Server output file has disappeared.")
217 file = open(io_db["path_out"], "r")
218 test = file.readline().rstrip("\n")
220 if test != io_db["teststring"]:
221 io_db["kicked_by_rival"] = True
222 msg = "Server test string in server output file does not match. This" \
223 " indicates that the current server process has been " \
224 "superseded by another one."
225 raise SystemExit(msg)
229 """Return next newline-delimited command from server in file.
231 Keep building return string until a newline is encountered. Pause between
232 unsuccessful reads, and after too much waiting, run server_test().
239 add = io_db["file_in"].readline()
241 command = command + add
242 if len(command) > 0 and "\n" == command[-1]:
243 command = command[:-1]
246 time.sleep(wait_on_fail)
247 if now + max_wait < time.time():
253 def try_worldstate_update():
254 """Write worldstate file if io_db["worldstate_updateable"] is set."""
255 if io_db["worldstate_updateable"]:
257 if [] == world_db["Things"][0]["T_CARRIES"]:
258 inventory = "(none)\n"
260 for id in world_db["Things"][0]["T_CARRIES"]:
261 type_id = world_db["Things"][id]["T_TYPE"]
262 name = world_db["ThingTypes"][type_id]["TT_NAME"]
263 inventory = inventory + name + "\n"
264 string = str(world_db["TURN"]) + "\n" + \
265 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
266 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
267 inventory + "%\n" + \
268 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
269 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
270 str(world_db["MAP_LENGTH"]) + "\n"
271 length = world_db["MAP_LENGTH"]
272 for i in range(length):
273 line = world_db["MAP"][i * length:(i * length) + length].decode()
274 string = string + line + "\n"
275 # TODO: no proper user-subjective map
276 atomic_write(io_db["path_worldstate"], string)
277 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
278 io_db["worldstate_updateable"] = False
282 """Replay game from record file.
284 Use opts.replay as breakpoint turn to which to replay automatically before
285 switching to manual input by non-meta commands in server input file
286 triggering further reads of record file. Ensure opts.replay is at least 1.
287 Run try_worldstate_update() before each interactive obey()/read_command().
291 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
292 " (if so late a turn is to be found).")
293 if not os.access(io_db["path_record"], os.F_OK):
294 raise SystemExit("No record file found to replay.")
295 io_db["file_record"] = open(io_db["path_record"], "r")
296 io_db["file_record"].prefix = "record file line "
297 io_db["file_record"].line_n = 1
298 while world_db["TURN"] < opts.replay:
299 line = io_db["file_record"].readline()
302 obey(line.rstrip(), io_db["file_record"].prefix
303 + str(io_db["file_record"].line_n))
304 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
306 try_worldstate_update()
307 obey(read_command(), "in file", replay=True)
311 """Play game by server input file commands. Before, load save file found.
313 If no save file is found, a new world is generated from the commands in the
314 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
315 command and all that follow via the server input file. Run
316 try_worldstate_update() before each interactive obey()/read_command().
318 if os.access(io_db["path_save"], os.F_OK):
319 obey_lines_in_file(io_db["path_save"], "save")
321 if not os.access(io_db["path_worldconf"], os.F_OK):
322 msg = "No world config file from which to start a new world."
323 raise SystemExit(msg)
324 obey_lines_in_file(io_db["path_worldconf"], "world config ",
326 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
328 try_worldstate_update()
329 obey(read_command(), "in file", do_record=True)
334 world_db["MAP"] = bytearray(b'.' * (world_db["MAP_LENGTH"] ** 2))
337 def set_world_inactive():
338 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
340 if os.access(io_db["path_worldstate"], os.F_OK):
341 os.remove(io_db["path_worldstate"])
342 world_db["WORLD_ACTIVE"] = 0
345 def integer_test(val_string, min, max):
346 """Return val_string if possible integer >= min and <= max, else None."""
348 val = int(val_string)
349 if val < min or val > max:
353 print("Ignoring: Please use integer >= " + str(min) + " and <= " +
358 def setter(category, key, min, max):
359 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
362 val = integer_test(val_string, min, max)
366 if category == "Thing":
367 id_store = command_tid
368 decorator = test_Thing_id
369 elif category == "ThingType":
370 id_store = command_ttid
371 decorator = test_ThingType_id
372 elif category == "ThingAction":
373 id_store = command_taid
374 decorator = test_ThingAction_id
378 val = integer_test(val_string, min, max)
380 world_db[category + "s"][id_store.id][key] = val
385 """Return prototype for Thing of T_TYPE of type."""
387 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
399 "T_MEMDEPTHMAP": False
403 def id_setter(id, category, id_store=False, start_at_1=False):
404 """Set ID of object of category to manipulate ID unused? Create new one.
406 The ID is stored as id_store.id (if id_store is set). If the integer of the
407 input is valid (if start_at_1, >= 0 and <= 255, else >= -32768 and <=
408 32767), but <0 or (if start_at_1) <1, calculate new ID: lowest unused ID
409 >=0 or (if start_at_1) >= 1, and <= 255. None is always returned when no
410 new object is created, otherwise the new object's ID.
412 min = 0 if start_at_1 else -32768
413 max = 255 if start_at_1 else 32767
415 id = integer_test(id, min, max)
417 if id in world_db[category]:
422 if (start_at_1 and 0 == id) \
423 or ((not start_at_1) and (id < 0 or id > 255)):
427 if id not in world_db[category]:
431 "No unused ID available to add to ID list.")
439 """Send PONG line to server output file."""
440 strong_write(io_db["file_out"], "PONG\n")
444 """Abort server process."""
445 raise SystemExit("received QUIT command")
448 def command_thingshere(y, x):
450 print("Ignoring not-yet implemented THINGS_HERE command.")
453 def command_seedmap(seed_string):
454 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
455 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
459 def command_makeworld(seed_string):
460 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
462 Make seed world_db["SEED_RANDOMNESS"] and world_db["SEED_MAP"]. Do more
463 only with a "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType
464 of TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
465 and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
466 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
467 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
468 other; init their FOV/memory maps. Write "NEW_WORLD" line to out file.
470 setter(None, "SEED_RANDOMNESS", 0, 4294967295)(seed_string)
471 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
472 player_will_be_generated = False
473 playertype = world_db["PLAYER_TYPE"]
474 for ThingType in world_db["ThingTypes"]:
475 if playertype == ThingType:
476 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
477 player_will_be_generated = True
479 if not player_will_be_generated:
480 print("Ignoring beyond SEED_MAP: " +
481 "No player type with start number >0 defined.")
484 for ThingAction in world_db["ThingActions"]:
485 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
488 print("Ignoring beyond SEED_MAP: " +
489 "No thing action with name 'wait' defined.")
491 world_db["Things"] = {}
493 world_db["WORLD_ACTIVE"] = 1
495 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
496 id = id_setter(-1, "Things")
497 world_db["Things"][id] = new_Thing(playertype)
498 for type in world_db["ThingTypes"]:
499 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
500 id = id_setter(-1, "Things")
501 world_db["Things"][id] = new_Thing(playertype)
502 # TODO: position? update FOVs / map memories? (only at the end)
503 strong_write(io_db["file_out"], "NEW_WORLD\n")
506 def command_maplength(maplength_string):
507 """Redefine map length. Invalidate map, therefore lose all things on it."""
509 world_db["Things"] = {}
510 setter(None, "MAP_LENGTH", 1, 256)(maplength_string)
513 def command_worldactive(worldactive_string):
514 """Toggle world_db["WORLD_ACTIVE"] if possible.
516 An active world can always be set inactive. An inactive world can only be
517 set active with a "wait" ThingAction, and a player Thing (of ID 0). On
518 activation, rebuild all Things' FOVs and map memories.
520 # In original version, map existence was also tested (unnecessarily?).
521 val = integer_test(worldactive_string, 0, 1)
523 if 0 != world_db["WORLD_ACTIVE"]:
527 print("World already active.")
528 elif 0 == world_db["WORLD_ACTIVE"]:
530 for ThingAction in world_db["ThingActions"]:
531 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
534 player_exists = False
535 for Thing in world_db["Things"]:
539 if wait_exists and player_exists:
540 # TODO: rebuild all things' FOVs, map memories
541 world_db["WORLD_ACTIVE"] = 1
544 def test_for_id_maker(object, category):
545 """Return decorator testing for object having "id" attribute."""
548 if hasattr(object, "id"):
551 print("Ignoring: No " + category +
552 " defined to manipulate yet.")
557 def command_tid(id_string):
558 """Set ID of Thing to manipulate. ID unused? Create new one.
560 Default new Thing's type to the first available ThingType, others: zero.
562 id = id_setter(id_string, "Things", command_tid)
564 if world_db["ThingTypes"] == {}:
565 print("Ignoring: No ThingType to settle new Thing in.")
567 type = list(world_db["ThingTypes"].keys())[0]
568 world_db["Things"][id] = new_Thing(type)
571 test_Thing_id = test_for_id_maker(command_tid, "Thing")
575 def command_tcommand(str_int):
576 """Set T_COMMAND of selected Thing."""
577 val = integer_test(str_int, 0, 255)
579 if 0 == val or val in world_db["ThingActions"]:
580 world_db["Things"][command_tid.id]["T_COMMAND"] = val
582 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
586 def command_ttype(str_int):
587 """Set T_TYPE of selected Thing."""
588 val = integer_test(str_int, 0, 255)
590 if val in world_db["ThingTypes"]:
591 world_db["Things"][command_tid.id]["T_TYPE"] = val
593 print("Ignoring: ThingType ID belongs to no known ThingType.")
597 def command_tcarries(str_int):
598 """Append int(str_int) to T_CARRIES of selected Thing.
600 The ID int(str_int) must not be of the selected Thing, and must belong to a
601 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
603 val = integer_test(str_int, 0, 255)
605 if val == command_tid.id:
606 print("Ignoring: Thing cannot carry itself.")
607 elif val in world_db["Things"] \
608 and not world_db["Things"][val]["carried"]:
609 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
610 world_db["Things"][val]["carried"] = True
612 print("Ignoring: Thing not available for carrying.")
613 # Note that the whole carrying structure is different from the C version:
614 # Carried-ness is marked by a "carried" flag, not by Things containing
619 def command_tmemthing(str_t, str_y, str_x):
620 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
622 The type must fit to an existing ThingType, and the position into the map.
624 type = integer_test(str_t, 0, 255)
625 posy = integer_test(str_y, 0, 255)
626 posx = integer_test(str_x, 0, 255)
627 if None != type and None != posy and None != posx:
628 if type not in world_db["ThingTypes"] \
629 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
630 print("Ignoring: Illegal value for thing type or position.")
632 memthing = (type, posy, posx)
633 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
636 def setter_map(maptype):
637 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
639 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
642 def helper(str_int, mapline):
643 val = integer_test(str_int, 0, 255)
645 if val >= world_db["MAP_LENGTH"]:
646 print("Illegal value for map line number.")
647 elif len(mapline) != world_db["MAP_LENGTH"]:
648 print("Map line length is unequal map width.")
650 length = world_db["MAP_LENGTH"]
652 if not world_db["Things"][command_tid.id][maptype]:
653 rmap = bytearray(b' ' * (length ** 2))
655 rmap = world_db["Things"][command_tid.id][maptype]
656 rmap[val * length:(val * length) + length] = mapline.encode()
657 world_db["Things"][command_tid.id][maptype] = rmap
661 def setter_tpos(axis):
662 """Generate setter for T_POSX or T_POSY of selected Thing."""
665 val = integer_test(str_int, 0, 255)
667 if val < world_db["MAP_LENGTH"]:
668 world_db["Things"][command_tid.id]["T_POS" + axis] = val
669 # TODO: Delete Thing's FOV, and rebuild it if world is active.
671 print("Ignoring: Position is outside of map.")
675 def command_ttid(id_string):
676 """Set ID of ThingType to manipulate. ID unused? Create new one.
678 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
680 id = id_setter(id_string, "ThingTypes", command_ttid)
682 world_db["ThingTypes"][id] = {
687 "TT_START_NUMBER": 0,
693 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
697 def command_ttname(name):
698 """Set TT_NAME of selected ThingType."""
699 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
703 def command_ttsymbol(char):
704 """Set TT_SYMBOL of selected ThingType. """
706 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
708 print("Ignoring: Argument must be single character.")
712 def command_ttcorpseid(str_int):
713 """Set TT_CORPSE_ID of selected ThingType."""
714 val = integer_test(str_int, 0, 255)
716 if val in world_db["ThingTypes"]:
717 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
719 print("Ignoring: Corpse ID belongs to no known ThignType.")
722 def command_taid(id_string):
723 """Set ID of ThingAction to manipulate. ID unused? Create new one.
725 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
727 id = id_setter(id_string, "ThingActions", command_taid, True)
729 world_db["ThingActions"][id] = {
735 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
739 def command_taname(name):
740 """Set TA_NAME of selected ThingAction.
742 The name must match a valid thing action function. If after the name
743 setting no ThingAction with name "wait" remains, call set_world_inactive().
745 if name == "wait" or name == "move" or name == "use" or name == "drop" \
746 or name == "pick_up":
747 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
748 if 1 == world_db["WORLD_ACTIVE"]:
750 for id in world_db["ThingActions"]:
751 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
757 print("Ignoring: Invalid action name.")
758 # In contrast to the original,naming won't map a function to a ThingAction.
761 """Commands database.
763 Map command start tokens to ([0]) number of expected command arguments, ([1])
764 the command's meta-ness (i.e. is it to be written to the record file, is it to
765 be ignored in replay mode if read from server input file), and ([2]) a function
769 "QUIT": (0, True, command_quit),
770 "PING": (0, True, command_ping),
771 "THINGS_HERE": (2, True, command_thingshere),
772 "MAKE_WORLD": (1, False, command_makeworld),
773 "SEED_MAP": (1, False, command_seedmap),
774 "SEED_RANDOMNESS": (1, False, setter(None, "SEED_RANDOMNESS",
776 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
777 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
778 "MAP_LENGTH": (1, False, command_maplength),
779 "WORLD_ACTIVE": (1, False, command_worldactive),
780 "TA_ID": (1, False, command_taid),
781 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
782 "TA_NAME": (1, False, command_taname),
783 "TT_ID": (1, False, command_ttid),
784 "TT_NAME": (1, False, command_ttname),
785 "TT_SYMBOL": (1, False, command_ttsymbol),
786 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
787 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
789 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
791 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
793 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
794 "T_ID": (1, False, command_tid),
795 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
796 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
797 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
798 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
799 "T_COMMAND": (1, False, command_tcommand),
800 "T_TYPE": (1, False, command_ttype),
801 "T_CARRIES": (1, False, command_tcarries),
802 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
803 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
804 "T_MEMTHING": (3, False, command_tmemthing),
805 "T_POSY": (1, False, setter_tpos("Y")),
806 "T_POSX": (1, False, setter_tpos("X")),
810 """World state database. With sane default values."""
814 "SEED_RANDOMNESS": 0,
824 """File IO database."""
827 "path_record": "record",
828 "path_worldconf": "confserver/world",
829 "path_server": "server/",
830 "path_in": "server/in",
831 "path_out": "server/out",
832 "path_worldstate": "server/worldstate",
833 "tmp_suffix": "_tmp",
834 "kicked_by_rival": False,
835 "worldstate_updateable": False
840 opts = parse_command_line_arguments()
842 # print("DUMMY: Run game.")
843 if None != opts.replay:
847 except SystemExit as exit:
848 print("ABORTING: " + exit.args[0])
850 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
854 # print("DUMMY: (Clean up C heap.)")