1
2 u"""Miscellaneous functions for the mudpy engine."""
3
4
5
6
7
9 u"""An element of the universe."""
10 - def __init__(self, key, universe, filename=None):
11 u"""Set up a new element."""
12 import data, os.path
13
14
15 self.key = key
16
17
18 self.universe = universe
19
20
21 if self.key in self.universe.contents:
22 old_element = self.universe.contents[self.key]
23 for attribute in vars(old_element).keys():
24 exec(u"self." + attribute + u" = old_element." + attribute)
25 if self.owner: self.owner.avatar = self
26
27
28 else:
29
30
31 self.owner = None
32
33
34 self.contents = {}
35
36
37 if self.key.find(u":") > 0:
38 self.category, self.subkey = self.key.split(u":", 1)
39 else:
40 self.category = u"other"
41 self.subkey = self.key
42 if not self.category in self.universe.categories:
43 self.category = u"other"
44 self.subkey = self.key
45
46
47 if not filename:
48 filename = self.universe.default_origins[self.category]
49 if not os.path.isabs(filename):
50 filename = os.path.abspath(filename)
51
52
53 if not filename in self.universe.files:
54 data.DataFile(filename, self.universe)
55
56
57 self.origin = self.universe.files[filename]
58
59
60 if not self.origin.data.has_section(self.key):
61 self.origin.data.add_section(self.key)
62
63
64 self.universe.contents[self.key] = self
65 self.universe.categories[self.category][self.subkey] = self
66
68 u"""Create a new element and replace this one."""
69 new_element = Element(self.key, self.universe, self.origin.filename)
70 del(self)
72 u"""Remove an element from the universe and destroy it."""
73 self.origin.data.remove_section(self.key)
74 del self.universe.categories[self.category][self.subkey]
75 del self.universe.contents[self.key]
76 del self
78 u"""Return a list of non-inherited facets for this element."""
79 if self.key in self.origin.data.sections():
80 return self.origin.data.options(self.key)
81 else: return []
83 u"""Return whether the non-inherited facet exists."""
84 return facet in self.facets()
86 u"""Remove a facet from the element."""
87 if self.has_facet(facet):
88 self.origin.data.remove_option(self.key, facet)
89 self.origin.modified = True
100 - def get(self, facet, default=None):
101 u"""Retrieve values."""
102 if default is None: default = u""
103 if self.origin.data.has_option(self.key, facet):
104 raw_data = self.origin.data.get(self.key, facet)
105 if raw_data.startswith(u"u\"") or raw_data.startswith(u"u'"):
106 raw_data = raw_data[1:]
107 raw_data.strip(u"\"'")
108 if type(raw_data) == str: return unicode(raw_data, "utf-8")
109 else: return raw_data
110 elif self.has_facet(u"inherit"):
111 for ancestor in self.ancestry():
112 if self.universe.contents[ancestor].has_facet(facet):
113 return self.universe.contents[ancestor].get(facet)
114 else: return default
116 u"""Retrieve values as boolean type."""
117 if default is None: default=False
118 if self.origin.data.has_option(self.key, facet):
119 return self.origin.data.getboolean(self.key, facet)
120 elif self.has_facet(u"inherit"):
121 for ancestor in self.ancestry():
122 if self.universe.contents[ancestor].has_facet(facet):
123 return self.universe.contents[ancestor].getboolean(facet)
124 else: return default
125 - def getint(self, facet, default=None):
126 u"""Return values as int/long type."""
127 if default is None: default = 0
128 if self.origin.data.has_option(self.key, facet):
129 return self.origin.data.getint(self.key, facet)
130 elif self.has_facet(u"inherit"):
131 for ancestor in self.ancestry():
132 if self.universe.contents[ancestor].has_facet(facet):
133 return self.universe.contents[ancestor].getint(facet)
134 else: return default
135 - def getfloat(self, facet, default=None):
136 u"""Return values as float type."""
137 if default is None: default = 0.0
138 if self.origin.data.has_option(self.key, facet):
139 return self.origin.data.getfloat(self.key, facet)
140 elif self.has_facet(u"inherit"):
141 for ancestor in self.ancestry():
142 if self.universe.contents[ancestor].has_facet(facet):
143 return self.universe.contents[ancestor].getfloat(facet)
144 else: return default
145 - def getlist(self, facet, default=None):
146 u"""Return values as list type."""
147 import data
148 if default is None: default = []
149 value = self.get(facet)
150 if value: return data.makelist(value)
151 else: return default
152 - def getdict(self, facet, default=None):
153 u"""Return values as dict type."""
154 import data
155 if default is None: default = {}
156 value = self.get(facet)
157 if value: return data.makedict(value)
158 else: return default
159 - def set(self, facet, value):
160 u"""Set values."""
161 if not self.has_facet(facet) or not self.get(facet) == value:
162 if type(value) is long: value = unicode(value)
163 elif not type(value) is unicode: value = repr(value)
164 self.origin.data.set(self.key, facet, value)
165 self.origin.modified = True
166 - def append(self, facet, value):
167 u"""Append value tp a list."""
168 if type(value) is long: value = unicode(value)
169 elif not type(value) is unicode: value = repr(value)
170 newlist = self.getlist(facet)
171 newlist.append(value)
172 self.set(facet, newlist)
173
175 u"""Create, attach and enqueue an event element."""
176
177
178 if not when: when = self.universe.get_time()
179
180
181 event = Element(u"event:" + self.key + u":" + counter)
182
183 - def send(
184 self,
185 message,
186 eol=u"$(eol)",
187 raw=False,
188 flush=False,
189 add_prompt=True,
190 just_prompt=False,
191 add_terminator=False,
192 prepend_padding=True
193 ):
194 u"""Convenience method to pass messages to an owner."""
195 if self.owner:
196 self.owner.send(
197 message,
198 eol,
199 raw,
200 flush,
201 add_prompt,
202 just_prompt,
203 add_terminator,
204 prepend_padding
205 )
206
208 u"""Check if the user can run this command object."""
209
210
211 if command not in self.universe.categories[u"command"].values():
212 result = False
213
214
215 elif self.owner and self.owner.account.getboolean(u"administrator"):
216 result = True
217
218
219 elif not command.getboolean(u"administrative"):
220 result = True
221
222
223 else:
224 result = False
225
226
227 return result
228
230 u"""Make sure the location's contents contain this element."""
231 location = self.get(u"location")
232 if location in self.universe.contents:
233 self.universe.contents[location].contents[self.key] = self
234 - def clean_contents(self):
235 u"""Make sure the element's contents aren't bogus."""
236 for element in self.contents.values():
237 if element.get(u"location") != self.key:
238 del self.contents[element.key]
239 - def go_to(self, location):
240 u"""Relocate the element to a specific location."""
241 current = self.get(u"location")
242 if current and self.key in self.universe.contents[current].contents:
243 del universe.contents[current].contents[self.key]
244 if location in self.universe.contents: self.set(u"location", location)
245 self.universe.contents[location].contents[self.key] = self
246 self.look_at(location)
248 u"""Relocate the element to its default location."""
249 self.go_to(self.get(u"default_location"))
250 self.echo_to_location(
251 u"You suddenly realize that " + self.get(u"name") + u" is here."
252 )
254 u"""Relocate the element in a specified direction."""
255 self.echo_to_location(
256 self.get(
257 u"name"
258 ) + u" exits " + self.universe.categories[
259 u"internal"
260 ][
261 u"directions"
262 ].getdict(
263 direction
264 )[
265 u"exit"
266 ] + u"."
267 )
268 self.send(
269 u"You exit " + self.universe.categories[
270 u"internal"
271 ][
272 u"directions"
273 ].getdict(
274 direction
275 )[
276 u"exit"
277 ] + u".",
278 add_prompt=False
279 )
280 self.go_to(
281 self.universe.contents[self.get(u"location")].link_neighbor(direction)
282 )
283 self.echo_to_location(
284 self.get(
285 u"name"
286 ) + u" arrives from " + self.universe.categories[
287 u"internal"
288 ][
289 u"directions"
290 ].getdict(
291 direction
292 )[
293 u"enter"
294 ] + u"."
295 )
297 u"""Show an element to another element."""
298 if self.owner:
299 element = self.universe.contents[key]
300 message = u""
301 name = element.get(u"name")
302 if name: message += u"$(cyn)" + name + u"$(nrm)$(eol)"
303 description = element.get(u"description")
304 if description: message += description + u"$(eol)"
305 portal_list = element.portals().keys()
306 if portal_list:
307 portal_list.sort()
308 message += u"$(cyn)[ Exits: " + u", ".join(
309 portal_list
310 ) + u" ]$(nrm)$(eol)"
311 for element in self.universe.contents[
312 self.get(u"location")
313 ].contents.values():
314 if element.getboolean(u"is_actor") and element is not self:
315 message += u"$(yel)" + element.get(
316 u"name"
317 ) + u" is here.$(nrm)$(eol)"
318 elif element is not self:
319 message += u"$(grn)" + element.get(
320 u"impression"
321 ) + u"$(nrm)$(eol)"
322 self.send(message)
324 u"""Map the portal directions for an area to neighbors."""
325 import re
326 portals = {}
327 if re.match(u"""^location:-?\d+,-?\d+,-?\d+$""", self.key):
328 coordinates = [(int(x)) for x in self.key.split(u":")[1].split(u",")]
329 directions = self.universe.categories[u"internal"][u"directions"]
330 offsets = dict(
331 [
332 (
333 x, directions.getdict(x)[u"vector"]
334 ) for x in directions.facets()
335 ]
336 )
337 for portal in self.getlist(u"gridlinks"):
338 adjacent = map(lambda c,o: c+o, coordinates, offsets[portal])
339 neighbor = u"location:" + u",".join(
340 [(unicode(x)) for x in adjacent]
341 )
342 if neighbor in self.universe.contents: portals[portal] = neighbor
343 for facet in self.facets():
344 if facet.startswith(u"link_"):
345 neighbor = self.get(facet)
346 if neighbor in self.universe.contents:
347 portal = facet.split(u"_")[1]
348 portals[portal] = neighbor
349 return portals
351 u"""Return the element linked in a given direction."""
352 portals = self.portals()
353 if direction in portals: return portals[direction]
355 u"""Show a message to other elements in the current location."""
356 for element in self.universe.contents[
357 self.get(u"location")
358 ].contents.values():
359 if element is not self: element.send(message)
360
362 u"""The universe."""
363
364 - def __init__(self, filename=u"", load=False):
365 u"""Initialize the universe."""
366 import os, os.path
367 self.categories = {}
368 self.contents = {}
369 self.default_origins = {}
370 self.loglines = []
371 self.pending_events_long = {}
372 self.pending_events_short = {}
373 self.private_files = []
374 self.reload_flag = False
375 self.startdir = os.getcwd()
376 self.terminate_flag = False
377 self.userlist = []
378 if not filename:
379 possible_filenames = [
380 u".mudpyrc",
381 u".mudpy/mudpyrc",
382 u".mudpy/mudpy.conf",
383 u"mudpy.conf",
384 u"etc/mudpy.conf",
385 u"/usr/local/mudpy/mudpy.conf",
386 u"/usr/local/mudpy/etc/mudpy.conf",
387 u"/etc/mudpy/mudpy.conf",
388 u"/etc/mudpy.conf"
389 ]
390 for filename in possible_filenames:
391 if os.access(filename, os.R_OK): break
392 if not os.path.isabs(filename):
393 filename = os.path.join(self.startdir, filename)
394 self.filename = filename
395 if load: self.load()
396
398 u"""Load universe data from persistent storage."""
399 import data
400
401
402 if not hasattr(
403 self, u"files"
404 ) or not (
405 self.filename in self.files and self.files[
406 self.filename
407 ].is_writeable()
408 ):
409
410
411 if hasattr(self, u"files"):
412 for data_filename in self.files.keys():
413 if not self.files[data_filename].is_writeable():
414 del self.files[data_filename]
415
416
417 data.DataFile(self.filename, self)
418
419
420 inactive_avatars = []
421 for account in self.categories[u"account"].values():
422 inactive_avatars += [
423 (self.contents[x]) for x in account.getlist(u"avatars")
424 ]
425 for user in self.userlist:
426 if user.avatar in inactive_avatars:
427 inactive_avatars.remove(user.avatar)
428
429
430 for element in self.contents.values():
431 location = element.get(u"location")
432 if element in inactive_avatars and location:
433 if location in self.contents and element.key in self.contents[
434 location
435 ].contents:
436 del self.contents[location].contents[element.key]
437 element.set(u"default_location", location)
438 element.remove_facet(u"location")
439
440
441 for element in self.contents.values():
442 element.update_location()
443 element.clean_contents()
444
446 u"""Create a new, empty Universe (the Big Bang)."""
447 new_universe = Universe()
448 for attribute in vars(self).keys():
449 exec(u"new_universe." + attribute + u" = self." + attribute)
450 new_universe.reload_flag = False
451 del self
452 return new_universe
453
455 u"""Save the universe to persistent storage."""
456 for key in self.files: self.files[key].save()
457
459 u"""Create and open the listening socket."""
460 import socket
461
462
463 host = self.categories[u"internal"][u"network"].get(u"host")
464 port = self.categories[u"internal"][u"network"].getint(u"port")
465
466
467 if not host:
468 if socket.has_ipv6: host = u"::"
469 else: host = u"0.0.0.0"
470
471
472 family = socket.getaddrinfo(host, port)[0][0]
473 if family is socket.AF_INET6 and not socket.has_ipv6:
474 log(u"No support for IPv6 address %s (use IPv4 instead)." % host)
475
476
477 self.listening_socket = socket.socket(family, socket.SOCK_STREAM)
478
479
480
481
482 self.listening_socket.setsockopt(
483 socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
484 )
485
486
487 self.listening_socket.bind((host, port))
488
489
490
491 self.listening_socket.setblocking(0)
492
493
494 self.listening_socket.listen(1)
495
496
497 log(
498 u"Listening for Telnet connections on: " + host + u":" + unicode(port)
499 )
500
502 u"""Convenience method to get the elapsed time counter."""
503 return self.categories[u"internal"][u"counters"].getint(u"elapsed")
504
506 u"""This is a connected user."""
507
509 u"""Default values for the in-memory user variables."""
510 import telnet
511 self.account = None
512 self.address = u""
513 self.authenticated = False
514 self.avatar = None
515 self.columns = 79
516 self.connection = None
517 self.error = u""
518 self.input_queue = []
519 self.last_address = u""
520 self.last_input = universe.get_time()
521 self.menu_choices = {}
522 self.menu_seen = False
523 self.negotiation_pause = 0
524 self.output_queue = []
525 self.partial_input = ""
526 self.password_tries = 0
527 self.state = u"initial"
528 self.telopts = {}
529
531 u"""Log, close the connection and remove."""
532 if self.account: name = self.account.get(u"name")
533 else: name = u""
534 if name: message = u"User " + name
535 else: message = u"An unnamed user"
536 message += u" logged out."
537 log(message, 2)
538 self.deactivate_avatar()
539 self.connection.close()
540 self.remove()
541
543 u"""Warn or disconnect idle users as appropriate."""
544 idletime = universe.get_time() - self.last_input
545 linkdead_dict = universe.categories[u"internal"][u"time"].getdict(
546 u"linkdead"
547 )
548 if self.state in linkdead_dict: linkdead_state = self.state
549 else: linkdead_state = u"default"
550 if idletime > linkdead_dict[linkdead_state]:
551 self.send(
552 u"$(eol)$(red)You've done nothing for far too long... goodbye!" \
553 + u"$(nrm)$(eol)",
554 flush=True,
555 add_prompt=False
556 )
557 logline = u"Disconnecting "
558 if self.account and self.account.get(u"name"):
559 logline += self.account.get(u"name")
560 else:
561 logline += u"an unknown user"
562 logline += u" after idling too long in the " + self.state + u" state."
563 log(logline, 2)
564 self.state = u"disconnecting"
565 self.menu_seen = False
566 idle_dict = universe.categories[u"internal"][u"time"].getdict(u"idle")
567 if self.state in idle_dict: idle_state = self.state
568 else: idle_state = u"default"
569 if idletime == idle_dict[idle_state]:
570 self.send(
571 u"$(eol)$(red)If you continue to be unproductive, " \
572 + u"you'll be shown the door...$(nrm)$(eol)"
573 )
574
576 u"""Save, load a new user and relocate the connection."""
577
578
579 self.remove()
580
581
582 new_user = User()
583
584
585 for attribute in vars(self).keys():
586 exec(u"new_user." + attribute + u" = self." + attribute)
587
588
589 if new_user.avatar: new_user.avatar.owner = new_user
590
591
592 universe.userlist.append(new_user)
593
594
595 del(self)
596
598 u"""Disconnect active users with the same name."""
599
600
601 return_value = False
602
603
604 for old_user in universe.userlist:
605
606
607 if hasattr(
608 old_user, u"account"
609 ) and old_user.account and old_user.account.get(
610 u"name"
611 ) == self.account.get(
612 u"name"
613 ) and old_user is not self:
614
615
616 log(
617 u"User " + self.account.get(
618 u"name"
619 ) + u" reconnected--closing old connection to " \
620 + old_user.address + u".",
621 2
622 )
623 old_user.send(
624 u"$(eol)$(red)New connection from " + self.address \
625 + u". Terminating old connection...$(nrm)$(eol)", \
626 flush=True,
627 add_prompt=False
628 )
629
630
631 old_user.connection.close()
632
633
634 old_user.send(
635 u"$(eol)$(red)Taking over old connection from " \
636 + old_user.address + u".$(nrm)"
637 )
638 old_user.connection = self.connection
639 old_user.last_address = old_user.address
640 old_user.address = self.address
641
642
643 self.remove()
644 del(self)
645 return_value = True
646 break
647
648
649 return return_value
650
652 u"""Flag the user as authenticated and disconnect duplicates."""
653 if not self.state is u"authenticated":
654 log(u"User " + self.account.get(u"name") + u" logged in.", 2)
655 self.authenticated = True
656 if self.account.subkey in universe.categories[
657 u"internal"
658 ][
659 u"limits"
660 ].getlist(
661 u"default_admins"
662 ):
663 self.account.set(u"administrator", u"True")
664
666 u"""Send the user their current menu."""
667 if not self.menu_seen:
668 self.menu_choices = get_menu_choices(self)
669 self.send(
670 get_menu(self.state, self.error, self.menu_choices),
671 u"",
672 add_terminator=True
673 )
674 self.menu_seen = True
675 self.error = False
676 self.adjust_echoing()
677
686
688 u"""Remove a user from the list of connected users."""
689 universe.userlist.remove(self)
690
691 - def send(
692 self,
693 output,
694 eol=u"$(eol)",
695 raw=False,
696 flush=False,
697 add_prompt=True,
698 just_prompt=False,
699 add_terminator=False,
700 prepend_padding=True
701 ):
702 u"""Send arbitrary text to a connected user."""
703 import telnet
704
705
706 if not raw:
707
708
709 while output.startswith(u"$(eol)"): output = output[6:]
710 while output.endswith(u"$(eol)"): output = output[:-6]
711 extra_lines = output.find(u"$(eol)$(eol)$(eol)")
712 while extra_lines > -1:
713 output = output[:extra_lines] + output[extra_lines+6:]
714 extra_lines = output.find(u"$(eol)$(eol)$(eol)")
715
716
717
718
719 if not just_prompt and prepend_padding:
720 if not self.output_queue \
721 or not self.output_queue[-1].endswith("\r\n"):
722 output = u"$(eol)" + output
723 elif not self.output_queue[-1].endswith(
724 "\r\n\x1b[0m\r\n"
725 ) and not self.output_queue[-1].endswith(
726 "\r\n\r\n"
727 ):
728 output = u"$(eol)" + output
729 output += eol + unichr(27) + u"[0m"
730
731
732 if self.state == u"active":
733 if not just_prompt: output += u"$(eol)"
734 if add_prompt:
735 output += u"> "
736 mode = self.avatar.get(u"mode")
737 if mode: output += u"(" + mode + u") "
738
739
740 output = replace_macros(self, output)
741
742
743 if self.columns:
744 if self.columns < 40: wrap = 40
745 else: wrap = self.columns
746 output = wrap_ansi_text(output, wrap)
747
748
749 if telnet.is_enabled(self, telnet.TELOPT_BINARY, telnet.US):
750 encoded_output = output.encode(u"utf-8")
751
752
753 else: encoded_output = output.encode(u"ascii", u"replace")
754
755
756 if add_prompt or add_terminator:
757 if telnet.is_enabled(self, telnet.TELOPT_EOR, telnet.US):
758 encoded_output += telnet.telnet_proto(telnet.IAC, telnet.EOR)
759 elif not telnet.is_enabled(self, telnet.TELOPT_SGA, telnet.US):
760 encoded_output += telnet.telnet_proto(telnet.IAC, telnet.GA)
761
762
763 self.output_queue.append(encoded_output)
764
765
766 if flush: self.flush()
767
768
769 else:
770 self.output_queue.append(output)
771 self.flush()
772
774 u"""All the things to do to the user per increment."""
775
776
777 if universe.terminate_flag:
778 self.state = u"disconnecting"
779 self.menu_seen = False
780
781
782 else: self.check_idle()
783
784
785 if self.state == u"initial":
786 if self.negotiation_pause: self.negotiation_pause -= 1
787 else: self.state = u"entering_account_name"
788
789
790 elif not self.state == u"active": self.show_menu()
791
792
793 self.flush()
794
795
796 if self.state == u"disconnecting": self.quit()
797
798
799 self.enqueue_input()
800
801
802 if self.input_queue:
803 handle_user_input(self)
804
806 u"""Try to send the last item in the queue and remove it."""
807 if self.output_queue:
808 try:
809 self.connection.send(self.output_queue[0])
810 del self.output_queue[0]
811 except:
812 if self.account and self.account.get(u"name"):
813 account = self.account.get(u"name")
814 else: account = u"an unknown user"
815 log(
816 u"Sending to %s raised an exception (broken pipe?)." % account,
817 7
818 )
819 pass
820
821
888
890 u"""Instantiate a new, unconfigured avatar for this user."""
891 counter = 0
892 while u"avatar:" + self.account.get(u"name") + u":" + unicode(
893 counter
894 ) in universe.categories[u"actor"].keys():
895 counter += 1
896 self.avatar = Element(
897 u"actor:avatar:" + self.account.get(u"name") + u":" + unicode(
898 counter
899 ),
900 universe
901 )
902 self.avatar.append(u"inherit", u"archetype:avatar")
903 self.account.append(u"avatars", self.avatar.key)
904
906 u"""Remove an avatar from the world and from the user's list."""
907 if self.avatar is universe.contents[avatar]: self.avatar = None
908 universe.contents[avatar].destroy()
909 avatars = self.account.getlist(u"avatars")
910 avatars.remove(avatar)
911 self.account.set(u"avatars", avatars)
912
914 u"""Enter the world with a particular indexed avatar."""
915 self.avatar = universe.contents[self.account.getlist(u"avatars")[index]]
916 self.avatar.owner = self
917 self.state = u"active"
918 self.avatar.go_home()
919
921 u"""Have the active avatar leave the world."""
922 if self.avatar:
923 current = self.avatar.get(u"location")
924 if current:
925 self.avatar.set(u"default_location", current)
926 self.avatar.echo_to_location(
927 u"You suddenly wonder where " + self.avatar.get(
928 u"name"
929 ) + u" went."
930 )
931 del universe.contents[current].contents[self.avatar.key]
932 self.avatar.remove_facet(u"location")
933 self.avatar.owner = None
934 self.avatar = None
935
937 u"""Destroy the user and associated avatars."""
938 for avatar in self.account.getlist(u"avatars"):
939 self.delete_avatar(avatar)
940 self.account.destroy()
941
943 u"""List names of assigned avatars."""
944 return [
945 universe.contents[avatar].get(
946 u"name"
947 ) for avatar in self.account.getlist(u"avatars")
948 ]
949
951 u"""Send a message to all connected users."""
952 for each_user in universe.userlist:
953 each_user.send(u"$(eol)" + message, add_prompt=add_prompt)
954
955 -def log(message, level=0):
956 u"""Log a message."""
957 import codecs, os.path, syslog, time
958
959
960 file_name = universe.categories[u"internal"][u"logging"].get(u"file")
961 max_log_lines = universe.categories[u"internal"][u"logging"].getint(
962 u"max_log_lines"
963 )
964 syslog_name = universe.categories[u"internal"][u"logging"].get(u"syslog")
965 timestamp = time.asctime()[4:19]
966
967
968 lines = filter(
969 lambda x: x!=u"", [ (x.rstrip()) for x in message.split(u"\n") ]
970 )
971
972
973 if file_name:
974 if not os.path.isabs(file_name):
975 file_name = os.path.join(universe.startdir, file_name)
976 file_descriptor = codecs.open(file_name, u"a", u"utf-8")
977 for line in lines: file_descriptor.write(timestamp + u" " + line + u"\n")
978 file_descriptor.flush()
979 file_descriptor.close()
980
981
982 if universe.categories[u"internal"][u"logging"].getboolean(u"stdout"):
983 for line in lines: print(timestamp + u" " + line)
984
985
986 if syslog_name:
987 syslog.openlog(
988 syslog_name.encode("utf-8"),
989 syslog.LOG_PID,
990 syslog.LOG_INFO | syslog.LOG_DAEMON
991 )
992 for line in lines: syslog.syslog(line)
993 syslog.closelog()
994
995
996 for user in universe.userlist:
997 if user.state == u"active" and user.account.getboolean(
998 u"administrator"
999 ) and user.account.getint(u"loglevel") <= level:
1000
1001 full_message = u""
1002 for line in lines:
1003 full_message += u"$(bld)$(red)" + timestamp + u" " + line.replace(
1004 u"$(", u"$_("
1005 ) + u"$(nrm)$(eol)"
1006 user.send(full_message, flush=True)
1007
1008
1009 for line in lines:
1010 while 0 < len(universe.loglines) >= max_log_lines:
1011 del universe.loglines[0]
1012 universe.loglines.append((level, timestamp + u" " + line))
1013
1015 u"""Return a specific range of loglines filtered by level."""
1016
1017
1018 loglines = filter(lambda x: x[0]>=level, universe.loglines)
1019
1020
1021 total_count = unicode(len(universe.loglines))
1022 filtered_count = len(loglines)
1023
1024
1025 if filtered_count:
1026
1027
1028 if start > filtered_count: start = filtered_count
1029 if start < 1: start = 1
1030
1031
1032 if stop > start: stop = start
1033 elif stop < 1: stop = 1
1034
1035
1036 message = u"There are " + unicode(total_count)
1037 message += u" log lines in memory and " + unicode(filtered_count)
1038 message += u" at or above level " + unicode(level) + u"."
1039 message += u" The matching lines from " + unicode(stop) + u" to "
1040 message += unicode(start) + u" are:$(eol)$(eol)"
1041
1042
1043 if stop > 1: range_lines = loglines[-start:-(stop-1)]
1044 else: range_lines = loglines[-start:]
1045 for line in range_lines:
1046 message += u" (" + unicode(line[0]) + u") " + line[1].replace(
1047 u"$(", u"$_("
1048 ) + u"$(eol)"
1049
1050
1051 else:
1052 message = u"None of the " + unicode(total_count)
1053 message += u" lines in memory matches your request."
1054
1055
1056 return message
1057
1059 u"""Convenience function to return the column width of a glyph."""
1060 import unicodedata
1061 if unicodedata.east_asian_width(character) in u"FW": return 2
1062 else: return 1
1063
1064 -def wrap_ansi_text(text, width):
1065 u"""Wrap text with arbitrary width while ignoring ANSI colors."""
1066 import unicodedata
1067
1068
1069
1070 absolute_position = 0
1071
1072
1073
1074 relative_position = 0
1075
1076
1077 last_whitespace = 0
1078
1079
1080 escape = False
1081
1082
1083 text = unicodedata.normalize(u"NFKC", text)
1084
1085
1086 for each_character in text:
1087
1088
1089 if each_character == u"\x1b" and not escape:
1090 escape = True
1091
1092
1093 elif escape:
1094
1095
1096
1097 if each_character == u"m":
1098 escape = False
1099
1100
1101
1102 elif each_character == u"\n":
1103 relative_position = 0
1104 last_whitespace = absolute_position
1105
1106
1107
1108
1109
1110 elif each_character != u"\r" and (
1111 relative_position >= width or (
1112 relative_position >= width -1 and glyph_columns(
1113 each_character
1114 ) == 2
1115 )
1116 ):
1117
1118
1119 if unicodedata.category(each_character) in (u"Cc",u"Zs"):
1120 last_whitespace = absolute_position
1121
1122
1123 text = text[:last_whitespace] + u"\r\n" + text[last_whitespace + 1:]
1124
1125
1126
1127 absolute_position += 1
1128
1129
1130
1131 relative_position = 0
1132 for remaining_characters in text[last_whitespace:absolute_position]:
1133 relative_position += glyph_columns(remaining_characters)
1134
1135
1136
1137
1138 elif each_character != u"\r":
1139 relative_position += glyph_columns(each_character)
1140 if unicodedata.category(each_character) in (u"Cc",u"Zs"):
1141 last_whitespace = absolute_position
1142
1143
1144 absolute_position += 1
1145
1146
1147 return text
1148
1150 u"""Takes a dict weighted by value and returns a random key."""
1151 import random
1152
1153
1154 expanded = []
1155
1156
1157 for key in data.keys():
1158 for count in range(data[key]):
1159 expanded.append(key)
1160
1161
1162 return random.choice(expanded)
1163
1165 u"""Returns a random character name."""
1166 import random
1167
1168
1169 vowels = [
1170 u"a",
1171 u"i",
1172 u"u",
1173 u"e",
1174 u"o"
1175 ]
1176 consonants = [
1177 u"'",
1178 u"k",
1179 u"z",
1180 u"s",
1181 u"sh",
1182 u"z",
1183 u"j",
1184 u"t",
1185 u"ch",
1186 u"ts",
1187 u"d",
1188 u"n",
1189 u"h",
1190 u"f",
1191 u"m",
1192 u"y",
1193 u"r",
1194 u"w"
1195 ]
1196
1197
1198 syllables = {}
1199
1200
1201 for consonant in consonants:
1202 for vowel in vowels:
1203 syllables[consonant + vowel] = 1
1204
1205
1206 name = u""
1207
1208
1209 for syllable in range(random.randrange(2, 6)):
1210 name += weighted_choice(syllables)
1211
1212
1213 return name.strip(u"'").capitalize()
1214
1216 u"""Replaces macros in text output."""
1217 import codecs, data, os.path
1218
1219
1220 pronouns = {
1221 u"female": { u"obj": u"her", u"pos": u"hers", u"sub": u"she" },
1222 u"male": { u"obj": u"him", u"pos": u"his", u"sub": u"he" },
1223 u"neuter": { u"obj": u"it", u"pos": u"its", u"sub": u"it" }
1224 }
1225
1226
1227 macros = {
1228 u"eol": u"\r\n",
1229 u"bld": unichr(27) + u"[1m",
1230 u"nrm": unichr(27) + u"[0m",
1231 u"blk": unichr(27) + u"[30m",
1232 u"blu": unichr(27) + u"[34m",
1233 u"cyn": unichr(27) + u"[36m",
1234 u"grn": unichr(27) + u"[32m",
1235 u"mgt": unichr(27) + u"[35m",
1236 u"red": unichr(27) + u"[31m",
1237 u"yel": unichr(27) + u"[33m",
1238 }
1239
1240
1241 if user.account:
1242 account_name = user.account.get(u"name")
1243 if account_name:
1244 macros[u"account"] = account_name
1245 if user.avatar:
1246 avatar_gender = user.avatar.get(u"gender")
1247 if avatar_gender:
1248 macros[u"tpop"] = pronouns[avatar_gender][u"obj"]
1249 macros[u"tppp"] = pronouns[avatar_gender][u"pos"]
1250 macros[u"tpsp"] = pronouns[avatar_gender][u"sub"]
1251
1252
1253 while True:
1254
1255
1256 macro_start = text.find(u"$(")
1257 if macro_start == -1: break
1258 macro_end = text.find(u")", macro_start) + 1
1259 macro = text[macro_start+2:macro_end-1]
1260 if macro in macros.keys():
1261 replacement = macros[macro]
1262
1263
1264 elif macro.startswith(u"inc:"):
1265 incfile = data.find_file(macro[4:], universe=universe)
1266 if os.path.exists(incfile):
1267 incfd = codecs.open(incfile, u"r", u"utf-8")
1268 replacement = u""
1269 for line in incfd:
1270 if line.endswith(u"\n") and not line.endswith(u"\r\n"):
1271 line = line.replace(u"\n", u"\r\n")
1272 replacement += line
1273
1274 replacement = replacement[:-2]
1275 else:
1276 replacement = u""
1277 log(u"Couldn't read included " + incfile + u" file.", 6)
1278
1279
1280 else:
1281 replacement = u""
1282 if not is_input:
1283 log(u"Unexpected replacement macro " + macro + u" encountered.", 6)
1284
1285
1286 text = text.replace(u"$(" + macro + u")", replacement)
1287
1288
1289 text = text.replace(u"$_(", u"$(")
1290
1291 return text
1292
1294 u"""Escapes replacement macros in text."""
1295 return text.replace(u"$(", u"$_(")
1296
1298 u"""Returns a tuple of the first word and the rest."""
1299 if text:
1300 if text.find(separator) > 0: return text.split(separator, 1)
1301 else: return text, u""
1302 else: return u"", u""
1303
1305 u"""The things which should happen on each pulse, aside from reloads."""
1306 import time
1307
1308
1309 if not hasattr(universe, u"listening_socket"):
1310 universe.initialize_server_socket()
1311
1312
1313 user = check_for_connection(universe.listening_socket)
1314 if user: universe.userlist.append(user)
1315
1316
1317 for user in universe.userlist: user.pulse()
1318
1319
1320 if not u"counters" in universe.categories[u"internal"]:
1321 universe.categories[u"internal"][u"counters"] = Element(
1322 u"internal:counters", universe
1323 )
1324
1325
1326 if not universe.categories[u"internal"][u"counters"].getint(u"mark"):
1327 log(unicode(len(universe.userlist)) + u" connection(s)")
1328 universe.categories[u"internal"][u"counters"].set(
1329 u"mark", universe.categories[u"internal"][u"time"].getint(
1330 u"frequency_log"
1331 )
1332 )
1333 else:
1334 universe.categories[u"internal"][u"counters"].set(
1335 u"mark", universe.categories[u"internal"][u"counters"].getint(
1336 u"mark"
1337 ) - 1
1338 )
1339
1340
1341 if not universe.categories[u"internal"][u"counters"].getint(u"save"):
1342 universe.save()
1343 universe.categories[u"internal"][u"counters"].set(
1344 u"save", universe.categories[u"internal"][u"time"].getint(
1345 u"frequency_save"
1346 )
1347 )
1348 else:
1349 universe.categories[u"internal"][u"counters"].set(
1350 u"save", universe.categories[u"internal"][u"counters"].getint(
1351 u"save"
1352 ) - 1
1353 )
1354
1355
1356 time.sleep(universe.categories[u"internal"][u"time"].getfloat(u"increment"))
1357
1358
1359 universe.categories[u"internal"][u"counters"].set(
1360 u"elapsed", universe.categories[u"internal"][u"counters"].getint(
1361 u"elapsed"
1362 ) + 1
1363 )
1364
1366 u"""Reload all relevant objects."""
1367 for user in universe.userlist[:]: user.reload()
1368 for element in universe.contents.values():
1369 if element.origin.is_writeable(): element.reload()
1370 universe.load()
1371
1373 u"""Check for a waiting connection and return a new user object."""
1374 import telnet
1375
1376
1377 try:
1378 connection, address = listening_socket.accept()
1379 except:
1380 return None
1381
1382
1383 log(u"Connection from " + address[0], 2)
1384
1385
1386 connection.setblocking(0)
1387
1388
1389 user = User()
1390
1391
1392 user.connection = connection
1393
1394
1395 user.address = address[0]
1396
1397
1398 telnet.enable(user, telnet.TELOPT_EOR, telnet.US)
1399 user.negotiation_pause = 2
1400
1401
1402 return user
1403
1427
1429 u"""True if echo is on, false if it is off."""
1430 return universe.categories[u"menu"][state].getboolean(u"echo", True)
1431
1433 u"""Return a message indicating that echo is off."""
1434 if menu_echo_on(state): return u""
1435 else: return u"(won't echo) "
1436
1438 u"""Return the default choice for a menu."""
1439 return universe.categories[u"menu"][state].get(u"default")
1440
1446
1448 u"""Get the description or error text."""
1449
1450
1451 if error:
1452
1453
1454
1455 description = universe.categories[u"menu"][state].get(u"error_" + error)
1456 if not description: description = u"That is not a valid choice..."
1457 description = u"$(red)" + description + u"$(nrm)"
1458
1459
1460 else:
1461
1462
1463 description = universe.categories[u"menu"][state].get(u"description")
1464
1465
1466 if description: description += u"$(eol)$(eol)"
1467 return description
1468
1470 u"""Try to get a prompt, if it was defined."""
1471 prompt = universe.categories[u"menu"][state].get(u"prompt")
1472 if prompt: prompt += u" "
1473 return prompt
1474
1476 u"""Return a dict of choice:meaning."""
1477 menu = universe.categories[u"menu"][user.state]
1478 create_choices = menu.get(u"create")
1479 if create_choices: choices = eval(create_choices)
1480 else: choices = {}
1481 ignores = []
1482 options = {}
1483 creates = {}
1484 for facet in menu.facets():
1485 if facet.startswith(u"demand_") and not eval(
1486 universe.categories[u"menu"][user.state].get(facet)
1487 ):
1488 ignores.append(facet.split(u"_", 2)[1])
1489 elif facet.startswith(u"create_"):
1490 creates[facet] = facet.split(u"_", 2)[1]
1491 elif facet.startswith(u"choice_"):
1492 options[facet] = facet.split(u"_", 2)[1]
1493 for facet in creates.keys():
1494 if not creates[facet] in ignores:
1495 choices[creates[facet]] = eval(menu.get(facet))
1496 for facet in options.keys():
1497 if not options[facet] in ignores:
1498 choices[options[facet]] = menu.get(facet)
1499 return choices
1500
1512
1514 u"""Return a dict of choice:branch."""
1515 branches = {}
1516 for facet in universe.categories[u"menu"][state].facets():
1517 if facet.startswith(u"branch_"):
1518 branches[
1519 facet.split(u"_", 2)[1]
1520 ] = universe.categories[u"menu"][state].get(facet)
1521 return branches
1522
1524 u"""Return the default branch."""
1525 return universe.categories[u"menu"][state].get(u"branch")
1526
1528 u"""Returns the new state matching the given choice."""
1529 branches = get_menu_branches(user.state)
1530 if choice in branches.keys(): return branches[choice]
1531 elif choice in user.menu_choices.keys():
1532 return get_default_branch(user.state)
1533 else: return u""
1534
1536 u"""Return a dict of choice:branch."""
1537 actions = {}
1538 for facet in universe.categories[u"menu"][state].facets():
1539 if facet.startswith(u"action_"):
1540 actions[
1541 facet.split(u"_", 2)[1]
1542 ] = universe.categories[u"menu"][state].get(facet)
1543 return actions
1544
1546 u"""Return the default action."""
1547 return universe.categories[u"menu"][state].get(u"action")
1548
1550 u"""Run any indicated script for the given choice."""
1551 actions = get_menu_actions(user.state)
1552 if choice in actions.keys(): return actions[choice]
1553 elif choice in user.menu_choices.keys():
1554 return get_default_action(user.state)
1555 else: return u""
1556
1576
1578 u"""A generic menu choice handler."""
1579
1580
1581 if user.input_queue:
1582 choice = user.input_queue.pop(0)
1583 if choice: choice = choice.lower()
1584 else: choice = u""
1585 if not choice: choice = get_default_menu_choice(user.state)
1586 if choice in user.menu_choices:
1587 exec(get_choice_action(user, choice))
1588 new_state = get_choice_branch(user, choice)
1589 if new_state: user.state = new_state
1590 else: user.error = u"default"
1591
1593 u"""Handle the login account name."""
1594
1595
1596 input_data = user.input_queue.pop(0)
1597
1598
1599 if input_data:
1600
1601
1602 name = input_data.lower()
1603
1604
1605 if name != filter(
1606 lambda x: x>=u"0" and x<=u"9" or x>=u"a" and x<=u"z", name
1607 ):
1608 user.error = u"bad_name"
1609
1610
1611 elif name in universe.categories[u"account"]:
1612 user.account = universe.categories[u"account"][name]
1613 user.state = u"checking_password"
1614
1615
1616 else:
1617 user.account = Element(u"account:" + name, universe)
1618 user.account.set(u"name", name)
1619 log(u"New user: " + name, 2)
1620 user.state = u"checking_new_account_name"
1621
1622
1623 else:
1624 user.state = u"disconnecting"
1625
1627 u"""Handle the login account password."""
1628 import password
1629
1630
1631 input_data = user.input_queue.pop(0)
1632
1633
1634 if password.verify( input_data, user.account.get(u"passhash") ):
1635
1636
1637 if not user.replace_old_connections():
1638 user.authenticate()
1639 user.state = u"main_utility"
1640
1641
1642 elif user.password_tries < universe.categories[
1643 u"internal"
1644 ][
1645 u"limits"
1646 ].getint(
1647 u"password_tries"
1648 ) - 1:
1649 user.password_tries += 1
1650 user.error = u"incorrect"
1651
1652
1653 else:
1654 user.send(
1655 u"$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)"
1656 )
1657 user.state = u"disconnecting"
1658
1660 u"""Handle a new password entry."""
1661 import password
1662
1663
1664 input_data = user.input_queue.pop(0)
1665
1666
1667
1668 if len(input_data) > 6 and len(
1669 filter( lambda x: x>=u"0" and x<=u"9", input_data )
1670 ) and len(
1671 filter( lambda x: x>=u"A" and x<=u"Z", input_data )
1672 ) and len(
1673 filter( lambda x: x>=u"a" and x<=u"z", input_data )
1674 ):
1675
1676
1677 user.account.set( u"passhash", password.create(input_data) )
1678 user.state = u"verifying_new_password"
1679
1680
1681 elif user.password_tries < universe.categories[
1682 u"internal"
1683 ][
1684 u"limits"
1685 ].getint(
1686 u"password_tries"
1687 ) - 1:
1688 user.password_tries += 1
1689 user.error = u"weak"
1690
1691
1692 else:
1693 user.send(
1694 u"$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)"
1695 )
1696 user.account.destroy()
1697 user.state = u"disconnecting"
1698
1700 u"""Handle the re-entered new password for verification."""
1701 import password
1702
1703
1704 input_data = user.input_queue.pop(0)
1705
1706
1707 if password.verify( input_data, user.account.get(u"passhash") ):
1708 user.authenticate()
1709
1710
1711 if not user.replace_old_connections(): user.state = u"main_utility"
1712
1713
1714
1715 elif user.password_tries < universe.categories[
1716 u"internal"
1717 ][
1718 u"limits"
1719 ].getint(
1720 u"password_tries"
1721 ) - 1:
1722 user.password_tries += 1
1723 user.error = u"differs"
1724 user.state = u"entering_new_password"
1725
1726
1727 else:
1728 user.send(
1729 u"$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)"
1730 )
1731 user.account.destroy()
1732 user.state = u"disconnecting"
1733
1735 u"""Handle input for active users."""
1736
1737
1738 input_data = user.input_queue.pop(0)
1739
1740
1741 if input_data:
1742
1743
1744 actor = user.avatar
1745 mode = actor.get(u"mode")
1746 if mode and input_data.startswith(u"!"):
1747 command_name, parameters = first_word(input_data[1:])
1748 elif mode == u"chat":
1749 command_name = u"say"
1750 parameters = input_data
1751 else:
1752 command_name, parameters = first_word(input_data)
1753
1754
1755 command_name = command_name.lower()
1756
1757
1758 if command_name in universe.categories[u"command"]:
1759 command = universe.categories[u"command"][command_name]
1760 else: command = None
1761
1762
1763 if actor.can_run(command): exec(command.get(u"action"))
1764
1765
1766 elif command_name: command_error(actor, input_data)
1767
1768
1769 else: user.send(u"", just_prompt=True)
1770
1772 u"""Halt the world."""
1773 if actor.owner:
1774
1775
1776 if parameters: message = u"Halting: " + parameters
1777 else:
1778 message = u"User " + actor.owner.account.get(
1779 u"name"
1780 ) + u" halted the world."
1781
1782
1783 broadcast(message, add_prompt=False)
1784 log(message, 8)
1785
1786
1787 universe.terminate_flag = True
1788
1790 u"""Reload all code modules, configs and data."""
1791 if actor.owner:
1792
1793
1794 actor.send(u"Reloading all code modules, configs and data.")
1795 log(
1796 u"User " + actor.owner.account.get(u"name") + u" reloaded the world.",
1797 8
1798 )
1799
1800
1801 universe.reload_flag = True
1802
1804 u"""Leave the world and go back to the main menu."""
1805 if actor.owner:
1806 actor.owner.state = u"main_utility"
1807 actor.owner.deactivate_avatar()
1808
1810 u"""List available commands and provide help for commands."""
1811
1812
1813 if parameters and actor.owner:
1814
1815
1816 if parameters in universe.categories[u"command"]:
1817 command = universe.categories[u"command"][parameters]
1818 else: command = None
1819
1820
1821 if actor.can_run(command):
1822
1823
1824 description = command.get(u"description")
1825 if not description:
1826 description = u"(no short description provided)"
1827 if command.getboolean(u"administrative"): output = u"$(red)"
1828 else: output = u"$(grn)"
1829 output += parameters + u"$(nrm) - " + description + u"$(eol)$(eol)"
1830
1831
1832 help_text = command.get(u"help")
1833 if not help_text:
1834 help_text = u"No help is provided for this command."
1835 output += help_text
1836
1837
1838 see_also = command.getlist(u"see_also")
1839 if see_also:
1840 really_see_also = u""
1841 for item in see_also:
1842 if item in universe.categories[u"command"]:
1843 command = universe.categories[u"command"][item]
1844 if actor.can_run(command):
1845 if really_see_also:
1846 really_see_also += u", "
1847 if command.getboolean(u"administrative"):
1848 really_see_also += u"$(red)"
1849 else:
1850 really_see_also += u"$(grn)"
1851 really_see_also += item + u"$(nrm)"
1852 if really_see_also:
1853 output += u"$(eol)$(eol)See also: " + really_see_also
1854
1855
1856 else:
1857 output = u"That is not an available command."
1858
1859
1860 else:
1861
1862
1863 output = u"These are the commands available to you:$(eol)$(eol)"
1864 sorted_commands = universe.categories[u"command"].keys()
1865 sorted_commands.sort()
1866 for item in sorted_commands:
1867 command = universe.categories[u"command"][item]
1868 if actor.can_run(command):
1869 description = command.get(u"description")
1870 if not description:
1871 description = u"(no short description provided)"
1872 if command.getboolean(u"administrative"): output += u" $(red)"
1873 else: output += u" $(grn)"
1874 output += item + u"$(nrm) - " + description + u"$(eol)"
1875 output += u"$(eol)Enter \"help COMMAND\" for help on a command " \
1876 + u"named \"COMMAND\"."
1877
1878
1879 actor.send(output)
1880
1882 u"""Move the avatar in a given direction."""
1883 if parameters in universe.contents[actor.get(u"location")].portals():
1884 actor.move_direction(parameters)
1885 else: actor.send(u"You cannot go that way.")
1886
1888 u"""Look around."""
1889 if parameters: actor.send(u"You can't look at or in anything yet.")
1890 else: actor.look_at(actor.get(u"location"))
1891
1893 u"""Speak to others in the same area."""
1894 import unicodedata
1895
1896
1897 parameters = escape_macros(parameters)
1898
1899
1900 if parameters.startswith(u"\"") and parameters.endswith(u"\""):
1901 message = parameters[1:-1]
1902 literal = True
1903
1904
1905 else:
1906 message = parameters.strip(u"\"'`")
1907 literal = False
1908
1909
1910 if message:
1911
1912
1913 actions = universe.categories[u"internal"][u"language"].getdict(
1914 u"actions"
1915 )
1916 default_punctuation = universe.categories[u"internal"][u"language"].get(
1917 u"default_punctuation"
1918 )
1919 action = u""
1920 for mark in actions.keys():
1921 if not literal and message.endswith(mark):
1922 action = actions[mark]
1923 break
1924
1925
1926 if not action:
1927 action = actions[default_punctuation]
1928 if message and not (
1929 literal or unicodedata.category(message[-1]) == u"Po"
1930 ):
1931 message += default_punctuation
1932
1933
1934 if message and not literal:
1935
1936
1937 message = message[0].lower() + message[1:]
1938
1939
1940 typos = universe.categories[u"internal"][u"language"].getdict(
1941 u"typos"
1942 )
1943 words = message.split()
1944 for index in range(len(words)):
1945 word = words[index]
1946 while unicodedata.category(word[0]) == u"Po":
1947 word = word[1:]
1948 while unicodedata.category(word[-1]) == u"Po":
1949 word = word[:-1]
1950 if word in typos.keys():
1951 words[index] = words[index].replace(word, typos[word])
1952 message = u" ".join(words)
1953
1954
1955 message = message[0].upper() + message[1:]
1956
1957
1958 if message:
1959 actor.echo_to_location(
1960 actor.get(u"name") + u" " + action + u"s, \"" + message + u"\""
1961 )
1962 actor.send(u"You " + action + u", \"" + message + u"\"")
1963
1964
1965 else:
1966 actor.send(u"What do you want to say?")
1967
1969 u"""Toggle chat mode."""
1970 mode = actor.get(u"mode")
1971 if not mode:
1972 actor.set(u"mode", u"chat")
1973 actor.send(u"Entering chat mode (use $(grn)!chat$(nrm) to exit).")
1974 elif mode == u"chat":
1975 actor.remove_facet(u"mode")
1976 actor.send(u"Exiting chat mode.")
1977 else: actor.send(u"Sorry, but you're already busy with something else!")
1978
1980 u"""Show program data."""
1981 import re
1982 message = u""
1983 arguments = parameters.split()
1984 if not parameters: message = u"What do you want to show?"
1985 elif arguments[0] == u"time":
1986 message = universe.categories[u"internal"][u"counters"].get(
1987 u"elapsed"
1988 ) + u" increments elapsed since the world was created."
1989 elif arguments[0] == u"categories":
1990 message = u"These are the element categories:$(eol)"
1991 categories = universe.categories.keys()
1992 categories.sort()
1993 for category in categories:
1994 message += u"$(eol) $(grn)" + category + u"$(nrm)"
1995 elif arguments[0] == u"files":
1996 message = u"These are the current files containing the universe:$(eol)"
1997 filenames = universe.files.keys()
1998 filenames.sort()
1999 for filename in filenames:
2000 if universe.files[filename].is_writeable(): status = u"rw"
2001 else: status = u"ro"
2002 message += u"$(eol) $(red)(" + status + u") $(grn)" + filename \
2003 + u"$(nrm)"
2004 elif arguments[0] == u"category":
2005 if len(arguments) != 2: message = u"You must specify one category."
2006 elif arguments[1] in universe.categories:
2007 message = u"These are the elements in the \"" + arguments[1] \
2008 + u"\" category:$(eol)"
2009 elements = [
2010 (
2011 universe.categories[arguments[1]][x].key
2012 ) for x in universe.categories[arguments[1]].keys()
2013 ]
2014 elements.sort()
2015 for element in elements:
2016 message += u"$(eol) $(grn)" + element + u"$(nrm)"
2017 else: message = u"Category \"" + arguments[1] + u"\" does not exist."
2018 elif arguments[0] == u"file":
2019 if len(arguments) != 2: message = u"You must specify one file."
2020 elif arguments[1] in universe.files:
2021 message = u"These are the elements in the \"" + arguments[1] \
2022 + u"\" file:$(eol)"
2023 elements = universe.files[arguments[1]].data.sections()
2024 elements.sort()
2025 for element in elements:
2026 message += u"$(eol) $(grn)" + element + u"$(nrm)"
2027 else: message = u"Category \"" + arguments[1] + u"\" does not exist."
2028 elif arguments[0] == u"element":
2029 if len(arguments) != 2: message = u"You must specify one element."
2030 elif arguments[1] in universe.contents:
2031 element = universe.contents[arguments[1]]
2032 message = u"These are the properties of the \"" + arguments[1] \
2033 + u"\" element (in \"" + element.origin.filename + u"\"):$(eol)"
2034 facets = element.facets()
2035 facets.sort()
2036 for facet in facets:
2037 message += u"$(eol) $(grn)" + facet + u": $(red)" \
2038 + escape_macros(element.get(facet)) + u"$(nrm)"
2039 else: message = u"Element \"" + arguments[1] + u"\" does not exist."
2040 elif arguments[0] == u"result":
2041 if len(arguments) < 2: message = u"You need to specify an expression."
2042 else:
2043 try:
2044 message = repr(eval(u" ".join(arguments[1:])))
2045 except:
2046 message = u"Your expression raised an exception!"
2047 elif arguments[0] == u"log":
2048 if len(arguments) == 4:
2049 if re.match(u"^\d+$", arguments[3]) and int(arguments[3]) >= 0:
2050 stop = int(arguments[3])
2051 else: stop = -1
2052 else: stop = 0
2053 if len(arguments) >= 3:
2054 if re.match(u"^\d+$", arguments[2]) and int(arguments[2]) > 0:
2055 start = int(arguments[2])
2056 else: start = -1
2057 else: start = 10
2058 if len(arguments) >= 2:
2059 if re.match(u"^\d+$", arguments[1]) and 0 <= int(arguments[1]) <= 9:
2060 level = int(arguments[1])
2061 else: level = -1
2062 elif 0 <= actor.owner.account.getint(u"loglevel") <= 9:
2063 level = actor.owner.account.getint(u"loglevel")
2064 else: level = 1
2065 if level > -1 and start > -1 and stop > -1:
2066 message = get_loglines(level, start, stop)
2067 else:
2068 message = u"When specified, level must be 0-9 (default 1), " \
2069 + u"start and stop must be >=1 (default 10 and 1)."
2070 else: message = u"I don't know what \"" + parameters + u"\" is."
2071 actor.send(message)
2072
2074 u"""Create an element if it does not exist."""
2075 if not parameters:
2076 message = u"You must at least specify an element to create."
2077 elif not actor.owner: message = u""
2078 else:
2079 arguments = parameters.split()
2080 if len(arguments) == 1: arguments.append(u"")
2081 if len(arguments) == 2:
2082 element, filename = arguments
2083 if element in universe.contents:
2084 message = u"The \"" + element + u"\" element already exists."
2085 else:
2086 message = u"You create \"" + element + u"\" within the universe."
2087 logline = actor.owner.account.get(
2088 u"name"
2089 ) + u" created an element: " + element
2090 if filename:
2091 logline += u" in file " + filename
2092 if filename not in universe.files:
2093 message += u" Warning: \"" + filename \
2094 + u"\" is not yet included in any other file and will " \
2095 + u"not be read on startup unless this is remedied."
2096 Element(element, universe, filename)
2097 log(logline, 6)
2098 elif len(arguments) > 2:
2099 message = u"You can only specify an element and a filename."
2100 actor.send(message)
2101
2103 u"""Destroy an element if it exists."""
2104 if actor.owner:
2105 if not parameters: message = u"You must specify an element to destroy."
2106 else:
2107 if parameters not in universe.contents:
2108 message = u"The \"" + parameters + u"\" element does not exist."
2109 else:
2110 universe.contents[parameters].destroy()
2111 message = u"You destroy \"" + parameters \
2112 + u"\" within the universe."
2113 log(
2114 actor.owner.account.get(
2115 u"name"
2116 ) + u" destroyed an element: " + parameters,
2117 6
2118 )
2119 actor.send(message)
2120
2122 u"""Set a facet of an element."""
2123 if not parameters:
2124 message = u"You must specify an element, a facet and a value."
2125 else:
2126 arguments = parameters.split(u" ", 2)
2127 if len(arguments) == 1:
2128 message = u"What facet of element \"" + arguments[0] \
2129 + u"\" would you like to set?"
2130 elif len(arguments) == 2:
2131 message = u"What value would you like to set for the \"" \
2132 + arguments[1] + u"\" facet of the \"" + arguments[0] \
2133 + u"\" element?"
2134 else:
2135 element, facet, value = arguments
2136 if element not in universe.contents:
2137 message = u"The \"" + element + u"\" element does not exist."
2138 else:
2139 universe.contents[element].set(facet, value)
2140 message = u"You have successfully (re)set the \"" + facet \
2141 + u"\" facet of element \"" + element \
2142 + u"\". Try \"show element " + element + u"\" for verification."
2143 actor.send(message)
2144
2146 u"""Delete a facet from an element."""
2147 if not parameters: message = u"You must specify an element and a facet."
2148 else:
2149 arguments = parameters.split(u" ")
2150 if len(arguments) == 1:
2151 message = u"What facet of element \"" + arguments[0] \
2152 + u"\" would you like to delete?"
2153 elif len(arguments) != 2:
2154 message = u"You may only specify an element and a facet."
2155 else:
2156 element, facet = arguments
2157 if element not in universe.contents:
2158 message = u"The \"" + element + u"\" element does not exist."
2159 elif facet not in universe.contents[element].facets():
2160 message = u"The \"" + element + u"\" element has no \"" + facet \
2161 + u"\" facet."
2162 else:
2163 universe.contents[element].remove_facet(facet)
2164 message = u"You have successfully deleted the \"" + facet \
2165 + u"\" facet of element \"" + element \
2166 + u"\". Try \"show element " + element + u"\" for verification."
2167 actor.send(message)
2168
2170 u"""Generic error for an unrecognized command word."""
2171 import random
2172
2173
2174 if random.randrange(10):
2175 message = u"I'm not sure what \"" + input_data + u"\" means..."
2176
2177
2178 else:
2179 message = u"Arglebargle, glop-glyf!?!"
2180
2181
2182 actor.send(message)
2183
2185 u"""Fork and disassociate from everything."""
2186 import codecs, ctypes, ctypes.util, os, os.path, sys
2187
2188
2189 if universe.contents[u"internal:process"].getboolean(u"daemon"):
2190
2191
2192
2193 new_argv = "\0".join(sys.argv) + "\0"
2194 new_short_argv0 = os.path.basename(sys.argv[0]) + "\0"
2195
2196
2197 try:
2198 argv_array = ctypes.POINTER(ctypes.c_char_p)
2199 ctypes.pythonapi.Py_GetArgcArgv.argtypes = (
2200 ctypes.POINTER(ctypes.c_int),
2201 ctypes.POINTER(argv_array)
2202 )
2203 argc = argv_array()
2204 ctypes.pythonapi.Py_GetArgcArgv(
2205 ctypes.c_int(0),
2206 ctypes.pointer(argc)
2207 )
2208 old_argv0_size = len(argc.contents.value)
2209 ctypes.memset( argc.contents, 0, len(new_argv)+old_argv0_size )
2210 ctypes.memmove( argc.contents, new_argv, len(new_argv) )
2211 ctypes.CDLL( ctypes.util.find_library(u"c") ).prctl(
2212 15,
2213 new_short_argv0,
2214 0,
2215 0,
2216 0
2217 )
2218
2219 except:
2220
2221
2222 try:
2223
2224
2225 ctypes.CDLL( ctypes.util.find_library(u"c") ).setproctitle(
2226 new_argv
2227 )
2228
2229 except:
2230
2231
2232 log(u"Failed to rename the interpreter process (cosmetic).")
2233
2234
2235 log(u"Disassociating from the controlling terminal.")
2236
2237
2238 if os.fork(): os._exit(0)
2239
2240
2241 os.setsid()
2242
2243
2244 if os.fork(): os._exit(0)
2245
2246
2247 os.chdir(u"/")
2248
2249
2250 os.umask(0)
2251
2252
2253 for stdpipe in range(3): os.close(stdpipe)
2254 sys.stdin = codecs.open(u"/dev/null", u"r", u"utf-8")
2255 sys.__stdin__ = codecs.open(u"/dev/null", u"r", u"utf-8")
2256 sys.stdout = codecs.open(u"/dev/null", u"w", u"utf-8")
2257 sys.stderr = codecs.open(u"/dev/null", u"w", u"utf-8")
2258 sys.__stdout__ = codecs.open(u"/dev/null", u"w", u"utf-8")
2259 sys.__stderr__ = codecs.open(u"/dev/null", u"w", u"utf-8")
2260
2262 u"""Write a file containing the current process ID."""
2263 import codecs, os, os.path
2264 pid = unicode(os.getpid())
2265 log(u"Process ID: " + pid)
2266 file_name = universe.contents[u"internal:process"].get(u"pidfile")
2267 if file_name:
2268 if not os.path.isabs(file_name):
2269 file_name = os.path.join(universe.startdir, file_name)
2270 file_descriptor = codecs.open(file_name, u"w", u"utf-8")
2271 file_descriptor.write(pid + u"\n")
2272 file_descriptor.flush()
2273 file_descriptor.close()
2274
2276 u"""Remove the file containing the current process ID."""
2277 import os, os.path
2278 file_name = universe.contents[u"internal:process"].get(u"pidfile")
2279 if file_name:
2280 if not os.path.isabs(file_name):
2281 file_name = os.path.join(universe.startdir, file_name)
2282 if os.access(file_name, os.W_OK): os.remove(file_name)
2283
2284 -def excepthook(excepttype, value, tracebackdata):
2285 u"""Handle uncaught exceptions."""
2286 import traceback
2287
2288
2289 message = u"".join(
2290 traceback.format_exception(excepttype, value, tracebackdata)
2291 )
2292
2293
2294 try: log(message, 9)
2295 except: pass
2296
2297
2298 try: sys.stderr.write(message)
2299 except: pass
2300
2302 u"""Handle external signals."""
2303 import signal
2304
2305
2306 message = u"Caught signal: "
2307
2308
2309 if what == signal.SIGHUP:
2310 message += u"hangup (reloading)"
2311 universe.reload_flag = True
2312
2313
2314 elif what == signal.SIGTERM:
2315 message += u"terminate (halting)"
2316 universe.terminate_flag = True
2317
2318
2319 else: message += unicode(what) + u" (unhandled)"
2320
2321
2322 log(message, 8)
2323
2328
2330 u"""Assign a customized handler for some signals."""
2331 import signal
2332 signal.signal(signal.SIGHUP, sighook)
2333 signal.signal(signal.SIGTERM, sighook)
2334
2336 """This contains functions to be performed when starting the engine."""
2337 import sys
2338
2339
2340 if len(sys.argv) > 1: conffile = sys.argv[1]
2341 else: conffile = u""
2342
2343
2344 global universe
2345 universe = Universe(conffile, True)
2346
2347
2348 log(u"Started mudpy with command line: " + u" ".join(sys.argv))
2349
2350
2351 daemonize(universe)
2352
2353
2354 override_excepthook()
2355
2356
2357 assign_sighook()
2358
2359
2360 create_pidfile(universe)
2361
2362
2363 return universe
2364
2366 """This contains functions to be performed when shutting down the engine."""
2367
2368
2369 universe.save()
2370
2371
2372 log(u"Shutting down now.")
2373
2374
2375 remove_pidfile(universe)
2376