home · contact · privacy
Plugin: Add carpentry.
[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             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")
62             else:
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")
69         else:
70             print("Ignoring: Invalid map coordinates.")
71     else:
72         print("Ignoring: Command only works on existing worlds.")
73
74
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)
79     if None != val:
80         rand.seed = val
81
82
83 def command_makeworld(seed_string):
84     """Call make_world()."""
85     val = integer_test(seed_string, 0, 4294967295)
86     if None != val:
87         from server.config.misc import make_world_func
88         make_world_func(val)
89
90
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)
94     if None != val:
95         from server.utils import libpr
96         world_db["MAP_LENGTH"] = val
97         world_db["MAP"] = False
98         set_world_inactive()
99         world_db["Things"] = {}
100         libpr.set_maplength(val)
101
102
103 def command_worldactive(worldactive_string):
104     """Toggle world_db["WORLD_ACTIVE"] if possible.
105
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.
109     """
110     val = integer_test(worldactive_string, 0, 1)
111     if None != val:
112         if 0 != world_db["WORLD_ACTIVE"]:
113             if 0 == val:
114                 set_world_inactive()
115             else:
116                 print("World already active.")
117         elif 0 == world_db["WORLD_ACTIVE"]:
118             wait_exists = False
119             for ThingAction in world_db["ThingActions"]:
120                 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
121                     wait_exists = True
122                     break
123             player_exists = False
124             for Thing in world_db["Things"]:
125                 if 0 == Thing:
126                     player_exists = True
127                     break
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])
132                         if 0 == 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
138             else:
139                 print("Ignoring: Not all conditions for world activation met.")
140
141
142 def command_tid(id_string):
143     """Set ID of Thing to manipulate. ID unused? Create new one.
144
145     Default new Thing's type to the first available ThingType, others: zero.
146     """
147     id = id_setter(id_string, "Things", command_tid)
148     if None != id:
149         if world_db["ThingTypes"] == {}:
150             print("Ignoring: No ThingType to settle new Thing in.")
151             return
152         type = list(world_db["ThingTypes"].keys())[0]
153         from server.new_thing import new_Thing
154         world_db["Things"][id] = new_Thing(type)
155
156
157 def command_ttid(id_string):
158     """Set ID of ThingType to manipulate. ID unused? Create new one.
159
160     Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, TT_TOOL to
161     "", others: 0. 
162     """
163     id = id_setter(id_string, "ThingTypes", command_ttid)
164     if None != id:
165         world_db["ThingTypes"][id] = {
166             "TT_NAME": "(none)",
167             "TT_TOOLPOWER": 0,
168             "TT_LIFEPOINTS": 0,
169             "TT_PROLIFERATE": 0,
170             "TT_START_NUMBER": 0,
171             "TT_SYMBOL": "?",
172             "TT_CORPSE_ID": id,
173             "TT_TOOL": ""
174         }
175
176
177 def command_taid(id_string):
178     """Set ID of ThingAction to manipulate. ID unused? Create new one.
179
180     Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
181     """
182     id = id_setter(id_string, "ThingActions", command_taid, True)
183     if None != id:
184         world_db["ThingActions"][id] = {
185             "TA_EFFORT": 1,
186             "TA_NAME": "wait"
187         }
188
189
190 def test_for_id_maker(object, category):
191     """Return decorator testing for object having "id" attribute."""
192     def decorator(f):
193         def helper(*args):
194             if hasattr(object, "id"):
195                 f(*args)
196             else:
197                 print("Ignoring: No " + category +
198                       " defined to manipulate yet.")
199         return helper
200     return decorator
201
202
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")
206
207
208 @test_Thing_id
209 def command_tcommand(str_int):
210     """Set T_COMMAND of selected Thing."""
211     val = integer_test(str_int, 0)
212     if None != val:
213         if 0 == val or val in world_db["ThingActions"]:
214             world_db["Things"][command_tid.id]["T_COMMAND"] = val
215         else:
216             print("Ignoring: ThingAction ID belongs to no known ThingAction.")
217
218
219 @test_Thing_id
220 def command_ttype(str_int):
221     """Set T_TYPE of selected Thing."""
222     val = integer_test(str_int, 0)
223     if None != val:
224         if val in world_db["ThingTypes"]:
225             world_db["Things"][command_tid.id]["T_TYPE"] = val
226         else:
227             print("Ignoring: ThingType ID belongs to no known ThingType.")
228
229
230 @test_Thing_id
231 def command_tcarries(str_int):
232     """Append int(str_int) to T_CARRIES of selected Thing.
233
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.
236     """
237     val = integer_test(str_int, 0)
238     if None != val:
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
245         else:
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
249     # Things internally.
250
251
252 @test_Thing_id
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.
255
256     The type must fit to an existing ThingType, and the position into the map.
257     """
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.")
265         else:
266             memthing = (type, posy, posx)
267             world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
268
269
270 @test_ThingType_id
271 def command_ttname(name):
272     """Set TT_NAME of selected ThingType."""
273     world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
274
275
276 @test_ThingType_id
277 def command_tttool(name):
278     """Set TT_TOOL of selected ThingType."""
279     world_db["ThingTypes"][command_ttid.id]["TT_TOOL"] = name
280
281
282 @test_ThingType_id
283 def command_ttsymbol(char):
284     """Set TT_SYMBOL of selected ThingType. """
285     if 1 == len(char):
286         world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
287     else:
288         print("Ignoring: Argument must be single character.")
289
290
291 @test_ThingType_id
292 def command_ttcorpseid(str_int):
293     """Set TT_CORPSE_ID of selected ThingType."""
294     val = integer_test(str_int, 0)
295     if None != val:
296         if val in world_db["ThingTypes"]:
297             world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
298         else:
299             print("Ignoring: Corpse ID belongs to no known ThignType.")
300
301
302 @test_ThingAction_id
303 def command_taname(name):
304     """Set TA_NAME of selected ThingAction.
305
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().
308     """
309     if name == "wait" or name == "move" or name == "use" or name == "drop" \
310        or name == "pickup":
311         world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
312         if 1 == world_db["WORLD_ACTIVE"]:
313             wait_defined = False
314             for id in world_db["ThingActions"]:
315                 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
316                     wait_defined = True
317                     break
318             if not wait_defined:
319                 set_world_inactive()
320     else:
321         print("Ignoring: Invalid action name.")
322     # In contrast to the original,naming won't map a function to a ThingAction.
323
324
325 def setter(category, key, min, max=None):
326     """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
327     if category is None:
328         def f(val_string):
329             val = integer_test(val_string, min, max)
330             if None != val:
331                 world_db[key] = val
332     else:
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
342
343         @decorator
344         def f(val_string):
345             val = integer_test(val_string, min, max)
346             if None != val:
347                 world_db[category + "s"][id_store.id][key] = val
348     return f
349
350
351 def setter_map(maptype):
352     """Set (world or Thing's) map of maptype's int(str_int)-th line to mapline.
353
354     If no map of maptype exists yet, initialize it with ' ' bytes first.
355     """
356
357     def valid_map_line(str_int, mapline):
358         val = integer_test(str_int, 0, 255)
359         if None != val:
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.")
364             else:
365                 return val
366         return None
367
368     def nonThingMap_helper(str_int, mapline):
369         val = valid_map_line(str_int, mapline)
370         if None != val:
371             length = world_db["MAP_LENGTH"]
372             if not world_db["MAP"]:
373                 map = bytearray(b' ' * (length ** 2))
374             else:
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
379
380     @test_Thing_id
381     def ThingMap_helper(str_int, mapline):
382         val = valid_map_line(str_int, mapline)
383         if None != val:
384             length = world_db["MAP_LENGTH"]
385             if not world_db["Things"][command_tid.id][maptype]:
386                 map = bytearray(b' ' * (length ** 2))
387             else:
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
392
393     return nonThingMap_helper if maptype == "MAP" else ThingMap_helper
394
395
396
397 def setter_tpos(axis):
398     """Generate setter for T_POSX or  T_POSY of selected Thing.
399
400     If world is active, rebuilds animate things' fovmap, player's memory map.
401     """
402     @test_Thing_id
403     def helper(str_int):
404         val = integer_test(str_int, 0, 255)
405         if None != val:
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])
413             else:
414                 print("Ignoring: Position is outside of map.")
415     return helper
416
417
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
423     turn_over()
424
425
426 def play_wait():
427     """Try "wait" as player's T_COMMAND."""
428     set_command("wait")
429
430
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:
435         return True
436     print("No appropriate ThingAction defined.")
437     return False
438
439
440 def play_pickup():
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"]]
448         if not len(ids):
449              log("NOTHING to pick up.")
450         else:
451             set_command("pickup")
452
453
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.")
460         else:
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
464                 set_command("drop")
465             else:
466                 print("Illegal inventory index.")
467
468
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.")
475         else:
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.")
482                     return
483                 world_db["Things"][0]["T_ARGUMENT"] = val
484                 set_command("use")
485             else:
486                 print("Illegal inventory index.")
487
488
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.")
496             return
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.")
504                 return
505             if chr(world_db["MAP"][pos]) in symbols_passable:
506                 world_db["Things"][0]["T_ARGUMENT"] = dir
507                 set_command("move")
508                 return
509         log("You CAN'T move there.")
510
511
512 def command_ai():
513     """Call ai() on player Thing, then turn_over()."""
514     from server.config.actions import ai_func
515     ai_func(world_db["Things"][0])
516     turn_over()