1 # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
2 # or any later version. For details on its copyright, license, and warranties,
3 # see the file NOTICE in the root directory of the PlomRogue source package.
6 from server.config.world_data import world_db
7 from server.config.io import io_db
8 from server.io import log, strong_write
9 from server.utils import integer_test, id_setter
10 from server.world import set_world_inactive, turn_over, eat_vs_hunger_threshold
11 from server.update_map_memory import update_map_memory
12 from server.build_fov_map import build_fov_map
15 def command_plugin(str_plugin):
16 """Run code in plugins/[str_plugin]."""
18 if (str_plugin.replace("_", "").isalnum()
19 and os.access("plugins/server/" + str_plugin + ".py", os.F_OK)):
20 exec(open("plugins/server/" + str_plugin + ".py").read())
21 world_db["PLUGIN"] += [str_plugin]
23 print("Bad plugin name:", str_plugin)
27 """Send PONG line to server output file."""
28 strong_write(io_db["file_out"], "PONG\n")
32 """Abort server process."""
33 from server.io import save_world, atomic_write
34 from server.utils import opts
35 if None == opts.replay:
36 if world_db["WORLD_ACTIVE"]:
38 atomic_write(io_db["path_record"], io_db["record_chunk"],
40 raise SystemExit("received QUIT command")
43 def command_thingshere(str_y, str_x):
44 """Write to out file list of Things known to player at coordinate y, x."""
45 if world_db["WORLD_ACTIVE"]:
46 y = integer_test(str_y, 0, 255)
47 x = integer_test(str_x, 0, 255)
48 length = world_db["MAP_LENGTH"]
49 if None != y and None != x and y < length and x < length:
50 pos = (y * world_db["MAP_LENGTH"]) + x
51 strong_write(io_db["file_out"], "THINGS_HERE START\n")
52 terrain = chr(world_db["Things"][0]["T_MEMMAP"][pos])
53 terrain_name = world_db["terrain_names"][terrain]
54 strong_write(io_db["file_out"], "terrain: " + terrain_name + "\n")
55 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
56 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
57 for id in world_db["Things"]
58 if not world_db["Things"][id]["carried"]
59 if world_db["Things"][id]["T_TYPE"] == tid
60 if pos == world_db["Things"][id]["pos"]]:
61 type = world_db["Things"][id]["T_TYPE"]
62 name = world_db["ThingTypes"][type]["TT_NAME"]
63 strong_write(io_db["file_out"], name + "\n")
65 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
66 for mt in world_db["Things"][0]["T_MEMTHING"]
67 if mt[0] == tid if y == mt[1] if x == mt[2]]:
68 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
69 strong_write(io_db["file_out"], name + "\n")
70 strong_write(io_db["file_out"], "THINGS_HERE END\n")
72 print("Ignoring: Invalid map coordinates.")
74 print("Ignoring: Command only works on existing worlds.")
77 def command_seedrandomness(seed_string):
78 """Set rand seed to int(seed_string)."""
79 from server.utils import rand
80 val = integer_test(seed_string, 0, 4294967295)
85 def command_makeworld(seed_string):
86 """Call make_world()."""
87 val = integer_test(seed_string, 0, 4294967295)
89 from server.make_world import make_world
93 def command_maplength(maplength_string):
94 """Redefine map length. Invalidate map, therefore lose all things on it."""
95 val = integer_test(maplength_string, 1, 256)
97 from server.utils import libpr
98 world_db["MAP_LENGTH"] = val
99 world_db["MAP"] = False
101 world_db["Things"] = {}
102 libpr.set_maplength(val)
105 def command_worldactive(worldactive_string):
106 """Toggle world_db["WORLD_ACTIVE"] if possible.
108 An active world can always be set inactive. An inactive world can only be
109 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
110 map. On activation, rebuild all Things' FOVs, and the player's map memory.
112 val = integer_test(worldactive_string, 0, 1)
114 if 0 != world_db["WORLD_ACTIVE"]:
118 print("World already active.")
119 elif 0 == world_db["WORLD_ACTIVE"]:
120 for ThingAction in world_db["ThingActions"]:
121 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
124 print("Ignored: No wait action defined for world to activate.")
126 for Thing in world_db["Things"]:
130 print("Ignored: No player defined for world to activate.")
132 if not world_db["MAP"]:
133 print("Ignoring: No map defined for world to activate.")
135 from server.config.commands import command_worldactive_test_hook
136 if not command_worldactive_test_hook():
138 for tid in world_db["Things"]:
139 if world_db["Things"][tid]["T_LIFEPOINTS"]:
140 build_fov_map(world_db["Things"][tid])
142 update_map_memory(world_db["Things"][tid], False)
143 if not world_db["Things"][0]["T_LIFEPOINTS"]:
144 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
145 world_db["Things"][0]["fovmap"] = empty_fovmap
146 world_db["WORLD_ACTIVE"] = 1
149 def command_tid(id_string):
150 """Set ID of Thing to manipulate. ID unused? Create new one.
152 Default new Thing's type to the first available ThingType, others: zero.
154 tid = id_setter(id_string, "Things", command_tid)
156 if world_db["ThingTypes"] == {}:
157 print("Ignoring: No ThingType to settle new Thing in.")
159 ty = list(world_db["ThingTypes"].keys())[0]
160 from server.new_thing import new_Thing
161 world_db["Things"][tid] = new_Thing(ty)
164 def command_ttid(id_string):
165 """Set ID of ThingType to manipulate. ID unused? Create new one.
167 Set new type's TT_CORPSE_ID to self, other fields to thingtype_defaults.
169 ttid = id_setter(id_string, "ThingTypes", command_ttid)
171 from server.config.world_data import thingtype_defaults
172 world_db["ThingTypes"][ttid] = {}
173 for key in thingtype_defaults:
174 world_db["ThingTypes"][ttid][key] = thingtype_defaults[key]
175 world_db["ThingTypes"][ttid]["TT_CORPSE_ID"] = ttid
178 def command_taid(id_string):
179 """Set ID of ThingAction to manipulate. ID unused? Create new one.
181 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
183 taid = id_setter(id_string, "ThingActions", command_taid, True)
185 world_db["ThingActions"][taid] = {
191 def test_for_id_maker(object, category):
192 """Return decorator testing for object having "id" attribute."""
195 if hasattr(object, "id"):
198 print("Ignoring: No " + category +
199 " defined to manipulate yet.")
204 test_Thing_id = test_for_id_maker(command_tid, "Thing")
205 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
206 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
210 def command_tcommand(str_int):
211 """Set T_COMMAND of selected Thing."""
212 val = integer_test(str_int, 0)
214 if 0 == val or val in world_db["ThingActions"]:
215 world_db["Things"][command_tid.id]["T_COMMAND"] = val
217 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
221 def command_ttype(str_int):
222 """Set T_TYPE of selected Thing."""
223 val = integer_test(str_int, 0)
225 if val in world_db["ThingTypes"]:
226 world_db["Things"][command_tid.id]["T_TYPE"] = val
228 print("Ignoring: ThingType ID belongs to no known ThingType.")
232 def command_tcarries(str_int):
233 """Append int(str_int) to T_CARRIES of selected Thing.
235 The ID int(str_int) must not be of the selected Thing, and must belong to a
236 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
238 val = integer_test(str_int, 0)
240 if val == command_tid.id:
241 print("Ignoring: Thing cannot carry itself.")
242 elif val in world_db["Things"] \
243 and not world_db["Things"][val]["carried"]:
244 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
245 world_db["Things"][val]["carried"] = True
247 print("Ignoring: Thing not available for carrying.")
248 # Note that the whole carrying structure is different from the C version:
249 # Carried-ness is marked by a "carried" flag, not by Things containing
254 def command_tmemthing(str_t, str_y, str_x):
255 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
257 The type must fit to an existing ThingType, and the position into the map.
259 type = integer_test(str_t, 0)
260 posy = integer_test(str_y, 0, 255)
261 posx = integer_test(str_x, 0, 255)
262 if None != type and None != posy and None != posx:
263 if type not in world_db["ThingTypes"] \
264 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
265 print("Ignoring: Illegal value for thing type or position.")
267 memthing = (type, posy, posx)
268 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
272 def command_ttname(name):
273 """Set TT_NAME of selected ThingType."""
274 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
278 def command_tttool(name):
279 """Set TT_TOOL of selected ThingType."""
280 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
284 def command_ttsymbol(char):
285 """Set TT_SYMBOL of selected ThingType. """
287 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
289 print("Ignoring: Argument must be single character.")
293 def command_ttcorpseid(str_int):
294 """Set TT_CORPSE_ID of selected ThingType."""
295 val = integer_test(str_int, 0)
297 if val in world_db["ThingTypes"]:
298 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
300 print("Ignoring: Corpse ID belongs to no known ThignType.")
304 def command_ttlifepoints(val_string):
305 setter("ThingType", "TT_LIFEPOINTS", 0, 255)(val_string)
306 tt = world_db["ThingTypes"][command_ttid.id]
307 tt["eat_vs_hunger_threshold"] = eat_vs_hunger_threshold(command_ttid.id)
311 def command_taname(name):
312 """Set TA_NAME of selected ThingAction.
314 The name must match a valid thing action function. If after the name
315 setting no ThingAction with name "wait" remains, call set_world_inactive().
317 if name == "wait" or name == "move" or name == "use" or name == "drop" \
319 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
320 if 1 == world_db["WORLD_ACTIVE"]:
321 for id in world_db["ThingActions"]:
322 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
327 print("Ignoring: Invalid action name.")
331 def command_taeffort(val_string):
332 setter("ThingAction", "TA_EFFORT", 0, 255)(val_string)
333 if world_db["ThingActions"][command_taid.id]["TA_NAME"] == "use":
334 for ttid in world_db["ThingTypes"]:
335 tt = world_db["ThingTypes"][ttid]
336 tt["eat_vs_hunger_threshold"] = eat_vs_hunger_threshold(ttid)
339 def setter(category, key, min, max=None):
340 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
343 val = integer_test(val_string, min, max)
347 if category == "Thing":
348 id_store = command_tid
349 decorator = test_Thing_id
350 elif category == "ThingType":
351 id_store = command_ttid
352 decorator = test_ThingType_id
353 elif category == "ThingAction":
354 id_store = command_taid
355 decorator = test_ThingAction_id
359 val = integer_test(val_string, min, max)
361 world_db[category + "s"][id_store.id][key] = val
365 def setter_map(maptype):
366 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
368 If no map of maptype exists yet, initialize it with ' ' bytes first.
371 def valid_map_line(str_int, mapline):
372 val = integer_test(str_int, 0, 255)
374 if val >= world_db["MAP_LENGTH"]:
375 print("Illegal value for map line number.")
376 elif len(mapline) != world_db["MAP_LENGTH"]:
377 print("Map line length is unequal map width.")
382 def nonThingMap_helper(str_int, mapline):
383 val = valid_map_line(str_int, mapline)
385 length = world_db["MAP_LENGTH"]
386 if not world_db["MAP"]:
387 map = bytearray(b' ' * (length ** 2))
389 map = world_db["MAP"]
390 map[val * length:(val * length) + length] = mapline.encode()
391 if not world_db["MAP"]:
392 world_db["MAP"] = map
395 def ThingMap_helper(str_int, mapline):
396 val = valid_map_line(str_int, mapline)
398 length = world_db["MAP_LENGTH"]
399 if not world_db["Things"][command_tid.id][maptype]:
400 map = bytearray(b' ' * (length ** 2))
402 map = world_db["Things"][command_tid.id][maptype]
403 map[val * length:(val * length) + length] = mapline.encode()
404 if not world_db["Things"][command_tid.id][maptype]:
405 world_db["Things"][command_tid.id][maptype] = map
407 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
411 def setter_tpos(axis):
412 """Generate setter for T_POSX or T_POSY of selected Thing.
414 If world is active, rebuilds animate things' fovmap, player's memory map.
418 val = integer_test(str_int, 0, 255)
420 if val < world_db["MAP_LENGTH"]:
421 t = world_db["Things"][command_tid.id]
422 t["T_POS" + axis] = val
423 t["pos"] = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
424 if world_db["WORLD_ACTIVE"] \
425 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
426 build_fov_map(world_db["Things"][command_tid.id])
427 if 0 == command_tid.id:
428 update_map_memory(world_db["Things"][command_tid.id])
430 print("Ignoring: Position is outside of map.")
434 def set_command(action):
435 """Set player's T_COMMAND, then call turn_over()."""
436 id = [x for x in world_db["ThingActions"]
437 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
438 world_db["Things"][0]["T_COMMAND"] = id
443 """Try "wait" as player's T_COMMAND."""
444 if world_db["WORLD_ACTIVE"]:
448 def action_exists(action):
449 matching_actions = [x for x in world_db["ThingActions"]
450 if world_db["ThingActions"][x]["TA_NAME"] == action]
451 if len(matching_actions) >= 1:
453 print("No appropriate ThingAction defined.")
458 """Try "pickup" as player's T_COMMAND"."""
459 if action_exists("pickup") and world_db["WORLD_ACTIVE"]:
460 t = world_db["Things"][0]
461 ids = [tid for tid in world_db["Things"] if tid
462 if not world_db["Things"][tid]["carried"]
463 if world_db["Things"][tid]["pos"] == t["pos"]]
464 from server.config.commands import play_pickup_attempt_hook
466 log("NOTHING to pick up.")
467 elif play_pickup_attempt_hook(t):
468 set_command("pickup")
471 def play_drop(str_arg):
472 """Try "drop" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
473 if action_exists("drop") and world_db["WORLD_ACTIVE"]:
474 t = world_db["Things"][0]
475 if 0 == len(t["T_CARRIES"]):
476 log("You have NOTHING to drop in your inventory.")
478 val = integer_test(str_arg, 0, 255)
479 if None != val and val < len(t["T_CARRIES"]):
480 world_db["Things"][0]["T_ARGUMENT"] = val
483 print("Illegal inventory index.")
486 def play_use(str_arg):
487 """Try "use" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
488 if action_exists("use") and world_db["WORLD_ACTIVE"]:
489 t = world_db["Things"][0]
490 if 0 == len(t["T_CARRIES"]):
491 log("You have NOTHING to use in your inventory.")
493 val = integer_test(str_arg, 0, 255)
494 if None != val and val < len(t["T_CARRIES"]):
495 tid = t["T_CARRIES"][val]
496 tt = world_db["ThingTypes"][world_db["Things"][tid]["T_TYPE"]]
497 from server.config.commands import play_use_attempt_hook
498 hook_test = play_use_attempt_hook(t, tt)
499 if not (tt["TT_TOOL"] == "food" or hook_test):
500 if hook_test != False:
501 log("You CAN'T use this thing.")
503 world_db["Things"][0]["T_ARGUMENT"] = val
506 print("Illegal inventory index.")
509 def play_move(str_arg):
510 """Try "move" as player's T_COMMAND, str_arg as T_ARGUMENT / direction."""
511 if action_exists("move") and world_db["WORLD_ACTIVE"]:
512 from server.config.world_data import directions_db, symbols_passable
513 t = world_db["Things"][0]
514 if not str_arg in directions_db:
515 print("Illegal move direction string.")
517 d = ord(directions_db[str_arg])
518 from server.utils import mv_yx_in_dir_legal
519 move_result = mv_yx_in_dir_legal(chr(d), t["T_POSY"], t["T_POSX"])
520 if 1 == move_result[0]:
521 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
522 if ord("~") == world_db["MAP"][pos]:
523 log("You can't SWIM.")
525 from server.config.commands import play_move_attempt_hook
526 if play_move_attempt_hook(t, d, pos):
528 if chr(world_db["MAP"][pos]) in symbols_passable:
529 world_db["Things"][0]["T_ARGUMENT"] = d
532 log("You CAN'T move there.")
536 """Call ai() on player Thing, then turn_over()."""
537 from server.ai import ai
538 if world_db["WORLD_ACTIVE"]:
539 ai(world_db["Things"][0])