1 """Miscellaneous objects for the MUFF Engine"""
3 # Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
4 # Licensed per terms in the LICENSE file distributed with this software.
8 # used by several functions for random calls
11 # used to match the 'L' at the end of a long int in repr_long
14 # random_name uses string.strip
17 # the log function uses time.asctime for creating timestamps
20 # hack to load all modules in the muff package
22 for module in muff.__all__:
23 exec("import " + module)
25 def broadcast(message):
26 """Send a message to all connected users."""
27 for each_user in muffvars.userlist: each_user.send("$(eol)" + message)
32 # the time in posix log timestamp format
33 timestamp = time.asctime()[4:19]
35 # send the timestamp and message to standard output
36 print(timestamp + " " + message)
38 def wrap_ansi_text(text, width):
39 """Wrap text with arbitrary width while ignoring ANSI colors."""
41 # the current position in the entire text string, including all
42 # characters, printable or otherwise
45 # the current text position relative to the begining of the line,
46 # ignoring color escape sequences
49 # whether the current character is part of a color escape sequence
52 # iterate over each character from the begining of the text
53 for each_character in text:
55 # the current character is the escape character
56 if each_character == chr(27):
59 # the current character is within an escape sequence
62 # the current character is m, which terminates the
63 # current escape sequence
64 if each_character == "m":
67 # the current character is a newline, so reset the relative
68 # position (start a new line)
69 elif each_character == "\n":
72 # the current character meets the requested maximum line width,
73 # so we need to backtrack and find a space at which to wrap
74 elif relative_position == width:
76 # distance of the current character examined from the
80 # count backwards until we find a space
81 while text[absolute_position - wrap_offset] != " ":
84 # insert an eol in place of the space
85 text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
87 # increase the absolute position because an eol is two
88 # characters but the space it replaced was only one
89 absolute_position += 1
91 # now we're at the begining of a new line, plus the
92 # number of characters wrapped from the previous line
93 relative_position = wrap_offset
95 # as long as the character is not a carriage return and the
96 # other above conditions haven't been met, count it as a
98 elif each_character != "\r":
99 relative_position += 1
101 # increase the absolute position for every character
102 absolute_position += 1
104 # return the newly-wrapped text
107 def weighted_choice(data):
108 """Takes a dict weighted by value and returns a random key."""
110 # this will hold our expanded list of keys from the data
113 # create thee expanded list of keys
114 for key in data.keys():
115 for count in range(data[key]):
118 # return one at random
119 return random.choice(expanded)
122 """Returns a random character name."""
124 # the vowels and consonants needed to create romaji syllables
125 vowels = [ "a", "i", "u", "e", "o" ]
126 consonants = ["'", "k", "z", "s", "sh", "z", "j", "t", "ch", "ts", "d", "n", "h", "f", "m", "y", "r", "w" ]
128 # this dict will hold our weighted list of syllables
131 # generate the list with an even weighting
132 for consonant in consonants:
134 syllables[consonant + vowel] = 1
136 # we'll build the name into this string
139 # create a name of random length from the syllables
140 for syllable in range(random.randrange(2, 6)):
141 name += weighted_choice(syllables)
143 # strip any leading quotemark, capitalize and return the name
144 return string.strip(name, "'").capitalize()
146 def repr_long(value):
147 string_value = repr(value)
148 if re.match('\d*L$', string_value): return string_value.strip("L")
149 else: return string_value
151 def getlong(config, section, option):
153 return int(config.get(section, option).strip("L"))
154 except ConfigParser.NoSectionError:
155 config.add_section(section)
156 return getlong(config, section, option)
157 except ConfigParser.NoOptionError:
158 setlong(config, section, option, 0)
159 return getlong(config, section, option)
161 def setlong(config, section, option, value):
162 return config.set(section, option, repr_long(value))
164 def replace_macros(user, text, is_input=False):
166 macro_start = string.find(text, "$(")
167 if macro_start == -1: break
168 macro_end = string.find(text, ")", macro_start) + 1
169 macro = text[macro_start:macro_end]
170 if macro in muffvars.macros.keys():
171 text = string.replace(text, macro, muffvars.macros[macro])
173 # the user's account name
174 elif macro == "$(account)":
175 text = string.replace(text, macro, user.name)
177 # third person subjective pronoun
178 elif macro == "$(tpsp)":
179 if user.avatar.get("gender") == "male":
180 text = string.replace(text, macro, "he")
181 elif user.avatar.get("gender") == "female":
182 text = string.replace(text, macro, "she")
184 text = string.replace(text, macro, "it")
186 # third person objective pronoun
187 elif macro == "$(tpop)":
188 if user.avatar.get("gender") == "male":
189 text = string.replace(text, macro, "him")
190 elif user.avatar.get("gender") == "female":
191 text = string.replace(text, macro, "her")
193 text = string.replace(text, macro, "it")
195 # third person possessive pronoun
196 elif macro == "$(tppp)":
197 if user.avatar.get("gender") == "male":
198 text = string.replace(text, macro, "his")
199 elif user.avatar.get("gender") == "female":
200 text = string.replace(text, macro, "hers")
202 text = string.replace(text, macro, "its")
204 # if we get here, log and replace it with null
206 text = string.replace(text, macro, "")
208 log("Unexpected replacement macro " + macro + " encountered.")
210 # replace the look-like-a-macro sequence
211 text = string.replace(text, "$_(", "$(")
215 def check_time(frequency):
216 """Check for a factor of the current increment count."""
217 if type(frequency) is str:
218 frequency = muffconf.getint("time", frequency)
219 return not getlong(muffvars.variable_data, "time", "elapsed") % frequency
222 """The things which should happen on each pulse, aside from reloads."""
224 # open the listening socket if it hasn't been already
225 if not muffvars.newsocket: muffsock.initialize_server_socket()
227 # assign a user if a new connection is waiting
228 user = muffsock.check_for_connection(muffvars.newsocket)
229 if user: muffvars.userlist.append(user)
231 # iterate over the connected users
232 for user in muffvars.userlist: user.pulse()
234 # update the log every now and then
235 if check_time("frequency_log"):
236 log(repr(len(muffvars.userlist)) + " connection(s)")
238 # periodically save everything
239 if check_time("frequency_save"):
241 for user in muffvars.userlist: user.save()
242 muffuniv.universe.save()
244 # pause for a configurable amount of time (decimal seconds)
245 time.sleep(muffconf.getfloat("time", "increment"))
247 # increment the elapsed increment counter
248 setlong(muffvars.variable_data, "time", "elapsed",
249 getlong(muffvars.variable_data, "time", "elapsed") + 1)
252 """Reload data into new persistent objects."""
255 temporary_userlist = []
256 for user in muffvars.userlist: temporary_userlist.append(user)
257 for user in temporary_userlist: user.reload()
258 del(temporary_userlist)