Imported from archive.
[mudpy.git] / 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 # random_name uses string.strip
12 import string
13
14 # the log function uses time.asctime for creating timestamps
15 import time
16
17 # hack to load all modules in the muff package
18 import muff
19 for module in muff.__all__:
20         exec("import " + module)
21
22 def broadcast(message):
23         """Send a message to all connected users."""
24         for each_user in muffvars.userlist: each_user.send("$(eol)" + message)
25
26 def log(message):
27         """Log a message."""
28
29         # the time in posix log timestamp format
30         timestamp = time.asctime()[4:19]
31
32         # send the timestamp and message to standard output
33         print(timestamp + " " + message)
34
35 def wrap_ansi_text(text, width):
36         """Wrap text with arbitrary width while ignoring ANSI colors."""
37
38         # the current position in the entire text string, including all
39         # characters, printable or otherwise
40         absolute_position = 0
41
42         # the current text position relative to the begining of the line,
43         # ignoring color escape sequences
44         relative_position = 0
45
46         # whether the current character is part of a color escape sequence
47         escape = False
48
49         # iterate over each character from the begining of the text
50         for each_character in text:
51
52                 # the current character is the escape character
53                 if each_character == chr(27):
54                         escape = True
55
56                 # the current character is within an escape sequence
57                 elif escape:
58
59                         # the current character is m, which terminates the
60                         # current escape sequence
61                         if each_character == "m":
62                                 escape = False
63
64                 # the current character is a newline, so reset the relative
65                 # position (start a new line)
66                 elif each_character == "\n":
67                         relative_position = 0
68
69                 # the current character meets the requested maximum line width,
70                 # so we need to backtrack and find a space at which to wrap
71                 elif relative_position == width:
72
73                         # distance of the current character examined from the
74                         # relative position
75                         wrap_offset = 0
76
77                         # count backwards until we find a space
78                         while text[absolute_position - wrap_offset] != " ":
79                                 wrap_offset += 1
80
81                         # insert an eol in place of the space
82                         text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
83
84                         # increase the absolute position because an eol is two
85                         # characters but the space it replaced was only one
86                         absolute_position += 1
87
88                         # now we're at the begining of a new line, plus the
89                         # number of characters wrapped from the previous line
90                         relative_position = wrap_offset
91
92                 # as long as the character is not a carriage return and the
93                 # other above conditions haven't been met, count it as a
94                 # printable character
95                 elif each_character != "\r":
96                         relative_position += 1
97
98                 # increase the absolute position for every character
99                 absolute_position += 1
100
101         # return the newly-wrapped text
102         return text
103
104 def weighted_choice(data):
105         """Takes a dict weighted by value and returns a random key."""
106
107         # this will hold our expanded list of keys from the data
108         expanded = []
109
110         # create thee expanded list of keys
111         for key in data.keys():
112                 for count in range(data[key]):
113                         expanded.append(key)
114
115         # return one at random
116         return random.choice(expanded)
117
118 def random_name():
119         """Returns a random character name."""
120
121         # the vowels and consonants needed to create romaji syllables
122         vowels = [ "a", "i", "u", "e", "o" ]
123         consonants = ["'", "k", "z", "s", "sh", "z", "j", "t", "ch", "ts", "d", "n", "h", "f", "m", "y", "r", "w" ]
124
125         # this dict will hold our weighted list of syllables
126         syllables = {}
127
128         # generate the list with an even weighting
129         for consonant in consonants:
130                 for vowel in vowels:
131                         syllables[consonant + vowel] = 1
132
133         # we'll build the name into this string
134         name = ""
135
136         # create a name of random length from the syllables
137         for syllable in range(random.randrange(2, 6)):
138                 name += weighted_choice(syllables)
139
140         # strip any leading quotemark, capitalize and return the name
141         return string.strip(name, "'").capitalize()
142
143 def replace_macros(user, text, is_input=False):
144         """Replaces macros in text output."""
145         while True:
146                 macro_start = string.find(text, "$(")
147                 if macro_start == -1: break
148                 macro_end = string.find(text, ")", macro_start) + 1
149                 macro = text[macro_start:macro_end]
150                 if macro in muffvars.macros.keys():
151                         text = string.replace(text, macro, muffvars.macros[macro])
152
153                 # the user's account name
154                 elif macro == "$(account)":
155                         text = string.replace(text, macro, user.account.get("name"))
156
157                 # third person subjective pronoun
158                 elif macro == "$(tpsp)":
159                         if user.avatar.get("gender") == "male":
160                                 text = string.replace(text, macro, "he")
161                         elif user.avatar.get("gender") == "female":
162                                 text = string.replace(text, macro, "she")
163                         else:
164                                 text = string.replace(text, macro, "it")
165
166                 # third person objective pronoun
167                 elif macro == "$(tpop)":
168                         if user.avatar.get("gender") == "male":
169                                 text = string.replace(text, macro, "him")
170                         elif user.avatar.get("gender") == "female":
171                                 text = string.replace(text, macro, "her")
172                         else:
173                                 text = string.replace(text, macro, "it")
174
175                 # third person possessive pronoun
176                 elif macro == "$(tppp)":
177                         if user.avatar.get("gender") == "male":
178                                 text = string.replace(text, macro, "his")
179                         elif user.avatar.get("gender") == "female":
180                                 text = string.replace(text, macro, "hers")
181                         else:
182                                 text = string.replace(text, macro, "its")
183
184                 # if we get here, log and replace it with null
185                 else:
186                         text = string.replace(text, macro, "")
187                         if not is_input:
188                                 log("Unexpected replacement macro " + macro + " encountered.")
189
190         # replace the look-like-a-macro sequence
191         text = string.replace(text, "$_(", "$(")
192
193         return text
194
195 def check_time(frequency):
196         """Check for a factor of the current increment count."""
197         if type(frequency) is str:
198                 frequency = muffuniv.universe.categories["internal"]["time"].getint(frequency)
199         if not "counters" in muffuniv.universe.categories["internal"]:
200                 muffuniv.Element("internal:counters", muffuniv.universe)
201         return not muffuniv.universe.categories["internal"]["counters"].getint("elapsed") % frequency
202
203 def on_pulse():
204         """The things which should happen on each pulse, aside from reloads."""
205
206         # open the listening socket if it hasn't been already
207         if not muffvars.newsocket: muffsock.initialize_server_socket()
208
209         # assign a user if a new connection is waiting
210         user = muffsock.check_for_connection(muffvars.newsocket)
211         if user: muffvars.userlist.append(user)
212
213         # iterate over the connected users
214         for user in muffvars.userlist: user.pulse()
215
216         # update the log every now and then
217         if check_time("frequency_log"):
218                 log(repr(len(muffvars.userlist)) + " connection(s)")
219
220         # periodically save everything
221         if check_time("frequency_save"):
222                 muffuniv.universe.save()
223
224         # pause for a configurable amount of time (decimal seconds)
225         time.sleep(muffuniv.universe.categories["internal"]["time"].getfloat("increment"))
226
227         # increment the elapsed increment counter
228         muffuniv.universe.categories["internal"]["counters"].set("elapsed", muffuniv.universe.categories["internal"]["counters"].getint("elapsed") + 1)
229
230 def reload_data():
231         """Reload data into new persistent objects."""
232
233         # reload the users
234         temporary_userlist = []
235         for user in muffvars.userlist: temporary_userlist.append(user)
236         for user in temporary_userlist: user.reload()
237         del(temporary_userlist)
238