* LICENSE, mudpy, mudpy.py: Altered the copyright statements to
correctly mention all years instead of only the most recent.
* command (command:chat, command:say), mydpy.py (User.send)
(command_chat, first_word, handler_active): Added a chat command
which toggles the avatar in and out of a mode where all further
lines of input are passed as parameters to the say command.
* command (command:quit): Minor clarifications to several command
help texts.
* command (command:say), mudpy.conf (internal:language), mudpy.py
(command_say): Moved the capitalize_words list facet to a more
flexible typos dict, and condensed punctuation_* facets into an
actions dict facet.
* mudpy.py (User.deactivate_avatar): Fixed a bug where avatars
without a location could trigger an exception.
(command_help): Added a see_also list facet for menu elements, which
allow help output to suggest other related commands.
(create_pidfile, remove_pidfile): Minor adjustments to assure file
path canonicalization.
(get_loglines): Aesthetic tweaks to the show log output.
(on_pulse): Create internal:counters if it doesn't exist, to avoid
throwing an exception.
-Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
[__control__]
read_only = yes
[__control__]
read_only = yes
+[command:chat]
+action = command_chat(actor)
+description = Enter and leave chat mode.
+help = The chat command toggles chat mode. When in chat mode, all input is passed as a parameter to the say command, unless prepended by an exclamation mark (!). For example, to leave chat mode, use:$(eol)$(eol) !chat
+see_also = say
+
[command:create]
action = command_create(actor, parameters)
administrative = yes
[command:create]
action = command_create(actor, parameters)
administrative = yes
[command:quit]
action = command_quit(actor)
description = Leave Example.
[command:quit]
action = command_quit(actor)
description = Leave Example.
-help = This will save your account and disconnect your client connection.
+help = This will deactivate your avatar and return you to the main menu.
[command:reload]
action = command_reload(actor)
[command:reload]
action = command_reload(actor)
[command:say]
action = command_say(actor, parameters)
description = State something out loud.
[command:say]
action = command_say(actor, parameters)
description = State something out loud.
-help = This allows you to speak to other characters within the same room. If you end your sentence with specific punctuation, the aparent speech action (ask, exclaim, et cetera) will be adapted accordingly. It will also add punctuation and capitalize your message where needed.
+help = This allows you to speak to other characters within the same room. If you end your sentence with punctuation, the message displayed will incorporate an appropriate action (ask, exclaim, et cetera). It will also correct common typographical errors, add punctuation and capitalize your sentence as needed (assuming you speak one sentence per line). For example:$(eol)$(eol) > say youre sure i went teh wrong way?$(eol) You ask, "You're sure I went the wrong way?"
+see_also = chat
[command:set]
action = command_set(actor, parameters)
[command:set]
action = command_set(actor, parameters)
#!/usr/bin/python
"""Skeletal executable for the mudpy engine."""
#!/usr/bin/python
"""Skeletal executable for the mudpy engine."""
-# Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
-# Licensed per terms in the LICENSE file distributed with this software.
+# Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+# reserved. Licensed per terms in the LICENSE file distributed with this
+# software.
# core objects for the mudpy engine
import mudpy
# core objects for the mudpy engine
import mudpy
read_only = yes
[internal:language]
read_only = yes
[internal:language]
-capitalize_words = [ "i", "i'd", "i'll", "i'm" ]
+actions = { "?": "ask", ",": "begin", "-": "begin", ":": "begin", ";": "begin", "!": "exclaim", "...": "muse", ".": "say" }
-punctuation_ask = ?
-punctuation_begin = [ ",", "-", ":", ";" ]
-punctuation_exclaim = !
-punctuation_muse = ...
-punctuation_say = .
+typos = { "i": "I", "i'd": "I'd", "i'll": "I'll", "i'm": "I'm", "teh": "the", "theyre": "they're", "youre": "you're" }
[internal:limits]
#default_admins = admin
[internal:limits]
#default_admins = admin
[internal:logging]
#file = mudpy.log
max_log_lines = 1000
[internal:logging]
#file = mudpy.log
max_log_lines = 1000
#syslog = mudpy
[internal:network]
#syslog = mudpy
[internal:network]
"""Core objects for the mudpy engine."""
"""Core objects for the mudpy engine."""
-# Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
-# Licensed per terms in the LICENSE file distributed with this software.
+# Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+# reserved. Licensed per terms in the LICENSE file distributed with this
+# software.
# import some things we need
from ConfigParser import RawConfigParser
# import some things we need
from ConfigParser import RawConfigParser
from signal import SIGHUP, SIGTERM, signal
from socket import AF_INET, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET, socket
from stat import S_IMODE, ST_MODE
from signal import SIGHUP, SIGTERM, signal
from socket import AF_INET, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET, socket
from stat import S_IMODE, ST_MODE
+from string import digits, letters, punctuation, uppercase
from sys import argv, stderr
from syslog import LOG_PID, LOG_INFO, LOG_DAEMON, closelog, openlog, syslog
from telnetlib import DO, DONT, ECHO, EOR, GA, IAC, LINEMODE, SB, SE, SGA, WILL, WONT
from sys import argv, stderr
from syslog import LOG_PID, LOG_INFO, LOG_DAEMON, closelog, openlog, syslog
from telnetlib import DO, DONT, ECHO, EOR, GA, IAC, LINEMODE, SB, SE, SGA, WILL, WONT
element.clean_contents()
def new(self):
element.clean_contents()
def new(self):
+ """Create a new, empty Universe (the Big Bang)."""
new_universe = Universe()
for attribute in vars(self).keys():
exec("new_universe." + attribute + " = self." + attribute)
new_universe = Universe()
for attribute in vars(self).keys():
exec("new_universe." + attribute + " = self." + attribute)
# tack on a prompt if active
if self.state == "active":
if not just_prompt: output += "$(eol)"
# tack on a prompt if active
if self.state == "active":
if not just_prompt: output += "$(eol)"
- if add_prompt: output += "> "
+ if add_prompt:
+ output += "> "
+ mode = self.avatar.get("mode")
+ if mode: output += "(" + mode + ") "
# find and replace macros in the output
output = replace_macros(self, output)
# find and replace macros in the output
output = replace_macros(self, output)
"""Have the active avatar leave the world."""
if self.avatar:
current = self.avatar.get("location")
"""Have the active avatar leave the world."""
if self.avatar:
current = self.avatar.get("location")
- self.avatar.set("default_location", current)
- self.avatar.echo_to_location("You suddenly wonder where " + self.avatar.get("name") + " went.")
- del universe.contents[current].contents[self.avatar.key]
- self.avatar.remove_facet("location")
+ if current:
+ self.avatar.set("default_location", current)
+ self.avatar.echo_to_location("You suddenly wonder where " + self.avatar.get("name") + " went.")
+ del universe.contents[current].contents[self.avatar.key]
+ self.avatar.remove_facet("location")
self.avatar.owner = None
self.avatar = None
self.avatar.owner = None
self.avatar = None
# a couple references we need
file_name = universe.categories["internal"]["logging"].get("file")
# a couple references we need
file_name = universe.categories["internal"]["logging"].get("file")
- if not isabs(file_name):
- file_name = path_join(universe.startdir, file_name)
max_log_lines = universe.categories["internal"]["logging"].getint("max_log_lines")
syslog_name = universe.categories["internal"]["logging"].get("syslog")
timestamp = asctime()[4:19]
max_log_lines = universe.categories["internal"]["logging"].getint("max_log_lines")
syslog_name = universe.categories["internal"]["logging"].get("syslog")
timestamp = asctime()[4:19]
# send the timestamp and line to a file
if file_name:
# send the timestamp and line to a file
if file_name:
+ if not isabs(file_name):
+ file_name = path_join(universe.startdir, file_name)
file_descriptor = file(file_name, "a")
for line in lines: file_descriptor.write(timestamp + " " + line + "\n")
file_descriptor.flush()
file_descriptor = file(file_name, "a")
for line in lines: file_descriptor.write(timestamp + " " + line + "\n")
file_descriptor.flush()
message = "There are " + str(total_count)
message += " log lines in memory and " + str(filtered_count)
message += " at or above level " + str(level) + "."
message = "There are " + str(total_count)
message += " log lines in memory and " + str(filtered_count)
message += " at or above level " + str(level) + "."
- message += " The lines from " + str(stop) + " to " + str(start)
- message += " are:$(eol)$(eol)"
+ message += " The matching lines from " + str(stop) + " to "
+ message += str(start) + " are:$(eol)$(eol)"
# add the text from the selected lines
if stop > 1: range_lines = loglines[-start:-(stop-1)]
# add the text from the selected lines
if stop > 1: range_lines = loglines[-start:-(stop-1)]
"""Escapes replacement macros in text."""
return text.replace("$(", "$_(")
"""Escapes replacement macros in text."""
return text.replace("$(", "$_(")
+def first_word(text, separator=" "):
+ """Returns a tuple of the first word and the rest."""
+ if text:
+ if text.find(separator) > 0: return text.split(separator, 1)
+ else: return text, ""
+ else: return "", ""
+
def on_pulse():
"""The things which should happen on each pulse, aside from reloads."""
def on_pulse():
"""The things which should happen on each pulse, aside from reloads."""
# iterate over the connected users
for user in universe.userlist: user.pulse()
# iterate over the connected users
for user in universe.userlist: user.pulse()
+ # add an element for counters if it doesn't exist
+ if not "counters" in universe.categories["internal"]:
+ universe.categories["internal"]["counters"] = Element("internal:counters", universe)
+
# update the log every now and then
if not universe.categories["internal"]["counters"].getint("mark"):
log(str(len(universe.userlist)) + " connection(s)")
# update the log every now and then
if not universe.categories["internal"]["counters"].getint("mark"):
log(str(len(universe.userlist)) + " connection(s)")
# is there input?
if input_data:
# is there input?
if input_data:
- # split out the command (first word) and parameters (everything else)
- if input_data.find(" ") > 0:
- command_name, parameters = input_data.split(" ", 1)
+ # split out the command and parameters
+ actor = user.avatar
+ mode = actor.get("mode")
+ if mode and input_data.startswith("!"):
+ command_name, parameters = first_word(input_data[1:])
+ elif mode == "chat":
+ command_name = "say"
+ parameters = input_data
- command_name = input_data
- parameters = ""
+ command_name, parameters = first_word(input_data)
# lowercase the command
command_name = command_name.lower()
# lowercase the command
command_name = command_name.lower()
else: command = None
# if it's allowed, do it
else: command = None
# if it's allowed, do it
if actor.can_run(command): exec(command.get("action"))
# otherwise, give an error
if actor.can_run(command): exec(command.get("action"))
# otherwise, give an error
help_text = "No help is provided for this command."
output += help_text
help_text = "No help is provided for this command."
output += help_text
+ # list related commands
+ see_also = command.getlist("see_also")
+ if see_also:
+ really_see_also = ""
+ for item in see_also:
+ if item in universe.categories["command"]:
+ command = universe.categories["command"][item]
+ if actor.can_run(command):
+ if really_see_also:
+ really_see_also += ", "
+ if command.getboolean("administrative"):
+ really_see_also += "$(red)"
+ else:
+ really_see_also += "$(grn)"
+ really_see_also += item + "$(nrm)"
+ if really_see_also:
+ output += "$(eol)$(eol)See also: " + really_see_also
+
# no data for the requested command word
else:
output = "That is not an available command."
# no data for the requested command word
else:
output = "That is not an available command."
# the user entered a message
elif parameters:
# the user entered a message
elif parameters:
- # get rid of quote marks on the ends of the message and
- # capitalize the first letter
+ # get rid of quote marks on the ends of the message
message = parameters.strip("\"'`")
message = parameters.strip("\"'`")
- message = message[0].capitalize() + message[1:]
-
- # a dictionary of punctuation:action pairs
- actions = {}
- for facet in universe.categories["internal"]["language"].facets():
- if facet.startswith("punctuation_"):
- action = facet.split("_")[1]
- for mark in universe.categories["internal"]["language"].getlist(facet):
- actions[mark] = action
# match the punctuation used, if any, to an action
# match the punctuation used, if any, to an action
+ actions = universe.categories["internal"]["language"].getdict("actions")
default_punctuation = universe.categories["internal"]["language"].get("default_punctuation")
default_punctuation = universe.categories["internal"]["language"].get("default_punctuation")
- action = actions[default_punctuation]
for mark in actions.keys():
for mark in actions.keys():
- if message.endswith(mark) and mark != default_punctuation:
+ if message.endswith(mark):
action = actions[mark]
break
action = actions[mark]
break
- # if the action is default and there is no mark, add one
- if action == actions[default_punctuation] and not message.endswith(default_punctuation):
- message += default_punctuation
+ # add punctuation if needed
+ if not action:
+ action = actions[default_punctuation]
+ if message and not message[-1] in punctuation:
+ message += default_punctuation
+
+ # decapitalize the first letter to improve matching
+ if message and message[0] in uppercase:
+ message = message[0].lower() + message[1:]
+
+ # iterate over all words in message, replacing typos
+ typos = universe.categories["internal"]["language"].getdict("typos")
+ words = message.split()
+ for index in range(len(words)):
+ word = words[index]
+ bare_word = word.strip(punctuation)
+ if bare_word in typos.keys():
+ words[index] = word.replace(bare_word, typos[bare_word])
+ message = " ".join(words)
- # capitalize a list of words within the message
- capitalize_words = universe.categories["internal"]["language"].getlist("capitalize_words")
- for word in capitalize_words:
- message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
+ # capitalize the first letter
+ message = message[0].upper() + message[1:]
# tell the room
actor.echo_to_location(actor.get("name") + " " + action + "s, \"" + message + "\"")
# tell the room
actor.echo_to_location(actor.get("name") + " " + action + "s, \"" + message + "\"")
else:
actor.send("What do you want to say?")
else:
actor.send("What do you want to say?")
+def command_chat(actor):
+ """Toggle chat mode."""
+ mode = actor.get("mode")
+ if not mode:
+ actor.set("mode", "chat")
+ actor.send("Entering chat mode (use $(grn)!chat$(nrm) to exit).")
+ elif mode == "chat":
+ actor.remove_facet("mode")
+ actor.send("Exiting chat mode.")
+ else: actor.send("Sorry, but you're already busy with something else!")
+
def command_show(actor, parameters):
"""Show program data."""
message = ""
def command_show(actor, parameters):
"""Show program data."""
message = ""
pid = str(getpid())
log("Process ID: " + pid)
file_name = universe.contents["internal:process"].get("pidfile")
pid = str(getpid())
log("Process ID: " + pid)
file_name = universe.contents["internal:process"].get("pidfile")
- if not isabs(file_name):
- file_name = path_join(universe.startdir, file_name)
+ if not isabs(file_name):
+ file_name = path_join(universe.startdir, file_name)
file_descriptor = file(file_name, 'w')
file_descriptor.write(pid + "\n")
file_descriptor.flush()
file_descriptor = file(file_name, 'w')
file_descriptor.write(pid + "\n")
file_descriptor.flush()
def remove_pidfile(universe):
"""Remove the file containing the current process ID."""
file_name = universe.contents["internal:process"].get("pidfile")
def remove_pidfile(universe):
"""Remove the file containing the current process ID."""
file_name = universe.contents["internal:process"].get("pidfile")
- if not isabs(file_name):
- file_name = path_join(universe.startdir, file_name)
- if file_name and access(file_name, W_OK): remove(file_name)
+ if file_name:
+ if not isabs(file_name):
+ file_name = path_join(universe.startdir, file_name)
+ if access(file_name, W_OK): remove(file_name)
def excepthook(excepttype, value, traceback):
"""Handle uncaught exceptions."""
def excepthook(excepttype, value, traceback):
"""Handle uncaught exceptions."""