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