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
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 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
53 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
54 for id in world_db["Things"]
55 if not world_db["Things"][id]["carried"]
56 if world_db["Things"][id]["T_TYPE"] == tid
57 if y == world_db["Things"][id]["T_POSY"]
58 if x == world_db["Things"][id]["T_POSX"]]:
59 type = world_db["Things"][id]["T_TYPE"]
60 name = world_db["ThingTypes"][type]["TT_NAME"]
61 strong_write(io_db["file_out"], name + "\n")
63 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
64 for mt in world_db["Things"][0]["T_MEMTHING"]
65 if mt[0] == tid if y == mt[1] if x == mt[2]]:
66 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
67 strong_write(io_db["file_out"], name + "\n")
68 strong_write(io_db["file_out"], "THINGS_HERE END\n")
70 print("Ignoring: Invalid map coordinates.")
72 print("Ignoring: Command only works on existing worlds.")
75 def command_seedrandomness(seed_string):
76 """Set rand seed to int(seed_string)."""
77 from server.utils import rand
78 val = integer_test(seed_string, 0, 4294967295)
83 def command_makeworld(seed_string):
84 """Call make_world()."""
85 val = integer_test(seed_string, 0, 4294967295)
87 from server.config.misc import make_world_func
91 def command_maplength(maplength_string):
92 """Redefine map length. Invalidate map, therefore lose all things on it."""
93 val = integer_test(maplength_string, 1, 256)
95 from server.utils import libpr
96 world_db["MAP_LENGTH"] = val
97 world_db["MAP"] = False
99 world_db["Things"] = {}
100 libpr.set_maplength(val)
103 def command_worldactive(worldactive_string):
104 """Toggle world_db["WORLD_ACTIVE"] if possible.
106 An active world can always be set inactive. An inactive world can only be
107 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
108 map. On activation, rebuild all Things' FOVs, and the player's map memory.
110 val = integer_test(worldactive_string, 0, 1)
112 if 0 != world_db["WORLD_ACTIVE"]:
116 print("World already active.")
117 elif 0 == world_db["WORLD_ACTIVE"]:
119 for ThingAction in world_db["ThingActions"]:
120 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
123 player_exists = False
124 for Thing in world_db["Things"]:
128 if wait_exists and player_exists and world_db["MAP"]:
129 for id in world_db["Things"]:
130 if world_db["Things"][id]["T_LIFEPOINTS"]:
131 build_fov_map(world_db["Things"][id])
133 update_map_memory(world_db["Things"][id], False)
134 if not world_db["Things"][0]["T_LIFEPOINTS"]:
135 empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
136 world_db["Things"][0]["fovmap"] = empty_fovmap
137 world_db["WORLD_ACTIVE"] = 1
139 print("Ignoring: Not all conditions for world activation met.")
142 def command_tid(id_string):
143 """Set ID of Thing to manipulate. ID unused? Create new one.
145 Default new Thing's type to the first available ThingType, others: zero.
147 id = id_setter(id_string, "Things", command_tid)
149 if world_db["ThingTypes"] == {}:
150 print("Ignoring: No ThingType to settle new Thing in.")
152 type = list(world_db["ThingTypes"].keys())[0]
153 from server.new_thing import new_Thing
154 world_db["Things"][id] = new_Thing(type)
157 def command_ttid(id_string):
158 """Set ID of ThingType to manipulate. ID unused? Create new one.
160 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
163 id = id_setter(id_string, "ThingTypes", command_ttid)
165 world_db["ThingTypes"][id] = {
170 "TT_START_NUMBER": 0,
177 def command_taid(id_string):
178 """Set ID of ThingAction to manipulate. ID unused? Create new one.
180 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
182 id = id_setter(id_string, "ThingActions", command_taid, True)
184 world_db["ThingActions"][id] = {
190 def test_for_id_maker(object, category):
191 """Return decorator testing for object having "id" attribute."""
194 if hasattr(object, "id"):
197 print("Ignoring: No " + category +
198 " defined to manipulate yet.")
203 test_Thing_id = test_for_id_maker(command_tid, "Thing")
204 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
205 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
209 def command_tcommand(str_int):
210 """Set T_COMMAND of selected Thing."""
211 val = integer_test(str_int, 0)
213 if 0 == val or val in world_db["ThingActions"]:
214 world_db["Things"][command_tid.id]["T_COMMAND"] = val
216 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
220 def command_ttype(str_int):
221 """Set T_TYPE of selected Thing."""
222 val = integer_test(str_int, 0)
224 if val in world_db["ThingTypes"]:
225 world_db["Things"][command_tid.id]["T_TYPE"] = val
227 print("Ignoring: ThingType ID belongs to no known ThingType.")
231 def command_tcarries(str_int):
232 """Append int(str_int) to T_CARRIES of selected Thing.
234 The ID int(str_int) must not be of the selected Thing, and must belong to a
235 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
237 val = integer_test(str_int, 0)
239 if val == command_tid.id:
240 print("Ignoring: Thing cannot carry itself.")
241 elif val in world_db["Things"] \
242 and not world_db["Things"][val]["carried"]:
243 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
244 world_db["Things"][val]["carried"] = True
246 print("Ignoring: Thing not available for carrying.")
247 # Note that the whole carrying structure is different from the C version:
248 # Carried-ness is marked by a "carried" flag, not by Things containing
253 def command_tmemthing(str_t, str_y, str_x):
254 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
256 The type must fit to an existing ThingType, and the position into the map.
258 type = integer_test(str_t, 0)
259 posy = integer_test(str_y, 0, 255)
260 posx = integer_test(str_x, 0, 255)
261 if None != type and None != posy and None != posx:
262 if type not in world_db["ThingTypes"] \
263 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
264 print("Ignoring: Illegal value for thing type or position.")
266 memthing = (type, posy, posx)
267 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
271 def command_ttname(name):
272 """Set TT_NAME of selected ThingType."""
273 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
277 def command_tttool(name):
278 """Set TT_TOOL of selected ThingType."""
279 world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
283 def command_ttsymbol(char):
284 """Set TT_SYMBOL of selected ThingType. """
286 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
288 print("Ignoring: Argument must be single character.")
292 def command_ttcorpseid(str_int):
293 """Set TT_CORPSE_ID of selected ThingType."""
294 val = integer_test(str_int, 0)
296 if val in world_db["ThingTypes"]:
297 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
299 print("Ignoring: Corpse ID belongs to no known ThignType.")
303 def command_taname(name):
304 """Set TA_NAME of selected ThingAction.
306 The name must match a valid thing action function. If after the name
307 setting no ThingAction with name "wait" remains, call set_world_inactive().
309 if name == "wait" or name == "move" or name == "use" or name == "drop" \
311 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
312 if 1 == world_db["WORLD_ACTIVE"]:
314 for id in world_db["ThingActions"]:
315 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
321 print("Ignoring: Invalid action name.")
322 # In contrast to the original,naming won't map a function to a ThingAction.
325 def setter(category, key, min, max=None):
326 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
329 val = integer_test(val_string, min, max)
333 if category == "Thing":
334 id_store = command_tid
335 decorator = test_Thing_id
336 elif category == "ThingType":
337 id_store = command_ttid
338 decorator = test_ThingType_id
339 elif category == "ThingAction":
340 id_store = command_taid
341 decorator = test_ThingAction_id
345 val = integer_test(val_string, min, max)
347 world_db[category + "s"][id_store.id][key] = val
351 def setter_map(maptype):
352 """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
354 If no map of maptype exists yet, initialize it with ' ' bytes first.
357 def valid_map_line(str_int, mapline):
358 val = integer_test(str_int, 0, 255)
360 if val >= world_db["MAP_LENGTH"]:
361 print("Illegal value for map line number.")
362 elif len(mapline) != world_db["MAP_LENGTH"]:
363 print("Map line length is unequal map width.")
368 def nonThingMap_helper(str_int, mapline):
369 val = valid_map_line(str_int, mapline)
371 length = world_db["MAP_LENGTH"]
372 if not world_db["MAP"]:
373 map = bytearray(b' ' * (length ** 2))
375 map = world_db["MAP"]
376 map[val * length:(val * length) + length] = mapline.encode()
377 if not world_db["MAP"]:
378 world_db["MAP"] = map
381 def ThingMap_helper(str_int, mapline):
382 val = valid_map_line(str_int, mapline)
384 length = world_db["MAP_LENGTH"]
385 if not world_db["Things"][command_tid.id][maptype]:
386 map = bytearray(b' ' * (length ** 2))
388 map = world_db["Things"][command_tid.id][maptype]
389 map[val * length:(val * length) + length] = mapline.encode()
390 if not world_db["Things"][command_tid.id][maptype]:
391 world_db["Things"][command_tid.id][maptype] = map
393 return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
397 def setter_tpos(axis):
398 """Generate setter for T_POSX or T_POSY of selected Thing.
400 If world is active, rebuilds animate things' fovmap, player's memory map.
404 val = integer_test(str_int, 0, 255)
406 if val < world_db["MAP_LENGTH"]:
407 world_db["Things"][command_tid.id]["T_POS" + axis] = val
408 if world_db["WORLD_ACTIVE"] \
409 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
410 build_fov_map(world_db["Things"][command_tid.id])
411 if 0 == command_tid.id:
412 update_map_memory(world_db["Things"][command_tid.id])
414 print("Ignoring: Position is outside of map.")
418 def set_command(action):
419 """Set player's T_COMMAND, then call turn_over()."""
420 id = [x for x in world_db["ThingActions"]
421 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
422 world_db["Things"][0]["T_COMMAND"] = id
427 """Try "wait" as player's T_COMMAND."""
431 def action_exists(action):
432 matching_actions = [x for x in world_db["ThingActions"]
433 if world_db["ThingActions"][x]["TA_NAME"] == action]
434 if len(matching_actions) >= 1:
436 print("No appropriate ThingAction defined.")
441 """Try "pickup" as player's T_COMMAND"."""
442 if action_exists("pickup"):
443 t = world_db["Things"][0]
444 ids = [id for id in world_db["Things"] if id
445 if not world_db["Things"][id]["carried"]
446 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
447 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
449 log("NOTHING to pick up.")
451 set_command("pickup")
454 def play_drop(str_arg):
455 """Try "drop" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
456 if action_exists("drop"):
457 t = world_db["Things"][0]
458 if 0 == len(t["T_CARRIES"]):
459 log("You have NOTHING to drop in your inventory.")
461 val = integer_test(str_arg, 0, 255)
462 if None != val and val < len(t["T_CARRIES"]):
463 world_db["Things"][0]["T_ARGUMENT"] = val
466 print("Illegal inventory index.")
469 def play_use(str_arg):
470 """Try "use" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
471 if action_exists("use"):
472 t = world_db["Things"][0]
473 if 0 == len(t["T_CARRIES"]):
474 log("You have NOTHING to use in your inventory.")
476 val = integer_test(str_arg, 0, 255)
477 if None != val and val < len(t["T_CARRIES"]):
478 id = t["T_CARRIES"][val]
479 type = world_db["Things"][id]["T_TYPE"]
480 if not world_db["ThingTypes"][type]["TT_TOOL"] == "food":
481 log("You CAN'T consume this thing.")
483 world_db["Things"][0]["T_ARGUMENT"] = val
486 print("Illegal inventory index.")
489 def play_move(str_arg):
490 """Try "move" as player's T_COMMAND, str_arg as T_ARGUMENT / direction."""
491 if action_exists("move"):
492 from server.config.world_data import directions_db, symbols_passable
493 t = world_db["Things"][0]
494 if not str_arg in directions_db:
495 print("Illegal move direction string.")
497 dir = ord(directions_db[str_arg])
498 from server.utils import mv_yx_in_dir_legal
499 move_result = mv_yx_in_dir_legal(chr(dir), t["T_POSY"], t["T_POSX"])
500 if 1 == move_result[0]:
501 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
502 if ord("~") == world_db["MAP"][pos]:
503 log("You can't SWIM.")
505 if chr(world_db["MAP"][pos]) in symbols_passable:
506 world_db["Things"][0]["T_ARGUMENT"] = dir
509 log("You CAN'T move there.")
513 """Call ai() on player Thing, then turn_over()."""
514 from server.config.actions import ai_func
515 ai_func(world_db["Things"][0])