5 from server.config.world_data import world_db
6 from server.config.io import io_db
10 """Ensure valid server out file belonging to current process.
12 This is done by comparing io_db["teststring"] to what's found at the start
13 of the current file at io_db["path_out"]. On failure, set
14 io_db["kicked_by_rival"] and raise SystemExit.
16 if not os.access(io_db["path_out"], os.F_OK):
17 raise SystemExit("Server output file has disappeared.")
18 file = open(io_db["path_out"], "r")
19 test = file.readline().rstrip("\n")
21 if test != io_db["teststring"]:
22 io_db["kicked_by_rival"] = True
23 msg = "Server test string in server output file does not match. This" \
24 " indicates that the current server process has been " \
25 "superseded by another one."
29 def safely_remove_worldstate_file():
30 from server.io import server_test
31 if os.access(io_db["path_worldstate"], os.F_OK):
33 os.remove(io_db["path_worldstate"])
36 def atomic_write(path, text, do_append=False, delete=True):
37 """Atomic write of text to file at path, appended if do_append is set."""
38 path_tmp = path + io_db["tmp_suffix"]
42 if os.access(path, os.F_OK):
43 from shutil import copyfile
44 copyfile(path, path_tmp)
45 file = open(path_tmp, mode)
46 strong_write(file, text)
48 if delete and os.access(path, os.F_OK):
50 os.rename(path_tmp, path)
53 def strong_write(file, string):
54 """Apply write(string), then flush()."""
59 def setup_server_io():
60 """Fill IO files DB with proper file( path)s. Write process IO test string.
62 Ensure IO files directory at server/. Remove any old input file if found.
63 Set up new input file for reading, and new output file for appending. Start
64 output file with process hash line of format PID + " " + floated UNIX time
65 (io_db["teststring"]). Raise SystemExit if file is found at path of either
66 record or save file plus io_db["tmp_suffix"].
68 def detect_atomic_leftover(path, tmp_suffix):
69 path_tmp = path + tmp_suffix
70 msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
71 "aborted previous attempt to write '" + path + "'. Aborting " \
72 "until matter is resolved by removing it from its current path."
73 if os.access(path_tmp, os.F_OK):
75 io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
76 io_db["save_wait_start"] = 0
77 io_db["verbose"] = False
78 io_db["record_chunk"] = ""
79 os.makedirs(io_db["path_server"], exist_ok=True)
80 io_db["file_out"] = open(io_db["path_out"], "a")
81 strong_write(io_db["file_out"], io_db["teststring"] + "\n")
82 if os.access(io_db["path_in"], os.F_OK):
83 os.remove(io_db["path_in"])
84 io_db["file_in"] = open(io_db["path_in"], "w")
85 io_db["file_in"].close()
86 io_db["file_in"] = open(io_db["path_in"], "r")
87 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
88 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
91 def cleanup_server_io():
92 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
93 def helper(file_key, path_key):
95 io_db[file_key].close()
96 if not io_db["kicked_by_rival"] \
97 and os.access(io_db[path_key], os.F_OK):
98 os.remove(io_db[path_key])
99 helper("file_in", "path_in")
100 helper("file_out", "path_out")
101 helper("file_worldstate", "path_worldstate")
102 if "file_record" in io_db:
103 io_db["file_record"].close()
106 """Return next newline-delimited command from server in file.
108 Keep building return string until a newline is encountered. Pause between
109 unsuccessful reads, and after too much waiting, run server_test().
111 wait_on_fail = io_db["wait_on_read_fail"]
112 max_wait = io_db["max_wait_on_read_fail"]
116 add = io_db["file_in"].readline()
118 command = command + add
119 if len(command) > 0 and "\n" == command[-1]:
120 command = command[:-1]
123 time.sleep(wait_on_fail)
124 if now + max_wait < time.time():
131 """Send "msg" to log."""
132 strong_write(io_db["file_out"], "LOG " + msg + "\n")
136 """Save all commands needed to reconstruct current world state."""
137 from server.utils import rand
139 def quote_escape(string):
140 string = string.replace("\u005C", '\u005C\u005C')
141 return '"' + string.replace('"', '\u005C"') + '"'
146 if key == "MAP" or world_db["Things"][id][key]:
147 map = world_db["MAP"] if key == "MAP" \
148 else world_db["Things"][id][key]
149 length = world_db["MAP_LENGTH"]
150 for i in range(length):
151 line = map[i * length:(i * length) + length].decode()
152 string = string + key + " " + str(i) + " " + \
153 quote_escape(line) + "\n"
159 for memthing in world_db["Things"][id]["T_MEMTHING"]:
160 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
161 str(memthing[1]) + " " + str(memthing[2]) + "\n"
164 def helper(category, id_string, special_keys={}):
166 for id in sorted(world_db[category].keys()):
167 string = string + id_string + " " + str(id) + "\n"
168 for key in sorted(world_db[category][id].keys()):
169 if not key in special_keys:
170 x = world_db[category][id][key]
171 argument = quote_escape(x) if str == type(x) else str(x)
172 string = string + key + " " + argument + "\n"
173 elif special_keys[key]:
174 string = string + special_keys[key](id)
178 for key in sorted(world_db.keys()):
179 if (not isinstance(world_db[key], dict)) and key != "MAP" and \
180 key != "WORLD_ACTIVE":
181 string = string + key + " " + str(world_db[key]) + "\n"
182 string = string + mapsetter("MAP")()
183 string = string + helper("ThingActions", "TA_ID")
184 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
185 for id in sorted(world_db["ThingTypes"].keys()):
186 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
187 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
188 string = string + helper("Things", "T_ID",
189 {"T_CARRIES": False, "carried": False,
190 "T_MEMMAP": mapsetter("T_MEMMAP"),
191 "T_MEMTHING": memthing, "fovmap": False,
192 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
193 for id in sorted(world_db["Things"].keys()):
194 if [] != world_db["Things"][id]["T_CARRIES"]:
195 string = string + "T_ID " + str(id) + "\n"
196 for carried in sorted(world_db["Things"][id]["T_CARRIES"]):
197 string = string + "T_CARRIES " + str(carried) + "\n"
198 string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
199 "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
200 atomic_write(io_db["path_save"], string)
203 def obey(command, prefix, replay=False, do_record=False):
204 """Call function from commands_db mapped to command's first token.
206 Tokenize command string with shlex.split(comments=True). If replay is set,
207 a non-meta command from the commands_db merely triggers obey() on the next
208 command from the records file. If not, non-meta commands set
209 io_db["worldstate_updateable"] to world_db["WORLD_ACTIVE"], and, if
210 do_record is set, are recorded to io_db["record_chunk"], and save_world()
211 is called (and io_db["record_chunk"] written) if io_db["save_wait"] seconds
212 have passed since the last time it was called. The prefix string is
213 inserted into the server's input message between its beginning 'input ' and
214 ':'. All activity is preceded by a server_test() call. Commands that start
215 with a lowercase letter are ignored when world_db["WORLD_ACTIVE"] is
219 from server.config.commands import commands_db
222 print("input " + prefix + ": " + command)
224 tokens = shlex.split(command, comments=True)
225 except ValueError as err:
226 print("Can't tokenize command string: " + str(err) + ".")
228 if len(tokens) > 0 and tokens[0] in commands_db \
229 and len(tokens) == commands_db[tokens[0]][0] + 1:
230 if commands_db[tokens[0]][1]:
231 commands_db[tokens[0]][2](*tokens[1:])
232 elif tokens[0][0].islower() and not world_db["WORLD_ACTIVE"]:
233 print("Ignoring lowercase-starting commands when world inactive.")
235 print("Due to replay mode, reading command as 'go on in record'.")
236 line = io_db["file_record"].readline()
238 obey(line.rstrip(), io_db["file_record"].prefix
239 + str(io_db["file_record"].line_n))
240 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
242 print("Reached end of record file.")
244 commands_db[tokens[0]][2](*tokens[1:])
246 io_db["record_chunk"] += command + "\n"
247 if time.time() > io_db["save_wait_start"] + io_db["save_wait"]:
248 atomic_write(io_db["path_record"], io_db["record_chunk"],
250 if world_db["WORLD_ACTIVE"]:
252 io_db["record_chunk"] = ""
253 io_db["save_wait"] = time.time()
254 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
255 elif 0 != len(tokens):
256 print("Invalid command/argument, or bad number of tokens.")
259 def obey_lines_in_file(path, name, do_record=False):
260 """Call obey() on each line of path's file, use name in input prefix."""
261 file = open(path, "r")
263 for line in file.readlines():
264 obey(line.rstrip(), name + "file line " + str(line_n),
270 def try_worldstate_update():
271 """Write worldstate file if io_db["worldstate_updateable"] is set."""
272 from server.config.commands import commands_db
273 if io_db["worldstate_updateable"]:
275 def write_map(string, map):
276 for i in range(length):
277 line = map[i * length:(i * length) + length].decode()
278 string = string + line + "\n"
282 if [] == world_db["Things"][0]["T_CARRIES"]:
283 inventory = "(none)\n"
285 for id in world_db["Things"][0]["T_CARRIES"]:
286 type_id = world_db["Things"][id]["T_TYPE"]
287 name = world_db["ThingTypes"][type_id]["TT_NAME"]
288 inventory = inventory + name + "\n"
289 string = str(world_db["TURN"]) + "\n" + \
290 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
291 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
292 inventory + "%\n" + \
293 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
294 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
295 str(world_db["MAP_LENGTH"]) + "\n"
296 length = world_db["MAP_LENGTH"]
297 fov = bytearray(b' ' * (length ** 2))
299 for pos in [pos for pos in range(length ** 2)
300 if ord_v == world_db["Things"][0]["fovmap"][pos]]:
301 fov[pos] = world_db["MAP"][pos]
302 length = world_db["MAP_LENGTH"]
303 for id in [id for tid in reversed(sorted(list(world_db["ThingTypes"])))
304 for id in world_db["Things"]
305 if not world_db["Things"][id]["carried"]
306 if world_db["Things"][id]["T_TYPE"] == tid
307 if world_db["Things"][0]["fovmap"][
308 world_db["Things"][id]["T_POSY"] * length
309 + world_db["Things"][id]["T_POSX"]] == ord_v]:
310 type = world_db["Things"][id]["T_TYPE"]
311 c = ord(world_db["ThingTypes"][type]["TT_SYMBOL"])
312 fov[world_db["Things"][id]["T_POSY"] * length
313 + world_db["Things"][id]["T_POSX"]] = c
314 string = write_map(string, fov)
315 mem = world_db["Things"][0]["T_MEMMAP"][:]
316 for mt in [mt for tid in reversed(sorted(list(world_db["ThingTypes"])))
317 for mt in world_db["Things"][0]["T_MEMTHING"]
319 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
320 mem[(mt[1] * length) + mt[2]] = ord(c)
321 string = write_map(string, mem)
322 atomic_write(io_db["path_worldstate"], string, delete=False)
323 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
324 io_db["worldstate_updateable"] = False