home · contact · privacy
Server: Split in many files, reorganize code slightly to fit change.
[plomrogue] / server / commands.py
1 from server.config.world_data import world_db
2 from server.config.io import io_db
3 from server.io import log, strong_write 
4 from server.utils import integer_test, id_setter
5 from server.world import build_fov_map, update_map_memory, set_world_inactive,\
6         turn_over
7
8
9 def command_plugin(str_plugin):
10     """Run code in plugins/[str_plugin]."""
11     import os
12     if (str_plugin.replace("_", "").isalnum()
13         and os.access("plugins/" + str_plugin, os.F_OK)):
14         exec(open("plugins/" + str_plugin).read())
15         return
16     print("Bad plugin name:", str_plugin)
17
18
19 def command_ping():
20     """Send PONG line to server output file."""
21     strong_write(io_db["file_out"], "PONG\n")
22
23
24 def command_quit():
25     """Abort server process."""
26     from server.io import save_world, atomic_write
27     from server.utils import opts
28     if None == opts.replay:
29         if world_db["WORLD_ACTIVE"]:
30             save_world()
31         atomic_write(io_db["path_record"], io_db["record_chunk"],
32             do_append=True)
33     raise SystemExit("received QUIT command")
34
35
36 def command_thingshere(str_y, str_x):
37     """Write to out file list of Things known to player at coordinate y, x."""
38     if world_db["WORLD_ACTIVE"]:
39         y = integer_test(str_y, 0, 255)
40         x = integer_test(str_x, 0, 255)
41         length = world_db["MAP_LENGTH"]
42         if None != y and None != x and y < length and x < length:
43             pos = (y * world_db["MAP_LENGTH"]) + x
44             strong_write(io_db["file_out"], "THINGS_HERE START\n")
45             if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
46                 for id in [id for tid in sorted(list(world_db["ThingTypes"]))
47                               for id in world_db["Things"]
48                               if not world_db["Things"][id]["carried"]
49                               if world_db["Things"][id]["T_TYPE"] == tid
50                               if y == world_db["Things"][id]["T_POSY"]
51                               if x == world_db["Things"][id]["T_POSX"]]:
52                     type = world_db["Things"][id]["T_TYPE"]
53                     name = world_db["ThingTypes"][type]["TT_NAME"]
54                     strong_write(io_db["file_out"], name + "\n")
55             else:
56                 for mt in [mt for tid in sorted(list(world_db["ThingTypes"]))
57                               for mt in world_db["Things"][0]["T_MEMTHING"]
58                               if mt[0] == tid if y == mt[1] if x == mt[2]]:
59                     name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
60                     strong_write(io_db["file_out"], name + "\n")
61             strong_write(io_db["file_out"], "THINGS_HERE END\n")
62         else:
63             print("Ignoring: Invalid map coordinates.")
64     else:
65         print("Ignoring: Command only works on existing worlds.")
66
67
68 def command_seedrandomness(seed_string):
69     """Set rand seed to int(seed_string)."""
70     from server.utils import rand
71     val = integer_test(seed_string, 0, 4294967295)
72     if None != val:
73         rand.seed = val
74
75
76 def command_makeworld(seed_string):
77     """Call make_world()."""
78     val = integer_test(seed_string, 0, 4294967295)
79     if None != val:
80         from server.world import make_world
81         make_world(val)
82
83
84 def command_maplength(maplength_string):
85     """Redefine map length. Invalidate map, therefore lose all things on it."""
86     val = integer_test(maplength_string, 1, 256)
87     if None != val:
88         from server.utils import libpr
89         world_db["MAP_LENGTH"] = val
90         world_db["MAP"] = False
91         set_world_inactive()
92         world_db["Things"] = {}
93         libpr.set_maplength(val)
94
95
96 def command_worldactive(worldactive_string):
97     """Toggle world_db["WORLD_ACTIVE"] if possible.
98
99     An active world can always be set inactive. An inactive world can only be
100     set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
101     map. On activation, rebuild all Things' FOVs, and the player's map memory.
102     """
103     val = integer_test(worldactive_string, 0, 1)
104     if None != val:
105         if 0 != world_db["WORLD_ACTIVE"]:
106             if 0 == val:
107                 set_world_inactive()
108             else:
109                 print("World already active.")
110         elif 0 == world_db["WORLD_ACTIVE"]:
111             wait_exists = False
112             for ThingAction in world_db["ThingActions"]:
113                 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
114                     wait_exists = True
115                     break
116             player_exists = False
117             for Thing in world_db["Things"]:
118                 if 0 == Thing:
119                     player_exists = True
120                     break
121             if wait_exists and player_exists and world_db["MAP"]:
122                 for id in world_db["Things"]:
123                     if world_db["Things"][id]["T_LIFEPOINTS"]:
124                         build_fov_map(world_db["Things"][id])
125                         if 0 == id:
126                             update_map_memory(world_db["Things"][id], False)
127                 if not world_db["Things"][0]["T_LIFEPOINTS"]:
128                     empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
129                     world_db["Things"][0]["fovmap"] = empty_fovmap
130                 world_db["WORLD_ACTIVE"] = 1
131             else:
132                 print("Ignoring: Not all conditions for world activation met.")
133
134
135 def command_tid(id_string):
136     """Set ID of Thing to manipulate. ID unused? Create new one.
137
138     Default new Thing's type to the first available ThingType, others: zero.
139     """
140     id = id_setter(id_string, "Things", command_tid)
141     if None != id:
142         if world_db["ThingTypes"] == {}:
143             print("Ignoring: No ThingType to settle new Thing in.")
144             return
145         type = list(world_db["ThingTypes"].keys())[0]
146         from server.world import new_Thing
147         world_db["Things"][id] = new_Thing(type)
148
149
150 def command_ttid(id_string):
151     """Set ID of ThingType to manipulate. ID unused? Create new one.
152
153     Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
154     "", others: 0. 
155     """
156     id = id_setter(id_string, "ThingTypes", command_ttid)
157     if None != id:
158         world_db["ThingTypes"][id] = {
159             "TT_NAME": "(none)",
160             "TT_TOOLPOWER": 0,
161             "TT_LIFEPOINTS": 0,
162             "TT_PROLIFERATE": 0,
163             "TT_START_NUMBER": 0,
164             "TT_SYMBOL": "?",
165             "TT_CORPSE_ID": id,
166             "TT_TOOL": ""
167         }
168
169
170 def command_taid(id_string):
171     """Set ID of ThingAction to manipulate. ID unused? Create new one.
172
173     Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
174     """
175     id = id_setter(id_string, "ThingActions", command_taid, True)
176     if None != id:
177         world_db["ThingActions"][id] = {
178             "TA_EFFORT": 1,
179             "TA_NAME": "wait"
180         }
181
182
183 def test_for_id_maker(object, category):
184     """Return decorator testing for object having "id" attribute."""
185     def decorator(f):
186         def helper(*args):
187             if hasattr(object, "id"):
188                 f(*args)
189             else:
190                 print("Ignoring: No " + category +
191                       " defined to manipulate yet.")
192         return helper
193     return decorator
194
195
196 test_Thing_id = test_for_id_maker(command_tid, "Thing")
197 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
198 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
199
200
201 @test_Thing_id
202 def command_tcommand(str_int):
203     """Set T_COMMAND of selected Thing."""
204     val = integer_test(str_int, 0)
205     if None != val:
206         if 0 == val or val in world_db["ThingActions"]:
207             world_db["Things"][command_tid.id]["T_COMMAND"] = val
208         else:
209             print("Ignoring: ThingAction ID belongs to no known ThingAction.")
210
211
212 @test_Thing_id
213 def command_ttype(str_int):
214     """Set T_TYPE of selected Thing."""
215     val = integer_test(str_int, 0)
216     if None != val:
217         if val in world_db["ThingTypes"]:
218             world_db["Things"][command_tid.id]["T_TYPE"] = val
219         else:
220             print("Ignoring: ThingType ID belongs to no known ThingType.")
221
222
223 @test_Thing_id
224 def command_tcarries(str_int):
225     """Append int(str_int) to T_CARRIES of selected Thing.
226
227     The ID int(str_int) must not be of the selected Thing, and must belong to a
228     Thing with unset "carried" flag. Its "carried" flag will be set on owning.
229     """
230     val = integer_test(str_int, 0)
231     if None != val:
232         if val == command_tid.id:
233             print("Ignoring: Thing cannot carry itself.")
234         elif val in world_db["Things"] \
235                 and not world_db["Things"][val]["carried"]:
236             world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
237             world_db["Things"][val]["carried"] = True
238         else:
239             print("Ignoring: Thing not available for carrying.")
240     # Note that the whole carrying structure is different from the C version:
241     # Carried-ness is marked by a "carried" flag, not by Things containing
242     # Things internally.
243
244
245 @test_Thing_id
246 def command_tmemthing(str_t, str_y, str_x):
247     """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
248
249     The type must fit to an existing ThingType, and the position into the map.
250     """
251     type = integer_test(str_t, 0)
252     posy = integer_test(str_y, 0, 255)
253     posx = integer_test(str_x, 0, 255)
254     if None != type and None != posy and None != posx:
255         if type not in world_db["ThingTypes"] \
256            or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
257             print("Ignoring: Illegal value for thing type or position.")
258         else:
259             memthing = (type, posy, posx)
260             world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
261
262
263 @test_ThingType_id
264 def command_ttname(name):
265     """Set TT_NAME of selected ThingType."""
266     world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
267
268
269 @test_ThingType_id
270 def command_tttool(name):
271     """Set TT_TOOL of selected ThingType."""
272     world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
273
274
275 @test_ThingType_id
276 def command_ttsymbol(char):
277     """Set TT_SYMBOL of selected ThingType. """
278     if 1 == len(char):
279         world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
280     else:
281         print("Ignoring: Argument must be single character.")
282
283
284 @test_ThingType_id
285 def command_ttcorpseid(str_int):
286     """Set TT_CORPSE_ID of selected ThingType."""
287     val = integer_test(str_int, 0)
288     if None != val:
289         if val in world_db["ThingTypes"]:
290             world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
291         else:
292             print("Ignoring: Corpse ID belongs to no known ThignType.")
293
294
295 @test_ThingAction_id
296 def command_taname(name):
297     """Set TA_NAME of selected ThingAction.
298
299     The name must match a valid thing action function. If after the name
300     setting no ThingAction with name "wait" remains, call set_world_inactive().
301     """
302     if name == "wait" or name == "move" or name == "use" or name == "drop" \
303        or name == "pick_up":
304         world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
305         if 1 == world_db["WORLD_ACTIVE"]:
306             wait_defined = False
307             for id in world_db["ThingActions"]:
308                 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
309                     wait_defined = True
310                     break
311             if not wait_defined:
312                 set_world_inactive()
313     else:
314         print("Ignoring: Invalid action name.")
315     # In contrast to the original,naming won't map a function to a ThingAction.
316
317
318 def setter(category, key, min, max=None):
319     """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
320     if category is None:
321         def f(val_string):
322             val = integer_test(val_string, min, max)
323             if None != val:
324                 world_db[key] = val
325     else:
326         if category == "Thing":
327             id_store = command_tid
328             decorator = test_Thing_id
329         elif category == "ThingType":
330             id_store = command_ttid
331             decorator = test_ThingType_id
332         elif category == "ThingAction":
333             id_store = command_taid
334             decorator = test_ThingAction_id
335
336         @decorator
337         def f(val_string):
338             val = integer_test(val_string, min, max)
339             if None != val:
340                 world_db[category + "s"][id_store.id][key] = val
341     return f
342
343
344 def setter_map(maptype):
345     """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
346
347     If no map of maptype exists yet, initialize it with ' ' bytes first.
348     """
349
350     def valid_map_line(str_int, mapline):
351         val = integer_test(str_int, 0, 255)
352         if None != val:
353             if val >= world_db["MAP_LENGTH"]:
354                 print("Illegal value for map line number.")
355             elif len(mapline) != world_db["MAP_LENGTH"]:
356                 print("Map line length is unequal map width.")
357             else:
358                 return val
359         return None
360
361     def nonThingMap_helper(str_int, mapline):
362         val = valid_map_line(str_int, mapline)
363         if None != val:
364             length = world_db["MAP_LENGTH"]
365             if not world_db["MAP"]:
366                 map = bytearray(b' ' * (length ** 2))
367             else:
368                 map = world_db["MAP"]
369             map[val * length:(val * length) + length] = mapline.encode()
370             if not world_db["MAP"]:
371                 world_db["MAP"] = map
372
373     @test_Thing_id
374     def ThingMap_helper(str_int, mapline):
375         val = valid_map_line(str_int, mapline)
376         if None != val:
377             length = world_db["MAP_LENGTH"]
378             if not world_db["Things"][command_tid.id][maptype]:
379                 map = bytearray(b' ' * (length ** 2))
380             else:
381                 map = world_db["Things"][command_tid.id][maptype]
382             map[val * length:(val * length) + length] = mapline.encode()
383             if not world_db["Things"][command_tid.id][maptype]:
384                 world_db["Things"][command_tid.id][maptype] = map
385
386     return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
387
388
389
390 def setter_tpos(axis):
391     """Generate setter for T_POSX or  T_POSY of selected Thing.
392
393     If world is active, rebuilds animate things' fovmap, player's memory map.
394     """
395     @test_Thing_id
396     def helper(str_int):
397         val = integer_test(str_int, 0, 255)
398         if None != val:
399             if val < world_db["MAP_LENGTH"]:
400                 world_db["Things"][command_tid.id]["T_POS" + axis] = val
401                 if world_db["WORLD_ACTIVE"] \
402                    and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
403                     build_fov_map(world_db["Things"][command_tid.id])
404                     if 0 == command_tid.id:
405                         update_map_memory(world_db["Things"][command_tid.id])
406             else:
407                 print("Ignoring: Position is outside of map.")
408     return helper
409
410
411 def set_command(action):
412     """Set player's T_COMMAND, then call turn_over()."""
413     id = [x for x in world_db["ThingActions"]
414           if world_db["ThingActions"][x]["TA_NAME"] == action][0]
415     world_db["Things"][0]["T_COMMAND"] = id
416     turn_over()
417
418
419 def play_wait():
420     """Try "wait" as player's T_COMMAND."""
421     set_command("wait")
422
423
424 def play_pickup():
425     """Try "pick_up" as player's T_COMMAND"."""
426     t = world_db["Things"][0]
427     ids = [id for id in world_db["Things"] if id
428            if not world_db["Things"][id]["carried"]
429            if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
430            if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
431     if not len(ids):
432          log("NOTHING to pick up.")
433     else:
434         set_command("pick_up")
435
436
437 def play_drop(str_arg):
438     """Try "drop" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
439     t = world_db["Things"][0]
440     if 0 == len(t["T_CARRIES"]):
441         log("You have NOTHING to drop in your inventory.")
442     else:
443         val = integer_test(str_arg, 0, 255)
444         if None != val and val < len(t["T_CARRIES"]):
445             world_db["Things"][0]["T_ARGUMENT"] = val
446             set_command("drop")
447         else:
448             print("Illegal inventory index.")
449
450
451 def play_use(str_arg):
452     """Try "use" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
453     t = world_db["Things"][0]
454     if 0 == len(t["T_CARRIES"]):
455         log("You have NOTHING to use in your inventory.")
456     else:
457         val = integer_test(str_arg, 0, 255)
458         if None != val and val < len(t["T_CARRIES"]):
459             id = t["T_CARRIES"][val]
460             type = world_db["Things"][id]["T_TYPE"]
461             if not world_db["ThingTypes"][type]["TT_TOOL"] == "food":
462                 log("You CAN'T consume this thing.")
463                 return
464             world_db["Things"][0]["T_ARGUMENT"] = val
465             set_command("use")
466         else:
467             print("Illegal inventory index.")
468
469
470 def play_move(str_arg):
471     """Try "move" as player's T_COMMAND, str_arg as T_ARGUMENT / direction."""
472     from server.config.world_data import directions_db
473     t = world_db["Things"][0]
474     if not str_arg in directions_db:
475         print("Illegal move direction string.")
476         return
477     dir = ord(directions_db[str_arg])
478     from server.utils import mv_yx_in_dir_legal
479     move_result = mv_yx_in_dir_legal(chr(dir), t["T_POSY"], t["T_POSX"])
480     if 1 == move_result[0]:
481         pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
482         if ord(".") == world_db["MAP"][pos]:
483             world_db["Things"][0]["T_ARGUMENT"] = dir
484             set_command("move")
485             return
486     log("You CAN'T move there.")
487
488
489 def command_ai():
490     """Call ai() on player Thing, then turn_over()."""
491     from server.ai import ai
492     ai(world_db["Things"][0])
493     turn_over()