Imported from archive.
[mudpy.git] / lib / muff / muffmisc.py
1 """Miscellaneous objects for the MUFF Engine"""
2
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.
5
6 import ConfigParser
7
8 # used by several functions for random calls
9 import random
10
11 # used to match the 'L' at the end of a long int in repr_long
12 import re
13
14 # random_name uses string.strip
15 import string
16
17 # the log function uses time.asctime for creating timestamps
18 import time
19
20 # hack to load all modules in the muff package
21 import muff
22 for module in muff.__all__:
23         exec("import " + module)
24
25 def broadcast(message):
26         """Send a message to all connected users."""
27         for each_user in muffvars.userlist: each_user.send("$(eol)" + message)
28
29 def log(message):
30         """Log a message."""
31
32         # the time in posix log timestamp format
33         timestamp = time.asctime()[4:19]
34
35         # send the timestamp and message to standard output
36         print(timestamp + " " + message)
37
38 def wrap_ansi_text(text, width):
39         """Wrap text with arbitrary width while ignoring ANSI colors."""
40
41         # the current position in the entire text string, including all
42         # characters, printable or otherwise
43         absolute_position = 0
44
45         # the current text position relative to the begining of the line,
46         # ignoring color escape sequences
47         relative_position = 0
48
49         # whether the current character is part of a color escape sequence
50         escape = False
51
52         # iterate over each character from the begining of the text
53         for each_character in text:
54
55                 # the current character is the escape character
56                 if each_character == chr(27):
57                         escape = True
58
59                 # the current character is within an escape sequence
60                 elif escape:
61
62                         # the current character is m, which terminates the
63                         # current escape sequence
64                         if each_character == "m":
65                                 escape = False
66
67                 # the current character is a newline, so reset the relative
68                 # position (start a new line)
69                 elif each_character == "\n":
70                         relative_position = 0
71
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:
75
76                         # distance of the current character examined from the
77                         # relative position
78                         wrap_offset = 0
79
80                         # count backwards until we find a space
81                         while text[absolute_position - wrap_offset] != " ":
82                                 wrap_offset += 1
83
84                         # insert an eol in place of the space
85                         text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
86
87                         # increase the absolute position because an eol is two
88                         # characters but the space it replaced was only one
89                         absolute_position += 1
90
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
94
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
97                 # printable character
98                 elif each_character != "\r":
99                         relative_position += 1
100
101                 # increase the absolute position for every character
102                 absolute_position += 1
103
104         # return the newly-wrapped text
105         return text
106
107 def weighted_choice(data):
108         """Takes a dict weighted by value and returns a random key."""
109
110         # this will hold our expanded list of keys from the data
111         expanded = []
112
113         # create thee expanded list of keys
114         for key in data.keys():
115                 for count in range(data[key]):
116                         expanded.append(key)
117
118         # return one at random
119         return random.choice(expanded)
120
121 def random_name():
122         """Returns a random character name."""
123
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" ]
127
128         # this dict will hold our weighted list of syllables
129         syllables = {}
130
131         # generate the list with an even weighting
132         for consonant in consonants:
133                 for vowel in vowels:
134                         syllables[consonant + vowel] = 1
135
136         # we'll build the name into this string
137         name = ""
138
139         # create a name of random length from the syllables
140         for syllable in range(random.randrange(2, 6)):
141                 name += weighted_choice(syllables)
142
143         # strip any leading quotemark, capitalize and return the name
144         return string.strip(name, "'").capitalize()
145
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
150
151 def getlong(config, section, option):
152         try:
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)
160
161 def setlong(config, section, option, value):
162         return config.set(section, option, repr_long(value))
163
164 def replace_macros(user, text, is_input=False):
165         while True:
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])
172
173                 # the user's account name
174                 elif macro == "$(account)":
175                         text = string.replace(text, macro, user.name)
176
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")
183                         else:
184                                 text = string.replace(text, macro, "it")
185
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")
192                         else:
193                                 text = string.replace(text, macro, "it")
194
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")
201                         else:
202                                 text = string.replace(text, macro, "its")
203
204                 # if we get here, log and replace it with null
205                 else:
206                         text = string.replace(text, macro, "")
207                         if not is_input:
208                                 log("Unexpected replacement macro " + macro + " encountered.")
209
210         # replace the look-like-a-macro sequence
211         text = string.replace(text, "$_(", "$(")
212
213         return text
214
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
220
221 def on_pulse():
222         """The things which should happen on each pulse, aside from reloads."""
223
224         # open the listening socket if it hasn't been already
225         if not muffvars.newsocket: muffsock.initialize_server_socket()
226
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)
230
231         # iterate over the connected users
232         for user in muffvars.userlist: user.pulse()
233
234         # update the log every now and then
235         if check_time("frequency_log"):
236                 log(repr(len(muffvars.userlist)) + " connection(s)")
237
238         # periodically save everything
239         if check_time("frequency_save"):
240                 muffvars.save()
241                 for user in muffvars.userlist: user.save()
242                 muffuniv.universe.save()
243
244         # pause for a configurable amount of time (decimal seconds)
245         time.sleep(muffconf.getfloat("time", "increment"))
246
247         # increment the elapsed increment counter
248         setlong(muffvars.variable_data, "time", "elapsed",
249                 getlong(muffvars.variable_data, "time", "elapsed") + 1)
250
251 def reload_data():
252         """Reload data into new persistent objects."""
253
254         # reload the users
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)
259