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