"""Miscellaneous functions for the mudpy engine."""
-# Copyright (c) 2004-2018 Jeremy Stanley <fungi@yuggoth.org>. Permission
-# to use, copy, modify, and distribute this software is granted under
-# terms provided in the LICENSE file distributed with this software.
+# Copyright (c) 2004-2018 mudpy authors. Permission to use, copy,
+# modify, and distribute this software is granted under terms
+# provided in the LICENSE file distributed with this software.
import codecs
import os
def reload(self):
"""Create a new element and replace this one."""
- Element(self.key, self.universe, self.origin)
- del(self)
+ args = (self.key, self.universe, self.origin)
+ self.destroy()
+ Element(*args)
def destroy(self):
"""Remove an element from the universe and destroy it."""
# it's possible for this to enter before logging configuration is read
pending_loglines = []
- # the files dict must exist and filename needs to be read-only
- if not hasattr(
- self, "files"
- ) or not (
- self.filename in self.files and self.files[
- self.filename
- ].is_writeable()
- ):
-
- # clear out all read-only files
- if hasattr(self, "files"):
- for data_filename in list(self.files.keys()):
- if not self.files[data_filename].is_writeable():
- del self.files[data_filename]
-
- # start loading from the initial file
- mudpy.data.Data(self.filename, self)
+ # start populating the (re)files dict from the base config
+ self.files = {}
+ mudpy.data.Data(self.filename, self)
# load default storage locations for groups
if hasattr(self, "contents") and "mudpy.filing" in self.contents:
if user.avatar in inactive_avatars:
inactive_avatars.remove(user.avatar)
- # go through all elements to clear out inactive avatar locations
- for element in self.contents.values():
- area = element.get("location")
- if element in inactive_avatars and area:
- if area in self.contents and element.key in self.contents[
- area
- ].contents:
- del self.contents[area].contents[element.key]
- element.set("default_location", area)
- element.remove_facet("location")
-
# another pass to straighten out all the element contents
for element in self.contents.values():
element.update_location()
self.output_queue = []
self.partial_input = b""
self.password_tries = 0
- self.state = "initial"
+ self.state = "telopt_negotiation"
self.telopts = {}
def quit(self):
"""Log, close the connection and remove."""
if self.account:
- name = self.account.get("name")
+ name = self.account.get("name", self)
else:
- name = ""
- if name:
- message = "User " + name
- else:
- message = "An unnamed user"
- message += " logged out."
- log(message, 2)
+ name = self
+ log("Logging out %s" % name, 2)
self.deactivate_avatar()
self.connection.close()
self.remove()
def reload(self):
"""Save, load a new user and relocate the connection."""
+ # copy old attributes
+ attributes = self.__dict__
+
# get out of the list
self.remove()
+ # get rid of the old user object
+ del(self)
+
# create a new user object
new_user = User()
# set everything equivalent
- for attribute in vars(self).keys():
- exec("new_user." + attribute + " = self." + attribute)
+ new_user.__dict__ = attributes
# the avatar needs a new owner
if new_user.avatar:
+ new_user.account = universe.contents[new_user.account.key]
+ new_user.avatar = universe.contents[new_user.avatar.key]
new_user.avatar.owner = new_user
# add it to the list
universe.userlist.append(new_user)
- # get rid of the old user object
- del(self)
-
def replace_old_connections(self):
"""Disconnect active users with the same name."""
log("Administrator %s authenticated." %
self.account.get("name"), 2)
else:
- # log("User %s authenticated." % self.account.get("name"), 2)
- log("User %s authenticated." % self.account.subkey, 2)
+ log("User %s authenticated for account %s." % (
+ self, self.account.subkey), 2)
def show_menu(self):
"""Send the user their current menu."""
def remove(self):
"""Remove a user from the list of connected users."""
+ log("Disconnecting account %s." % self, 0)
universe.userlist.remove(self)
def send(
self.check_idle()
# if output is paused, decrement the counter
- if self.state == "initial":
+ if self.state == "telopt_negotiation":
if self.negotiation_pause:
self.negotiation_pause -= 1
else:
mudpy.telnet.negotiate_telnet_options(self)
# separate multiple input lines
- new_input_lines = self.partial_input.split(b"\n")
+ new_input_lines = self.partial_input.split(b"\r\0")
+ if len(new_input_lines) == 1:
+ new_input_lines = new_input_lines[0].split(b"\r\n")
# if input doesn't end in a newline, replace the
# held partial input with the last line of it
- if not self.partial_input.endswith(b"\n"):
+ if not (
+ self.partial_input.endswith(b"\r\0") or
+ self.partial_input.endswith(b"\r\n")):
self.partial_input = new_input_lines.pop()
# otherwise, chop off the extra null input and reset
line = line.strip()
# log non-printable characters remaining
- if mudpy.telnet.is_enabled(self, mudpy.telnet.TELOPT_BINARY,
- mudpy.telnet.HIM):
+ if not mudpy.telnet.is_enabled(
+ self, mudpy.telnet.TELOPT_BINARY, mudpy.telnet.HIM):
asciiline = bytes([x for x in line if 32 <= x <= 126])
if line != asciiline:
logline = "Non-ASCII characters from "
universe)
self.avatar.append("inherit", "archetype.avatar")
self.account.append("avatars", self.avatar.key)
+ log("Created new avatar %s for user %s." % (
+ self.avatar.key, self.account.get("name")), 0)
def delete_avatar(self, avatar):
"""Remove an avatar from the world and from the user's list."""
if self.avatar is universe.contents[avatar]:
self.avatar = None
+ log("Deleting avatar %s for user %s." % (
+ avatar, self.account.get("name")), 0)
universe.contents[avatar].destroy()
avatars = self.account.get("avatars")
avatars.remove(avatar)
self.account.get("avatars")[index]]
self.avatar.owner = self
self.state = "active"
+ log("Activated avatar %s (%s)." % (
+ self.avatar.get("name"), self.avatar.key), 0)
self.avatar.go_home()
def deactivate_avatar(self):
"""Have the active avatar leave the world."""
if self.avatar:
+ log("Deactivating avatar %s (%s) for user %s." % (
+ self.avatar.get("name"), self.avatar.key,
+ self.account.get("name")), 0)
current = self.avatar.get("location")
if current:
self.avatar.set("default_location", current)
"""Destroy the user and associated avatars."""
for avatar in self.account.get("avatars"):
self.delete_avatar(avatar)
+ log("Destroying account %s for user %s." % (
+ self.account.get("name"), self), 0)
self.account.destroy()
def list_avatar_names(self):
# escape sequence
escape = False
- # track the most recent whitespace we've seen
- # TODO(fungi) exclude non-breaking spaces (\x0a)
- elif unicodedata.category(each_character) in ("Cc", "Zs"):
- if each_character == "\n":
- # the current character is a newline, so reset the relative
- # position too (start a new line)
- rel_pos = 0
- if each_character != "\r":
- # the current character is not a carriage return, so mark it as
- # whitespace (we don't want to break and wrap between CR+LF)
- last_abs_whitespace = abs_pos
- last_rel_whitespace = rel_pos
+ # the current character is a space
+ elif each_character == " ":
+ last_abs_whitespace = abs_pos
+ last_rel_whitespace = rel_pos
+
+ # the current character is a newline, so reset the relative
+ # position too (start a new line)
+ elif each_character == "\n":
+ rel_pos = 0
+ last_abs_whitespace = abs_pos
+ last_rel_whitespace = rel_pos
# the current character meets the requested maximum line width, so we
# need to wrap unless the current word is wider than the terminal (in
# printable character
elif each_character != "\r":
rel_pos += glyph_columns(each_character)
- if unicodedata.category(each_character) in ("Cc", "Zs"):
- # TODO(fungi) exclude non-breaking spaces (\x0a)
+ if each_character in (" ", "\n"):
last_abs_whitespace = abs_pos
last_rel_whitespace = rel_pos
def reload_data():
"""Reload all relevant objects."""
- for user in universe.userlist[:]:
- user.reload()
- for element in universe.contents.values():
- if element.origin.is_writeable():
- element.reload()
+ universe.save()
+ old_userlist = universe.userlist[:]
+ for element in list(universe.contents.values()):
+ element.destroy()
universe.load()
+ for user in old_userlist:
+ user.reload()
def check_for_connection(listening_socket):
return None
# note that we got one
- log("Connection from " + address[0], 2)
+ log("New connection from %s." % address[0], 2)
# disable blocking so we can proceed whether or not we can send/receive
connection.setblocking(0)
# create a new user object
user = User()
+ log("Instantiated %s for %s." % (user, address[0]), 0)
# associate this connection with it
user.connection = connection