winopenrpg-developer Mailing List for winOpenRPG
Status: Inactive
Brought to you by:
digitalxero
You can subscribe to this list here.
| 2005 |
Jan
|
Feb
(6) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(22) |
Nov
|
Dec
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2006 |
Jan
(45) |
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
|
From: Digital X. <dig...@us...> - 2006-01-26 17:42:17
|
Update of /cvsroot/winopenrpg/openrpg1/plugins In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1906/plugins Added Files: blank.py cac.py cherrypy.py namesound.py ooc.py savewindow.py url2link.py Log Message: removed the xx requirment for plugin names --- NEW FILE: ooc.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !chat : instance of the chat window to write to def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'OOC Comments Tool' self.author = 'mDuo13' self.help = "Type '/ooc *message*' to send '(( *message* ))' -- it just preformats\n" self.help += "out of character comments for you." def plugin_enabled(self): #This is where you set any variables that need to be initalized when your plugin starts self.plugin_addcommand('/ooc', self.on_ooc, 'message - This puts (( message )) to let other players know you are talking out of character') def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin self.plugin_removecmd('/ooc') def on_ooc(self, cmdargs): #this is just an example function for a command you create create your own self.chat.ParsePost('(( ' + cmdargs + ' ))', 1, 1) --- NEW FILE: cherrypy.py --- import os import orpg.pluginhandler import thread from cherrypy import cpg import socket class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'CherryPy Web Server' self.author = 'Dj Gilcrease' self.help = 'This plugin turns OpenRPG into a Web server\n' self.help += 'allowing you to host your map and mini files localy' #You can set variables below here. Always set them to a blank value in this section. Use plugin_enabled #to set their proper values. self.isServerRunning = 'off' self.host = 0 def plugin_enabled(self): self.plugin_addcommand('/cherrypy', self.on_cherrypy, '[on | off | status] - This controls the CherryPy Web Server') tmp = socket.gethostbyname_ex('') for ip in tmp[2]: if ip[:7] == '192.168' or ip[:3] == '10.' or ip == '127.0.0.1' or (ip[:3] == '172' and (int(ip[5:6]) >= 16 and int(ip[5:6]) <=32)) : continue else: self.host = ip def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin self.plugin_removecmd('/cherrypy') cpg.server.stop() self.isServerRunning = 'off' def on_cherrypy(self, cmdargs): args = cmdargs.split(None,-1) if len(args) == 0 or args[0] == 'status': self.chat.InfoPost("CherryPy Web Server is currently: " + self.isServerRunning) self.chat.InfoPost("CherryPy Web Server address is: http://" + self.host + '/webfiles/') elif args[0] == 'on' and self.isServerRunning == 'off': self.webserver = thread.start_new_thread(self.startServer, (80,)) self.isServerRunning = 'on' elif args[0] == 'off' and self.isServerRunning == 'on': cpg.server.stop() self.isServerRunning = 'off' self.chat.InfoPost("CherryPy Web Server is now disabled") def startServer(self, port): try: if self.host == 0: raise Exception("Invalid IP address.<br>This error means you are behind a router or some other form of network that is giving you a Privet IP only (ie. 192.168.x.x, 10.x.x.x, 172.16 - 32.x.x)") self.chat.InfoPost("CherryPy Web Server is now running on http://" + self.host + '/webfiles/') cpg.server.start(configMap = {'staticContentList': [['images', r''+orpg.dirpath.dir_struct["icon"]+''],['webfiles', r''+orpg.dirpath.dir_struct["user"]+'webfiles/']], 'socketPort': port, 'logToScreen': 0, 'logFile':orpg.dirpath.dir_struct["user"]+'webfiles/log.txt', 'sessionStorageType':'ram', 'threadPool':10, 'sessionTimeout':30, 'sessionCleanUpDelay':30}) except Exception, e: self.chat.InfoPost("FAILED to start server!") self.chat.InfoPost(str(e)) self.isServerRunning = 'off' --- NEW FILE: savewindow.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'SaveWindow' self.author = 'mDuo13' self.help = "Saves the size and position of your OpenRPG window, as well as\n" self.help += "whether or not it is maximized. You must set the plugin to load on startup\n" self.help += "in order for this to work." #You can set variables below here. Always set them to a blank value in this section. Use plugin_enabled #to set their proper values. def plugin_enabled(self): if self.name in self.startplugs: win_xpos = self.plugindb.GetString(self.name,"win_xpos","-1") win_ypos = self.plugindb.GetString(self.name,"win_ypos","-1") maximized = self.plugindb.GetString(self.name,"maximized","0") win_xsize = self.plugindb.GetString(self.name,"win_xsize","-1") win_ysize = self.plugindb.GetString(self.name,"win_ysize","-1") x = int(win_xpos) y = int(win_ypos) is_maxed = int(maximized) width = int(win_xsize) height = int(win_ysize) if not is_maxed: self.parent.SetDimensions(x,y,width,height) self.parent.Maximize(is_maxed) def plugin_disabled(self): #This is called when OpenRPG shuts down (x_size,y_size) = self.parent.GetSizeTuple() (x_pos,y_pos) = self.parent.GetPositionTuple() is_maximized = self.bool2int(self.parent.IsMaximized()) self.plugindb.SetString(self.name,"win_xsize",str(x_size)) self.plugindb.SetString(self.name,"win_ysize",str(y_size)) self.plugindb.SetString(self.name,"win_xpos",str(x_pos)) self.plugindb.SetString(self.name,"win_ypos",str(y_pos)) self.plugindb.SetString(self.name,"maximized",str(is_maximized)) def bool2int(self, x): if x: return 1 else: return 0 --- NEW FILE: blank.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'Example Plugin' self.author = 'Your Name' self.help = 'Info About your plugin' #You can set variables below here. Always set them to a blank value in this section. Use plugin_enabled #to set their proper values. self.sample_variable = {} def plugin_enabled(self): #You can add new /commands like # self.plugin_addcommand(cmd, function, helptext) self.plugin_addcommand('/test', self.on_test, '- This is an example plugin command') #If you want your plugin to have more then one way to call the same function you can #use self.plugin_commandalias(alias name, command name) #You can also make shortcut commands like the following self.plugin_commandalias('/example', '/me is giving you an example') #if you want your plugin to use custom messages to comunicate with other people using the same plugin #you can add a message handler in a simmilar way to adding a new slash command. The first variable #'tester' in this case is the tage name for your custom xml message. The second variable is the function #you want to handle proccessing your messages when one is recived. #Be sure to delete your handler in plugin_disabled self.plugin_add_msg_handler('xxblank', self.on_xml_recive) #This is where you set any variables that need to be initalized when your plugin starts self.sample_variable = {1:'one', 2:'two'} def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin self.plugin_removecmd('/test') self.plugin_removecmd('/example') #This is the command to delete a message handler self.plugin_delete_msg_handler('tester') #This is how you should destroy a frame when the plugin is disabled #This same method should be used in close_module as well try: self.frame.Destroy() except: pass def on_test(self, cmdargs): #this is just an example function for a command you create. # cmdargs contains everything you typed after the command # so if you typed /test this is a test, cmdargs = this is a test # args are the individual arguments split. For the above example # args[0] = this , args[1] = is , args[2] = a , args[3] = test self.plugin_send_msg('<xxblank>' + cmdargs + '</xxblank>') args = cmdargs.split(None,-1) msg = 'cmdargs = %s' % (cmdargs) self.chat.InfoPost(msg) if len(args) == 0: self.chat.InfoPost("You have no args") else: i = 0 for n in args: msg = 'args[' + str(i) + '] = ' + n self.chat.InfoPost(msg) i += 1 def on_xml_recive(self,id,data,xml_dom): self.chat.InfoPost(self.name + ":: Message recived<br>" + data.replace("<","<").replace(">",">")) def pre_parse(self, text): #This is called just before a message is parsed by openrpg return text def send_msg(self, text, send): #This is called when a message is about to be sent out. #It covers all messages sent by the user, before they have been formatted. #If send is set to 0, the message will not be sent out to other #users, but it will still be posted to the user's chat normally. #Otherwise, send defaults to 1. (The message is sent as normal) return text, send def plugin_incoming_msg(self, text, type, name, player): #This is called whenever a message from someone else is received, no matter #what type of message it is. #The text variable is the text of the message. If the type is a regular #message, it is already formatted. Otherwise, it's not. #The type variable is an integer which tells you the type: 1=chat, 2=whisper #3=emote, 4=info, and 5=system. #The name variable is the name of the player who sent you the message. #The player variable contains lots of info about the player sending the #message, including name, ID#, and currently-set role. #Uncomment the following line to see the format for the player variable. #print player return text, type, name def post_msg(self, text, myself): #This is called whenever a message from anyone is about to be posted #to chat; it doesn't affect the copy of the message that gets sent to others #Be careful; system and info messages trigger this too. return text def refresh_counter(self): #This is called once per second. That's all you need to know. pass --- NEW FILE: namesound.py --- import os import orpg.pluginhandler from orpg.tools.orpg_sound import orpg_sound import orpg.dirpath class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'Name Sound' self.author = 'mDuo13' self.help = "This plays a 'hey!' sound whenever your name is said in chat. It is\n" self.help += "not HTML- or case-sensitive. You can also create nicknames to which the plugin\n" self.help += "will also respond. To add a nickname, type '/xxnick add *name*', where *name*\n" self.help += "is the nickname you want to add. Then, whenever *name* is said in chat, you'll\n" self.help += "hear the sound also. You can remove your nicknames by typing\n" self.help += "'/xxnick del*name*' where *name* is the nickname you wish to delete. Neither is\n" self.help += "case sensitive. Additionally, you can see what nicknames you currently have\n" self.help += "with '/xxnick list'." self.antispam = 0 self.names = [] self.soundfile = '' self.soundplayer = '' def plugin_enabled(self): self.plugin_addcommand('/xxnick', self.on_xxnick, 'add name|del name|list - This is the command for the namesound plugin') self.names = self.plugindb.GetList("xxnamesound", "names", []) self.soundfile = orpg.dirpath.dir_struct['plugins'] + 'heya.wav' self.soundplayer = orpg_sound(self.settings.get_setting("UnixSoundPlayer")) if not self.chat.html_strip(self.session.name.lower()) in self.names: self.names.append(self.chat.html_strip(self.session.name.lower())) def plugin_disabled(self): self.plugin_removecmd('/xxnick') def on_xxnick(self, cmdargs): args = cmdargs.split(None,-1) if len(args): name = cmdargs[len(args[0])+1:].lower().strip() if len(args) == 0 or args[0] == 'list': name_list = '' i = 0 for name in self.names: name_list += name if i < len(self.names)-1: name_list += ', ' i += 1 self.chat.InfoPost('Currently chacking for ' + name_list) elif args[0] == 'add': if name not in self.names and name != '': self.names.append(name) self.plugindb.SetList('xxnamesound', 'names', self.names) self.chat.InfoPost('The name ' + name + ' has been added to your nickname list. You will now hear a sound when someone says it in chat.') else: self.chat.InfoPost('The name ' + name + ' is already in your nickname list.') elif args[0] == 'del': if name in self.names: self.names.remove(name) self.plugindb.SetList('xxnamesound', 'names', self.names) self.chat.InfoPost('The name ' + name + ' has been removed from your nickname list.') else: self.chat.InfoPost('The name ' + name + ' is not in your nickname list.') def plugin_incoming_msg(self, text, type, name, player): if self.antispam > 0: return text, type, name for name in self.names: #print self.chat.html_strip(text.lower()).find(name.lower()) if self.chat.html_strip(text.lower()).find(name.lower()) != -1: self.soundplayer.play(self.soundfile) self.antispam = 1 break return text, type, name def refresh_counter(self): #This is called once per second. That's all you need to know. if self.antispam: self.antispam -= 0.04 --- NEW FILE: cac.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'Command Alias Creator' self.author = 'Dj Gilcrease' self.help = "This plugin lets you add Command Aliases.\neg /sits insted of /me sits down" self.newcmdaliases = {} def plugin_enabled(self): self.plugin_addcommand('/cmdalias', self.on_cmdalias, '[cmdalias_name fullcommand] [remove cmdalias_name] [clear] - (eg. <font color="#000000">/cmdalias /sits /me sits down</font> to add a command. OR <font color="#000000">/cmdalias remove /sits</font> to remove a single command. OR <font color="#000000">/cmdalias clear</font to clear the entire list)') self.newcmdaliases = self.plugindb.GetDict("xxcac", "newcmdaliases", {}) for n in self.newcmdaliases: if not self.shortcmdlist.has_key(n) and not self.cmdlist.has_key(n): self.plugin_commandalias(n, self.newcmdaliases[n]) def plugin_disabled(self): self.plugin_removecmd('/cmdalias') for n in self.newcmdaliases: self.plugin_removecmd(n) def on_cmdalias(self, cmdargs): args = cmdargs.split(" ",-1) if len(args) == 0: self.chat.InfoPost("USAGE: /cmdalias [cmdalias_name fullcommand] [remove cmdalias_name] [clear] - (eg. /sits /me sits down)") elif args[0] == 'remove': if self.newcmdaliases.has_key(args[1]): del self.newcmdaliases[args[1]] self.plugindb.SetDict("xxcac", "newcmdaliases", self.newcmdaliases) self.plugin_removecmd(args[1]) elif args[0] == 'clear': for n in self.newcmdaliases: self.plugin_removecmd(n) self.newcmdaliases = {} self.plugindb.SetDict("xxcac", "newcmdaliases", self.newcmdaliases) else: oldcmd = cmdargs[len(args[0])+1:] self.newcmdaliases[args[0]] = oldcmd self.plugindb.SetDict("xxcac", "newcmdaliases", self.newcmdaliases) self.plugin_commandalias(args[0], oldcmd) --- NEW FILE: url2link.py --- import os import orpg.pluginhandler import re class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !chat : instance of the chat window to write to def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'URL to link conversion' self.author = 'tdb30 tb...@wr...' self.help = "This plugin automaticaly wraps urls in link tags\n" self.help += "making them clickable." self.url_regex = None self.mailto_regex = None def plugin_enabled(self): #This is where you set any variables that need to be initalized when your plugin starts self.url_regex = re.compile(r"""\w{3,}://[A-Za-z0-9.=,:/&;?_%~+!$#-]{2,63}\.[A-Za-z0-9.=,:/&;?_%~+!$#-]{2,63}|[\w-]{2,63}\.[\w-]{2,63}\.[A-Za-z]{2,6}(/[A-Za-z0-9.=,:/&;?_%~+!$#-]+)?|[0-9]{2,3}\.[0-9]{2,3}\.[0-9]{1,3}\.[0-9]{1,3}""", re.I) self.mailto_regex = re.compile(r"""(mailto:)?[\w._-]+@[A-Za-z0-9.-]+""", re.I) def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin pass def send_msg(self, text, send): text = self.link_emails(text) text = self.link_urls(text) return text, send def plugin_incoming_msg(self, text, type, name, player): text = self.link_emails(text) text = self.link_urls(text) return text, type, name def link_urls(self, text): #The modified text accumulates into text2 so that the indices of the regex matches #in the original text variable don't get screwed up by the replacement. text2 = "" url = self.url_regex.search(text) if not url:#so that it doesn't accidentally delete the message text2 = text textafterurl = "" while url: urltext = url.group() if urltext.find("://")<0:#it might not load the web browser but is usable. For example, "maps.google.com" urltext = "http://"+urltext textbeforeurl = text[:url.start()] textafterurl = text[url.end():] if not (len(re.findall("<",textbeforeurl)) > len(re.findall(">",textbeforeurl))) and text[url.start()-1]!="@": text2 += textbeforeurl + "<a href='" + urltext + "'>" + url.group() + "</a>" else: text2 += textbeforeurl + url.group()#not urltext, because we don't wanna screw with it text = textafterurl url = self.url_regex.search(text) else:#once it's done -- this happens whether or not it found a URL to begin with text2 += textafterurl return text2 def link_emails(self, text): #The modified text accumulates into text2 so that the indices of the regex matches #in the original text variable don't get screwed up by the replacement. #the main differences between this and the link_urls function are: # (a) this one uses the mailto_regex instead of url_regex # (b) this one doesn't append http:// but rather mailto: text2 = "" url = self.mailto_regex.search(text) if not url:#so that it doesn't accidentally delete the message text2 = text textafterurl = "" while url: urltext = url.group() if urltext.find("mailto:")<0:#it's just a plain e-mail like md...@ya... instead of a mailto URL urltext = "mailto:"+urltext textbeforeurl = text[:url.start()] textafterurl = text[url.end():] if not (len(re.findall("<",textbeforeurl)) > len(re.findall(">",textbeforeurl))): #here it doesn't use urltext, but rather the "prettier" version (without mailto:) in the displayed text #even though the href URL is actually a mailto. text2 += textbeforeurl + "<a href='" + urltext + "'>" + url.group() + "</a>" else: text2 += textbeforeurl + url.group()#not urltext, because we don't wanna screw with it text = textafterurl url = self.mailto_regex.search(text) else:#once it's done -- this happens whether or not it found a URL to begin with text2 += textafterurl return text2 |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:40:44
|
Update of /cvsroot/winopenrpg/openrpg1/orpg In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1406/orpg Modified Files: plugins.py Log Message: removed the xx requirment for plugin names Index: plugins.py =================================================================== RCS file: /cvsroot/winopenrpg/openrpg1/orpg/plugins.py,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** plugins.py 26 Jan 2006 17:33:15 -0000 1.1 --- plugins.py 26 Jan 2006 17:40:36 -0000 1.2 *************** *** 228,232 **** self.socket={} for p in list_of_plugin_dir: ! if p[:2].lower()=="xx" and p[-3:]==".py": self.ImpPlugin(p[:-3]) --- 228,232 ---- self.socket={} for p in list_of_plugin_dir: ! if p[-3:]==".py": self.ImpPlugin(p[:-3]) |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:36:34
|
Update of /cvsroot/winopenrpg/openrpg1/plugins In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32400/plugins Removed Files: xxblank.py xxcac.py xxcherrypy.py xxnamesound.py xxooc.py xxsavewindow.py xxurl2link.py Log Message: Changing plugin names --- xxblank.py DELETED --- --- xxcac.py DELETED --- --- xxnamesound.py DELETED --- --- xxurl2link.py DELETED --- --- xxcherrypy.py DELETED --- --- xxsavewindow.py DELETED --- --- xxooc.py DELETED --- |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:29
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/templates In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/templates Added Files: about.html default_LobbyMessage.html default_Lobby_map.xml default_gui.xml default_ini.xml default_map.xml default_plugindb.xml default_server_ini.xml default_tree.xml feature.xml metaservers.cache Log Message: Initial commit of OpenRPG++ python --- NEW FILE: default_ini.xml --- <openrpg> <autoupdate_config catagory="general" options= "0=off, 1=on" help="Turns on/off auto-configfile updating of the ini.xml file" value="1"/> <player catagory="chat" help="This is your name as it appears in chat." options="Any text" value="No Name"/> <gametree catagory="gametree" help="This is the path on your computer pointing to the xml file\n for your tree (normaly tree.xml in the myfiles directory)." options="URL" value="myfiles/tree.xml"/> <SaveGameTreeOnExit catagory="gametree" help="Set this to 1 if you want your game tree to automaticaly be saved when you log out." options="0=no; 1=yes" value="1"/> <bgcolor catagory="colors" help="This is the background color of the chat window." options="color in hex RRGGBB" value="#ffffff"/> <textcolor catagory="colors" help="This is the default color used when text is printed into the chat window." options="color in hex RRGGBB" value="#000000"/> <mytextcolor catagory="colors" help="This is the color of your text in the chat window." options="color in hex RRGGBB" value="#000080"/> <syscolor catagory="colors" help="This is the color of system messages printed in the chat window." options="color in hex RRGGBB" value="#ff0000"/> <infocolor catagory="colors" help="This is the color of informational messages printed in the chat window." options="color in hex RRGGBB" value="#ff8000"/> <emotecolor catagory="colors" help="This is the color of your emotes in the chat window." options="color in hex RRGGBB" value="#008000"/> <whispercolor catagory="colors" help="This is the color of whisper messages in the chat window." options="color in hex RRGGBB" value="#ff8000"/> <striphtml catagory="chat" help="Set this to 1 to have HTML tags stripped from the chat window." options="0=no; 1=yes" value="0"/> <tabbedwhispers catagory="tabs" help="Set this to 1 to receive whispered messages in separate chat tabs ." options="0=no; 1=yes" value="0"/> <GMWhisperTab catagory="tabs" help="Creates a tab for all GM whispers, tabbedwhispers being on is required for this too work" options="0=no; 1=yes" value="1"/> <GroupWhisperTab catagory="tabs" help="Creates a tab for all Group whispers, tabbedwhispers being on is required for this too work" options="0=no; 1=yes" value="1"/> <defaultfont catagory="chat" help="Set this to a preferred font to use at startup." options="a font name" value="Arial"/> <defaultfontsize catagory="chat" help="Set this to a preferred fontsize to use at startup." options="a font size" value="10"/> <buffersize catagory="chat" help="This is the amount of backscroll allowed. If this number is too large your computer will lag after a while.\nIt is not the same as the history buffer which is infinite unless you set the purge options." options="Any number" value="100"/> <MultipleWindows help="Setting this to 1 will break apart the Map, Chat,\nGametree, and player list into their own windows." options="0=off; 1=on" value="0"/> <UnixSoundPlayer help="This is the path to the executable used by unix clients to play sounds." options="path to executable" value=""/> <SendSound help="Path to sound file played when you send a message." options="Path to file" value=""/> <RecvSound help="Path to sound file played when you receive a message." options="Path to file" value=""/> <WhisperSound help="Path to sound file played when you receive a whisper." options="Path to file" value=""/> <AddSound help="Path to sound file played when a new user joins the room." options="Path to file" value=""/> <DelSound help="Path to sound file played when a user exits the room." options="Path to file" value=""/> <MetaServerBaseURL help="This is the URL that contains the server list." options="URL" value="http://openrpg.servegame.com/openrpg_servers.php"/> <GameLogPrefix help="This text is the files name minus the extention of your log file. You can use\n%d, %m, %y for the log to insert the day, month or year respectively." options="Any text" value="logs/Log %m-%d-%y"/> <TimeStampGameLog options="0=no; 1=yes" value="1" help="Set this to 1 to have time stamps added to the log." /> <ShowIDInChat catagory="chat" options="0=no; 1=yes" value="1" help="Set this to 1 to have the Player Id show up next to the player name in chat." /> <AlwaysShowMapScale catagory="map" options="0=no; 1=yes" value="0" help="Setting this to 1 will keep the map scale displayed in the upper left corner of the map." /> <SuppressChatAutoComplete catagory="chat" options="0=no; 1=yes" value="0" help="Setting this to 1 will turn off auto complete in chat." /> <TypingStatusAlias catagory="chat" options="Any text" value="Typing" help="This is the text displayed in the Player list under status while you are typing." /> <IdleStatusAlias catagory="chat" options="Any text" value="Idle" help="This is the text displayed in the Player list under status while you are not typing." /> <treedclick catagory="gametree" options="use, design, print, chat" value="use" help="This sets the action performed on a node when you double click it in the game tree." /> <dieroller catagory="chat" options="std, wod, d20, hero" value="std" help="This sets the dieroller to use." /> <NameSameEmoteColor catagory="chat" options="yes, no" value="No" help="Setting this will display your name in the same color as your emote." /> <ImageCacheSize catagory="map" options="Any number" value="32" help="This sets the number of images to cache. A higher number will load a map\nfaster if the map contains images pointing to the same URL. It will also take\nup more memory." /> <F1 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F1 macro key"/> <F2 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F2 macro key"/> <F3 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F3 macro key"/> <F4 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F4 macro key"/> <F5 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F5 macro key"/> <F6 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F6 macro key"/> <F7 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F7 macro key"/> <F8 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F8 macro key"/> <F9 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F9 macro key"/> <F10 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F10 macro key"/> <F11 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F11 macro key"/> <F12 catagory="macros" help="What you enter here will be sent to chat when this function key is pressed." options="Any text" value="/me found the F12 macro key"/> <EnableSplittersAutoExpand options="0=no; 1=yes" value="0" help="Setting this will invert the size of the splitter with its neighbors." /> <PackagesBaseURL options="URL" value="http://openrpg.servegame.com/orpg_packages.xml" help="This is a URL pointing where to get updates from." /> <Disableupdate options="0=no; 1=yes" value="0" help="Setting this to 1 will render the /update command unavailable and prevent automatic update checking on startup." /> <LoadGameTreeFeatures catagory="gametree" options="0=no; 1=yes" value="1" help="Setting this to 1 will load the gametree features next time you run OpenRPG." /> <Heartbeat options="0=off, 1=on" value="0" help="This sends a message to the server to keep alive your connection when idle.\nThis is usefull if your ISP automaticaly disconnects you when you are idle or if\nan OpenRPG server's ISP drops the connection when you are idle." /> <ColorizeRoles catagory="chat" options="on,off" value="on" help="Setting this to 1 colorizes roles in the player list. Setting this to 0 disables the colors in the player list." /> <AutoPurgeAfterSave catagory="chat" help="When saving your log, this option will either automaticaly purge the buffer\n(see PurgAtBuffersizeTimesLines), ask you if you want to purge the buffer,\nor not purge at all." options="ask,yes,no" value="ask"/> <PurgeAtBuffersizeTimesLines catagory="chat" help="This option tells the program when to purge old history.\nWhen the buffer exceeds this number times the buffersize\nall history is removed and you are just left with a history \nas large as the number you set your buffersize to." options="Any number" value="2"/> <dcmsg catagory="chat" help="This is the message that gets sent when you disconnect from a server. It can be regular text or an emote action (started with /me)." options="max 80 chars of text" value="Disconnecting from server..."/> <RoomColor_Lobby catagory="roomlist" help="Sets the color used to display the 'Lobby' in the Room List in the Gameserver window" options="color in hex RRGGBB" value="#000080"/> <RoomColor_Empty catagory="roomlist" help="Sets the color used to display empty rooms (persistant only) in the Room List in the Gameserver window" options="color in hex RRGGBB" value="#bebebe"/> <RoomColor_Locked catagory="roomlist" help="Sets the color used to display password protected rooms in the Room List in the Gameserver window" options="color in hex RRGGBB" value="#b70000"/> <RoomColor_Active catagory="roomlist" help="Sets the color used to display non-passworded non-empty rooms in the Room List in the Gameserver window" options="color in hex RRGGBB" value="#000000"/> <Toolbar_On catagory="toolbar" help="Turns the toolbar on or off" options="1=yes, 0=no" value="1"/> <AliasTool_On catagory="toolbar" help="Show the Alias Tool in the toolbar?" options="1=yes, 0=no" value="1"/> <FormattingButtons_On catagory="toolbar" help="Show the Formatting Buttons (Bold, italic, underline, color) in the toolbar?" options="1=yes, 0=no" value="1"/> <DiceButtons_On catagory="toolbar" help="Show the dice buttons in the toolbar?" options="1=yes, 0=no" value="1"/> <ToGMsButton_On catagory="toolbar" help="Show the 'To GM(s)' button in the toolbar?" options="1=yes, 0=no" value="0"/> <dievars catagory="general" help="Replace the ? in die rolls with the value when it is sent to chat" options="1=yes, 0=no" value="1"/> <gwtext catagory="chat" help="This is attached prior to your group whispers(ie /gw Hello group would send '(GW): Hello group')" options="Any Text" value="(GW): "/> <Chat_Time_Indexing catagory="chat" help="Allows messages to be prepended with their arrival time using either of two preset formats or a formated timestring (see time.strftime() in python docs)" options="0=none, 1=short, 2=long, [timestring]" value="0"/> <Show_Images_In_Chat catagory="chat" help="Allows Images to be displaied in the chat window. Default is off because large images can crash you client" options="0=Off(Do not display img), 1=On(Display img)" value="0"/> </openrpg> --- NEW FILE: default_gui.xml --- <!-- Window Container Options <tab> <splitter pos="?" type="v or h" > <dialog width="?" height="?" posx="?" posy="?" stayontop="1 or 0" > OpenRPG Window Tags <map enable="1" /> <chat enable="1" /> <tree enable="1" /> <player enable="1" /> --> <!-- STANDARD LAYOUT --> <orpg_gui width="1" height="1" posx="10" posy="10" > <splitter pos="300" type="v" > <splitter pos="400" type="h" > <tree enable="1" name="Tree" /> <player enable="1" name="Player"/> </splitter> <splitter pos="300" type="h" > <map enable="1" name="Map" /> <chat enable="1" name="Chat" /> </splitter> </splitter> </orpg_gui> <!-- Tree and Player list in Tab. Chat and Map in a splitter <orpg_gui width="1" height="1" posx="1" posy="1" > <splitter pos="300" type="v" > <tab pos="400" type="h" > <tree enable="1" name="Tree" /> <player enable="1" name="Player"/> </tab> <splitter pos="300" type="h" > <map enable="1" name="Map" /> <chat enable="1" name="Chat" /> </splitter> </splitter> </orpg_gui> --> <!-- Tree Map, Chat in splitters. Player list in dialog. <orpg_gui width="1" height="1" posx="1" posy="1" > <splitter pos="300" type="v" > <tree enable="1" name="Tree" /> <splitter pos="300" type="h" > <map enable="1" name="Map" /> <chat enable="1" name="Chat" /> </splitter> </splitter> <dialog width="200" height="200" posx="10" posy="10" stayontop="1" > <player enable="1" name="Player"/> </dialog> </orpg_gui> --> <!-- Map main window. Tree, players, and chat in dialogs. <orpg_gui width="1" height="1" posx="1" posy="1" > <map enable="1" name="Map" /> <dialog width="250" height="400" posx="10" posy="10" stayontop="1" > <tree enable="1" name="Tree" /> </dialog> <dialog width="250" height="200" posx="10" posy="350" stayontop="1" > <player enable="1" name="Player"/> </dialog> <dialog width="600" height="250" posx="200" posy="10" stayontop="1" > <chat enable="1" name="Chat" /> </dialog> </orpg_gui> --> --- NEW FILE: default_server_ini.xml --- <server> <service port='6774' address='hostname/address' /> <map file='myfiles/Lobby_map.xml' /> <message file='myfiles/LobbyMessage.html' /> <validate_protocol value='true' /> <autokick silent='no' delay='480' /> <version min="1.6.3" /> <cheat text='**Fudged Roll**' help='The text will be included in any faked roll by anyone but a GM' /> <room_defaults> <passwords allow='yes'/> <map file=''/> <message file='myfiles/LobbyMessage.html'/> </room_defaults> <room name="Example Persistant #1" password="password" boot="password"> <map file='myfiles/Lobby_map.xml' /> <message file='myfiles/LobbyMessage.html' /> </room> <room name="Example Persistant #2" password="" boot="password"> <map file='myfiles/Lobby_map.xml' /> <message file='myfiles/LobbyMessage.html' /> </room> </server> --- NEW FILE: metaservers.cache --- http://www.openrpg.com/openrpg_servers.php 1 2 --- NEW FILE: default_Lobby_map.xml --- <nodehandler class="min_map" icon="compass" module="core" name="miniature Map"> <map version='1.0' sizex='300' sizey='300' action='new'> <grid size='50' mode='0' line='0' snap='1' color='#000000'/> <bg path='http://www.openrpg.com/images/maps/Lobby_image.png' type='2'/> <miniatures serial='2'/> </map> </nodehandler> --- NEW FILE: default_LobbyMessage.html --- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <body> <table cellspacing=3 cellpadding=4 width="100%"> <tr> <td bgcolor="#101010" align="bottom"> <center><a href="http://www.openrpg.com"><img src="images/splash.gif" border="0"></a></center> </td> </tr> <tr> <td bgcolor="#73A183" align="center"> <table cellpadding="0" cellspacing="0" width="100%"> <tr> <td align="center" width="100%"> Many thanks goes to all of those who contributed! <BR> The developers in alphabetical order are: <BR> Thomas Baleno, Andrew Bennett, Lex Berezhny, Ted Berg, Bernhard Bergbauer, Chris Blocher ,Ben Collins-Sussman, Robin Cook, Greg Copeland, Chris Davis, Michael Edwards, Andrew Ettinger, Dj Gilcrease, Todd Faris, Christopher Hickman, Paul Hosking, Scott Mackay, Brian Manning, Jesse McConnell, Brian Osman, Rome Reginelli, Christopher Rouse, Dave Sanders and Mark Tarrabain. </td> </tr> <tr> <td align="center" width="100%"> This product is licensed under the <a href="http://www.gnu.org">GNU</a> <a href="http://www.gnu.org/philosophy/license-list.html">GPL License.</a> </td> </tr> </table> </td> </tr> </table> <!-- Created: Thursday November 9 23:55:12 PDT 2003 --> </body> </html> --- NEW FILE: default_tree.xml --- <gametree version="1.0"> <nodehandler class="tabber_handler" icon="tabber" module="containers" name="Behir (Example Sheet)" version="1.0"> <nodehandler class="form_handler" icon="form" module="forms" name="Details" version="1.0"> <form height="500" width="400"/> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Name" version="1.0"> <text multiline="0" send_button="0">Behir</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="HD" version="1.0"> <text multiline="0" send_button="0">9d10+45 (94 hp)</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Speed" version="1.0"> <text multiline="0" send_button="0">40 ft., climb 15 ft</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="AC" version="1.0"> <text multiline="0" send_button="0">16 (-2 size, +1 Dex, +7 natural)</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Face Reach" version="1.0"> <text multiline="0" send_button="0">10 ft. by 30 ft./10 ft.</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Feats" version="1.0"> <text multiline="0" send_button="0">Cleave, Power Attack</text> </nodehandler> <nodehandler class="listbox_handler" icon="gear" module="forms" name="Skills" version="1.0"> <list send_button="1" type="1"> <option selected="0" value="0">Climb [1d20+18]</option> <option selected="0" value="0">Hide [1d20+5]</option> <option selected="1" value="0">Spot [1d20+7]</option> </list> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Abilities" version="1.0"> <grid autosize="1" border="1"> <row version="1.0"> <cell>Str</cell> <cell>28</cell> </row> <row version="1.0"> <cell>Dex</cell> <cell>13</cell> </row> <row version="1.0"> <cell>Con</cell> <cell>21</cell> </row> <row version="1.0"> <cell>Int</cell> <cell>7</cell> </row> <row version="1.0"> <cell>Wis</cell> <cell>14</cell> </row> <row version="1.0"> <cell>Cha</cell> <cell>12</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> <nodehandler class="form_handler" icon="form" module="forms" name="Combat Rolls" version="1.0"> <form height="300" width="400"/> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Current HP" version="1.0"> <text multiline="0" send_button="0">text</text> </nodehandler> <nodehandler class="textctrl_handler" icon="d20" module="forms" name="Initiative" version="1.0"> <text multiline="0" send_button="1">[1d20+1]</text> </nodehandler> <nodehandler class="listbox_handler" icon="gear" module="forms" name="Attacks" version="1.0"> <list send_button="1" type="3"> <option selected="0" value="0">Bite [1d20+15], Damage [2d4+8]</option> <option selected="0" value="0">Claw [1d20+10], Damage [1d4+4]</option> <option selected="0" value="0">Claw [1d20+10], Damage [1d4+4]</option> </list> </nodehandler> <nodehandler class="listbox_handler" icon="gear" module="forms" name="Saving Throws" version="1.0"> <list send_button="1" type="1"> <option selected="1" value="0">Will Power [1d20+5]</option> <option selected="0" value="0">Relex [1d20+7]</option> <option selected="0" value="0">Fortitude [1d20+11]</option> </list> </nodehandler> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Combat Info" version="1.0"> <text multiline="1" send_button="1">A behir usually bites and grabs its prey first, then either swallows or constricts the opponent. If beset by a large number of foes, it uses its breath weapon. Breath Weapon (Su): Line of lightning 5 feet wide, 5 feet high, and 20 feet long, once a minute; damage 7d6, Reflex half DC 19. Improved Grab (Ex): To use this ability, the behir must hit with its bite attack. If it gets a hold, it can attempt to swallow or constrict the opponent. Swallow Whole (Ex): A behir can try to swallow a grabbed Medium-size or smaller opponent by making a successful grapple check. A behir that swallows an opponent can use its Cleave feat to bite and grab another opponent. The swallowed creature takes 2d8+8 points of crushing damage and 8 points of acid damage per round from the behirs gizzard. A swallowed creature can also cut its way out by using claws or a Small or Tiny slashing weapon to deal 25 points of damage to the gizzard (AC 20). Once the creature exits, muscular action closes the hole; another swallowed opponent must again cut its own way out. The behirs gizzard can hold two Medium-size, four Small, eight Tiny, sixteen Diminutive, or thirty-two Fine or smaller opponents. Constrict (Ex): A behir deals 2d8+8 damage with a successful grapple check against Gargantuan or smaller creatures. It can use its claws against the grappled foe as well. </text> </nodehandler> </nodehandler> </gametree> --- NEW FILE: default_map.xml --- <nodehandler class="min_map" icon="compass" module="core" name="miniature Map"> <map version='1.0' sizex='1000' sizey='1000' action='new'> <miniatures serial='0'/> <bg color='#008040' type='3'/> <grid snap='1' color='#000000' line='2' mode='0' size='60'/> <whiteboard serial='0'/> </map> </nodehandler> --- NEW FILE: default_plugindb.xml --- <plugindb></plugindb> --- NEW FILE: about.html --- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <body bgcolor="#FFFFFF"> <table cellspacing=3 cellpadding=4 width="100%"> <tr> <td bgcolor="#101010" align="bottom"> <center><a href="http://www.openrpg.com"><img src="images/splash.gif"></a></center> </td> </tr> <tr> <td bgcolor="#101010" align="center"> <font size="+4" color="#FFFFFF"><b><br>Version VeRsIoNrEpLaCeMeNtStRiNg<br></b></font> </td> </tr> <tr> <td bgcolor="#73A183" align="center"> <b><font size="+1">Special thanks to Chris Davis for this endeavor</a></font></b> <p> <table cellpadding="0" cellspacing="0" width="100%"> <tr> <td align="center" width="100%"> Of course, many thanks goes to all of those who contributed. <BR> The developers in alphabetical order are: <BR> Thomas Baleno, Andrew Bennett, Lex Berezhny, Ted Berg, Bernhard Bergbauer, Chris Blocher, Ben Collins-Sussman, Robin Cook, Greg Copeland, Chris Davis, Michael Edwards, Andrew Ettinger, Todd Faris, Dj Gilcrease, Christopher Hickman, Paul Hosking, Brian Manning, Scott Mackay, Jesse McConnell, Brian Osman, Rome Reginelli, Christopher Rouse, Dave Sanders and Mark Tarrabain. </td> </tr> <tr> <td align="center" width="100%"> <a href="http://www.python.org"><img src="images/python55.gif"></a> <a href="http://www.wxwindows.org"><img src="images/wxWinButton.png"></a> <a href="http://www.wxpython.org"><img src="images/wxPyButton.png"></a> <a href="http://www.sourceforge.net"><img src="images/sflogo.png"></a> </td> </tr> <tr> <td align="center" width="100%"> This product is licensed under the <a href="http://www.gnu.org">GNU</a> <a href="http://www.gnu.org/philosophy/license-list.html">GPL License.</a> </td> </tr> </table> </td> </tr> </table> <!-- Created: Fri Jun 1 17:32:22 CDT 2001 --> </body> </html> --- NEW FILE: feature.xml --- <nodehandler class="tabber_handler" icon="help" module="containers" name="OpenRPG 1.6.3" version="1.0"> <nodehandler class="link_handler" icon="html" module="forms" name="Release Notes" version="1.0"> <link href="http://openrpg.wrathof.com/faq/Current_Version"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="OpenRPG User Guide" version="1.0"> <link href="http://openrpg.wrathof.com/faq/OpenRPG_User_Guide"/> </nodehandler> <nodehandler class="file_loader" icon="help" module="core" name="Load Die Roller Notes" version="1.0"> <file name="die_roller_notes.xml"/> </nodehandler> <nodehandler class="group_handler" icon="gear" module="containers" name="Templates" status="useful" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="group_handler" icon="flask" module="containers" name="Nodes" status="useful" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="file_loader" icon="note" module="core" name="Create New Text Box" version="1.0"> <file name="textctrl.xml"/> </nodehandler> <nodehandler class="file_loader" icon="gear" module="core" name="Create New List Box" version="1.0"> <file name="listbox.xml"/> </nodehandler> <nodehandler class="file_loader" icon="grid" module="core" name="Create New Grid" version="1.0"> <file name="grid.xml"/> </nodehandler> <nodehandler class="file_loader" icon="html" module="core" name="Create New Web Link" version="1.0"> <file name="link.xml"/> </nodehandler> <nodehandler class="file_loader" icon="image" module="core" name="Create New Web Image" version="1.0"> <file name="image.xml"/> </nodehandler> </nodehandler> <nodehandler class="group_handler" module="containers" name="Containers" status="useful" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="file_loader" module="core" name="Create New Folder" version="1.0"> <file name="group.xml"/> </nodehandler> <nodehandler class="file_loader" icon="tabber" module="core" name="Create New Tabber" version="1.0"> <file name="tabber.xml"/> </nodehandler> <nodehandler class="file_loader" icon="divider" module="core" name="Create New Splitter" version="1.0"> <file name="split.xml"/> </nodehandler> <nodehandler class="file_loader" icon="form" module="core" name="Create New Form" version="1.0"> <file name="form.xml"/> </nodehandler> </nodehandler> <nodehandler class="group_handler" icon="gear" module="containers" name="Tools" status="useful" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="file_loader" icon="gear" module="core" name="Create New Chat Macro" version="1.0"> <file name="macro.xml"/> </nodehandler> <nodehandler class="file_loader" icon="player" module="core" name="Create New Alias Library Tool" version="1.0"> <file name="alias.xml"/> </nodehandler> <nodehandler class="file_loader" icon="gear" module="core" name="Create New Miniature Library Tool" version="1.0"> <file name="minlib.xml"/> </nodehandler> <nodehandler class="file_loader" icon="gear" module="core" name="Create remote node loader" version="1.0"> <file name="urloader.xml"/> </nodehandler> <nodehandler class="file_loader" icon="d20" module="core" name="Create New d20 Character Tool" version="1.0"> <file name="d20character.xml"/> </nodehandler> <nodehandler class="file_loader" icon="d20" module="core" name="Create New St*r W*rs Character Tool" version="1.0"> <file name="StarWars_d20character.xml"/> </nodehandler> <nodehandler class="file_loader" icon="d20" module="core" name="3rd Edition Character Tool" version="1.0"> <file name="dnd3e.xml"/> </nodehandler> </nodehandler> </nodehandler> <nodehandler class="group_handler" icon="browser" module="containers" name="OpenRPG Resources" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="link_handler" icon="html" module="forms" name="OpenRPG Home Page" version="1.0"> <link href="http://www.openrpg.com"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="OpenRPG Project Page" version="1.0"> <link href="http://sourceforge.net/projects/openrpg"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="OpenRPG Forums" version="1.0"> <link href="http://forums.rpghost.com/forumdisplay.php?s=&forumid=118"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="Submit A Bug Report" version="1.0"> <link href="http://sourceforge.net/tracker/?group_id=2237&atid=102237"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="OpenRPG Plugin HQ" version="1.0"> <link href="http://mduo13.no-ip.org/"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="OpenRPG Web Ring" version="1.0"> <link href="http://www.ringsurf.com/netring?ring=OpenRPG;action=home"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="jOpenRPG homepage" version="1.0"> <link href="http://jopenrpg.sourceforge.net"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="AutoRealm" version="1.0"> <link href="http://www.gryc.ws/autorealm.htm"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="PCGen" version="1.0"> <link href="http://sourceforge.net/projects/pcgen"/> </nodehandler> <nodehandler class="link_handler" icon="html" module="forms" name="Izandawo" version="1.0"> <link href="http://www.realmcreator.com/downloads.php"/> </nodehandler> </nodehandler> <nodehandler class="group_handler" module="containers" name="Examples (Adventures)" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="file_loader" icon="d20" module="core" name="Bastion Press d20 Adventure" version="1.0"> <file name="Bastion_adventure.xml"/> </nodehandler> <nodehandler class="file_loader" icon="d20" module="core" name="Darwin's World d20 Adventure" version="1.0"> <file name="Darwin_adventure.xml"/> </nodehandler> </nodehandler> </nodehandler> |
Update of /cvsroot/winopenrpg/openrpg1/orpg/mapper In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/mapper Added Files: __init__.py background.py background_handler.py background_msg.py base.py base_handler.py base_msg.py fog.py fog_handler.py fog_msg.py grid.py grid_handler.py grid_msg.py images.py isometric.py map.py map_handler.py map_msg.py map_prop_dialog.py map_utils.py map_version.py min_dialogs.py miniatures.py miniatures_handler.py miniatures_msg.py region.py whiteboard.py whiteboard_handler.py whiteboard_msg.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: whiteboard_msg.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: mapper/whiteboard_msg.py # Author: Chris Davis # Maintainer: # Version: # $Id: whiteboard_msg.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: This file contains some of the basic definitions for the chat # utilities in the orpg project. # __version__ = "$Id: whiteboard_msg.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from base_msg import * class item_msg(map_element_msg_base): def __init__(self,reentrant_lock_object = None, tagname = "line"): self.tagname = tagname # set this to be for items. Tagname gets used in some base class functions. map_element_msg_base.__init__(self,reentrant_lock_object) # call base class # convenience method to use if only this item is modified # outputs a <map/> element containing only the changes to this item def standalone_update_text(self,update_id_string): buffer = "<map id='" + update_id_string + "'>" buffer += "<whiteboard>" buffer += self.get_changed_xml() buffer += "</whiteboad></map>" return buffer # convenience method to use if only this item is modified # outputs a <map/> element that deletes this item def standalone_delete_text(self,update_id_string): buffer = None if self._props.has_key("id"): buffer = "<map id='" + update_id_string + "'>" buffer += "<whiteboard>" buffer += "<"+self.tagname+" action='del' id='" + self._props("id") + "'/>" buffer += "</whiteboard></map>" return buffer # convenience method to use if only this item is modified # outputs a <map/> element to add this item def standalone_add_text(self,update_id_string): buffer = "<map id='" + update_id_string + "'>" buffer += "<whiteboard>" buffer += self.get_all_xml() buffer += "</whiteboard></map>" return buffer def get_all_xml(self,action="new",output_action=1): return map_element_msg_base.get_all_xml(self,action,output_action) def get_changed_xml(self,action="update",output_action=1): return map_element_msg_base.get_changed_xml(self,action,output_action) class whiteboard_msg(map_element_msg_base): def __init__(self,reentrant_lock_object = None): self.tagname = "whiteboard" map_element_msg_base.__init__(self,reentrant_lock_object) def init_from_dom(self,xml_dom): self.p_lock.acquire() if xml_dom.tagName == self.tagname: if xml_dom.getAttributeKeys(): for k in xml_dom.getAttributeKeys(): self.init_prop(k,xml_dom.getAttribute(k)) for c in xml_dom._get_childNodes(): item = item_msg(self.p_lock,c._get_nodeName()) try: item.init_from_dom(c) except Exception, e: print e continue id = item.get_prop("id") action = item.get_prop("action") if action == "new": self.children[id] = item elif action == "del": if self.children.has_key(id): self.children[id] = None del self.children[id] elif action == "update": if self.children.has_key(id): self.children[id].init_props(item.get_all_props()) else: self.p_lock.release() raise Exception, "Error attempting to initialize a " + self.tagname + " from a non-<" + self.tagname + "/> element in whiteboard" self.p_lock.release() def set_from_dom(self,xml_dom): self.p_lock.acquire() if xml_dom.tagName == self.tagname: if xml_dom.getAttributeKeys(): for k in xml_dom.getAttributeKeys(): self.set_prop(k,xml_dom.getAttribute(k)) for c in xml_dom._get_childNodes(): item = item_msg(self.p_lock, c._get_nodeName()) try: print "setting from dom" item.set_from_dom(c) except Exception, e: print e continue id = item.get_prop("id") action = item.get_prop("action") if action == "new": self.children[id] = item elif action == "del": if self.children.has_key(id): self.children[id] = None del self.children[id] elif action == "update": if self.children.has_key(id): self.children[id].set_props(item.get_all_props()) else: self.p_lock.release() raise Exception, "Error attempting to set a " + self.tagname + " from a non-<" + self.tagname + "/> element" self.p_lock.release() --- NEW FILE: grid_handler.py --- # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: orpg/mapper/grid_handler.py # Author: OpenRPG Team # Maintainer: # Version: # $Id: grid_handler.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: grid layer handler # __version__ = "$Id: grid_handler.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from grid import * from base_handler import * CTRL_GRID_LINE = wxNewId() CTRL_GRID_MODE = wxNewId() CTRL_GRID_COLOR = wxNewId() CTRL_GRID_SNAP = wxNewId() CTRL_GRID_SIZE = wxNewId() CTRL_GRID_RATIO = wxNewId() CTRL_GRID_APPLY = wxNewId() class grid_handler(base_layer_handler): def __init__(self, parent, id, canvas): base_layer_handler.__init__(self, parent, id, canvas) def build_ctrls(self): base_layer_handler.build_ctrls(self) self.line_type = wxChoice(self, CTRL_GRID_LINE, choices = ["No Lines", "Dotted Lines", "Solid Lines" ]) #EVT_CHOICE(self, CTRL_GRID_LINE, self.on_line_type) self.grid_mode = wxChoice(self, CTRL_GRID_MODE, choices = ["Rectangular", "Hexagonal","Isometric"]) self.grid_snap = wxCheckBox(self, CTRL_GRID_SNAP, " Snap") self.grid_size = orpgTextCtrl(self, CTRL_GRID_SIZE, size=(32,-1) ) self.grid_ratio = orpgTextCtrl(self, CTRL_GRID_RATIO, size=(32,-1) ) self.color_button = wxButton(self, CTRL_GRID_COLOR, "Color", style=wxBU_EXACTFIT) self.apply_button = wxButton(self, CTRL_GRID_APPLY, "Apply", style=wxBU_EXACTFIT) self.color_button.SetBackgroundColour(wxBLACK) self.color_button.SetForegroundColour(wxWHITE) #EVT_CHOICE(self, CTRL_GRID_MODE, self.on_mode) self.sizer.Prepend(wxSize(20,25),1) self.sizer.Prepend(self.apply_button, 0, wxEXPAND) self.sizer.Prepend(wxSize(10,25)) self.sizer.Prepend(self.color_button, 0, wxEXPAND) self.sizer.Prepend(wxSize(10,25)) self.sizer.Prepend(self.grid_snap, 0, wxEXPAND) self.sizer.Prepend(wxSize(3,25)) self.sizer.Prepend(self.grid_mode, 0, wxEXPAND) self.sizer.Prepend(wxSize(3,25)) self.sizer.Prepend(self.line_type, 0, wxEXPAND) self.sizer.Prepend(wxSize(3,25)) self.sizer.Prepend(self.grid_ratio, 0, wxEXPAND) self.sizer.Prepend(wxStaticText(self, -1, "Ratio: "),0,wxALIGN_CENTER) self.sizer.Prepend(wxSize(3,25)) self.sizer.Prepend(self.grid_size, 0, wxEXPAND) self.sizer.Prepend(wxStaticText(self, -1, "Size: "),0,wxALIGN_CENTER) EVT_BUTTON(self, CTRL_GRID_COLOR, self.on_bg_color) EVT_BUTTON(self, CTRL_GRID_APPLY, self.on_apply) self.update_info() def update_info(self): layer = self.canvas.layers['grid'] self.grid_size.SetValue(str(layer.get_unit_size())) self.grid_ratio.SetValue(str(layer.get_iso_ratio())) self.grid_mode.SetSelection(layer.get_mode()) self.line_type.SetSelection(layer.get_line_type()) self.color_button.SetBackgroundColour(layer.get_color()) self.grid_snap.SetValue(layer.is_snap()) def build_menu(self,label = "Grid"): base_layer_handler.build_menu(self,label) def on_bg_color(self,evt): data = wxColourData() data.SetChooseFull(true) dlg = wxColourDialog(self.canvas, data) if dlg.ShowModal() == wxID_OK: data = dlg.GetColourData() color = data.GetColour() self.color_button.SetBackgroundColour(color) dlg.Destroy() def on_apply(self, evt): session=self.canvas.frame.session if (session.my_role() <> session.ROLE_GM): self.top_frame.myopenrpg.get_component("chat").InfoPost("You must be a GM to use this feature") return self.canvas.layers['grid'].set_grid(int(self.grid_size.GetValue()),self.grid_snap.GetValue(), self.color_button.GetBackgroundColour(),self.grid_mode.GetSelection(),self.line_type.GetSelection(),float(self.grid_ratio.GetValue())) self.update_info() self.canvas.send_map_data() self.canvas.Refresh() --- NEW FILE: map_msg.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: mapper/map_msg.py # Author: OpenRPG # Maintainer: # Version: # $Id: map_msg.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: # __version__ = "$Id: map_msg.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" #from base import * from base_msg import * from background_msg import * from grid_msg import * from miniatures_msg import * from whiteboard_msg import * from fog_msg import * """ <map name=? id=? > <bg type=? file=? color=? /> <grid size=? snap=? /> <miniatures serial=? > <miniature path=? posx=? posy=? heading=? face=? owner=? label=? locked=? width=? height=? /> </miniatures> </map> """ class map_msg(map_element_msg_base): def __init__(self,reentrant_lock_object = None): self.tagname = "map" map_element_msg_base.__init__(self,reentrant_lock_object) def init_from_dom(self,xml_dom): self.p_lock.acquire() if xml_dom.tagName == self.tagname: # If this is a map message, look for the "action=new" # Notice we only do this when the root is a map tag if self.tagname == "map": if xml_dom.getAttributeKeys(): for k in xml_dom.getAttributeKeys(): if k == "action" and xml_dom.getAttribute( k ) == "new": #print "******* Resetting all map attributes for a new map load..." self.clear() #print "******* Old map has been cleared...loading new map..." # Process all of the properties in each tag if xml_dom.getAttributeKeys(): for k in xml_dom.getAttributeKeys(): self.init_prop(k,xml_dom.getAttribute(k)) for c in xml_dom._get_childNodes(): name = c._get_nodeName() if not self.children.has_key(name): if name == "miniatures": self.children[name] = minis_msg(self.p_lock) elif name == "grid": self.children[name] = grid_msg(self.p_lock) elif name == "bg": self.children[name] = bg_msg(self.p_lock) elif name == "whiteboard": self.children[name] = whiteboard_msg(self.p_lock) elif name == "fog": self.children[name] = fog_msg(self.p_lock) else: print "Unrecognized tag " + name + " found in map_msg.init_from_dom - skipping" continue try: self.children[name].init_from_dom(c) except Exception, e: print "map_msg.init_from_dom() exception: ", e continue else: self.p_lock.release() raise Exception, "Error attempting to initialize a " + self.tagname + " from a non-<" + self.tagname + "/> element" self.p_lock.release() def set_from_dom(self,xml_dom): self.p_lock.acquire() if xml_dom.tagName == self.tagname: # If this is a map message, look for the "action=new" # Notice we only do this when the root is a map tag if self.tagname == "map": if xml_dom.getAttributeKeys(): for k in xml_dom.getAttributeKeys(): if k == "action" and xml_dom.getAttribute( k ) == "new": #print "******* Resetting all map attributes for a new map load..." self.clear() #print "******* Old map has been cleared...loading new map..." # Process all of the properties in each tag if xml_dom.getAttributeKeys(): for k in xml_dom.getAttributeKeys(): self.set_prop(k,xml_dom.getAttribute(k)) for c in xml_dom._get_childNodes(): name = c._get_nodeName() if not self.children.has_key(name): if name == "miniatures": self.children[name] = minis_msg(self.p_lock) elif name == "grid": self.children[name] = grid_msg(self.p_lock) elif name == "bg": self.children[name] = bg_msg(self.p_lock) elif name == "whiteboard": self.children[name] = whiteboard_msg(self.p_lock) print "set_from dom msg", self.children[name] elif name == "fog": self.children[name] = fog_msg(self.p_lock) else: print "Unrecognized tag " + name + " found in map_msg.init_from_dom - skipping" continue try: self.children[name].set_from_dom(c) except Exception, e: print "map_msg.set_from_dom() exception: " , e continue else: self.p_lock.release() raise Exception, "Error attempting to set a " + self.tagname + " from a non-<" + self.tagname + "/> element in map" self.p_lock.release() def get_all_xml(self,action="new",output_action=1): return map_element_msg_base.get_all_xml(self,action,output_action) def get_changed_xml(self,action="update",output_action=1): return map_element_msg_base.get_changed_xml(self,action,output_action) --- NEW FILE: miniatures.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: mapper/miniatures.py # Author: Chris Davis # Maintainer: # Version: # $Id: miniatures.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: This file contains some of the basic definitions for the chat # utilities in the orpg project. # __version__ = "$Id: miniatures.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from base import * #from images import * import images MIN_STICKY_BACK = -0XFFFFFF MIN_STICKY_FRONT = 0xFFFFFF ##---------------------------------------- ## miniature object ##---------------------------------------- FACE_NONE = 0 FACE_NORTH = 1 FACE_NORTHEAST = 2 FACE_EAST = 3 FACE_SOUTHEAST = 4 FACE_SOUTH = 5 FACE_SOUTHWEST = 6 FACE_WEST = 7 FACE_NORTHWEST = 8 SNAPTO_ALIGN_CENTER = 0 SNAPTO_ALIGN_TL = 1 def cmp_zorder(first,second): f = first.zorder s = second.zorder if f == None: f = 0 if s == None: s = 0 if f == s: value = 0 elif f < s: value = -1 else: value = 1 return value class bmp_miniature(protectable_attributes): def __init__(self, id,path, bmp, pos=cmpPoint(0,0), heading=FACE_NONE, face=FACE_NONE, label="", locked=false, hide=false, snap_to_align=SNAPTO_ALIGN_CENTER,zorder = 0, width=0, height=0): protectable_attributes.__init__(self) self._protected_attr = ["heading", "face", "label", "path", "pos", "locked", "snap_to_align", "hide", "id", "zorder", "width", "height"] self.heading = heading self.face = face self.label = label self.path = path self.bmp = bmp self.pos = pos self.selected = false self.locked = locked self.snap_to_align = snap_to_align self.hide = hide self.id = id self.zorder = zorder self.left = 0 if not width: self.width = 0 else: self.width = width if not height: self.height = 0 else: self.height = height self.right = bmp.GetWidth() self.top = 0 self.bottom = bmp.GetHeight() #self._clean_all_attr() def __del__(self): #images.delete_img(self.bmp) del self.bmp self.bmp = None def set_bmp(self,bmp): self.bmp = bmp def set_min_props(self, heading=FACE_NONE, face=FACE_NONE, label="", locked=false, hide=false, width=0, height=0): self.heading = heading self.face = face self.label = label self.locked = locked self.hide = hide self.width = width self.height = height def hit_test(self, pt): rect = self.get_rect() result = None # fixes problem between wxPython version 2.4.0.7 and higher # and wxPython version 2.4.0.2 and lower --Snowdog try: #2.4.0.7 and up check result = rect.InsideXY(pt.x, pt.y) except: #2.4.0.2 and lower check result = rect.Inside(pt.x, pt.y) return result def get_rect(self): return wxRect(self.pos.x, self.pos.y, self.bmp.GetWidth(), self.bmp.GetHeight()) def draw(self, dc, op = wxCOPY): if self.bmp != None and self.bmp.Ok(): # check if hidden if self.hide: return true dc.DrawBitmap(self.bmp,self.pos.x,self.pos.y,true) # Blit the miniature bmp # memDC = wxMemoryDC() # memDC.SelectObject(self.bmp) # dc.Blit(self.pos.x, self.pos.y, # self.bmp.GetWidth(), self.bmp.GetHeight(), # memDC, 0, 0, op, true) # memDC.SelectObject(wxNullBitmap) # del memDC # set the width and height of the image if self.width and self.height: tmp_image = wxImageFromBitmap(self.bmp) tmp_image.Rescale(int(self.width), int(self.height)) self.bmp = wxBitmapFromImage(tmp_image) self.left = 0 self.right = self.bmp.GetWidth() self.top = 0 self.bottom = self.bmp.GetHeight() # Draw the facing marker if needed if self.face: x_mid = self.pos.x + (self.bmp.GetWidth()/2) x_right = self.pos.x + self.bmp.GetWidth() y_mid = self.pos.y + (self.bmp.GetHeight()/2) y_bottom = self.pos.y + self.bmp.GetHeight() dc.SetPen( wxWHITE_PEN ) dc.SetBrush( wxRED_BRUSH ) triangle = [] # Figure out which direction to draw the marker!! if self.face == FACE_WEST: triangle.append(cmpPoint(self.pos.x,self.pos.y)) triangle.append(cmpPoint(self.pos.x - 5, y_mid)) triangle.append(cmpPoint(self.pos.x, y_bottom)) elif self.face == FACE_EAST: triangle.append(cmpPoint(x_right, self.pos.y)) triangle.append(cmpPoint(x_right + 5, y_mid)) triangle.append(cmpPoint(x_right, y_bottom)) elif self.face == FACE_SOUTH: triangle.append(cmpPoint(self.pos.x, y_bottom)) triangle.append(cmpPoint(x_mid, y_bottom + 5)) triangle.append(cmpPoint(x_right, y_bottom)) elif self.face == FACE_NORTH: triangle.append(cmpPoint(self.pos.x, self.pos.y)) triangle.append(cmpPoint(x_mid, self.pos.y - 5)) triangle.append(cmpPoint(x_right, self.pos.y)) elif self.face == FACE_NORTHEAST: triangle.append(cmpPoint(x_mid, self.pos.y)) triangle.append(cmpPoint(x_right + 5, self.pos.y - 5)) triangle.append(cmpPoint(x_right, y_mid)) triangle.append(cmpPoint(x_right, self.pos.y)) elif self.face == FACE_SOUTHEAST: triangle.append(cmpPoint(x_right, y_mid)) triangle.append(cmpPoint(x_right + 5, y_bottom + 5)) triangle.append(cmpPoint(x_mid, y_bottom)) triangle.append(cmpPoint(x_right, y_bottom)) elif self.face == FACE_SOUTHWEST: triangle.append(cmpPoint(x_mid, y_bottom)) triangle.append(cmpPoint(self.pos.x - 5, y_bottom + 5)) triangle.append(cmpPoint(self.pos.x, y_mid)) triangle.append(cmpPoint(self.pos.x, y_bottom)) elif self.face == FACE_NORTHWEST: triangle.append(cmpPoint(self.pos.x, y_mid)) triangle.append(cmpPoint(self.pos.x - 5, self.pos.y - 5)) triangle.append(cmpPoint(x_mid, self.pos.y)) triangle.append(cmpPoint(self.pos.x, self.pos.y)) dc.DrawPolygon(triangle) dc.SetBrush(wxNullBrush) dc.SetPen(wxNullPen) # Draw the heading if needed if self.heading: x_adjust = 0 y_adjust = 4 x_half = self.bmp.GetWidth()/2 y_half = self.bmp.GetHeight()/2 x_quarter = self.bmp.GetWidth()/4 y_quarter = self.bmp.GetHeight()/4 x_3quarter = x_quarter*3 y_3quarter = y_quarter*3 x_full = self.bmp.GetWidth() y_full = self.bmp.GetHeight() x_center = self.pos.x + x_half y_center = self.pos.y + y_half # Remember, the pen/brush must be a different color than the # facing marker!!!! We'll use black/cyan for starters. # Also notice that we will draw the heading on top of the # larger facing marker. dc.SetPen( wxBLACK_PEN ) dc.SetBrush( wxCYAN_BRUSH ) ## wxBrush = dc.GetBrush() ## wxBrush.SetStyle( wxCROSSDIAG_HATCH ) ## dc.SetBrush( wxBrush ) triangle = [] # Figure out which direction to draw the marker!! if self.heading == FACE_NORTH: triangle.append(cmpPoint(x_center - x_quarter, y_center - y_half )) triangle.append(cmpPoint(x_center, y_center - y_3quarter )) triangle.append(cmpPoint(x_center + x_quarter, y_center - y_half )) elif self.heading == FACE_SOUTH: triangle.append(cmpPoint(x_center - x_quarter, y_center + y_half )) triangle.append(cmpPoint(x_center, y_center + y_3quarter )) triangle.append(cmpPoint(x_center + x_quarter, y_center + y_half )) elif self.heading == FACE_NORTHEAST: triangle.append(cmpPoint(x_center + x_quarter, y_center - y_half )) triangle.append(cmpPoint(x_center + x_3quarter, y_center - y_3quarter )) triangle.append(cmpPoint(x_center + x_half, y_center - y_quarter )) elif self.heading == FACE_EAST: triangle.append(cmpPoint(x_center + x_half, y_center - y_quarter )) triangle.append(cmpPoint(x_center + x_3quarter, y_center )) triangle.append(cmpPoint(x_center + x_half, y_center + y_quarter )) elif self.heading == FACE_SOUTHEAST: triangle.append(cmpPoint(x_center + x_half, y_center + y_quarter )) triangle.append(cmpPoint(x_center + x_3quarter, y_center + y_3quarter )) triangle.append(cmpPoint(x_center + x_quarter, y_center + y_half )) elif self.heading == FACE_SOUTHWEST: triangle.append(cmpPoint(x_center - x_quarter, y_center + y_half )) triangle.append(cmpPoint(x_center - x_3quarter, y_center + y_3quarter )) triangle.append(cmpPoint(x_center - x_half, y_center + y_quarter )) elif self.heading == FACE_WEST: triangle.append(cmpPoint(x_center - x_half, y_center + y_quarter )) triangle.append(cmpPoint(x_center - x_3quarter, y_center )) triangle.append(cmpPoint(x_center - x_half, y_center - y_quarter )) elif self.heading == FACE_NORTHWEST: triangle.append(cmpPoint(x_center - x_half, y_center - y_quarter )) triangle.append(cmpPoint(x_center - x_3quarter, y_center - y_3quarter )) triangle.append(cmpPoint(x_center - x_quarter, y_center - y_half )) dc.DrawPolygon(triangle) dc.SetBrush(wxNullBrush) dc.SetPen(wxNullPen) #selected outline if self.selected: dc.SetPen(wxRED_PEN) dc.SetBrush(wxTRANSPARENT_BRUSH) dc.DrawRectangle(self.pos.x, self.pos.y, self.bmp.GetWidth(), self.bmp.GetHeight()) dc.SetBrush(wxNullBrush) dc.SetPen(wxNullPen) # draw label if len(self.label): dc.SetTextForeground(wxRED) (textWidth,textHeight) = dc.GetTextExtent(self.label) x = self.pos.x +((self.bmp.GetWidth() - textWidth) /2) - 1 y = self.pos.y + self.bmp.GetHeight() + 6 dc.SetPen(wxWHITE_PEN) dc.SetBrush(wxWHITE_BRUSH) dc.DrawRectangle(x,y,textWidth+2,textHeight+2) if (textWidth+2>self.right): self.right+=int((textWidth+2-self.right)/2)+1 self.left-=int((textWidth+2-self.right)/2)+1 self.bottom=y+textHeight+2-self.pos.y dc.SetPen(wxNullPen) dc.SetBrush(wxNullBrush) dc.DrawText(self.label,x+1,y+1) self.top-=5 self.bottom+=5 self.left-=5 self.right+=5 return true else: return false def toxml(self, action = "update",preserve_changed=0): xml_str = "" if preserve_changed: original_changed = self._changed_attr() if action == "del": xml_str = "<miniature action='del' id='" + self.id + "'/>" return xml_str if action == "new": self._dirty_all_attr() changed = self._changed_attr() if changed: # if there are any changes, make sure id is one of them if not changed.has_key("id"): self._dirty_attr("id") changed = self._changed_attr() xml_str = "<miniature" xml_str += " action='" + action + "'" for a in changed.keys(): if a == "pos": if not(self.pos is None): xml_str += " posx='" + str(self.pos.x) + "'" xml_str += " posy='" + str(self.pos.y) + "'" elif a == "heading": if not (self.heading is None): xml_str += " heading='" + str(self.heading) + "'" elif a == "face": if not (self.face is None): xml_str += " face='" + str(self.face) + "'" elif a == "path": if not (self.path is None): xml_str += " path='" + self.path + "'" elif a == "locked": if not (self.locked is None): xml_str+= " locked='" + str(self.locked) + "'" elif a == "hide": if not (self.hide is None): xml_str+= " hide='" + str(self.hide) + "'" elif a == "label": if not (self.label is None): xml_str+= " label='" + self.label + "'" elif a == "snap_to_align": if not (self.snap_to_align is None): xml_str+= " align='" + str(self.snap_to_align) + "'" elif a == "id": if not (self.id is None): xml_str+= " id='" + self.id + "'" elif a == "zorder": if not(self.id is None): xml_str+= " zorder='" + str(self.zorder) + "'" elif a == "width": if not(self.width is None): xml_str+= " width='" + str(self.width) + "'" elif a == "height": if not(self.height is None): xml_str+= " height='" + str(self.height) + "'" xml_str += "/>" self._clean_all_attr() if preserve_changed: for a in original_changed.keys(): self._dirty_attr(a) return xml_str def takedom(self,xml_dom): posx = xml_dom.getAttribute("posx") if posx <> "": self.pos.x = int(posx) self._clean_attr("pos") posy = xml_dom.getAttribute("posy") if posy <> "": self.pos.y = int(posy) self._clean_attr("pos") heading = xml_dom.getAttribute("heading") if heading <> "": self.heading = int(heading) self._clean_attr("heading") face = xml_dom.getAttribute("face") if face <> "": self.face = int(face) self._clean_attr("face") path = xml_dom.getAttribute("path") if path <> "": self.path = path ## self.bmp = wxBitmap("icons/fetching.png",wxBITMAP_TYPE_PNG) ## load_img(path,"miniature",self.id) # change the bitmap image # Sorry if I hosed this up...just didn't look right self.set_bmp(images.load_img(path,"miniature",self.id)) # change the bitmap image self._clean_attr("path") locked = xml_dom.getAttribute("locked") if locked <> "": self.locked = int(locked) self._clean_attr("locked") hide = xml_dom.getAttribute("hide") if hide <> "": self.hide = int(hide) self._clean_attr("hide") label = xml_dom.getAttribute("label") if label <> "": self.label = label self._clean_attr("label") zorder = xml_dom.getAttribute("zorder") if zorder <> "": self.zorder = int(zorder) self._clean_attr("zorder") snap_to_align = xml_dom.getAttribute("align") if snap_to_align <> "": self.snap_to_align = int(snap_to_align) self._clean_attr("snap_to_align") id = xml_dom.getAttribute("id") if id <> "": self.id = id self._clean_attr("id") width = xml_dom.getAttribute("width") if width <> "": self.width = int(width) self._clean_attr("width") height = xml_dom.getAttribute("height") if height <> "": self.height = int(height) self._clean_attr("height") ##----------------------------- ## miniature layer ##----------------------------- class miniature_layer(layer_base): def __init__(self, canvas): layer_base.__init__(self) self._protected_attr = ["serial_number"] self.canvas = canvas self.id = -1 self.miniatures = [] self.serial_number = 0 self._clean_all_attr() def next_serial( self ): self.serial_number += 1 return self.serial_number def get_next_highest_z(self): z = len(self.miniatures)+1 return z def cleanly_collapse_zorder(self): # lock the zorder stuff sorted_miniatures = self.miniatures[:] sorted_miniatures.sort(cmp_zorder) i = 0 for mini in sorted_miniatures: mini.zorder = i i = i + 1 mini._clean_attr("zorder") # unlock the zorder stuff def collapse_zorder(self): # lock the zorder stuff sorted_miniatures = self.miniatures[:] sorted_miniatures.sort(cmp_zorder) i = 0 for mini in sorted_miniatures: if (mini.zorder != MIN_STICKY_BACK) and (mini.zorder != MIN_STICKY_FRONT): ## print "zorder is " + str(mini.zorder) + " and is being set to " + str(i) mini.zorder = i else: print "sticky item found having value of " + str( mini.zorder) i = i + 1 # unlock the zorder stuff def rollback_serial( self ): self.serial_number -= 1 def add_miniature(self,id,path,pos=cmpPoint(0,0),label="",heading=FACE_NONE,face=FACE_NONE,width=0, height=0): print "Before mini creation:" + str(self.get_next_highest_z()) bmp = images.load_img(path,"miniature",id) if bmp: mini = bmp_miniature(id,path,bmp,pos,heading,face,label, zorder = self.get_next_highest_z(),width = width,height = height) print "After mini creation:" + str(self.get_next_highest_z()) self.miniatures.append(mini) print "After mini addition:" + str(self.get_next_highest_z()) mini._dirty_all_attr() xml_str = "<map><miniatures>" xml_str += mini.toxml("new") xml_str += "</miniatures></map>" self.canvas.frame.session.send(xml_str) else: print "Invalid image " + path + " has been ignored!" def get_miniature_by_id(self,id): for mini in self.miniatures: if mini.id == id: return mini return None def del_miniature(self,min): xml_str = "<map><miniatures>" xml_str += min.toxml("del") xml_str += "</miniatures></map>" self.canvas.frame.session.send(xml_str) self.miniatures.remove(min) del min self.collapse_zorder() def del_all_miniatures(self): while len(self.miniatures): min = self.miniatures.pop() del min self.collapse_zorder() def draw(self,dc,topleft,size): #min = self.miniatures.keys() sorted_miniatures = self.miniatures[:] sorted_miniatures.sort(cmp_zorder) for m in sorted_miniatures: if (m.pos.x>topleft[0]-m.right and m.pos.y>topleft[1]-m.bottom and m.pos.x<topleft[0]+size[0]-m.left and m.pos.y<topleft[1]+size[1]-m.top): m.draw(dc) def find_miniature(self, pt,only_locked=false): min_list = [] for m in self.miniatures: if m.hit_test(pt): if only_locked and not m.locked: min_list.append(m) elif not only_locked: min_list.append(m) else: continue if len(min_list) > 0: return min_list else: return None def toxml(self,action="update"): """ format """ attributes = "" if action == "new": self._dirty_all_attr() changed = self._changed_attr() if changed: for a in changed.keys(): if a == "serial_number": attributes += " serial='" + str(self.serial_number) + "'" self._clean_attr("serial_number") minis_string = "" if self.miniatures: for m in self.miniatures: minis_string += m.toxml(action) if minis_string or changed: s = "<miniatures" s += attributes if minis_string: s += ">" s += minis_string s += "</miniatures>" else: s+="/>" return s else: return "" def takedom(self,xml_dom): serial_number = xml_dom.getAttribute('serial') if serial_number <> "": self.serial_number = int(serial_number) self._clean_attr("serial_number") children = xml_dom._get_childNodes() for c in children: action = c.getAttribute("action") id = c.getAttribute('id') if action == "del": mini = self.get_miniature_by_id(id) if mini: self.miniatures.remove(mini) del mini else: wxMessageBox("Deletion of unknown mini attempted","Map Synchronization Error") elif action == "new": pos = cmpPoint(int(c.getAttribute('posx')),int(c.getAttribute('posy'))) path = c.getAttribute('path') label = c.getAttribute('label') try: height = int(c.getAttribute('height')) width = int(c.getAttribute('width')) locked = int(c.getAttribute('locked')) hide = int(c.getAttribute('hide')) heading = int(c.getAttribute('heading')) face = int(c.getAttribute('face')) snap_to_align = int(c.getAttribute('align')) except: height = width = locked = hide = heading = face = snap_to_align = zorder = 0 # The following section is necessary because the zorder might not be sent # e.g. 0.9.4 clients # Assume everything is okay zorder_bad = 0 # If there's a problem getting a zorder (like it's not there) # set the flag try: zorder = int(c.getAttribute('zorder')) except: zorder_bad = 1 zorder = 0 min = bmp_miniature(id, path, images.load_img(path,"miniature",id),pos, heading, face, label, locked, hide, snap_to_align, zorder, width, height) self.miniatures.append(min) # collapse the zorder. If the client behaved well, then nothing should change. # Otherwise, this will ensure that there's some kind of z-order self.collapse_zorder() # Do the normal clean of this minis attributes min._clean_all_attr() # But dirty zorder if there was a problem with it. A new zorder should now # be in effect if zorder_bad: min._dirty_attr('zorder') else: mini = self.get_miniature_by_id(id) if mini: mini.takedom(c) else: wxMessageBox("Update of unknown mini attempted","Map Synchronization Error") #self.canvas.send_map_data() --- NEW FILE: __init__.py --- __all__ = [ 'background_handler', 'fog_msg', 'map_msg', 'background_msg', 'map_prop_dialog', 'background', 'map', 'base_handler', 'map_version', 'base_msg', 'min_dialogs', 'base', 'miniatures_handler', 'grid_handler', 'miniatures_msg', 'grid_msg', 'miniatures', 'grid', 'whiteboard_handler', 'images', 'whiteboard_msg', 'whiteboard', 'map_handler' ] --- NEW FILE: fog_msg.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: mapper/fog_msg.py # Author: Mark Tarrabain # Maintainer: # Version: # $Id: fog_msg.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # __version__ = "$Id: fog_msg.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from base_msg import * from region import * from orpg.minidom import Element import string class fog_msg(map_element_msg_base): def __init__(self,reentrant_lock_object = None): self.tagname = "fog" map_element_msg_base.__init__(self,reentrant_lock_object) self.use_fog = 0 self.fogregion=IRegion() self.fogregion.Clear() def get_line(self,outline,action,output_act): # xml_str = "" # xml_str += "<poly" # if (output_act): # xml_str += " action='" + action + "'" # xml_str += " outline='" + outline + "'" # xml_str += "/>" # return xml_str elem = Element( "poly" ) if ( output_act ): elem.setAttribute( "action", action ) if ( outline == 'all' ) or ( outline == 'none' ): elem.setAttribute( "outline", outline ) else: elem.setAttribute( "outline", "points" ) for pair in string.split( outline, ";" ): p = string.split( pair, "," ) point = Element( "point" ) point.setAttribute( "x", p[ 0 ] ) point.setAttribute( "y", p[ 1 ] ) elem.appendChild( point ) str = elem.toxml() elem.unlink() return str # convenience method to use if only this line is modified # outputs a <map/> element containing only the changes to this line def standalone_update_text(self,update_id_string): buffer = "<map id='" + update_id_string + "'>" buffer += "<fog>" buffer += self.get_changed_xml() buffer += "</fog></map>" return buffer def get_all_xml(self,action="new",output_action=1): return self.toxml(action,output_action) def get_changed_xml(self,action="update",output_action=1): return self.toxml(action,output_action) def toxml(self,action,output_action): #print "fog_msg.toxml called" #print "use_fog :",self.use_fog #print "output_action :",output_action #print "action :",action if not (self.use_fog): return "" fog_string = "" if self.fogregion.IsEmpty(): fog_string=self.get_line("all","del",output_action) for ri in self.fogregion.GetRectList(): x1=ri.GetX() x2=x1+ri.GetW()-1 y1=ri.GetY() y2=y1+ri.GetH()-1 fog_string += self.get_line(str(x1)+","+str(y1)+";"+ str(x2)+","+str(y1)+";"+ str(x2)+","+str(y2)+";"+ str(x1)+","+str(y2),action,output_action) s = "<fog" if fog_string: s += ">" s += fog_string s += "</fog>" else: s+="/>" return s def interpret_dom(self,xml_dom): self.use_fog=1 #print 'fog_msg.interpret_dom called' children = xml_dom._get_childNodes() #print "children",children for l in children: action = l.getAttribute("action") outline = l.getAttribute("outline") #print "action/outline",action, outline if (outline=="all"): polyline=[] self.fogregion.Clear() elif (outline=="none"): polyline=[] self.use_fog=0 self.fogregion.Clear() else: polyline=[] list = l._get_childNodes() for node in list: polyline.append( IPoint().make( int(node.getAttribute("x")), int(node.getAttribute("y")) ) ) # pointarray = outline.split(";") # for m in range(len(pointarray)): # pt=pointarray[m].split(",") # polyline.append(IPoint().make(int(pt[0]),int(pt[1]))) #print "length of polyline", len(polyline) if (len(polyline)>2): if action=="del": self.fogregion.FromPolygon(polyline,0) else: self.fogregion.FromPolygon(polyline,1) def init_from_dom(self,xml_dom): #print "xml_dom",xml_dom self.interpret_dom(xml_dom) --- NEW FILE: whiteboard_handler.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: mapper/whiteboard_hander.py # Author: OpenRPG Team # Maintainer: # Version: # $Id: whiteboard_handler.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: Whiteboard layer handler # __version__ = "$Id: whiteboard_handler.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from base_handler import * DELETE_ALL_LINES= wxNewId() LINE_REMOVE = wxNewId() LINE_TITLE_HACK = wxNewId() LINE_COLOR = wxNewId() LINE_WIDTH = wxNewId() SELECT_ITEM = wxNewId() # Text properties menu variables TEXT_REMOVE = wxNewId() TEXT_TITLE_HACK = wxNewId() TEXT_POINT_SIZE = wxNewId() TEXT_WEIGHT = wxNewId() TEXT_STYLE = wxNewId() TEXT_STRING = wxNewId() TEXT_COLOR = wxNewId() TEXT_STYLES = [wxNORMAL, wxITALIC] TEXT_WEIGHTS = [wxNORMAL, wxBOLD] TEXT_OKAY = wxNewId() TEXT_PROPERTIES = wxNewId() #drawing modes (added by Snowdog 05-09-2003) DRAW_MODE = wxNewId() DRAW_FREEFORM = 1 DRAW_POLYLINE = 2 DRAW_TEXT = 3 POLYLINE_END_TOLERANCE = 5 LIVE_REFRESH = wxNewId() LIVE_POLYLINE_DEFAULT = 1 class whiteboard_handler(base_layer_handler): def __init__(self, parent, id, canvas): self.drawing_mode = DRAW_FREEFORM self.line_string = "0,0;" self.drawing = false self.upperleft = wxPoint(0,0) self.lowerright = wxPoint(0,0) #polyline drawing vars self.polypoints = 0 self.lastpoint = None self.selected = None #text drawing vars self.style = str(wxNORMAL) self.weight = str(wxNORMAL) self.pointsize = str(12) self.text_selected_item = None #self.r_h = RGBHex() base_layer_handler.__init__(self, parent, id, canvas) self.build_text_properties_menu() self.wb = self.canvas.layers['whiteboard'] def build_ctrls(self): base_layer_handler.build_ctrls(self) self.color_button = wxButton(self, LINE_COLOR, "Pen Color", style=wxBU_EXACTFIT) self.drawmode_ctrl = wxChoice(self, DRAW_MODE, choices = ["Freeform", "Polyline","Text"]) self.drawmode_ctrl.SetSelection(0) #always start showing "Freeform" self.live_refresh = wxCheckBox(self, LIVE_REFRESH, " Live Refresh") self.live_refresh.SetValue(LIVE_POLYLINE_DEFAULT) self.color_button.SetBackgroundColour(wxBLACK) self.color_button.SetForegroundColour(wxWHITE) dwidthList=['1','2','3','4','5','6','7','8','9','10'] self.widthList=wxChoice(self,LINE_WIDTH,size=wxSize(40, 20),choices=dwidthList, name="choice") self.widthList.SetSelection(0) #always start showing "1" self.sizer.Prepend(wxSize(20,25),1) self.sizer.Prepend(self.color_button, 0, wxEXPAND) self.sizer.Prepend(wxSize(20,25)) self.sizer.Prepend(self.live_refresh, 0, wxEXPAND) self.sizer.Prepend(wxSize(10,25)) self.sizer.Prepend(self.drawmode_ctrl, 0, wxEXPAND) self.sizer.Prepend(wxStaticText(self, -1, "Drawing Mode: "),0,wxALIGN_CENTER) self.sizer.Prepend(wxSize(10,25)) self.sizer.Prepend(self.widthList, 0, wxEXPAND) self.sizer.Prepend(wxStaticText(self, -1, "Line Width: "),0,wxALIGN_CENTER) EVT_MOTION(self, self.on_motion) EVT_CHOICE(self, DRAW_MODE, self.check_draw_mode) EVT_BUTTON(self, LINE_COLOR, self.on_pen_color) EVT_CHOICE(self, LINE_WIDTH, self.on_pen_width) def build_text_properties_menu(self, label="Text Properties"): self.text_properties_dialog = wxDialog(self, -1, "Text Properties", name = "Text Properties") self.text_props_sizer = wxBoxSizer(wxVERTICAL) okay_boxer = wxBoxSizer(wxHORIZONTAL) okay_button = wxButton(self.text_properties_dialog, TEXT_OKAY, "APPLY") cancel_button = wxButton(self.text_properties_dialog, wxID_CANCEL,"CANCEL") okay_boxer.Add(okay_button, 1, wxALIGN_LEFT) okay_boxer.Add(wxSize(10,10)) okay_boxer.Add(cancel_button, 1, wxALIGN_RIGHT) self.txt_boxer = wxBoxSizer(wxHORIZONTAL) self.txt_static = wxStaticText(self.text_properties_dialog, -1, "Text: ") self.text_control = wxTextCtrl(self.text_properties_dialog, TEXT_STRING, "", name = "Text: ") self.txt_boxer.Add(self.txt_static,0,wxEXPAND) self.txt_boxer.Add(wxSize(10,10)) self.txt_boxer.Add(self.text_control,1,wxEXPAND) self.point_boxer = wxBoxSizer(wxHORIZONTAL) # self.point_static = wxStaticText(self.text_properties_dialog, -1, "Text Size: ") # self.point_control = wxSpinCtrl(self.text_properties_dialog,TEXT_POINT_SIZE, value = "12",min = 1, initial = 12, name = "Font Size: ") # self.point_boxer.Add(self.point_static,1,wxEXPAND) # self.point_boxer.Add(wxSize(10,10)) # self.point_boxer.Add(self.point_control,0,wxEXPAND) self.text_color_control = wxButton(self.text_properties_dialog, TEXT_COLOR, "TEXT COLOR",style=wxBU_EXACTFIT) # self.weight_control = wxRadioBox(self.text_properties_dialog, TEXT_WEIGHT, "Weight",choices = ["Normal","Bold"]) # self.style_control = wxRadioBox(self.text_properties_dialog, TEXT_STYLE, "Style",choices = ["Normal", "Italic"]) self.text_props_sizer.Add(self.txt_boxer,0,wxEXPAND) self.text_props_sizer.Add(self.point_boxer,0, wxEXPAND) # self.text_props_sizer.Add(self.weight_control,0, wxEXPAND) # self.text_props_sizer.Add(self.style_control,0, wxEXPAND) self.text_props_sizer.Add(self.text_color_control, 0, wxEXPAND) self.text_props_sizer.Add(wxSize(10,10)) self.text_props_sizer.Add(okay_boxer,0, wxEXPAND) self.text_props_sizer.Fit(self) self.text_properties_dialog.SetSizer(self.text_props_sizer) self.text_properties_dialog.Fit() EVT_BUTTON(self.text_properties_dialog,TEXT_COLOR, self.on_text_color) EVT_BUTTON(self.text_properties_dialog,TEXT_OKAY,self.on_text_properties) # EVT_MENU(self.canvas,TEXT_POINT_SIZE,self.on_text_properties) # EVT_MENU(self.canvas,TEXT_WEIGHT,self.on_text_properties) # EVT_MENU(self.canvas,TEXT_STYLE,self.on_text_properties) # self.text_properties_dialog.Destroy() pass def build_menu(self,label = "Whiteboard"): base_layer_handler.build_menu(self,label) self.main_menu.AppendSeparator() self.main_menu.Append(LINE_COLOR,"&Change Pen Color") self.main_menu.Append(DELETE_ALL_LINES,"Delete &All Lines") EVT_MENU(self.canvas,LINE_COLOR,self.on_pen_color) EVT_MENU(self.canvas,DELETE_ALL_LINES,self.delete_all_lines) self.line_menu = wxMenu() if wxPlatform == '__WXMSW__': self.line_menu.SetTitle(label) else: self.line_menu.Append(LINE_TITLE_HACK,label) self.line_menu.AppendSeparator() self.line_menu.Append(LINE_REMOVE,"&Remove") EVT_MENU(self.canvas, LINE_REMOVE, self.on_line_menu_item) self.text_menu = wxMenu() if wxPlatform == '__WXMSW__': self.text_menu.SetTitle(label) else: self.text_menu.Append(TEXT_TITLE_HACK,label) self.text_menu.AppendSeparator() self.text_menu.Append(TEXT_PROPERTIES,"&Properties") self.text_menu.Append(TEXT_REMOVE,"&Remove") EVT_MENU(self.canvas, TEXT_PROPERTIES, self.text_menu_properties) EVT_MENU(self.canvas, TEXT_REMOVE, self.on_text_menu_item) def do_line_menu(self,pos): self.canvas.PopupMenu(self.line_menu, pos) def item_selected(self,evt): item = evt.GetId() self.item_selection = self.selection_list[item] def on_text_properties(self,evt): text_string = self.text_control.GetValue() style = wxNORMAL weight = wxNORMAL point = 12 c = self.text_color_control.GetForegroundColour() color = self.canvas.layers['whiteboard'].r_h.hexstring(c.Red(), c.Green(), c.Blue()) # self.text_selected_item.set_text_props(text_string, style, point, weight,color) self.text_to_xml() self.text_properties_dialog.Show(FALSE) self.text_selected_item.selected = false self.text_selected_item = None def on_text_color(self,evt): dlg = wxColourDialog(self) if dlg.ShowModal() == wxID_OK: c = dlg.GetColourData() self.text_color_control.SetForegroundColour(c.GetColour()) dlg.Destroy() def text_to_xml(self): xml_str = "<map><whiteboard>" xml_str += self.text_selected_item.toxml('update') xml_str += "</whiteboard></map>" self.canvas.frame.session.send(xml_str) self.canvas.Refresh(false) def get_text_properties(self, pos, item): self.text_color_control.SetForegroundColour(self.text_selected_item.textcolor) self.text_control.SetValue(self.text_selected_item.text_string) # self.point_control.SetValue(int(self.text_selected_item.pointsize)) self.text_properties_dialog.Center() self.text_properties_dialog.Show(TRUE) def do_text_menu(self,pos): self.canvas.PopupMenu(self.text_menu, pos) return def text_menu_properties(self, text_list): self.selection_list = text_list self.text_select_menu = None self.text_select_menu = wxMenu() self.text_select_menu.SetTitle("Which item?") menu_list = [] loop_count = 0 try: for m in text_list: menu_list.append(loop_count) self.text_select_menu.Append(menu_list[loop_count],m.text_string) EVT_MENU(self.canvas, menu_list[loop_count] , self.item_selected) loop_count += 1 item = self.canvas.PopupMenu(self.text_select_menu,pos) return self.item_selection except Exception, e: print "Error found in do_text_menu!",e def on_right_down(self,evt): line = 0 scale = self.canvas.layers['grid'].mapscale dc = wxClientDC(self.canvas) self.canvas.PrepareDC(dc) dc.SetUserScale(scale,scale) pos = evt.GetLogicalPosition(dc) if self.drawing_mode == DRAW_TEXT: self.on_text_right_down(evt,dc) elif (self.drawing_mode == DRAW_FREEFORM) or (self.drawing_mode == DRAW_POLYLINE): line_list = self.canvas.layers['whiteboard'].find_line(pos) if line_list: self.sel_rline = self.canvas.layers['whiteboard'].get_line_by_id(line_list.id) if self.sel_rline: s... [truncated message content] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:28
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/tools In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/tools Added Files: __init__.py autoupdate.py config_files.py config_update.py inputValidator.py orpg_settings.py orpg_sound.py orpg_update.py passtool.py predTextCtrl.py rgbhex.py scriptkit.py server_probe.py toolBars.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: orpg_sound.py --- from orpg.orpg_windows import * if wxPlatform == '__WXMSW__': import winsound class orpg_sound: def __init__(self,unix_player=None): self.unix_player = unix_player def play(self,sound_file): if not sound_file: return if wxPlatform == '__WXMSW__': self.play_windows(sound_file) elif wxPlatform == '__WXGTK__': self.play_unix(sound_file) def play_windows(self,sound_file): print sound_file winsound.PlaySound(sound_file, winsound.SND_FILENAME) def play_unix(self,sound_file): if self.unix_player: os.system(self.unix_player + " " + sound_file + " &") --- NEW FILE: server_probe.py --- #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: server_probe.py # Author: Chris Davis # Maintainer: # Version: # $Id: server_probe.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: This is a periodic maintenance script for removing # Unresponsive servers from the meta list. # from threading import Event from orpg.networking import mplay_client from orpg.networking import meta_server_lib import time class server_probe: def __init__(self): self.evts = { } self.evts['on_receive'] = self.on_receive self.evts['on_mplay_event'] = self.on_mplay_event self.evts['on_group_event'] = self.on_group_event self.evts['on_player_event'] = self.on_player_event self.evts['on_status_event'] = self.on_status_event self.session = mplay_client.mplay_client("Server Probe",self.evts) self.removed=0 self.ok =0 self.lock = Event() def probe_servers(self): names = [] addresses = [] node_list = None try: xml_dom = meta_server_lib.get_server_list(); node_list = xml_dom.getElementsByTagName('server') for n in node_list: address = n.getAttribute('address') name = n.getAttribute( 'name' ) id = n.getAttribute('id') if address not in addresses and name not in names: names.append( name ) addresses.append( address ) self.probe_server(address,id) else: # If we are here, we found a duplicate print "Duplicate entry, \"" + name + "\", is being removed." self.removed = self.removed + 1 meta_server_lib.remove_server(id) except: print "An exception has occured. Attempting to ignore it..." print "\n\nServers probe done " if node_list != None: print "Total Servers:" + str(len(node_list)) print "servers removed: " + str(self.removed) print "servers ok: " + str(self.ok) def probe_server(self,address,id): print "trying server: " + address if address == "asdfasdf": # replace with address of server to force from list print "Forced removal of server!!!!!!!!!!" meta_server_lib.remove_server(id) else: if self.session.connect(address): self.lock.wait( timeout=20 ) self.session.start_disconnect() while self.session.is_connected(): time.sleep( 1 ) self.session.check_my_status() print "server: " + address + " ok\n" self.ok = self.ok + 1 print "disconnected from valid server." else: print "**********>failed connnection!" print "**********>removng server " + address + "\n" self.removed = self.removed + 1 meta_server_lib.remove_server(id) ## meta_server_lib.post_failed_connection( id ) while self.session.is_connected(): time.sleep(1) def on_receive( self, evt, data ): """Not used """ self.lock.set() def on_mplay_event( self, evt ): """Not used """ self.lock.set() def on_group_event( self, evt ): """Not used """ self.lock.set() def on_player_event( self, evt ): """Disconnects from the server if a 'new player' event is generated. """ self.lock.set() def on_status_event( self, evt ): """Not used """ self.lock.set() if __name__ == "__main__": probe = server_probe() probe.probe_servers() --- NEW FILE: orpg_settings.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: orpg_settings.py # Author: Chris Davis # Maintainer: # Version: # $Id: orpg_settings.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: classes for orpg settings # from orpg.orpg_windows import * import orpg.dirpath import config_files import config_update from orpg.orpg_xml import * from rgbhex import * import sys import os class settings_grid(wxGrid): """grid for gen info""" def __init__(self, parent, setobj, settings, openrpg = None): wxGrid.__init__(self, parent, -1, style=wxSUNKEN_BORDER| wxWANTS_CHARS) self.openrpg = openrpg self.setting_data = [] EVT_SIZE(self, self.on_size) EVT_GRID_CELL_CHANGE(self, self.on_cell_change) EVT_GRID_CELL_LEFT_CLICK(self, self.on_left_click) self.CreateGrid(len(settings),3) self.SetRowLabelSize(0) self.SetColLabelValue(0,"Setting") self.SetColLabelValue(1,"Value") self.SetColLabelValue(2,"Available Options") for i in range(len(settings)): self.SetCellValue(i,0,settings[i]) value = setobj.get_setting(settings[i]) self.SetCellValue(i,1,value) if value and value[0] == '#': self.SetCellBackgroundColour(i,1,value) options = setobj.get_options(settings[i]) self.SetCellValue(i,2,options) self.setobj = setobj def on_left_click(self,evt): row = evt.GetRow() col = evt.GetCol() if col == 2: return elif col == 0: name = self.GetCellValue(row,0) str = self.setobj.get_help(name) msg = wxMessageBox(str,name) return setting = self.GetCellValue(row,0) value = self.GetCellValue(row,1) if value and value[0] == '#': hexcolor = RGBHex().do_hex_color_dlg(self) if hexcolor: self.setobj.set_setting(setting,hexcolor) self.SetCellBackgroundColour(row,1,hexcolor) self.Refresh() else: evt.Skip() def on_cell_change(self,evt): row = evt.GetRow() col = evt.GetCol() if col <> 1: return setting = self.GetCellValue(row,0) value = self.GetCellValue(row,1) self.setting_data += [[setting, value]] def get_h(self): (w,h) = self.GetClientSizeTuple() rows = self.GetNumberRows() minh = 0 for i in range (0,rows): minh += self.GetRowSize(i) minh += 120 return minh def on_size(self,evt): (w,h) = self.GetClientSizeTuple() cols = self.GetNumberCols() col_w = w/(cols) for i in range(0,cols): self.SetColSize(i,col_w) self.Refresh() ##----------------------------- ## Preferences Dialog ##----------------------------- class prefs_dialog(wxDialog): "Preferences dialog for OpenRPG" def __init__(self,parent,setobj,openrpg): SETTINGS_TABS = wxNewId() wxDialog.__init__(self,parent,-1,"OpenRPG Preferences",wxDefaultPosition,size = wxSize(-1,-1), style = wxRESIZE_BORDER | wxSYSTEM_MENU | wxCAPTION) self.chat = openrpg.get_component('chat') self.settings_tabs = wxNotebook(self, SETTINGS_TABS) self.settings_handlers = [] chat = [] tabs = [] general = [] map = [] gametree = [] macros = [] roomlist = [] toolbar = [] colors = [] settings = setobj.get_setting_keys() for m in range(len(settings)): if setobj.get_catagory(settings[m]) == "chat": chat.append(settings[m]) elif setobj.get_catagory(settings[m]) == "tabs": tabs.append(settings[m]) elif setobj.get_catagory(settings[m]) == "general": general.append(settings[m]) elif setobj.get_catagory(settings[m]) == "map": map.append(settings[m]) elif setobj.get_catagory(settings[m]) == "gametree": gametree.append(settings[m]) elif setobj.get_catagory(settings[m]) == "macros": macros.append(settings[m]) elif setobj.get_catagory(settings[m]) == "roomlist": roomlist.append(settings[m]) elif setobj.get_catagory(settings[m]) == "toolbar": toolbar.append(settings[m]) elif setobj.get_catagory(settings[m]) == "colors": colors.append(settings[m]) # general tab general_tab = settings_grid(self.settings_tabs,setobj,general) self.settings_handlers.append(general_tab) self.settings_tabs.AddPage(self.settings_handlers[0],"General") # chat tab chat_tab = settings_grid(self.settings_tabs,setobj,chat) self.settings_handlers.append(chat_tab) self.settings_tabs.AddPage(self.settings_handlers[1],"Chat") # tabs tab tabs_tab = settings_grid(self.settings_tabs,setobj,tabs) self.settings_handlers.append(tabs_tab) self.settings_tabs.AddPage(self.settings_handlers[2],"Chat Tabs") # map tab map_tab = settings_grid(self.settings_tabs,setobj,map) self.settings_handlers.append(map_tab) self.settings_tabs.AddPage(self.settings_handlers[3],"Map") # gametree tab gametree_tab = settings_grid(self.settings_tabs,setobj,gametree) self.settings_handlers.append(gametree_tab) self.settings_tabs.AddPage(self.settings_handlers[4],"Game Tree") # macros tab macros_tab = settings_grid(self.settings_tabs,setobj, macros) self.settings_handlers.append(macros_tab) self.settings_tabs.AddPage(self.settings_handlers[5],"Macros") # Game Server/Room List tab roomlist_tab = settings_grid(self.settings_tabs,setobj, roomlist) self.settings_handlers.append(roomlist_tab) self.settings_tabs.AddPage(self.settings_handlers[6],"Server/Room Lists") # Chat Toolbar tab toolbar_tab = settings_grid(self.settings_tabs,setobj, toolbar) self.settings_handlers.append(toolbar_tab) self.settings_tabs.AddPage(self.settings_handlers[7],"Chat Toolbar") # Chat Colors tab colors_tab = settings_grid(self.settings_tabs,setobj, colors) self.settings_handlers.append(colors_tab) self.settings_tabs.AddPage(self.settings_handlers[8],"Chat Colors") winsizer = wxBoxSizer(wxVERTICAL) sizer = wxBoxSizer(wxHORIZONTAL) tab_sizer = wxBoxSizer(wxHORIZONTAL) tab_sizer.Add(self.settings_tabs, 1, wxEXPAND) sizer.Add(wxButton(self, wxID_OK, "OK"), 1, wxEXPAND) sizer.Add(wxSize(10,10)) sizer.Add(wxButton(self, wxID_CANCEL, "Cancel"), 1, wxEXPAND) winsizer.Add(tab_sizer, 1, wxEXPAND | wxALIGN_TOP) winsizer.Add(sizer, 0, wxEXPAND | wxALIGN_BOTTOM) self.general_tab = general_tab self.chat_tab = chat_tab self.tabs_tab = tabs_tab self.map_tab = map_tab self.gametree_tab = gametree_tab self.macros_tab = macros_tab self.roomlist_tab = roomlist_tab self.toolbar_tab = toolbar_tab self.colors_tab = colors_tab self.setobj = setobj self.winsizer = winsizer minh = self.general_tab.get_h() if minh > 750: minh = 750 self.SetSize((580,minh+10)) self.winsizer.SetDimension(0,0,580,minh-25) EVT_BUTTON(self, wxID_OK, self.on_ok) EVT_NOTEBOOK_PAGE_CHANGED(self, SETTINGS_TABS, self.on_page_change) EVT_SIZE(self, self.on_size) def on_size(self,evt): (w,h) = self.GetClientSizeTuple() self.winsizer.SetDimension(0,0,w,h-25) def on_page_change(self,evt): p = self.settings_tabs.GetPage(evt.GetOldSelection()) p.SaveEditControlValue() p.HideCellEditControl() tmp = evt.GetSelection() if tmp == 0: minh = self.general_tab.get_h() elif tmp == 1: minh = self.chat_tab.get_h() elif tmp == 2: minh = self.tabs_tab.get_h() elif tmp == 3: minh = self.map_tab.get_h() elif tmp == 4: minh = self.gametree_tab.get_h() elif tmp == 5: minh = self.macros_tab.get_h() elif tmp == 6: minh = self.roomlist_tab.get_h() elif tmp == 7: minh = self.toolbar_tab.get_h() elif tmp == 8: minh = self.colors_tab.get_h() if minh > 750: minh = 750 self.SetSize((580,minh+10)) self.winsizer.SetDimension(0,0,580,minh-25) def on_ok(self,evt): #tablist = [self.settings_tabs.GetPage(t) for t in range(0,self.settings_tabs.GetPageCount())] for t in range(0,self.settings_tabs.GetPageCount()): self.settings_tabs.GetPage(t).SaveEditControlValue() for m in self.general_tab.setting_data: self.setobj.set_setting(m[0],m[1]) for m in self.chat_tab.setting_data: self.setobj.set_setting(m[0],m[1]) if m[0] == "tabbedwhispers": self.warning_msg() try: if len(self.chat.parent.panel_list) > 0: for panel in self.chat.parent.panel_list: if m[0] == "defaultfont": panel.set_default_font(fontname=m[1]) elif m[0] == "defaultfontsize": panel.set_default_font(fontsize=int(m[1])) raise Exception() except: if m[0] == "defaultfont": self.chat.set_default_font(fontname=m[1]) elif m[0] == "defaultfontsize": self.chat.set_default_font(fontsize=int(m[1])) for m in self.map_tab.setting_data: self.setobj.set_setting(m[0],m[1]) for m in self.tabs_tab.setting_data: self.setobj.set_setting(m[0],m[1]) if m[0] == "tabbedwhispers": self.warning_msg() if m[0] == "GMWhisperTab" and m[1] == '1': self.chat.parent.create_gm_tab() for m in self.gametree_tab.setting_data: self.setobj.set_setting(m[0],m[1]) for m in self.macros_tab.setting_data: self.setobj.set_setting(m[0],m[1]) for m in self.roomlist_tab.setting_data: self.setobj.set_setting(m[0],m[1]) for m in self.colors_tab.setting_data: self.setobj.set_setting(m[0],m[1]) for m in self.toolbar_tab.setting_data: self.setobj.set_setting(m[0],m[1]) ##tabbed whispers are on try: if len(self.chat.parent.panel_list) > 0: for panel in self.chat.parent.panel_list: if m[0] == 'AliasTool_On': panel.toggle_alias(m[1]) elif m[0] == 'ToGMsButton_On': panel.toggle_gm(m[1]) elif m[0] == 'DiceButtons_On': panel.toggle_dice(m[1]) elif m[0] == 'FormattingButtons_On': panel.toggle_formating(m[1]) raise Exception() except: if m[0] == 'AliasTool_On': self.chat.toggle_alias(m[1]) elif m[0] == 'ToGMsButton_On': self.chat.toggle_gm(m[1]) elif m[0] == 'DiceButtons_On': self.chat.toggle_dice(m[1]) elif m[0] == 'FormattingButtons_On': self.chat.toggle_formating(m[1]) self.general_tab.DisableCellEditControl() self.chat_tab.DisableCellEditControl() self.map_tab.DisableCellEditControl() self.gametree_tab.DisableCellEditControl() self.macros_tab.DisableCellEditControl() self.roomlist_tab.DisableCellEditControl() self.toolbar_tab.DisableCellEditControl() self.colors_tab.DisableCellEditControl() self.EndModal(wxID_OK) def warning_msg(self): warning_dlg = wxMessageBox("You will need to restart for\nthese changes to take effect", "Information") #################### ## settings xml object #################### class settings: def __init__(self): ini_xml="ini.xml" index=1 while(index<len(sys.argv)-1): if (sys.argv[index] == "-i"): ini_xml=sys.argv[index+1] sys.argv[index]="" sys.argv[index+1]="" index = index + 1 # This stores a dictionary of runnable objects that will be called # when a key's value gets set. See settings.set_change_handler() self.change_handler = {} # Load the tree self.setup_ini(ini_xml) #=================================================== # setup_ini() # # Reworked for version 1.6.3 release. --Snowdog (5/10/05) # Revamped tree loading proceedure # Added new user ini updater to auto-add missing keys #=================================================== def setup_ini(self,ini_xml): default_filename = "default_ini.xml" #default ini template name self.filename = orpg.dirpath.dir_struct["user"] + ini_xml # This is the filename we're going attempt to use template = orpg.dirpath.dir_struct["template"]+default_filename #template fully qualified filename result = config_files.validate_config_file(ini_xml,default_filename) #ensures user ini file exists (by creating it if it doesn't) if (result == 2): #file user_file was created. Print console warning. print("Config file "+ini_xml+" was created from default template.") if (result == 0): msg = wxMessageDialog(None,"Restore " + ini_xml + " or default_ini.xml and start again.","Settings files bad or missing",wxOK) msg.ShowModal() msg.Destroy() sys.exit() my_tree = self.loadTree(self.filename) #get the DOM for the config file if ( not my_tree ): text = ini_xml + " structure invalid.\nUsing default config file instead.\nPlease remove or repair "+ini_xml+" file." msg = wxMessageDialog(None,text,"Config File Error",wxOK) msg.ShowModal() msg.Destroy() if (os.path.exists(template)): my_tree = self.loadTree(template) if (not my_tree): text = default_filename + " template invalid.\nPlease obtain a new copy of the template file. OpenRPG cannot load config information and will terminate." msg = wxMessageDialog(None,text,"Unrecoverable Error",wxOK) msg.ShowModal() msg.Destroy() sys.exit() #savedAs = self.saveTreeSafely(ini_xml, my_tree) #move the old ini.xml file out of the way. This has to be done with file I/O cause the tree is not viable savedAs = self.saveTreeSafely(ini_xml, my_tree) text = "Your "+ini_xml+" config file has been automatically repaired.\nYour original version has been saved as "+savedAs+" if needed." msg = wxMessageDialog(None,text,"Config File Updated",wxOK) msg.ShowModal() msg.Destroy() if (result == 1): #file was loaded from the user file... if (config_update.UpdateIniFile(my_tree, template)): #verify all keys against the template ini file and update/add if required. #user ini has been updated. Save to disk. savedAs = self.saveTreeSafely(ini_xml, my_tree) text = "Your "+ini_xml+" config file has been automatically updated.\nYour original version has been saved as "+savedAs+" if needed." msg = wxMessageDialog(None,text,"Config File Updated",wxOK) msg.ShowModal() msg.Destroy() #At this point my_tree should contain valid and template updated config data self.xml_dom = my_tree def reload_settings(self,chat): try: chat.session.set_name(self.get_setting("player")) chat.set_colors() chat.set_buffersize() chat.roller_manager.set_roller(self.get_setting('dieroller')) except Exception,e: print e chat.InfoPost("One or more settings were invalid check your settings") def loadTree(self, filename): try: ini = open(filename,"r") txt = ini.read() tree = parseXml(txt)._get_documentElement() ini.close() return tree except: return None def load(self, filename): self.xml_dom = self.loadTree(filename) if self.xml_dom: return 1 else: return 0 def get_tree_setting_keys(self,tree,hidden=0): node_list = tree._get_childNodes() keys = [] for n in node_list: if not hidden and n.getAttribute("hidden"): #all none hidden elements continue keys.append(n._get_tagName()) return keys def get_setting_keys(self,hidden=0): return self.get_tree_setting_keys(self.xml_dom,hidden) def get_tree_xml_node(self,tree,name): node_list = tree.getElementsByTagName(name) if not len(node_list): node = minidom.Element(name) node = tree.appendChild(node) #print tree.toxml(1) node_list = tree.getElementsByTagName(name) return node_list[0] def get_xml_node(self,name): return self.get_tree_xml_node(self.xml_dom,name) def get_tree_value(self,tree,name,create = 1,attrib = "value"): node = self.get_tree_xml_node(tree,name) return node.getAttribute(attrib) def get_setting(self,name,create=1): return self.get_tree_value(self.xml_dom,name,create) def get_help(self,name): node = self.get_tree_xml_node(self.xml_dom,name) try: str = node.getAttribute("help") # # funkyness to allow \n in help names display properly in dialog. # if you can find a better way then go for it. # splited = str.split("\\n") return_str = "" for m in range(len(splited)): return_str = return_str + splited[m] +"\n" # # end of funkyness # if return_str == "\n": return_str = "No help available for this setting" return return_str except: return "No help available for this setting" def get_catagory(self,name): node = self.get_tree_xml_node(self.xml_dom,name) try: catagory = node.getAttribute("catagory").lower() if catagory == "": catagory = "general" return catagory except: return "general" def get_options(self,name): node = self.get_tree_xml_node(self.xml_dom,name) try: option = node.getAttribute("options") except: option = "" return option def set_change_handler(self,key,handler): # takes a key (key) to watch and runnable object (handler) to execute # when key changes. When handler is executed, it is passed a single # argument, which will hold the value being stored. self.change_handler[key] = handler def get_change_handler(self,key): if self.change_handler.has_key(key): return self.change_handler[key] else: return None def set_tree_value(self,tree,name,value,hidden = 0, attrib="value"): node = self.get_tree_xml_node(tree,name) node.setAttribute(attrib,value) if hidden: node.setAttribute("hidden", "true") if self.change_handler.has_key(name) and attrib == "value": self.change_handler[name](value) def set_setting(self,name,value,hidden=0): node = self.get_xml_node(name) node.setAttribute("value",value) if hidden: node.setAttribute("hidden", "true") if self.change_handler.has_key(name): self.change_handler[name](value) def get_tree_multi_setting(self,tree,name): node_list = tree.getElementsByTagName(name) node_list = node_list[0]._get_childNodes() if len(node_list) == 0: return None ary = [] for n in node_list: ary.append(n.getAttribute("value")) return ary def get_multi_setting(self,name): return self.get_tree_multi_setting(self.xml_dom,name) def saveTree(self,tree): ini = open(self.filename,"w") ini.write(toxml(tree,1)) ini.close() def save(self): self.saveTree(self.xml_dom) #========================================================== # saveTreeSafely() # # Moves a copy of the original ini file out of the way # then saves the (updated) ini data to the original location # automatic filename postfix prevents file overwrite # 5/10/05 --Snowdog #========================================================== def saveTreeSafely(self, baseFilename, tree): #write file to disk making sure not to overwrite an existing file. index = 1 file = baseFilename+"-"+str(index) while ( os.path.exists(orpg.dirpath.dir_struct["user"]+file)): index = index +1 file = baseFilename+"-"+str(index) #move the original ini out of the way os.rename(orpg.dirpath.dir_struct["user"]+baseFilename, orpg.dirpath.dir_struct["user"]+file) #write the updated file ini = open(orpg.dirpath.dir_struct["user"] + baseFilename,"w") ini.write(toxml(tree,1)) ini.close() return file --- NEW FILE: predTextCtrl.py --- # Copyright (C) 2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: predTextCtrl.py # Author: Andrew Bennett # Maintainer: Andrew Bennett # Version: $id:$ # # Description: This file contains an extension to the wxPython wxTextCtrl that provides predictive word completion # based on a word file loaded at instantiation. Also, it learns new words as you type, dynamically # adjusting a weight for each word based on how often you type it. # ## ## Module Loading ## import string from orpg.orpg_windows import * # This line added to test CVS commit ## ## Class Definitions ## # This class implements a tree node that represents a letter # # Defines: # __init__(self,filename) # class Letter: def __init__(self,asciiCharIn,parentNodeIn): self.asciiChar = asciiCharIn # should be an ASCII char self.parentNode = parentNodeIn # should be ref to a class Letter self.priority = 0 # should be an int self.mostCommon = self # should be a ref to a class Letter self.children = {} # should be a ref to a dictionary of class Letter # This class implements the tree structure to hold the words # # Defines: # __init__(self,filename) # updateMostCommon(self,target) # setWord(self,wordText,priority,sumFlag) # addWord(self,wordText) # setWord(self,wordText) # setWordPriority(self,wordText,priority) # findWordNode(self,wordText) returns class Letter # findWordPriority(self,wordText) returns int # getPredition(self,k,cur) returns string class LetterTree: # Initialization subroutine. # # self : instance of self # filename : name of word file to use # # returns None # # Purpose: Constructor for LetterTree. Basically, it initializes itself with a word file, if present. def __init__(self,filename): self.rootNode = Letter("",None) # rootNode is a class Letter self.rootNode.children = {} # initialize the children list try: FILE = open(filename,"r") for line in FILE.readlines(): # read-in each line and ... self.addWord(string.lower(string.strip(line))) # ... add a stripped, lowercased version of it to the tree FILE.close() except: # silently ignore errors here pass # updateMostCommon subroutine. # # self : instance of self # target : class Letter that was updated # # Returns None # # Purpose: Updates all of the parent nodes of the target, such that their mostCommon member # points to the proper class Letter, based on the newly updated priorities. def updateMostCommon(self, target): # cur is a class Letter prev = target.parentNode # prev is a class Letter while prev: if prev.mostCommon is None: prev.mostCommon = target else: if target.priority > prev.mostCommon.priority: prev.mostCommon = target prev = prev.parentNode # setWord subroutine. # # self : instance of self # wordText : string representing word to add # priority : integer priority to set the word # sumFlag : if true, add the priority to the existing, else assign the priority # # Returns: None # # Purpose: Sets or increments the priority of a word, adding the word if necessary def setWord(self,wordText,priority = 1,sumFlag = 0): cur = self.rootNode # start from the root for ch in wordText: # for each character in the word if cur.children.has_key(ch): # check to see if we've found a new word cur = cur.children[ch] # if we haven't found a new word, move to the next letter and try again else: # in this clause, we're creating a new branch, as the word is new newLetter = Letter(ch,cur) # create a new class Letter using this ascii code and the current letter as a parent if cur is self.rootNode: # special case: Others expect the top level letters to point to None, not self.rootNode newLetter.parentNode = None cur.children[ch] = newLetter # add the new letter to the list of children of the current letter cur = newLetter # make the new letter the current one for the next time through # at this point, cur is pointing to the last letter of either a new or existing word. if sumFlag: # if the caller wants to add to the existing (0 if new) cur.priority += priority else: # else, the set the priority directly cur.priority = priority self.updateMostCommon(cur) # this will run back through the tree to fix up the mostCommon members # addWord subroutine. # # self : instance of self # wordText : string representing word to add # # Returns: None # # Purpose: Convenience method that wraps setWord. Used to add words known not to exist. def addWord(self,wordText): self.setWord(wordText,priority = 1) # incWord subroutine. # # self : instance of self # wordText : string representing word to add # # Returns: None # # Purpose: Convenience method that wraps setWord. Used to increment the priority of existing words and add new words. # Note: Generally, this method can be used instead of addWord. def incWord(self,wordText): self.setWord(wordText,priority = 1, sumFlag = 1) # setWordPriority subroutine. # # self : instance of self # wordText : string representing word to add # priority: int that is the new priority # # Returns: None # # Purpose: Convenience method that wraps setWord. Sets existing words to priority or adds new words with priority = priority def setWordPriority(self,wordText,priority): self.setWord(wordText,priority = priority) # findWordNode subroutine. # # self : instance of self # wordText : string representing word to add # # Returns: class Letter or None if word isn't found. # # Purpose: Given a word, it returns the class Letter node that corresponds to the word. Used mostly in prep for a call to # getPrediction() def findWordNode(self,wordText): #returns class Letter that represents the last letter in the word cur = self.rootNode # start at the root for ch in wordText: # move through each letter in the word if cur.children.has_key(ch): # if the next letter exists, make cur equal that letter and loop cur = cur.children[ch] else: return None # return None if letter not found return cur # return cur, as this points to the last letter if we got this far # findWordPriority subroutine. # # self : instance of self # wordText : string representing word to add # # Returns: Int representing the word's priority or 0 if not found. # # Purpose: Returns the priority of the given word def findWordPriority(self,wordText): cur = self.findWordNode(wordText) # find the class Letter node that corresponds to this word if cur: return cur.priority # if it was found, return it's priority else: return 0 # else, return 0, meaning word not found # getPrediction subroutine. # # self : instance of self # k : ASCII char that was typed # cur : class Letter that points to the current node in LetterTree # # Returns: The predicted text or "" if none found # # Purpose: This is the meat and potatoes of data structure. It takes the "current" Letter node and the next key typed # and returns it's guess of the rest of the word, based on the highest priority letter in the rest of the branch. def getPrediction(self,k,cur): if cur.children.has_key(k) : # check to see if the key typed is a sub branch # If so, make a prediction. Otherwise, do the else at the bottom of # the method (see below). cur = cur.children[k] # set the cur to the typed key's class Letter in the sub-branch backtrace = cur.mostCommon # backtrace is a class Letter. It's used as a placeholder to back trace # from the last letter of the mostCommon word in the # sub-tree up through the tree until we meet ourself at cur. We'll # build the guess text this way returnText = "" # returnText is a string. This will act as a buffer to hold the string # we build. while cur is not backtrace: # Here's the loop. We loop until we've snaked our way back to cur returnText = backtrace.asciiChar + returnText # Create a new string that is the character at backtrace + everything # so far. So, for "tion" we'll build "n","on","ion","tion" as we .. backtrace = backtrace.parentNode # ... climb back towards cur return returnText # And, having reached here, we've met up with cur, and returnText holds # the string we built. Return it. else: # This is the else to the original if. # If the letter typed isn't in a sub branch, then # the letter being typed isn't in our tree, so return "" # return the empty string # End of class LetterTree! # This class extends wxTextCtrl # # Extends: wxTextCtrl # # Overrides: # wxTextCtrl.__init__(self,parent,id,value,size,style,name) # wxTextCtrl.OnChar(self,Event) # # Defines: # findWord(self,insert,st) class predTextCtrl(orpgTextCtrl): # Initialization subroutine. # # self : instance of self # parent: reference to parent window (wxWindow, me-thinks) # id: new Window Id, default to -1 to create new (I think: see docs for wxPython) # value: String that is the initial value the control holds, defaulting to "" # size: defaults to wxDefaultSize # style: defaults to 0 # name: defaults to "text" # keyHook: must be a function pointer that takes self and a KeyCode object # validator: defaults to None # # Note: These parameters are essentially just passed back to the native wxTextCtrl. # I basically just included (stole) enough of them from chatutils.py to make # it work. Known missing args are pos and validator, which aren't used by # chatutils.py. # # Returns: None # # Purpose: Constructor for predTextCtrl. Calls wxTextCtrl.__init__ to get default init # behavior and then inits a LetterTree and captures the parent for later use in # passing events up the chain. def __init__(self, parent, id = -1, value = "" , size = wxDefaultSize, style = 0, name = "text",keyHook = None, validator=None): # Call super() for default behavior orpgTextCtrl.__init__(self,parent,id = id,value = value,size = size,style = style,name = name, validator = validator ) self.tree = LetterTree("defaultwordlist.txt") # Instantiate a new LetterTree. # TODO: make name of word file an argument. self.parent = parent # Save parent for later use in passing KeyEvents self.cur = self.tree.rootNode # self.cur is a short cut placeholder for typing consecutive chars # It may be vestigal self.keyHook = keyHook # Save the keyHook passed in # findWord subroutine. # # self : instance of self # insert: index of last char in st # st : string from insert to the left # # Note: This implementation is about the third one for this method. Originally, # st was an arbitrary string and insert was the point within # this string to begin looking left. Since, I finally got it # to work right as it is around 2 in the morning, I'm not touching it, for now. # # Returns: String that is the word or "" if none found. This generally doesn't # happen, as st usually ends in a letter, which will be returned. # # Purpose: This function is generally used to figure out the beginning of the # current word being typed, for later use in a LetterTree.getPrediction() def findWord(self,insert,st): # Good luck reading this one. Basically, I started out with an idea, and fiddled with the # constants as best I could until it worked. It's not a piece of work of which I'm too # proud. Basically, it's intent is to check each character to the left until it finds one # that isn't a letter. If it finds such a character, it stops and returns the slice # from that point to insert. Otherwise, it returns the whole thing, due to begin being # initialized to 0 begin = 0 for offset in range(insert - 1): if st[-(offset + 2)] not in string.letters: begin = insert - (offset + 1) break return st[begin:insert] # OnChar subroutine. # # self : instance of self # event: a KeyCode object # # Returns: None # # Purpose: This function is the key event handler for predTextCtrl. It handles what it # needs to and passes the event on to it's parent's OnChar method. def OnChar(self,event): # Before we do anything, call the keyHook handler, if not None # This is currently used to implement the typing/not_typing messages in a manner that # doesn't place the code here. Maybe I should reconsider that. :) if(self.keyHook): if self.keyHook(event) == 1: # if the passed in keyHook routine returns a one, it wants no predictive behavior self.parent.OnChar(event) return # This bit converts the KeyCode() return (int) to a char if it's in a certain range asciiKey = "" if (event.KeyCode() < 256) and (event.KeyCode() > 19): asciiKey = chr(event.KeyCode()) if asciiKey == "": # If we didn't convert it to a char, then process based on the int KeyCodes if event.KeyCode() == WXK_TAB: # We want to hook tabs to allow the user to signify acceptance of a # predicted word. # Handle Tab key fromPos = toPos = 0 # get the current selection range (fromPos,toPos) = self.GetSelection() if (toPos - fromPos) == 0: # if there is no selection, pass tab on self.parent.OnChar(event) return else: # This means at least one char is selected self.SetInsertionPoint(toPos) # move the insertion point to the end of the selection self.SetSelection(toPos,toPos) # and set the selection to no chars # The prediction, if any, had been inserted into the text earlier, so # moving the insertion point to the spot directly afterwards is # equivalent to acceptance. Without this, the next typed key would # clobber the prediction. return # Don't pass tab on in this case elif event.KeyCode() == WXK_RETURN: # We want to hook returns, so that we can update the word list st = self.GetValue() # Grab the text from the control newSt = "" # Init a buffer # This block of code, by popular demand, changes the behavior of the control to ignore any prediction that # hasn't been "accepted" when the enter key is struck. (startSel,endSel) = self.GetSelection() # get the curren selection # # Start update # Changed the following to allow for more friendly behavior in # a multilined predTextCtrl. # # front = st[:startSel] # Slice off the text to the front of where we are # back = st[endSel:] # Slice off the text to the end from where we are # st = front + back # This expression creates a string that get rid of any selected text. # self.SetValue(st) self.Remove( startSel, endSel ) st = string.strip( self.GetValue() ) # # End update # # this loop will walk through every character in st and add it to # newSt if it's a letter. If it's not a letter, (e.g. a comma or # hyphen) a space is added to newSt in it's place. for ch in st: if ch not in string.letters: newSt += " " else: newSt += ch # Now that we've got a string of just letter sequences (words) and spaces # split it and to a LetterTree.incWord on the lowercase version of it. # Reminder: incWord will increment the priority of existing words and add # new ones for aWord in string.split(newSt): self.tree.incWord(string.lower(aWord)) self.parent.OnChar(event) # Now that all of the words are added, pass the event and return return # We want to capture the right arrow key to fix a slight UI bug that occurs when one right arrows # out of a selection. I set the InsertionPoint to the beginning of the selection. When the default # right arrow event occurs, the selection goes away, but the cursor is in an unexpected location. # This snippet fixes this behavior and then passes on the event. elif event.KeyCode() == WXK_RIGHT: (startSel,endSel) = self.GetSelection() self.SetInsertionPoint(endSel) self.parent.OnChar(event) return # Ditto as WXK_RIGHT, but for completeness sake elif event.KeyCode() == WXK_LEFT: (startSel,endSel) = self.GetSelection() self.SetInsertionPoint(startSel) self.parent.OnChar(event) return else: # Handle any other non-ascii events by calling parent's OnChar() self.parent.OnChar(event) #Call super.OnChar to get default behavior return elif asciiKey in string.letters: # This is the real meat and potatoes of predTextCtrl. This is where most of the # wxTextCtrl logic is changed. (startSel,endSel) = self.GetSelection() # get the curren selection st = self.GetValue() # and the text in the control front = st[:startSel] # Slice off the text to the front of where we are back = st[endSel:] # Slice off the text to the end from where we are st = front + asciiKey + back # This expression creates a string that will insert the # typed character (asciiKey is generated at the # beginning of OnChar()) into the text. If there # was text selected, that text will not be part # of the new string, due to the way front and back # were sliced. insert = startSel + 1 # creates an int that denotes where the new InsertionPoint # should be. curWord = "" # Assume there's a problem with finding the curWord if (len(back) == 0) or (back[0] not in string.letters): # We should only insert a prediction if we are typing # at the end of a word, not in the middle. There are # three cases: we are typing at the end of the string or # we are typing in the middle of the string and the next # char is NOT a letter or we are typing in the middle of the # string and the next char IS a letter. Only the former two # cases denote that we should make a prediction # Note: The order of the two logical clauses here is important! # If len(back) == 0, then the expression back[0] will # blow up with an out of bounds array subscript. Luckily # the or operator is a short-circuit operator and in this # case will only evaluate back[0] if len(back) != 0, in # which we're safely in bounds. curWord = self.findWord(insert,front + asciiKey) # Now that we know we're supposed to make a prediction, # let's find what word root to use in our prediction. # Note: This is using the confusing findWord method. I # send it insert and the text from the beginning # of the text through the key just entered. This is # NOT the original usage, but it does work. See # findWord() for more details. else: # Here, we've found we're in the middle of a word, so we're # going to call the parent's event handler. self.parent.OnChar(event) return if curWord == "": # Here, we do a quick check to make sure we have a good root # word. If not, allow the default thing to happen. Of course, # now that I'm documenting this, it occurs to me to wonder why # I didn't do the same thing I just talked about. Hmmmmmm. self.parent.OnChar(event) # we're done here return self.cur = self.tree.findWordNode(string.lower(curWord[:-1])) # Still with me? We're almost done. At this point, we # need to convert our word string to a Letter node, # because that's what getPrediction expects. Notice # that we're feeding in the string with the last # char sliced off. For developmentally historical # reasons, getPrediction wants the node just before # the typed character and the typed char separately. if self.cur is None: self.parent.OnChar(event) # if there's no word or no match, we're done return # get the prediction predictText = self.tree.getPrediction(string.lower(asciiKey),self.cur) # This is the big prediction, as noted above # Note the use of string.lower() because we # keep the word list in all lower case,but we # want to be able to match any capitalization if predictText == "": self.parent.OnChar(event) # if there's no prediction, we're done return # And now for the big finale. We're going to take the string st # we created earlier and insert the prediction right after the # newly typed character. front = st[:insert] # Grab a new front from st back = st[insert:] # Grab a new back st = front + predictText + back # Insert the prediction self.SetValue(st) # Now, overwrite the controls text with the new text self.SetInsertionPoint(insert) # Set the proper insertion point, directly behind the # newly typed character and directly in front of the # predicted text. self.SetSelection(insert,insert+len(predictText)) # Very important! Set the selection to encompass the predicted # text. This way, the user can ignore the prediction by simply # continuing to type. Remember, if the user wants the prediction # s/he must strike the tab key at this point. Of course, one could # just use the right arrow key as well, but that's not as easy to # reach. return # Done! Do NOT pass the event on at this point, because it's all done. else: # Handle every other non-letter ascii (e.g. semicolon) by passing the event on. self.parent.OnChar(event) #Call super.OnChar to get default behavior return # End of class predTextCtrl! --- NEW FILE: config_files.py --- # file: config_files.py # # Author: Todd Faris (Snowdog) # Date: 5/10/2005 # # Misc. config file service methods # import orpg.dirpath import os def validate_config_file(user_file,template_file): #STEP 1: verify the template exists if (not os.path.exists(orpg.dirpath.dir_struct["template"] + template_file)): return 0 #STEP 2: verify the user file exists. If it doesn't then create it from template if (not os.path.exists(orpg.dirpath.dir_struct["user"]+user_file)): default = open(orpg.dirpath.dir_struct["template"] + template_file,"r") file = default.read() newfile = open(orpg.dirpath.dir_struct["user"] + user_file,"w") newfile.write(file) default.close() newfile.close() return 2 #returning 2 (True) so calling method will know if file was created #STEP 3: user file exists (is openable) return 1 indicating no-create operation required else: return 1 --- NEW FILE: __init__.py --- __all__ = ['inittool','inputValidatior', 'orpg_settings' , 'orpg_update', 'predTextCtrl', 'rgbhex', 'scriptkit', 'server_probe', 'toolBars' ] --- NEW FILE: config_update.py --- # file: config_update.py # # Author: Todd Faris (Snowdog) # Date: 5/10/2005 # # Comparison and update functions for openrpg's main config (.ini) file # #----IMPORTS---- from orpg.orpg_xml import * #----CONSTANTS---- AUTOUPDATE_KEY_NAME = "autoupdate_config" #---------------------------------------------------------------------------------------- # UpdateIniFile() # Orchistrates the updating of the users settings ini (ini.xml) file. # Returns number of chances made to memory loaded tree. If non-zero the # tree should be resaved. #---------------------------------------------------... [truncated message content] |
Update of /cvsroot/winopenrpg/openrpg1 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930 Added Files: license.txt platform.py pyver.py readme.txt start.py start.pyw start_server.py start_server_gui.py system_check.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: start.py --- #!/usr/bin/env python import pyver pyver.checkPyVersion() from orpg.orpg_wx import * if WXLOADED: import orpg.main app = orpg.main.orpgApp(0) app.MainLoop() --- NEW FILE: start_server_gui.py --- #!/usr/bin/env python import pyver pyver.checkPyVersion() import orpg.networking.mplay_server_gui import os import sys app = orpg.networking.mplay_server_gui.ServerGUIApp(0) app.MainLoop() --- NEW FILE: platform.py --- #!/usr/local/bin/python """ This module tries to retrieve as much platform identifying data as possible. It makes this information available via function APIs. If called from the command line, it prints the platform information concatenated as single string to stdout. The output format is useable as part of a filename. Note that this module is a fast moving target. I plan to release version 1.0 as the final version. Still needed: · more support for WinCE · support for MS-DOS (PythonDX ?) · support for Amiga and other still unsupported platforms running Python · support for additional Linux distributions Many thanks to all those who helped adding platform specific [...1178 lines suppressed...] if terse: platform = _platform(system,release) else: bits,linkage = architecture(sys.executable) platform = _platform(system,release,machine,processor,bits,linkage) if aliased: _platform_aliased_cache = platform elif terse: pass else: _platform_cache = platform return platform if __name__ == '__main__': # Default is to print the aliased verbose platform string terse = ('terse' in sys.argv or '--terse' in sys.argv) aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv) print platform(aliased,terse) sys.exit(0) --- NEW FILE: system_check.py --- import sys import time import platform from orpg.orpg_wx import * class system_check: def start(self,log_file='openrpg_sysinfo.txt'): self.log_file = open(log_file,'w') self.log_file.write("OpenRPG System Info " + time.strftime( '%d-%m-%y', time.localtime( time.time() ) )) self.check_py() self.check_wxpython() self.check_platform() self.log_file.close() def check_wxpython(self): self.log_file.write("\nwxPython Version: " + wx.__version__) def check_py(self): self.log_file.write("\nPython: " + sys.version) def check_platform(self): self.log_file.write("\nPlatform: " + platform.platform()) if __name__ == "__main__": syscheck = system_check() syscheck.start() --- NEW FILE: pyver.py --- import sys # Needed for version import string # Needed for split from orpg.orpg_version import * # To get NEEDS_PYTHON_MAJOR, MINOR, and MICRO def getNumber(numberstringtoconvert): currentnumberstring = "" for number in numberstringtoconvert: if number >= "0" and number <="9": currentnumberstring += number else: break if currentnumberstring == "": return 0 else: return int(currentnumberstring) # This checks to make sure a certain version of python or later is in use # The actual version requested is set in orpg/openrpg_version def checkPyVersion(): # taking the first split on whitespace of sys.version gives us the version info without the build stuff vernumstring = string.split(sys.version)[0] # This splits the version string into (major,minor,micro). Actually, a complicating factor # is that there sometimes isn't a micro, e.g. 2.0. We'll just do it the hard way to build # the numbers instead of tuple unpacking. splits = string.split(vernumstring,'.') # Assign default values micro = 0 minor = 0 major = 0 # Assign the integer conversion of each, assuming that it was found. If not found, we assumed 0 just above. if len(splits) > 0: major = getNumber(splits[0]) if len(splits) > 1: minor = getNumber(splits[1]) if len(splits) > 2: micro = getNumber(splits[2]) # Check against min version info from orpg/orpg_version if major >= NEEDS_PYTHON_MAJOR: if major > NEEDS_PYTHON_MAJOR: # If it's greater, there's no need to check the minor return if minor >= NEEDS_PYTHON_MINOR: if minor > NEEDS_PYTHON_MINOR: # If it's greater, there's no need to check the micro return if micro >= NEEDS_PYTHON_MICRO: return # If we get here, then the version check failed so we inform the user of the required version and exit print "Invalid python version being used. Detected version %s," % (vernumstring) print "but version %i.%i.%i or better is required!" % (NEEDS_PYTHON_MAJOR,NEEDS_PYTHON_MINOR,NEEDS_PYTHON_MICRO) print "You either have the wrong version of Python installed or you" print "have multiple versions installed. If you have multiple versions," print "please make sure Python %i.%i.%i or better is found first in your path or explicitly" % (NEEDS_PYTHON_MAJOR,NEEDS_PYTHON_MINOR,NEEDS_PYTHON_MICRO) print "start using, \"<path>\python <program>\"." sys.exit( 1 ) --- NEW FILE: start_server.py --- #!/usr/bin/env python import pyver import sys pyver.checkPyVersion() import time import gc import getopt import orpg.networking.mplay_server import orpg.networking.meta_server_lib import traceback # Simple usuage text for request via help or command line errors def usage( retValue ): print sys.argv[0] + " " + \ "[-n Server Name]\n" + \ "[-p]\n" + \ "[-l Lobby Boot Password]\n" + \ "[-r Run From???]\n" + \ "[-h --help]\n\n" + \ "Where -p is used to request meta registration. If -p is given, the boot\n" + \ "password and server name MUST be provided. If no options are given, user\n" + \ "will be prompted for information.\n\n" sys.exit( retValue ) if __name__ == '__main__': lobby_boot_pwd = "" name = "" post = "N" opt="N" gc.set_debug(gc.DEBUG_UNCOLLECTABLE) gc.enable() # See if we have command line arguments in need of processing try: (opts, args) = getopt.getopt( sys.argv[1:], "n:pl:h", "help" ) for o in opts: # Server Name if o[0] in ( "-n", ): name = o[1] # Post server to meta if o[0] in ( "-p", ): post = 'Y' # Lobby Password if o[0] in ( "-l", ): lobby_boot_pwd = o[1] # Help if o[0] in ( "-h", "--help" ): usage( 0 ) except: usage( 1 ) # Now, validate that if we had options passed in, they make sense! If # no options were passed in, follow the normal prompt for information # start up path. If -p is passed in, make sure name and password is # also provided. if len(opts): if (post == 'Y') and ((len(name) == 0) or (len(lobby_boot_pwd) == 0)): usage( 1 ) if (len(lobby_boot_pwd) == 0): lobby_boot_pwd = raw_input("Enter boot password for the Lobby: ") # Only ask if we didn't pass in options and post isn't 'Y' if ( (post == 'N') and (opt == 'N') ): opt = raw_input("Do you want to post your server to the OpenRPG Meta Server list? (y,n)") if opt[0] == 'y' or opt[0] == 'Y': post = 'Y' # If we are going to post make sure we have a server name, if not, ask for it if ( post == 'Y' ) and (len(name) == 0 ): name = raw_input("Server Name?") # start server! orpg_server = orpg.networking.mplay_server.mplay_server() orpg_server.force_check = 1 for index in range(len(sys.argv)-1): if (sys.argv[index] == "-v"): orpg_server.force_check=0 if ( post == 'Y' ) and (len(name)): # Start the registration thread # register servers orpg_server.register( name ) print "-----------------------------------------------------" print "Type 'help' or '?' or 'h' for server console commands" print "-----------------------------------------------------" #orpg_server.print_help() orpg_server.groups['0'].boot_pwd = lobby_boot_pwd opt = "None" try: while (opt != "kill") and ( opt != "quit"): opt = raw_input("action?:") words = opt.split() if opt == "broadcast": msg = raw_input("Message:") orpg_server.broadcast(msg) elif opt == "dump": orpg_server.player_dump() elif opt == "dump groups": orpg_server.groups_list() elif opt == "get lobby boot password": print "Lobby boot password is: " + orpg_server.groups['0'].boot_pwd print elif opt == "register": msg = raw_input("Enter server name: ") orpg_server.register(msg) elif opt == "unregister": orpg_server.unregister() elif opt == "set lobby boot password": lobby_boot_pwd = raw_input("Enter boot password for the Lobby: ") orpg_server.groups['0'].boot_pwd = lobby_boot_pwd elif len(words) == 2 and words[0] == "group": orpg_server.group_dump(words[1]) elif opt == "help" or opt == "?" or opt == "h": orpg_server.print_help() elif opt == "search": msg = raw_input("Pattern:") orpg_server.search(msg) elif opt == "remove room": print "Removing a room will kick everyone in that room off your server." print "You might consider going to that room and letting them know what you are about to do." groupnumber = raw_input("Room group number:") orpg_server.remove_room(groupnumber) elif opt == "uptime": orpg_server.uptime() elif opt == "roompasswords": print orpg_server.RoomPasswords() elif opt == "list": orpg_server.player_list() elif opt == "log": orpg_server.console_log() elif opt == "log meta": orpg_server.toggleMetaLogging() elif len(words) > 0 and words[0] == "logfile": if len(words) > 1: if words[1] == "off": orpg_server.NetworkLogging(0) elif words[1] == "on": orpg_server.NetworkLogging(1) elif words[1] == "split": orpg_server.NetworkLogging(2) else: print "<command useage> logfile [off|on|split]" else: print orpg_server.NetworkLoggingStatus() elif (len(words) > 0 and words[0]) == "monitor": if len(words) >1: print "Attempting to monitor client \""+str(words[1])+"\"" orpg_server.monitor(words[1]) else: print "<command useage> monitor (player id #)" elif opt == "purge clients": try: orpg_server.kick_all_clients() except Exception, e: traceback.print_exc() elif len(words)>0 and words[0] == "zombie": if len(words) > 1: if words[1] == "set": if len(words) > 2: try: t = int(words[2]) orpg_server.zombie_time = t print ("--> Zombie auto-kick time set to "+str(t)+" minutes"); except Exception, e: print "Invalid zombie time!" traceback.print_exc() else: orpg_server.zombie_time = 480 print "--> Zombie auto-kick time set to default (480 mins)"; else: print "<command useage> zombie [set [mins]]" else: timeout = int(orpg_server.zombie_time) print ("--> Zombie auto-kick time set to "+str(timeout)+" minutes. Use \"zombie set [min]\" to change."); elif opt == "kick": kick_id = raw_input("Kick Player # ") kick_msg = raw_input("Reason(optional): ") orpg_server.admin_kick(kick_id,kick_msg) else: if (opt == "kill") or (opt == "quit"): print ("Closing down OpenRPG server. Please wait...") else: print ("[UNKNOWN COMMAND: \""+opt+"\" ]") except Exception, e: print "EXCEPTION: "+str(e) traceback.print_exc() raw_input("press <enter> key to terminate program") orpg_server.kill_server() --- NEW FILE: license.txt --- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. --- NEW FILE: readme.txt --- How to use OpenRPG version 1.6.1 Make sure you have installed python 2.3+ and wxPython 2.4+! (it will probably work with Python2.2 and/or wxPython2.3.0+) Launching OpenRPG: OpenRPG can be launch by executing the start.py script located in the openrpg1 folder. On windows, Macs, and Unix with a GUI, this can be accomplished by double clicking start.py. From a shell, type: python2 start.py Launching a OpenRPG game server: You want to launch your own server execute the start_server.py. For more info on how to use OpenRPG, visit http://www.openrpg.com. -OpenRPG Team --- NEW FILE: start.pyw --- #!/usr/bin/env python import pyver pyver.checkPyVersion() import orpg.main app = orpg.main.orpgApp(0) app.MainLoop() |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:28
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/gametree/nodehandlers In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/gametree/nodehandlers Added Files: StarWarsd20.py __init__.py chatmacro.py containers.py core.py d20.py dnd3e.py forms.py map_miniature_nodehandler.py minilib.py nodehandler_version.py rpg_grid.py voxchat.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: dnd3e.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # [...3573 lines suppressed...] elif id == PP_FRE: self.master_dom.setAttribute('free',evt.GetString()) elif id == PP_MFRE: self.master_dom.setAttribute('maxfree',evt.GetString()) def on_size(self,evt): s = self.GetClientSizeTuple() self.sizer.SetDimension(0,0,s[0],s[1]) #a 5.015 this whole function. def on_refresh(self,attr,value): if attr == 'current1': self.dyn1.SetValue(value) else: self.dyn3.SetValue(value) --- NEW FILE: chatmacro.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: chatmacro.py # Author: Chris Davis # Maintainer: # Version: # $Id: chatmacro.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: The file contains code for the form based nodehanlers # __version__ = "$Id: chatmacro.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from core import * ########################## ## text node handler ########################## class macro_handler(node_handler): """ A nodehandler for text blocks. Will open text in a text frame <nodehandler name='?' module='chatmacro' class='macro_handler'> <text>some text here</text> </nodehandler > """ def __init__(self,xml_dom,tree_node,openrpg): node_handler.__init__(self,xml_dom,tree_node,openrpg) self.text_elem = self.master_dom.getElementsByTagName('text')[0] self.text = safe_get_text_node(self.text_elem) def set_text(self,txt): self.text._set_nodeValue(txt) def on_use(self,evt): txt = self.text._get_nodeValue() actionlist = txt.split("\n") for line in actionlist: if(line != ""): if line[0] != "/": ## it's not a slash command action = self.chat.ParsePost(self.chat.colorize(self.chat.mytextcolor, line),true,true) else: action = line self.chat.chat_cmds.docmd(action) return 1 def get_design_panel(self,parent): return macro_edit_panel(parent,self) def tohtml(self): title = self.master_dom.getAttribute("name") txt = self.text._get_nodeValue() txt = string.replace(txt,'\n',"<br>") return "<P><b>"+title+":</b><br>"+txt P_TITLE = wxNewId() P_BODY = wxNewId() B_CHAT = wxNewId() class macro_edit_panel(wxBoxedSizer): def __init__(self, parent, handler): wxBoxedSizer.__init__(self, parent, "Chat Macro") self.handler = handler self.text = { P_TITLE : orpgTextCtrl(self, P_TITLE, handler.master_dom.getAttribute('name')), P_BODY : orpgTextCtrl(self,P_BODY,handler.text._get_nodeValue(),style=wxTE_MULTILINE) } #P_BODY : wxTextCtrl(self, P_BODY,handler.text._get_nodeValue(), style=wxTE_MULTILINE) sizer = wxBoxSizer(wxVERTICAL) sizer.Add(wxStaticText(self, -1, "Title:"), 0, wxEXPAND) sizer.Add(self.text[P_TITLE], 0, wxEXPAND) sizer.Add(wxStaticText(self, -1, "Text Body:"), 0, wxEXPAND) sizer.Add(self.text[P_BODY], 1, wxEXPAND) sizer.Add(wxButton(self, B_CHAT, "Send To Chat"),0,wxEXPAND) EVT_SIZE(self, self.on_size) EVT_TEXT(self, P_TITLE, self.on_text) EVT_TEXT(self, P_BODY, self.on_text) EVT_BUTTON(self, B_CHAT, self.handler.on_use) self.set_sizer(sizer) #EVT_TEXT(self, P_BODY, self.on_text) def on_text(self,evt): id = evt.GetId() txt = self.text[id].GetValue() if txt == "": return if id == P_TITLE: self.handler.master_dom.setAttribute('name',txt) self.handler.rename(txt) elif id == P_BODY: self.handler.set_text(txt) --- NEW FILE: voxchat.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # [...1198 lines suppressed...] # ## PAGE DOWN # elif event.KeyCode() == WXK_NEXT and event.ControlDown(): # if self.bufferpointer > 0: # self.bufferpointer = self.bufferpointer - self.buffersize # # self.Post() # self.do_chat_action( None ) # else: # event.Skip() # # ## END # elif event.KeyCode() == WXK_END and event.ControlDown(): # self.bufferpointer = 0 # self.do_chat_action( None ) # # self.Post() # event.Skip() ## NOTHING else: event.Skip() # def OnChar - end --- NEW FILE: nodehandler_version.py --- ### this file holds the nodehandler version ### NODEHANDLER_VERSION = "1.0" --- NEW FILE: core.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: core.py # Author: Chris Davis # Maintainer: # Version: # $Id: core.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: The file contains code for the core nodehanlers # __version__ = "$Id: core.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from nodehandler_version import NODEHANDLER_VERSION from orpg.orpg_windows import * import orpg.dirpath from orpg.orpg_xml import * import webbrowser from orpg.mapper import map import os #html defaults TH_BG = "#E9E9E9" ########################## ## base node handler ########################## class node_handler: """ Base nodehandler with virtual functions and standard implmentations """ def __init__(self,xml_dom,tree_node,openrpg): self.master_dom = xml_dom self.mytree_node = tree_node self.myopenrpg = openrpg self.tree = openrpg.get_component('tree') self.frame = openrpg.get_component('frame') self.chat = openrpg.get_component('chat') self.drag = true self.myeditor = None # designing self.myviewer = None # prett print self.mywindow = None # using # call version hook self.on_version(self.master_dom.getAttribute("version")) # set to current version self.master_dom.setAttribute("version",NODEHANDLER_VERSION) # null events def on_version(self,old_version): ## added version control code here or implement a new on_version in your derived class. ## always call the base class on_version ! pass def on_rclick(self,evt): self.tree.do_std_menu(evt,self) def on_ldclick(self,evt): return 0 def usefulness(self,text): if text=="useful": self.master_dom.setAttribute('status',"useful") elif text=="useless": self.master_dom.setAttribute('status',"useless") elif text=="indifferent": self.master_dom.setAttribute('status',"indifferent") def on_design(self,evt): if self.myeditor == None: del self.myeditor self.create_designframe() try: if self.myeditor.destroyed: del self.myeditor self.create_designframe() except: self.create_designframe() self.myeditor.Show(1) self.myeditor.Raise() def create_designframe(self): title = self.master_dom.getAttribute('name') + " Editor" self.myeditor = wxPFrame(self.frame,title,orpg.dirpath.dir_struct["icon"]+'grid.ico') self.myeditor.panel = self.get_design_panel(self.myeditor) self.myeditor.Show(1) self.myeditor.Raise() def on_use(self,evt): if self.mywindow == None: del self.mywindow self.create_useframe() try: if self.mywindow.destroyed: del self.mywindow self.create_useframe() except: self.create_useframe() self.mywindow.Show(1) self.mywindow.Raise() def create_useframe(self): caption = self.master_dom.getAttribute('name') self.mywindow = wxPFrame(self.frame, caption, orpg.dirpath.dir_struct["icon"] + 'note.ico') self.mywindow.panel = self.get_use_panel(self.mywindow) self.mywindow.Show(1) self.mywindow.Raise() def on_html_view(self,evt): try: self.myviewer.Raise() except: caption = self.master_dom.getAttribute('name') self.myviewer = wxPFrame(self.frame,caption,orpg.dirpath.dir_struct["icon"]+'note.ico') self.myviewer.panel = self.get_html_panel(self.myviewer) self.myviewer.Show(1) def map_aware(self): return 0 def can_clone(self): return 1; def on_del(self,evt): print "on del" def on_new_data(self,xml_dom): pass def get_scaled_bitmap(self,x,y): return None # def is_my_parent(self,parent_node,compare_node): # parent_node = self.tree.GetItemParent(parent_node) # if compare_node == parent_node: # return 1 # elif parent_node == self.tree.root: # return 0 # else: # return self.is_my_parent(compare_node,parent_node) # def is_my_child(self,compare_node,parent_node): # x = self.tree.GetItemParent(compare_node) # while true: # if self.tree.GetItemText(x) == self.tree.GetItemText(parent_node): # return 1 # x = self.tree.GetItemParent(x) # if len(self.tree.GetItemText(x)) < 1: # break # return 0 def on_send_to_map(self,evt): pass def on_send_to_chat(self,evt): self.chat.ParsePost(self.tohtml(),true,true) def on_drop(self,evt): drag_obj = self.tree.drag_obj if drag_obj == self or self.tree.is_parent_node(self.mytree_node,drag_obj.mytree_node): return #if self.is_my_child(self.mytree_node,drag_obj.mytree_node): # return xml_dom = self.tree.drag_obj.delete() parent = self.master_dom._get_parentNode() xml_dom = parent.insertBefore(xml_dom,self.master_dom) parent_node = self.tree.GetItemParent(self.mytree_node) prev_sib = self.tree.GetPrevSibling(self.mytree_node) self.tree.load_xml(xml_dom, parent_node, prev_sib) def toxml(self,pretty=0): return toxml(self.master_dom,pretty) def tohtml(self): return self.master_dom.getAttribute("name") def delete(self): """ removes the tree_node and xml_node, and returns the removed xml_node """ self.tree.Delete(self.mytree_node) parent = self.master_dom._get_parentNode() return parent.removeChild(self.master_dom) def rename(self,name): if len(name): self.tree.SetItemText(self.mytree_node,name) def change_icon(self,icon): self.master_dom.setAttribute("icon",icon) self.tree.SetItemImage(self.mytree_node,self.tree.icons[icon]) self.tree.SetItemSelectedImage(self.mytree_node,self.tree.icons[icon]) def on_save(self,evt): f =wxFileDialog(self.tree,"Select a file", orpg.dirpath.dir_struct["user"],"","XML files (*.xml)|*.xml",wxSAVE) if f.ShowModal() == wxID_OK: type = f.GetFilterIndex() file = open(f.GetPath(),"w") file.write(self.toxml(1)) file.close() f.Destroy() def get_design_panel(self,parent): return None def get_use_panel(self,parent): return None def get_html_panel(self,parent): html_str = "<html><body bgcolor=\"#FFFFFF\" >"+self.tohtml()+"</body></html>" wnd = wxHTMLpanel(parent,-1) html_str = self.chat.ParseDice(html_str) wnd.load_text(html_str) return wnd def get_size_constraint(self): return 0 def about(self): html_str = "<b>"+ self.master_dom.getAttribute('class') html_str += " Applet</b><br>by Chris Davis<br>ch...@rp..." return html_str # All the functions below are foe backward compatiablity ! def old_group_xml(xml_dom,tree_node,openrpg): import containers xml_dom.setAttribute("class","group_handler") xml_dom.setAttribute("module","containers") return containers.group_handler(xml_dom,tree_node,openrpg) static_handler = old_group_xml def old_text_xml(xml_dom,tree_node,openrpg): old_text = safe_get_text_node(xml_dom) elem = minidom.Element('text') elem.setAttribute("multiline","1") t_node = minidom.Text(old_text._get_nodeValue()) t_node = elem.appendChild(t_node) text = safe_get_text_node(elem) xml_dom.appendChild(elem) old_text._set_nodeValue("") xml_dom.setAttribute("class","textctrl_handler") xml_dom.setAttribute("module","forms") import forms return forms.textctrl_handler(xml_dom,tree_node,openrpg) text_handler = old_text_xml dieroll_handler = old_text_xml def old_macro_xml(xml_dom,tree_node,openrpg): old_text = safe_get_text_node(xml_dom) elem = minidom.Element('text') t_node = minidom.Text(old_text._get_nodeValue()) t_node = elem.appendChild(t_node) text = safe_get_text_node(elem) xml_dom.appendChild(elem) old_text._set_nodeValue("") xml_dom.setAttribute("class","macro_handler") xml_dom.setAttribute("module","chatmacro") import chatmacro return chatmacro.macro_handler(xml_dom,tree_node,openrpg) macro_handler = old_macro_xml def old_link_xml(xml_dom,tree_node,openrpg): xml_dom.setAttribute("class","link_handler") xml_dom.setAttribute("module","forms") import forms return forms.link_handler(xml_dom,tree_node,openrpg) link_handler = old_link_xml webbrowser_handler = old_link_xml def old_webimg_xml(xml_dom,tree_node,openrpg): xml_dom.setAttribute("class","webimg_handler") xml_dom.setAttribute("module","forms") import forms return forms.webimg_handler(xml_dom,tree_node,openrpg) webimg_handler = old_webimg_xml # ########################## # ## link node handler # ########################## # class link_handler(node_handler): # """ A nodehandler for URLs. Will open URL in a wxHTMLFrame # <nodehandler name='?' module='core' class='link_handler' > # <link href='http//??.??' /> # </nodehandler > # """ # def __init__(self,xml_dom,tree_node,openrpg): # node_handler.__init__(self,xml_dom,tree_node,openrpg) # self.link = self.master_dom._get_firstChild() # self.wnd = None # self.frame = openrpg.get_component('frame') # def on_use(self,evt): # href = self.link.getAttribute("href") # title = self.master_dom.getAttribute("name") # self.myframe = wxPFrame(self.frame,title,orpg.dirpath.dir_struct["icon"] + 'note.ico') # wnd = wxHTMLpanel(self.myframe,-1) # self.myframe.panel = wnd # wnd.load_url(href) # self.myframe.Show(1) # def on_design(self,evt): # tlist = ['Title','href'] # vlist = [self.master_dom.getAttribute("name"), # self.link.getAttribute("href")] # dlg = wxMultiTextEntry(self.tree.GetParent(),tlist,vlist,"Link Edit") # if dlg.ShowModal() == wxID_OK: # vlist = dlg.get_values() # self.link.setAttribute('href', vlist[1]) # self.master_dom.setAttribute('name', vlist[0]) # self.tree.SetItemText(self.mytree_node,vlist[0]) # dlg.Destroy() # def get_design_panel(self,parent): # return None # def tohtml(self): # href = self.link.getAttribute("href") # title = self.master_dom.getAttribute("name") # return "<a href=\""+href+"\" >"+title+"</a>" # ########################## # ## webimg node handler # ########################## # class webimg_handler(link_handler): # """ A nodehandler for URLs. Will open URL in a wxHTMLFrame # <nodehandler name='?' module='core' class='webimg_handler' > # <link href='http//??.??' /> # </nodehandler > # """ # def __init__(self,xml_dom,tree_node,openrpg): # link_handler.__init__(self,xml_dom,tree_node,openrpg) # def on_use(self,evt): # href = self.link.getAttribute("href") # title = self.master_dom.getAttribute("name") # self.myframe = wxPFrame(self.frame,title,orpg.dirpath.dir_struct["icon"] + 'note.ico') # wnd = scrolled_img_panel(self.myframe,-1) # self.myframe.panel = wnd # wnd.load_url(href) # self.wnd = wnd # self.myframe.Show(1) # def tohtml(self): # href = self.link.getAttribute("href") # title = self.master_dom.getAttribute("name") # return "<img src=\""+href+"\" alt="+title+" >" # ########################## # ## webbrowser node handler # ########################## # class webbrowser_handler(link_handler): # """ A nodehandler for webbroser URLs. Will open URL in the # default webbrowser # <nodehandler name='?' module='core' class='webbrowser_handler' > # <link href='http//??.??' /> # </nodehandler > # """ # def __init__(self,xml_dom,tree_node,openrpg): # link_handler.__init__(self,xml_dom,tree_node,openrpg) # def on_use(self,evt): # href = self.link.getAttribute("href") # wb = webbrowser.get() # wb.open(href) # # def tohtml(self): # href = self.link.getAttribute("href") # title = self.master_dom.getAttribute("name") # return "<a href=\""+href+"\" >"+title+"</a>" # # ########################## # ## text node handler # ########################## # class text_handler2(node_handler): # """ A nodehandler for text blocks. Will open text in a text frame # <nodehandler name='?' module='core' class='text_handler'> # some text here # </nodehandler > # """ # def __init__(self,xml_dom,tree_node,openrpg): # node_handler.__init__(self,xml_dom,tree_node,openrpg) # self.text = safe_get_text_node(self.master_dom) # self.wnd = None # self.frame = openrpg.get_component('frame') # self.myeditor = None # def on_change(self,txt): # self.text._set_nodeValue(txt) # # def on_design(self,evt): # if self.myeditor == None or self.myeditor.destroyed: # title = self.master_dom.getAttribute('name') + " Editor" # self.myeditor = wxPFrame(self.frame,title,orpg.dirpath.dir_struct["icon"]+ 'note.ico') # wnd = text_edit_panel(self.myeditor,self) # self.myeditor.panel = wnd # self.wnd = wnd # self.myeditor.Show(1) # else: # self.myeditor.Raise() # def get_design_panel(self,parent): # return text_edit_panel(parent,self) # # def tohtml(self): # title = self.master_dom.getAttribute("name") # txt = self.text._get_nodeValue() # txt = string.replace(txt,'\n',"<br>") # return "<P><b>"+title+":</b><br>"+txt P_TITLE = 10 P_BODY = 20 class text_edit_panel(wxPanel): def __init__(self, parent, handler): wxPanel.__init__(self, parent, -1) self.handler = handler sizer = wxBoxSizer(wxVERTICAL) self.text = { P_TITLE : orpgTextCtrl(self, P_TITLE, handler.master_dom.getAttribute('name')), P_BODY : html_text_edit(self,P_BODY,handler.text._get_nodeValue(),self.on_text) } #P_BODY : wxTextCtrl(self, P_BODY,handler.text._get_nodeValue(), style=wxTE_MULTILINE) sizer.Add(wxStaticText(self, -1, "Title:"), 0, wxEXPAND) sizer.Add(self.text[P_TITLE], 0, wxEXPAND) sizer.Add(wxStaticText(self, -1, "Text Body:"), 0, wxEXPAND) sizer.Add(self.text[P_BODY], 1, wxEXPAND) self.sizer = sizer self.outline = wxStaticBox(self,-1,"Text Block") EVT_SIZE(self, self.on_size) EVT_TEXT(self, P_TITLE, self.on_text) #EVT_TEXT(self, P_BODY, self.on_text) def on_text(self,evt): id = evt.GetId() if id == P_TITLE: txt = self.text[id].GetValue() # The following block strips out 8-bit characters u_txt = "" bad_txt_found = 0 for c in txt: if ord(c) < 128: u_txt += c else: bad_txt_found = 1 if bad_txt_found: wxMessageBox("Some non 7-bit ASCII characters found and stripped","Warning!") txt = u_txt if txt != "": self.handler.master_dom.setAttribute('name',txt) self.handler.rename(txt) elif id == P_BODY: txt = self.text[id].get_text() u_txt = "" bad_txt_found = 0 for c in txt: if ord(c) < 128: u_txt += c else: bad_txt_found = 1 if bad_txt_found: wxMessageBox("Some non 7-bit ASCII characters found and stripped","Warning!") txt = u_txt self.handler.text._set_nodeValue(txt) def on_size(self,evt): s = self.GetClientSizeTuple() self.sizer.SetDimension(20,20,s[0]-40,s[1]-40) self.outline.SetDimensions(5,5,s[0]-10,s[1]-10) # ########################## # ## macro node handler # ########################## # class macro_handler(text_handler2): # """ A nodehandler for text blocks. Will open text in a text frame # <nodehandler name='?' module='core' class='macro_handler'> # a line of text to be macro'ed here # another line of text to be macro'ed # </nodehandler > # """ # # def on_ldclick(self,evt): # txt = self.text._get_nodeValue() # actionlist = txt.split("\n") # for line in actionlist: # if(line != ""): # if line[0] != "/": ## it's not a slash command # action = self.chat.ParsePost(self.chat.colorize(self.chat.mytextcolor, line),true,true) # else: # action = self.chat.ParseDice(line) # self.chat.emote.docmd(line) # return 1 # ########################## # ## dice macro node handler # ########################## # class dieroll_handler(text_handler2): # """ A nodehandler for text blocks. Will open text in a text frame # <nodehandler name='?' module='core' class='dieroll_handler'> # attack roll [1d20+4] # </nodehandler > # """ # def __init__(self,xml_dom,tree_node,openrpg): # text_handler2.__init__(self,xml_dom,tree_node,openrpg) # self.text = safe_get_text_node(self.master_dom) # def tohtml(self): # title = self.master_dom.getAttribute("name") # txt = self.text._get_nodeValue() # txt = string.replace(txt,'\n',"<br>") # return txt ########################## ## node loader ########################## class node_loader(node_handler): """ clones childe node and insert it at top of tree <nodehandler name='?' module='core' class='node_loader' /> """ def __init__(self,xml_dom,tree_node,openrpg): node_handler.__init__(self,xml_dom,tree_node,openrpg) def on_rclick(self,evt): pass def on_ldclick(self,evt): title = self.master_dom.getAttribute('name') new_node = self.master_dom._get_firstChild() new_node = new_node.cloneNode(true) child = self.tree.master_dom._get_firstChild() new_node = self.tree.master_dom.insertBefore(new_node,child) tree_node = self.tree.load_xml(new_node,self.tree.root,self.tree.root) obj = self.tree.GetPyData(tree_node) return 1 #obj.on_design(None) ########################## ## file loader ########################## class file_loader(node_handler): """ loads file and insert into game tree <nodehandler name='?' module='core' class='file_loader' > <file name="file_name.xml" /> </nodehandler> """ def __init__(self,xml_dom,tree_node,openrpg): node_handler.__init__(self,xml_dom,tree_node,openrpg) self.file_node = self.master_dom._get_firstChild() self.frame = openrpg.get_component('frame') def on_ldclick(self,evt): file_name = self.file_node.getAttribute("name") self.tree.insert_xml(open(orpg.dirpath.dir_struct["addon"] + file_name,"r").read()) return 1 def on_design(self,evt): tlist = ['Title','File Name'] vlist = [self.master_dom.getAttribute("name"), self.file_node.getAttribute("name")] dlg = wxMultiTextEntry(self.tree.GetParent(),tlist,vlist,"File Loader Edit") if dlg.ShowModal() == wxID_OK: vlist = dlg.get_values() self.file_node.setAttribute('name', vlist[1]) self.master_dom.setAttribute('name', vlist[0]) self.tree.SetItemText(self.mytree_node,vlist[0]) dlg.Destroy() ########################## ## URL loader ########################## class url_loader(node_handler): """ loads file from url and insert into game tree <nodehandler name='?' module='core' class='url_loader' > <file name="http://file_name.xml" /> </nodehandler> """ def __init__(self,xml_dom,tree_node,openrpg): node_handler.__init__(self,xml_dom,tree_node,openrpg) self.file_node = self.master_dom._get_firstChild() self.frame = openrpg.get_component('frame') def on_ldclick(self,evt): file_name = self.file_node.getAttribute("url") file = urllib.urlopen(file_name) self.tree.insert_xml(file.read()) return 1 def on_design(self,evt): tlist = ['Title','URL'] print "design filename",self.master_dom.getAttribute('name') vlist = [self.master_dom.getAttribute("name"), self.file_node.getAttribute("url")] dlg = wxMultiTextEntry(self.tree.GetParent(),tlist,vlist,"File Loader Edit") if dlg.ShowModal() == wxID_OK: vlist = dlg.get_values() self.file_node.setAttribute('url', vlist[1]) self.master_dom.setAttribute('name', vlist[0]) self.tree.SetItemText(self.mytree_node,vlist[0]) dlg.Destroy() ########################## ## minature map loader ########################## class min_map(node_handler): """ clones childe node and insert it at top of tree <nodehandler name='?' module='core' class='min_map' /> """ def __init__(self,xml_dom,tree_node,openrpg): node_handler.__init__(self,xml_dom,tree_node,openrpg) self.map = openrpg.get_component('map') self.mapdata = self.master_dom._get_firstChild() def on_ldclick(self,evt): self.map.new_data(toxml(self.mapdata)) return 1 --- NEW FILE: minilib.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: minilib.py # Author: Ted Berg # Maintainer: # Version: # $Id: minilib.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: nodehandler for a collection of miniatures. # __version__ = "$Id: minilib.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" """Nodehandler for collections of miniatures. User can add, delete, edit miniatures as sending them to the map singly or in batches. """ from core import * import orpg.dirpath import string import map_miniature_nodehandler import orpg.mapper.map_msg # import scriptkit # GUI Constants # DLG_FRAME = 1 # DLG_DIALOG = 2 # DLG_MYMINIFRAME = 3 # DLG_DEFAULT = 4 LISTBOX_ID = 10 MINICOUNT_ID = 20 OK_BUTTON = 30 CANCEL_BUTTON = 40 LAYER_MINIATURES='miniatures' # Constants TO_MINILIB_MAP = { 'path' : 'url', 'label' : 'name', 'id' : None, 'action':None } FROM_MINILIB_MAP = { 'url' : 'path', 'name' : 'label', 'unique' : None, } CORE_ATTRIBUTES = [ 'name', 'url', 'unique', 'posy', 'posx', 'hide', 'face', 'heading', 'align', 'locked', 'width', 'height', ] ATTRIBUTE_NAME = 'name' ATTRIBUTE_URL = 'url' ATTRIBUTE_UNIQUE = 'unique' ATTRIBUTE_ID = 'id' ATTRIBUTE_POSX = 'posx' ATTRIBUTE_POSY = 'posy' TAG_MINIATURE = 'miniature' COMPONENT_MAP = 'map' COMPONENT_SESSION = 'session' # <nodehandler name='?' module='minilib' class='minilib_handler'> # <miniature name='?' url='?' unique='?'></miniature> # </nodehandler> class minilib_handler( node_handler ): """A nodehandler that manages a collection of miniatures for the map. <pre> <nodehandler name='?' module='minilib' class='minilib_handler'> <miniature name='?' url='?' unique='?'></miniature> </nodehandler> </pre> """ def __init__( self, xml_dom, tree_node, openrpg ): """Instantiates the class, and sets all vars to their default state """ node_handler.__init__( self, xml_dom, tree_node, openrpg ) self.openrpg = openrpg self.myeditor = None self.mywindow = None self.tree_node = tree_node # self.xml_dom = xml_dom self.update_leaves() self.sanity_check_nodes() def get_design_panel( self, parent ): """returns an instance of the miniature library edit control ( see on_design ). This is for use with the the 'edit multiple nodes in a single frame' code. """ return minpedit( parent, self ) def get_use_panel( self, parent ): """returns an instance of the miniature library view control ( see on_use ). This is for use with the the 'view multiple nodes in a single frame' code. """ return minilib_use_panel( parent, self ) def tohtml( self ): """Returns an HTML representation of this node in string format. The table columnwidths are currently being forced, as the wxHTML widgets being used don't handle cells wider than the widgets are expecting for a given column. """ str = '<table border="2" >' list = self.master_dom.getElementsByTagName(TAG_MINIATURE) str += "<tr><th width='20%'>Label</th><th>Image</th><th width='65%'>URL</th><th>Unique</th></t>" for mini in list: url = mini.getAttribute(ATTRIBUTE_URL) label = mini.getAttribute(ATTRIBUTE_NAME) flag = 0 try: flag = eval( mini.getAttribute(ATTRIBUTE_UNIQUE) ) except: pass show = 'yes' if flag: show = 'no' str += """<tr> <td> %s </td> <td><img src="%s"></td> <td> %s </td> <td> %s </td> </tr>""" % ( label, url, url, show ) str += "</table>" print str return str def html_view( self ): """see to_html """ return self.tohtml() def on_drop( self, evt ): drag_obj = self.tree.drag_obj if drag_obj == self or self.tree.is_parent_node( self.mytree_node, drag_obj.mytree_node ): return if isinstance( drag_obj, minilib_handler ): item = self.tree.GetSelection() name = self.tree.GetItemText( item ) if isinstance( drag_obj, map_miniature_nodehandler.map_miniature_handler ): xml_dom = self.tree.drag_obj.master_dom#.delete() obj = xml_dom.firstChild print obj.getAttributeKeys() dict = {} unique = '' for attrib in obj.getAttributeKeys(): key = TO_MINILIB_MAP.get( attrib, attrib ) if key != None: dict[ key ] = obj.getAttribute( attrib ) # if dict[ ATTRIBUTE_NAME ][-1] in string.digits: # unique = '1' # while dict[ ATTRIBUTE_NAME ][-1] in string.digits: # dict[ ATTRIBUTE_NAME ] = dict[ ATTRIBUTE_NAME ][:-1] dict[ ATTRIBUTE_UNIQUE ] = unique self.new_mini( dict ) def new_mini( self, data={}, add=1 ): mini = minidom.Element( TAG_MINIATURE ) for key in data.keys(): mini.setAttribute( key, data[ key ] ) for key in CORE_ATTRIBUTES: if mini.getAttribute( key ) == '': mini.setAttribute( key, '0' ) if add: self.add_mini( mini ) self.add_leaf( mini ) return mini def add_mini( self, mini ): self.master_dom.appendChild( mini ) def add_leaf( self, mini, icon='gear' ): tree = self.tree icons = tree.icons key = mini.getAttribute( ATTRIBUTE_NAME ) self.mydata.append( mini ) # new_tree_node = tree.AppendItem( self.mytree_node, key, icons[ icon ], icons[ icon ] ) # handler = mini_handler( mini, new_tree_node, self.myopenrpg, self ) # tree.SetPyData( new_tree_node, handler ) def update_leaves( self ): self.mydata = [] nl = self.master_dom.getElementsByTagName( TAG_MINIATURE ) for n in nl: self.add_leaf( n ) def on_drag( self, evt ): print 'drag event caught' def send_mini_to_map( self, mini, count=1 ): if mini == None: return if mini.getAttribute( ATTRIBUTE_URL ) == '' or mini.getAttribute( ATTRIBUTE_URL ) == 'http://': self.chat.ParsePost( self.chat.colorize(self.chat.syscolor, '"%s" is not a valid URL, the mini "%s" will not be added to the map' % ( mini.getAttribute( ATTRIBUTE_URL ), mini.getAttribute( ATTRIBUTE_NAME ) )) ) return session = self.myopenrpg.get_component( COMPONENT_SESSION ) if (session.my_role() <> session.ROLE_GM) and (session.my_role()<>session.ROLE_PLAYER): self.myopenrpg.get_component("chat").InfoPost("You must be either a player or GM to use the miniature Layer") return map = self.myopenrpg.get_component( COMPONENT_MAP ) for loop in range( count ): msg = self.get_miniature_XML( mini ) msg = str("<map action='update'><miniatures>" + msg + "</miniatures></map>") map.new_data( msg ) session.send( msg ) def get_miniature_XML( self, mini ): msg = orpg.mapper.map_msg.mini_msg() map = self.myopenrpg.get_component( COMPONENT_MAP ) session = self.myopenrpg.get_component( COMPONENT_SESSION ) msg.init_prop( ATTRIBUTE_ID, session.get_next_id() ) for k in mini.getAttributeKeys(): # translate our attributes to map attributes key = FROM_MINILIB_MAP.get( k, k ) if key != None: msg.init_prop( key, mini.getAttribute( k ) ) unique = self.is_unique( mini ) label = mini.getAttribute( ATTRIBUTE_NAME ) # use_serial = map.canvas.use_serial # auto_label = map.canvas..auto_label # if auto_label: # if use_serial: # label = '%s %d' % ( label, map.canvas.layers[ LAYER_MINIATURE ].next_serial() ) # msg.set_prop( ATTRIBUTE_NAME, label ) # else: # msg.set_prop( ATTRIBUTE_NAME, '' ) return msg.get_all_xml() def is_unique( self, mini ): unique = mini.getAttribute( ATTRIBUTE_UNIQUE ) val = 0 try: val = eval( unique ) except: val = len( unique ) return val def sanity_check_nodes( self ): nl = self.master_dom.getElementsByTagName( TAG_MINIATURE ) for node in nl: if node.getAttribute( ATTRIBUTE_POSX ) == '': node.setAttribute( ATTRIBUTE_POSX, '0' ) if node.getAttribute( ATTRIBUTE_POSY ) == '': node.setAttribute( ATTRIBUTE_POSY, '0' ) def get_mini( self, index ): try: nl = self.master_dom.getElementsByTagName( TAG_MINIATURE ) return nl[ index ] except: return None class mini_handler( node_handler ): def __init__( self, xml_dom, tree_node, openrpg, handler ): node_handler.__init__( self, xml_dom, tree_node, openrpg ) self.handler = handler def on_ldclick( self, evt ): self.handler.send_mini_to_map( self.master_dom ) def on_drop( self, evt ): pass def on_lclick( self, evt ): print 'hi' evt.Skip() class minilib_use_panel( wxPanel ): """This panel will be displayed when the user double clicks on the miniature library node. It is a sorted listbox of miniature labels, a text field for entering a count ( for batch adds ) and 'add'/'done' buttons. """ def __init__( self, frame, handler ): """Constructor. """ wxPanel.__init__( self, frame, -1 ) self.handler = handler self.frame = frame self.map = self.handler.openrpg.get_component('map') names = self.buildList() # self.keys = self.list.keys() # self.keys.sort() s = self.GetClientSizeTuple() self.sizer = wxBoxSizer( wxVERTICAL ) box = wxBoxSizer( wxHORIZONTAL ) self.listbox = wxListBox( self, LISTBOX_ID, ( 10, 10 ), (s[0] - 10, s[1] - 30 ), names, wxLB_SINGLE ) self.count = wxTextCtrl( self, MINICOUNT_ID, '1' ) box.Add( wxStaticText( self, -1, 'Minis to add' ), 0, wxEXPAND ) box.Add(wxSize(10,10)) box.Add( self.count, 1, wxEXPAND ) self.sizer.Add( self.listbox, 1, wxEXPAND ) self.sizer.Add( box, 0, wxEXPAND ) box = wxBoxSizer( wxHORIZONTAL ) box.Add( wxButton( self, OK_BUTTON, 'Add' ), 0, wxEXPAND ) box.Add( wxButton( self, CANCEL_BUTTON, 'Done' ), 0, wxEXPAND ) self.sizer.Add(wxSize(10,10)) self.sizer.Add( box, 0, wxEXPAND ) EVT_SIZE( self, self.on_size ) EVT_BUTTON( self, OK_BUTTON, self.on_ok ) EVT_BUTTON( self, CANCEL_BUTTON, self.on_cancel ) self.SetSizer(self.sizer) def buildList( self ): """Returns a dictionary of label => game tree miniature DOM node mappings. """ list = self.handler.master_dom.getElementsByTagName(TAG_MINIATURE) self.list = [] for mini in list: self.list.append( mini.getAttribute( ATTRIBUTE_NAME ) ) return self.list # self.list = {} # for mini in list: # name = mini.getAttribute( ATTRIBUTE_NAME ) # if name == '': # name = self.map.canvas.get_label_from_url( mini.getAttribute( ATTRIBUTE_URL ) ) # self.list[ name ] = mini def on_size( self, evt ): """Adjusts the sizer dimensions. Is this really necessary? """ s = self.GetClientSizeTuple() self.sizer.SetDimension( 10, 10, s[0] - 20 , s[1] - 20 ) def on_ok( self, evt ): """Event handler for the 'add' button. """ #key = self.keys[ self.listbox.GetSelection() ] index = self.listbox.GetSelection() # nl = self.handler.master_dom.getElementsByTagName( TAG_MINIATURE ) # name = nl[ index ].getAttribute( ATTRIBUTE_NAME ) # url = nl[ index ].getAttribute( ATTRIBUTE_URL ) # unique = nl[ index ].getAttribute( ATTRIBUTE_UNIQUE ) #url = self.list[ key ].getAttribute( ATTRIBUTE_URL ) #unique = self.list[ key ].getAttribute( ATTRIBUTE_UNIQUE ) try: count = eval( self.count.GetValue() ) except: count = 1 try: if eval( unique ): count = 1 unique = eval( unique ) except: pass self.handler.send_mini_to_map( self.handler.get_mini( index ), count ) # for loop in range( 0, count ): # self.handler.send_mini_to_map( nl[ index ] ) # #self.map.canvas.add_miniature( url, name, unique ) def on_cancel( self, evt ): """Event handler for 'done' button. Calls wxPFrame.OnCloseWindow so that all proper frame closing details are taken care of. """ wxPFrame.OnCloseWindow( self.frame, None ) ADD_MINI = 10 DEL_MINI = 20 SEND_TO_MAP = 30 SEND_GROUP_TO_MAP = 40 class minpedit( wxPanel ): """Panel for editing game tree miniature nodes. Node information is displayed in a grid, and buttons are provided for adding, deleting nodes, and for sending minis to the map ( singly and in batches ). """ def __init__( self, frame, handler ): """Constructor. """ wxPanel.__init__( self, frame, -1 ) self.handler = handler self.frame = frame self.sizer = wxBoxSizer( wxVERTICAL ) self.grid = minilib_grid( self, handler ) bbox = wxBoxSizer( wxHORIZONTAL ) bbox.Add( wxButton( self, ADD_MINI, "New mini" ), 0, wxEXPAND ) bbox.Add( wxButton( self, DEL_MINI, "Del mini" ), 0, wxEXPAND ) bbox.Add(wxSize(10,10)) bbox.Add( wxButton( self, SEND_TO_MAP, "Add 1" ), 0, wxEXPAND ) bbox.Add( wxButton( self, SEND_GROUP_TO_MAP, "Add Batch" ), 0, wxEXPAND ) self.sizer.Add( self.grid, 1, wxEXPAND) self.sizer.Add( bbox, 0) self.SetSizer(self.sizer) EVT_SIZE( self, self.on_size ) EVT_BUTTON( self, ADD_MINI, self.add_mini ) EVT_BUTTON( self, DEL_MINI, self.del_mini ) EVT_BUTTON( self, SEND_TO_MAP, self.send_to_map ) EVT_BUTTON( self, SEND_GROUP_TO_MAP, self.send_group_to_map ) def add_mini( self, evt=None ): """Event handler for the 'New mini' button. It calls minilib_grid.add_row """ self.grid.add_row() def del_mini( self, evt=None ): """Event handler for the 'Del mini' button. It calls minilib_grid.del_row """ self.grid.del_row( ) def send_to_map( self, evt=None ): """Event handler for the 'Add 1' button. Sends the miniature defined by the currently selected row to the map, once. """ # map = self.handler.openrpg.get_component('map') # min_label = self.grid.getSelectedLabel() # min_url = self.grid.getSelectedURL() # flag = self.grid.getSelectedSerial() # try: # flag = eval( flag ) # except: # pass # map.canvas.add_miniature( min_url, min_label, flag ) index = self.grid.GetGridCursorRow() self.handler.send_mini_to_map( self.handler.get_mini( index ) ) def send_group_to_map( self, evt=None ): """Event handler for the 'Add batch' button. Querys the user for a mini count and sends the miniature defined by the currently selected row to the map, the specified number of times. """ if self.grid.GetNumberRows() > 0: dlg = wxTextEntryDialog( self.frame, 'How many %s\'s do you want to add?' % ( self.grid.getSelectedLabel() ), 'Batch mini add', '2' ) if dlg.ShowModal() == wxID_OK: try: value = eval( dlg.GetValue() ) except: value = 0 # for loop in range( 0, value ): # self.send_to_map() print 'getting selected index for batch send' index = self.grid.GetGridCursorRow() print 'sending batch to map' self.handler.send_mini_to_map( self.handler.get_mini( index ), value ) def on_size( self, evt ): """Event handler for panel resizing events. """ s = self.GetClientSizeTuple() self.sizer.SetDimension( 10, 10, s[0] - 20 , s[1] - 20 ) class minilib_grid( wxGrid ): """A wxGrid subclass designed for editing game tree miniature library nodes. """ def __init__( self, parent, handler ): """Constructor. """ wxGrid.__init__(self, parent, -1, style=wxSUNKEN_BORDER | wxWANTS_CHARS ) self.parent = parent self.handler = handler #self.keys = [ ATTRIBUTE_NAME, ATTRIBUTE_URL, ATTRIBUTE_UNIQUE ] self.keys = CORE_ATTRIBUTES self.CreateGrid( 1, len( self.keys ) ) # self.SetColLabelValue( 0, 'Name' ) # self.SetColLabelValue( 1, 'URL' ) # self.SetColSize( 1, 250 ) # self.SetColLabelValue( 2, 'Unique' ) for key in self.keys: self.SetColLabelValue( self.keys.index( key ), key ) self.update_all() self.selectedRow = 0 self.AutoSizeColumns() EVT_GRID_CELL_CHANGE( self, self.on_cell_change ) EVT_GRID_SELECT_CELL( self, self.select_cell ) def update_cols( self ): nl = self.handler.master_dom.getElementsByTagName( TAG_MINIATURE ) for n in nl: for k in n.getAttributeKeys(): if k not in self.keys: self.keys.append( k ) def select_cell( self, evt ): """Event handler for grid cell selection changes. It stores the last selected row in a variable for use by the add[*] and del_row operations. """ self.BeginBatch() self.selectedRow = evt.GetRow() self.SelectRow( self.selectedRow ) self.EndBatch() evt.Skip() def getList( self ): """Returns the list of 'miniature' DOM elements associated with this miniature library. """ return self.handler.master_dom.getElementsByTagName( TAG_MINIATURE ) def add_row( self, count = 1 ): """creates a new miniature node, and then adds it to the current miniature library, and to the grid. """ self.AppendRows( count ) node = self.handler.new_mini( { ATTRIBUTE_NAME :' ', ATTRIBUTE_URL :'http://'} )# minidom.Element( TAG_MINIATURE ) self.update_all() #self.handler.master_dom.appendChild( node ) def del_row( self ): """deletes the miniature associated with the currently selected row. BUG BUG BUG this method should drop a child from the DOM but does not. """ if self.selectedRow > -1: pos = self.selectedRow list = self.handler.master_dom.getElementsByTagName(TAG_MINIATURE) self.handler.master_dom.removeChild( list[pos] ) self.DeleteRows( pos, 1 ) list = self.getList() del list[ pos ] def on_cell_change( self, evt ): """Event handler for cell selection changes. selected row is used to update data for that row. """ row = evt.GetRow() self.update_data_row( row ) def update_all( self ): """ensures that the grid is displaying the correct number of rows, and then updates all data displayed by the grid """ list = self.getList() count = 0 for n in list: for k in n.getAttributeKeys(): if k not in self.keys: self.keys.append( k ) count = len( self.keys ) if self.GetNumberCols() < count: self.AppendCols( count - self.GetNumberCols() ) for k in self.keys: self.SetColLabelValue( self.keys.index( k ), k ) count = len( list ) rowcount = self.GetNumberRows() if ( count > rowcount ): total = count - rowcount self.AppendRows( total ) elif ( count < rowcount ): total = rowcount - count self.DeleteRows( 0, total ); for index in range( 0, count ): self.update_grid_row( index ) def getSelectedLabel( self ): """Returns the label for the selected row """ return self.GetTable().GetValue( self.selectedRow, 0 ) def getSelectedURL( self ): """Returns the URL for the selected row """ return self.GetTable().GetValue( self.selectedRow, 1 ) def getSelectedSerial( self ): """Returns the ATTRIBUTE_UNIQUE value for the selected row """ return self.GetTable().GetValue( self.selectedRow, 2 ) def update_grid_row( self, row ): """Updates the specified grid row with data from the DOM node specified by 'row' """ list = self.getList() item = list[ row ] # self.GetTable().SetValue( row, 0, item.getAttribute(ATTRIBUTE_NAME) ) # self.GetTable().SetValue( row, 1, item.getAttribute(ATTRIBUTE_URL) ) # self.GetTable().SetValue( row, 2, item.getAttribute(ATTRIBUTE_UNIQUE) ) for key in self.keys: self.GetTable().SetValue( row, self.keys.index( key ), item.getAttribute( key ) ) def update_data_row( self, row ): """Updates the DOM nodw 'row' with grid data from 'row' """ list = self.getList() item = list[ row ] for key in self.keys: item.setAttribute( key, string.strip( self.GetTable().GetValue( row, self.keys.index( key ) ) ) ) # item.setAttribute( ATTRIBUTE_NAME, string.strip( self.GetTable().GetValue( row, 0 ) ) ) # item.setAttribute( ATTRIBUTE_URL, string.strip( self.GetTable().GetValue( row, 1 ) ) ) # item.setAttribute( ATTRIBUTE_UNIQUE, string.strip( self.GetTable().GetValue( row, 2 ) ) ) # self.GetTable().SetValue( row, 0, item.getAttribute(ATTRIBUTE_NAME) ) # self.GetTable().SetValue( row, 1, item.getAttribute(ATTRIBUTE_URL) ) # self.GetTable().SetValue( row, 2, item.getAttribute(ATTRIBUTE_UNIQUE) ) --- NEW FILE: rpg_grid.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: rpg_grid.py # Author: Chris Davis # Maintainer: # Version: # $Id: rpg_grid.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: The file contains code for the grid nodehanlers # __version__ = "$Id: rpg_grid.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from core import * from forms import * class rpg_grid_handler(node_handler): """ Node handler for rpg grid tool <nodehandler module='rpg_grid' class='rpg_grid_handler' name='sample'> <grid border='' autosize='1' > <row> <cell size='?'></cell> <cell></cell> </row> <row> <cell></cell> <cell></cell> </row> </grid> <macros> <macro name=''/> </macros> </nodehandler> """ def __init__(self,xml_dom,tree_node,openrpg): node_handler.__init__(self,xml_dom,tree_node,openrpg) self.grid = self.master_dom.getElementsByTagName('grid')[0] if self.grid.getAttribute("border") == "": self.grid.setAttribute("border","1") if self.grid.getAttribute("autosize") == "": self.grid.setAttribute("autosize","1") self.macros = self.master_dom.getElementsByTagName('macros')[0] self.frame = openrpg.get_component('frame') self.myeditor = None self.refresh_rows() def refresh_die_macros(self): pass def refresh_rows(self): self.rows = {} tree = self.tree icons = self.tree.icons tree.CollapseAndReset(self.mytree_node) node_list = self.master_dom.getElementsByTagName('row') for n in node_list: cells = n.getElementsByTagName('cell') t_node = cells[0]._get_firstChild() if t_node == None: name = "Row" else: name = t_node._get_nodeValue() if name == "": name = "Row" new_tree_node = tree.AppendItem(self.mytree_node,name,icons['gear'],icons['gear']) handler = grid_row_handler(n,new_tree_node,self.myopenrpg,self) tree.SetPyData(new_tree_node,handler) def tohtml(self): border = self.grid.getAttribute("border") name = self.master_dom.getAttribute('name') rows = self.grid.getElementsByTagName('row') colspan = str(len(rows[0].getElementsByTagName('cell'))) html_str = "<table border=\""+border+"\" align=center><tr bgcolor=\""+TH_BG+"\" ><th colspan="+colspan+">"+name+"</th></tr>" for r in rows: cells = r.getElementsByTagName('cell') html_str += "<tr>" for c in cells: #html_str += "<td width='"+c.getAttribute('size')+"' >" bug here html_str += "<td >" t_node = c._get_firstChild() if t_node == None: html_str += "<br></td>" else: html_str += t_node._get_nodeValue() + "</td>" html_str += "</tr>" html_str += "</table>" return html_str def get_design_panel(self,parent): return rpg_grid_edit_panel(parent,self) def get_use_panel(self,parent): return rpg_grid_panel(parent,self) def get_size_constraint(self): return 1 def is_autosized(self): return int(self.grid.getAttribute("autosize")) def set_autosize(self,autosize=1): self.grid.setAttribute("autosize",str(autosize)) class grid_row_handler(node_handler): """ Node Handler grid row. """ def __init__(self,xml_dom,tree_node,openrpg,parent): node_handler.__init__(self,xml_dom,tree_node,openrpg) self.drag = false self.frame = self.myopenrpg.get_component('frame') def on_drop(self,evt): pass def can_clone(self): return 0; def tohtml(self): cells = self.master_dom.getElementsByTagName('cell') html_str = "<table border=1 align=center><tr >" for c in cells: html_str += "<td >" t_node = c._get_firstChild() if t_node == None: html_str += "<br></td>" else: html_str += t_node._get_nodeValue() + "</td>" html_str += "</tr>" html_str += "</table>" return html_str class MyCellEditor(wxPyGridCellEditor): """ This is a sample GridCellEditor that shows you how to make your own custom grid editors. All the methods that can be overridden are show here. The ones that must be overridden are marked with "*Must Override*" in the docstring. Notice that in order to call the base class version of these special methods we use the method name preceded by "base_". This is because these methods are "virtual" in C++ so if we try to call wxGridCellEditor.Create for example, then when the wxPython extension module tries to call ptr->Create(...) then it actually calls the derived class version which looks up the method in this class and calls it, causing a recursion loop. If you don't understand any of this, don't worry, just call the "base_" version instead. ---------------------------------------------------------------------------- This class is copied from the wxPython examples directory and was written by Robin Dunn. I have pasted it directly in and removed all references to "log" -- Andrew """ # def __init__(self, log): def __init__(self): # self.log = log # self.log.write("MyCellEditor ctor\n") wxPyGridCellEditor.__init__(self) def Create(self, parent, id, evtHandler): """ Called to create the control, which must derive from wxControl. *Must Override* """ # self.log.write("MyCellEditor: Create\n") self._tc = orpgTextCtrl(parent, id, "", style=wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB) self._tc.SetInsertionPoint(0) self.SetControl(self._tc) if evtHandler: self._tc.PushEventHandler(evtHandler) def SetSize(self, rect): """ Called to position/size the edit control within the cell rectangle. If you don't fill the cell (the rect) then be sure to override PaintBackground and do somethi... [truncated message content] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:28
|
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib/filter In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/plugins/cherrypy/lib/filter Added Files: __init__.py basefilter.py baseurlfilter.py cachefilter.py decodingfilter.py encodingfilter.py gzipfilter.py logdebuginfofilter.py tidyfilter.py virtualhostfilter.py xmlrpcfilter.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: xmlrpcfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ ########################################################################## ## Remco Boerma ## ## History: ## 1.0.3 : 2005-01-28 Bugfix on content-length in 1.0.2 code fixed by ## Gian Paolo Ciceri ## 1.0.2 : 2005-01-26 changed infile dox based on ticket #97 ## 1.0.1 : 2005-01-26 Speedup due to generator usage in CP2. ## The result is now converted to a list with length 1. So the complete ## xmlrpc result is written at once, and not per character. Thanks to ## Gian Paolo Ciceri for reporting the slowdown. ## 1.0.0 : 2004-12-29 Released with CP2 ## 0.0.9 : 2004-12-23 made it CP2 #59 compatible (returns an iterable) ## Please note: as the xmlrpc doesn't know what you would want to return ## (and for the logic of marshalling) it will return Generator objects, as ## it is.. So it'll brake on that one!! ## NOTE: __don't try to return a Generator object to the caller__ ## You could of course handle the generator usage internally, before sending ## the result. This breaks from the general cherrypy way of handling generators... ## 0.0.8 : 2004-12-23 cpg.request.paramList should now be a filter. ## 0.0.7 : 2004-12-07 inserted in the experimental branch (all remco boerma till here) ## 0.0.6 : 2004-12-02 Converted basefilter to baseinputfileter,baseoutputfilter ## 0.0.5 : 2004-11-22 "RPC2/" now changed to "/RPC2/" with the new mapping function ## Gian paolo ciceri notified me with the lack of passing parameters. ## Thanks Gian, it's now implemented against the latest trunk. ## Gian also came up with the idea of lazy content-type checking: if it's sent ## as a header, it should be 'text/xml', if not sent at all, it should be ## accepted. (While this it not the xml/rpc standard, it's handy for those ## xml-rpc client implementations wich don't send this header) ## 0.0.4 : 2004-11-20 in setting the path, the dot is replaces by a slash ## therefore the regular CP2 routines knows how to handle things, as ## dots are not allowed in object names, it's varely easily adopted. ## Path + method handling. The default path is 'RPC2', this one is ## stripped. In case of path 'someurl' it is used for 'someurl' + method ## and 'someurl/someotherurl' is mapped to someurl.someotherurl + method. ## this way python serverproxies initialised with an url other than ## just the host are handled well. I don't hope any other service would map ## it to 'RPC2/someurl/someotherurl', cause then it would break i think. . ## 0.0.3 : 2004-11-19 changed some examples (includes error checking ## wich returns marshalled Fault objects if the request is an RPC call. ## took testing code form afterRequestHeader and put it in ## testValidityOfRequest to make things a little simpler. ## simply log the requested function with parameters to stdout ## 0.0.2 : 2004-11-19 the required cgi.py patch is no longer needed ## (thanks remi for noticing). Webbased calls to regular objects ## are now possible again ;) so it's no longer a dedicated xmlrpc ## server. The test script is also in a ready to run file named ## testRPC.py along with the test server: filterExample.py ## 0.0.1 : 2004-11-19 informing the public, dropping loads of useless ## tests and debugging ## 0.0.0 : 2004-11-19 initial alpha ## ##--------------------------------------------------------------------- ## ## EXAMPLE CODE FOR THE SERVER: ## from cherrypy.lib.filter.xmlrpcfilter import XmlRpcFilter ## from cherrypy import cpg ## ## class Root: ## _cpFilterList = [XmlRpcFilter()] ## ## def longString(self,s,times): ## return s*times ## longString.exposed = True ## ## cpg.root = Root() ## if __name__=='__main__': ## cpg.server.start(configMap = {'socketPort': 9001, ## 'threadPool':0, ## 'socketQueueSize':10 }) ## EXAMPLE CODE FOR THE CLIENT: ## >>> import xmlrpclib ## >>> server = xmlrpclib.ServerProxy('http://localhost:9001') ## >>> assert server.longString('abc',3) == 'abcabcabc' ## >>> ###################################################################### from basefilter import BaseInputFilter, BaseOutputFilter from cherrypy import cpg import xmlrpclib class XmlRpcFilter(BaseInputFilter,BaseOutputFilter): """ Derivative of basefilter. Test to convert XMLRPC to CherryPy2 object system and reverse PLEASE NOTE: afterRequestHeader: Unmarshalls the posted data to a methodname and parameters. - These are stored in cpg.request.rpcMethod and cpg.request.rpcParams - The method is also stored in cpg.request.path, so CP2 will find the right method to call for you. Based on the root's position beforeResponse: Marshalls the result of the excecuted function (in cpg.response.body) to xmlrpc. - Until resolved: the result must be a python souce string with the results, this string is 'eval'ed to return the results. This will be resolved in the future. - the Content-Type and -Length are set according to the new (marshalled) data. """ def testValidityOfRequest(self): # test if the content-length was sent result = int(cpg.request.headerMap.get('Content-Length',0)) > 0 result = result and cpg.request.headerMap.get('Content-Type','text/xml').lower() in ['text/xml'] return result def afterRequestHeader(self): """ Called after the request header has been read/parsed""" cpg.request.isRPC = self.testValidityOfRequest() if not cpg.request.isRPC: # used for debugging or more info # print 'not a valid xmlrpc call' return # break this if it's not for this filter!! # used for debugging, or more info: # print "xmlrpcmethod...", cpg.request.parsePostData = 0 dataLength = int(cpg.request.headerMap.get('Content-Length',0)) data = cpg.request.rfile.read(dataLength) try: params, method = xmlrpclib.loads(data) except Exception,e: params, method = ('ERROR PARAMS',),'ERRORMETHOD' cpg.request.rpcMethod, cpg.request.rpcParams = method,params # patch the path. .there are only a few options: # - 'RPC2' + method >> method # - 'someurl' + method >> someurl.method # - 'someurl/someother' + method >> someurl.someother.method if not cpg.request.path.endswith('/'): cpg.request.path+='/' if cpg.request.path.startswith('/RPC2/'): cpg.request.path=cpg.request.path[5:] ## strip the irst /rpc2 cpg.request.path+=str(method).replace('.','/') cpg.request.paramList = list(params) # used for debugging and more info # print "XMLRPC Filter: calling '%s' with args: '%s' " % (cpg.request.path,params) def beforeResponse(self): """ Called before starting to write response """ if not cpg.request.isRPC: return # it's not an RPC call, so just let it go with the normal flow try: cpg.response.body = [xmlrpclib.dumps((cpg.response.body[0],), methodresponse=1,allow_none=1)] except xmlrpclib.Fault,fault: cpg.response.body = xmlrpclib.dumps(fault,allow_none=1) except Exception,e: print 'EXCEPTION: ',e cpg.response.headerMap['Content-Type']='text/xml' try: cpg.response.headerMap['Content-Length']=`len(cpg.response.body[0])` except TypeError: # 1.0.3 : in case of an error, cpg.response.body is unscriptable pass --- NEW FILE: gzipfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import zlib import struct import time from basefilter import BaseOutputFilter from cherrypy import cpg class GzipFilter(BaseOutputFilter): """ Filter that gzips the response. """ def __init__(self, mimeTypeList = ['text/html'], compresslevel=9): # List of mime-types to compress self.mimeTypeList = mimeTypeList self.compresslevel = compresslevel def beforeResponse(self): if not cpg.response.body: # Response body is empty (might be a 304 for instance) return ct = cpg.response.headerMap.get('Content-Type').split(';')[0] ae = cpg.request.headerMap.get('Accept-Encoding', '') if (ct in self.mimeTypeList) and ('gzip' in ae): # Set header cpg.response.headerMap['Content-Encoding'] = 'gzip' # Return a generator that compresses the page cpg.response.body = self.zip_body(cpg.response.body) def write_gzip_header(self): """ Adapted from the gzip.py standard module code """ header = '\037\213' # magic header header += '\010' # compression method header += '\0' header += struct.pack("<L", long(time.time())) header += '\002' header += '\377' return header def write_gzip_trailer(self, crc, size): footer = struct.pack("<l", crc) footer += struct.pack("<L", size & 0xFFFFFFFFL) return footer def zip_body(self, body): # Compress page yield self.write_gzip_header() crc = zlib.crc32("") size = 0 zobj = zlib.compressobj(self.compresslevel, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) for line in body: size += len(line) crc = zlib.crc32(line, crc) yield zobj.compress(line) yield zobj.flush() yield self.write_gzip_trailer(crc, size) --- NEW FILE: cachefilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import threading import Queue import time import cStringIO from basefilter import BaseInputFilter, RequestHandled from cherrypy import cpg def defaultCacheKey(): return cpg.request.browserUrl class Tee: """ Wraps a stream object; chains the content that is written and keep a copy in a StringIO for caching purposes. """ def __init__(self, wfile, maxobjsize): self.wfile = wfile self.cache = cStringIO.StringIO() self.maxobjsize = maxobjsize self.caching = True self.size = 0 def write(self, s): self.wfile.write(s) if self.caching: self.size += len(s) if self.size < self.maxobjsize: self.cache.write(s) else: # exceeded the limit, aborts caching self.stopCaching() def flush(self): self.wfile.flush() def close(self): self.wfile.close() if self.caching: self.stopCaching() def stopCaching(self): self.caching = False self.cache.close() class MemoryCache: def __init__(self, key, delay, maxobjsize, maxsize, maxobjects): self.key = key self.delay = delay self.maxobjsize = maxobjsize self.maxsize = maxsize self.maxobjects = maxobjects self.cursize = 0 self.cache = {} self.expirationQueue = Queue.Queue() self.expirationThread = threading.Thread(target=self.expireCache, name='expireCache') self.expirationThread.setDaemon(True) self.expirationThread.start() self.totPuts = 0 self.totGets = 0 self.totHits = 0 self.totExpires = 0 self.totNonModified = 0 def expireCache(self): while True: expirationTime, objSize, objKey = self.expirationQueue.get(block=True, timeout=None) while (time.time() < expirationTime): time.sleep(0.1) try: del self.cache[objKey] self.totExpires += 1 self.cursize -= objSize except KeyError: # the key may have been deleted elsewhere pass def get(self): """ If the content is in the cache, returns a tuple containing the expiration time, the lastModified response header and the object (rendered as a string); returns None if the key is not found. """ self.totGets += 1 cacheItem = self.cache.get(self.key(), None) if cacheItem: self.totHits += 1 return cacheItem else: return None def put(self, lastModified, obj): objSize = len(obj) totalSize = self.cursize + objSize # checks if there's space for the object if ((objSize < self.maxobjsize) and (totalSize < self.maxsize) and (len(self.cache) < self.maxobjects)): # add to the expirationQueue & cache try: expirationTime = time.time() + self.delay objKey = self.key() self.expirationQueue.put((expirationTime, objSize, objKey)) self.totPuts += 1 self.cursize += objSize except Queue.Full: # can't add because the queue is full return self.cache[objKey] = (expirationTime, lastModified, obj) class CacheInputFilter(BaseInputFilter): """ Works on the input chain. If the page is already stored in the cache serves the contents. If the page is not in the cache, it wraps the cpg.response.wfile object; in this way, everything that is written is recorded, independent if it was sent directly or not. """ def __init__( self, CacheClass=MemoryCache, key=defaultCacheKey, delay=600, # 10 minutes maxobjsize=100000, # 100 KB maxsize=10000000, # 10 MB maxobjects=1000 # 1000 objects ): cpg._cache = CacheClass(key, delay, maxobjsize, maxsize, maxobjects) def afterRequestBody(self): """ Checks if the page is already in the cache """ cacheData = cpg._cache.get() if cacheData: expirationTime, lastModified, obj = cacheData # found a hit! check the if-modified-since request header modifiedSince = cpg.request.headerMap.get('If-Modified-Since', None) #print "Cache hit: If-Modified-Since=%s, lastModified=%s" % (modifiedSince, lastModified) if modifiedSince == lastModified: cpg._cache.totNonModified += 1 # the code below was borrowed from the sendResponse function # it should be refactored & put into a function to allow reuse cpg.response.wfile.write('%s %s\r\n' % (cpg.configOption.protocolVersion, 304)) # the code below doesn't work because the data isn't available at this point... #cpg.response.wfile.write('%s: %s\r\n' % ('Date', cpg.request.headerMap['Date'])) # should the cache record & replay cookies it too? cpg.response.wfile.write('\r\n') raise RequestHandled else: # serve it & get out from the request cpg.response.wfile.write(obj) raise RequestHandled else: # sets a wrapper to cache the contents cpg.response.wfile = Tee(cpg.response.wfile, cpg._cache.maxobjsize) cpg.threadData.cacheable = True class CacheOutputFilter(object): """ Works on the output chain. Stores the content of the page in the cache. """ def beforeResponse(self): """ Checks if the page is cacheable; if not so disables the cache. Uses a flag that may be reset by intermediate filters. Note that the output filter is usually the last filter in the chain, so this method is probably the last one called before the response is written. """ if isinstance(cpg.response.wfile, Tee): if cpg.threadData.cacheable: return # cancel caching wrapper = cpg.response.wfile wrapper.stopCaching() cpg.response.wfile = wrapper.wfile def afterResponse(self): """ Close & fix the cache entry after content was fully written """ if isinstance(cpg.response.wfile, Tee): wrapper = cpg.response.wfile if wrapper.caching: if cpg.response.headerMap.get('Pragma', None) != 'no-cache': lastModified = cpg.response.headerMap.get('Last-Modified', None) # saves the cache data cpg._cache.put(lastModified, wrapper.cache.getvalue()) # closes the wrapper wrapper.stopCaching() cpg.response.wfile = wrapper.wfile def percentual(n,d): """calculates the percentual, dealing with div by zeros""" if d == 0: return 0 else: return (float(n)/float(d))*100 def formatSize(n): """formats a number as a memory size, in bytes, kbytes, MB, GB)""" if n < 1024: return "%4d bytes" % n elif n < 1024*1024: return "%4d kbytes" % (n / 1024) elif n < 1024*1024*1024: return "%4d MB" % (n / (1024*1024)) else: return "%4d GB" % (n / (1024*1024*1024)) class CacheStats: def index(self): cpg.response.headerMap['Content-Type'] = 'text/plain' cpg.response.headerMap['Pragma'] = 'no-cache' cache = cpg._cache yield "Cache statistics\n" yield "Maximum object size: %s\n" % formatSize(cache.maxobjsize) yield "Maximum cache size: %s\n" % formatSize(cache.maxsize) yield "Maximum number of objects: %d\n" % cache.maxobjects yield "Current cache size: %s\n" % formatSize(cache.cursize) yield "Approximated expiration queue size: %d\n" % cache.expirationQueue.qsize() yield "Number of cache entries: %d\n" % len(cache.cache) yield "Total cache writes: %d\n" % cache.totPuts yield "Total cache read attempts: %d\n" % cache.totGets yield "Total hits: %d (%1.2f%%)\n" % (cache.totHits, percentual(cache.totHits, cache.totGets)) yield "Total misses: %d (%1.2f%%)\n" % (cache.totGets-cache.totHits, percentual(cache.totGets-cache.totHits, cache.totGets)) yield "Total expires: %d\n" % cache.totExpires yield "Total non-modified content: %d\n" % cache.totNonModified index.exposed = True --- NEW FILE: __init__.py --- --- NEW FILE: baseurlfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ from basefilter import BaseInputFilter from cherrypy import cpg class BaseUrlFilter(BaseInputFilter): """ Filter that changes the base URL. Useful when running a CP server behind Apache. """ def __init__(self, baseUrl = 'http://localhost', useXForwardedHost = True): # New baseUrl self.baseUrl = baseUrl self.useXForwardedHost = useXForwardedHost def afterRequestHeader(self): if self.useXForwardedHost: newBaseUrl = cpg.request.headerMap.get("X-Forwarded-Host", self.baseUrl) else: newBaseUrl = self.baseUrl if newBaseUrl.find("://") == -1: # add http:// or https:// if needed newBaseUrl = cpg.request.base[:cpg.request.base.find("://") + 3] + newBaseUrl cpg.request.browserUrl = cpg.request.browserUrl.replace( cpg.request.base, newBaseUrl) cpg.request.base = newBaseUrl --- NEW FILE: tidyfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import os, cgi from basefilter import BaseOutputFilter from cherrypy import cpg class TidyFilter(BaseOutputFilter): """ Filter that runs the response through Tidy. Note that we use the standalone Tidy tool rather than the python mxTidy module. This is because this module doesn't seem to be stable and it crashes on some HTML pages (which means that the server would also crash) """ def __init__(self, tidyPath, tmpDir, errorsToIgnore = []): self.tidyPath = tidyPath self.tmpDir = tmpDir self.errorsToIgnore = errorsToIgnore def beforeResponse(self): # the tidy filter, by its very nature it's not generator friendly, # so we just collect the body and work with it. originalBody = ''.join(cpg.response.body) cpg.response.body = [originalBody] fct = cpg.response.headerMap.get('Content-Type', '') ct = fct.split(';')[0] if ct == 'text/html': pageFile = os.path.join(self.tmpDir, 'page.html') outFile = os.path.join(self.tmpDir, 'tidy.out') errFile = os.path.join(self.tmpDir, 'tidy.err') f = open(pageFile, 'wb') f.write(originalBody) f.close() encoding = '' i = fct.find('charset=') if i != -1: encoding = fct[i+8:] encoding = encoding.replace('utf-8', 'utf8') if encoding: encoding = '-' + encoding os.system('"%s" %s -f %s -o %s %s' % ( self.tidyPath, encoding, errFile, outFile, pageFile)) f = open(errFile, 'rb') err = f.read() f.close() errList = err.splitlines() newErrList = [] for err in errList: if (err.find('Warning') != -1 or err.find('Error') != -1): ignore = 0 for errIgn in self.errorsToIgnore: if err.find(errIgn) != -1: ignore = 1 break if not ignore: newErrList.append(err) if newErrList: newBody = "Wrong HTML:<br>" + cgi.escape('\n'.join(newErrList)).replace('\n','<br>') newBody += '<br><br>' i=0 for line in originalBody.splitlines(): i += 1 newBody += "%03d - "%i + cgi.escape(line).replace('\t',' ').replace(' ',' ') + '<br>' cpg.response.body = [newBody] --- NEW FILE: encodingfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ from basefilter import BaseOutputFilter from cherrypy import cpg import types class EncodingFilter(BaseOutputFilter): """ Filter that automatically encodes the response. """ def __init__(self, encoding = 'utf-8', mimeTypeList = ['text/html']): self.encoding = encoding self.mimeTypeList = mimeTypeList def beforeResponse(self): contentType = cpg.response.headerMap.get("Content-Type") if contentType: ctlist = contentType.split(';')[0] if (ctlist in self.mimeTypeList): # Add "charset=..." to response Content-Type header if contentType and 'charset' not in contentType: cpg.response.headerMap["Content-Type"] += ";charset=%s" % self.encoding # Return a generator that encodes the sequence cpg.response.body = self.encode_body(cpg.response.body) def encode_body(self, body): for line in body: yield line.encode(self.encoding) --- NEW FILE: basefilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ class InternalRedirect(Exception): pass class RequestHandled(Exception): pass class BaseInputFilter(object): """ Base class for input filters. Derive new filter classes from this, then override some of the methods to add some side-effects. """ def afterRequestHeader(self): """ Called after the request header has been read/parsed""" pass def afterRequestBody(self): """ Called after the request body has been read/parsed""" pass class BaseOutputFilter(object): """ Base class for output filters. Derive new filter classes from this, then override some of the methods to add some side-effects. """ def beforeResponse(self): """ Called before starting to write response """ pass def afterResponse(self): """ Called after writing the response (header & body included) """ pass --- NEW FILE: decodingfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ from basefilter import BaseInputFilter from cherrypy import cpg import types class DecodingFilter(BaseInputFilter): """ Filter that automatically decodes the request parameters (except files being uploaded). """ def __init__(self, encoding = 'utf-8'): self.encoding = encoding def afterRequestBody(self): for key, value in cpg.request.paramMap.items(): if key in cpg.request.filenameMap: # This is a file being uploaded: skip it continue if isinstance(value, list): # value is a list: decode each element newValue = [v.decode(self.encoding) for v in value] else: # value is a regular string: decode it newValue = value.decode(self.encoding) cpg.request.paramMap[key] = newValue --- NEW FILE: virtualhostfilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import basefilter from cherrypy import cpg, _cphttptools class VirtualHostFilter(basefilter.BaseInputFilter): """ Filter that changes the ObjectPath based on the Host. Useful when running multiple sites within one CP server. See CherryPy recipes for the documentation. """ def __init__(self, siteMap, useXForwardedHost = True): self.siteMap = siteMap self.useXForwardedHost = useXForwardedHost def afterRequestHeader(self): domain = cpg.request.base.split('//')[1] if self.useXForwardedHost: domain = cpg.request.headerMap.get( "X-Forwarded-Host", domain) prefix = self.siteMap.get(domain) if prefix: # Re-use "mapPathToObject" function to find the actual # objectPath candidate, objectPathList, virtualPathList = \ _cphttptools.mapPathToObject( prefix + cpg.request.path ) cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) raise basefilter.InternalRedirect --- NEW FILE: logdebuginfofilter.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import time, StringIO, pickle from basefilter import BaseInputFilter, BaseOutputFilter from cherrypy import cpg from itertools import chain class LogDebugInfoStartFilter(BaseInputFilter, BaseOutputFilter): """ Filter that adds debug information to the page """ def __init__(self, mimeTypeList = ['text/html'], preTag = '<br><br>', logBuildTime = True, logPageSize = True, logSessionSize = True, logAsComment = False): # List of mime-types to which this applies self.mimeTypeList = mimeTypeList self.preTag = preTag self.logBuildTime = logBuildTime self.logPageSize = logPageSize self.logSessionSize = logSessionSize self.logAsComment = logAsComment def afterRequestBody(self): cpg.request.startBuilTime = time.time() def beforeResponse(self): ct = cpg.response.headerMap.get('Content-Type') if (ct in self.mimeTypeList): debuginfo = '\n' if self.logAsComment: debuginfo += '<!-- ' else: debuginfo += self.preTag logList = [] if self.logBuildTime: logList.append("Build time: %.03fs" % ( time.time() - cpg.request.startBuilTime)) if self.logPageSize: logList.append("Page size: %.02fKB" % ( len(cpg.response.body)/float(1024))) if self.logSessionSize and cpg.configOption.sessionStorageType: # Pickle session data to get its size f = StringIO.StringIO() pickle.dump(cpg.request.sessionMap, f, 1) dumpStr = f.getvalue() f.close() logList.append("Session data size: %.02fKB" % ( len(dumpStr)/float(1024))) debuginfo += ', '.join(logList) if self.logAsComment: debuginfo += '-->' cpg.response.body = chain(cpg.response.body, [debuginfo]) |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:28
|
Update of /cvsroot/winopenrpg/openrpg1/data/SWd20 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/data/SWd20 Added Files: SWd20classes.xml d20armor.xml d20feats.xml d20weapons.xml Log Message: Initial commit of OpenRPG++ python --- NEW FILE: d20feats.xml --- <feats> <feat name='Absorb Energy' type='General' /> <feat name='Acrobatic' type='General' /> <feat name='Advanced Martial Arts' type='General' /> <feat name='Alertness' type='General' /> <feat name='Alter' type='General' /> <feat name='Ambidexterity' type='General' /> <feat name='Animal Affinity' type='General' /> <feat name='Armor Proficiency (heavy)' type='General' /> <feat name='Armor Proficiency (light)' type='General' /> <feat name='Armor Proficiency (medium)' type='General' /> <feat name='Armor Proficiency (powered)' type='General' /> <feat name='Athletic' type='General' /> <feat name='Attuned' type='General' /> <feat name='Aware' type='General' /> <feat name='Blind-fight' type='General' /> <feat name='Burst of Speed' type='General' /> <feat name='Cautious' type='General' /> <feat name='Cleave' type='General' /> <feat name='Combat Expertise' type='General' /> <feat name='Combat Reflexes' type='General' /> <feat name='Compassion' type='General' /> <feat name='Control' type='General' /> <feat name='Defensive Martial Arts' type='General' /> <feat name='Dissipate Energy' type='General' /> <feat name='Dodge' type='General' /> <feat name='Drain Force' type='General' /> <feat name='Endurance' type='General' /> <feat name='Exotic Weapon Proficiency (amphistaff)' type='General' /> <feat name='Exotic Weapon Proficiency (atlatl)' type='General' /> <feat name='Exotic Weapon Proficiency (bowcaster)' type='General' /> <feat name='Exotic Weapon Proficiency (cesta)' type='General' /> <feat name='Exotic Weapon Proficiency (double lightsaber)' type='General' /> <feat name='Exotic Weapon Proficiency (gaderffii)' type='General' /> <feat name='Exotic Weapon Proficiency (lightsaber)' type='General' /> <feat name='Exotic Weapon Proficiency (lightwhip)' type='General' /> <feat name='Exotic Weapon Proficiency (massassi lanvarok)' type='General' /> <feat name='Exotic Weapon Proficiency (plaeryin bol)' type='General' /> <feat name='Exotic Weapon Proficiency (quills)' type='General' /> <feat name='Exotic Weapon Proficiency (riot gun)' type='General' /> <feat name='Exotic Weapon Proficiency (san-ni staff)' type='General' /> <feat name='Exotic Weapon Proficiency (short lightsaber)' type='General' /> <feat name='Exotic Weapon Proficiency (sith lanvarok)' type='General' /> <feat name='Exotic Weapon Proficiency (sith sword)' type='General' /> <feat name='Exotic Weapon Proficiency (snare rifle)' type='General' /> <feat name='Exotic Weapon Proficiency (stinger)' type='General' /> <feat name='Exotic Weapon Proficiency (tsaisi)' type='General' /> <feat name='Expert Gunner' type='General' /> <feat name='Fame' type='General' /> <feat name='Far Shot' type='General' /> <feat name='Focus' type='General' /> <feat name='Force Dodge' type='General' /> <feat name='Force Flight' type='General' /> <feat name='Force Mastery' type='General' /> <feat name='Force Mind' type='General' /> <feat name='Force Pilot' type='General' /> <feat name='Force Shot' type='General' /> <feat name='Force Speed' type='General' /> <feat name='Force Whirlwind' type='General' /> <feat name='Force-Sensitive' type='General' /> <feat name='Frightful Presence' type='General' /> <feat name='Gearhead' type='General' /> <feat name='Great Cleave' type='General' /> <feat name='Great Fortitude' type='General' /> <feat name='Guided Shot' type='General' /> <feat name='Gunner' type='General' /> <feat name='Hatred' type='General' /> <feat name='Headstrong' type='General' /> <feat name='Heroic Surge' type='General' /> <feat name='High Force Mastery' type='General' /> <feat name='Improved Bantha Rush' type='General' /> <feat name='Improved Critical' type='General' /> <feat name='Improved Disarm' type='General' /> <feat name='Improved Force Mind' type='General' /> <feat name='Improved Initiative' type='General' /> <feat name='Improved Martial Arts' type='General' /> <feat name='Improved Trip' type='General' /> <feat name='Improved Two-weapon Fighting' type='General' /> <feat name='Infamy' type='General' /> <feat name='Influence' type='General' /> <feat name='Iron Will' type='General' /> <feat name='Knight Defense' type='General' /> <feat name='Knight Mind' type='General' /> <feat name='Knight Speed' type='General' /> <feat name='Lightning Reflexes' type='General' /> <feat name='Lightsaber Defense' type='General' /> <feat name='Link' type='General' /> <feat name='Low Profile' type='General' /> <feat name='Malevolant' type='General' /> <feat name='Maneuver Expertise' type='General' /> <feat name='Martial Arts' type='General' /> <feat name='Master Defense' type='General' /> <feat name='Master Mind' type='General' /> <feat name='Master Speed' type='General' /> <feat name='Mettle' type='General' /> <feat name='Mimic' type='General' /> <feat name='Mind Trick' type='General' /> <feat name='Mobility' type='General' /> <feat name='Multishot' type='General' /> <feat name='Nimble' type='General' /> <feat name='Persuasive' type='General' /> <feat name='Pinpoint Accuracy' type='General' /> <feat name='Point Blank Shot' type='General' /> <feat name='Power Attack' type='General' /> <feat name='Precise Shot' type='General' /> <feat name='Psychometry' type='General' /> <feat name='Quick Draw' type='General' /> <feat name='Quickness' type='General' /> <feat name='Rage' type='General' /> <feat name='Rapid Gunner' type='General' /> <feat name='Rapid Shot' type='General' /> <feat name='Rugged' type='General' /> <feat name='Run' type='General' /> <feat name='Sense' type='General' /> <feat name='Shapeshifter' type='General' /> <feat name='Sharp-eyed' type='General' /> <feat name='Shot On The Run' type='General' /> <feat name='Sith Sorcery' type='General' /> <feat name='Sith Sword Defense' type='General' /> <feat name='Sith Sword Expert Defense' type='General' /> <feat name='Sith Sword Mastery' type='General' /> <feat name='Skill Emphasis (Affect Mind)' type='General' /> <feat name='Skill Emphasis (Alchemy)' type='General' /> <feat name='Skill Emphasis (Appraise)' type='General' /> <feat name='Skill Emphasis (Astrogate)' type='General' /> <feat name='Skill Emphasis (Balance)' type='General' /> <feat name='Skill Emphasis (Battlemind)' type='General' /> <feat name='Skill Emphasis (Bluff)' type='General' /> <feat name='Skill Emphasis (Climb)' type='General' /> <feat name='Skill Emphasis (Computer Use)' type='General' /> <feat name='Skill Emphasis (Control Mind)' type='General' /> <feat name='Skill Emphasis (Craft)' type='General' /> <feat name='Skill Emphasis (Demolitions)' type='General' /> <feat name='Skill Emphasis (Diplomacy)' type='General' /> <feat name='Skill Emphasis (Disable Device)' type='General' /> <feat name='Skill Emphasis (Disguise)' type='General' /> <feat name='Skill Emphasis (Drain Energy)' type='General' /> <feat name='Skill Emphasis (Drain Knowledge)' type='General' /> <feat name='Skill Emphasis (Empathy)' type='General' /> <feat name='Skill Emphasis (Enhance Ability)' type='General' /> <feat name='Skill Emphasis (Enhance Senses)' type='General' /> <feat name='Skill Emphasis (Entertain)' type='General' /> <feat name='Skill Emphasis (Escape Artist)' type='General' /> <feat name='Skill Emphasis (Farseeing)' type='General' /> <feat name='Skill Emphasis (Fear)' type='General' /> <feat name='Skill Emphasis (Force Defense)' type='General' /> <feat name='Skill Emphasis (Force Grip)' type='General' /> <feat name='Skill Emphasis (Force Lightning)' type='General' /> <feat name='Skill Emphasis (Force Stealth)' type='General' /> <feat name='Skill Emphasis (Force Strike)' type='General' /> <feat name='Skill Emphasis (Forgery)' type='General' /> <feat name='Skill Emphasis (Friendship)' type='General' /> <feat name='Skill Emphasis (Gamble)' type='General' /> <feat name='Skill Emphasis (Gather Information)' type='General' /> <feat name='Skill Emphasis (Handle Animal)' type='General' /> <feat name='Skill Emphasis (Heal Another)' type='General' /> <feat name='Skill Emphasis (Heal Self)' type='General' /> <feat name='Skill Emphasis (Hide)' type='General' /> <feat name='Skill Emphasis (Illusion)' type='General' /> <feat name='Skill Emphasis (Intimidate)' type='General' /> <feat name='Skill Emphasis (Jump)' type='General' /> <feat name='Skill Emphasis (Knowledge)' type='General' /> <feat name='Skill Emphasis (Listen)' type='General' /> <feat name='Skill Emphasis (Move Object)' type='General' /> <feat name='Skill Emphasis (Move Silently)' type='General' /> <feat name='Skill Emphasis (Pilot)' type='General' /> <feat name='Skill Emphasis (Profession)' type='General' /> <feat name='Skill Emphasis (Repair)' type='General' /> <feat name='Skill Emphasis (Ride)' type='General' /> <feat name='Skill Emphasis (Search)' type='General' /> <feat name='Skill Emphasis (See Force)' type='General' /> <feat name='Skill Emphasis (Sense Motive)' type='General' /> <feat name='Skill Emphasis (Sleight of Hand)' type='General' /> <feat name='Skill Emphasis (Spot)' type='General' /> <feat name='Skill Emphasis (Survival)' type='General' /> <feat name='Skill Emphasis (Swim)' type='General' /> <feat name='Skill Emphasis (Telepathy)' type='General' /> <feat name='Skill Emphasis (Transfer Essence)' type='General' /> <feat name='Skill Emphasis (Treat Injury)' type='General' /> <feat name='Skill Emphasis (Tumble)' type='General' /> <feat name='Spacer' type='General' /> <feat name='Spring Attack' type='General' /> <feat name='Stamina' type='General' /> <feat name='Starship Dodge (space transport)' type='General' /> <feat name='Starship Dodge (starfighter)' type='General' /> <feat name='Starship Operation (capital ship)' type='General' /> <feat name='Starship Operation (space transport)' type='General' /> <feat name='Starship Operation (starfighter)' type='General' /> <feat name='Starship Point Blank Shot (capital ship)' type='General' /> <feat name='Starship Point Blank Shot (space transport)' type='General' /> <feat name='Starship Point Blank Shot (starfighter)' type='General' /> <feat name='Steady' type='General' /> <feat name='Stealthy' type='General' /> <feat name='Summon Storm' type='General' /> <feat name='Sunder' type='General' /> <feat name='Surgery' type='General' /> <feat name='Toughness' type='General' /> <feat name='Track' type='General' /> <feat name='Trick' type='General' /> <feat name='Trustworthy' type='General' /> <feat name='Two-weapon Fighting' type='General' /> <feat name='Vehicle Dodge' type='General' /> <feat name='Weapon Finesse' type='General' /> <feat name='Weapon Focus' type='General' /> <feat name='Weapons Group Proficiency (blaster pistols)' type='General' /> <feat name='Weapons Group Proficiency (blaster rifles)' type='General' /> <feat name='Weapons Group Proficiency (heavy weapons)' type='General' /> <feat name='Weapons Group Proficiency (primitive weapons)' type='General' /> <feat name='Weapons Group Proficiency (simple weapons)' type='General' /> <feat name='Weapons Group Proficiency (slug throwers)' type='General' /> <feat name='Weapons Group Proficiency (starship weapons)' type='General' /> <feat name='Weapons Group Proficiency (vehicle weapons)' type='General' /> <feat name='Weapons Group Proficiency (vibro weapons)' type='General' /> <feat name='Whirlwind Attack' type='General' /> <feat name='Wookiee Brachiation' type='General' /> <feat name='Zero-G Training' type='General' /> </feats> --- NEW FILE: SWd20classes.xml --- <classes> <class level="1" name="Big-Game Hunter" vd="d10"/> <class level="1" name="Blockade Runner" vd="d6"/> <class level="1" name="Bounty Hunter" vd="d10"/> <class level="1" name="Commoner" vd="d0"/> <class level="1" name="Crimelord" vd="d6"/> <class level="1" name="Dark Force Witch" vd="d8"/> <class level="1" name="Dark Side Devotee" vd="d8"/> <class level="1" name="Dark Side Marauder" vd="d10"/> <class level="1" name="Deep Space Pilot" vd="d6"/> <class level="1" name="Diplomat" vd="d0"/> <class level="1" name="Elite Trooper" vd="d10"/> <class level="1" name="Emperor's Hand" vd="d8"/> <class level="1" name="Expert" vd="d0"/> <class level="1" name="First-Contact Specialist" vd="d6"/> <class level="1" name="Force Adept" vd="d8"/> <class level="1" name="Fringer" vd="d8"/> <class level="1" name="Gand Findsman" vd="d8"/> <class level="1" name="Imperial Inquisitor" vd="d10"/> <class level="1" name="Jedi Ace" vd="d8"/> <class level="1" name="Jedi Consular" vd="d8"/> <class level="1" name="Jedi Guardian" vd="d10"/> <class level="1" name="Jedi Investigator" vd="d8"/> <class level="1" name="Jedi Master" vd="d8"/> <class level="1" name="Jedi Weapon Master" vd="d10"/> <class level="1" name="Martial Artist" vd="d10"/> <class level="1" name="Master Gunner" vd="d6"/> <class level="1" name="Naval Officer" vd="d6"/> <class level="1" name="Noble" vd="d6"/> <class level="1" name="Noghri Bodyguard" vd="d8"/> <class level="1" name="Officer" vd="d8"/> <class level="1" name="Privateer" vd="d10"/> <class level="1" name="Scoundrel" vd="d6"/> <class level="1" name="Scout" vd="d8"/> <class level="1" name="Sector Ranger" vd="d8"/> <class level="1" name="Sharpshooter" vd="d6"/> <class level="1" name="Sith Acolyte" vd="d8"/> <class level="1" name="Sith Lord" vd="d10"/> <class level="1" name="Sith Warrior" vd="d10"/> <class level="1" name="Slicer" vd="d6"/> <class level="1" name="Soldier" vd="d10"/> <class level="1" name="Starfighter Ace" vd="d8"/> <class level="1" name="Starship Ace" vd="d8"/> <class level="1" name="Tech Specialist" vd="d6"/> <class level="1" name="Thug" vd="d0"/> <class level="1" name="Vehicle Ace" vd="d8"/> </classes> --- NEW FILE: d20armor.xml --- <ac> <armor name="Blast helmet, vest" cost="500" type="Light" maxdex="5" bonus="2" checkpenalty="-1" weight="3" speed="10" speed6="6" > <description >This armour consists of a lightweight helmet and a composite vest that, when worn together, offer limited protection against shrapnel, mele weapons, slugthrowers, and blasters </description > </armor> <armor name="Combat jumpsuit" cost="1500" type="Light" maxdex="4" bonus="3" checkpenalty="-3" weight="8" speed="10" speed6="6" > <description > </description > </armor> <armor name="Padded flight suit" cost="800" type="Light" maxdex="4" bonus="2" checkpenalty="-2" weight="5" speed="10" speed6="6" > <description > </description > </armor> <armor name="Armoured flight suit" cost="4000" type="Medium" maxdex="3" bonus="4" checkpenalty="-4" weight="20" speed="8" speed6="4" > <description > </description > </armor> <armor name="Battle armour, padded" cost="2000" type="Medium" maxdex="3" bonus="4" checkpenalty="-4" weight="13" speed="8" speed6="4" > <description > </description > </armor> <armor name="Battle armour, medium" cost="6000" type="Medium" maxdex="2" bonus="5" checkpenalty="-5" weight="16" speed="8" speed6="4" > <description > </description > </armor> <armor name="Armoured spacesuit" cost="10000" type="Heavy" maxdex="1" bonus="6" checkpenalty="-6" weight="45" speed="6" speed6="2" > <description > </description > </armor> <armor name="Battle armor, heavy" cost="12000" type="Heavy" maxdex="0" bonus="7" checkpenalty="-7" weight="35" speed="6" speed6="2" > <description > </description > </armor> <armor name="Corellian powersuit" cost="10000" type="Powered" maxdex="0" bonus="4" checkpenalty="-4" weight="18" speed="8" speed6="4" > <description > </description > </armor> <armor name="Stormtrooper armour" cost="8000" type="Powered" maxdex="2" bonus="5" checkpenalty="-2" weight="16" speed="8" speed6="4" > <description > </description > </armor> <armor name="Battleframe" cost="12000" type="Powered" maxdex="0" bonus="3" checkpenalty="-8" weight="20" speed="6" speed6="2" > <description > </description > </armor> </ac> --- NEW FILE: d20weapons.xml --- <weapons> <weapon mod="0" name="Amphistaff" cost="5000" category="Exotic Weapon Proficiency (amphistaff)" size="Large" damage="1d6" critical="20" range="0" weight="2" type="Piercing/Slashing" > <description ></description > </weapon> <weapon mod="0" name="Atlatl" cost="50" category="Exotic Weapon Proficiency (atlatl)" size="Medium" damage="2d4" critical="20" range="0" weight="2" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Cannon)" cost="3000" category="Weapons Group Proficiency (heavy weapons)" size="Large" damage="4d8" critical="19" range="40" weight="18" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Carbine)" cost="900" category="Weapons Group Proficiency (blaster rifles)" size="Medium" damage="3d8" critical="19" range="20" weight="2" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (E-Web Repeating)" cost="8000" category="Weapons Group Proficiency (heavy weapons)" size="Large" damage="6d8" critical="19" range="80" weight="38" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Heavy Pistol)" cost="750" category="Weapons Group Proficiency (blaster pistols)" size="Medium" damage="3d8" critical="20" range="8" weight="1" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Heavy Repeating)" cost="4000" category="Weapons Group Proficiency (heavy weapons)" size="Large" damage="4d8" critical="19" range="30" weight="12" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Hold-out)" cost="300" category="Weapons Group Proficiency (blaster pistols)" size="Small" damage="3d4" critical="20" range="4" weight="0" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Light Repeating)" cost="2000" category="Weapons Group Proficiency (blaster rifles)" size="Large" damage="3d8" critical="19" range="40" weight="6" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Pistol)" cost="500" category="Weapons Group Proficiency (blaster pistols)" size="Small" damage="3d6" critical="20" range="10" weight="1" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Rifle, Sporting)" cost="800" category="Weapons Group Proficiency (blaster rifles)" size="Medium" damage="3d6" critical="19" range="40" weight="2" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Rifle)" cost="1000" category="Weapons Group Proficiency (blaster rifles)" size="Medium" damage="3d8" critical="19" range="40" weight="4" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Blaster (Sporting)" cost="300" category="Weapons Group Proficiency (blaster pistols)" size="Small" damage="3d4" critical="20" range="8" weight="1" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Bow" cost="300" category="Weapons Group Proficiency (primitive weapons)" size="Medium" damage="1d8" critical="20" range="12" weight="1" type="Piercing" > <description ></description > </weapon> <weapon mod="0" name="Bowcaster" cost="1500" category="Exotic Weapon Proficiency (bowcaster)" size="Large" damage="3d10" critical="19" range="10" weight="8" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Cesta" cost="100" category="Exotic Weapon Proficiency (cesta)" size="Large" damage="2d4" critical="20" range="0" weight="2" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Club/Baton" cost="15" category="Weapons Group Proficiency (simple weapons)" size="Medium" damage="1d6" critical="20" range="0" weight="2" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Combat Gloves" cost="200" category="Weapons Group Proficiency (simple weapons)" size="Medium" damage="+2" critical="0" range="0" weight="1" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Force Pike" cost="500" category="Weapons Group Proficiency (vibro weapons)" size="Large" damage="2d8" critical="20" range="0" weight="2" type="Slashing" > <description ></description > </weapon> <weapon mod="0" name="Gaderffii" cost="50" category="Exotic Weapon Proficiency (gaderffii)" size="Large" damage="1d8" damage2= "1d6" critical="20" range="0" weight="2" type="Piercing/Slashing" > <description ></description > </weapon> <weapon mod="0" name="Grenade (Frag)" cost="200" category="Weapons Group Proficiency (simple weapons)" size="Tiny" damage="4d6+1" critical="0" range="4" weight="0" type="Slashing" > <description ></description > </weapon> <weapon mod="0" name="Grenade (Stun)" cost="250" category="Weapons Group Proficiency (simple weapons)" size="Tiny" damage="-" critical="0" range="4" weight="0" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Ion Gun (Pistol)" cost="250" category="Weapons Group Proficiency (blaster pistols)" size="Small" damage="3d6" critical="20" range="8" weight="1" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Ion Gun (Rifle)" cost="800" category="Weapons Group Proficiency (blaster rifles)" size="Medium" damage="3d8" critical="19" range="30" weight="2" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Knife" cost="25" category="Weapons Group Proficiency (simple weapons)" size="Small" damage="1d4" critical="20" range="0" weight="1" type="Piercing" > <description ></description > </weapon> <weapon mod="0" name="Lightsaber" cost="3000" category="Exotic Weapon Proficiency (lightsaber)" size="Medium" damage="2d8" critical="19" range="0" weight="1" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Lightsaber (Double-bladed)" cost="7000" category="Exotic Weapon Proficiency (double lightsaber)" size="Medium" damage="2d8" damage2="2d8" critical="19" range="0" weight="2" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Net" cost="25" category="Weapons Group Proficiency (primitive weapons)" size="Medium" damage="-" critical="0" range="2" weight="4" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Quarterstaff" cost="65" category="Weapons Group Proficiency (simple weapons)" size="Large" damage="1d6" damage2 = "1d6" critical="20" range="0" weight="1" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Razorbug" cost="0" category="Weapons Group Proficiency (simple weapons)" size="Small" damage="2d6+2" critical="20" range="20" weight="1" type="Slashing" > <description ></description > </weapon> <weapon mod="0" name="Sling" cost="35" category="Weapons Group Proficiency (primitive weapons)" size="Small" damage="1d4" critical="20" range="6" weight="0" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Slugthrower (Pistol)" cost="275" category="Weapons Group Proficiency (slug throwers)" size="Small" damage="2d6" critical="20" range="10" weight="1" type="Piercing" > <description ></description > </weapon> <weapon mod="0" name="Slugthrower (Rifle)" cost="300" category="Weapons Group Proficiency (slug throwers)" size="Medium" damage="2d8" critical="20" range="20" weight="4" type="Piercing" > <description ></description > </weapon> <weapon mod="0" name="Spear" cost="60" category="Weapons Group Proficiency (primitive weapons)" size="Large" damage="1d8" critical="20" range="0" weight="1" type="Piercing" > <description ></description > </weapon> <weapon mod="0" name="Stun Baton" cost="500" category="Weapons Group Proficiency (simple weapons)" size="Medium" damage="-" critical="20" range="0" weight="1" type="Bludgeoning" > <description ></description > </weapon> <weapon mod="0" name="Thermal Detonator" cost="200" category="Weapons Group Proficiency (simple weapons)" size="Tiny" damage="8d6+6" critical="20" range="4" weight="0" type="Energy" > <description ></description > </weapon> <weapon mod="0" name="Thud Bug" cost="0" category="Weapons Group Proficiency (simple weapons)" size="Small" damage="2d6" critical="20" range="20" weight="1" type="Slashing" > <description ></description > </weapon> <weapon mod="0" name="Tsaisi" cost="7500" category="Exotic Weapon Proficiency (tsaisi)" size="Medium" damage="1d6" critical="20" range="0" weight="1" type="Piercing/Slashing" > <description ></description > </weapon> <weapon mod="0" name="Vibro-Ax" cost="500" category="Weapons Group Proficiency (vibro weapons)" size="Large" damage="2d10" critical="20" range="0" weight="11" type="Slashing" > <description ></description > </weapon> <weapon mod="0" name="Vibroblade" cost="250" category="Weapons Group Proficiency (vibro weapons)" size="Medium" damage="2d6" critical="20" range="0" weight="2" type="Slashing" > <description ></description > </weapon> <weapon mod="0" name="Vibrodagger" cost="200" category="Weapons Group Proficiency (vibro weapons)" size="Small" damage="2d4" critical="20" range="0" weight="1" type="Slashing" > <description ></description > </weapon> </weapons> |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:27
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/gametree In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/gametree Added Files: __init__.py gametree.py gametree_version.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: gametree_version.py --- ### this file holds the gametree version ### GAMETREE_VERSION = "1.0" --- NEW FILE: gametree.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: gametree.py # Author: Chris Davis # Maintainer: # Version: # $Id: gametree.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: The file contains code fore the game tree shell # __version__ = "$Id: gametree.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" from orpg.orpg_wx import * from orpg.orpg_windows import * import orpg.dirpath import orpg.tools.config_files from orpg.orpg_xml import * from nodehandlers import core from gametree_version import GAMETREE_VERSION import string import urllib import time import os STD_MENU_DELETE = wxNewId() STD_MENU_DESIGN = wxNewId() STD_MENU_USE = wxNewId() STD_MENU_PP = wxNewId() STD_MENU_RENAME = wxNewId() STD_MENU_SEND = wxNewId() STD_MENU_SAVE = wxNewId() STD_MENU_ICON = wxNewId() STD_MENU_CLONE = wxNewId() STD_MENU_ABOUT = wxNewId() STD_MENU_HTML = wxNewId() STD_MENU_EMAIL = wxNewId() STD_MENU_CHAT = wxNewId() STD_MENU_WHISPER = wxNewId() STD_MENU_WIZARD = wxNewId() STD_MENU_NODE_SUBMENU = wxNewId() STD_MENU_NODE_USEFUL = wxNewId() STD_MENU_NODE_USELESS = wxNewId() STD_MENU_NODE_INDIFFERENT = wxNewId() STD_MENU_MAP = wxNewId() TOP_IFILE = wxNewId() TOP_INSERT_URL = wxNewId() TOP_NEW_TREE = wxNewId() TOP_SAVE_TREE = wxNewId() TOP_SAVE_TREE_AS = wxNewId() TOP_TREE_PROP = wxNewId() TOP_FEATURES = wxNewId() class dbl_clk_timer_class(wxTimer): def __init__(self,tree_ctrl): self.tree_ctrl = tree_ctrl wxTimer.__init__(self) def Notify(self): if self.tree_ctrl.rename_flag: self.tree_ctrl.EditLabel(self.item) def Start(self,item): self.item = item wxTimer.Start(self,500,1) # quarter second and the 1 is for a one-shot event class game_tree(wxTreeCtrl): def __init__(self, parent, id, openrpg): wxTreeCtrl.__init__(self,parent,id, wxDefaultPosition, wxDefaultSize,style = wxTR_EDIT_LABELS | wxTR_HAS_BUTTONS) self.myopenrpg = openrpg self.build_img_list() self.build_std_menu() self.nodehandlers = {} EVT_LEFT_DCLICK(self, self.on_ldclick) EVT_RIGHT_DOWN(self, self.on_rclick) EVT_TREE_BEGIN_DRAG(self, id, self.on_drag) EVT_LEFT_UP(self,self.on_left_up) EVT_LEFT_DOWN(self,self.on_left_down) EVT_TREE_END_LABEL_EDIT(self,self.GetId(),self.on_label_change) EVT_TREE_BEGIN_LABEL_EDIT(self,self.GetId(),self.on_label_begin) EVT_CHAR(self,self.on_char) EVT_KEY_UP(self,self.on_key_up) self.id = 1 self.dragging = false self.root_dir = orpg.dirpath.dir_struct["home"] self.last_save_dir = orpg.dirpath.dir_struct["user"] #Create tree from default if it does not exist orpg.tools.config_files.validate_config_file("tree.xml","default_tree.xml") self.myopenrpg.add_component("tree",self) #build tree self.root = self.AddRoot("Game Tree",self.icons['gear']) # click timer self.dbl_clk_timer = dbl_clk_timer_class(self) self.was_labeling = 0 self.rename_flag = 0 self.image_cache = {} def initialize(self,openrpg): #add the tree to the openrpg object self.myopenrpg = openrpg self.myopenrpg.add_component("tree",self) #build tree s = self.myopenrpg.get_component('settings') self.root = self.AddRoot("Game Tree",self.icons['gear']) self.load_tree(s.get_setting("gametree")) # event = wxKeyEvent # set to be called by wxWindows by EVT_CHAR macro in __init__ def on_key_up(self, evt): #print "key up" key_code = evt.GetKeyCode() if self.dragging and (key_code == WXK_SHIFT): curSelection = self.GetSelection() cur = wxStockCursor(wxCURSOR_ARROW) self.SetCursor(cur) self.dragging = false obj = self.GetPyData(curSelection) self.SelectItem(curSelection) if(isinstance(obj,core.node_handler)): obj.on_drop(evt) self.drag_obj = None #print "dragging object dropped!" evt.Skip() def on_char(self, evt): key_code = evt.GetKeyCode() curSelection = self.GetSelection() # Get the current selection #name = self.GetItemText(curSelection) if evt.ShiftDown() and ((key_code == WXK_UP) or (key_code == WXK_DOWN)) and not self.dragging: curSelection = self.GetSelection() obj = self.GetPyData(curSelection) self.SelectItem(curSelection) if(isinstance(obj,core.node_handler)): self.dragging = true cur = wxStockCursor(wxCURSOR_HAND) self.SetCursor(cur) self.drag_obj = obj #print "dragging object set!" elif key_code == WXK_LEFT: self.Collapse(curSelection) elif key_code == WXK_DELETE: # Handle the delete key #curSelection = self.GetSelection() # Get the current selection if curSelection: nextSelect = self.GetItemParent(curSelection) self.on_del(evt) try: if self.GetItemText(nextSelect) != "": self.SelectItem(nextSelect) except: pass evt.Skip() ## locate_valid_tree ## GUI based dialogs to locate/fix missing treefile issues --Snowdog 3/05 def locate_valid_tree(self,error,msg,dir,filename): """prompts the user to locate a new tree file or create a new one""" response = wxMessageBox(msg,error,wxYES|wxNO|wxICON_ERROR) if response == wxYES: file = None filetypes = "Gametree (*.xml)|*.xml|All files (*.*)|*.*" dlg = wxFileDialog(self, "Locate Gametree file", dir, filename, filetypes,wxOPEN | wxCHANGE_DIR) if dlg.ShowModal() == wxID_OK: file = dlg.GetPath() dlg.Destroy() if not file: self.load_tree(error=1) else: self.load_tree(file) return else: orpg.tools.config_files.validate_config_file("tree.xml","default_tree.xml") self.load_tree(error=1) return def load_tree(self,filename=orpg.dirpath.dir_struct["user"]+'tree.xml',error=0): #self.filename = filename s = self.myopenrpg.get_component('settings') s.set_setting("gametree",filename) tmp = None xml_dom = None xml_doc = None try: print "Reading Gametree file: " + filename +"...", tmp = open(filename,"r") xml_doc = parseXml(tmp.read()) if xml_doc == None: pass else: xml_dom = xml_doc._get_documentElement() tmp.close() print "done." except IOError: emsg = "Gametree Missing!\n"+filename+" cannot be found.\n\n"\ "Would you like to locate it?\n"\ "(Selecting 'No' will cause a new default gametree to be generated)" fn = filename[ ((filename.rfind(os.sep))+len(os.sep)):] self.locate_valid_tree("Gametree Error", emsg, orpg.dirpath.dir_struct["user"], fn) return if not xml_dom: os.rename(filename,filename+".corrupt") fn = filename[ ((filename.rfind(os.sep))+len(os.sep)):] emsg = "Your gametree is being regenerated.\n\n"\ "To salvage a recent version of your gametree\n"\ "exit OpenRPG and copy the lastgood.xml file in\n"\ "your myfiles directory to "+fn+ "\n"\ "in your myfiles directory.\n\n"\ "lastgood.xml WILL BE OVERWRITTEN NEXT TIME YOU RUN OPENRPG.\n\n"\ "Would you like to select a different gametree file to use?\n"\ "(Selecting 'No' will cause a new default gametree to be generated)" self.locate_valid_tree("Corrupt Gametree!",emsg,orpg.dirpath.dir_struct["user"], fn) return if xml_dom._get_tagName() != "gametree": fn = filename[ ((filename.rfind(os.sep))+len(os.sep)):] emsg = fn+" does not appear to be a valid gametree file.\n\n"\ "Would you like to select a different gametree file to use?\n"\ "(Selecting 'No' will cause a new default gametree to be generated)" self.locate_valid_tree("Invalid Gametree!",emsg,orpg.dirpath.dir_struct["user"], fn) return # get gametree version - we could write conversion code here! self.master_dom = xml_dom try: version = self.master_dom.getAttribute("version") # see if we should load the gametree s = self.myopenrpg.get_component('settings') loadfeatures = int(s.get_setting("LoadGameTreeFeatures")) if loadfeatures: xml_dom = parseXml(open(orpg.dirpath.dir_struct["template"]+"feature.xml","r").read()) xml_dom = xml_dom._get_documentElement() xml_dom = self.master_dom.appendChild(xml_dom) s.set_setting("LoadGameTreeFeatures","0") ## load tree self.CollapseAndReset(self.root) children = self.master_dom._get_childNodes() print "Parsing Gametree Nodes ", for c in children: print '.', self.load_xml(c,self.root) print "done" self.Expand(self.root) self.SetPyData(self.root,self.master_dom) if error != 1: infile = open(filename, "rb") outfile = open(orpg.dirpath.dir_struct["user"]+"lastgood.xml", "wb") outfile.write(infile.read()) else: print "Not overwriting lastgood.xml file." except Exception, e: print e wxMessageBox("Corrupt Tree!\nYour game tree is being regenerated. To\nsalvage a recent version of your gametree\nexit OpenRPG and copy the lastgood.xml\nfile in your myfiles directory\nto "+filename+ "\nin your myfiles directory.\nlastgood.xml WILL BE OVERWRITTEN NEXT TIME YOU RUN OPENRPG.") os.rename(filename,filename+".corrupt") orpg.tools.config_files.validate_config_file("tree.xml","default_tree.xml") self.load_tree(error=1) def build_std_menu(self,obj=None): # build useful menu useful_menu = wxMenu() useful_menu.Append(STD_MENU_NODE_USEFUL,"Use&ful") useful_menu.Append(STD_MENU_NODE_USELESS,"Use&less") useful_menu.Append(STD_MENU_NODE_INDIFFERENT,"&Indifferent") # build standard menu self.std_menu = wxMenu() self.std_menu.SetTitle("game tree") self.std_menu.Append(STD_MENU_USE,"&Use") self.std_menu.Append(STD_MENU_DESIGN,"&Design") self.std_menu.Append(STD_MENU_PP,"&Pretty Print") self.std_menu.AppendSeparator() self.std_menu.Append(STD_MENU_SEND,"Send To Player") self.std_menu.Append(STD_MENU_MAP,"Send To Map") self.std_menu.Append(STD_MENU_CHAT,"Send To Chat") self.std_menu.Append(STD_MENU_WHISPER,"Whisper To Player") self.std_menu.AppendSeparator() # self.std_menu.Append(STD_MENU_DESIGN,"&Edit") #self.std_menu.Append(STD_MENU_RENAME,"&Rename") self.std_menu.Append(STD_MENU_ICON,"Change &Icon") self.std_menu.Append(STD_MENU_DELETE,"D&elete") self.std_menu.Append(STD_MENU_CLONE,"&Clone") self.std_menu.AppendMenu(STD_MENU_NODE_SUBMENU,"Node &Usefulness",useful_menu) self.std_menu.AppendSeparator() self.std_menu.Append(STD_MENU_SAVE,"&Save Node") self.std_menu.Append(STD_MENU_HTML,"E&xport as HTML") #self.std_menu.Append(STD_MENU_WIZARD,"Create &Wizard") #self.std_menu.Append(STD_MENU_EMAIL,"Email") self.std_menu.AppendSeparator() self.std_menu.Append(STD_MENU_ABOUT,"&About") EVT_MENU(self, STD_MENU_SEND, self.on_send_to) #EVT_MENU(self, STD_MENU_RENAME, self.on_rename) EVT_MENU(self, STD_MENU_NODE_INDIFFERENT, self.indifferent) EVT_MENU(self, STD_MENU_NODE_USEFUL, self.useful) EVT_MENU(self, STD_MENU_NODE_USELESS, self.useless) EVT_MENU(self, STD_MENU_DELETE, self.on_del) EVT_MENU(self, STD_MENU_MAP, self.on_send_to_map) EVT_MENU(self, STD_MENU_DESIGN, self.on_node_design) EVT_MENU(self, STD_MENU_USE, self.on_node_use) EVT_MENU(self, STD_MENU_PP, self.on_node_pp) EVT_MENU(self, STD_MENU_SAVE, self.on_save) EVT_MENU(self, STD_MENU_ICON, self.on_icon) EVT_MENU(self, STD_MENU_CLONE, self.on_clone) EVT_MENU(self, STD_MENU_ABOUT, self.on_about) EVT_MENU(self, STD_MENU_CHAT, self.on_send_to_chat) EVT_MENU(self, STD_MENU_WHISPER, self.on_whisper_to) EVT_MENU(self, STD_MENU_HTML, self.on_export_html) #EVT_MENU(self, STD_MENU_WIZARD, self.on_wizard) self.top_menu = wxMenu() self.top_menu.SetTitle("game tree") self.top_menu.Append(TOP_IFILE,"&Insert File") self.top_menu.Append(TOP_INSERT_URL,"Insert &URL") self.top_menu.Append(TOP_FEATURES, "Insert &Features Node") self.top_menu.Append(TOP_NEW_TREE, "&Load New Tree") self.top_menu.Append(TOP_SAVE_TREE,"&Save Tree") self.top_menu.Append(TOP_SAVE_TREE_AS,"Save Tree &As...") self.top_menu.Append(TOP_TREE_PROP,"&Tree Properties") EVT_MENU(self, TOP_IFILE, self.on_insert_file) EVT_MENU(self, TOP_INSERT_URL, self.on_insert_url) EVT_MENU(self, TOP_SAVE_TREE_AS, self.on_save_tree_as) EVT_MENU(self, TOP_SAVE_TREE, self.on_save_tree) EVT_MENU(self, TOP_NEW_TREE, self.on_load_new_tree) EVT_MENU(self, TOP_TREE_PROP, self.on_tree_prop) EVT_MENU(self, TOP_FEATURES, self.on_insert_features) def do_std_menu(self,evt,obj): pt = evt.GetPosition() self.std_menu.Enable(STD_MENU_MAP,obj.map_aware()) self.std_menu.Enable(STD_MENU_CLONE,obj.can_clone()) self.PopupMenu(self.std_menu,pt) def strip_html(self,player): ret_string = "" x = 0 in_tag = 0 for x in range(len(player[0])) : if player[0][x] == "<" or player[0][x] == ">" or in_tag == 1 : if player[0][x] == "<" : in_tag = 1 elif player[0][x] == ">" : in_tag = 0 else : pass else : ret_string = ret_string + player[0][x] return ret_string def on_receive_data(self,data,player): beg = string.find(data,"<tree>") end = string.rfind(data,"</tree>") data = data[6:end] self.insert_xml(data) def on_send_to_chat(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.on_send_to_chat(evt) #chat = self.myopenrpg.get_component('chat') #chat.ParsePost(obj.tohtml(),true,true) def on_whisper_to(self,evt): session = self.myopenrpg.get_component('session') players = session.get_players() opts = [] myid = session.get_id() me = None for p in players: if p[2] != myid: opts.append("("+p[2]+") " + self.strip_html(p)) else: me = p if len(opts): players.remove(me) if len(opts): dlg = wxMultiCheckBoxDlg( self.GetParent(),opts,"Select Players:","Whisper To",[] ) ## dlg = wxMultiCheckBoxDlg(self.GetParent(),opts,"Select Players:","Whisper To",range(len(opts))) if dlg.ShowModal() == wxID_OK: item = self.GetSelection() obj = self.GetPyData(item) selections = dlg.get_selections() chat = self.myopenrpg.get_component('chat') #data = chat.ParsePost(obj.tohtml(),false,true) if len(selections) == len(opts): chat.ParsePost(obj.tohtml(),true,true) else: player_ids = [] for s in selections: player_ids.append(players[s][2]) chat.whisper_to_players(obj.tohtml(),player_ids) def on_export_html(self,evt): f =wxFileDialog(self,"Select a file", self.last_save_dir,"","HTML (*.html)|*.html",wxSAVE) if f.ShowModal() == wxID_OK: item = self.GetSelection() obj = self.GetPyData(item) type = f.GetFilterIndex() file = open(f.GetPath(),"w") data = "<html><head><title>"+obj.master_dom.getAttribute("name")+"</title></head>" data += "<body bgcolor=\"#FFFFFF\" >"+obj.tohtml()+"</body></html>" for tag in ("</tr>","</td>","</th>","</table>","</html>","</body>"): data = data.replace(tag,tag+"\n") file.write(data) file.close() self.last_save_dir, throwaway = os.path.split( f.GetPath() ) f.Destroy() os.chdir(self.root_dir) def indifferent(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.usefulness("indifferent") def useful(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.usefulness("useful") def useless(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.usefulness("useless") def on_email(self,evt): pass def on_send_to(self,evt): session = self.myopenrpg.get_component('session') players = session.get_players() opts = [] myid = session.get_id() me = None for p in players: if p[2] != myid: opts.append("("+p[2]+") " + self.strip_html(p)) else: me = p if len(opts): players.remove(me) ## dlg = wxMultiCheckBoxDlg(self.GetParent(),opts,"Select Players:","Send To",range(len(opts))) dlg = wxMultiCheckBoxDlg( self.GetParent(),opts,"Select Players:","Send To", [] ) if dlg.ShowModal() == wxID_OK: item = self.GetSelection() obj = self.GetPyData(item) xmldata = "<tree>"+obj.toxml()+"</tree>" selections = dlg.get_selections() if len(selections) == len(opts): session.send(xmldata) else: for s in selections: session.send(xmldata,players[s][2]) #obj.toxml() def on_icon(self,evt): icons = self.icons.keys() icons.sort() dlg = wxSingleChoiceDialog(self,"Choose Icon?","Change Icon",icons) if dlg.ShowModal() == wxID_OK: key = dlg.GetStringSelection() item = self.GetSelection() obj = self.GetPyData(item) obj.change_icon(key) dlg.Destroy() def on_wizard(self,evt): item = self.GetSelection() obj = self.GetPyData(item) name = "New " + obj.master_dom.getAttribute("name") icon = obj.master_dom.getAttribute("icon") xml_data = "<nodehandler name=\""+name+"\" icon=\"" + icon + "\" module=\"core\" class=\"node_loader\" >" xml_data += obj.toxml() xml_data += "</nodehandler>" self.insert_xml(xml_data) def on_clone(self,evt): item = self.GetSelection() obj = self.GetPyData(item) if obj.can_clone(): self.insert_xml(obj.toxml()) def on_save(self,evt): "save node to a xml file" item = self.GetSelection() obj = self.GetPyData(item) obj.on_save(evt) os.chdir(self.root_dir) def on_save_tree_as(self,evt): f =wxFileDialog(self,"Select a file", self.last_save_dir,"","*.xml",wxSAVE) if f.ShowModal() == wxID_OK: self.save_tree(f.GetPath()) self.last_save_dir, throwaway = os.path.split( f.GetPath() ) f.Destroy() os.chdir(self.root_dir) def on_save_tree(self,evt=None): s = self.myopenrpg.get_component('settings') filename = s.get_setting("gametree") self.save_tree(filename) def save_tree(self,filename=orpg.dirpath.dir_struct["user"]+'tree.xml'): self.master_dom.setAttribute("version",GAMETREE_VERSION) s = self.myopenrpg.get_component('settings') s.set_setting("gametree",filename) file = open(filename,"w") file.write(toxml(self.master_dom,1)) file.close() def on_load_new_tree(self,evt): f =wxFileDialog(self,"Select a file", self.last_save_dir,"","*.xml",wxOPEN) if f.ShowModal() == wxID_OK: self.load_tree(f.GetPath()) self.last_save_dir, throwaway = os.path.split( f.GetPath() ) f.Destroy() os.chdir(self.root_dir) def on_insert_file(self,evt): "loads xml file into the tree" if self.last_save_dir == ".": self.last_save_dir = orpg.dirpath.dir_struct["user"] f =wxFileDialog(self,"Select a file", self.last_save_dir,"","*.xml",wxOPEN) if f.ShowModal() == wxID_OK: self.insert_xml(open(f.GetPath(),"r").read()) self.last_save_dir, throwaway = os.path.split( f.GetPath() ) f.Destroy() os.chdir(self.root_dir) def on_insert_url(self,evt): "loads xml url into the tree" dlg = wxTextEntryDialog(self,"URL?","Insert URL", "http://") if dlg.ShowModal() == wxID_OK: path = dlg.GetValue() file = urllib.urlopen(path) self.insert_xml(file.read()) dlg.Destroy() def on_insert_features(self,evt): self.insert_xml(open(orpg.dirpath.dir_struct["template"]+"feature.xml","r").read()) def on_tree_prop(self,evt): dlg = gametree_prop_dlg(self,self.myopenrpg.get_component('settings')) if dlg.ShowModal() == wxID_OK: pass dlg.Destroy() def on_node_design(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.on_design(evt) def on_node_use(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.on_use(evt) def on_node_pp(self,evt): item = self.GetSelection() obj = self.GetPyData(item) obj.on_html_view(evt) def on_del(self,evt): status_value = "none" try: item = self.GetSelection() if item: obj = self.GetPyData(item) parent_obj = obj try: status_value = parent_obj.master_dom.getAttribute('status') name = parent_obj.master_dom.getAttribute('name') except: status_value = "none" parent_obj = parent_obj.master_dom._get_parentNode() while status_value<>"useful" and status_value<>"useless": try: status_value = parent_obj.getAttribute('status') name = parent_obj.getAttribute('name') if status_value == "useless": break elif status_value == "useful": break except: status_value = "none" try: parent_obj = parent_obj._get_parentNode() except: break if status_value == "useful": dlg = wxMessageDialog(self, `name` + " And everything beneath it are considered useful. \n\nAre you sure you want to delete this item?",'Important Item',wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION) if dlg.ShowModal() == wxID_YES: obj.delete() else: obj.delete() except: if self.GetSelection() == self.GetRootItem(): msg = wxMessageDialog(None,"You can't delete the root item.","Delete Error",wxOK) else: msg = wxMessageDialog(None,"Unknown error deleting node.","Delete Error",wxOK) msg.ShowModal() msg.Destroy() def on_about(self,evt): item = self.GetSelection() obj = self.GetPyData(item) about = MyAboutBox(self,obj.about()) about.ShowModal() about.Destroy() def on_send_to_map(self,evt): item = self.GetSelection() obj = self.GetPyData(item) if hasattr(obj,"on_send_to_map"): obj.on_send_to_map(evt) def insert_xml(self,txt): #Updated to allow safe merging of gametree files #without leaving an unusable and undeletable node. # -- Snowdog 8/03 xml_dom = parseXml(txt) if xml_dom == None: wxMessageBox("Import Failed: Invalid or missing node data") return xml_temp = xml_dom._get_documentElement() if not xml_temp: wxMessageBox("Error Importing Node or Tree") return if xml_temp._get_tagName() == "gametree": children = xml_temp._get_childNodes() for c in children: self.load_xml(c,self.root) return if not xml_dom: wxMessageBox("XML Error") return xml_dom = xml_dom._get_firstChild() child = self.master_dom._get_firstChild() xml_dom = self.master_dom.insertBefore(xml_dom,child) self.load_xml(xml_dom,self.root,self.root) def build_img_list(self): "make image list" helper = img_helper() self.icons = { } self._imageList=wxImageList(16,16,false) man = open(orpg.dirpath.dir_struct["icon"]+"icons.xml","r") xml_dom = parseXml(man.read()) man.close() xml_dom = xml_dom._get_documentElement() node_list = xml_dom._get_childNodes() for n in node_list: key = n.getAttribute("name") path = orpg.dirpath.dir_struct["icon"] + n.getAttribute("file") img = helper.load_file(path) self.icons[key] = self._imageList.Add(img) self.SetImageList(self._imageList) def load_xml(self,xml_dom,parent_node,prev_node=None): #add the first tree node i = 0 text = xml_dom.getAttribute("name") icon = xml_dom.getAttribute("icon") if self.icons.has_key(icon): i = self.icons[icon] name = xml_dom._get_nodeName() if prev_node: if prev_node == parent_node: new_tree_node = self.PrependItem(parent_node,text,i,i) else: new_tree_node = self.InsertItem(parent_node,prev_node,text,i,i) else: new_tree_node = self.AppendItem(parent_node,text,i,i) #create a nodehandler or continue loading xml into tree if name == "nodehandler": wxBeginBusyCursor() try: mod = xml_dom.getAttribute("module") mod = string.split(mod)[0] py_class = xml_dom.getAttribute("class") py_class = string.split(py_class)[0] cmd = "from nodehandlers import " + mod exec cmd cmd = "self.nodehandlers[self.id] = "+mod+"."+py_class+"(xml_dom,new_tree_node,self.myopenrpg)" exec cmd self.SetPyData(new_tree_node,self.nodehandlers[self.id]) obj = self.nodehandlers[self.id] bmp = obj.get_scaled_bitmap(16,16) if bmp: self.cached_load_of_image(bmp,new_tree_node,) #self.Expand(new_tree_node) self.id = self.id + 1 except StandardError, er: wxMessageBox("Error Info:\n" + str(er),"Tree Node Load Error") self.Delete(new_tree_node) parent = xml_dom._get_parentNode() parent.removeChild(xml_dom) wxEndBusyCursor() return new_tree_node def cached_load_of_image(self,bmp_in,new_tree_node): image_list = self.GetImageList() img = wxImageFromBitmap(bmp_in) img_data = img.GetData() image_index = None for key in self.image_cache.keys(): if self.image_cache[key] == str(img_data): image_index = key break if image_index is None: image_index = image_list.Add(bmp_in) self.image_cache[image_index] = img_data self.SetItemImage(new_tree_node,image_index) self.SetItemImage(new_tree_node,image_index,wxTreeItemIcon_Selected) return image_index def on_rclick(self,evt): pt = evt.GetPosition() (item, flag) = self.HitTest(pt) if item.IsOk(): obj = self.GetPyData(item) self.SelectItem(item) if(isinstance(obj,core.node_handler)): obj.on_rclick(evt) else: self.PopupMenu(self.top_menu,pt) else: self.PopupMenu(self.top_menu,pt) def on_ldclick(self,evt): self.rename_flag = 0 pt = evt.GetPosition() (item, flag) = self.HitTest(pt) if item.IsOk(): obj = self.GetPyData(item) self.SelectItem(item) if(isinstance(obj,core.node_handler)): if not obj.on_ldclick(evt): s = self.myopenrpg.get_component('settings') action = s.get_setting("treedclick") if action == "use": obj.on_use(evt) elif action == "design": obj.on_design(evt) elif action == "print": obj.on_html_view(evt) elif action == "chat": self.on_send_to_chat(evt) def on_left_down(self,evt): pt = evt.GetPosition() (item, flag) = self.HitTest(pt) if item.IsOk(): if self.was_labeling: self.SelectItem(item) self.rename_flag = 0 self.was_labeling = 0 evt.Skip() else: if self.IsSelected(item): # this next if tests to ensure that the mouse up occurred over a label, and not the icon if (flag & wxTREE_HITTEST_ONITEMLABEL) == wxTREE_HITTEST_ONITEMLABEL : self.rename_flag = 1 self.dbl_clk_timer.Start(item) # derived from wxTimer evt.Skip() else: evt.Skip() else: self.SelectItem(item) evt.Skip() def on_left_up(self,evt): if self.dragging: cur = wxStockCursor(wxCURSOR_ARROW) self.SetCursor(cur) self.dragging = false pt = evt.GetPosition() (item, flag) = self.HitTest(pt) if item.IsOk(): obj = self.GetPyData(item) self.SelectItem(item) if(isinstance(obj,core.node_handler)): obj.on_drop(evt) self.drag_obj = None def on_label_change(self,evt): item = evt.GetItem() txt = evt.GetLabel() self.was_labeling = 0 self.rename_flag = 0 if txt != "": obj = self.GetPyData(item) obj.master_dom.setAttribute('name',txt) else: evt.Veto() def on_label_begin(self,evt): if not self.rename_flag: evt.Veto() else: self.was_labeling = 1 item = evt.GetItem() if item == self.GetRootItem(): evt.Veto() def on_drag(self,evt): self.rename_flag = 0 item = self.GetSelection() obj = self.GetPyData(item) self.SelectItem(item) if(isinstance(obj,core.node_handler) and obj.drag): self.dragging = true cur = wxStockCursor(wxCURSOR_HAND) self.SetCursor(cur) self.drag_obj = obj def is_parent_node(self,node,compare_node): parent_node = self.GetItemParent(node) if compare_node == parent_node: #print "parent node" return 1 elif parent_node == self.root: #print "not parent" return 0 else: return self.is_parent_node(parent_node,compare_node) CTRL_TREE_FILE = wxNewId() CTRL_YES = wxNewId() CTRL_NO = wxNewId() CTRL_USE = wxNewId() CTRL_DESIGN = wxNewId() CTRL_CHAT = wxNewId() CTRL_PRINT = wxNewId() class gametree_prop_dlg(wxDialog): def __init__(self,parent,settings): wxDialog.__init__(self,parent,-1,"Game Tree Properties",wxDefaultPosition,wxSize(300,275)) self.settings = settings #sizers sizers = {} sizers['but'] = wxBoxSizer(wxHORIZONTAL) sizers['main'] = wxBoxSizer(wxVERTICAL) sizers['tree'] = wxBoxSizer(wxVERTICAL) sizers['save'] = wxBoxSizer(wxHORIZONTAL) sizers['dclick'] = wxBoxSizer(wxHORIZONTAL) #box sizers box_sizers = {} box_sizers["save"] = wxBoxedSizer( self, "Save On Exit" ) box_sizers["file"] = wxBoxedSizer( self, "Tree File" ) box_sizers["dclick"] = wxBoxedSizer( self, "Double Click Action" ) self.ctrls = { CTRL_TREE_FILE : FileBrowseButtonWithHistory(box_sizers["file"], -1, labelText="" ) , CTRL_YES : wxRadioButton(box_sizers["save"], CTRL_YES, "Yes", style=wxRB_GROUP), CTRL_NO : wxRadioButton(box_sizers["save"], CTRL_NO, "No"), CTRL_USE : wxRadioButton(box_sizers["dclick"], CTRL_USE, "Use", style=wxRB_GROUP), CTRL_DESIGN : wxRadioButton(box_sizers["dclick"], CTRL_DESIGN, "Desgin"), CTRL_CHAT : wxRadioButton(box_sizers["dclick"], CTRL_CHAT, "Chat"), CTRL_PRINT : wxRadioButton(box_sizers["dclick"], CTRL_PRINT, "Pretty Print") } self.ctrls[CTRL_TREE_FILE].SetValue(settings.get_setting("gametree")) opt = settings.get_setting("SaveGameTreeOnExit") self.ctrls[CTRL_YES].SetValue(opt=="1") self.ctrls[CTRL_NO].SetValue(opt=="0") opt = settings.get_setting("treedclick") self.ctrls[CTRL_DESIGN].SetValue(opt=="design") self.ctrls[CTRL_USE].SetValue(opt=="use") self.ctrls[CTRL_CHAT].SetValue(opt=="chat") self.ctrls[CTRL_PRINT].SetValue(opt=="print") sizers['save'].Add(self.ctrls[CTRL_YES],0, wxEXPAND) sizers['save'].Add(wxSize(10,10)) sizers['save'].Add(self.ctrls[CTRL_NO],0, wxEXPAND) sizers['dclick'].Add(self.ctrls[CTRL_USE],0, wxEXPAND) sizers['dclick'].Add(wxSize(10,10)) sizers['dclick'].Add(self.ctrls[CTRL_DESIGN],0, wxEXPAND) sizers['dclick'].Add(wxSize(10,10)) sizers['dclick'].Add(self.ctrls[CTRL_CHAT],0, wxEXPAND) sizers['dclick'].Add(wxSize(10,10)) sizers['dclick'].Add(self.ctrls[CTRL_PRINT],0, wxEXPAND) box_sizers["save"].set_sizer(sizers["save"]) box_sizers["file"].set_ctrl(self.ctrls[CTRL_TREE_FILE]) box_sizers["dclick"].set_sizer(sizers["dclick"]) for box in box_sizers.values(): box.SetSize(wxSize(20,65)) csize = self.GetClientSizeTuple() # buttons sizers['but'].Add(wxButton(self, wxID_OK, "Apply"), 1, wxEXPAND) sizers['but'].Add(wxSize(10,10)) sizers['but'].Add(wxButton(self, wxID_CANCEL, "Cancel"), 1, wxEXPAND) sizers['main'].Add(box_sizers['save'],1, wxEXPAND) sizers['main'].Add(box_sizers['file'],1, wxEXPAND) sizers['main'].Add(box_sizers['dclick'],1, wxEXPAND) sizers['main'].Add(sizers['but'],0, wxEXPAND|wxALIGN_BOTTOM ) sizers['main'].SetDimension(10,10,csize[0]-20,csize[1]-20) self.SetSizer(sizers['main']) EVT_BUTTON(self, wxID_OK, self.on_ok) def on_ok(self,evt): self.settings.set_setting("gametree",self.ctrls[CTRL_TREE_FILE].GetValue()) self.settings.set_setting("SaveGameTreeOnExit",str(self.ctrls[CTRL_YES].GetValue())) if self.ctrls[CTRL_USE].GetValue(): self.settings.set_setting("treedclick","use") elif self.ctrls[CTRL_DESIGN].GetValue(): self.settings.set_setting("treedclick","design") elif self.ctrls[CTRL_PRINT].GetValue(): self.settings.set_setting("treedclick","print") elif self.ctrls[CTRL_CHAT].GetValue(): self.settings.set_setting("treedclick","chat") self.EndModal(wxID_OK) --- NEW FILE: __init__.py --- |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:27
|
Update of /cvsroot/winopenrpg/openrpg1/plugins In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/plugins Added Files: heya.wav xxblank.py xxcac.py xxcherrypy.py xxnamesound.py xxooc.py xxsavewindow.py xxurl2link.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: xxblank.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'Example Plugin' self.author = 'Your Name' self.help = 'Info About your plugin' #You can set variables below here. Always set them to a blank value in this section. Use plugin_enabled #to set their proper values. self.sample_variable = {} def plugin_enabled(self): #You can add new /commands like # self.plugin_addcommand(cmd, function, helptext) self.plugin_addcommand('/test', self.on_test, '- This is an example plugin command') #If you want your plugin to have more then one way to call the same function you can #use self.plugin_commandalias(alias name, command name) #You can also make shortcut commands like the following self.plugin_commandalias('/example', '/me is giving you an example') #if you want your plugin to use custom messages to comunicate with other people using the same plugin #you can add a message handler in a simmilar way to adding a new slash command. The first variable #'tester' in this case is the tage name for your custom xml message. The second variable is the function #you want to handle proccessing your messages when one is recived. #Be sure to delete your handler in plugin_disabled self.plugin_add_msg_handler('xxblank', self.on_xml_recive) #This is where you set any variables that need to be initalized when your plugin starts self.sample_variable = {1:'one', 2:'two'} def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin self.plugin_removecmd('/test') self.plugin_removecmd('/example') #This is the command to delete a message handler self.plugin_delete_msg_handler('tester') #This is how you should destroy a frame when the plugin is disabled #This same method should be used in close_module as well try: self.frame.Destroy() except: pass def on_test(self, cmdargs): #this is just an example function for a command you create. # cmdargs contains everything you typed after the command # so if you typed /test this is a test, cmdargs = this is a test # args are the individual arguments split. For the above example # args[0] = this , args[1] = is , args[2] = a , args[3] = test self.plugin_send_msg('<xxblank>' + cmdargs + '</xxblank>') args = cmdargs.split(None,-1) msg = 'cmdargs = %s' % (cmdargs) self.chat.InfoPost(msg) if len(args) == 0: self.chat.InfoPost("You have no args") else: i = 0 for n in args: msg = 'args[' + str(i) + '] = ' + n self.chat.InfoPost(msg) i += 1 def on_xml_recive(self,id,data,xml_dom): self.chat.InfoPost(self.name + ":: Message recived<br>" + data.replace("<","<").replace(">",">")) def pre_parse(self, text): #This is called just before a message is parsed by openrpg return text def send_msg(self, text, send): #This is called when a message is about to be sent out. #It covers all messages sent by the user, before they have been formatted. #If send is set to 0, the message will not be sent out to other #users, but it will still be posted to the user's chat normally. #Otherwise, send defaults to 1. (The message is sent as normal) return text, send def plugin_incoming_msg(self, text, type, name, player): #This is called whenever a message from someone else is received, no matter #what type of message it is. #The text variable is the text of the message. If the type is a regular #message, it is already formatted. Otherwise, it's not. #The type variable is an integer which tells you the type: 1=chat, 2=whisper #3=emote, 4=info, and 5=system. #The name variable is the name of the player who sent you the message. #The player variable contains lots of info about the player sending the #message, including name, ID#, and currently-set role. #Uncomment the following line to see the format for the player variable. #print player return text, type, name def post_msg(self, text, myself): #This is called whenever a message from anyone is about to be posted #to chat; it doesn't affect the copy of the message that gets sent to others #Be careful; system and info messages trigger this too. return text def refresh_counter(self): #This is called once per second. That's all you need to know. pass --- NEW FILE: xxcac.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'Command Alias Creator' self.author = 'Dj Gilcrease' self.help = "This plugin lets you add Command Aliases.\neg /sits insted of /me sits down" self.newcmdaliases = {} def plugin_enabled(self): self.plugin_addcommand('/cmdalias', self.on_cmdalias, '[cmdalias_name fullcommand] [remove cmdalias_name] [clear] - (eg. <font color="#000000">/cmdalias /sits /me sits down</font> to add a command. OR <font color="#000000">/cmdalias remove /sits</font> to remove a single command. OR <font color="#000000">/cmdalias clear</font to clear the entire list)') self.newcmdaliases = self.plugindb.GetDict("xxcac", "newcmdaliases", {}) for n in self.newcmdaliases: if not self.shortcmdlist.has_key(n) and not self.cmdlist.has_key(n): self.plugin_commandalias(n, self.newcmdaliases[n]) def plugin_disabled(self): self.plugin_removecmd('/cmdalias') for n in self.newcmdaliases: self.plugin_removecmd(n) def on_cmdalias(self, cmdargs): args = cmdargs.split(" ",-1) if len(args) == 0: self.chat.InfoPost("USAGE: /cmdalias [cmdalias_name fullcommand] [remove cmdalias_name] [clear] - (eg. /sits /me sits down)") elif args[0] == 'remove': if self.newcmdaliases.has_key(args[1]): del self.newcmdaliases[args[1]] self.plugindb.SetDict("xxcac", "newcmdaliases", self.newcmdaliases) self.plugin_removecmd(args[1]) elif args[0] == 'clear': for n in self.newcmdaliases: self.plugin_removecmd(n) self.newcmdaliases = {} self.plugindb.SetDict("xxcac", "newcmdaliases", self.newcmdaliases) else: oldcmd = cmdargs[len(args[0])+1:] self.newcmdaliases[args[0]] = oldcmd self.plugindb.SetDict("xxcac", "newcmdaliases", self.newcmdaliases) self.plugin_commandalias(args[0], oldcmd) --- NEW FILE: xxnamesound.py --- import os import orpg.pluginhandler from orpg.tools.orpg_sound import orpg_sound import orpg.dirpath class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'Name Sound' self.author = 'mDuo13' self.help = "This plays a 'hey!' sound whenever your name is said in chat. It is\n" self.help += "not HTML- or case-sensitive. You can also create nicknames to which the plugin\n" self.help += "will also respond. To add a nickname, type '/xxnick add *name*', where *name*\n" self.help += "is the nickname you want to add. Then, whenever *name* is said in chat, you'll\n" self.help += "hear the sound also. You can remove your nicknames by typing\n" self.help += "'/xxnick del*name*' where *name* is the nickname you wish to delete. Neither is\n" self.help += "case sensitive. Additionally, you can see what nicknames you currently have\n" self.help += "with '/xxnick list'." self.antispam = 0 self.names = [] self.soundfile = '' self.soundplayer = '' def plugin_enabled(self): self.plugin_addcommand('/xxnick', self.on_xxnick, 'add name|del name|list - This is the command for the namesound plugin') self.names = self.plugindb.GetList("xxnamesound", "names", []) self.soundfile = orpg.dirpath.dir_struct['plugins'] + 'heya.wav' self.soundplayer = orpg_sound(self.settings.get_setting("UnixSoundPlayer")) if not self.chat.html_strip(self.session.name.lower()) in self.names: self.names.append(self.chat.html_strip(self.session.name.lower())) def plugin_disabled(self): self.plugin_removecmd('/xxnick') def on_xxnick(self, cmdargs): args = cmdargs.split(None,-1) if len(args): name = cmdargs[len(args[0])+1:].lower().strip() if len(args) == 0 or args[0] == 'list': name_list = '' i = 0 for name in self.names: name_list += name if i < len(self.names)-1: name_list += ', ' i += 1 self.chat.InfoPost('Currently chacking for ' + name_list) elif args[0] == 'add': if name not in self.names and name != '': self.names.append(name) self.plugindb.SetList('xxnamesound', 'names', self.names) self.chat.InfoPost('The name ' + name + ' has been added to your nickname list. You will now hear a sound when someone says it in chat.') else: self.chat.InfoPost('The name ' + name + ' is already in your nickname list.') elif args[0] == 'del': if name in self.names: self.names.remove(name) self.plugindb.SetList('xxnamesound', 'names', self.names) self.chat.InfoPost('The name ' + name + ' has been removed from your nickname list.') else: self.chat.InfoPost('The name ' + name + ' is not in your nickname list.') def plugin_incoming_msg(self, text, type, name, player): if self.antispam > 0: return text, type, name for name in self.names: #print self.chat.html_strip(text.lower()).find(name.lower()) if self.chat.html_strip(text.lower()).find(name.lower()) != -1: self.soundplayer.play(self.soundfile) self.antispam = 1 break return text, type, name def refresh_counter(self): #This is called once per second. That's all you need to know. if self.antispam: self.antispam -= 0.04 --- NEW FILE: xxurl2link.py --- import os import orpg.pluginhandler import re class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !chat : instance of the chat window to write to def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'URL to link conversion' self.author = 'tdb30 tb...@wr...' self.help = "This plugin automaticaly wraps urls in link tags\n" self.help += "making them clickable." self.url_regex = None self.mailto_regex = None def plugin_enabled(self): #This is where you set any variables that need to be initalized when your plugin starts self.url_regex = re.compile(r"""\w{3,}://[A-Za-z0-9.=,:/&;?_%~+!$#-]{2,63}\.[A-Za-z0-9.=,:/&;?_%~+!$#-]{2,63}|[\w-]{2,63}\.[\w-]{2,63}\.[A-Za-z]{2,6}(/[A-Za-z0-9.=,:/&;?_%~+!$#-]+)?|[0-9]{2,3}\.[0-9]{2,3}\.[0-9]{1,3}\.[0-9]{1,3}""", re.I) self.mailto_regex = re.compile(r"""(mailto:)?[\w._-]+@[A-Za-z0-9.-]+""", re.I) def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin pass def send_msg(self, text, send): text = self.link_emails(text) text = self.link_urls(text) return text, send def plugin_incoming_msg(self, text, type, name, player): text = self.link_emails(text) text = self.link_urls(text) return text, type, name def link_urls(self, text): #The modified text accumulates into text2 so that the indices of the regex matches #in the original text variable don't get screwed up by the replacement. text2 = "" url = self.url_regex.search(text) if not url:#so that it doesn't accidentally delete the message text2 = text textafterurl = "" while url: urltext = url.group() if urltext.find("://")<0:#it might not load the web browser but is usable. For example, "maps.google.com" urltext = "http://"+urltext textbeforeurl = text[:url.start()] textafterurl = text[url.end():] if not (len(re.findall("<",textbeforeurl)) > len(re.findall(">",textbeforeurl))) and text[url.start()-1]!="@": text2 += textbeforeurl + "<a href='" + urltext + "'>" + url.group() + "</a>" else: text2 += textbeforeurl + url.group()#not urltext, because we don't wanna screw with it text = textafterurl url = self.url_regex.search(text) else:#once it's done -- this happens whether or not it found a URL to begin with text2 += textafterurl return text2 def link_emails(self, text): #The modified text accumulates into text2 so that the indices of the regex matches #in the original text variable don't get screwed up by the replacement. #the main differences between this and the link_urls function are: # (a) this one uses the mailto_regex instead of url_regex # (b) this one doesn't append http:// but rather mailto: text2 = "" url = self.mailto_regex.search(text) if not url:#so that it doesn't accidentally delete the message text2 = text textafterurl = "" while url: urltext = url.group() if urltext.find("mailto:")<0:#it's just a plain e-mail like md...@ya... instead of a mailto URL urltext = "mailto:"+urltext textbeforeurl = text[:url.start()] textafterurl = text[url.end():] if not (len(re.findall("<",textbeforeurl)) > len(re.findall(">",textbeforeurl))): #here it doesn't use urltext, but rather the "prettier" version (without mailto:) in the displayed text #even though the href URL is actually a mailto. text2 += textbeforeurl + "<a href='" + urltext + "'>" + url.group() + "</a>" else: text2 += textbeforeurl + url.group()#not urltext, because we don't wanna screw with it text = textafterurl url = self.mailto_regex.search(text) else:#once it's done -- this happens whether or not it found a URL to begin with text2 += textafterurl return text2 --- NEW FILE: xxcherrypy.py --- import os import orpg.pluginhandler import thread from cherrypy import cpg import socket class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'CherryPy Web Server' self.author = 'Dj Gilcrease' self.help = 'This plugin turns OpenRPG into a Web server\n' self.help += 'allowing you to host your map and mini files localy' #You can set variables below here. Always set them to a blank value in this section. Use plugin_enabled #to set their proper values. self.isServerRunning = 'off' self.host = 0 def plugin_enabled(self): self.plugin_addcommand('/cherrypy', self.on_cherrypy, '[on | off | status] - This controls the CherryPy Web Server') tmp = socket.gethostbyname_ex('') for ip in tmp[2]: if ip[:7] == '192.168' or ip[:3] == '10.' or ip == '127.0.0.1' or (ip[:3] == '172' and (int(ip[5:6]) >= 16 and int(ip[5:6]) <=32)) : continue else: self.host = ip def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin self.plugin_removecmd('/cherrypy') cpg.server.stop() self.isServerRunning = 'off' def on_cherrypy(self, cmdargs): args = cmdargs.split(None,-1) if len(args) == 0 or args[0] == 'status': self.chat.InfoPost("CherryPy Web Server is currently: " + self.isServerRunning) self.chat.InfoPost("CherryPy Web Server address is: http://" + self.host + '/webfiles/') elif args[0] == 'on' and self.isServerRunning == 'off': self.webserver = thread.start_new_thread(self.startServer, (80,)) self.isServerRunning = 'on' elif args[0] == 'off' and self.isServerRunning == 'on': cpg.server.stop() self.isServerRunning = 'off' self.chat.InfoPost("CherryPy Web Server is now disabled") def startServer(self, port): try: if self.host == 0: raise Exception("Invalid IP address.<br>This error means you are behind a router or some other form of network that is giving you a Privet IP only (ie. 192.168.x.x, 10.x.x.x, 172.16 - 32.x.x)") self.chat.InfoPost("CherryPy Web Server is now running on http://" + self.host + '/webfiles/') cpg.server.start(configMap = {'staticContentList': [['images', r''+orpg.dirpath.dir_struct["icon"]+''],['webfiles', r''+orpg.dirpath.dir_struct["user"]+'webfiles/']], 'socketPort': port, 'logToScreen': 0, 'logFile':orpg.dirpath.dir_struct["user"]+'webfiles/log.txt', 'sessionStorageType':'ram', 'threadPool':10, 'sessionTimeout':30, 'sessionCleanUpDelay':30}) except Exception, e: self.chat.InfoPost("FAILED to start server!") self.chat.InfoPost(str(e)) self.isServerRunning = 'off' --- NEW FILE: heya.wav --- (This appears to be a binary file; contents omitted.) --- NEW FILE: xxsavewindow.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !openrpg : instance of the the base openrpg control def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'SaveWindow' self.author = 'mDuo13' self.help = "Saves the size and position of your OpenRPG window, as well as\n" self.help += "whether or not it is maximized. You must set the plugin to load on startup\n" self.help += "in order for this to work." #You can set variables below here. Always set them to a blank value in this section. Use plugin_enabled #to set their proper values. def plugin_enabled(self): if self.name in self.startplugs: win_xpos = self.plugindb.GetString(self.name,"win_xpos","-1") win_ypos = self.plugindb.GetString(self.name,"win_ypos","-1") maximized = self.plugindb.GetString(self.name,"maximized","0") win_xsize = self.plugindb.GetString(self.name,"win_xsize","-1") win_ysize = self.plugindb.GetString(self.name,"win_ysize","-1") x = int(win_xpos) y = int(win_ypos) is_maxed = int(maximized) width = int(win_xsize) height = int(win_ysize) if not is_maxed: self.parent.SetDimensions(x,y,width,height) self.parent.Maximize(is_maxed) def plugin_disabled(self): #This is called when OpenRPG shuts down (x_size,y_size) = self.parent.GetSizeTuple() (x_pos,y_pos) = self.parent.GetPositionTuple() is_maximized = self.bool2int(self.parent.IsMaximized()) self.plugindb.SetString(self.name,"win_xsize",str(x_size)) self.plugindb.SetString(self.name,"win_ysize",str(y_size)) self.plugindb.SetString(self.name,"win_xpos",str(x_pos)) self.plugindb.SetString(self.name,"win_ypos",str(y_pos)) self.plugindb.SetString(self.name,"maximized",str(is_maximized)) def bool2int(self, x): if x: return 1 else: return 0 --- NEW FILE: xxooc.py --- import os import orpg.pluginhandler class Plugin(orpg.pluginhandler.PluginHandler): # Initialization subroutine. # # !self : instance of self # !chat : instance of the chat window to write to def __init__(self, openrpg, plugindb, parent): orpg.pluginhandler.PluginHandler.__init__(self, openrpg, plugindb, parent) # The Following code should be edited to contain the proper information self.name = 'OOC Comments Tool' self.author = 'mDuo13' self.help = "Type '/ooc *message*' to send '(( *message* ))' -- it just preformats\n" self.help += "out of character comments for you." def plugin_enabled(self): #This is where you set any variables that need to be initalized when your plugin starts self.plugin_addcommand('/ooc', self.on_ooc, 'message - This puts (( message )) to let other players know you are talking out of character') def plugin_disabled(self): #Here you need to remove any commands you added, and anything else you want to happen when you disable the plugin #such as closing windows created by the plugin self.plugin_removecmd('/ooc') def on_ooc(self, cmdargs): #this is just an example function for a command you create create your own self.chat.ParsePost('(( ' + cmdargs + ' ))', 1, 1) |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:27
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/dieroller In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/dieroller Added Files: HOWTO.txt __init__.py d20.py die.py dieroller.txt hackmaster.py hero.py shadowrun.py sr4.py srex.py utils.py wod.py wodex.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: HOWTO.txt --- HOW TO CREATE A NEW DIE ROLLER So you want a make a new roller or add a new option? here's a short guide. Step 1: Create a new die roller sub class. You need to derive a new die roller class from an existing die roller class. Most likely, this will be the std die roller class. The basics would look like this: class new_roller(std): def __init__(self,source=[]): std.__init__(self,source) ..... .... Step 2: Implement new methods and/or override existing ones. Now, you just need to implement any new die options and override any existing ones that you want to act differently. The most common options to override are the sum and __str__ functions. Sum is used to determine the result of the rolls and __str__ is used to display the results in a user friendly string. For example: class new_roller(std): def __init__(self,source=[]): std.__init__(self,source) ..... def myoption(self,param): .... def sum(self): .... def __str__(self): .... REMEMBER! Always return an instance of your die roller for each option expect str and sum. Step 3: Modify Utils.py You need to make some minor modifications to utils.py to facilitate your new roller. You need to a) add an import call for your roller, and b) add your roller to the list of available rollers. For example: from die import * # add addtional rollers here from myroller import * .... rollers = ['std','wod','d20','myroller'] Step 4: You're done! Test it and make sure it works. When you think its done, send it to the openrpg developers and they might include it in future releases. -Chris Davis --- NEW FILE: wodex.py --- ## a vs die roller as used by WOD games #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: wodex.py # Original Author: Darloth # Maintainer: # Original Version: 1.0 # # Description: A modified form of the World of Darkness die roller to # conform to ShadowRun rules-sets, then modified back to the WoD for # the new WoD system. Thanks to the ORPG team # for the original die rollers. # Much thanks to whoever wrote the original shadowrun roller (akoman I believe) from die import * __version__ = "$Id: wodex.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" class wodex(std): def __init__(self,source=[]): std.__init__(self,source) def vs(self,actualtarget=6): return oldwodVs(self,actualtarget,(6)) def wod(self,actualtarget=8): return newwodVs(self,actualtarget,(8)) def vswide(self,actualtarget=6,maxtarget=10): #wide simply means it reports TNs from 2 to a specified max. return oldwodVs(self,actualtarget,2,maxtarget) class oldwodVs(std): def __init__(self,source=[],actualtarget=6,mintn=2,maxtn=10): std.__init__(self, source) if actualtarget > 10: actualtarget = 10 if mintn > 10: mintn = 10 if maxtn > 10: maxtn = 10 if actualtarget < 2: self.target = 2 else: self.target = actualtarget #if the target number is higher than max (Mainly for wide rolls) then increase max to tn if actualtarget > maxtn: maxtn = actualtarget if actualtarget < mintn: mintn = actualtarget #store minimum for later use as well, also in result printing section. if mintn < 2: self.mintn = 2 else: self.mintn = mintn self.maxtn = maxtn #store for later use in printing results. (Yeah, these comments are now disordered) # WoD etc uses d10 but i've left it so it can roll anything openended # self.openended(self[0].sides) #count successes, by looping through each die, and checking it against the currently set TN #1's subtract successes. def __sum__(self): s = 0 for r in self.data: if r >= self.target: s += 1 elif r == 1: s -= 1 return s #a modified sum, but this one takes a target argument, and is there because otherwise it is difficult to loop through #tns counting successes against each one without changing target, which is rather dangerous as the original TN could #easily be lost. 1s subtract successes from everything. def xsum(self,curtarget): s = 0 for r in self.data: if r >= curtarget: s += 1 elif r == 1: s -= 1 return s def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] Results: " #cycle through from mintn to maxtn, summing successes for each separate TN for targ in range(self.mintn,self.maxtn+1): if (targ == self.target): myStr += "<b>" myStr += "(" + str(self.xsum(targ)) + " vs " + str(targ) + ") " if (targ == self.target): myStr += "</b>" else: myStr = "[] = (0)" return myStr class newwodVs(std): def __init__(self,source=[],actualtarget=8,mintn=8,maxtn=8): std.__init__(self, source) if actualtarget > 30: actualtarget = 30 if mintn > 10: mintn = 10 if maxtn > 10: maxtn = 10 if actualtarget < 2: self.target = 2 else: self.target = actualtarget #if the target number is higher than max (Mainly for wide rolls) then increase max to tn if actualtarget > maxtn: maxtn = actualtarget if actualtarget < mintn: mintn = actualtarget #store minimum for later use as well, also in result printing section. if mintn < 2: self.mintn = 2 else: self.mintn = mintn self.maxtn = maxtn #store for later use in printing results. (Yeah, these comments are now disordered) # WoD etc uses d10 but i've left it so it can roll anything openended # self.openended(self[0].sides) #a modified sum, but this one takes a target argument, and is there because otherwise it is difficult to loop through #tns counting successes against each one without changing target, which is rather dangerous as the original TN could #easily be lost. 1s subtract successes from original but not re-rolls. def xsum(self,curtarget,subones=1): s = 0 done = 1 for r in self.data: if r >= curtarget: s += 1 elif ((r == 1) and (subones == 1)): s -= 1 if r == 10: done = 0 subones = 0 self.append(di(10)) if done == 1: return s else: return self.xsum(0) def openended(self,num): if num <= 1: self done = 1 for i in range(len(self.data)): if self.data[i].lastroll() == num: self.data[i].extraroll() done = 0 if done: return self else: return self.openended(num) def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] Results: " #cycle through from mintn to maxtn, summing successes for each separate TN for targ in range(self.mintn,self.maxtn+1): if (targ == self.target): myStr += "<b>" myStr += "(" + str(self.xsum(targ)) + " vs " + str(targ) + ") " if (targ == self.target): myStr += "</b>" else: myStr = "[] = (0)" return myStr --- NEW FILE: srex.py --- ## a vs die roller as used by WOD games #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: srex.py # Original Author: Michael Edwards (AKA akoman) # Maintainer: # Original Version: 1.0 # # Description: A modified form of the World of Darkness die roller to # conform to ShadowRun rules-sets. Thanks to the ORPG team # for the original die rollers. # Thanks to tdb30_ for letting me think out loud with him. # I take my hint from the HERO dieroller: It creates for wildly variant options # Further, .vs and .open do not work together in any logical way. One method of # chaining them results in a [Bad Dice Format] and the other results in a standard # output from calling .open() # vs is a classic 'comparison' method function, with one difference. It uses a # c&p'ed .open(int) from die.py but makes sure that once the target has been exceeded # then it stops rerolling. The overhead from additional boolean checking is probably # greater than the gains from not over-rolling. The behaviour is in-line with # Shadowrun Third Edition which recommends not rolling once you've exceeded the target # open is an override of .open(int) in die.py. The reason is pretty simple. In die.py open # refers to 'open-ended rolling' whereas in Shadowrun it refers to an 'Open Test' where # the objective is to find the highest die total out of rolled dice. This is then generally # used as the target in a 'Success Test' (for which .vs functions) # Modified by: Darloth # Mod Version: 1.1 # Modified Desc: # I've altered the vs call to make it report successes against every target number (tn) # in a specified (default 3) range, with the original as median. # This reduces rerolling if the TN was calculated incorrectly, and is also very useful # when people are rolling against multiple TNs, which is the case with most area-effect spells. # To aid in picking the specified TN out from the others, it will be in bold. # vswide is a version which can be used with no arguments, or can be used to get a very wide range, by # directly specifying the upper bound (Which is limited to 30) from die import * __version__ = "1.1" class srex(std): def __init__(self,source=[]): std.__init__(self,source) def vs(self,actualtarget=4,tnrange=3): #reports all tns around specified, max distance of range return srVs(self,actualtarget,(actualtarget-tnrange),(actualtarget+tnrange)) def vswide(self,actualtarget=4,maxtarget=12): #wide simply means it reports TNs from 2 to a specified max. return srVs(self,actualtarget,2,maxtarget) def open(self): #unchanged from standard shadowrun open. return srOpen(self) class srVs(std): def __init__(self,source=[],actualtarget=4,mintn=2,maxtn=12): std.__init__(self, source) if actualtarget > 30: actualtarget = 30 if mintn > 30: mintn = 30 if maxtn > 30: maxtn = 30 # In Shadowrun, not target number may be below 2. Any # thing lower is scaled up. if actualtarget < 2: self.target = 2 else: self.target = actualtarget #if the target number is higher than max (Mainly for wide rolls) then increase max to tn if actualtarget > maxtn: maxtn = actualtarget #store minimum for later use as well, also in result printing section. if mintn < 2: self.mintn = 2 else: self.mintn = mintn self.maxtn = maxtn #store for later use in printing results. (Yeah, these comments are now disordered) # Shadowrun was built to use the d6 but in the interests of experimentation I have # made the dieroller generic enough to use any die type self.openended(self[0].sides) def openended(self,num): if num <= 1: self done = 1 #reroll dice if they hit the highest number, until they are greater than the max TN (recursive) for i in range(len(self.data)): if (self.data[i].lastroll() >= num) and (self.data[i] < self.maxtn): self.data[i].extraroll() done = 0 if done: return self else: return self.openended(num) #count successes, by looping through each die, and checking it against the currently set TN def __sum__(self): s = 0 for r in self.data: if r >= self.target: s += 1 return s #a modified sum, but this one takes a target argument, and is there because otherwise it is difficult to loop through #tns counting successes against each one without changing target, which is rather dangerous as the original TN could #easily be lost. def xsum(self,curtarget): s = 0 for r in self.data: if r >= curtarget: s += 1 return s def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] Results: " #cycle through from mintn to maxtn, summing successes for each separate TN for targ in range(self.mintn,self.maxtn+1): if targ == self.target: myStr += "<b>" myStr += "(" + str(self.xsum(targ)) + " vs " + str(targ) + ") " if targ == self.target: myStr += "</b>" else: myStr = "[] = (0)" return myStr class srOpen(std): def __init__(self,source=[]): std.__init__(self,source) self.openended(self[0].sides) def openended(self,num): if num <= 1: self done = 1 for i in range(len(self.data)): if self.data[i].lastroll() == num: self.data[i].extraroll() done = 0 if done: return self else: return self.openended(num) def __sum__(self): s = 0 for r in self.data: if r > s: s = r return s def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) self.takeHighest(1) myStr += "] for a result of (" + str(self.__sum__().__int__()) + ")" else: myStr = "[] = (0)" return myStr --- NEW FILE: __init__.py --- __all__ = ['die','utils'] --- NEW FILE: wod.py --- ## a vs die roller as used by WOD games #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: wod.py # Author: OpenRPG Dev Team # Maintainer: # Version: # $Id: wod.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: WOD die roller # # Targetthr is the Threshhold target # for compatibility with Mage die rolls. # Threshhold addition by robert t childers from die import * __version__ = "$Id: wod.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" class wod(std): def __init__(self,source=[],target=0,targetthr=0): std.__init__(self,source) self.target = target self.targetthr = targetthr def vs(self,target): self.target = target return self def thr(self,targetthr): self.targetthr = targetthr return self def sum(self): rolls = [] s = 0 s1 = self.targetthr botch = 0 for a in self.data: rolls.extend(a.gethistory()) for r in rolls: if r >= self.target or r == 10: s += 1 if s1 >0: s1 -= 1 s -= 1 else: botch = 1 elif r == 1: s -= 1 if botch == 1 and s < 0: s = 0 return s def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) if self.sum() < 0: myStr += "] vs " +str(self.target)+" result of a botch" elif self.sum() == 0: myStr += "] vs " +str(self.target)+" result of a failure" else: myStr += "] vs " +str(self.target)+" result of (" + str(self.sum()) + ")" return myStr --- NEW FILE: hero.py --- # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: Hero.py # Version: # $Id: Hero.py,v .3 DJM & Heroman # # Description: Hero System die roller originally based on Posterboy's D20 Dieroller # # Changelog: # v.3 by Heroman # Added hl() to show hit location (+side), and hk() for Hit Location killing damage # (No random stun multiplier) # v.2 DJM # Removed useless modifiers from the Normal damage roller # Changed Combat Value roller and SKill roller so that positive numbers are bonuses, # negative numbers are penalties # Changed Killing damage roller to correct stun multiplier bug # Handled new rounding issues # # v.1 original release DJM from die import * from time import time, clock import random __version__ = "$Id: hero.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" # Hero stands for "Hero system" not 20 sided die :) class hero(std): def __init__(self,source=[]): std.__init__(self,source) # these methods return new die objects for specific options def k(self,mod): return herok(self,mod) def hl(self): return herohl(self) def hk(self): return herohk(self) def n(self): return heron(self) def cv(self,cv,mod): return herocv(self,cv,mod) def sk(self,sk,mod): return herosk(self,sk,mod) class herocv(std): def __init__(self,source=[],cv=10,mod=0): std.__init__(self,source) self.cv = cv self.mod = mod def __str__(self): myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (" + str(self.sum()) + ")" myStr += " with a CV of " + str(self.cv) myStr += " and a modifier of " + str(self.mod) cvhit = 11 + self.cv - self.sum() + self.mod myStr += " hits up to <b>DCV <font color='#ff0000'>" + str(cvhit) + "</font></b>" return myStr class herosk(std): def __init__(self,source=[],sk=11,mod=0): std.__init__(self,source) self.sk = sk self.mod = mod def is_success(self): return (((self.sum()-self.mod) <= self.sk)) def __str__(self): myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) strAdd="] - " swapmod=self.mod if self.mod < 0: strAdd= "] + " swapmod= -self.mod myStr += strAdd + str(swapmod) modSum = self.sum()-self.mod myStr += " = (" + str(modSum) + ")" myStr += " vs " + str(self.sk) if self.is_success(): myStr += " or less <font color='#ff0000'>Success!" else: myStr += " or less <font color='#ff0000'>Failure!" Diff = self.sk - modSum myStr += " by " + str(Diff) +" </font>" return myStr class herok(std): def __init__(self,source=[],mod=0): std.__init__(self,source) self.mod = mod self.gen = random.random() def __str__(self): myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (<font color='#ff0000'><b>" + str(int(round(self.sum()))) + "</b></font>)" stunx = self.gen.randint(1,6)-1 if stunx <= 1: stunx = 1 myStr += " <b>Body</b> and a stunx of (" + str(stunx) stunx=stunx + self.mod myStr += " + " + str(self.mod) stunsum = round(self.sum()) * stunx myStr += ") for a total of (<font color='#ff0000'><b>" + str(int(stunsum)) + "</b></font>) <b>Stun</b>" return myStr class herohl(std): def __init__(self,source=[],mod=0): std.__init__(self,source) self.mod = mod self.gen = random.random() def __str__(self): myStr = "[" + str(self.data[0]) side = self.gen.randint(1,6) sidestr = "Left " if side >=4: sidestr = "Right " for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (<font color='#ff0000'><b>" + str(int(round(self.sum()))) + "</b></font>) " location = int(round(self.sum())) if location <= 5: myStr += "Location: <B>Head</B>, StunX:<B>x5</B>, NStun:<B>x2</B>, Bodyx:<B>x2</B>" elif location == 6: myStr += "Location: <B>" + sidestr + "Hand</B>, StunX:<B>x1</B>, NStun:<B>x1/2</B>, Bodyx:<B>x1/2</B>" elif location == 7: myStr += "Location: <B>" + sidestr + "Arm</B>, StunX:<B>x2</B>, NStun:<B>x1/2</B>, Bodyx:<B>x1/2</B>" elif location == 8: myStr += "Location: <B>" + sidestr + "Arm</B>, StunX:<B>x2</B>, NStun:<B>x1/2</B>, Bodyx:<B>x1/2</B>" elif location == 9: myStr += "Location: <B>" + sidestr + "Shoulder</B>, StunX:<B>x3</B>, NStun:<B>x1</B>, Bodyx:<B>x1</B>" elif location == 10: myStr += "Location: <B>Chest</B>, StunX:<B>x3</B>, NStun:<B>x1</B>, Bodyx:<B>x1</B>" elif location == 11: myStr += "Location: <B>Chest</B>, StunX:<B>x3</B>, NStun:<B>x1</B>, Bodyx:<B>x1</B>" elif location == 12: myStr += "Location: <B>Stomach</B>, StunX:<B>x4</B>, NStun:<B>x1 1/2</B>, Bodyx:<B>x1</B>" elif location == 13: myStr += "Location: <B>Vitals</B>, StunX:<B>x4</B>, NStun:<B>x1 1/2</B>, Bodyx:<B>x2</B>" elif location == 14: myStr += "Location: <B>" + sidestr + "Thigh</B>, StunX:<B>x2</B>, NStun:<B>x1</B>, Bodyx:<B>x1</B>" elif location == 15: myStr += "Location: <B>" + sidestr + "Leg</B>, StunX:<B>x2</B>, NStun:<B>x1/2</B>, Bodyx:<B>x1/2</B>" elif location == 16: myStr += "Location: <B>" + sidestr + "Leg</B>, StunX:<B>x2</B>, NStun:<B>x1/2</B>, Bodyx:<B>x1/2</B>" elif location >= 17: myStr += "Location: <B>" + sidestr + "Foot</B>, StunX:<B>x1</B>, NStun:<B>x1/2</B>, Bodyx:<B>x1/2</B>" return myStr class herohk(std): def __init__(self,source=[],mod=0): std.__init__(self,source) self.mod = mod self.gen = random.random() def __str__(self): myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (<font color='#ff0000'><b>" + str(int(round(self.sum()))) + "</b></font>)" stunx = 1 myStr += " <b>Body</b> " stunx=stunx + self.mod stunsum = round(self.sum()) * stunx myStr += " for a total of (<font color='#ff0000'><b>" + str(int(stunsum)) + "</b></font>) <b>Stun</b>" return myStr class heron(std): def __init__(self,source=[],mod=0): std.__init__(self,source) self.bodtot=0 def __str__(self): myStr = "[" + str(self.data[0]) if self.data[0] == 6: self.bodtot=self.bodtot+2 else: self.bodtot=self.bodtot+1 if self.data[0] <= 1: self.bodtot=self.bodtot-1 for a in self.data[1:]: myStr += "," myStr += str(a) if a == 6: self.bodtot=self.bodtot+2 else: self.bodtot=self.bodtot+1 if a <= 1: self.bodtot=self.bodtot-1 myStr += "] = (<font color='#ff0000'><b>" + str(self.bodtot) + "</b></font>)" myStr += " <b>Body</b> and " myStr += "(<font color='#ff0000'><b>" + str(int(round(self.sum()))) + "</b></font>) <b>Stun</b>" return myStr --- NEW FILE: shadowrun.py --- ## a vs die roller as used by WOD games #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: shadowrun.py # Author: Michael Edwards (AKA akoman) # Maintainer: # Version: 1.0 # # Description: A modified form of the World of Darkness die roller to # conform to ShadowRun rules-sets. Thanks to the ORPG team # for the original die rollers. # Thanks to tdb30_ for letting me think out loud with him. # I take my hint from the HERO dieroller: It creates for wildly variant options # Further, .vs and .open do not work together in any logical way. One method of # chaining them results in a [Bad Dice Format] and the other results in a standard # output from calling .open() # vs is a classic 'comparison' method function, with one difference. It uses a # c&p'ed .open(int) from die.py but makes sure that once the target has been exceeded # then it stops rerolling. The overhead from additional boolean checking is probably # greater than the gains from not over-rolling. The behaviour is in-line with # Shadowrun Third Edition which recommends not rolling once you've exceeded the target # open is an override of .open(int) in die.py. The reason is pretty simple. In die.py open # refers to 'open-ended rolling' whereas in Shadowrun it refers to an 'Open Test' where # the objective is to find the highest die total out of rolled dice. This is then generally # used as the target in a 'Success Test' (for which .vs functions) from die import * __version__ = "1.0" class shadowrun(std): def __init__(self,source=[],target=2): std.__init__(self,source) def vs(self,target): return srVs(self, target) def open(self): return srOpen(self) class srVs(std): def __init__(self,source=[], target=2): std.__init__(self, source) # In Shadowrun, not target number may be below 2. All defaults are set to two and any # thing lower is scaled up. if target < 2: self.target = 2 else: self.target = target # Shadowrun was built to use the d6 but in the interests of experimentation I have # made the dieroller generic enough to use any die type self.openended(self[0].sides) def openended(self,num): if num <= 1: self done = 1 for i in range(len(self.data)): if (self.data[i].lastroll() >= num) and (self.data[i] < self.target): self.data[i].extraroll() done = 0 if done: return self else: return self.openended(num) def __sum__(self): s = 0 for r in self.data: if r >= self.target: s += 1 return s def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] vs " + str(self.target) + " for a result of (" + str(self.sum()) + ")" else: myStr = "[] = (0)" return myStr class srOpen(std): def __init__(self,source=[]): std.__init__(self,source) self.openended(self[0].sides) def openended(self,num): if num <= 1: self done = 1 for i in range(len(self.data)): if self.data[i].lastroll() == num: self.data[i].extraroll() done = 0 if done: return self else: return self.openended(num) def __sum__(self): s = 0 for r in self.data: if r > s: s = r return s def __str__(self): if len(self.data) > 0: myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) self.takeHighest(1) myStr += "] for a result of (" + str(self.__sum__().__int__()) + ")" else: myStr = "[] = (0)" return myStr --- NEW FILE: sr4.py --- ## a vs die roller as used by WOD games #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: shadowrun.py # Author: Veggiesama, ripped straight from Michael Edwards (AKA akoman) # Maintainer: # Version: 1.0 # # Description: Modified from the original Shadowrun dieroller by akoman, # but altered to follow the new Shadowrun 4th Ed dice system. # # SR4 VS # Typing [Xd6.vs(Y)] will roll X dice, checking each die # roll against the MIN_TARGET_NUMBER (default: 5). If it # meets or beats it, it counts as a hit. If the total hits # meet or beat the Y value (threshold), there's a success. # # SR4 EDGE VS # Identical to the above function, except it looks like # [Xd6.edge(Y)] and follows the "Rule of Six". That rule # states any roll of 6 is counted as a hit and rerolled # with a potential to score more hits. The "Edge" bonus # dice must be included into X. # # SR4 INIT # Typing [Xd6.init(Y)] will roll X dice, checking each # die for a hit. All hits are added to Y (the init attrib # of the player), to give an Init Score for the combat. # # SR4 EDGE INIT # Typing [Xd6.initedge(Y)] or [Xd6.edgeinit(Y)] will do # as above, except adding the possibility of Edge dice. # # Note about non-traditional uses: # - D6's are not required. This script will work with any # die possible, and the "Rule of Six" will only trigger # on the highest die roll possible. Not throughly tested. # - If you want to alter the minimum target number (ex. # score a hit on a 4, 5, or 6), scroll down and change # the global value MIN_TARGET_NUMBER to your liking. from die import * __version__ = "1.0" MIN_TARGET_NUMBER = 5 GLITCH_NUMBER = 1 class sr4(std): def __init__(self,source=[]): std.__init__(self,source) self.threshold = None self.init_attrib = None def vs(self,threshold=0): return sr4vs(self, threshold) def edge(self,threshold=0): return sr4vs(self, threshold, 1) def init(self,init_attrib=0): return sr4init(self, init_attrib) def initedge(self,init_attrib=0): return sr4init(self, init_attrib, 1) def edgeinit(self,init_attrib=0): return sr4init(self, init_attrib, 1) def countEdge(self,num): if num <= 1: self done = 1 for i in range(len(self.data)): if (self.data[i].lastroll() >= num): # counts every rerolled 6 as a hit self.hits += 1 self.data[i].extraroll() done = 0 if done: return self else: return self.countEdge(num) def countHits(self,num): for i in range(len(self.data)): if (self.data[i].lastroll() >= MIN_TARGET_NUMBER): # (Rule of Six taken into account in countEdge(), not here) self.hits += 1 def __str__(self): if len(self.data) > 0: self.hits = 0 for i in range(len(self.data)): if (self.data[i].lastroll() >= MIN_TARGET_NUMBER): self.hits += 1 firstpass = 0 myStr = "[" for a in self.data[0:]: if firstpass != 0: myStr += "," firstpass = 1 if a >= MIN_TARGET_NUMBER: myStr += "<B>" + str(a) + "</B>" elif a <= GLITCH_NUMBER: myStr += "<i>" + str(a) + "</i>" else: myStr += str(a) myStr += "] " myStr += "Hits: (" + str(self.hits) + ")" else: myStr = "[] = (0)" return myStr class sr4init(sr4): def __init__(self,source=[],init_attrib=1,edge=0): std.__init__(self,source) if init_attrib < 2: self.init_attrib = 2 else: self.init_attrib = init_attrib self.dicesides = self[0].sides self.hits = 0 if edge: self.countEdge(self.dicesides) self.countHits(self.dicesides) def __str__(self): if len(self.data) > 0: firstpass = 0 myStr = "[" for a in self.data[0:]: if firstpass != 0: myStr += "," firstpass = 1 if a >= MIN_TARGET_NUMBER: myStr += "<B>" + str(a) + "</B>" elif a <= GLITCH_NUMBER: myStr += "<i>" + str(a) + "</i>" else: myStr += str(a) myStr += "] " init_score = str(self.init_attrib + self.hits) myStr += "InitScore: " + str(self.init_attrib) + "+" myStr += str(self.hits) + " = (" + init_score + ")" else: myStr = "[] = (0)" return myStr class sr4vs(sr4): def __init__(self,source=[], threshold=1, edge=0): std.__init__(self, source) if threshold < 0: self.threshold = 0 else: self.threshold = threshold self.dicesides = self[0].sides self.hits = 0 if edge: self.countEdge(self.dicesides) self.countHits(self.dicesides) def __str__(self): if len(self.data) > 0: firstpass = 0 myStr = "[" for a in self.data[0:]: if firstpass != 0: myStr += "," firstpass = 1 if a >= MIN_TARGET_NUMBER: myStr += "<B>" + str(a) + "</B>" elif a <= GLITCH_NUMBER: myStr += "<i>" + str(a) + "</i>" else: myStr += str(a) myStr += "] " myStr += "Threshold=" + str(self.threshold) if self.hits >= self.threshold: myStr += " *SUCCESS* " else: myStr += " *FAILED* " myStr += "Hits: (" + str(self.hits) + ")" else: myStr = "[] = (0)" return myStr --- NEW FILE: dieroller.txt --- The New Dicing System: A Proposal for OpenRPG ---------------------------------------------- The current dice system for OpenRPG has several limitations. Foremost among these are the fact that adding a new, non-standard dicing mechanism requires editing of the basic dice code. There are several secondary limitations, such as the fact that while the dice system can handle math, it cannot be used as a calculator -- it will not allow expressions that do not involve dice. This proposal is for a new dicing system to replace the current one in OpenRPG. Since the dicing system is something that users will interact with frequently, a new system needs to be considered carefully. This document attempts to describe the new dicing system so that such consideration can be given to it. It is expected that this document will grow and change as it is scrutinized. Design goals for this dicing system: 1. Should be easy for new users to get started with, based on knowing standard RPG dice notation (NdX) and basic math. 2. Should, as far as practical, maintain compatibility with existing character sheets, etc., that use the current dice system. 3. Should allow users to create new dice types and new ways of counting dice. Ideally, this should not require programming, except in exceptional cases. 4. The dice system should be usable for doing basic math that does not involve dice. 5. The dice system should be able to handle most current RPG dicing systems. Things this dicing system is designed to NOT do: 1. Be a programming language. There are no facilities in it for user input, output formatting, loops, if-then-else, or similar things. If these are desired for something involving dice, an appropriate node and nodehandler can be created. 2. Handle all theoretically possible dicing systems without the need for programming plugins. First off, this is impossible. Second, even making an attempt to would require supporting dicing methods that don't actually turn up in any real game. 3. Handle floating-point math. I don't know of any systems that use it in their dice schemes right now. If there are some, we might have to consider adding it. Syntax Specification What follows is a BNF specification for the proposed dicing system, with explanatory text interleaved. At the end of this document is a copy of the BNF with no explanations, for those who would like to look at it "all together". Note that BNF describes only syntax, and not semantics; thus, while anything generated with this grammar should be syntactically correct, that doesn't mean it will make sense or be allowed. dice string ::= <expression> <expression> of <comparison> | <comparison> This is the top level. The major thing of note here is that comparisons only occur at this level. This is intentional; the result of a comparison is a boolean true/false flag rather than a number. Thus, it makes no sense to allow people to perform further numerical operations on the result of a comparison. Systems where dice are triggered by the results of other dice are left for the realm of plugins. comparison ::= <expression> <relation> <expression> expression ::= <factor> | <factor> <low-op> <factor> The separation into "low-op" and "high-op" of the operators is to allow order of operations to be handled more easily. Syntactically, it's not really necessary, but it should be helpful in implementation. factor :: = <term> | <term> <high-op> <term> | <multi-dice> | <multi-dice> <high-op> <term> | <term> <high-op> <multi-dice> Here we start to hit some complication. The intent of the different entries for multi-dice is that we don't want to allow things like [3d6 each * 2d6 each]. We are *not* doing vector multiplication! The "expression" level doesn't have any such limitation on syntax; things like [3d6 each + 2d6 each] we'll have to either think of a logical way to handle, or disallow on a semantic level. (Well... I suppose it could be handled in the BNF, but I think it would get kind of messy.) term ::= <dice> | <unit> unit ::= <number> | ( <expression> ) Dice are not considered a unit. This means that things like [3d6d10] can't be done without using parentheses. I consider that to be a win for clarity. dice ::= <unit>d<unit> | <unit>d<name> | <dice> <flag> | lastroll The <name> entry here allows for user-created dice (in the syntax, at least...). multi-dice ::= ( <dice>, <dice>+ ) | # (1d6,1d8) <dice> each | # 3d6 each ( <expression> of <expression> ) | # (3 of 2d6) lastroll | # lastroll <multi-dice> <flag> # (3 of 2d6) best 2 "lastroll" by itself can be either dice or multi-dice. I'm thinking that it should be whatever type the last roll was. flag ::= reroll <condition> | # repeats reroll <slice> | # once only grow <condition> | # reroll and add shrink <condition> | # reroll and subtract drop <condition> | drop <slice> | take <condition> | take <slice> | <slice> | # implied "take" <name> <condition> | # user-created <name> # user-created Technically, we don't need both "drop" and "take" -- one implies the other. However, having both should make the language easier to use. "reroll" will work differently depending on whether a condition or a slice is given. If a condition is given, it will reroll until none of the dice in the set meet the reroll condition (or until it hits a maximum allowed number of rerolls). If a slice is given, it will reroll those dice once. IMHO, this behavior makes the most sense. The <name> entries here are to allow for user-created flags. Note that as I've specified things right now, a user-created flag can have a condition, but not a slice. That's mostly because I couldn't think of a case where a slice would be useful... should we add it anyways? slice ::= highest | lowest | highest <number> | lowest <number> "highest" and "lowest" without a number are equivalent to doing them with 1 as the number. This is to simplify things like [4d6 drop lowest]. condition ::= <relation> <unit> This is for conditions on flags. Note that it can take a unit, so you could use dice in a condition; however, I think the unit should only be evaluated once, to make things faster. Anyone for repetitive evaluation? low-op ::= + | - | min | max "min" takes two values and returns the highest of them, and "max" returns the lowest of them. This might seem counterintuitive, but it's meant to be used with dice, like so: 3d6 min 8 - always returns 8 or higher 3d6 max 15 - always returns 15 or lower I decided to put min and max as having the same precedence as + and -, because if they had higher precedence, then: 3d6+2 min 10 would be equivalent to 3d6+10. (It would take the max of 2 and 10, then add that to 3d6). One problem that does arise here is with multiplication and division: [1d6 min 5 * 2] will be equivalent to [1d6 min 10], since multiplication has higher precedence. We may just want to warn people that min and max can be screwy unless you parenthesize, unless someone can think of a better way to handle them. high-op ::= * | / | mod The / is integer division, of course, since we're doing integer math. number ::= <digit>+ | -<digit>+ Positive and negative numbers are allowed. This means that, syntactically, [-2d-4] is legal. Do we want to modify the BNF to disallow this, or handle it on a semantic level? name ::= <letter>[<letter>|<digit>]* We may want to expand to allow underscores and dashes in user-created names. letter ::= A-Z | a-z digit ::= 0-9 relation ::= < | > | <= | >= | => | =< | = | == Well, that's the BNF. Again, at the end is a copy without all the running commentary. Thoughts on Implementation: First, I think a sort of "dice library" of common functions needs to be created. This would include rolling a set of dice, getting the highest of a group of dice, growing and shrinking dice from a set based on conditions, and so on. These functions should be available for use by custom-written dice types. Next, that library should be used as a tool in implementing a dice-string interpreter. That will require creating a parser for the dice-string 'language'. This could be either a custom-written parser, or possibly one created with some of the Python parser generators. A custom-written parser may take longer to do and be a bit more finicky to maintain, but it would remove a dependency from the code. User-created flags and dice types could be supported in two ways: - First, by allowing users to specify strings in the dice language that the flags/expressions would expand to -- basically, allowing dice macros. - Second, by adding hooks for python modules to be associated with user-created dice or flag types. This is likely to be the more complicated of the two solutions, but it would also be more flexible. Personally, I think both are desirable -- the first, so that non-programming users can create simple die and flag types. The second, because by design, there are some things this dice system just won't do. Further work needed: - specs for the "dice library" - specs on an interface for python modules meant to be dice and flag types. ---------------------------------------------------------------- start ::= <expression> | <comparison> comparison ::= <expression> <condition> expression ::= <term> | <term> <low-op> <factor> term ::= <factor> | <factor> <high-op> <unit> factor ::= <atom> | <dice_set> atom ::= <number> | ( <expression> ) dice_set ::= <dice> <dice_set>, <dice> | <expr> of <dice> | <dice> each | lastroll dice ::= <atom>d<atom> | <atom>d<name> | <dice> <flag> flag ::= reroll <condition> | reroll <slice> | grow <condition> | shrink <condition> | drop <condition> | drop <slice> | take <condition> | take <slice> | <slice> | <name> <condition> | <name> <slice> | <name> slice ::= highest | lowest | highest <number> | lowest <number> condition ::= <relation> <unit> low-op ::= + | - high-op ::= * | / | mod| max | min number ::= <digit>+ | -<digit>+ name ::= <letter>[<letter>|<digit>]* letter ::= A-Z | a-z digit ::= 0-9 relation ::= < | > | <= | >= | => | =< | = | == --- NEW FILE: utils.py --- #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: dieroller/utils.py # Author: OpenRPG Team # Maintainer: # Version: # $Id: utils.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: Classes to help manage the die roller # __version__ = "$Id: utils.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" from die import * # add addtional rollers here from wod import * from d20 import * from hero import * from shadowrun import * from sr4 import * from hackmaster import * from wodex import * from srex import * import re rollers = ['std','wod','d20','hero','shadowrun', 'sr4','hackmaster','srex','wodex'] class roller_manager: def __init__(self,roller_class="d20"): try: self.set_roller(roller_class) except: self.roller_class = "std" def set_roller(self,roller_class): try: rollers.index(roller_class) self.roller_class = roller_class except: raise Exception, "Invalid die roller!" def get_roller(self): return self.roller_class def get_rollers(self): return rollers def stdDieToDClass(self,match): s = match.group(0) (num,sides) = s.split('d') if sides.strip().upper() == 'F': sides = '15' if int(num)>100 or int(sides)>10000: return "none" else: return "(" + num.strip() + "**"+self.roller_class+"(" + sides.strip() + "))" # Use this to convert ndm-style (3d6) dice to d_base format def convertTheDieString(self,s): reg = re.compile("\d+\s*[a-zA-Z]+\s*[\dFf]+") (result,num_matches) = re.subn(reg,self.stdDieToDClass,s) return result def resolveDieStr(self,s): return str(eval(self.convertTheDieString(s))) --- NEW FILE: d20.py --- # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: d20.py # Author: OpenRPG Dev Team # Maintainer: # Version: # $Id: d20.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: d20 die roller # from die import * __version__ = "$Id: d20.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" # d20 stands for "d20 system" not 20 sided die :) class d20(std): def __init__(self,source=[]): std.__init__(self,source) # these methods return new die objects for specific options def attack(self,AC,mod,critical): return d20attack(self,AC,mod,critical) def dc(self,DC,mod): return d20dc(self,DC,mod) class d20dc(std): def __init__(self,source=[],DC=10,mod=0): std.__init__(self,source) self.DC = DC self.mod = mod self.append(static_di(mod)) def is_success(self): return ((self.sum() >= self.DC or self.data[0] == 20) and self.data[0] != 1) def __str__(self): myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (" + str(self.sum()) + ")" myStr += " vs DC " + str(self.DC) if self.is_success(): myStr += " Success!" else: myStr += " Failure!" return myStr class d20attack(std): def __init__(self,source=[],AC=10,mod=0,critical=20): std.__init__(self,source) self.mod = mod self.critical = critical self.AC = AC self.append(static_di(mod)) self.critical_check() def attack(AC=10,mod=0,critical=20): self.mod = mod self.critical = critical self.AC = AC def critical_check(self): self.critical_result = 0 self.critical_roll = 0 if self.data[0] >= self.critical and self.is_hit(): self.critical_roll = die_base(20) + self.mod if self.critical_roll.sum() >= self.AC: self.critical_result = 1 def is_critical(self): return self.critical_result def is_hit(self): return ((self.sum() >= self.AC or self.data[0] == 20) and self.data[0] != 1) def __str__(self): myStr = "[" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (" + str(self.sum()) + ")" myStr += " vs AC " + str(self.AC) if self.is_critical(): myStr += " Critical" if self.is_hit(): myStr += " Hit!" else: myStr += " Miss!" return myStr --- NEW FILE: hackmaster.py --- #!/usr/bin/env python # Copyright Not Yet, see how much I trust you # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: hackmaster.py # Author: Ric Soard # Maintainer: # Version: # $Id: hackmaster.py,v 0.4 2003/08/12 # # Description: special die roller for HackMaster(C)(TM) RPG # has penetration damage - .damage(bonus,honor) # has attack - .attack(bonus, honor) # has severity .severity(honor) # has help - .help() # # import random from die import * __version__ = "$Id: hackmaster.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" #hackmaster Class basically passes into functional classes class hackmaster(std): def __init__(self,source=[]): std.__init__(self,source) def damage(self, mod, hon): return HMdamage(self, mod, hon) def attack(self, mod, hon): return HMattack(self, mod, hon) def help(self): return HMhelp(self) def severity(self, honor): return HMSeverity(self, honor) # HM Damage roller - rolles penetration as per the PHB - re-rolles on max die - 1, adds honor to the penetration rolls # and this appears to be invisible to the user ( if a 4 on a d4 is rolled a 3 will appear and be followed by another # die. if High honor then a 4 will appear followed by a another die. class HMdamage(std): def __init__(self,source=[], mod = 0, hon = 0): std.__init__(self,source) self.mod = mod self.hon = hon self.check_pen() #here we roll the mod die self.append(static_di(self.mod)) #here we roll the honor die self.append(static_di(self.hon)) def damage(mod = 0, hon = 0): self.mod = mod self.hon = hon # This function is called by default to display the die string to the chat window. # Our die string attempts to explain the results def __str__(self): myStr = "Damage " myStr += "[Damage Roll, Modifiers, Honor]: " + " [" + str(self.data[0]) for a in self.data[1:]: myStr += "," myStr += str(a) myStr += "] = (" + str(self.sum()) + ")" return myStr # This function checks to see if we need to reroll for penetration def check_pen(self): for i in range(len(self.data)): if self.data[i].lastroll() >= self.data[i].sides: self.pen_roll(i) #this function rolls the penetration die, and checks to see if it needs to be re-rolled again. def pen_roll(self,num): result = int(random.uniform(1,self.data[num].sides+1)) self.data[num].value += (result - 1 + self.hon) self.data[num].history.append(result - 1 + self.hon) if result >= self.data[num].sides: self.pen_roll(num) # this function rolls for the HM Attack. the function checks for a 20 and displays critical, and a 1 # and displays fumble class HMattack(std): def __init__(self, source=[], mod = 0, base_severity = 0, hon = 0, size = 0): std.__init__(self,source) self.size = size self.mod = mod self.base_severity = base_severity self.hon = hon self.fumble = 0 self.crit = 0 self.check_crit() #this is a static die that adds the modifier self.append(static_di(self.mod)) #this is a static die that adds honor, we want high rolls so it's +1 self.append(static_di(self.hon)) def check_crit(self): if self.data[0] == self.data[0].sides: self.crit = 1 if self.data[0] == 1: self.fumble = 1 #this function is the out put to the chat window, it basicaly just displays the roll unless #i... [truncated message content] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:27
|
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/plugins/cherrypy Added Files: __init__.py _cpconfig.py _cpdefaults.py _cphttpserver.py _cphttptools.py _cpserver.py _cpthreadinglocal.py _cputil.py cperror.py cpg.py wsgiapp.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: wsgiapp.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ WSGI interface for CherryPy """ import StringIO, Cookie, time from cherrypy import cpg, _cphttptools, _cpserver def init(*a, **kw): kw['initOnly'] = 1 _cpserver.start(*a, **kw) def wsgiApp(environ, start_response): cpg.request.method = environ['REQUEST_METHOD'] # Rebuild first line of the request pathInfo = environ['PATH_INFO'] qString = environ.get('QUERY_STRING') if qString: pathInfo += '?' + qString firstLine = '%s %s %s' % ( environ['REQUEST_METHOD'], pathInfo or '/', environ['SERVER_PROTOCOL'] ) _cphttptools.parseFirstLine(firstLine) # Initialize variables now = time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now) date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (_cphttptools.weekdayname[wd], day, _cphttptools.monthname[month], year, hh, mm, ss) cpg.request.headerMap = {} cpg.request.simpleCookie = Cookie.SimpleCookie() cpg.response.simpleCookie = Cookie.SimpleCookie() # Rebuild headerMap for cgiName, headerName in [ ('HTTP_HOST', 'Host'), ('HTTP_USER_AGENT', 'User-Agent'), ('HTTP_CGI_AUTHORIZATION', 'Authorization'), ('CONTENT_LENGTH', 'Content-Length'), ('CONTENT_TYPE', 'Content-Type'), ('HTTP_COOKIE', 'Cookie'), ('REMOTE_HOST', 'Remote-Host'), ('REMOTE_ADDR', 'Remote-Addr'), ('HTTP_REFERER', 'Referer'), ('HTTP_ACCEPT_ENCODING', 'Accept-Encoding'), ]: if cgiName in environ: _cphttptools.insertIntoHeaderMap(headerName, environ[cgiName]) # TODO: handle POST # set up stuff similar to initRequest cpg.response.headerMap = { "protocolVersion": cpg.configOption.protocolVersion, "Status": "200 OK", "Content-Type": "text/html", "Server": "CherryPy/" + cpg.__version__, "Date": date, "Set-Cookie": [], "Content-Length": 0 } cpg.request.base = "http://" + cpg.request.headerMap['Host'] cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl cpg.request.isStatic = False cpg.request.parsePostData = True cpg.request.rfile = environ["wsgi.input"] cpg.request.objectPath = None if 'Cookie' in cpg.request.headerMap: cpg.request.simpleCookie.load(cpg.request.headerMap['Cookie']) cpg.response.simpleCookie = Cookie.SimpleCookie() cpg.response.sendResponse = 1 if cpg.request.method == 'POST' and cpg.request.parsePostData: _cphttptools.parsePostData(cpg.request.rfile) # Execute request wfile = StringIO.StringIO() cpg.response.wfile = wfile _cphttptools.handleRequest(wfile) response = wfile.getvalue() # Extract header from response headerLines = [] i = 0 while 1: j = response.find('\n', i) line = response[i:j] if line[-1] == '\r': line = line[:-1] headerLines.append(line) i = j+1 if not line: break response = response[i:] status = headerLines[0] # Remove "HTTP/1.0" at the beginning of status i = status.find(' ') status = status[i+1:] responseHeaders = [] for line in headerLines[1:]: i = line.find(':') header = line[:i] value = line[i+1:].lstrip() responseHeaders.append((header,value)) start_response(status, responseHeaders) return response if __name__ == '__main__': from cherrypy import cpg, wsgiapp class Root: def index(self, name = "world"): count = cpg.request.sessionMap.get('count', 0) + 1 cpg.request.sessionMap['count'] = count return """ <html><body> Hello, %s, count is %s: <form action="/post" method="post"> Post some data: <input name=myData type=text"> <input type=submit> </form> """ % (name, count) index.exposed = True def post(self, myData): return "myData: " + myData post.exposed = True cpg.root = Root() import sys # This uses the WSGI HTTP server from PEAK.wsgiref # sys.path.append(r"C:\Tmp\PEAK\src") from wsgiref.simple_server import WSGIServer, WSGIRequestHandler # Read the CherryPy config file and initialize some variables wsgiapp.init(configMap = {'socketPort': 8000, 'sessionStorageType': 'ram'}) server_address = ("", 8000) httpd = WSGIServer(server_address, WSGIRequestHandler) httpd.set_app(wsgiapp.wsgiApp) sa = httpd.socket.getsockname() #print "Serving HTTP on", sa[0], "port", sa[1], "..." httpd.serve_forever() --- NEW FILE: cperror.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Main CherryPy module: - Parses config file - Creates the HTTP server """ class Error(Exception): pass class InternalError(Error): """ Error that should never happen """ pass class NotFound(Error): """ Happens when a URL couldn't be mapped to any class.method """ pass class WrongResponseType(Error): """ Happens when the cpg.response.body is not a string """ pass --- NEW FILE: _cputil.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ A module containing a few utility classes/functions used by CherryPy """ import time, thread, cpg, _cpdefaults, cperror try: import zlib except ImportError: pass class EmptyClass: """ An empty class """ pass def getSpecialFunction(name): """ Return the special function """ # First, we look in the right-most object if this special function is implemented. # If not, then we try the previous object and so on until we reach cpg.root # If it's still not there, we use the implementation from the # "_cpdefaults.py" module moduleList = [_cpdefaults] root = getattr(cpg, 'root', None) if root: moduleList.append(root) # Try object path try: path = cpg.request.objectPath or cpg.request.path except: path = '/' if path: pathList = path.split('/')[1:] obj = cpg.root previousObj = None # Successively get objects from the path for newObj in pathList: previousObj = obj try: obj = getattr(obj, newObj) moduleList.append(obj) except AttributeError: break moduleList.reverse() for module in moduleList: func = getattr(module, name, None) if func != None: return func raise cperror.InternalError, "Special function %s could not be found" % repr(name) --- NEW FILE: __init__.py --- __version__ = '2.0.0' --- NEW FILE: _cpconfig.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import _cputil, ConfigParser, cpg def setDefaultConfigOption(): """ Return an EmptyClass instance with the default config options """ cpg.configOption = _cputil.EmptyClass() # Set default values for all options # Parameters used for logging cpg.configOption.logToScreen = 1 cpg.configOption.logFile = '' # Parameters used to tell which socket the server should listen on # Note that socketPort and socketFile conflict wich each # other: if one has a non-null value, the other one should be null cpg.configOption.socketHost = '' cpg.configOption.socketPort = 8080 cpg.configOption.socketFile = '' # Used if server should listen on # AF_UNIX socket cpg.configOption.reverseDNS = 0 cpg.configOption.socketQueueSize = 5 # Size of the socket queue cpg.configOption.protocolVersion = "HTTP/1.0" # Parameters used to tell what kind of server we want cpg.configOption.threadPool = 0 # Used if we want to create a pool # of threads at the beginning # Variables used to tell if this is an SSL server cpg.configOption.sslKeyFile = "" cpg.configOption.sslCertificateFile = "" cpg.configOption.sslClientCertificateVerification = 0 cpg.configOption.sslCACertificateFile = "" cpg.configOption.sslVerifyDepth = 1 # Variable used to flush cache cpg.configOption.flushCacheDelay=0 # Variable used for enabling debugging cpg.configOption.debugMode=0 # Variable used to serve static content cpg.configOption.staticContentList = [] # Variable used for session handling cpg.configOption.sessionStorageType = "" cpg.configOption.sessionTimeout = 60 # In minutes cpg.configOption.sessionCleanUpDelay = 60 # In minutes cpg.configOption.sessionCookieName = "CherryPySession" cpg.configOption.sessionStorageFileDir = "" def parseConfigFile(configFile = None, parsedConfigFile = None): """ Parse the config file and set values in cpg.configOption """ _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') if configFile: cpg.parsedConfigFile = ConfigParser.ConfigParser() if hasattr(configFile, 'read'): _cpLogMessage("Reading infos from configFile stream", 'CONFIG') cpg.parsedConfigFile.readfp(configFile) else: _cpLogMessage("Reading infos from configFile: %s" % configFile, 'CONFIG') cpg.parsedConfigFile.read(configFile) else: cpg.parsedConfigFile = parsedConfigFile # Read parameters from configFile for sectionName, optionName, valueType in [ ('server', 'logToScreen', 'int'), ('server', 'logFile', 'str'), ('server', 'socketHost', 'str'), ('server', 'protocolVersion', 'str'), ('server', 'socketPort', 'int'), ('server', 'socketFile', 'str'), ('server', 'reverseDNS', 'int'), ('server', 'threadPool', 'int'), ('server', 'sslKeyFile', 'str'), ('server', 'sslCertificateFile', 'str'), ('server', 'sslClientCertificateVerification', 'int'), ('server', 'sslCACertificateFile', 'str'), ('server', 'sslVerifyDepth', 'int'), ('session', 'storageType', 'str'), ('session', 'timeout', 'float'), ('session', 'cleanUpDelay', 'float'), ('session', 'cookieName', 'str'), ('session', 'storageFileDir', 'str') ]: try: value = cpg.parsedConfigFile.get(sectionName, optionName) if valueType == 'int': value = int(value) elif valueType == 'float': value = float(value) if sectionName == 'session': optionName = 'session' + optionName[0].upper() + optionName[1:] setattr(cpg.configOption, optionName, value) except: pass try: staticDirList = cpg.parsedConfigFile.options('staticContent') for staticDir in staticDirList: staticDirTarget = cpg.parsedConfigFile.get('staticContent', staticDir) cpg.configOption.staticContentList.append((staticDir, staticDirTarget)) except: pass def outputConfigOptions(): _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') _cpLogMessage("Server parameters:", 'CONFIG') _cpLogMessage(" logToScreen: %s" % cpg.configOption.logToScreen, 'CONFIG') _cpLogMessage(" logFile: %s" % cpg.configOption.logFile, 'CONFIG') _cpLogMessage(" protocolVersion: %s" % cpg.configOption.protocolVersion, 'CONFIG') _cpLogMessage(" socketHost: %s" % cpg.configOption.socketHost, 'CONFIG') _cpLogMessage(" socketPort: %s" % cpg.configOption.socketPort, 'CONFIG') _cpLogMessage(" socketFile: %s" % cpg.configOption.socketFile, 'CONFIG') _cpLogMessage(" reverseDNS: %s" % cpg.configOption.reverseDNS, 'CONFIG') _cpLogMessage(" socketQueueSize: %s" % cpg.configOption.socketQueueSize, 'CONFIG') _cpLogMessage(" threadPool: %s" % cpg.configOption.threadPool, 'CONFIG') _cpLogMessage(" sslKeyFile: %s" % cpg.configOption.sslKeyFile, 'CONFIG') if cpg.configOption.sslKeyFile: _cpLogMessage(" sslCertificateFile: %s" % cpg.configOption.sslCertificateFile, 'CONFIG') _cpLogMessage(" sslClientCertificateVerification: %s" % cpg.configOption.sslClientCertificateVerification, 'CONFIG') _cpLogMessage(" sslCACertificateFile: %s" % cpg.configOption.sslCACertificateFile, 'CONFIG') _cpLogMessage(" sslVerifyDepth: %s" % cpg.configOption.sslVerifyDepth, 'CONFIG') _cpLogMessage(" flushCacheDelay: %s min" % cpg.configOption.flushCacheDelay, 'CONFIG') _cpLogMessage(" sessionStorageType: %s" % cpg.configOption.sessionStorageType, 'CONFIG') if cpg.configOption.sessionStorageType: _cpLogMessage(" sessionTimeout: %s min" % cpg.configOption.sessionTimeout, 'CONFIG') _cpLogMessage(" cleanUpDelay: %s min" % cpg.configOption.sessionCleanUpDelay, 'CONFIG') _cpLogMessage(" sessionCookieName: %s" % cpg.configOption.sessionCookieName, 'CONFIG') _cpLogMessage(" sessionStorageFileDir: %s" % cpg.configOption.sessionStorageFileDir, 'CONFIG') _cpLogMessage(" staticContent: %s" % cpg.configOption.staticContentList, 'CONFIG') def dummy(): # Check that parameters are correct and that they don't conflict with each other if _protocolVersion not in ("HTTP/1.1", "HTTP/1.0"): raise "CherryError: protocolVersion must be 'HTTP/1.1' or 'HTTP/1.0'" if _reverseDNS not in (0,1): raise "CherryError: reverseDNS must be '0' or '1'" if _socketFile and not hasattr(socket, 'AF_UNIX'): raise "CherryError: Configuration file has socketFile, but this is only available on Unix machines" if _sslKeyFile: try: global SSL from OpenSSL import SSL except: raise "CherryError: PyOpenSSL 0.5.1 or later must be installed to use SSL. You can get it from http://pyopenssl.sourceforge.net" if _socketPort and _socketFile: raise "CherryError: In configuration file: socketPort and socketFile conflict with each other" if not _socketFile and not _socketPort: _socketPort=8000 # Default port if _sslKeyFile and not _sslCertificateFile: raise "CherryError: Configuration file has sslKeyFile but no sslCertificateFile" if _sslCertificateFile and not _sslKeyFile: raise "CherryError: Configuration file has sslCertificateFile but no sslKeyFile" try: sys.stdout.flush() except: pass if _sessionStorageType not in ('', 'custom', 'ram', 'file', 'cookie'): raise "CherryError: Configuration file an invalid sessionStorageType: '%s'"%_sessionStorageType if _sessionStorageType in ('custom', 'ram', 'cookie') and _sessionStorageFileDir!='': raise "CherryError: Configuration file has sessionStorageType set to 'custom, 'ram' or 'cookie' but a sessionStorageFileDir is specified" if _sessionStorageType=='file' and _sessionStorageFileDir=='': raise "CherryError: Configuration file has sessionStorageType set to 'file' but no sessionStorageFileDir" --- NEW FILE: cpg.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Global module that all modules developing with CherryPy should import. """ from __init__ import __version__ # import server module import _cpserver as server # decorator function for exposing methods def expose(func): func.exposed = True return func --- NEW FILE: _cpthreadinglocal.py --- # This is a backport of Python-2.4's threading.local() implementation """Thread-local objects (Note that this module provides a Python version of thread threading.local class. Depending on the version of Python you're using, there may be a faster one available. You should always import the local class from threading.) Thread-local objects support the management of thread-local data. If you have data that you want to be local to a thread, simply create a thread-local object and use its attributes: >>> mydata = local() >>> mydata.number = 42 >>> mydata.number 42 You can also access the local-object's dictionary: >>> mydata.__dict__ {'number': 42} >>> mydata.__dict__.setdefault('widgets', []) [] >>> mydata.widgets [] What's important about thread-local objects is that their data are local to a thread. If we access the data in a different thread: >>> log = [] >>> def f(): ... items = mydata.__dict__.items() ... items.sort() ... log.append(items) ... mydata.number = 11 ... log.append(mydata.number) >>> import threading >>> thread = threading.Thread(target=f) >>> thread.start() >>> thread.join() >>> log [[], 11] we get different data. Furthermore, changes made in the other thread don't affect data seen in this thread: >>> mydata.number 42 Of course, values you get from a local object, including a __dict__ attribute, are for whatever thread was current at the time the attribute was read. For that reason, you generally don't want to save these values across threads, as they apply only to the thread they came from. You can create custom local objects by subclassing the local class: >>> class MyLocal(local): ... number = 2 ... initialized = False ... def __init__(self, **kw): ... if self.initialized: ... raise SystemError('__init__ called too many times') ... self.initialized = True ... self.__dict__.update(kw) ... def squared(self): ... return self.number ** 2 This can be useful to support default values, methods and initialization. Note that if you define an __init__ method, it will be called each time the local object is used in a separate thread. This is necessary to initialize each thread's dictionary. Now if we create a local object: >>> mydata = MyLocal(color='red') Now we have a default number: >>> mydata.number 2 an initial color: >>> mydata.color 'red' >>> del mydata.color And a method that operates on the data: >>> mydata.squared() 4 As before, we can access the data in a separate thread: >>> log = [] >>> thread = threading.Thread(target=f) >>> thread.start() >>> thread.join() >>> log [[('color', 'red'), ('initialized', True)], 11] without affecting this thread's data: >>> mydata.number 2 >>> mydata.color Traceback (most recent call last): ... AttributeError: 'MyLocal' object has no attribute 'color' Note that subclasses can define slots, but they are not thread local. They are shared across threads: >>> class MyLocal(local): ... __slots__ = 'number' >>> mydata = MyLocal() >>> mydata.number = 42 >>> mydata.color = 'red' So, the separate thread: >>> thread = threading.Thread(target=f) >>> thread.start() >>> thread.join() affects what we see: >>> mydata.number 11 >>> del mydata """ # Threading import is at end class _localbase(object): __slots__ = '_local__key', '_local__args', '_local__lock' def __new__(cls, *args, **kw): self = object.__new__(cls) key = '_local__key', 'thread.local.' + str(id(self)) object.__setattr__(self, '_local__key', key) object.__setattr__(self, '_local__args', (args, kw)) object.__setattr__(self, '_local__lock', RLock()) if args or kw and (cls.__init__ is object.__init__): raise TypeError("Initialization arguments are not supported") # We need to create the thread dict in anticipation of # __init__ being called, to make sire we don't cal it # again ourselves. dict = object.__getattribute__(self, '__dict__') currentThread().__dict__[key] = dict return self def _patch(self): key = object.__getattribute__(self, '_local__key') d = currentThread().__dict__.get(key) if d is None: d = {} currentThread().__dict__[key] = d object.__setattr__(self, '__dict__', d) # we have a new instance dict, so call out __init__ if we have # one cls = type(self) if cls.__init__ is not object.__init__: args, kw = object.__getattribute__(self, '_local__args') cls.__init__(self, *args, **kw) else: object.__setattr__(self, '__dict__', d) class local(_localbase): def __getattribute__(self, name): lock = object.__getattribute__(self, '_local__lock') lock.acquire() try: _patch(self) return object.__getattribute__(self, name) finally: lock.release() def __setattr__(self, name, value): lock = object.__getattribute__(self, '_local__lock') lock.acquire() try: _patch(self) return object.__setattr__(self, name, value) finally: lock.release() def __delattr__(self, name): lock = object.__getattribute__(self, '_local__lock') lock.acquire() try: _patch(self) return object.__delattr__(self, name) finally: lock.release() def __del__(): threading_enumerate = enumerate __getattribute__ = object.__getattribute__ def __del__(self): key = __getattribute__(self, '_local__key') try: threads = list(threading_enumerate()) except: # if enumerate fails, as it seems to do during # shutdown, we'll skip cleanup under the assumption # that there is nothing to clean up return for thread in threads: try: __dict__ = thread.__dict__ except AttributeError: # Thread is dying, rest in peace continue if key in __dict__: try: del __dict__[key] except KeyError: pass # didn't have anything in this thread return __del__ __del__ = __del__() from threading import currentThread, enumerate, RLock --- NEW FILE: _cpdefaults.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ A module containing a few utility classes/functions used by CherryPy """ import time, thread, os, cpg import cPickle as pickle def _cpLogMessage(msg, context = '', severity = 0): """ Default method for logging messages """ nowTuple = time.localtime(time.time()) nowStr = '%04d/%02d/%02d %02d:%02d:%02d' % (nowTuple[:6]) if severity == 0: level = "INFO" elif severity == 1: level = "WARNING" elif severity == 2: level = "ERROR" else: lebel = "UNKNOWN" try: logToScreen = int(cpg.configOption.logToScreen) except: logToScreen = True s = nowStr + ' ' + context + ' ' + level + ' ' + msg if logToScreen: print s if cpg.configOption.logFile: f = open(cpg.configOption.logFile, 'ab') f.write(s + '\n') f.close() def _cpOnError(): """ Default _cpOnError method """ import traceback, StringIO bodyFile = StringIO.StringIO() traceback.print_exc(file = bodyFile) cpg.response.body = [bodyFile.getvalue()] cpg.response.headerMap['Content-Type'] = 'text/plain' def _cpSaveSessionData(sessionId, sessionData, expirationTime, threadPool = None, sessionStorageType = None, sessionStorageFileDir = None): """ Save session data if needed """ if threadPool is None: threadPool = cpg.configOption.threadPool if sessionStorageType is None: sessionStorageType = cpg.configOption.sessionStorageType if sessionStorageFileDir is None: sessionStorageFileDir = cpg.configOption.sessionStorageFileDir t = time.localtime(expirationTime) if sessionStorageType == 'file': fname=os.path.join(sessionStorageFileDir,sessionId) if threadPool > 1: cpg._sessionFileLock.acquire() f = open(fname,"wb") pickle.dump((sessionData, expirationTime), f) f.close() if threadPool > 1: cpg._sessionFileLock.release() elif sessionStorageType=="ram": # Update expiration time cpg._sessionMap[sessionId] = (sessionData, expirationTime) """ TODO: implement cookie storage type elif sessionStorageType == "cookie": TODO: set siteKey in _cpConfig # Get site key from config file or compute it try: cpg._SITE_KEY_ = configFile.get('server','siteKey') except: _SITE_KEY_ = '' for i in range(30): _SITE_KEY_ += random.choice(string.letters) # Update expiration time sessionData = (sessionData, expirationTime) dumpStr = pickle.dumps(_sessionData) try: dumpStr = zlib.compress(dumpStr) except: pass # zlib is not available in all python distros dumpStr = binascii.hexlify(dumpStr) # Need to hexlify it because it will be stored in a cookie cpg.response.simpleCookie['CSession'] = dumpStr cpg.response.simpleCookie['CSession-sig'] = md5.md5(dumpStr + cpg.configOption.siteKey).hexdigest() cpg.response.simpleCookie['CSession']['path'] = '/' cpg.response.simpleCookie['CSession']['max-age'] = sessionTimeout * 60 cpg.response.simpleCookie['CSession-sig']['path'] = '/' cpg.response.simpleCookie['CSession-sig']['max-age'] = sessionTimeout * 60 """ def _cpLoadSessionData(sessionId, threadPool = None, sessionStorageType = None, sessionStorageFileDir = None): """ Return the session data for a given sessionId. The _expirationTime will be checked by the caller of this function """ if threadPool is None: threadPool = cpg.configOption.threadPool if sessionStorageType is None: sessionStorageType = cpg.configOption.sessionStorageType if sessionStorageFileDir is None: sessionStorageFileDir = cpg.configOption.sessionStorageFileDir if sessionStorageType == "ram": if cpg._sessionMap.has_key(sessionId): return cpg._sessionMap[sessionId] else: return None elif sessionStorageType == "file": fname = os.path.join(sessionStorageFileDir, sessionId) if os.path.exists(fname): if threadPool > 1: cpg._sessionFileLock.acquire() f = open(fname, "rb") sessionData = pickle.load(f) f.close() if threadPool > 1: cpg._sessionFileLock.release() return sessionData else: return None """ TODO: implement cookie storage type elif _sessionStorageType == "cookie": if request.simpleCookie.has_key('CSession') and request.simpleCookie.has_key('CSession-sig'): data = request.simpleCookie['CSession'].value sig = request.simpleCookie['CSession-sig'].value if md5.md5(data + cpg.configOption.siteKey).hexdigest() == sig: try: dumpStr = binascii.unhexlify(data) try: dumpStr = zlib.decompress(dumpStr) except: pass # zlib is not available in all python distros dumpStr = pickle.loads(dumpStr) return dumpStr except: pass return None """ def _cpCleanUpOldSessions(threadPool = None, sessionStorageType = None, sessionStorageFileDir = None): """ Clean up old sessions """ if threadPool is None: threadPool = cpg.configOption.threadPool if sessionStorageType is None: sessionStorageType = cpg.configOption.sessionStorageType if sessionStorageFileDir is None: sessionStorageFileDir = cpg.configOption.sessionStorageFileDir # Clean up old session data now = time.time() if sessionStorageType == "ram": sessionIdToDeleteList = [] for sessionId, (dummy, expirationTime) in cpg._sessionMap.items(): if expirationTime < now: sessionIdToDeleteList.append(sessionId) for sessionId in sessionIdToDeleteList: del cpg._sessionMap[sessionId] elif sessionStorageType=="file": # This process is very expensive because we go through all files, parse them and then delete them if the session is expired # One optimization would be to just store a list of (sessionId, expirationTime) in *one* file sessionFileList = os.listdir(sessionStorageFileDir) for sessionId in sessionFileList: try: dummy, expirationTime = _cpLoadSessionData(sessionId) if expirationTime < now: os.remove(os.path.join(sessionStorageFileDir, sessionId)) except: pass elif sessionStorageType == "cookie": # Nothing to do in this case: the session data is stored on the client pass _cpFilterList = [] --- NEW FILE: _cphttptools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os import mimetypes, sha, random, string, _cputil, cperror, Cookie, urlparse from lib.filter import basefilter """ Common Service Code for CherryPy """ mimetypes.types_map['.dwg']='image/x-dwg' mimetypes.types_map['.ico']='image/x-icon' weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] class IndexRedirect(Exception): pass def parseFirstLine(data): cpg.request.path = data.split()[1] cpg.request.queryString = "" cpg.request.browserUrl = cpg.request.path cpg.request.paramMap = {} cpg.request.paramList = [] # Only used for Xml-Rpc cpg.request.filenameMap = {} cpg.request.fileTypeMap = {} i = cpg.request.path.find('?') if i != -1: # Parse parameters from URL if cpg.request.path[i+1:]: k = cpg.request.path[i+1:].find('?') if k != -1: j = cpg.request.path[:k].rfind('=') if j != -1: cpg.request.path = cpg.request.path[:j+1] + \ urllib.quote_plus(cpg.request.path[j+1:]) for paramStr in cpg.request.path[i+1:].split('&'): sp = paramStr.split('=') if len(sp) > 2: j = paramStr.find('=') sp = (paramStr[:j], paramStr[j+1:]) if len(sp) == 2: key, value = sp value = urllib.unquote_plus(value) if cpg.request.paramMap.has_key(key): # Already has a value: make a list out of it if type(cpg.request.paramMap[key]) == type([]): # Already is a list: append the new value to it cpg.request.paramMap[key].append(value) else: # Only had one value so far: start a list cpg.request.paramMap[key] = [cpg.request.paramMap[key], value] else: cpg.request.paramMap[key] = value cpg.request.queryString = cpg.request.path[i+1:] cpg.request.path = cpg.request.path[:i] def cookHeaders(clientAddress, remoteHost, headers, requestLine): """Process the headers into the request.headerMap""" cpg.request.headerMap = {} cpg.request.requestLine = requestLine cpg.request.simpleCookie = Cookie.SimpleCookie() # Build headerMap for item in headers.items(): # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that) # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie) insertIntoHeaderMap(item[0],item[1]) # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key cookieList = headers.getallmatchingheaders('cookie') for cookie in cookieList: cpg.request.simpleCookie.load(cookie) cpg.request.remoteAddr = clientAddress cpg.request.remoteHost = remoteHost # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate try: cpg.request.peerCertificate = self.request.get_peer_certificate() except: pass _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP") def parsePostData(rfile): # Read request body and put it in data len = int(cpg.request.headerMap.get("Content-Length","0")) if len: data = rfile.read(len) else: data="" # Put data in a StringIO so FieldStorage can read it newRfile = StringIO.StringIO(data) # Create a copy of headerMap with lowercase keys because # FieldStorage doesn't work otherwise lowerHeaderMap = {} for key, value in cpg.request.headerMap.items(): lowerHeaderMap[key.lower()] = value forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) for key in forms.keys(): # Check if it's a list or not valueList = forms[key] if type(valueList) == type([]): # It's a list of values cpg.request.paramMap[key] = [] cpg.request.filenameMap[key] = [] cpg.request.fileTypeMap[key] = [] for item in valueList: cpg.request.paramMap[key].append(item.value) cpg.request.filenameMap[key].append(item.filename) cpg.request.fileTypeMap[key].append(item.type) else: # It's a single value # In case it's a file being uploaded, we save the filename in a map (user might need it) cpg.request.paramMap[key] = valueList.value cpg.request.filenameMap[key] = valueList.filename cpg.request.fileTypeMap[key] = valueList.type def applyFilterList(methodName): try: filterList = _cputil.getSpecialFunction('_cpFilterList') for filter in filterList: method = getattr(filter, methodName, None) if method: method() except basefilter.InternalRedirect: # If we get an InternalRedirect, we start the filter list # from scratch. Is cpg.request.path or cpg.request.objectPath # has been modified by the hook, then a new filter list # will be applied. # We use recursion so if there is an infinite loop, we'll # get the regular python "recursion limit exceeded" exception. applyFilterList(methodName) def insertIntoHeaderMap(key,value): normalizedKey = '-'.join([s.capitalize() for s in key.split('-')]) cpg.request.headerMap[normalizedKey] = value def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): parseFirstLine(requestLine) cookHeaders(clientAddress, remoteHost, headers, requestLine) cpg.request.base = "http://" + cpg.request.headerMap['Host'] cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl cpg.request.isStatic = False cpg.request.parsePostData = True cpg.request.rfile = rfile # Change objectPath in filters to change the object that will get rendered cpg.request.objectPath = None applyFilterList('afterRequestHeader') if cpg.request.method == 'POST' and cpg.request.parsePostData: parsePostData(rfile) applyFilterList('afterRequestBody') def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): # creates some attributes on cpg.response so filters can use them cpg.response.wfile = wfile cpg.response.sendResponse = 1 try: initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile) except basefilter.RequestHandled: # request was already fully handled; it may be a cache hit return # Prepare response variables now = time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now) date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss) cpg.response.headerMap = { "protocolVersion": cpg.configOption.protocolVersion, "Status": "200 OK", "Content-Type": "text/html", "Server": "CherryPy/" + cpg.__version__, "Date": date, "Set-Cookie": [], "Content-Length": 0 } cpg.response.simpleCookie = Cookie.SimpleCookie() try: handleRequest(cpg.response.wfile) except: # TODO: in some cases exceptions and filters are conflicting; # error reporting seems to be broken in some cases. This code is # a helper to check it err = "" exc_info_1 = sys.exc_info()[1] if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1: err = exc_info_1.args[0] try: _cputil.getSpecialFunction('_cpOnError')() # Still save session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) if (cpg.response.headerMap.has_key('Content-Length') and cpg.response.headerMap['Content-Length']==0): buf = StringIO.StringIO() [buf.write(x) for x in cpg.response.body] buf.seek(0) cpg.response.body = [buf.read()] cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n'%(key, value)) wfile.write('\r\n') for line in cpg.response.body: wfile.write(line) except: bodyFile = StringIO.StringIO() traceback.print_exc(file = bodyFile) body = bodyFile.getvalue() wfile.write('%s 200 OK\r\n' % cpg.configOption.protocolVersion) wfile.write('Content-Type: text/plain\r\n') wfile.write('Content-Length: %s\r\n' % len(body)) wfile.write('\r\n') wfile.write(body) def sendResponse(wfile): applyFilterList('beforeResponse') # Set the content-length if (cpg.response.headerMap.has_key('Content-Length') and cpg.response.headerMap['Content-Length']==0): buf = StringIO.StringIO() [buf.write(x) for x in cpg.response.body] buf.seek(0) cpg.response.body = [buf.read()] cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) # Save session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n' % (key, value)) # Send response cookies cookie = cpg.response.simpleCookie.output() if cookie: wfile.write(cookie+'\r\n') wfile.write('\r\n') for line in cpg.response.body: wfile.write(line) # finalization hook for filter cleanup & logging purposes applyFilterList('afterResponse') def handleRequest(wfile): # Clean up expired sessions if needed: now = time.time() if cpg.configOption.sessionStorageType and cpg.configOption.sessionCleanUpDelay and cpg._lastSessionCleanUpTime + cpg.configOption.sessionCleanUpDelay * 60 <= now: cpg._lastSessionCleanUpTime = now _cputil.getSpecialFunction('_cpCleanUpOldSessions')() # Save original values (in case they get modified by filters) cpg.request.originalPath = cpg.request.path cpg.request.originalParamMap = cpg.request.paramMap cpg.request.originalParamList = cpg.request.paramList path = cpg.request.path if path.startswith('/'): # Remove leading slash path = path[1:] if path.endswith('/'): # Remove trailing slash path = path[:-1] path = urllib.unquote(path) # Replace quoted chars (eg %20) from url # Handle static directories for urlDir, fsDir in cpg.configOption.staticContentList: if path == urlDir or path[:len(urlDir)+1]==urlDir+'/': cpg.request.isStatic = 1 fname = fsDir + path[len(urlDir):] start_url_var = cpg.request.browserUrl.find('?') if start_url_var != -1: fname = fname + cpg.request.browserUrl[start_url_var:] try: stat = os.stat(fname) except OSError: raise cperror.NotFound modifTime = stat.st_mtime strModifTime = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(modifTime)) # Check if browser sent "if-modified-since" in request header if cpg.request.headerMap.has_key('If-Modified-Since'): # Check if if-modified-since date is the same as strModifTime if cpg.request.headerMap['If-Modified-Since'] == strModifTime: cpg.response.headerMap = { 'Status': 304, 'protocolVersion': cpg.configOption.protocolVersion, 'Date': cpg.response.headerMap['Date']} cpg.response.body = [] sendResponse(wfile) return cpg.response.headerMap['Last-Modified'] = strModifTime # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory cpg.response.headerMap['Content-Length'] = stat[6] cpg.response.body = open(fname, 'rb') # Set content-type based on filename extension i = path.rfind('.') if i != -1: ext = path[i:] else: ext = "" contentType = mimetypes.types_map.get(ext, "text/plain") cpg.response.headerMap['Content-Type'] = contentType sendResponse(wfile) return # Get session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: now = time.time() # First, get sessionId from cookie try: sessionId = cpg.request.simpleCookie[cpg.configOption.sessionCookieName].value except: sessionId=None if sessionId: # Load session data from wherever it was stored sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId) if sessionData == None: sessionId = None else: cpg.request.sessionMap, expirationTime = sessionData # Check that is hasn't expired if now > expirationTime: # Session expired sessionId = None # Create a new sessionId if needed if not sessionId: cpg.request.sessionMap = {} sessionId = generateSessionId() cpg.request.sessionMap['_sessionId'] = sessionId cpg.response.simpleCookie[cpg.configOption.sessionCookieName] = sessionId cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/' cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1 try: func, objectPathList, virtualPathList = mapPathToObject() except IndexRedirect, inst: # For an IndexRedirect, we don't go through the regular # mechanism: we return the redirect immediately newUrl = urlparse.urljoin(cpg.request.base, inst.args[0]) wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion'])) cpg.response.headerMap['Location'] = newUrl for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n'%(key, value)) wfile.write('\r\n') return # Remove "root" from objectPathList and join it to get objectPath cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap)) # builds a uniform return type if not isinstance(body, types.GeneratorType): cpg.response.body = [body] else: cpg.response.body = body if cpg.response.sendResponse: sendResponse(wfile) def generateSessionId(): s = '' for i in range(50): s += random.choice(string.letters+string.digits) s += '%s'%time.time() return sha.sha(s).hexdigest() def getObjFromPath(objPathList, objCache): """ For a given objectPathList (like ['root', 'a', 'b', 'index']), return the object (or None if it doesn't exist). Also keep a cache for maximum efficiency """ # Let cpg be the first valid object. validObjects = ["cpg"] # Scan the objPathList in order from left to right for index, obj in enumerate(objPathList): # maps virtual filenames to Python identifiers (substitutes '.' for '_') obj = obj.replace('.', '_') # currentObjStr holds something like 'cpg.root.something.else' currentObjStr = ".".join(validObjects) #--------------- # Cache check #--------------- # Generate a cacheKey from the first 'index' elements of objPathList cacheKey = tuple(objPathList[:index+1]) # Is this cacheKey in the objCache? if cacheKey in objCache: # And is its value not None? if objCache[cacheKey]: # Yes, then add it to the list of validObjects validObjects.append(obj) # OK, go to the next iteration continue # Its value is None, so we stop # (This means it is not a valid object) break #----------------- # Attribute check #----------------- if getattr(eval(currentObjStr), obj, None): # obj is a valid attribute of the current object validObjects.append(obj) # Store it in the cache objCache[cacheKey] = eval(".".join(validObjects)) else: # obj is not a valid attribute # Store None in the cache objCache[cacheKey] = None # Stop, we won't process the remaining objPathList break # Return the last cached object (even if its None) return objCache[cacheKey] def mapPathToObject(path = None): # Traverse path: # for /a/b?arg=val, we'll try: # root.a.b.index -> redirect to /a/b/?arg=val # root.a.b.default(arg='val') -> redirect to /a/b/?arg=val # root.a.b(arg='val') # root.a.default('b', arg='val') # root.default('a', 'b', arg='val') # Also, we ignore trailing slashes # Also, a method has to have ".exposed = True" in order to be exposed if path is None: path = cpg.request.objectPath or cpg.request.path if path.startswith('/'): path = path[1:] # Remove leading slash if path.endswith('/'): path = path[:-1] # Remove trailing slash if not path: objectPathList = [] else: objectPathList = path.split('/') objectPathList = ['root'] + objectPathList + ['index'] # Try successive objects... (and also keep the remaining object list) objCache = {} isFirst = True isSecond = False isDefault = False foundIt = False virtualPathList = [] while objectPathList: if isFirst or isSecond: # Only try this for a.b.index() or a.b() candidate = getObjFromPath(objectPathList, objCache) if callable(candidate) and getattr(candidate, 'exposed', False): foundIt = True break # Couldn't find the object: pop one from the list and try "default" lastObj = objectPathList.pop() if (not isFirst) or (not path): virtualPathList.insert(0, lastObj) objectPathList.append('default') candidate = getObjFromPath(objectPathList, objCache) if callable(candidate) and getattr(candidate, 'exposed', False): foundIt = True isDefault = True break objectPathList.pop() # Remove "default" if isSecond: isSecond = False if isFirst: isFirst = False isSecond = True # Check results of traversal if not foundIt: raise cperror.NotFound # We didn't find anything if isFirst: # We fo... [truncated message content] |
Update of /cvsroot/winopenrpg/openrpg1/orpg/templates/nodes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/templates/nodes Added Files: Bastion_adventure.xml Darwin_adventure.xml FFE_adventure.xml Idiots_guide_to_openrpg.xml MiniatureLibrary.xml StarWars_d20character.xml Userguide098.xml Userguide13.xml adnd_2e_char_sheet.xml alias.xml browser.xml d20character.xml d20sites.xml d20srd.xml d20wizards.xml default_map.xml die_macro.xml die_roller_notes.xml dnd3e.xml encounter.xml form.xml grid.xml group.xml image.xml link.xml listbox.xml macro.xml minlib.xml openrpg_links.xml split.xml tabber.xml text.xml textctrl.xml u_idiots_guide_to_openrpg.xml urloader.xml wizards.xml Log Message: Initial commit of OpenRPG++ python --- NEW FILE: listbox.xml --- <nodehandler class="listbox_handler" icon="gear" module="forms" name="List Box" version="1.0"> <list send_button="0" type="0"> <option selected="1" value="">Option Text I</option> <option selected="0" value="">Option Text II</option> <option selected="0" value="">Option Text III</option> </list> </nodehandler> --- NEW FILE: textctrl.xml --- <nodehandler class="textctrl_handler" icon="note" module="forms" name="Text" version="1.0"> <text multiline="0" send_button="0">text</text> </nodehandler> --- NEW FILE: die_roller_notes.xml --- <nodehandler class="tabber_handler" icon="tabber" module="containers" name="Die Roller Notes" version="1.0"> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Basics" version="1.0"> <text multiline="1" send_button="1"> The new dieroller is design with expansion in mind. While there are a number of new dieroller options in the base roller, the new design facilitates the building of new rollers that can be loaded at any time. In this test build three are 3 rollers: std, d20, and wod. The std roller is the generic roller. It has generic dice options and is the base for all other dierollers. The d20 and wod rollers are game specific rollers and have game specific options. They also serve as examples for how to create your own rollers in python. ** Please not that this is our initial release of the roller. The new syntax might see odd to you. We are considering an alternative syntax, and this is being discussed on the openrpg.com forums. In if you have strong opinions on this, you might want to hop over there and give your 2 cents. ** Dierollers: You can see what roller you are using by using the "/dieroller" command in chat. By default it should be "std". To set the die roller, use "/dieroller roller_name". So to load the d20 roller, type "/dieroller d20". Its easy! Basic Syntax. The basic syntax is the same, 3d6+3, rolls three six side dice plus 3. However, the new roller has other options, they look like this: 3d6.option(value) If you know anything about programming, that probably looks familiar. For average users, this might look a little confusing, but lets look at a real example. [10d6.takeLowest(2)] Now this option rolls 10d6 and takes the lowest two rolls. Basically, to use an option, you have put a . + the option name + the values for the option between ( ). You can also chain many options together. [10d10.minroll(4).takeLowest(5)] This example rolls 10d10 with a minimum roll of 4 and takes the lowest 5. Pretty nifty if I do say so myself. </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="STD rollers" version="1.0"> <text multiline="1" send_button="1"> Now that you know how to roll dice, lets look at the standard options. takeHighest - take highest X rolls [10d10.takeHighest(4)] - takes highest 4 takeLowest - take lowest x rolls [10d10.takeLowest(4) - take lowest 4 minroll - minimum low range [10d10.minroll(4)] - no die roll lower than 4 extra - roll an extra die when roll greater or equal to X [10d10.extra(9)] - roll an extra die when a die roll is 9 or higher. open - same as extra but roll extra dice until a die is not greater or equal to X (even the extra roll). [10d10.open(9)] - roll extra dice until a die roll is not 9 or higher. each - apply X value to all dice [10d10.each(2)] - add 2 to every die roll </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="d20 roller" version="1.0"> <text multiline="1" send_button="1"> Remember, to use the d20 roller type: "/dieroller d20" dc(DC,mod) - make a DC check. [1d20.dc(20,5)] - make a DC check against DC value of 20 and a modifier of +5. attack(AC,mod,critical) - make an attack roll. [1d20.attack(20,5,19) - make an attack roll against AC 20 with a modifier of +5 and a critical range of 19-20. </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="wod roller" version="1.0"> <text multiline="1" send_button="1"> Remember, to use the wod roller type: "/dieroller wod" vs(target) - vs roll against target [3d10.vs(5)] - vs roll against 5.</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Hero Roller" version="1.0"> <text multiline="1" send_button="0"> Skill Roller, example [3d6.sk(11,0)]-- Make a SKill roll. The first number of the two modifiers is the rating in the skill, 11 meaning 11 or less. The second number is any penalty or bonus you have for the roll. A positive number is a bonus, a negative number is a penalty. As with many Hero system rolls, the only die choice that makes sense is 3d6 To-hit roller, example [3d6.cv(5,1)] Make a to-hit roll. The first modifier is your Combat Value. The second number is any penalty or bonus you have for the roll. A bonus is positive, and a penalty is negative. Again, the only roll that is sensible is 3d6. The result of the roll is the the highest Defensive Combat Value that can be hit with that roll. Killing damage roller, example [(1d6+1d6/2).k(0)] Make a damage roll for Killing damage. The only modifier is the bonus to the stun multiplier. A 1 in that field would indicate an increased stun multiplier of +1. The result shows body and stun totals. Only sensible for d6 values. Normal damage roller, example [(5d6+1d6/2).n()] Make a damage roll for Normal damage. Results show body and stun totals. No modifiers exist. Only sensible for d6 values. Hit Location roller, example [3d6.hl()] Roll on the hit location chart. Results show the location hit (including left or right side) and multipliers to damage when hitting that location. No modifiers exist. Contributed by Heroman Basic Killing damage roller, example [2d6.hk()] Make a damage roll for Killing damage. Always uses a stun multiplier of 1 for ease of use with the Hit Location roller mentioned above. No modifiers exist. Contributed by Heroman </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="? Option" version="1.0"> <text multiline="1" send_button="1"> Another new feature is the ? option. If you place a ? in a dice string you will be prompt by a dialog for the value. This is useful when using die rolls in character sheets. From example: [3d6+?] - will ask you for a value to replace ?. A more game specific example might look like this: [1d20.dc(?,5)] - this will prompted you for the ? value, which is the DC.</text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="The End" version="1.0"> <text multiline="1" send_button="1">Well, that's all I have to say about the new roller. More options and game specific rollers on the way. If you're interested in coding a roller for your favorite game, drop by the dev server and we'll try and help you out. -Chris Davis </text> </nodehandler> </nodehandler> --- NEW FILE: die_macro.xml --- <nodehandler class="dieroll_handler" icon="d20" module="core" name="Die Macro">example roll [1d20+4]</nodehandler> --- NEW FILE: alias.xml --- <nodehandler class="voxchat_handler" icon="player" module="voxchat" name="Alias Library" use.filter="0" version="1.0"> <voxchat.filter name="Rogue or Pirate"> <rule match="ia" sub="'a"/> <rule match="(\W+)(?i)it is(\W+)" sub="\1t'is\2"/> <rule match="(\W+)(?i)h" sub="\1'"/> <rule match="(?i)his" sub="'is"/> <rule match="n[tkg](\W+)" sub="n'\1"/> <rule match="(\W+)(?i)to(\W+)" sub="\1t'\2"/> <rule match="(\w+)th(\W+)" sub="\1t'\2"/> <rule match="(\W+)([Yy])ou(\W+)" sub="\1\2ea\3"/> <rule match="(\W+)([Yy])our(\W+)" sub="\1\2er\3"/> <rule match="'+" sub="'"/> <rule match="th" sub="d'"/> <rule match="ass" sub="arse"/> </voxchat.filter> <voxchat.filter name="Static Communicator"> <rule match="ce" sub="ssss(**shhhshh**)"/> <rule match="[IiOo]" sub="(**sss**)"/> <rule match="wi" sub="(*squack*) "/> <rule match="run" sub="@#$%DDD "/> <rule match="[Ss][Tt]" sub=";lkj%$# "/> <rule match="opy" sub="op.....he"/> <rule match="[Bb]"/> <rule match="the" sub="te"/> <rule match="ow" sub="ashh"/> <rule match="[Aa][Bb]" sub="bbb"/> <rule match="ll" sub="ya"/> <rule match="support" sub="(*rssshhhh*)'pt"/> <rule match="ey" sub="eeee"/> </voxchat.filter> </nodehandler> --- NEW FILE: macro.xml --- <nodehandler class="macro_handler" icon="gear" module="chatmacro" name="Macro" version="1.0"> <text>/name MyOtherName Hello, World /name MyRealName</text> </nodehandler> --- NEW FILE: minlib.xml --- <nodehandler class="minilib_handler" icon="gear" module="minilib" name="Miniature Library" version="1.0"/> --- NEW FILE: grid.xml --- <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Grid" version="1.0"> <grid autosize="1" border="1"> <row version="1.0"> <cell/> <cell/> </row> <row version="1.0"> <cell/> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> --- NEW FILE: adnd_2e_char_sheet.xml --- <nodehandler class="static_handler" icon="d10" module="core" name="ADnD Character Sheet "> <group_atts border="1" cols="1"/> <nodehandler class="static_handler" module="core" name="<b><u>Main</b></u>"> <group_atts border="1" cols="2"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Vitals"> <grid border="1"> <row> <cell>Name</cell> <cell/> </row> <row> <cell>Character</cell> <cell/> </row> <row> <cell>Race</cell> <cell/> </row> <row> <cell>Class</cell> <cell/> </row> <row> <cell>Sex</cell> <cell/> </row> <row> <cell>Level</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="static_handler" module="core" name="Statistics"> <group_atts border="0" cols="1"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name=" "> <grid border="1"> <row> <cell>HP</cell> <cell>0</cell> <cell>0</cell> </row> <row> <cell>XP</cell> <cell>0</cell> <cell>0</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name=" "> <grid border="1"> <row> <cell>AC </cell> <cell/> </row> <row> <cell>Thaco (base)</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="<b><u>Stats</b></u>"> <group_atts border="0" cols="4"/> <nodehandler class="static_handler" module="core" name="<b>Basic stat</b> "> <group_atts border="1" cols="2"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Stats"> <grid border="0"> <row> <cell>Str</cell> </row> <row> <cell>Dex</cell> </row> <row> <cell>Con</cell> </row> <row> <cell>Int</cell> </row> <row> <cell>Wis</cell> </row> <row> <cell>Cha</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Stat ##"> <grid border="1"> <row> <cell>0</cell> </row> <row> <cell>0</cell> </row> <row> <cell>0</cell> </row> <row> <cell>0</cell> </row> <row> <cell>0</cell> </row> <row> <cell>0</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Strength"> <grid border="1"> <row> <cell size="119">Hit Prob</cell> <cell size="39"/> </row> <row> <cell>Dmg Adj</cell> <cell/> </row> <row> <cell>Wght Allow</cell> <cell/> </row> <row> <cell>Max Press</cell> <cell/> </row> <row> <cell>opn Drs</cell> <cell/> </row> <row> <cell>bb/lg</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Constitution"> <grid border="1"> <row> <cell>HP Adj.</cell> <cell/> </row> <row> <cell>Sys Shock</cell> <cell/> </row> <row> <cell>Res Surv.</cell> <cell/> </row> <row> <cell>Pois Save</cell> <cell/> </row> <row> <cell>Regen.</cell> <cell/> </row> <row> <cell>/ / / / / / / / / /</cell> <cell>/ / / / / / / / / /</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Intelligence"> <grid border="1"> <row> <cell># of Lang</cell> <cell/> </row> <row> <cell>Spell lvl.</cell> <cell/> </row> <row> <cell>Learn Spl</cell> <cell/> </row> <row> <cell>Spells / lvl</cell> <cell/> </row> <row> <cell>Immun.</cell> <cell/> </row> <row> <cell>/ / / / / / / / / /</cell> <cell>/ / / / / / / / / /</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name=" "> <group_atts border="0" cols="3"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Dexterity"> <grid border="1"> <row> <cell>React Adj.</cell> <cell/> </row> <row> <cell>Missile Adj.</cell> <cell/> </row> <row> <cell>Defense Adj.</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Charisma"> <grid border="1"> <row> <cell># of Henchmen</cell> <cell/> </row> <row> <cell>Loyalty Base</cell> <cell/> </row> <row> <cell>React Adj.</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Wisdom"> <grid border="1"> <row> <cell>Magic Def.</cell> <cell/> </row> <row> <cell>Bonus Spls.</cell> <cell/> </row> <row> <cell>Spl Fail</cell> <cell/> </row> <row> <cell>Immune.</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="<b><u>Weapon Proficiencies</b></u>"> <group_atts border="1" cols="1"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Weapons"> <grid border="1"> <row> <cell>Weapon</cell> <cell>Thaco</cell> <cell>To Hit/Dmg</cell> <cell>Dmg S/M</cell> <cell>Dmg L</cell> <cell>spd</cell> </row> <row> <cell/> <cell/> <cell/> <cell/> <cell/> <cell/> </row> <row> <cell/> <cell/> <cell/> <cell/> <cell/> <cell/> </row> <row> <cell/> <cell/> <cell/> <cell/> <cell/> <cell/> </row> <row> <cell/> <cell/> <cell/> <cell/> <cell/> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="<b><u>NonWeapon Proficiencies, Saves, etc</b></u>"> <group_atts border="1" cols="2"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Non-Weapon Proficiencies"> <grid border="1"> <row> <cell>Proficiency</cell> <cell>modifier</cell> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> <row> <cell/> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="static_handler" module="core" name="<b>Saving Throws</b>"> <group_atts border="0" cols="2"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name=" "> <grid border="0"> <row> <cell><b>Save</b></cell> </row> <row> <cell>Para/Pois/Death</cell> </row> <row> <cell>Rod/Staff/Wand</cell> </row> <row> <cell>Petri/Poly</cell> </row> <row> <cell>Breath Wpn</cell> </row> <row> <cell>Spell</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name=" ##"> <grid border="1"> <row> <cell>Roll</cell> <cell>mod</cell> </row> <row> <cell/> <cell>0</cell> </row> <row> <cell/> <cell>0</cell> </row> <row> <cell/> <cell>0</cell> </row> <row> <cell/> <cell>0</cell> </row> <row> <cell/> <cell>0</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> </nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="<b><u>Equipment</b></u>"> <group_atts border="1" cols="2"/> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Money"> <grid border="1"> <row> <cell>Platinum</cell> <cell/> </row> <row> <cell>Gold</cell> <cell/> </row> <row> <cell>Silver</cell> <cell/> </row> <row> <cell>Copper</cell> <cell/> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="Backpack / Pouches / etc.">empty</nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="<b><u>Magic</b></u>"> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Spells per level"> <grid border="1"> <row> <cell>1st lev</cell> <cell/> <cell>2nd lev</cell> <cell/> <cell>3rd lev</cell> <cell/> <cell>4th lev</cell> <cell/> <cell>5th lev</cell> <cell/> </row> <row> <cell>6th lev</cell> <cell/> <cell>7th lev</cell> <cell/> <cell>8th lev</cell> <cell/> <cell>9th lev</cell> <cell/> <cell>///</cell> <cell>///</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="Spells">Magic Missile Sleep Bigby's Poking Finger</nodehandler> <group_atts border="1" cols="2"/> </nodehandler> </nodehandler> --- NEW FILE: tabber.xml --- <nodehandler class="tabber_handler" icon="tabber" module="containers" name="Tabber" version="1.0"/> --- NEW FILE: split.xml --- <nodehandler class="splitter_handler" icon="divider" module="containers" name="Splitter" version="1.0"/> --- NEW FILE: Idiots_guide_to_openrpg.xml --- <nodehandler class="static_handler" module="core" name="Idiot's Guide, 0.9.4" status="useful"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="Table of contents:"><a href="#c1">Chapter 1:</a> Chatting up them OpenRPGers <a href="#c2">Chapter 2:</a> Rolling those dice and whispering those sweet nothings <a href="#c3">Chapter 3:</a> Creating your own Character sheet <a href="#c4">Chapter 4:</a> Opening your own room and sharing your character with all <a href="#c5">Chapter 5:</a> Advanced stuffs (like pictures) <a href="#c6">Chapter 6:</a> Setting up your own server</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="Introduction">Welcome, Mateys, to the online gaming universe of the OpenRPG! By now you have successfully downloaded and installed the program or you would not be able to read this document. But we are not here to discuss what you already know, we are here to get around to telling you how to do those things you don't know. <b>What we'll cover in this guide:</b> 1)How to use the basic OpenRPG 2)How to do those really nifty advanced things in OpenRPG 3)How to get your brother to do those nifty things in OpenRPG while you beat him with a whip 4)Getting along with those others on the OpenRPG program And, before long, before you know it, and before that chicken in the microwave is done, you will know and have mastered the art of using OpenRPG.</nodehandler> <nodehandler class="static_handler" module="core" name="Chapter 1:OpenRPG Basics"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="Chapter 1: OpenRPG and you"><a name="c1"></a> Alright. Let us start at the beginning. Many many years ago the dinosaurs roamed the planet. But then an asteroid, now most commonly known as "Bill Gates 1," crashed into the earth and wiped them all out, turning them into oil. The basic upshot of all that is with this oil we have created electricity which is now running the program. Before we start it is nessessary to make a name for yourself. Why? Well, you wouldn't like to be going around with everybody knowing only as 'blankman' would you? To type in your name, go up to the top left window, and dragging donw the "OpenRPG" menu bar press "settings". in there you can alter your name and colours. Whenever you wish to change your name, you must first disconnect from any server (We'll get to servers and conenction next paragraph)) then reconnect before the change occurs. You can also rename yourself in a much more simple way by typing in the command "/name " and your name behind it in the chat window. I just made you go into the settings so that you know where they are later. Now that you have yourself labled, you are probably wondering "where's all the chat?" Well, my friend, we are here to answer that. As your program booted up, it should have brought up a window marked "OpenRPG" in the top left, a "Player list" in the bottom left, a map on the top right, and a chat screen on the lower right. We will first concern outselves with getting that lower right window to do the work and play with the others later. To get the chat window to work, first we actually need a place to chat. To do this, we need to browse the list of rooms on what is known as the <i>Tracker</i>. To open the tracker window go to the "OpenRPG" window and under the menu "Game Server" click on "Browse Tracker" As you can see, a new window popped up. This is the <i>Tracker Window</i>. On the left you will see a list of various servers running OpenRPG online. Click on the one at the top of the list and press "Connect". After pressing the "Connect" button you will join that server and be instantly dropped into it's lobby. Welcome to your first chatroom. Feel free to stay in this room as long as you like or move off to another room listed on the Tracker. When you first turned on OpenRPG the chat window filled up with all the different chat commands available. If you are like me and have forgotten them by now you can either click on the little text entry box and press <i>Page Up</i> or type in <i>/help</i>. Should you wish a way to scroll up instead of 'page up'ing you can increase the amount of lines the Chat Window stores by making the <i>Buffer Size</i> larger. Either do this in the settings window or in the chatwindow itself and it will be saved for the next time you use OpenRPG. By now I'm sure you are also wondering what that <i>status</i> thingy in the bottom left window is. If you notice, every time you type a message in the chat window your status changes. And when you finish your msg (or sit for 5 seconds waiting) it changes back to Idle. What a clever invention. Of course, if you wish to set your own status to override "Idle" temporarily, just type in the chat window "/status My_Status"; replacing "My_Status" for whatever msg you wish (no more than 13 letters can be seen though). As for now, spend some time and enjoy yourself in the lobby... we can come back to the tutorial when you are ready to learn about Rolling dice, whispering, and creating and using character sheets.</nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="Chapter 2: Dice Rolling and Whispering"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="Ah, you're back!"></a name="c2"></a> I'm astonished. 82% of all people who use this program never bother to read the tutorial, let alone come back to it. Then again 67% of all statistics are made up on the spot so we'll leave that for now and get on with what we are doing Now, to get back to business. By now you've hopefully held a conversation with someone (or at least yourself) in our chatroom and now I'm sure you're wondering "How can I roll some dice so I can smite those foolish mortals." Well, don't be discouraged, because that is what I'm here for. To roll dice you have two options. you can simply press the button on the dice toolbar or you can type in how many times you wish to roll. As pressing buttons is mostly self explanitory, this part will only cover typing in dicerolls. First we will choose how many dice we wish to roll at once. To say how many dice you wish to roll, just type in the number ((say.. 1))... so it would look like this: 1 now tell the type of dice you wish to roll. to do this you write a 'd' ((d for dice)) and the number of sides that dice has. Say I want to roll a 20 sided dice that one time it would look like this: 1d20 Now put those square brackets around it. They are the ones directly to the right of the "P" key. ((for quick reference on what I mean, find chapter two in this guide, right click-edit it, and look at how I have the dice written in the text blocks)) [1d20] hope I rolled well. I know it looks like those round brackets above the '9' and '0' keys but to type it in you need to use square brackets between the "P" and "\" keys. All you do is type that in the into the chat text box and it will automatically roll it for you. You could even do combinations of dice or simple mathmatics inside the dice macro as well... [4d20+1d10] [1d8-4] [6d4+3] [1d10*10] [(1d10+5)+(6d4-3)-12] Now that you have gotten the basics of the dice roll down, let us move on to some more advanced stuff. What I'm talking about is mainly for Whitewolf players, but good to know anyway. let's take a dice roll, shall we. Let's roll 5d10: [5d10] as you can see, I used a lowercase "d" and it came up with each dice roll and the sum of all of them. Let's change that by capitalising the "D" shall we: [5D10] As you can see, instead of giving us a sum of all the dice, it instead told us what each dice had rolled. This is useful if you want individual dice rolls shown rather than a final result. Now that we have that basis down, we are going to set up the dice macro so that it compares how many dice have gotten over a number you tell it. you'll see what I mean in this demonstration: [5D10 vs 7] All that was added was the capital "D" and add the words "vs 7". Of course, you can change the number to whatever you wish. You will also notice, when you use it, that it will automatically detract the result for every '1' you roll on the dice. So if you rolled 5D10 and got a 7, an 8, a 9, and two '1's with a "vs 7" you would only have one success. Now for those of you who play AEG's Legend of the 5 Rings RPG (and possibly their other games), your probably wondering how do I handle rolling for L5R. No worries there, we've got you covered. The syntax is the same as in the rule book. Say we want to roll 5 dice and keep only 2, we type that in as: [5k2] This will roll 5d10 and keep the highest 2 dice, and will handle the re-rolling of any 10's. But what about all those unskilled and similar skill rolls you need to make, where you can't re-roll the 10's. It only takes a small change to the previous roll. We just tack on a 'u' to the end, like so: [5k2u] </nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>Need to tell that special someone that you like them but don't want the room to hear?</u></b>">Well, the designer of OpenRPG thought of such eventualities, and has provided us with the ability to whisper at will to whoever is in the room. Doing so is quite easy in it's own way, and we'll show you here. All that is required is for you to type in a <i>/w</i> then the person's name ((Caps are important!!!)), an equals sign (( = )) and then your message... so it would probably look something like this: <i>/w Monkeyman=</i>I think that Woody person that is usually in the Lobby is soo cute <i>/w Susan=</i>yeah, but I hear he likes bananas so I won't go near him And there we go. But I can hear your cries "What about those people that have extrodinarily annoying names?" Well, do not fret... just go over to your player list in the lower lefthand corner and right click on the little heads next to their name. Magically, one of those menu bars will appear and give you the option to whisper to them. Press it and watch as the '/w' command comes up in the chat text box. How nice! And should you wish to whisper to more than one person, just add each name after the initial '/w' and put commas between them, like so: <i>/w Susan, Monkeyman, Thatotherguy=</i>Vote Woody for Overlord 2001!!! simple as that. You could even whisper dice the exact same way. Stay tuned for the next chapter where we get into how to create, use, and abuse your own character sheets.</nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="Chapter 3: Nodes and Character Sheets"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="<b>The Basics</b>"><a name="c3"></a> alright.. by now you have grasped the basics on how to chat, so we will move on to the next phase: character sheets. This part of the tutorial will cover how to make a full usable document from the nodes. Let us first start by describing what the different nodes are and where we can find them:</nodehandler> <nodehandler class="static_handler" module="core" name="The Nodes"> <group_atts border="1" cols="4"/> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>the Group Node</u></b>">as we look at the various nodes they all appear to be stored in a blue bottled icon called "Wizards". Wizards itself is a group node and it shows the function of the 'group': to store and combine all the nodes which would otherwise just float randomly on the Gametree. When we make a group, we do so so that we can store our information into one well organized array rather than a collection of scattered files.</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>Text Block</u></b>">The text block is where we will be storing most of our text information. It allows you to type in words, paragraphs, or entire pages of information you want to keep on your character sheet without the needless bother of grids. If you look carefully you will notice that the Dice Macro box is also just another Text Box but with some dice in it. You are fully able to put any dice on here in any place in the Text Body and when the character sheet is opened you will see that the dice has been rolled.</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>The Grid</u></b>">Now, text boxes are all well and good for storing words and paragraphs, but for easy storage of numbers that we use for reference there is nothing better than a Grid. The grid allows us to set up any manner of chart or graph to for quick reference and study.</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>The Macro Node</u></b>">The Macro Node works just like a normal text box node, but instead of seeing it in it's own window, everything that is typed into it is broadcast directly into the chat window as if you were saying it. This includes actions, whispers, status changes, etc. Good if you want to change names or perform series of speeches and actions in order just by doubleclicking on a node. We won't be getting to know them in this, but they are pretty self explanitory. Just doubleclick to use them and right click to edit.</nodehandler> </nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>Pick your Nodes and Eat It Too</u></b>">Now that you know what the various nodes are, I'm sure you're asking "how do I use then then?" Well, let us start with the main node, the Group. To create a group doubleclick on the "New Group" Node in the Wizards tree. you will see that a new "Group" has appeared at the top in the main Gametree. Open the new group by doubleclicking on it. ......... Oh my... all rather empty, isn't it? maybe we should fill it up with something. Let's make a text box to put in that void, shall we? Alright. Go into the Wizards directory again and doubleclick on the "New Text Block". Again you will see a Text Block has appeared at the top in the gametree. Doubleclick on it to see what's inside. ......... again it all seems very flat. I don't like that text in there so let's change it. this time rightclick on the Text Block and press "Edit". Voila!! You will see an 'edit' box where you can change the title and the text body, with lots of little extras down below. Go ahead and type in a new title and message for yourself. Now that you have added your personal message to the world it is time to add it to your group. Close down the Text Box Editor (it automatically saves, so just hit the 'x' button) and look up to your gametree again. Once you see it, grab your new edited Text Box and drag it into the group that you created earlier. Now doubleclick on the group and marvel at your creation. Congratulations on making your first character sheet. **Note. To edit everything in a group at once just right click on the group and press Edit.**</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>That's It?!?</u></b>">"That's it?! I want my money back! In fact, since it was free, I want you to give me money to compensate for having to read this long tutorial just for that!!!" Is what I'm sure you're thinking right now. But don't worry, our character sheets will be getting much more complex and much better looking from this point out. Turning back to our creation, we have a Group with a single Text box inside it. Now, I like that message you have typed in there, so let's double it's voice across the program. Right click on the text box inside the group you created, and press "clone". Voila again!!! You'll see an exact copy of your message has appeared at the top of the Gametree. Grab that Text box and Drag it onto the Group as well. Now open up your group by doubleclicking on it and take a look again at what you have created. ..... Hhhmmm.... It's all very well and dandy, but it's going to get a little long, isn't it. If each item we put in there is going to be lined up straight down this thing is going to be pages in length. Well, luckily for you, we have a way around that. closing down your group window let us look up to the Gametree again. This time, instead of doubleclicking on the Group, right click on it. You'll see it brings up the same menubar as what the textbox had. Don't worry, we'll get to play with all the nifty features later. Go down and press edit. A little edit box should have popped up with Three Options. The first is to rename the Group, the second dictates how many columns we can have (1-4), and the third just asks if we want borders. Lets first change the name of our group to "Lagmonkey's Group". Then, lets change the 'Columns" from one to two then close down the edit box. Open up the Group again and look now as it appears the two text messages are side by side. How convenient! In fact, as you saw you can have up to four things side by side at the same time. Any further Nodes inside the group ((the 5th one and beyond)) will just get put in one of the columns under the first four, in order.</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>Ah, but will she swallow it, Stevie Wonder?</u></b>">Now that we have our simple group with it's two columns and it's text boxes, it's time to add a Grid, I think. create a grid the same way as we did the Text Box and the group, then go in and edit it. What we want is a Grid that looks exactly like this:</nodehandler> <nodehandler class="rpg_grid_handler" icon="grid" module="rpg_grid" name="Grid"> <grid border="1"> <row> <cell>I</cell> <cell>Really</cell> <cell>Love</cell> <cell>Bananas</cell> <cell>Matey!</cell> </row> </grid> <macros> <macro name=""/> </macros> </nodehandler> <nodehandler class="text_handler" icon="note" module="core" name=" ">Be sure to add three extra columns for the words by pressing the "Add Columns" button. Oh, and get rid of that bottom row, we don't need it right now (though, if you wish, you are welcome to put numbers in the bottom row instead of removing it for practice if you like.) Now that we have our grid, let's drag it onto our group and open up our group, shall we? Oh my.. the Bananas are overlapping, aren't they? It appears the column isn't wide enough to support a grid so wide. But I like bananas, so we are going to have to keep the grid. What we need would be a nice large column to hold it though, so let's make one. Go into the Wizards again and create another new group. Now drag the Grid over onto the new group, then picku p and drag "Lagmonkey's Group' onto the new group. yes, matey, a group within a group. Open it up and see what the result is. Interesting. It appears that the grid and the two columns are all inside the new group's main column. We could continue to stack columns into each other into infinity, though it would get increasingly hard to read. Getting back, you can now see the grid is on top and Lagmonkey's group is on the bottom. I don't like that arrangement so let's change it. Close down the New Group box then on the gametree grab the grid and drag it onto the new group's icon once again. You'll see that that had the effect of re-adding the grid to the new group, this time placing it at the bottom. Whenever you add something to a group, it always appears at the bottom.</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="<b><u>That's it!</u></b>">That's basically it for making character sheets. Feel free to experiment with various combinations of items and settings.. add and remove borders to see what you like.. create grids of varous sizes and shapes and stick them into your columns...perhaps put some dice in those text boxes of yours ((with the appropriate "[3d6]" around them))... but most of all: Have fun! **Note: Be sure to save your character sheet if you want to. To do so, right click on it and press "Save Node"**</nodehandler> <nodehandler class="text_handler" icon="note" module="core" name="Moving Characters from almost ANY Character Generator to here.">The following are stpes on how to move your character sheets from your favorite Character Generation program into OpenRPG. It's very quick and clean cut. 1) first, using your character generator, you convert your character to either txt format, or, preferably (if you have the ability) HTML format. 2) Then, you go to wizards.put a text block into the gametree 3) if you are using txt, select all, and copy. if you are using HTML format, edit it using notepad, but don't change any of the HTML Program. then select all and copy 4) now, going back into OpenRPG, right click on the text block you created and push 'edit' 5) clear the text block of the few words of text that appear there then paste the copied info into by either rightclicking-paste or pressing 'ctrl v' 6) then name your character in the Title then close the text block. Finished no more need to create character sheets! That simple and any time you want to see it, just doubleclick on the text node.</nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="Chapter 4: Setting up rooms/games and Ignore"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="There's a party going on down my street tonight..."><a name="c4"></a> now that you have your custom built char sheet and you have talked enough players into starting a game, it's time to set up your own room. Let's bring up that Tracker window again ((under the Game Server menu)). Looking to the bottom left of it, you'll see a little box that allows you to type in your own room name and add a password if you like. Let's start a room called "Working on that darned fun tutorial" and not put a password up. as you see the room you create will be exactly the same as the lobby, so there is nothing to worry about. Lets load up our character sheet by right clicking on the "Game Tree" and pressing Insert file" Now that we have our little char sheet we might as well show the world. This can be done in one of three ways. The first way is to right click on it and send it to other players (provided there are any). This is how other players can get your sheet as they can't see it until you send it to them. The second way is to send it directly to the chat and let everyone see it there. go ahead and do that by right clicking on the char sheet and pressing "Send to Chat". Now everyone gets to see it as it appears on their text chat window. the third option, Whisper to Player, is much the same as "Send to chat" only it sends it to a specific person or persons to see in their chat instead. Of course, there sometimes comes a day when there is a little spammonkey running about. You know the type. Just keeps askin pointless questions, hitting you with the same text, and generally making himself a nuesance. Well fear not! We have added in a brand new feature that allows you to ignore those unscrupulous people. In the chat entry box just enter the command "/i player_ID#" and that will put a person on ignore (or toggle them back to un-ignore). To get a list of ID's who are on your ignore list, just type "/i" alone, with nothing else. You can even ignore/un-ignore multiple people at once, just put commas (( , )) between the names. And that's it. Ignore at your leasure.</nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="Chapter 5: Maps and Minis"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="So you had to ask..."><a name="c5"></a> Now comes the hardest part of the tutorial.. describing how to use minis and pictures. Well, the first step is to bring up the map. With any luck it is still open so let's find it. It'll be the big green one that was in the top right of the screen. If it's not there, click on the "Windows" menu under the OpenRPG main window and press "Map Window". </center>**note!** If you are in the lobby, the changes you make to the map will not be seen by other players. You will need to create another room to have a shared map.</center> In this map window you'll see all of the basic operations needed to load an image and edit the map. Let us attempt to load our first picture. At the bottom of the map window you'll see a large box where you can type in text. This is where you put the Webpage Address for the image you wish. Let's type in this addy. http://www.openrpg.com/images/mins/amazon.gif after you have typed it in press the "add minature" button, sit back, and watch as it loads the beautiful amazonian woman (well, almost) into the map folder <center><b><u>OpenRPG tip:</u></b> All images need to be on webpages... you cannot load directly from your hard drive. The reason being this is actually one giant web browser.</center> Feel free to move it about, get a feel for how it works. You'll see at the moment that the amazon seems to hop from grid square to grid square. Well... if you're like me, you don't like being confined to grids or rules. So let's get rid of that grid, shall we? Looking at the map window again, you will see a little red diamond thing. They say it is a compass but it just looks like a flying fish to me. Anyway.. click on that and it will bring up all the map settings. In there you can change the size of the map, what colour it is, or even load up a background on which all the mini's sit ((say, a dungeon map you drew or perhaps that picture of Britney Spears I know you have lying around)). The thing we are interested the most, though, will be the grid settings at the bottom. As you can see, you can change the size of the grid and switch the grid from square(4 sided) to hex (6 sided). You'll also be able to let mini's either abide by the grid, or become free from the black lined prison. Let us turn off the grid snap and press "apply." Now, go back into the map window and try moving your mini again. You'll see that it now moves freely, and ignores those lines. But ignoring them is not enough, did I hear you say? You want them gone? Ok. Let's go back into the settings (the red star button) and this time change the grid size to zero. Pressing 'apply' you will see that the grid has disappeared entirely from map. Course, if you want it back, just go put in the size again (50 is default). There are other options (like direction pointing for your mini's) so feel free to play around a bit. <center><b><u>Note:</u></b> you can find many great mini's on the OpenRPG webpage.</center></nodehandler> <nodehandler class="link_handler" icon="html" module="core" name="<center>www.OpenRPG.com</center>"> <link href="http://www.OpenRPG.com"/> </nodehandler> </nodehandler> <nodehandler class="static_handler" module="core" name="Chapter 6: Make your fortune in Server Control!"> <group_atts border="1" cols="1"/> <nodehandler class="text_handler" icon="note" module="core" name="<b>Well, maybe not any money...</b>"><a name="c6"></a> Well, maybe not any money in creating a server on this program, but there are definately perks to running one. But to run one we must first understand what they do. A server is the mother for all of us on OpenRPG. It runs all of our major commmands, keeps us all talking together, and hosts all the games on OpenRPG. When you first connected to OpenRPG you had to choose a server from a list in the tracker, on the left. Now let us put your name (or at least your computer's) out there so others can flock to you. For this we must return to your OS. Go into your computer, into the OpenRPG folder. In there, you will see a file marked: mplay_server.py You can also find it in your start menu, right next to the OpenRPG program itself. Once you find it, (double)click on it. The first thing that will come up is a python MSDOS window... It will first prompt on if you wish for your server to bee seen by the OpenRPG tracker list. Press "Y" and hit enter (won't get anyone if we don't know it exists). Next, it will prompt you for a name for your Server. Write in "Temporary Server" and press enter. Now the window will start running the various server-y looking bits of code. as soon as it is up and going, you will told the various commands that are available to the server. The first one will be "kill". this is what you type into the MSDOS window to shut the server down. <b>**IMPORTANT**</b> if you shut down the server all rooms on it will close. You don't want to shut it down if others are using it. The second command is the "dump" command. This will list all the people on your server, as well as their ID number. It really isn't important at this time but it will come in handy later. Broadcast is self explanitory. The 'Announce' and 'Remove' features allow you to choose later on if your server can be seen on the tracker, or not. Dump Groups gets the same info as Dump but does Groups instead of people. And finally you can bring up this little list of commands at any time by typing 'help' or '?'. You can type these commands in any time in the python server window. Go ahead and try 'help' or 'dump' and see what comes up. Alright! That's it. to access your server, run you OpenRPG program as normal (with the server MSDOS window in the background) and log onto your server as we had shown you in chapter 1. This will come in handy if there are no Servers running and you wish to use OpenRPG with, perhaps your gaming buddies or maybe some underworld kingpins you need to 'have a talk' with. And From All of us at OpenRPG HQ, we wish you good gaming!</nodehandler> </nodehandler> </nodehandler> --- NEW FILE: browser.xml --- <nodehandler class="webbrowser_handler" icon="browser" module="core" name="Browser Link"> <link href="http://"/> </nodehandler> --- NEW FILE: Darwin_adventure.xml --- <nodehandler class="group_handler" module="containers" name="Darwin's World" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="link_handler" icon="html" module="forms" name="Website" version="1.0"> <link href="http://www.darwinrpg.com/"/> </nodehandler> <nodehandler class="group_handler" module="containers" name="Cave of Life" version="1.0"> <group_atts border="1" cols="1"/> <nodehandler class="link_handler" icon="html" module="forms" name="Cave of Life Adventure (PDF)" version="1.0"> <link href="http://www.rpgobjects.com/dlm/download.php?id=4"/> </nodehandler> <nodehandler class="tabber_handler" icon="tabber" module="containers" name="Descriptions" version="1.0"> <nodehandler class="textctrl_handler" icon="note" module="forms" name="Introduction" version="1.0"> <text multiline="1" send_button="1"> The deserts of the post-holocaust world are a dangerous place, where fresh water is scarce and men brave enough to defend it even scarcer. Storms of radiated wind, blights lasting years, and the savage bands of mutated raiders - often well equipped - are just some of the hazards that threaten to squelch good men and communities from rising from the ashes to find a new future. You and your fellows are a group of youths who have been raised in a small desert community in the wasteland. You have seen the efforts and toil of your forefathers to build this small community into a hopeful future, and you have lived well among the others behind the adobe walls of your desert home. Yet the time comes when you and your friends, nearing adulthood, will be required to pass the rite of manhood that all warriors and heroes of your tribe have taken since ages past. You must travel to the infamous Cave of Life, and retrieve water from the endless supply said to be hidden deep within the complex's many twisting caves. You must brave the dangers of the cave, its rumored denizens, and bring back at least four full waterskins to your community so that you can take your place among the worthy. </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="A. Cave Entrance." version="1.0"> <text multiline="1" send_button="1"> Following the words of your elders, half concealed in deep spiritual poetry and deceptive metaphors, you have at last found what must be the entrance to the legendary Cave of Life. Here, the flat level expanse of nothingness suddenly gives way to isolated bumps and rises, as if the sands themselves were acting to hide some secret just beneath their surface. A few hundred paces up ahead, silhouetted by the rays of the dying sun, can be seen distant metal "pillars", perhaps fifteen feet high or so, jutting from the sand in the distance. Nearby, amid the circle of rubble where the wind cannot groom the sand completely flat, the observant among you spot something of quieting wonder - a section of cracked stone, cleared of sand, at the center of which lies a huge "hatch" of thick metal, almost as large as a man. Jammed open by thick flakes of age-old rust on its heavy iron hinges, it seems to beckon your approach with an almost "malevolent" impatience. This surely must be the Cave of Life. </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="B. Entranceway." version="1.0"> <text multiline="1" send_button="1"> A narrow shaft, made of some remarkably smooth stone - no doubt worked by some unknown hand - descends into the cold darkness below. One by one you descend on old iron rungs set straight into the stone, aware of the growing blackness and increasing chill. Finally, at the bottom, you find what appears to be a series of small empty chambers, each filled with dust and accumulated debris - bits of stone, rotted trash, and bundles of wiring, piping, and twisted iron bars fused in some great catastrophe long, long ago. Your footsteps echo eerily into the darkness, but there is a slight comfort - a comfort in knowing that past heroes of your people once passed this way as well in search of the cave's eternal source of water. </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="C. Elevator Shaft." version="1.0"> <text multiline="1" send_button="1"> The cold and unfeeling silence of concrete has given way to an even colder and more menacing nothingness. A metal platform sits here, ringed by a slender, weakened railing of rusted iron, over the edge of which you can only see plummeting darkness with seemingly no end - up or down. A small set of rusted stairs appears to descend into pitch blackness as well, running along the insides of the inner wall - from this height, with your meager lights (torch or lantern), your eyes can barely see the level below where the stairs level off, but you cannot make out any distinct features. </text> </nodehandler> <nodehandler class="textctrl_handler" icon="note" module="forms" name="D. Blast Doors" version="1.0"> <text multiline="1" send_button="1"> Your long descent into darkness, though precarious and terrifying, has finally come to an end at the great central shaft's end. But the area at the bottom of the rickety iron stairs is no less cold and lifeless as the chambers above. What few lights you have illuminate the open metal elevator shaft that continues on down past this area as well into more darkness below. A careful look over the edge shows a calm, black water has filled up the shaft nearly to this level, and there is no telling what lies further down the flooded shaft underwater. The stairs, too, continue on under the murky water, slipping out of sight due to the weakness of your lights. Along the surface of the walls of this area, you find a small alcove; here stands a pair of truly gigantic metal doors, each several feet larger than the tallest m... [truncated message content] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:27
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/networking In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/networking Added Files: __init__.py gsclient.py meta_server_lib.py mplay_client.py mplay_server.py mplay_server_gui.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: mplay_server.py --- #!/usr/bin/python2.1 # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- [...2464 lines suppressed...] else: FG = LCOLOR else: FG = PCOLOR pl += "<tr><td bgcolor=" + COLOR3 + ">" pl += "<font color=" + FG + " " + SIZE + "> (" + (self.players[id]).id + ") " pl += (self.players[id]).name pl += "</font></td><td bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + ">[IP: " + (self.players[id]).ip + "]</font></td><td bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + "> " pl += (self.players[id]).idle_status() pl += "</font></td><td><font color=" + FG + " " + SIZE + ">" pl += (self.players[id]).connected_time_string() pl += "</font>" else: self.groups[k].remove_player(id) pl +="<tr><td colspan='4' bgcolor=" + COLOR3 + " >Bad Player Ref (#" + id + ") in group" pl+="</td></tr>" pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b><i>Statistics: groups: " + str(len(self.groups)) + " players: " + str(len(self.players)) + "</i></b></font></td></tr></table>" except Exception, e: self.log_msg(str(e)) self.p_lock.release() return pl --- NEW FILE: meta_server_lib.py --- #!/usr/bin/python2.1 # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: meta_server_lib.py # Author: Chris Davis # Maintainer: # Version: # $Id: meta_server_lib.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: A collection of functions to communicate with the meta server. # #added debug flag for meta messages to cut console server spam --Snowdog META_DEBUG = 0 __version__ = "$Id: meta_server_lib.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" from orpg.orpg_version import PROTOCOL_VERSION from orpg.orpg_xml import * import orpg.dirpath import orpg.tools.config_files import urllib import orpg.minidom from threading import * import time import sys import random import traceback import re metacache_lock = RLock() def get_server_dom(data=None,path=None): # post data at server and get the resulting DOM if path == None: # get meta server URI path = getMetaServerBaseURL() # POST the data # print # print "Sending the following POST info to Meta at " + path + ":" # print "==========================================" # print data # print file = urllib.urlopen(path,data) data = file.read() # Remove any leading or trailing data. This can happen on some satellite connections p = re.compile('(<servers>.*?</servers>)',re.DOTALL|re.IGNORECASE) mo=p.search(data) if mo: data=mo.group(0) # print # print "Got this string from the Meta at " + path + ":" # print "===============================================" # print data # print # build dom xml_dom = parseXml(data) xml_dom = xml_dom._get_documentElement() return xml_dom def post_server_data( name, realHostName=None): # build POST data ## data = urllib.urlencode( {"server_data[name]":name, ## "server_data[version]":PROTOCOL_VERSION, ## "act":"new"} ) ## if realHostName: data = urllib.urlencode( {"server_data[name]":name, "server_data[version]":PROTOCOL_VERSION, "act":"new", "REMOTE_ADDR": realHostName } ) else: #print "Letting meta server decide the hostname to list..." data = urllib.urlencode( {"server_data[name]":name, "server_data[version]":PROTOCOL_VERSION, "act":"new"} ) xml_dom = get_server_dom( data , "http://openrpg.sf.net/openrpg_servers.php") ret_val = int( xml_dom.getAttribute( "id" ) ) return ret_val def post_failed_connection(id,meta=None,address=None,port=None): # For now, turning this off. This needs to be re-vamped for # handling multiple Metas. return 0 # data = urllib.urlencode({"id":id,"act":"failed"}); # xml_dom = get_server_dom(data) # ret_val = int(xml_dom.getAttribute("return")) # return ret_val def remove_server(id): data = urllib.urlencode({"id":id,"act":"del"}); xml_dom = get_server_dom(data) ret_val = int(xml_dom.getAttribute("return")) return ret_val def byStartAttribute(first,second): # This function is used to easily sort a list of nodes # by their start time if first.hasAttribute("start"): first_start = int(first.getAttribute("start")) else: first_start = 0 if second.hasAttribute("start"): second_start = int(second.getAttribute("start")) else: second_start = 0 # Return the result of the cmp function on the two strings return cmp(first_start,second_start) def byNameAttribute(first,second): # This function is used to easily sort a list of nodes # by their name attribute # Ensure there is something to sort with for each if first.hasAttribute("name"): first_name = str(first.getAttribute("name")).lower() else: first_name = "" if second.hasAttribute("name"): second_name = str(second.getAttribute("name")).lower() else: second_name = "" # Return the result of the cmp function on the two strings return cmp(first_name,second_name) def get_server_list(versions = None,sort_by="start"): data = urllib.urlencode({"version":PROTOCOL_VERSION,"ports":"%"}) all_metas = getMetaServers(versions,1) # get the list of metas base_meta = getMetaServerBaseURL() #all_metas.reverse() # The last one checked will take precedence, so reverse the order # so that the top one on the actual list is checked last return_hash = {} # this will end up with an amalgamated list of servers for meta in all_metas: # check all of the metas # get the server's xml from the current meta bad_meta = 0 #print "Getting server list from " + meta + "..." try: xml_dom = get_server_dom(data=data,path=meta) except: #print "Trouble getting servers from " + meta + "..." bad_meta = 1 if bad_meta: continue if base_meta == meta: #print "This is our base meta: " + meta updateMetaCache(xml_dom) node_list = xml_dom.getElementsByTagName('server') if len(node_list): # if there are entries in the node list # otherwise, just loop to next meta # for each node found, we're going to check the nodes from prior # metas in the list. If a match is found, then use the new values. for n in node_list: # set them from current node if not n.hasAttribute('name'): n.setAttribute('name','NO_NAME_GIVEN') name = n.getAttribute('name') if not n.hasAttribute('num_users'): n.setAttribute('num_users','N/A') num_users = n.getAttribute('num_users') if not n.hasAttribute('address'): n.setAttribute('address','NO_ADDRESS_GIVEN') address = n.getAttribute('address') if not n.hasAttribute('port'): n.setAttribute('port','6774') port = n.getAttribute('port') n.setAttribute('meta',meta) end_point = str(address) + ":" + str(port) if return_hash.has_key(end_point): if META_DEBUG: print "Replacing duplicate server entry at " + end_point return_hash[end_point] = n # At this point, we have an amalgamated list of servers # Now, we have to construct a new DOM to pass back. # Create a servers element return_dom = orpg.minidom.Element("servers") # get the nodes stored in return_hash return_list = return_hash.values() # sort them by their name attribute. Uses byNameAttribute() # defined above as a comparison function if sort_by == "start": return_list.sort(byStartAttribute) elif sort_by == "name": return_list.sort(byNameAttribute) # Add each node to the DOM for n in return_list: return_dom.appendChild(n) return return_dom ## List Format: ## <servers> ## <server address=? id=? name=? failed_count=? > ## </servers> def updateMetaCache(xml_dom): try: if META_DEBUG: print "Updating Meta Server Cache" metaservers = xml_dom.getElementsByTagName( 'metaservers' ) # pull out the metaservers bit authoritative = metaservers[0].getAttribute('auth') if META_DEBUG: print " Authoritive Meta: "+str(authoritative) metas = metaservers[0].getElementsByTagName("meta") # get the list of metas if META_DEBUG: print " Meta List ("+str(len(metas))+" servers)" try: metacache_lock.acquire() ini = open(orpg.dirpath.dir_struct["user"]+"metaservers.cache","w") for meta in metas: if META_DEBUG: print " Writing: "+str(meta.getAttribute('path')) ini.write(str(meta.getAttribute('path')) + " " + str(meta.getAttribute('versions')) + "\n") ini.close() finally: metacache_lock.release() except Exception, e: if META_DEBUG: traceback.print_exc() print "Meta Server Lib: UpdateMetaChache(): " + str(e) def getRawMetaList(): try: try: metacache_lock.acquire() # Read in the metas orpg.tools.config_files.validate_config_file("metaservers.cache","metaservers.cache") ini = open(orpg.dirpath.dir_struct["user"]+"metaservers.cache","r") metas = ini.readlines() ini.close() return metas finally: metacache_lock.release() except Exception, e: if META_DEBUG: traceback.print_exc() print "Meta Server Lib: getRawMetaList(): " + str(e) return [] def getMetaServers(versions = None, pick_random=0): # get meta server URLs as a list # versions is a list of acceptable version numbers. # A false truth value will use getMetaServerBaseURL() # set a default if we have weird reading problems # default_url = "http://www.openrpg.com/openrpg_servers.php" meta_names = [] if(versions): # If versions are supplied, then look in metaservers.conf try: # read in the metas from file # format of file is one meta entry per line # each entry will be the meta url, followed by one or more version numbers that it # handle. Generally, this will be either a 1 for the original Meta format, or # 2 for the new one. # Read in the metas metas = getRawMetaList() #print str(metas) # go through each one to check if it should be returned, based on the # version numbers allowed. for meta in metas: # split the line on whitespace # obviously, your meta servers urls shouldn't contain whitespace. duh. words = meta.split() success = 0 # init success flag for version check for version in versions: # run through each allowed version from caller if version in words[1:]: # if the allowed version token was found success += 1 # then increment the success indicator if success: # if the meta entry is acceptable to the caller meta_names.append(words[0]) # add the entry if META_DEBUG: print "adding metaserver " + meta # at this point, we should have at least one name from the cache. If not ... if not meta_names: default_meta = getMetaServerBaseURL() # grab the meta from ini.xml meta_names.append(default_meta) # add it to the return list # print "Warning!!\nNo valid metaservers cached." # print "Using meta from MetaServerBaseURL: " + default_meta + "\n" # if we have more than one and want a random one elif pick_random: if META_DEBUG: print "choosing random meta from: " + str(meta_names) i = int(random.uniform(0,len(meta_names))) #meta = meta_names[i] meta_names = [meta_names[i]] if META_DEBUG: print "using: " + str(meta_names) else: if META_DEBUG: print "using all metas: " + str(meta_names) return meta_names except Exception,e: print e #print "using default meta server URI: " + default_url metas = [] #metas.append(default_url) return metas # return an empty list else: # otherwise, use MetaServerBaseURL() url = getMetaServerBaseURL() meta_names.append(url) return meta_names def getMetaServerBaseURL(): # get meta server URL url = "http://www.openrpg.com/openrpg_servers.php" try: orpg.tools.config_files.validate_config_file("ini.xml","default_ini.xml") ini = open(orpg.dirpath.dir_struct["user"]+"ini.xml","r") txt = ini.read() tree = parseXml(txt)._get_documentElement() ini.close() node_list = tree.getElementsByTagName("MetaServerBaseURL") if node_list: url = node_list[0].getAttribute("value") # allow tree to be collected try: tree.unlink() except: pass except Exception,e: print e # print "using meta server URI: " + url return url ####################################################################################### # Beginning of Class registerThread # # A Class to Manage Registration with the Meta2 # Create an instance and call it's start() method # if you want to be (and stay) registered. This class # will take care of registering and re-registering as # often as necessary to stay in the Meta list. # # You may call register() yourself if you wish to change your # server's name. It will immediately update the Meta. There # is no need to unregister first. # # Call unregister() when you no longer want to be registered. # This will result in the registerThread dying after # attempting to immediately remove itself from the Meta. # # If you need to become registered again after that, you # must create a new instance of class registerThread. Don't # just try to call register() on the old, dead thread class. class registerThread(Thread): # Originally, I wrote this as a sub-class of wxThread, but # A) I couldn't get it to import right # B) I realized that I want this to be used in a server, # which I don't want needing wxWindows to run! # # Because of this fact, there are some methods from wxThread # that I implemented to minimize changes to the code I had # just written, i.e. TestDeleteStatus() and Delete() def __init__(self,name=None,realHostName=None,num_users = "Hmmm",MetaPath=None,port=6774,register_callback=None): Thread.__init__(self,name="registerThread") self.rlock = RLock() # Re-entrant lock used to make this class thread safe self.die_event = Event() # The main loop in run() will wait with timeout on this if name: self.name = name # Name that the server want's displayed on the Meta else: self.name = "Unnamed server" # But use this if for some crazy reason no name is # passed to the constructor self.num_users = num_users # the number of users currently on this server self.realHostName = realHostName # Name to advertise for connection self.id = "0" # id returned from Meta. Defaults to "0", which # indicates a new registration. self.cookie = "0" # cookie returned from Meta. Defaults to "0",which # indicates a new registration. self.interval = 0 # interval returned from Meta. Is how often to # re-register, in minutes. self.destroy = 0 # Used to flag that this thread should die self.port = str(port) self.register_callback = register_callback # set a method to call to report result of register # This thread will communicate with one and only one # Meta. If the Meta in ini.xml is changed after # instantiation, then this instance must be # unregistered and a new instance instantiated. # # Also, if MetaPath is specified, then use that. Makes # it easier to have multiple registerThreads going to keep the server registered # on multiple (compatible) Metas. if MetaPath == None: self.path = getMetaServerBaseURL() # Do this if no Meta specified else: self.path = MetaPath def TestDeleteStatus(self): try: self.rlock.acquire() return self.die_event.isSet() finally: self.rlock.release() def Delete(self): try: self.rlock.acquire() self.die_event.set() finally: self.rlock.release() def run(self): # This method gets called by Thread implementation # when self.start() is called to begin the thread's # execution # # We will basically enter a loop that continually # re-registers this server and sleeps Interval # minutes until the thread is ordered to die in place while(not self.TestDeleteStatus()): # Loop while until told to die # Otherwise, call thread safe register(). self.register(self.name, self.realHostName, self.num_users) # register() will end up setting the state variables # for us, including self.interval. try: self.rlock.acquire() # Serialize access to this state information if self.interval >= 1: # If the number of minutes is one or greater self.interval -= .5 # wake up with 30 seconds left to re-register else: self.interval = .1 # Otherwise, we probably experienced some kind # of error from the Meta in register(). Sleep # for 6 seconds and start from scratch. finally: # no matter what, release the lock self.rlock.release() # Wait interval minutes for a command to die die_signal = self.die_event.wait(self.interval*60) # If we get past the while loop, it's because we've been asked to die, # so just let run() end. Once this occurs, the thread is dead and # calls to Thread.isAlive() return false. def unregister(self): # This method can (I hope) be called from both within the thread # and from other threads. It will attempt to unregister this # server from the Meta database # When this is either accomplished or has been tried hard enough # (after which it just makes sense to let the Meta remove the # entry itself when we don't re-register using this id), # this method will either cause the thread to immediately die # (if called from this thread's context) or set the Destroy flag # (if called from the main thread), a positive test for which will cause # the code in Entry() to exit() when the thread wakes up and # checks TestDeleteStatus(). # lock the critical section. The unlock will # automatically occur at the end of the function in the finally clause try: self.rlock.acquire() if not self.isAlive(): # check to see if this thread is dead return 1 # If so, return an error result # Do the actual unregistering here data = urllib.urlencode( {"server_data[id]":self.id, "server_data[cookie]":self.cookie, "server_data[version]":PROTOCOL_VERSION, "act":"unregister"} ) try: xml_dom = get_server_dom( data=data,path=self.path ) # this POSTS the request and returns the result if xml_dom.hasAttribute("errmsg"): if META_DEBUG: print "Error message returned from Meta: " + xml_dom.getAttribute("errmsg") except: if META_DEBUG: print "Problem talking to Meta. Will go ahead and die, letting Meta remove us." # If there's an error, echo it to the console # No special handling is required. If the de-registration worked we're done. If # not, then it's because we've already been removed or have a bad cookie. Either # way, we can't do anything else, so die. self.Delete() # This will cause the registerThread to die in register() # prep xml_dom for garbage collection try: xml_dom.unlink() except: pass return 0 finally: self.rlock.release() def register(self,name = None, realHostName=None, num_users = None): # Designed to handle the registration, both new and # repeated. # # It is intended to be called once every interval # (or interval - delta) minutes. # lock the critical section. The unlock will # automatically occur at the end of the function in the finally clause try: self.rlock.acquire() if not self.isAlive(): # check to see if this thread is dead return 1 # If so, return an error result # Set the server's attibutes, if specified. if name: self.name = name if num_users != None: self.num_users = num_users if realHostName: self.realHostName = realHostName # build POST data if self.realHostName: data = urllib.urlencode( {"server_data[id]":self.id, "server_data[cookie]":self.cookie, "server_data[name]":self.name, "server_data[port]":self.port, "server_data[version]":PROTOCOL_VERSION, "server_data[num_users]":self.num_users, "act":"register", "server_data[address]": self.realHostName } ) else: # print "Letting meta server decide the hostname to list..." data = urllib.urlencode( {"server_data[id]":self.id, "server_data[cookie]":self.cookie, "server_data[name]":self.name, "server_data[port]":self.port, "server_data[version]":PROTOCOL_VERSION, "server_data[num_users]":self.num_users, "act":"register"} ) try: xml_dom = get_server_dom( data=data,path=self.path ) # this POSTS the request and returns the result except: if META_DEBUG: print "Problem talking to server. Setting interval for retry ..." self.interval = 0 if self.register_callback: # if a callback is registered, call it self.register_callback(None,self.path) # If we are in the registerThread thread, then setting interval to 0 # will end up causing a retry in about 6 seconds (see self.run()) # If we are in the main thread, then setting interval to 0 will do one # of two things: # 1) Do the same as if we were in the registerThread # 2) Cause the next, normally scheduled register() call to use the values # provided in this call. # # Which case occurs depends on where the registerThread thread is when # the main thread calls register(). return 0 # indicates that it was okay to call, not that no errors occurred # If there is a DOM returned .... if xml_dom: if self.register_callback: # if a callback is registered, call it self.register_callback(xml_dom,self.path) # If there's an error, echo it to the console if xml_dom.hasAttribute("errmsg"): if META_DEBUG: print "Error message returned from Meta: " + xml_dom.getAttribute("errmsg") # No special handling is required. If the registration worked, id, cookie, and interval # can be stored and used for the next time. # If an error occurred, then the Meta will delete us and we need to re-register as # a new server. The way to indicate this is with a "0" id and "0" cookie sent to # the server during the next registration. Since that's what the server returns to # us on an error anyway, we just store them and the next registration will # automatically be set up as a new one. # # Unless the server calls register() itself in the meantime. Of course, that's okay # too, because a success on THAT register() call will set up the next one to use # the issued id and cookie. # # The interval is stored unconditionally for similar reasons. If there's an error, # the interval will be less than 1, and the main thread's while loop will reset it # to 6 seconds for the next retry. # Is it wrong to have a method where there's more comments than code? :) try: self.interval = int(xml_dom.getAttribute("interval")) self.id = xml_dom.getAttribute("id") self.cookie = xml_dom.getAttribute("cookie") except: if META_DEBUG: print if META_DEBUG: print "OOPS! Is the Meta okay? It should be returning an id, cookie, and interval." if META_DEBUG: print "Check to see what it really returned.\n" # Let xml_dom get garbage collected try: xml_dom.unlink() except: pass else: # else if no DOM is returned from get_server_dom() if META_DEBUG: print "Error - no DOM constructed from Meta message!" return 0 # Let caller know it was okay to call us finally: self.rlock.release() # End of class registerThread ################################################################################ --- NEW FILE: mplay_client.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: mplay_client.py # Author: Chris Davis # Maintainer: # Version: # $Id: mplay_client.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $ # # Description: This file contains the code for the client stubs of the multiplayer # features in the orpg project. # __version__ = "$Id: mplay_client.py,v 1.1 2006/01/26 17:33:16 digitalxero Exp $" import orpg.minidom import socket import Queue import thread import traceback from threading import Event, Lock from xml.sax.saxutils import escape from struct import pack, unpack, calcsize from string import * from orpg.orpg_version import * import errno import os import time # This should be configurable OPENRPG_PORT = 6774 # We should be sending a length for each packet MPLAY_LENSIZE = calcsize( 'i' ) MPLAY_DISCONNECTED = 0 MPLAY_CONNECTED = 1 MPLAY_DISCONNECTING = 3 MPLAY_GROUP_CHANGE = 4 MPLAY_GROUP_CHANGE_F = 5 PLAYER_NEW = 1 PLAYER_DEL = 2 PLAYER_GROUP = 3 # The next two messages are used to inform others that a player is typing PLAYER_TYPING = 4 PLAYER_NOT_TYPING = 5 PLAYER_UPDATE = 6 GROUP_JOIN = 1 GROUP_NEW = 2 GROUP_DEL = 3 GROUP_UPDATE = 4 STATUS_SET_URL = 1 def parseXml(data): "parse and return doc" #print data doc = orpg.minidom.parseString(data) doc.normalize() return doc def myescape(data): return escape(data,{"\"":""}) class mplay_event: def __init__(self,id,data=None): self.id = id self.data = data def get_id(self): return self.id def get_data(self): return self.data BOOT_MSG = "YoU ArE ThE WeAkEsT LiNk. GoOdByE." class client_base: # Player role definitions def __init__(self): self.outbox = Queue.Queue(0) self.inbox = Queue.Queue(0) self.startedEvent = Event() self.exitEvent = Event() self.sendThreadExitEvent = Event() self.recvThreadExitEvent = Event() self.id = "0" self.group_id = "0" self.name = "" self.role = "GM" self.ROLE_GM = "GM" self.ROLE_PLAYER = "PLAYER" self.ROLE_LURKER = "LURKER" self.ip = socket.gethostbyname(socket.gethostname()) self.remote_ip = None self.version = VERSION self.protocol_version = PROTOCOL_VERSION self.client_string = CLIENT_STRING self.status = MPLAY_DISCONNECTED self.log_console = None self.sock = None self.text_status = "Idle" self.statLock = Lock() self.useroles = 0 self.ROLE_GM="GM" self.ROLE_PLAYER="PLAYER" self.ROLE_LURKER="LURKER" self.lastmessagetime = time.time() self.connecttime = time.time() def sendThread( self, arg ): "Sending thread. This thread reads from the data queue and writes to the socket." # Wait to be told it's okay to start running self.startedEvent.wait() # Loop as long as we have a connection while( self.get_status() == MPLAY_CONNECTED ): try: readMsg = self.outbox.get( block=1 ) except Exception, text: self.log_msg( ("outbox.get() got an exception: ", text) ) # If we are here, it's because we have data to send, no doubt! if self.status == MPLAY_CONNECTED: try: # Send the entire message, properly formated/encoded sent = self.sendMsg( self.sock, readMsg ) except Exception, e: self.log_msg( e ) else: # If we are not connected, purge the data queue self.log_msg( "Data queued without a connection, purging data from queue..." ) self.sendThreadExitEvent.set() self.log_msg( "sendThread has terminated..." ) def recvThread( self, arg ): "Receiving thread. This thread reads from the socket and writes to the data queue." # Wait to be told it's okay to start running self.startedEvent.wait() while( self.get_status() == MPLAY_CONNECTED ): readMsg = self.recvMsg( self.sock ) # Make sure we didn't get disconnected bytes = len( readMsg ) if bytes == 0: break # Check the length of the message bytes = len( readMsg ) # Make sure we are still connected if bytes == 0: break else: # Pass along the message so it can be processed self.inbox.put( readMsg ) self.update_idle_time() #update the last message time if bytes == 0: self.log_msg( "Remote has disconnected!" ) self.set_status( MPLAY_DISCONNECTING ) self.outbox.put( "" ) # Make sure the other thread is woken up! self.sendThreadExitEvent.set() self.log_msg( "recvThread has terminated..." ) def sendMsg( self, sock, msg ): """Very simple function that will properly encode and send a message to te remote on the specified socket.""" # Calculate our message length length = len( msg ) # Encode the message length into network byte order lp = pack( 'i', socket.htonl( length ) ) try: # Send the encoded length sentl = sock.send( lp ) # Now, send the message the the length was describing sentm = sock.send( msg ) if self.isServer(): self.log_msg(("data_sent", sentl+sentm)) except socket.error, e: self.log_msg( e ) except Exception, e: self.log_msg( e ) return sentm def recvData( self, sock, readSize ): """Simple socket receive method. This method will only return when the exact byte count has been read from the connection, if remote terminates our connection or we get some other socket exception.""" data = "" offset = 0 try: while offset != readSize: frag = sock.recv( readSize - offset ) # See if we've been disconnected rs = len( frag ) if rs <= 0: # Loudly raise an exception because we've been disconnected! raise IOError, "Remote closed the connection!" else: # Continue to build complete message offset += rs data += frag except socket.error, e: self.log_msg( e ) data = "" return data def recvMsg( self, sock ): """This method now expects to receive a message having a 4-byte prefix length. It will ONLY read completed messages. In the event that the remote's connection is terminated, it will throw an exception which should allow for the caller to more gracefully handle this exception event. Because we use strictly reading ONLY based on the length that is told to use, we no longer have to worry about partially adjusting for fragmented buffers starting somewhere within a buffer that we've read. Rather, it will get ONLY a whole message and nothing more. Everything else will remain buffered with the OS until we attempt to read the next complete message.""" msgData = "" try: lenData = self.recvData( sock, MPLAY_LENSIZE ) # Now, convert to a usable form (length,) = unpack( 'i', lenData ) length = socket.ntohl( length ) # Read exactly the remaining amount of data msgData = self.recvData( sock, length ) if self.isServer(): self.log_msg(("data_recv", length+4)) # Make the peer IP address available for reference later if self.remote_ip is None: self.remote_ip = self.sock.getpeername() except IOError, e: self.log_msg( e ) except Exception, e: self.log_msg( e ) return msgData def initialize_threads(self): "Starts up our threads (2) and waits for them to make sure they are running!" self.status = MPLAY_CONNECTED self.sock.setblocking(1) # Confirm that our threads have started thread.start_new_thread( self.sendThread,(0,) ) thread.start_new_thread( self.recvThread,(0,) ) self.startedEvent.set() def disconnect(self): self.set_status(MPLAY_DISCONNECTING) self.log_msg("client stub " + self.ip +" disconnecting...") self.log_msg("closing sockets...") try: self.sock.shutdown( 2 ) except Exception, e: print "Caught exception: " + str(e) print print "Continuing" self.set_status(MPLAY_DISCONNECTED) def reset(self,sock): self.disconnect() self.sock = sock self.initialize_threads() def update_role(self,role): self.useroles = 1 self.role = role def use_roles(self): if self.useroles: return 1 else: return 0 def update_self_from_player(self, player): try: (self.name, self.ip, self.id, self.text_status, self.version, self.protocol_version, self.client_string,role) = player except Exception, e: print e # The IP field should really be deprecated as too many systems are NAT'd and/or behind firewalls for a # client provided IP address to have much value. As such, we now label it as deprecated. def toxml(self,action): xml_data = "<player name=\""+myescape(self.name) + "\"" xml_data += " action=\""+action+"\" id=\""+self.id + "\"" xml_data += " group_id=\""+self.group_id+"\" ip=\""+self.ip + "\"" xml_data += " status=\""+self.text_status + "\"" xml_data += " version=\""+self.version + "\"" xml_data += " protocol_version=\""+self.protocol_version + "\"" xml_data += " client_string=\""+self.client_string + "\"" xml_data += "/>" return xml_data def log_msg(self,msg): if self.log_console: self.log_console(msg) # else: # print "message", msg def get_status(self): self.statLock.acquire() status = self.status self.statLock.release() return status def my_role(self): if self.role == "GM": return self.ROLE_GM elif self.role == "Player": return self.ROLE_PLAYER elif self.role == "Lurker": return self.ROLE_LURKER return -1 def set_status(self,status): self.statLock.acquire() self.status = status self.statLock.release() def isServer( self ): # Return 1 if we are running as a server, else, return 0. # This method must be overloaded by whomever derives from us pass def __str__(self): return "%s(%s)\nIP:%s\ngroup_id:%s\n" % (self.name, self.id, self.ip, self.group_id) # idle time functions added by snowdog 3/31/04 def update_idle_time(self): self.lastmessagetime = time.time() def idle_time(self): curtime = time.time() idletime = curtime - self.lastmessagetime return idletime def idle_status(self): idletime = self.idle_time() idlemins = idletime / 60 status = "Unknown" if idlemins < 3: status = "Active" elif idlemins < 10: status = "Idle ("+str(int(idlemins))+" mins)" else: status = "Inactive ("+str(int(idlemins))+" mins)" return status def connected_time(self): curtime = time.time() timeoffset = curtime - self.connecttime return timeoffset def connected_time_string(self): "returns the time client has been connected as a formated time string" ct = self.connected_time() d = int(ct/86400) h = int( (ct-(86400*d))/3600 ) m = int( (ct-(86400*d)-(3600*h))/60) s = int( (ct-(86400*d)-(3600*h)-(60*m)) ) cts = zfill(d,2)+":"+zfill(h,2)+":"+zfill(m,2)+":"+zfill(s,2) return cts #======================================================================== # # # MPLAY CLIENT # # #======================================================================== class mplay_client(client_base): "mplay client" def __init__(self,name,callbacks): client_base.__init__(self) self.set_name(name) self.on_receive = callbacks['on_receive'] self.on_mplay_event = callbacks['on_mplay_event'] self.on_group_event = callbacks['on_group_event'] self.on_player_event = callbacks['on_player_event'] self.on_status_event = callbacks['on_status_event'] self.on_password_signal = callbacks['on_password_signal'] # I know this is a bad thing to do but it has to be # be done to use the unified password manager. # Should really find a better solution. -- SD 8/03 self.orpgFrame_callback = callbacks['orpgFrame'] self.settings = self.orpgFrame_callback.settings #self.version = VERSION #self.protocol_version = PROTOCOL_VERSION #self.client_string = CLIENT_STRING self.ignore_id = [] self.ignore_name = [] self.players = {} self.groups = {} self.unique_cookie = 0 self.msg_handlers = {} self.core_msg_handlers = [] self.load_core_msg_handlers() # implement from our base class def isServer( self ): return 0 def set_name(self,name): self.name = name self.update() def set_text_status(self,status): if self.text_status != status: self.text_status = status self.update() def set_status_url(self,url="None"): self.on_status_event(mplay_event(STATUS_SET_URL,url)) def update(self): if self.status == MPLAY_CONNECTED: self.outbox.put(self.toxml('update')) self.inbox.put(self.toxml('update')) def get_group_info(self,id=0): self.statLock.acquire() id = self.groups[id] self.statLock.release() return id def get_my_group(self): self.statLock.acquire() id = self.groups[self.group_id] self.statLock.release() return id def get_groups(self): self.statLock.acquire() groups = self.groups.values() self.statLock.release() return groups def get_players(self): self.statLock.acquire() players = self.players.values() self.statLock.release() return players def get_player_info(self,id): self.statLock.acquire() player = self.players[id] self.statLock.release() return player def get_player_by_player_id(self,player): players = self.get_players() if self.players.has_key(player): for m in players: if player == m[2]: return m return -1 def get_id(self): return self.id def get_my_info(self): return (self.name, self.ip, self.id, self.text_status, self.version, self.protocol_version, self.client_string, self.role) def is_valid_id(self,id): self.statLock.acquire() value = self.players.has_key( id ) self.statLock.release() return value def clear_players(self,save_self=0): self.statLock.acquire() keys = self.players.keys() for k in keys: del self.players[k] self.statLock.release() def clear_groups(self): self.statLock.acquire() keys = self.groups.keys() for k in keys: del self.groups[k] self.statLock.release() def find_role(self,id): return self.players[id].role def get_ignore_list(self): try: return (self.ignore_id, self.ignore_name) except: return (None, None) def toggle_ignore(self, id): for m in self.ignore_id: if `self.ignore_id[self.ignore_id.index(m)]` == `id`: name = self.ignore_name[self.ignore_id.index(m)] self.ignore_id.remove(m) self.ignore_name.remove(name) return (0,id,name) self.ignore_name.append(self.players[id][0]) self.ignore_id.append(self.players[id][2]) return (1,self.players[id][2],self.players[id][0]) def boot_player(self,id,boot_pwd = ""): #self.send(BOOT_MSG,id) msg = '<boot boot_pwd="' + boot_pwd + '"/>' self.send(msg,id) #--------------------------------------------------------- # [START] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- def set_room_pass(self,npwd,pwd=""): self.outbox.put("<alter key=\"pwd\" val=\"" +npwd+ "\" bpw=\"" + pwd + "\" plr=\"" + self.id +"\" gid=\"" + self.group_id + "\" />") self.update() def set_room_name(self,name,pwd=""): loc = name.find("&") oldloc=0 while loc > -1: loc = name.find("&",oldloc) if loc > -1: b = name[:loc] e = name[loc+1:] name = b + "&" + e oldloc = loc+1 loc = name.find('"') oldloc=0 while loc > -1: loc = name.find('"',oldloc) if loc > -1: b = name[:loc] e = name[loc+1:] name = b + """ + e oldloc = loc+1 loc = name.find("'") oldloc=0 while loc > -1: loc = name.find("'",oldloc) if loc > -1: b = name[:loc] e = name[loc+1:] name = b + "'" + e oldloc = loc+1 self.outbox.put("<alter key=\"name\" val=\"" + name + "\" bpw=\"" + pwd + "\" plr=\"" + self.id +"\" gid=\"" + self.group_id + "\" />") self.update() #--------------------------------------------------------- # [END] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- def display_roles(self): self.outbox.put("<role action=\"display\" player=\"" + self.id +"\" group_id=\""+self.group_id + "\" />") def get_role(self): self.outbox.put("<role action=\"get\" player=\"" + self.id +"\" group_id=\""+self.group_id + "\" />") def set_role(self,player,role,pwd=""): self.outbox.put("<role action=\"set\" player=\"" + player + "\" role=\"" +role+ "\" boot_pwd=\"" + pwd + "\" group_id=\"" + self.group_id + "\" />") self.update() def send(self,msg,player="all"): if self.status == MPLAY_CONNECTED and player != self.id: self.outbox.put("<msg to='"+player+"' from='"+self.id+"' group_id='"+self.group_id+"' />"+msg) self.check_my_status() def send_create_group(self,name,pwd,boot_pwd,minversion): self.outbox.put("<create_group from=\""+self.id+"\" pwd=\""+pwd+"\" name=\""+ name+"\" boot_pwd=\""+boot_pwd+"\" min_version=\"" + minversion +"\" />") def send_join_group(self,group_id,pwd): if (group_id != 0): self.update_role("LURKER") self.outbox.put("<join_group from=\""+self.id+"\" pwd=\""+pwd+"\" group_id=\""+str(group_id)+"\" />") def poll(self): try: msg = self.inbox.get_nowait() except: if self.get_status() <> MPLAY_CONNECTED: self.check_my_status() else: try: self.pretranslate(msg) except Exception, e: print "The following message: " + str(msg) print "created the following exception: " traceback.print_exc() print self.players self.poll() def add_msg_handler(self, tag, function, core=False): if not self.msg_handlers.has_key(tag): self.msg_handlers[tag] = function if core: self.core_msg_handlers.append(tag) else: print 'XML Messages ' + tag + ' already has a handler' def remove_msg_handler(self, tag): if self.msg_handlers.has_key(tag) and not tag in self.core_msg_handlers: del self.msg_handlers[tag] else: print 'XML Messages ' + tag + ' already deleted' def load_core_msg_handlers(self): self.add_msg_handler('msg', self.on_msg, True) self.add_msg_handler('ping', self.on_ping, True) self.add_msg_handler('group', self.on_group, True) self.add_msg_handler('role', self.on_role, True) self.add_msg_handler('player', self.on_player, True) self.add_msg_handler('password', self.on_password, True) def pretranslate(self,data): # Pre-qualify our data. If we don't have atleast 5-bytes, then there is # no way we even have a valid message! if len(data) < 5: return end = data.find(">") head = data[:end+1] msg = data[end+1:] xml_dom = parseXml(head) xml_dom = xml_dom._get_documentElement() tag_name = xml_dom._get_tagName() id = xml_dom.getAttribute("from") if id == '': id = xml_dom.getAttribute("id") if self.msg_handlers.has_key(tag_name): self.msg_handlers[tag_name](id, data, xml_dom) else: #Unknown messages recived ignoring #using pass insted or printing an error message #because plugins should now be able to send and proccess messages #if someone is using a plugin to send messages and this user does not #have the plugin they would be getting errors pass if xml_dom: xml_dom.unlink() def on_msg(self, id, data, xml_dom): end = data.find(">") head = data[:end+1] msg = data[end+1:] if id == "0": self.on_receive(msg,None) # None get's interpreted in on_receive as the sys admin. # Doing it this way makes it harder to impersonate the admin else: if self.is_valid_id(id): self.on_receive(msg,self.players[id]) if xml_dom: xml_dom.unlink() def on_ping(self, id, msg, xml_dom): #a REAL ping time implementation by Snowdog 8/03 # recieves special server <ping time="###" /> command # where ### is a returning time from the clients ping command #get current time, pull old time from object and compare them # the difference is the latency between server and client * 2 ct = time.clock() ot = xml_dom.getAttribute("time") latency = float(float(ct) - float(ot)) latency = int( latency * 10000.0 ) latency = float( latency) / 10.0 ping_msg = "Ping Results: "+str(latency)+" ms (parsed message, round trip)" self.on_receive(ping_msg,None) if xml_dom: xml_dom.unlink() def on_group(self, id, msg, xml_dom): name = xml_dom.getAttribute("name") players = xml_dom.getAttribute("players") act = xml_dom.getAttribute("action") pwd = xml_dom.getAttribute("pwd") group_data = (id,name,pwd,players) if act=='new': self.groups[id] = group_data self.on_group_event(mplay_event(GROUP_NEW,group_data)) elif act=='del': self.on_group_event(mplay_event(GROUP_DEL,group_data)) del self.groups[id] elif act=='update': self.groups[id] = group_data self.on_group_event(mplay_event(GROUP_UPDATE,group_data)) if xml_dom: xml_dom.unlink() def on_role(self, id, msg, xml_dom): act = xml_dom.getAttribute("action") role = xml_dom.getAttribute("role") if (act == "set") or (act == "update"): try: (a,b,c,d,e,f,g,h) = self.players[id] if id == self.id: self.players[id] = (a,b,c,d,e,f,g,role) self.update_role(role) else: self.players[id] = (a,b,c,d,e,f,g,role) self.on_player_event(mplay_event(PLAYER_UPDATE,self.players[id])) except: pass if xml_dom: xml_dom.unlink() def on_player(self, id, msg, xml_dom): act = xml_dom.getAttribute("action") ip = xml_dom.getAttribute("ip") name = xml_dom.getAttribute("name") status = xml_dom.getAttribute("status") version = xml_dom.getAttribute("version") protocol_version = xml_dom.getAttribute("protocol_version") client_string = xml_dom.getAttribute("client_string") try: player = (name,ip,id,status,version,protocol_version,client_string,self.players[id][7]) except Exception, e: player = (name,ip,id,status,version,protocol_version,client_string,"Player") if act == "new": self.players[id] = player self.on_player_event(mplay_event(PLAYER_NEW,self.players[id])) elif act == "group": self.group_id = xml_dom.getAttribute("group_id") self.clear_players() self.on_mplay_event(mplay_event(MPLAY_GROUP_CHANGE,self.groups[self.group_id])) self.players[self.id] = self.get_my_info() #(self.name,self.ip,self.id,self.text_status) self.on_player_event(mplay_event(PLAYER_NEW,self.players[self.id])) elif act == "failed": self.on_mplay_event(mplay_event(MPLAY_GROUP_CHANGE_F)) elif act == "del": self.on_player_event(mplay_event(PLAYER_DEL,self.players[id])) del self.players[id] if id == self.id: self.do_disconnect() # the next two cases handle the events that are used to let you know when others are typing elif act == "update": if id == self.id: self.players[id] = player self.update_self_from_player(player) else: self.players[id] = player dont_send = 0 for m in self.ignore_id: if m == id: dont_send=1 if dont_send != 1: self.on_player_event(mplay_event(PLAYER_UPDATE,self.players[id])) if xml_dom: xml_dom.unlink() def on_password(self, id, msg, xml_dom): signal = type = id = data = None id = xml_dom.getAttribute("id") type = xml_dom.getAttribute("type") signal = xml_dom.getAttribute("signal") data = xml_dom.getAttribute("data") self.on_password_signal( signal,type,id,data ) if xml_dom: xml_dom.unlink() def check_my_status(self): status = self.get_status() if status == MPLAY_DISCONNECTING: self.do_disconnect() def connect(self,addressport): """Establish a connection to a server while still using sendThread & recvThread for its communication.""" if self.is_connected(): self.log_msg( "Client is already connected to a server?!? Need to disconnect first." ) return 0 xml_dom = None self.inbox = Queue.Queue(0) self.outbox = Queue.Queue(0) addressport_ar = addressport.split(":") if len(addressport_ar) == 1: address = addressport_ar[0] port = OPENRPG_PORT else: address = addressport_ar[0] port = int(addressport_ar[1]) self.host_server = addressport self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: self.sock.connect((address,port)) # send client into with id=0 self.sendMsg( self.sock, self.toxml("new") ) data = self.recvMsg( self.sock ) # get new id and group_id xml_dom = parseXml(data) xml_dom = xml_dom._get_documentElement() self.id = xml_dom.getAttribute("id") self.group_id = xml_dom.getAttribute("group_id") #send confirmation self.sendMsg( self.sock, self.toxml("new") ) except Exception, e: self.log_msg(e) if xml_dom: xml_dom.unlink() return 0 # Start things rollings along self.initialize_threads() self.on_mplay_event(mplay_event(MPLAY_CONNECTED)) self.players[self.id] = (self.name,self.ip,self.id,self.text_status,self.version,self.protocol_version,self.client_string,self.role) self.on_player_event(mplay_event(PLAYER_NEW,self.players[self.id])) if xml_dom: xml_dom.unlink() return 1 def start_disconnect(self): self.on_mplay_event(mplay_event(MPLAY_DISCONNECTING)) self.outbox.put( self.toxml("del") ) ## Client Side Disconect Forced -- Snowdog 10-09-2003 #pause to allow GUI events time to sync. time.sleep(1) self.do_disconnect() def do_disconnect(self): client_base.disconnect(self) self.clear_players() self.clear_groups() self.useroles = 0 ... [truncated message content] |
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/plugins/cherrypy/lib Added Files: __init__.py aspect.py cptools.py csauthenticate.py defaultformmask.py form.py htmltools.py httptools.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: form.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Simple form handling module. """ from cherrypy import cpg import defaultformmask class FormField: def __init__(self, label, name, typ, mask=None, mandatory=0, size=15, optionList=[], defaultValue='', defaultMessage='', validate=None): self.isField=1 self.label=label self.name=name self.typ=typ if not mask: self.mask=defaultformmask.defaultMask else: self.mask=mask self.mandatory=mandatory self.size=size self.optionList=optionList self.defaultValue=defaultValue self.defaultMessage=defaultMessage self.validate=validate self.errorMessage="" def render(self, leaveValues): if leaveValues: if self.typ!='submit': if cpg.request.paramMap.has_key(self.name): self.currentValue=cpg.request.paramMap[self.name] else: self.currentValue="" else: self.currentValue=self.defaultValue else: self.currentValue=self.defaultValue self.errorMessage=self.defaultMessage return self.mask(self) class FormSeparator: def __init__(self, label, mask): self.isField=0 self.label=label self.mask=mask def render(self, dummy): return self.mask(self.label) class Form: method="post" enctype="" def formView(self, leaveValues=0): if self.enctype: enctypeTag='enctype="%s"'%self.enctype else: enctypeTag="" res='<form method="%s" %s action="postForm">'%(self.method, enctypeTag) for field in self.fieldList: res+=field.render(leaveValues) return res+"</form>" def validateFields(self): # Should be subclassed # Update field's errorMessage value to set an error pass def validateForm(self): # Reset errorMesage for each field for field in self.fieldList: if field.isField: field.errorMessage="" # Validate mandatory fields for field in self.fieldList: if field.isField and field.mandatory and (not cpg.request.paramMap.has_key(field.name) or not cpg.request.paramMap[field.name]): field.errorMessage="Missing" # Validate fields one by one for field in self.fieldList: if field.isField and field.validate and not field.errorMessage: if cpg.request.paramMap.has_key(field.name): value=cpg.request.paramMap[field.name] else: value="" field.errorMessage=field.validate(value) # Validate all fields together (ie: check that passwords match) self.validateFields() for field in self.fieldList: if field.isField and field.errorMessage: return 0 return 1 def setFieldErrorMessage(self, fieldName, errorMessage): for field in self.fieldList: if field.isField and field.name==fieldName: field.errorMessage=errorMessage def getFieldOptionList(self, fieldName): for field in self.fieldList: if field.isField and field.name==fieldName: return field.optionList def getFieldDefaultValue(self, fieldName): for field in self.fieldList: if field.isField and field.name==fieldName: return field.defaultValue def setFieldDefaultValue(self, fieldName, defaultValue): for field in self.fieldList: if field.isField and field.name==fieldName: field.defaultValue=defaultValue def getFieldNameList(self, exceptList=[]): fieldNameList=[] for field in self.fieldList: if field.isField and field.name and field.name not in exceptList: fieldNameList.append(field.name) return fieldNameList --- NEW FILE: httptools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Just a few convenient functions """ from cherrypy import cpg import urlparse def canonicalizeUrl(url): """ Canonicalize a URL. The URL might be relative, absolute or canonical """ return urlparse.urljoin(cpg.request.base, url) def redirect(url): """ Sends a redirect to the browser (after canonicalizing the URL) """ cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = canonicalizeUrl(url) return "" --- NEW FILE: csauthenticate.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import time, whrandom from cherrypy import cpg from aspect import Aspect, STOP, CONTINUE class CSAuthenticate(Aspect): timeoutMessage = "Session timed out" wrongLoginPasswordMessage = "Wrong login/password" noCookieMessage = "No cookie" logoutMessage = "You have been logged out" sessionIdCookieName = "CherrySessionId" timeout = 60 # in minutes def _before(self, methodName, method): # If the method is not exposed, don't do anything if not getattr(method, 'exposed', None): return CONTINUE, None cpg.request.login = '' # If the method is one of these 4, do not try to find out who is logged in if methodName in ["loginScreen", "logoutScreen", "doLogin", "doLogout"]: return CONTINUE, None # Check if a user is logged in: # - If they are, set request.login with the right value # - If not, return the login screen if not cpg.request.simpleCookie.has_key(self.sessionIdCookieName): return STOP, self.loginScreen(self.noCookieMessage, cpg.request.browserUrl) sessionId = cpg.request.simpleCookie[self.sessionIdCookieName].value now=time.time() # Check that session exists and hasn't timed out timeout=0 if not cpg.request.sessionMap.has_key(sessionId): return STOP, self.loginScreen(self.noCookieMessage, cpg.request.browserUrl) else: login, expire = cpg.request.sessionMap[sessionId] if expire < now: timeout=1 else: expire = now + self.timeout*60 cpg.request.sessionMap[sessionId] = login, expire if timeout: return STOP, self.loginScreen(self.timeoutMessage, cpg.request.browserUrl) cpg.request.login = login return CONTINUE, None def checkLoginAndPassword(self, login, password): if (login,password) == ('login','password'): return '' return 'Wrong login/password' def generateSessionId(self, sessionIdList): choice="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" while 1: sessionId="" for dummy in range(20): sessionId += whrandom.choice(choice) if sessionId not in sessionIdList: return sessionId def doLogin(self, login, password, fromPage): # Check that login/password match errorMsg = self.checkLoginAndPassword(login, password) if errorMsg: cpg.request.login = '' return self.loginScreen(errorMsg, fromPage, login) cpg.request.login = login # Set session newSessionId = self.generateSessionId(cpg.request.sessionMap.keys()) cpg.request.sessionMap[newSessionId] = login, time.time()+self.timeout*60 cpg.response.simpleCookie[self.sessionIdCookieName] = newSessionId cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/' cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 31536000 cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1 cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = fromPage return "" doLogin.exposed = True def doLogout(self): try: sessionId = request.simpleCookie[self.sessionIdCookieName].value del request.sessionMap[sessionId] except: pass cpg.response.simpleCookie[self.sessionIdCookieName] = "" cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/' cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 0 cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1 cpg.request.login = '' cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = 'logoutScreen' # TBCTBC: may not be the right URL return "" doLogout.exposed = True def logoutScreen(self): return self.loginScreen(self.logoutMessage, '/index') # TBC logoutScreen.exposed = True def loginScreen(self, message, fromPage, login=''): return """ <html><body> Message: %s <form method="post" action="doLogin"> Login: <input type=text name=login value="%s" size=10><br> Password: <input type=password name=password size=10><br> <input type=hidden name=fromPage value="%s"><br> <input type=submit> </form> </body></html> """ % (message, login, fromPage) loginScreen.exposed = True --- NEW FILE: aspect.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # return codes for _before and _after aspect methods STOP = 0 CONTINUE = 1 class Aspect(object): """ Base class for aspects. Derive new aspect classes from this, then override one or both of _before and _after. """ def __getattribute__(self, methodName): # find method specified by methodName try: method = object.__getattribute__(self, methodName) except: raise # if requested attribute is not a method, simply return it if not callable(method): return method # define wrapper function def _wrapper(*k, **kw): # call _before method status, value = object.__getattribute__(self, '_before')(methodName, method) if status == STOP: return value # call wrapped method and append results result = method(*k, **kw) if value: result = value + result # call _after method status, value = object.__getattribute__(self, '_after')(methodName, method) if status == STOP: return value if value: result += value # done! return result # expose wrapper function if wrapped method is exposed if getattr(method, 'exposed', None): _wrapper.exposed = True # return wrapper function. It'll get called instead of the # requested method. return _wrapper def _before(self, methodName, method): return CONTINUE, None def _after(self, methodName, method): return CONTINUE, None --- NEW FILE: __init__.py --- """ CherryPy Standard Library """ --- NEW FILE: defaultformmask.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Default mask for the form.py module """ def defaultMask(field): res="<tr><td valign=top>%s</td>"%field.label if field.typ=='text': res+='<td><input name="%s" type=text value="%s" size=%s></td>'%(field.name, field.currentValue, field.size) elif field.typ=='forced': res+='<td><input name="%s" type=hidden value="%s">%s</td>'%(field.name, field.currentValue, field.currentValue) elif field.typ=='password': res+='<td><input name="%s" type=password value="%s"></td>'%(field.name, field.currentValue) elif field.typ=='select': res+='<td><select name="%s">'%field.name for option in field.optionList: if type(option)==type(()): optionId, optionLabel=option if optionId==field.currentValue or str(optionId)==field.currentValue: res+="<option selected value=%s>%s</option>"%(optionId, optionLabel) else: res+="<option value=%s>%s</option>"%(optionId, optionLabel) else: if option==field.currentValue: res+="<option selected>%s</option>"%option else: res+="<option>%s</option>"%option res+='</select></td>' elif field.typ=='textarea': # Size is colsxrows if field.size==15: size="15x15" else: size=field.size cols, rows=size.split('x') res+='<td><textarea name="%s" rows="%s" cols="%s">%s</textarea></td>'%(field.name, rows, cols, field.currentValue) elif field.typ=='submit': res+='<td><input type=submit value="%s"></td>'%field.name elif field.typ=='hidden': if type(field.currentValue)==type([]): currentValue=field.currentValue else: currentValue=[field.currentValue] res="" for value in currentValue: res+='<input name="%s" type=hidden value="%s">'%(field.name, value) return res elif field.typ=='checkbox' or field.typ=='radio': res+='<td>' # print "##### currentValue:", field.currentValue # TBC for option in field.optionList: if type(option)==type(()): optionValue, optionLabel=option else: optionValue, optionLabel=option, option res+='<input type="%s" name="%s" value="%s"'%(field.typ, field.name, optionValue) if type(field.currentValue)==type([]): if optionValue in field.currentValue: res+=' checked' else: if optionValue==field.currentValue: res+=' checked' res+='> %s<br>'%optionLabel res+='</td>' if field.errorMessage: res+="<td><font color=red>%s</font></td>"%field.errorMessage else: res+="<td> </td>" return res+"</tr>" def hiddenMask(field): if type(field.currentValue)==type([]): currentValue=field.currentValue else: currentValue=[field.currentValue] res="" for value in currentValue: res+='<input name="%s" type=hidden value="%s">'%(field.name, value) return res def defaultHeader(label): return "<table>" def defaultFooter(label): return "</table>" def echoMask(label): return label --- NEW FILE: htmltools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Just a few convenient functions """ from cherrypy import cpg def redirect(newUrl): """ Sends a redirect to the browser """ if not newUrl.startswith('http://') and not newUrl.startswith('https://'): # If newUrl is not canonical, we must make it canonical if newUrl.startswith('/'): # newUrl was absolute: # we just add request.base in front of it newUrl = cpg.request.base + newUrl else: # newUrl was relative: # we remove the last bit from browserUrl and add newUrl to it i = cpg.request.browserUrl.rfind('/') newUrl = cpg.request.browserUrl[:i+1] + newUrl cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = newUrl return "" --- NEW FILE: cptools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Just a few convenient functions """ class ExposeItems: """ Utility class that exposes a getitem-aware object. It does not provide index() or default() methods, and it does not expose the individual item objects - just the list or dict that contains them. User-specific index() and default() methods can be implemented by inheriting from this class. Use case: from cherrypy.lib.cptools import ExposeItems ... cpg.root.foo = ExposeItems(mylist) cpg.root.bar = ExposeItems(mydict) """ exposed = True def __init__(self, items): self.items = items def __getattr__(self, key): return self.items[key] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:25
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/chat In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/chat Added Files: __init__.py chat_msg.py chat_util.py chat_version.py chatwnd.py commands.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: chat_msg.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: chat_msg.py # Author: Ted Berg # Maintainer: # Version: # $Id: chat_msg.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: Contains class definitions for manipulating <chat/> messages # # __version__ = "$Id: chat_msg.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" import orpg.orpg_xml from chat_version import CHAT_VERSION CHAT_MESSAGE = 1 WHISPER_MESSAGE = 2 EMOTE_MESSAGE = 3 INFO_MESSAGE = 4 SYSTEM_MESSAGE = 5 class chat_msg: def __init__(self,xml_text="<chat type=\"1\" version=\""+CHAT_VERSION+"\" alias=\"\" ></chat>"): self.chat_dom = None self.takexml(xml_text) def __del__(self): if self.chat_dom: self.chat_dom.unlink() def toxml(self): return orpg.orpg_xml.toxml(self.chat_dom) def takexml(self,xml_text): xml_dom = orpg.orpg_xml.parseXml(xml_text) node_list = xml_dom.getElementsByTagName("chat") if len(node_list) < 1: print "Warning: no <chat/> elements found in DOM." else: if len(node_list) > 1: print "Found more than one instance of <" + self.tagname + "/>. Taking first one" self.takedom(node_list[0]) def takedom(self,xml_dom): if self.chat_dom: self.text_node = None self.chat_dom.unlink() self.chat_dom = xml_dom self.text_node = orpg.orpg_xml.safe_get_text_node(self.chat_dom) def set_text(self,text): text = orpg.orpg_xml.strip_text(text) self.text_node._set_nodeValue(text) def set_type(self,type): self.chat_dom.setAttribute("type",str(type)) def get_type(self): return int(self.chat_dom.getAttribute("type")) def set_alias(self,alias): self.chat_dom.setAttribute("alias",alias) def get_alias(self): return self.chat_dom.getAttribute("alias") def get_text(self): return self.text_node._get_nodeValue() def get_version(self): return self.chat_dom.getAttribute("version") --- NEW FILE: chatwnd.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # [...1968 lines suppressed...] def getAttentionBitmap(): return wxBitmapFromImage(getAttentionImage()) def getAttentionImage(): stream = cStringIO.StringIO(getAttentionData()) return wxImageFromStream(stream) def getNormalData(): return \ '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x02\x00\x00\x00\x90\x91h6\x00\x00\x00\tpHYs\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd~\xfc\x00\x00\x01\x9eIDATx\x9c\x8d\xcb\xcfK\xd3q\x1c\xc7\xf1\xd7\xeb\xfd\xf9\xda\x886\x1c$\xb9\x8c\x99V\xb3\x15\x16\xe8\x8eI\x87\x0e\xb1\xf5\x0fx\xebo\x88N\xfd\x03\xd1\xbf\xe1\xcds\x04\x99\x04\x15\x11A\x87\x141b?\x88pPN\x17\xe5\\\x10\xad\xef\xe7\xfd\xea\xd0\xa5\xc3\x98>\xcf\x8f\'%\x17$\r\xa8CaO\xda\x97w\x1c}@B& o,\x90S\xb2<\x99#,\x01"\x90B\xfb\xee\x9f\xdc?\xc8w\x1cm\xe9\x07%\xd9I`"r\xca\xec\nq\xd5\xec\x02\x99M$I]\xf7\xed\x187\\\xef\xdc[\xd5\xb9\x06\xfe\xebYk!\xc1.\xd1#\x06\xb29F\xef*n\xa7\xfe&\x8d\xcf\xab\xa5\xd7\x18Vmy\xf5\xfe\xc3\'I\xb8i\xb6\xc44\xfd\x18\xfde\x8c\xeb\xb7/=\x1e\xaa\xff\xb5\xd6(\'\xc9\xad\xc4\xaa&\xecI\x9f\xa3\xea#4\x80\xda\xe5\xba{\xdd\xd16\xa9\x0buj\xa5\xc6\xe8\x01\x80\xb4\xeb\xfab\xe0o\xe9\xe7\x91\x1a@\xbfw\n\x18\x18\x00\x80\xc7\x19\x00\x900*G\xe4\x8f\xa3s\xe3N\x8c\x1bY0\xce\xae7\x17\x8e\x1c\x8c\xd3\xc6i3L\xd2.Z\xb8v\xb6\xd8\x1e\xa1\x9f\xb6\x16ieb\xd6`\xf9\xc0r\xb0\xca\xca\xab{\xb5\xe5\xd5\xa1z\xadY\x19\x0b7\x12[4;O\xd7\x1fx\xcfU\x8fq+\xea\xbd\xd4|p\xf7\xd1\xe6\xdb%\x00\x85b{\xe5\xc5\x1d\x0bE\xf2z@%\xd8<m\x86\x92\x0b\x11\xfe\xcd\xbd\x13\xd5\x14v\xa0\xaf\xf2CA\xb41\xd3\x19\x84s\xc6\x92q\x86\x9c$3\x94\x04@J\xa1_\xc0\x81\xf4]:\x10\xfa\x92\x1b3D\x16v\x1a\x9c0f\x85\x13\x04\xff\x02\xf1\xb8\xd1\xec\x08\xfa\x9c\x89\x00\x00\x00\x00IEND\xaeB`\x82' def getNormalBitmap(): return wxBitmapFromImage(getNormalImage()) def getNormalImage(): stream = cStringIO.StringIO(getNormalData()) return wxImageFromStream(stream) --- NEW FILE: __init__.py --- --- NEW FILE: commands.py --- # This class implements the basic chat commands available in the chat interface. # # Defines: # __init__(self,chat) # docmd(self,text) # on_help(self) # on_whisper(self,text) # import string import time import orpg.tools.orpg_update import orpg.orpg_version import orpg.orpg_windows import orpg.plugins#mDuo13 added import traceback ##-------------------------------------------------------------- ## dynamically loading module for extended developer commands ## allows developers to work on new chat commands without ## integrating them directly into the ORPG code allowing ## updating of their code without merging changes ## cmd_ext.py should NOT be included in the CVS or Actual Releases try: import cmd_ext print "Importing Developer Extended Command Set" except: pass ##---------------------------------------------------------------- ANTI_LOG_CHAR = '!' class chat_commands: # Initialization subroutine. # # !self : instance of self # !chat : instance of the chat window to write to def __init__(self,chat): self.post = chat.Post self.colorize = chat.colorize self.session = chat.session #self.send = chat.session.send self.settings = chat.settings self.chat = chat self.cmdlist = {} self.shortcmdlist = {} self.defaultcmds() self.defaultcmdalias() # def __init__ - end self.previous_whisper = [] # This subroutine will take a text string and attempt to match to a series # of implemented emotions. # # !self : instance of self # !text : string of text matching an implemented emotion def addcommand(self, cmd, function, helpmsg): if not self.cmdlist.has_key(cmd) and not self.shortcmdlist.has_key(cmd): self.cmdlist[cmd] = {} self.cmdlist[cmd]['function'] = function self.cmdlist[cmd]['help'] = helpmsg #print 'Command Added: ' + cmd def addshortcmd(self, shortcmd, longcmd): if not self.shortcmdlist.has_key(shortcmd) and not self.cmdlist.has_key(shortcmd): self.shortcmdlist[shortcmd] = longcmd def removecmd(self, cmd): if self.cmdlist.has_key(cmd): del self.cmdlist[cmd] elif self.shortcmdlist.has_key(cmd): del self.shortcmdlist[cmd] #print 'Command Removed: ' + cmd def defaultcmds(self): self.addcommand('/help', self.on_help, '- Displays this help message') self.addcommand('/version', self.on_version, ' - Displays current version of OpenRPG.') self.addcommand('/me', self.chat.emote_message, ' - Alias for **yourname does something.**') self.addcommand('/ignore', self.on_ignore, '[player_id,player_id,... | ignored_ip,ignored_ip,... | list] - Toggle ignore for user associated with that player ID. Using the IP will remove the only not toggle.') self.addcommand('/lines', self.on_lines, 'number - View or set the maximum number of lines shown in chat.') self.addcommand('/load', self.on_load, 'filename - Loads settings from another ini file from the myfiles directory.') self.addcommand('/purge', self.chat.Purge_buffer, '- Purge history older than your buffersize.') self.addcommand('/role', self.on_role, '[player_id = GM | Player | Lurker] - Get player roles from ther server, self.or change the role of a player.') self.addcommand('/font', self.on_font, 'fontname - Sets the font.') self.addcommand('/fontsize', self.on_fontsize, 'size - Sets the size of your fonts. Recomended 8 or better for the size.') self.addcommand('/close', self.on_close, 'Close the chat tab') self.addcommand('/set', self.on_set, '[setting[=value]] - Displays one or all settings, self.or sets a setting.') self.addcommand('/whisper', self.on_whisper, 'player_id_number, ... = message - Whisper to player(s). Can contain multiple IDs.') self.addcommand('/gw', self.on_groupwhisper, 'group_name=message - Type /gw help for more information') self.addcommand('/gm', self.on_gmwhisper, 'message - Whispers to all GMs in the room') self.addcommand('/name', self.on_name, 'name - Change your name.') self.addcommand('/time', self.on_time, '- Display the local and GMT time and date.') self.addcommand('/status', self.on_status, 'your_status - Set your online status (afk,away,etc..).') self.addcommand('/dieroller', self.on_dieroller, '- Set your dieroller or list the available rollers.') self.addcommand('/log', self.on_log, '[ on | off | to <em>filename</em> ] - Check log state, additionally turn logging on, self.off, self.or set the log filename prefix.') self.addcommand('/update', self.on_update, '[get] - Get the latest version of OpenRPG.') self.addcommand('/moderate', self.on_moderate, '[ on | off ][player_id=on|off] - Show who can speak in a moderated room, self.or turn room moderation on or off.') self.addcommand('/tab', self.invoke_tab, 'player_id - Creates a tab so you can whisper rolls to youror what ever') self.addcommand('/ping', self.on_ping, '- Ask for a response from the server.') self.addcommand('/admin', self.on_remote_admin, '- Remote admin commands') self.addcommand('/description', self.on_description, 'message - Creates a block of text, used for room descriptions and such') def defaultcmdalias(self): self.addshortcmd('/?', '/help') self.addshortcmd('/he', '/me') self.addshortcmd('/she', '/me') self.addshortcmd('/i', '/ignore') self.addshortcmd('/w', '/whisper') self.addshortcmd('/nick', '/name') self.addshortcmd('/date', '/time') self.addshortcmd('/desc', '/description') self.addshortcmd('/d', '/description') #This is just an example or a differant way the shorcmd can be used self.addshortcmd('/sleep', '/me falls asleep') def docmd(self,text): cmdsearch = string.split(text,None,1) cmd = string.lower(cmdsearch[0]) start = len(cmd) end = len(text) cmdargs = text[start+1:end] if self.cmdlist.has_key(cmd): self.cmdlist[cmd]['function'](cmdargs) elif self.shortcmdlist.has_key(cmd): self.docmd(self.shortcmdlist[cmd] + " " + cmdargs) else: msg = "Sorry I don't know what %s is!" % (cmd) self.chat.InfoPost(msg) def on_version(self, cmdargs=""): self.chat.InfoPost("Version is OpenRPG " + self.chat.version) def on_load(self, cmdargs): args = string.split(cmdargs,None,-1) try: self.settings.setup_ini(args[0]) self.settings.reload_settings(self.chat) self.chat.InfoPost("Settings Loaded from file " + args[0] ) except Exception,e: print e self.chat.InfoPost("ERROR Loading settings") def on_font(self, cmdargs): try: fontsettings = self.chat.set_default_font(fontname=cmdargs, fontsize=None) except: self.chat.InfoPost("ERROR setting default font") def on_fontsize(self, cmdargs): args = string.split(cmdargs,None,-1) try: fontsettings = self.chat.set_default_font(fontname=None, fontsize=int(args[0])) except Exception, e: print e self.chat.InfoPost("ERROR setting default font size") def on_close(self, cmdargs): try: chatpanel = self.chat if (chatpanel.sendtarget == "all"): chatpanel.InfoPost("Error: cannot close public chat tab.") else: chatpanel.chat_timer.Stop() chatpanel.parent.destroy_private_tab(chatpanel) except: self.chat.InfoPost("Error: cannot close private chat tab.") def on_time(self, cmdargs): local_time = time.localtime() gmt_time = time.gmtime() format_string = "%A %b %d, %Y %I:%M:%S%p" self.chat.InfoPost("<br>Local: " + time.strftime(format_string)+\ "<br>GMT: "+time.strftime(format_string,gmt_time)) def on_dieroller(self, cmdargs): args = string.split(cmdargs,None,-1) rm = self.chat.roller_manager try: rm.set_roller(args[0]) self.chat.SystemPost("You have changed your die roller to the <b>\"" + args[0] + "\"</b> roller.") self.settings.set_setting('dieroller',args[0]) except Exception, e: print e self.chat.InfoPost("Available die rollers: " + str(rm.get_rollers())) self.chat.InfoPost("You are using the <b>\"" + rm.get_roller() + "\"</b> die roller.") def on_ping(self, cmdargs): ct = time.clock() msg = "<ping player='"+self.session.id+"' time='"+str(ct)+"' />" self.session.outbox.put(msg) def on_log(self,cmdargs): args = string.split(cmdargs,None,-1) logfile = self.settings.get_setting( 'GameLogPrefix' ) if len( args ) == 0: self.postLoggingState() elif args[0] == "on" and logfile != '': try: while logfile[ 0 ] == ANTI_LOG_CHAR: print logfile logfile = logfile[ 1: ] except IndexError,e: self.chat.SystemPost("log filename is blank, system will *not* be logging until a valid filename is specified" ) self.settings.set_setting( 'GameLogPrefix', logfile ) return self.settings.set_setting( 'GameLogPrefix', logfile ) self.postLoggingState() elif args[0] == "off": logfile = ANTI_LOG_CHAR+logfile self.settings.set_setting( 'GameLogPrefix', logfile ) self.postLoggingState() elif args[0] == "to": if len( args ) > 1: logfile = args[1] self.settings.set_setting( 'GameLogPrefix', logfile ) else: self.chat.SystemPost('You must also specify a filename with the <em>/log to</em> command.' ) self.postLoggingState() else: self.chat.InfoPost("Unknown logging command, use 'on' or 'off'" ) def on_lines(self, cmdargs): if int(cmdargs) > 0: self.settings.set_setting('buffersize',cmdargs) self.chat.SystemPost("Maximum number of lines in chat now set to " + cmdargs) # self.chat.buftxt = cmdargs self.chat.on_buffer_size(cmdargs) else: self.chat.SystemPost("Maximum lines in chat: " + self.settings.get_setting("buffersize")) self.chat.Post() def postLoggingState( self ): logfile = self.settings.get_setting( 'GameLogPrefix' ) try: if logfile[0] != ANTI_LOG_CHAR: comment = 'is' else: comment = 'is not' except: comment = 'is not' suffix = time.strftime( '-%d-%m-%y.html', time.localtime( time.time() ) ) self.chat.InfoPost('Log filename is "%s%s", system is %s logging.' % (logfile, suffix, comment) ) # This subroutine will set the players netork status. # #!self : instance of self def on_name(self, cmdargs): #only 20 chars no more! :) if cmdargs == "": self.chat.InfoPost("**Incorrect syntax for name.") else: #txt = txt[:50] self.settings.set_setting('player', cmdargs) self.session.set_name(cmdargs) # def on_status - end # This subroutine will set the players netork status. # # !self : instance of self def on_status(self, cmdargs): if cmdargs == "": self.chat.InfoPost("Incorrect synatx for status.") else: #only 20 chars no more! :) txt = cmdargs[:20] self.session.set_text_status(txt) # def on_status - end def on_set(self, cmdargs): args = string.split(cmdargs,None,-1) keys = self.settings.get_setting_keys() if len(args) == 0: line = "<table border='2'>" for m in range(len(keys)): line += "<tr><td>" + keys[m] + "</td><td> " + self.settings.get_setting(keys[m]) + "</td></tr>" line += "</table>" self.chat.InfoPost(line) else: split_name_from_data = cmdargs.find("=") if split_name_from_data == -1: for m in keys: if m == args[0]: return_string = "<table border='2'><tr><td>" + args[0] + "</td><td>"\ + self.settings.get_setting(args[0]) + "</td></tr></table>" self.chat.InfoPost(return_string) else: name = cmdargs[:split_name_from_data].strip() for m in keys: if m == name: setting = cmdargs[split_name_from_data+1:].strip() self.settings.set_setting(name,setting) return_string = name + " changed to " + setting self.chat.InfoPost(return_string) self.session.set_name(self.settings.get_setting("player")) self.chat.set_colors() self.chat.set_buffersize() # This subroutine will display the correct usage of the different emotions. # #!self : instance of self def on_help(self, cmdargs=""): cmds = self.cmdlist.keys() cmds.sort() shortcmds = self.shortcmdlist.keys() shortcmds.sort() msg = '<br><b>Command Alias List:</b>' for shortcmd in shortcmds: msg += '<br><b><font color="#0000CC">%s</font></b> is short for <font color="#000000">%s</font>' % (shortcmd, self.shortcmdlist[shortcmd]) msg += '<br><br><b>Command List:</b>' for cmd in cmds: msg += '<br><b><font color="#000000">%s</font></b>' % (cmd) for shortcmd in shortcmds: if self.shortcmdlist[shortcmd] == cmd: msg += ', <b><font color="#0000CC">%s</font></b>' % (shortcmd) msg += ' %s' % (self.cmdlist[cmd]['help']) self.chat.InfoPost(msg) # This subroutine will either show the list of currently ignored users # !self : instance of self # !text : string that is comprised of a list of users to toggle the ignore flag def on_ignore(self, cmdargs): args = string.split(cmdargs,None,-1) (ignore_list, ignore_name) = self.session.get_ignore_list() ignore_output = self.colorize(self.chat.syscolor,"<br><u>Player IDs Currently being Ignored:</u><br>") if cmdargs == "": if len(ignore_list) == 0: ignore_output += self.colorize(self.chat.infocolor,"No players are currently being ignored.<br>") else: for m in ignore_list: ignore_txt = m + " " + ignore_name[ignore_list.index(m)] + "<br>" ignore_output += self.colorize(self.chat.infocolor,ignore_txt) self.chat.Post(ignore_output) else: players = cmdargs.split(",") for m in players: try: id = `int(m)` (result, id, name) = self.session.toggle_ignore(id) if result == 0: self.chat.InfoPost("Player " + name + " with ID:" + id + " no longer ignored") if result == 1: self.chat.InfoPost("Player " + name + " with ID:" + id + " now being ignored") except: self.chat.InfoPost(m + " was ignored because it is an invalid player ID") traceback.print_exc() def on_role(self, cmdargs): if cmdargs == "": self.session.display_roles() return delim = cmdargs.find("=") if delim < 0: self.chat.InfoPost("**Incorrect synatax for Role." + str(delim)) return player_ids = string.split(cmdargs[:delim],",") role = cmdargs[delim+1:].strip() role = role.lower() if (role.lower() == "player") or (role.lower() == "gm") or (role.lower() == "lurker"): if role.lower() == "player": role = "Player" elif role.lower() == "gm": role = "GM" else: role = "Lurker" try: role_pwd = self.session.orpgFrame_callback.password_manager.GetPassword("admin",int(self.session.group_id)) if role_pwd != None: for m in player_ids: self.session.set_role(m.strip(),role,role_pwd) except: traceback.print_exc() # return # This subroutine implements the whisper functionality that enables a user # to whisper to another user. # # !self : instance of self # !text : string that is comprised of a list of users and the message to #whisper. def on_whisper(self, cmdargs): delim = cmdargs.find("=") if delim < 0: if self.previous_whisper: player_ids = self.previous_whisper else: self.chat.InfoPost("**Incorrect syntax for whisper." + str(delim)) return else: player_ids = string.split(cmdargs[:delim], ",") self.previous_whisper = player_ids mesg = string.strip(cmdargs[delim+1:]) self.chat.whisper_to_players(mesg,player_ids) #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- def on_groupwhisper(self, cmdargs): args = string.split(cmdargs,None,-1) delim = cmdargs.find("=") if delim > 0: group_ids = string.split(cmdargs[:delim], ",") elif args[0] == "add": if not orpg.player_list.WG_LIST.has_key(args[2]): orpg.player_list.WG_LIST[args[2]] = {} orpg.player_list.WG_LIST[args[2]][int(args[1])] = int(args[1]) return elif args[0] == "remove" or args[0] == "delete": del orpg.player_list.WG_LIST[args[2]][int(args[1])] if len(orpg.player_list.WG_LIST[args[2]]) == 0: del orpg.player_list.WG_LIST[args[2]] return elif args[0] == "create" or args[0] == "new_group": if not orpg.player_list.WG_LIST.has_key(args[1]): orpg.player_list.WG_LIST[args[1]] = {} return elif args[0] == "list": if orpg.player_list.WG_LIST.has_key(args[1]): for n in orpg.player_list.WG_LIST[args[1]]: player = self.session.get_player_info(str(n)) self.chat.InfoPost(str(player[0])) else: self.chat.InfoPost("Invalid Whisper Group Name") return elif args[0] == "clear": if orpg.player_list.WG_LIST.has_key(args[1]): orpg.player_list.WG_LIST[args[1]].clear() else: self.chat.InfoPost("Invalid Whisper Group Name") return elif args[0] == "clearall": orpg.player_list.WG_LIST.clear() return else: self.chat.InfoPost("<b>/gw add</b> (player_id) (group_name) - Adds [player_id] to [group_name]") self.chat.InfoPost("<b>/gw remove</b> (player_id) (group_name) - Removes [player_id] from [group_name]") self.chat.InfoPost("<b>/gw</b> (group_name)<b>=</b>(message) - Sends [message] to [group_name]") self.chat.InfoPost("<b>/gw create</b> (group_name) - Creates a whisper group called [group_name]") self.chat.InfoPost("<b>/gw list</b> (group_name) - Lists all players in [group_name]") self.chat.InfoPost("<b>/gw clear</b> (group_name) - Removes all players from [group_name]") self.chat.InfoPost("<b>/gw clearall</b> - Removes all existing whisper groups") return msg = string.strip(cmdargs[delim+1:]) for gid in group_ids: idList = "" for n in orpg.player_list.WG_LIST[gid]: if idList == "": idList = str(n) else: idList = str(n) + ", " + idList self.on_whisper(idList + "=" + self.settings.get_setting("gwtext") + msg) #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- def on_gmwhisper(self, cmdargs): if cmdargs == "": self.chat.InfoPost("**Incorrect syntax for GM Whisper.") else: the_gms = self.chat.get_gms() if len(the_gms): gmstring = "" for each_gm in the_gms: if gmstring != "": gmstring += "," gmstring += each_gm self.on_whisper(gmstring + "=" + cmdargs) else: self.chat.InfoPost("**No GMs to Whisper to.") def on_moderate(self, cmdargs): if cmdargs <> "": pos = cmdargs.find("=") if (pos < 0): plist = "" if cmdargs.lower() == "on": action = "enable" elif cmdargs.lower() == "off": action="disable" else: self.chat.InfoPost("Wrong syntax for moderate command!") return else: plist = string.strip(cmdargs[:pos]) tag = string.strip(cmdargs[pos+1:]) if tag.lower() == "on": action = "addvoice" elif tag.lower() == "off": action = "delvoice" else: self.chat.InfoPost("Wrong syntax for moderate command!") return pwd = self.session.orpgFrame_callback.password_manager.GetPassword("admin",int(self.session.group_id)) if pwd != None: msg = "<moderate" msg += " action = '" + action + "'" msg +=" from = '" + self.session.id + "' pwd='" + pwd + "'" if (plist <> ""): msg += " users='"+plist+"'" msg += " />" self.session.outbox.put(msg) pass else: msg = "<moderate action='list' from='"+self.session.id+"' />" self.session.outbox.put(msg) self.session.update() def on_update(self, cmdargs): url = self.settings.get_setting( "PackagesBaseURL" ) Disableupdate = int(self.settings.get_setting("Disableupdate" )) updater = orpg.tools.orpg_update.orpg_update(url,self.chat.InfoPost) self.chat.InfoPost("You are running " + orpg.orpg_version.VERSION + " (build "+orpg.orpg_version.BUILD+")") p = updater.get_package() self.chat.InfoPost("The latest version is " + p.getAttribute("version") + " (build "+p.getAttribute("build")+")") is_up2date = updater.is_up2date(orpg.orpg_version.VERSION, orpg.orpg_version.BUILD) if is_up2date: self.chat.InfoPost("OpenRPG does not require updating.") else: self.chat.SystemPost("Your code is out of date!") self.chat.SystemPost("Type \"/update get\" to update to the latest version.") if cmdargs == "get" and is_up2date: self.chat.InfoPost("OpenRPG does not require updating.") elif cmdargs == "get" and not is_up2date: if not Disableupdate: dlg = orpg.orpg_windows.do_progress_dlg(self.chat,"OpenRPG Updater","Please wait while OpenRPG upates...",3) dlg.Update(1,"Please wait while OpenRPG upates...") updater.update() # set the setting back so we get new gametree features self.settings.set_setting("LoadGameTreeFeatures","1") self.chat.SystemPost("Please restart OpenRPG!") dlg.Update(2,"Done.") dlg.Destroy() def on_description(self, cmdargs): if len(cmdargs) <= 0: self.chat.InfoPost("**No description text to display." + str(delim)) return mesg = "<table bgcolor='#c0c0c0' border='3' cellpadding='5' cellspacing='0' width='100%'><tr><td><font color='#000000'>" mesg += string.strip(cmdargs) mesg += "</font></td></tr></table>" self.chat.Post(mesg) self.chat.send_chat_message(mesg) def invoke_tab(self, cmdargs): ######START mDuo13's Tab Initiator######## playerid = cmdargs.strip() tabbed_whispers_p = self.settings.get_setting("tabbedwhispers") panelexists = 0 if (tabbed_whispers_p == "1"): # Check to see if parent notebook already has a private tab for player for panel in self.chat.parent.panel_list: if (panel.sendtarget == playerid): self.chat.Post("Cannot invoke tab: Tab already exists.") return try: displaypanel = self.chat.parent.create_private_tab(playerid) except: self.chat.Post("That ID# is not valid.") return cidx = self.chat.parent.GetSelection() nidx = self.chat.parent.get_tab_index(displaypanel) self.chat.parent.SetSelection(nidx) self.chat.parent.SetSelection(cidx) self.chat.parent.SetPageImage(nidx, self.chat.parent.chatAttentionIdx) return else: self.chat.Post("Cannot invoke tab: Tabbed whispering is disabled.") return #######END mDuo13's Tab Initiator######### def on_remote_admin(self, cmdargs): args = string.split(cmdargs,None,-1) #handles remote administration commands try: pass_state = 0 pwd = self.session.orpgFrame_callback.password_manager.GetSilentPassword("server") if pwd != None: pass_state = 1 else: pwd = "<i>[NONE]</i>" if len( args ) == 0: #raw command return state info msg = "<br><b>Remote Administrator Config:</b>" if pass_state != 1 : msg += " Password not set. Remote admin functions disabled<br>" else: msg += " Enabled. Using password \""+pwd+"\"<br>" self.chat.SystemPost(msg) return if pass_state != 1 and args[0] != "set": #no commands under this point will execute unless an admin password has been previously set self.chat.SystemPost("Command ignored. No remote administrator password set!!") return msgbase = "<admin id=\""+self.session.id+"\" group_id=\""+self.session.group_id+"\" pwd=\""+pwd+"\" " if args[0] == "set": if len( args ) > 1: self.session.orpgFrame_callback.password_manager.server = str( args[1] ) self.chat.SystemPost( "Remote administration commands using password: \""+str(self.session.orpgFrame_callback.password_manager.GetSilentPassword("server"))+"\"" ) else: pwd = self.session.orpgFrame_callback.password_manager.GetPassword("server") if pwd != None: self.chat.SystemPost( "Remote administration commands using password: \""+pwd+"\"" ) elif args[0] == "help": #request help from server msg = msgbase + " cmd=\"help\" />" self.session.outbox.put(msg) elif args[0] == "nameroom": #reqest room renaming on server msg = msgbase+" cmd=\"nameroom\" rmid=\""+ str(args[1])+"\" name=\""+ string.join(args[2:])+"\" />" self.session.outbox.put(msg) elif args[0] == "roompasswords": #reqest room renaming on server msg = msgbase+" cmd=\"roompasswords\"/>" self.session.outbox.put(msg) elif args[0] == "message": #send message to a specific player on the server via the system administrator msg = msgbase+" cmd=\"message\" to_id=\""+ str(args[1])+"\" msg=\""+ string.join(args[2:])+"\" />" self.session.outbox.put(msg) elif args[0] == "broadcast": #send a message to all players on server from the system administrator msg = msgbase+" cmd=\"broadcast\" msg=\""+ string.join(args[1:])+"\" />" self.session.outbox.put(msg) elif args[0] == "killgroup": #remove a group from the server and drop all players within the group msg = msgbase+" cmd=\"killgroup\" gid=\""+ str(args[1])+"\" />" self.session.outbox.put(msg) elif args[0] == "uptime": #request uptime report from server msg = msgbase+" cmd=\"uptime\" />" self.session.outbox.put(msg) elif args[0] == "createroom": #request creation of a (temporary) persistant room if len( args ) < 2: self.chat.SystemPost( "You must supply a name and boot password at least. <br>/admin createroom <name> <boot password> [password]" ) return if len( args ) < 3: self.chat.SystemPost( "You must supply a boot password also.<br>/admin createroom <name> <boot password> [password]" ) return if len( args ) < 4: cmdlist.append("") msg = msgbase+" cmd=\"createroom\" name=\""+str(args[1])+"\" boot=\""+ str(args[2])+"\" pass=\""+ str(args[3])+"\" />" self.session.outbox.put(msg) elif args[0] == "passwd": #request boot password change on a room msg = msgbase+" cmd=\"passwd\" gid=\""+str(args[1])+"\" pass=\""+ str(args[2])+"\" />" self.session.outbox.put(msg) elif args[0] == "list": #request a list of rooms and players from server msg = msgbase+" cmd=\"list\" />" self.session.outbox.put(msg) elif args[0] == "killserver": #remotely kill the server msg = msgbase+" cmd=\"killserver\" />" self.session.outbox.put(msg) else: self.chat.InfoPost("Unknown administrator command" ) except: self.chat.InfoPost("An error has occured while processing a Remote Administrator command!") traceback.print_exc() --- NEW FILE: chat_version.py --- ## this file hold the chat version ## CHAT_VERSION = "1.0" --- NEW FILE: chat_util.py --- # utility function; see Post() in chatwnd.py import re #============================================ # simple_html_repair(string) # # Crude html/xml parser/verifier. # Catches many mistyped and/or malformed # html tags and prevents them from causing # issues with the chat display (see chatwnd.py) # DOES NOT catch misused but properly formated # html like <script> or <li> which are known # to cause issues with the chat display # # Created 04-25-2005 by Snowdog #============================================= def simple_html_repair(string): "Returns string with extra > symbols to isolate badly formated HTML" #walk though string checking positions of < and > tags. first_instance = string.find('<') if first_instance == -1: return string #no html, bail out. #strip string of an instances of ">>" and "<<" recursively #while (string.find(">>") != -1):string = string.replace(">>",">") while (string.find("<<") != -1):string = string.replace("<<","<") last_start = first_instance in_tag_flag = 1 a = first_instance + 1 while a < len(string): if string[a] == '<': if in_tag_flag == 1: #attempt to figure out best place to put missing > #search from last_start to current position at_front = 1 for best_pos in range(last_start,a): if (str(string[best_pos]).isspace())and (at_front == 0): break else: at_front = 0 best_pos = best_pos + 1 a = best_pos string = string[:a]+">"+string[a:] in_tag_flag = 0 #jump back up one character to catch the last > and reset the in_tag_flag a = a - 1 else: in_tag_flag = 1 last_start = a if string[a] == '>': last_start = a #found a closing tag, move start of scan block up. in_tag_flag = 0 if (a >= (len(string)-1))and(in_tag_flag == 1): #at end of string and need a closing tag marker string = string +">" a = a+1 #strip string of an instances of "<>" string = string.replace("<>","") #sanity check. Count the < and > characters, if there arn't enough > chars #tack them on the end to avoid open-tag conditions diff = string.count('<') - string.count('>') if diff > 0: for d in range(1,diff): string = string+">" return string #================================================ # strip_script_tags(string) # # removes all script tags (start and end) # 04-26-2005 Snowdog #================================================ def strip_script_tags(string): #kill the <script> issue p = re.compile( '<(\s*)(/*)[Ss][Cc][Rr][Ii][Pp][Tt](.*?)>') string = p.sub( "<!-- script tag removed //-->", string) return string #================================================ # strip_li_tags(string) # # removes all li tags (start and end) # 05-13-2005 #================================================ def strip_li_tags(string): #kill the <li> issue string = re.sub( r'<(\s*)[Ll][Ii](.*?)>', r'<b><font color="#000000" size=+1>*</font></b> ', string) string = re.sub( r'<(/*)[Ll][Ii](.*?)>', r'<br>', string) return string #================================================ # strip_body_tags(string) # # removes all body tags (start and end) from messages # should not break the setting of custom background colors # through legitimate means such as the OpenRPG settings. # 07-27-2005 by mDuo13 #================================================ def strip_body_tags(string): bodytag_regex = re.compile(r"""<\/?body.*?>""", re.I) string = re.sub(bodytag_regex, "", string) return string #================================================ # strip_misalignment_tags(string) # # removes the alignment aspect of <p> tags, since # simply closing one doesn't actually fix the text # alignment. (I'm assuming this is a bug in wxWindows' # html parser.) # However, closing <center> tags does # return the text to its normal alignment, so this # algorithm simply closes them, allowing them to be # used legitimately without causing much annoyance. # 07-27-2005 mDuo13 #================================================ def strip_misalignment_tags(string): alignment_regex = re.compile(r"""<p([^>]*?)align\s*=\s*('.*?'|".*?"|[^\s>]*)(.*?)>""", re.I) string = re.sub(alignment_regex, "<p\\1\\3>", string) center_regex = re.compile(r"""<center.*?>""", re.I) endcenter_regex = re.compile(r"""</center.*?>""", re.I) num_centertags = center_regex.findall(string) num_endcentertags = endcenter_regex.findall(string) if num_centertags > num_endcentertags: missing_tags = len(num_centertags) - len(num_endcentertags) string = string + missing_tags*"</center>"#yes, you can do this. return string #================================================ # strip_img_tags(string) # # removes all img tags (start and end) # 05-13-2005 # redone 07-11-2005 by mDuo13 #================================================ def strip_img_tags(string): #This is a Settings definable feature, Allowing users to enable or disable image display to fix the client crash due to large img posted to chat. #p = re.sub( r'<(\s*)(/*)[Ii][Mm][Gg][ ][Ss][Rr][Cc][=](.*?)>', r'<!-- img tag removed //--> <a href=\3>\3</a>', string) #this regex is substantially more powerful than the one above img_tag_regex = re.compile(r"""<img.*?src\s*?=\s*('.*?'|".*?"|[^\s>]*).*?>""", re.I) #this is what replaces the regex match. the \\1 refers to the URL from the previous string img_repl_str = "<a href=\\1>[img]</a>" #replaces all instances of images in the string with links p = re.sub(img_tag_regex, img_repl_str, string) return p |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:24
|
Update of /cvsroot/winopenrpg/openrpg1/data/dnd3e In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/data/dnd3e Added Files: dnd3earmor.xml dnd3echaracter.xml dnd3eclasses.xml dnd3edivine.xml dnd3efeats.xml dnd3epowers.xml dnd3espells.xml dnd3eweapons.xml feats.txt skills.txt Log Message: Initial commit of OpenRPG++ python --- NEW FILE: dnd3eweapons.xml --- <weapons version="01.02"> <footnotes> <c txt="Shuriken don't get str bonus, 3/attack; don't mark t" ></c> <c txt="monkWeap unarmed base, monk att/round, and other monk attack mods" ></c> <c txt="fn field for a weapon stands for 'footnotes'" ></c> <f mark="b" txt="classified as a bow or sling, str penalty applies " ></f> <f mark="c" txt="set for charge weapon, 2x dam when set Vs charging opponent." ></f> <f mark="C" txt="2x damaged when used on charge (may need to be mounted) " ></f> <f mark="d" txt="Classified as a double weapon." ></f> <f mark="m" txt="Monks get special 'Monk Weapon'advantages. " ></f> <f mark="o" txt="+2 opposed attack rolls wrt disarming/being disarmed on fail." ></f> <f mark="r" txt="Classified as a reach weapon, can strike at 6-10, but cannot strike 0-5" ></f> <f mark="R" txt="Classified as a special reach weapon, can strike at 0-10" ></f> <f mark="s" txt="Weapon can only do subdual damage." ></f> <f mark="t" txt="Classified as a thrown (or throwable) weapon by PH,standard thrown characteristics will be applied" ></f> <f mark="T" txt="Weapon can be used for making trip attacks." ></f> <f mark="X" txt="Custom weapon, should have appropriate footnotes added." ></f> </footnotes> <weapon mod="0" fn="" name="Antimatter rifle" cost="-1" category="Futuristic Weapons-Ranged" size="Medium" damage="6d10" critical="x2" range="10" weight="10" type="Special" > <description ></description > </weapon> <weapon mod="0" fn="d" name="Axe, orc double" cost="60" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d8" critical="x3" range="0" weight="25" type="S" > <description ></description > </weapon> <weapon mod="0" fn="t" name="Axe, throwing" cost="8" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="x2" range="10" weight="4" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Battleaxe" cost="10" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x3" range="0" weight="7" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Blowgun" cost="1" category="Asian Weapons-Ranged" size="Small" damage="1" critical="x2" range="10" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" fn="oT" name="Chain, spiked" cost="25" category="Exotic Weapons-Melee" size="Large" damage="2d4" critical="x2" range="0" weight="15" type="P" > <description ></description > </weapon> <weapon mod="0" fn="t" name="Club" cost="0" category="Simple Weapons-Melee" size="Medium" damage="1d6" critical="x2" range="10" weight="3" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Crossbow, hand" cost="100" category="Exotic Weapons-Ranged" size="Tiny" damage="1d4" critical="19-20/x2" range="30" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Crossbow, heavy" cost="50" category="Simple Weapons-Ranged" size="Medium" damage="1d10" critical="19-20/x2" range="120" weight="9" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Crossbow, light" cost="35" category="Simple Weapons-Ranged" size="Small" damage="1d8" critical="19-20/x2" range="80" weight="6" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Crossbow, repeating" cost="250" category="Exotic Weapons-Ranged" size="Medium" damage="1d8" critical="19-20/x2" range="80" weight="16" type="P" > <description ></description > </weapon> <weapon mod="0" fn="t" name="Dagger" cost="2" category="Simple Weapons-Melee" size="Tiny" damage="1d4" critical="19-20/x2" range="10" weight="1" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Dagger, punching" cost="2" category="Simple Weapons-Melee" size="Tiny" damage="1d4" critical="x3" range="0" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Dart" cost="0" category="Simple Weapons-Ranged" size="Small" damage="1d4" critical="x2" range="20" weight="0" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Falchion" cost="75" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="18-29/x2" range="0" weight="16" type="S" > <description ></description > </weapon> <weapon mod="0" fn="doT" name="Flail, dire" cost="90" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d8" critical="x2" range="0" weight="20" type="B" > <description ></description > </weapon> <weapon mod="0" fn="oT" name="Flail, heavy" cost="15" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="19-20/x2" range="0" weight="20" type="B" > <description ></description > </weapon> <weapon mod="0" fn="oT" name="Flail, light" cost="8" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="0" weight="5" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Flamer" cost="-1" category="Futuristic Weapons-Ranged" size="Medium" damage="3d6*" critical="-" range="20" weight="8" type="Special" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Flurry of Blows(Monk Med)" cost="-1" category="Exotic Weapons-Melee" size="Unarmed" damage="Monk Med" critical="x2" range="0" weight="0" type="Special" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Flurry of Blows(Monk Small)" cost="-1" category="Exotic Weapons-Melee" size="Unarmed" damage="Monk Small" critical="x2" range="0" weight="0" type="Special" > <description ></description > </weapon> <weapon mod="0" fn="" name="Gauntlet" cost="2 gp" category="Simple Weapons-Melee" size="Unarmed" damage="*" critical="*" range="0" weight="2" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Gauntlet, spiked" cost="5" category="Simple Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" fn="r" name="Glaive" cost="8" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Greataxe" cost="20" category="Martial Weapons-Melee" size="Large" damage="1d12" critical="x3" range="0" weight="20" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Greatclub" cost="5" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="x2" range="0" weight="10" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Greatsword" cost="50" category="Martial Weapons-Melee" size="Large" damage="2d6" critical="19-20/x2" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Grenade launcher" cost="-1" category="Modern Weapons-Ranged" size="Large" damage="*" critical="*" range="200" weight="12" type="*" > <description ></description > </weapon> <weapon mod="0" fn="rT" name="Guisarme" cost="9" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" fn="Ts" name="Halberd" cost="10" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="x3" range="0" weight="15" type="P&S" > <description ></description > </weapon> <weapon mod="0" fn="tc" name="Halfspear" cost="1" category="Simple Weapons-Melee" size="Medium" damage="1d6" critical="x3" range="20" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="d" name="Hammer, gnome hooked" cost="20" category="Exotic Weapons-Melee" size="Medium" damage="1d6/1d4" critical="x3/x4" range="0" weight="6" type="B&P" > <description >for proper treatment, read PH pg 101</description > </weapon> <weapon mod="0" fn="t" name="Hammer, light" cost="1" category="Martial Weapons-Melee" size="Small" damage="1d4" critical="x2" range="20" weight="2" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Handaxe" cost="6" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="x3" range="0" weight="5" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Javelin" cost="1" category="Simple Weapons-Ranged" size="Medium" damage="1d6" critical="x2" range="30" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Kama" cost="2" category="Exotic Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="2" type="S" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Kama, halfling" cost="2" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="1" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Katana" cost="400" category="Exotic Weapons-Melee" size="Large" damage="1d10" critical="19-20/x2" range="0" weight="6" type="S" > <description >Always masterwork</description > </weapon> <weapon mod="0" fn="" name="Katana, used 2 handed" cost="400" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="19-20/x2" range="0" weight="6" type="S" > <description >Always masterwork</description > </weapon> <weapon mod="0" fn="" name="Kukri" cost="8" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="18-29/x2" range="0" weight="3" type="S" > <description ></description > </weapon> <weapon mod="0" fn="oTR" name="Kusari-gama" cost="10" category="Asian Weapons-Melee" size="Medium" damage="1d6" critical="x2" range="0" weight="3" type="S" > <description >like a spiked chain</description > </weapon> <weapon mod="0" fn="Cr" name="Lance, heavy" cost="10" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x3" range="0" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" fn="C" name="Lance, light" cost="6" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="x3" range="0" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Laser pistol" cost="-1" category="Futuristic Weapons-Ranged" size="Small" damage="2d10" critical="x2" range="100" weight="2" type="Special" > <description ></description > </weapon> <weapon mod="0" fn="" name="Laser rifle" cost="-1" category="Futuristic Weapons-Ranged" size="Medium" damage="3d20" critical="x2" range="200" weight="7" type="Special" > <description ></description > </weapon> <weapon mod="0" fn="b" name="Longbow" cost="75" category="Martial Weapons-Ranged" size="Large" damage="1d8" critical="x3" range="100" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="b" name="Longbow, composite" cost="100" category="Martial Weapons-Ranged" size="Large" damage="1d8" critical="x3" range="110" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="rc" name="Longspear" cost="5" category="Martial Weapons-Melee" size="Large" damage="1d8" critical="x3" range="0" weight="9" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Longsword" cost="15" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="19-20/x2" range="0" weight="4" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Mace, heavy" cost="12" category="Simple Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="0" weight="12" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Mace, light" cost="5" category="Simple Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="6" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Morningstar" cost="8" category="Simple Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="0" weight="8" type="B&P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Musket" cost="500" category="Renaissance Weapons-Ranged" size="Medium" damage="1d12" critical="x3" range="150" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Net" cost="20" category="Exotic Weapons-Ranged" size="Medium" damage="0" critical="0" range="10" weight="10" type="-" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Nunchaku" cost="2" category="Exotic Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="2" type="S" > <description > </description > </weapon> <weapon mod="0" fn="m" name="Nunchaku, halfling" cost="2" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="1" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Pick, heavy" cost="8" category="Martial Weapons-Melee" size="Medium" damage="1d6" critical="x4" range="0" weight="6" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Pick, light" cost="4" category="Martial Weapons-Melee" size="Small" damage="1d4" critical="x4" range="0" weight="4" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Pistol" cost="250" category="Renaissance Weapons-Ranged" size="Small" damage="1d10" critical="x3" range="50" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Pistol, automatic" cost="-1" category="Modern Weapons-Ranged" size="Small" damage="1d10" critical="x3" range="150" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Pistol, revolver" cost="-1" category="Modern Weapons-Ranged" size="Small" damage="1d10" critical="x3" range="100" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="d" name="Quarterstaff" cost="0" category="Simple Weapons-Melee" size="Large" damage="1d6" critical="x2" range="0" weight="4" type="B" > <description ></description > </weapon> <weapon mod="0" fn="ro" name="Ranseur" cost="10" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="x3" range="0" weight="15" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Rapier" cost="20" category="Martial Weapons-Melee" size="Medium" damage="1d6" critical="18-20/x2" range="0" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Rifle, automatic" cost="-1" category="Modern Weapons-Ranged" size="Medium" damage="1d12" critical="x3" range="250" weight="12" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Rifle, repeater" cost="-1" category="Modern Weapons-Ranged" size="Medium" damage="1d12" critical="x3" range="200" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" fn="s" name="Sap" cost="1" category="Martial Weapons-Melee" size="Small" damage="1d6s" critical="x2" range="0" weight="3" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Scattergun" cost="-1" category="Modern Weapons-Ranged" size="Medium" damage="*" critical="*" range="10" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Scimitar" cost="15" category="Martial Weapons-Melee" size="Medium" damage="1d6" critical="18-20/x2" range="0" weight="4" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Scythe" cost="18" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="x4" range="0" weight="12" type="P&S" > <description ></description > </weapon> <weapon mod="0" fn="b" name="Shortbow" cost="30" category="Martial Weapons-Ranged" size="Medium" damage="1d6" critical="x3" range="60" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" fn="b" name="Shortbow, composite" cost="75" category="Martial Weapons-Ranged" size="Medium" damage="1d6" critical="x3" range="70" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" fn="tc" name="Shortspear" cost="2" category="Simple Weapons-Melee" size="Large" damage="1d8" critical="x3" range="20" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Shuriken" cost="1" category="Exotic Weapons-Ranged" size="Tiny" damage="1" critical="x2" range="100" weight="0" type="P" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Siangham" cost="3" category="Exotic Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="m" name="Siangham, halfling" cost="2" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="1" type="P" > <description ></description > </weapon> <weapon mod="0" fn="" name="Sickle" cost="6" category="Simple Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="3" type="S" > <description ></description > </weapon> <weapon mod="0" fn="b" name="Sling" cost="1d4" category="Simple Weapons-Ranged" size="Small" damage="1d4" critical="x2" range="50" weight="0" type="B" > <description ></description > </weapon> <weapon mod="0" fn="s" name="Strike, unarmed (med)" cost="0" category="Simple Weapons-Melee" size="Unarmed" damage="1d3" critical="x2" range="0" weight="0" type="B" > <description ></description > </weapon> <weapon mod="0" fn="s" name="Strike, unarmed (small)" cost="0" category="Simple Weapons-Melee" size="Unarmed" damage="1d2" critical="x2" range="0" weight="0" type="B" > <description ></description > </weapon> <weapon mod="0" fn="" name="Sword, bastard" cost="35" category="Exotic Weapons-Melee" size="Medium" damage="1d10" critical="19-20/x2" range="0" weight="10" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Sword, bastard used 2 handed" cost="35" category="Martial Weapons-Melee" size="Medium" damage="1d10" critical="19-20/x2" range="0" weight="10" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Sword, short" cost="10" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="19-20/x2" range="0" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" fn="d" name="Sword, two-bladed" cost="100" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d8" critical="19-20/X2" range="0" weight="30" type="S" > <description ></description > </weapon> <weapon mod="0" fn="ct" name="Trident" cost="15" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="10" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" fn="dc" name="Urgrosh, dwarven" cost="50" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d6" critical="x3" range="0" weight="15" type="S&P" > <description >see PH pg 103 for proper treatment </description > </weapon> <weapon mod="0" fn="m" name="UnArmed(Monk Med)" cost="0" category="Exotic Weapons-Melee" size="Unarmed" damage="Monk Med" critical="x2" range="0" weight="00" type="S&P" > <description >An unArmed strike does more dmg as a monk</description > </weapon> <weapon mod="0" fn="m" name="UnArmed(Monk Small)" cost="0" category="Exotic Weapons-Melee" size="Unarmed" damage="Monk Small" critical="x2" range="0" weight="00" type="S&P" > <description >An unArmed strike does more dmg as a monk</description > </weapon> <weapon mod="0" fn="" name="Wakizashi" cost="300" category="Asian Weapons-Melee" size="Small" damage="1d6" critical="19-20/x2" range="0" weight="3" type="S" > <description >Always masterwork</description > </weapon> <weapon mod="0" fn="" name="Waraxe, dwarven" cost="30" category="Exotic Weapons-Melee" size="Medium" damage="1d10" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Waraxe, dwarven used 2handed" cost="30" category="Martial Weapons-Melee" size="Medium" damage="1d10" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" fn="" name="Warhammer" cost="12" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x3" range="0" weight="8" type="B" > <description ></description > </weapon> <weapon mod="0" fn="oT" name="Whip" cost="1" category="Exotic Weapons-Ranged" size="Small" damage="1d2s" critical="x2" range="15" weight="2" type="S" > <description >range15, maxrange15, no penalties to max range; ineffective Vs armor or +3 natural adjustment PH pg 104 </description > </weapon> </weapons> --- NEW FILE: dnd3echaracter.xml --- <nodehandler class="d20char_handler" icon="knight" module="d20" name="D20 Character Tool"> <howtouse> <howto> To use this you do: To use this you just need to add all the stuff you wish, set your stats ect, then click on the +(plus) next too the gears, and right click. This will cause it to roll what is needed to roll, except dmg on spells, you will still need to roll that normaly, I will work on a way to add it, but for now it isnt required. </howto> </howtouse> <general> <name>Player Name</name> <player>Your Name</player> <race>none</race> <alignment abbr="LG">none</alignment> <deity>none</deity> <size acmodifier="0">none</size> <height>none</height> <weight>none</weight> <age>none</age> <gender>none</gender> <eyes>none</eyes> <hair>none</hair> <speed>30</speed> </general> <classes level="0"/> <abilities> <stat abbr="Str" base="0" name="Strength"/> <stat abbr="Dex" base="0" name="Dexterity"/> <stat abbr="Con" base="0" name="Constitution"/> <stat abbr="Int" base="0" name="Intelligence"/> <stat abbr="Wis" base="0" name="Wisdom"/> <stat abbr="Cha" base="0" name="Charisma"/> </abilities> <inventory> <plat>0</plat> <gold>0</gold> <silver>0</silver> <copper>0</copper> <generalgear>None</generalgear> <magicalgear>None</magicalgear> </inventory> <saves> <save base="0" magmod="0" miscmod="0" name="Fortitude" stat="Con"/> <save base="0" magmod="0" miscmod="0" name="Reflex" stat="Dex"/> <save base="0" magmod="0" miscmod="0" name="Will" stat="Wis"/> </saves> <hp current="0" max="0"/> <pp current1="0" free="0" max1="0" maxfree="0"/> <skills> <skill armorcheck="0" crossclass="0" misc="0" name="Alchemy" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Animal Empathy" rank="0" stat="Cha" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Appraise" rank="0" stat="Int" untrained="1"/> <skill armorcheck="1" crossclass="0" misc="0" name="Balance" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Bluff" rank="0" stat="Cha" untrained="1"/> <skill armorcheck="1" crossclass="1" misc="0" name="Climb" rank="0" stat="Str" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Concetration" rank="0" stat="Con" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Craft" rank="0" stat="Int" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Decipher Script" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Diplomacy" rank="0" stat="Cha" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Disable Device" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Disguise" rank="0" stat="Cha" untrained="1"/> <skill armorcheck="1" crossclass="0" misc="0" name="Escape Artist" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Forgery" rank="0" stat="Int" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Gather Information" rank="0" stat="Cha" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Handle Animal" rank="0" stat="Cha" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Heal" rank="0" stat="Wis" untrained="1"/> <skill armorcheck="1" crossclass="0" misc="0" name="Hide" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Innuendo" rank="0" stat="Wis" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Intimidate" rank="0" stat="Cha" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Intuit Direction" rank="0" stat="Wis" untrained="0"/> <skill armorcheck="1" crossclass="0" misc="0" name="Jump" rank="0" stat="Str" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Arcana" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Architecture and Engineering" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Geography" rank="0" stat="Int" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: History" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Local" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Nature" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Nobility and Royalty" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: The Planes" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Knowledge: Religion" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Listen" rank="0" stat="Wis" untrained="1"/> <skill armorcheck="1" crossclass="0" misc="0" name="Move Silently" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Open Lock" rank="0" stat="Dex" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Perform" rank="0" stat="Cha" untrained="1"/> <skill armorcheck="1" crossclass="0" misc="0" name="Pick Pocket" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Profession" rank="0" stat="Wis" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Read Lips" rank="0" stat="Int" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Ride" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Scry" rank="0" stat="Int" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Search" rank="0" stat="Int" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Sense Motive" rank="0" stat="Wis" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Spellcraft" rank="0" stat="Int" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Spot" rank="0" stat="Wis" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Swim" rank="0" stat="Str" untrained="1"/> <skill armorcheck="1" crossclass="0" misc="0" name="Tumble" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Use Magic Device" rank="0" stat="Cha" untrained="0"/> <skill armorcheck="0" crossclass="0" misc="0" name="Use Rope" rank="0" stat="Dex" untrained="1"/> <skill armorcheck="0" crossclass="0" misc="0" name="Wilderness Lore" rank="0" stat="Wis" untrained="1"/> </skills> <feats/> <spells/> <powers/> <divine/> <attacks> <melee base="0" misc="0"/> <ranged base="0" misc="0"/> </attacks> <ac misc="" natural=""/> </nodehandler> --- NEW FILE: dnd3efeats.xml --- <feats> <feat name="A Thousand Furs" type="Special" desc="Touched by the Gods page 71"/> <feat name="Acrobatic" type="General" desc="Song and Silence page 38"/> <feat name="Acrobatic (Legends and Lairs version)" type="General" desc="Traps and Treachery page 34"/> <feat name="Airy Gallop" type="Special" desc="Dragon Magazine d20 Special-Annual 6 page 65"/> <feat name="Alertness" type="General" desc="Players Handbook page 80"/> <feat name="Alluring" type="General" desc="Song and Silence page 38"/> <feat name="Ambidexterity" type="General" desc="Players Handbook page 80"/> <feat name="Ancient Lineage" type="General" desc="The Taan page 81"/> <feat name="Arcane Defense" type="General" desc="Tome and Blood page 38"/> <feat name="Arcane Preparation" type="General" desc="Tome and Blood page 38"/> <feat name="Arcane Schooling" type="General" desc="Forgotten Realms Campaign Setting page 33"/> <feat name="Armor Proficiency (Heavy)" type="General" desc="Players Handbook page80"/> <feat name="Armor Proficiency (Light)" type="General" desc="Players Handbook page 80"/> <feat name="Armor Proficiency (Medium)" type="General" desc="Players Handbook page 80"/> <feat name="Art of Fascination" type="Ancestor" desc="Oriental Adventures page 60"/> <feat name="Arterial Strike" type="General" desc="Song and Silence page 38"/> <feat name="Artist" type="General" desc="Forgotten Realms Campaign Setting page 33"/> <feat name="Artist" type="Ancestor" desc="Oriental Adventures page 61"/> <feat name="Athletic" type="General" desc="Song and Silence page 38"/> <feat name="Attention to Detail" type="Ancestor" desc="Oriental Adventures page 61"/> <feat name="Attune Gem" type="Item Creation" desc="Magic of Faerun page 21"/> <feat name="Augment Construct" type="Psionic" desc="Dragon Magazine 287 page 54"/> <feat name="Augment Summoning" type="General" desc="Tome and Blood page 39"/> <feat name="Aura of Serenity" type="Mystic Warrior" desc="Mystic Warriors page 112"/> <feat name="Battle Howl" type="General" desc="Touched by the Gods page 109"/> <feat name="Battle Roar" type="Kaiju" desc="Dragon Magazine 289 page 70"/> <feat name="Blind Casting" type="General" desc="Dungeons page 81"/> <feat name="Blind-Fight" type="General" desc="Players Handbook page 80"/> <feat name="Blindsight 5-foot Radius" type="General" desc="Sword and Fist page 5"/> <feat name="Blood Frenzy" type="General" desc="The Taan page 81"/> <feat name="Blood Sorcerer" type="Ancestor" desc="Oriental Adventures page 61"/> <feat name="Blooded" type="General" desc="Forgotten Realms Campaign Setting page 33"/> <feat name="Bloodline of Fire" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Body Fuel" type="Psionic" desc="Psionics Handbook page 24"/> <feat name="Bootlicker" type="General" desc="Evil page 58"/> <feat name="Born Duelist" type="Ancestor" desc="Oriental Adventures page 61"/> <feat name="Breeze Dance" type="Fighting Stance" desc="Mystic Warriors page 112"/> <feat name="Brew Poison" type="Item Creation" desc="Traps and Treachery page 45"/> <feat name="Brew Potion" type="Item Creation" desc="Players Handbook page 80"/> <feat name="Bribery" type="General" desc="Evil page 58"/> <feat name="Bullheaded" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Cabalistic Spellcasting" type="Metamagic" desc="Sovereign Stone Campaign Sourcebook page 60"/> <feat name="Casing Sense" type="General" desc="Traps and Treachery page 34"/> <feat name="Chain Power" type="Metapsionic" desc="Dragon Magazine 287 page 54"/> <feat name="Chain Spell" type="Metamagic" desc="Tome and Blood page 39"/> <feat name="Chain Spell (Scarred Lands version)" type="Metamagic" desc="Relics and Rituals page 25"/> <feat name="Change Instruction" type="Special" desc="Demonology page 41"/> <feat name="Chariot Archery" type="General" desc="Sword and Fist page 78"/> <feat name="Chariot Charge" type="General" desc="Sword and Fist page 79"/> <feat name="Chariot Combat" type="General" desc="Sword and Fist page 78"/> <feat name="Chariot Sideswipe" type="General" desc="Sword and Fist page 79"/> <feat name="Chariot Trample" type="General" desc="Sword and Fist page 78"/> <feat name="Charlatan" type="General" desc="Song and Silence page 38"/> <feat name="Chink in the Armor" type="General" desc="Song and Silence page 38"/> <feat name="Choke Hold" type="General" desc="Oriental Adventures page 61"/> <feat name="Circle Kick" type="General" desc="Sword and Fist page 5"/> <feat name="Claws/Fangs" type="Infernal" desc="Evil page 24"/> <feat name="Cleave" type="General" desc="Players Handbook page 80"/> <feat name="Close-Order Fighting" type="Special" desc="Dragon Lords of Melnibone page 63"/> <feat name="Close-Quarters Fighting" type="General" desc="Sword and Fist page 5"/> <feat name="Cloud Running" type="Special" desc="Creature Collection page 72"/> <feat name="Combat Agility" type="General" desc="Dragon Magazine 284 page 123"/> <feat name="Combat Casting" type="General" desc="Players Handbook page 80"/> <feat name="Combat Manifestation" type="Psionic" desc="Psionics Handbook page 24"/> <feat name="Combat Reflexes" type="General" desc="Players Handbook page 80"/> <feat name="Combat Sense" type="General" desc="The Taan page 81"/> <feat name="Conjure Mastery" type="Eldritch" desc="The Book of Eldritch Might page 4"/> <feat name="Construct Familiar" type="General" desc="Dragon Magazine 280 page 62"/> <feat name="Controlled Breathing" type="General" desc="Dungeons page 81"/> <feat name="Cool Head" type="General" desc="Oriental Adventures page 61"/> <feat name="Cooperative Spell" type="Metamagic" desc="Tome and Blood page 39"/> <feat name="Cooperative Spellcasting" type="Metamagic" desc="Sovereign Stone Campaign Sourcebook page 60"/> <feat name="Cosmopolitan" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Courteous Magocracy" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Crab Walk" type="Fighting Stance" desc="Mystic Warriors page 112"/> <feat name="Craft Anaema Tool" type="Item Creation" desc="Mythic Races page 15"/> <feat name="Craft Crystal Capacitor" type="Item Creation" desc="Psionics Handbook page 24"/> <feat name="Craft Crystal Weapon" type="Item Creation" desc="Oriental Adventures page 61"/> <feat name="Craft Dorje" type="Item Creation" desc="Psionics Handbook page 24"/> <feat name="Craft Kirpan" type="Item Creation" desc="Mystic Warriors page 112"/> <feat name="Craft Magic Arms and Armor" type="Item Creation" desc="Players Handbook page 81"/> <feat name="Craft Magic Trap" type="Item Creation" desc="Traps and Treachery page 34"/> <feat name="Craft Mystic Talisman" type="Item Creation" desc="Mystic Warriors page 112"/> <feat name="Craft Named Weapon" type="Item Creation" desc="Mystic Warriors page 112"/> <feat name="Craft Psionic Arms and Armor" type="Item Creation" desc="Psionics Handbook page 24"/> <feat name="Craft Rod" type="Item Creation" desc="Players Handbook page 81"/> <feat name="Craft Staff" type="Item Creation" desc="Players Handbook page 81"/> <feat name="Craft Talisman" type="Item Creation" desc="Oriental Adventures page 61"/> <feat name="Craft Universal Item" type="Item Creation" desc="Psionics Handbook page 24"/> <feat name="Craft Vitus Amulet" type="Item Creation" desc="Mystic Warriors page 113"/> <feat name="Craft Wand" type="Item Creation" desc="Players Handbook page 81"/> <feat name="Craft Wondrous Item" type="Item Creation" desc="Players Handbook page 81"/> <feat name="Create Graft" type="Item Creation" desc="Touched by the Gods page 26"/> <feat name="Create Portal" type="Item Creation" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Dance of the Dirk" type="Fighting Stance" desc="Mystic Warriors page 113"/> <feat name="Darkvision" type="Infernal" desc="Evil page 24"/> <feat name="Dash" type="General" desc="Song and Silence page 38"/> <feat name="Daylight Adaptation" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Dead Shot" type="General" desc="Sovereign Stone Campaign Sourcebook page 60"/> <feat name="Death Blow" type="General" desc="Sword and Fist page 6"/> <feat name="Deep Impact" type="Psionic" desc="Psionics Handbook page 25"/> <feat name="Defensive Strike" type="General" desc="Oriental Adventures page 62"/> <feat name="Defensive Throw" type="General" desc="Oriental Adventures page 62"/> <feat name="Deflect Arrows" type="General" desc="Players Handbook page 81"/> <feat name="Deflect Ranged Attack" type="General" desc="Dragon Magazine 274 page 60"/> <feat name="Delay Power" type="Metapsionic" desc="Psionics Handbook page 25"/> <feat name="Delay Spell" type="Metamagic" desc="Tome and Blood page 39"/> <feat name="Dirty Fighting" type="General" desc="Sword and Fist page 6"/> <feat name="Disarm Mind" type="Psionic" desc="Psionics Handbook page 25"/> <feat name="Discipline" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Discipline" type="Ancestor" desc="Oriental Adventures page 62"/> <feat name="Disguise Spell" type="Metamagic" desc="Song and Silence page 38"/> <feat name="Dismiss Demon" type="Special" desc="Demonlogy page 41"/> <feat name="Divine Cleansing" type="Divine" desc="Defenders of the Faith page 19"/> <feat name="Divine Might" type="Divine" desc="Defenders of the Faith page 19"/> <feat name="Divine Perception" type="General" desc="Touched by the Gods page 34"/> <feat name="Divine Resistance" type="Divine" desc="Defenders of the Faith page 19"/> <feat name="Divine Shield" type="Divine" desc="Defenders of the Faith page 19"/> <feat name="Divine Vengeance" type="Divine" desc="Defenders of the Faith page 20"/> <feat name="Divine Vigor" type="Divine" desc="Defenders of the Faith page 20"/> <feat name="Dodge" type="General" desc="Players Handbook page 81"/> <feat name="Dreamspeaking" type="General" desc="The Book of Eldritch Might page 4"/> <feat name="Drug Tolerance" type="General" desc="Caravan of Hope page 27"/> <feat name="Dual Strike" type="General" desc="Sword and Fist page 6"/> <feat name="Eagle Claw Attack" type="General" desc="Sword and Fist page 6"/> <feat name="Earths Embrace" type="General" desc="Oriental Adventures page 62"/> <feat name="Education" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Eidetic Memory" type="General" desc="Dungeons page 81"/> <feat name="Element Resistance" type="Infernal" desc="Evil page 24"/> <feat name="Empathy" type="General" desc="Traps and Treachery page 35"/> <feat name="Empower Spell" type="Metamagic" desc="Players Handbook page 82"/> <feat name="Empower Turning" type="Special" desc="Defenders of the Faith page 20"/> <feat name="Enchant Stone" type="Item Creation" desc="The Taan page 81"/> <feat name="Encode Stone" type="Item Creation" desc="Psionics Handbook page 25"/> <feat name="Endurance" type="General" desc="Players Handbook page 82"/> <feat name="Energy Admixture" type="Metamagic" desc="Tome and Blood page 39"/> <feat name="Energy of Life" type="Special" desc="Creature Collection page 72"/> <feat name="Energy Substitution" type="Metamagic" desc="Tome and Blood page 40"/> <feat name="Enlarge Power" type="Metapsionic" desc="Psionics Handbook page 25"/> <feat name="Enlarge Spell" type="Metamagic" desc="Players Handbook page 82"/> <feat name="Enspell Familiar" type="General" desc="Dragon Magazine 280 page 62"/> <feat name="Eschew Materials" type="Metamagic" desc="Tome and Blood page 40"/> <feat name="Etch Object Rune" type="Item Creation" desc="The Book of Eldritch Might page 4"/> <feat name="Ethran" type="General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Exotic Weapon Proficiency" type="General" desc="Players Handbook page 82"/> <feat name="Expert Tactician" type="General" desc="Song and Silence page 38"/> <feat name="Expertise" type="General" desc="Players Handbook page 82"/> <feat name="Extend Power" type="Metapsionic" desc="Psionics Handbook page 25"/> <feat name="Extend Spell" type="Metamagic" desc="Players Handbook page 82"/> <feat name="Extra Familiar" type="General" desc="Dragon Magazine 280 page 62"/> <feat name="Extra Music" type="General" desc="Song and Silence page 39"/> <feat name="Extra Power" type="Psionic" desc="Dragon Magazine 287 page 55"/> <feat name="Extra Slot" type="General" desc="Tome and Blood page 40"/> <feat name="Extra Smiting" type="Special" desc="Defenders of the Faith page 20"/> <feat name="Extra Spell" type="General" desc="Tome and Blood page 40"/> <feat name="Extra Stunning Attacks" type="General" desc="Sword and Fist page 6"/> <feat name="Extra Turning" type="Special" desc="Players Handbook page 82 (rules: ppage 32 42)"/> <feat name="Eye for Detail" type="General" desc="Traps and Treachery page 35"/> <feat name="Eyes in the Back of Your Head" type="General" desc="Sword and Fist page 6"/> <feat name="Eyes of Calaam" type="Special" desc="Touched by the Gods page 59"/> <feat name="Falling Star Strike" type="General" desc="Oriental Adventures page 62"/> <feat name="Far Shot" type="General" desc="Players Handbook page 82"/> <feat name="Fast Armor" type="General" desc="Dragon Magazine 284 page 123"/> <feat name="Fast Rider" type="General" desc="Dragon Magazine 285 page 98"/> <feat name="Fast Talker" type="General" desc="Traps and Treachery page 35"/> <feat name="Fearsome and Fearless" type="Ancestor" desc="Oriental Adventures page 62"/> <feat name="Feign Weakness" type="General" desc="Sword and Fist page 6"/> <feat name="Fell Shot" type="Psionic" desc="Psionics Handbook page 25"/> <feat name="Firearms Drill" type="General" desc="Dragon Magazine d20 Special/Annual 6 page 70"/> <feat name="Fists of Calaam" type="Special" desc="Touched by the Gods page 59"/> <feat name="Fists of Iron" type="General" desc="Sword and Fist page 6"/> <feat name="Fleet of Foot" type="General" desc="Song and Silence page 39"/> <feat name="Flick of the Wrist" type="General" desc="Song and Silence page 39"/> <feat name="Flight" type="Infernal" desc="Evil page 25"/> <feat name="Flying Kick" type="General] -Oriental Adventures page 62" desc=""/> <feat name="Foe Hunter" type="Fighter General" desc="Forgotten Realms Campaign Setting page 34"/> <feat name="Forester" type="General" desc="Forgotten Realms Campaign Setting page 35"/> <feat name="Forge Ring" type="Item Creation" desc="Players Handbook page 82"/> <feat name="Fortify Power" type="Metapsionic" desc="Dragon Magazine 287 page 55"/> <feat name="Freezing the Lifeblood" type="General" desc="Oriental Adventures page 62 "/> <feat name="Gifted General" type="Ancestor" desc="Oriental Adventures page 62"/> <feat name="Golden Tongue" type="General" desc="Dungeons page 81"/> <feat name="Grace Under Pressure" type="General" desc="Dungeons page 81"/> <feat name="Grappling Block" type="General" desc="Oriental Adventures page 63"/> <feat name="Grasshopper Strike" type="General" desc="Dragon Magazine 279 page 63"/> <feat name="Great Cleave" type="General" desc="Players Handbook page 82"/> <feat name="Great Crafter" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Great Diplomat" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Great Fortitude" type="General" desc="Players Handbook page 82"/> <feat name="Great Ki Shout" type="General" desc="Oriental Adventures page 63"/> <feat name="Great Stamina" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Great Sunder" type="Psionic" desc="Psionics Handbook page 26"/> <feat name="Great Teamwork" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Greater Power Penetration" type="Psionic" desc="Psionics Handbook page 26"/> <feat name="Greater Psionic Focus" type="Psionic" desc="Psionics Handbook page 26"/> <feat name="Greater Spell Focus" type="General" desc="Tome and Blood page 40"/> <feat name="Greater Spell Penetration" type="General" desc="Tome and Blood page 40"/> <feat name="Green Ear" type="General" desc="Song and Silence page 39"/> <feat name="Green Viper Style" type="Fighting Stance" desc="Mystic Warriors page 113"/> <feat name="Hammer Fist" type="General" desc="Dragon Magazine 279 page 63"/> <feat name="Hamstring" type="General" desc="Song and Silence page 39"/> <feat name="Heavy Scarring" type="General" desc="The Taan page 82"/> <feat name="Heighten Power" type="Metapsionic" desc="Psionics Handbook page 26"/> <feat name="Heighten Spell" type="Metamagic" desc="Players Handbook page 82"/> <feat name="Heighten Turning" type="Special" desc="Defenders of the Faith page 20"/> <feat name="Heroic Destiny" type="Special" desc="Touched by the Gods page 109"/> <feat name="Hide Power" type="Metapsionic" desc="Psionics Handbook page 26"/> <feat name="Hide Spell" type="Metamagic" desc="Relics and Rituals page 25"/> <feat name="Hill Fighter" type="General" desc="Dragon Magazine 285 page 98"/> <feat name="Hold the Line" type="General" desc="Sword and Fist page 7"/> <feat name="Honest Merchant" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Horse Nomad" type="Fighter General" desc="Forgotten Realms Campaign Setting page 35"/> <feat name="Iaijutsu Master" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Immortality" type="Infernal" desc="Evil page 25"/> <feat name="Immunity" type="Infernal" desc="Evil page 25"/> <feat name="Imp" type="Infernal" desc="Evil page 25"/> <feat name="Improved Aid" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Improved Alertness" type="General" desc="Dungeons page 82"/> <feat name="Improved Bull Rush" type="General" desc="Players Handbook page 82"/> <feat name="Improved Counterspell" type="General" desc="Forgotten Realms Campaign Setting page 35"/> <feat name="Improved Critical" type="General" desc="Players Handbook page 82"/> <feat name="Improved Disarm" type="General" desc="Players Handbook page 83"/> <feat name="Improved Endurance" type="General" desc="Dungeons page 82"/> <feat name="Improved Familiar" type="General" desc="Tome and Blood page 40"/> <feat name="Improved Feint" type="General" desc="Evil page 59"/> <feat name="Improved Flight" type="Infernal" desc="Evil page 25"/> <feat name="Improved Grab" type="General" desc="Mythic Races page 77"/> <feat name="Improved Grapple" type="General" desc="Oriental Adventures page 63"/> <feat name="Improved Initiative" type="General" desc="Players Handbook page 83"/> <feat name="Improved Knockout Attack" type="General" desc="Traps and Treachery page 35"/> <feat name="Improved Low Blow" type="General" desc="Dragon Magazine 285 page 33"/> <feat name="Improved Mounted Archery" type="General" desc="Dragon Magazine 285 page 99"/> <feat name="Improved Mounted Combat" type="General" desc="Sovereign Stone Campaign Sourcebook page 62"/> <feat name="Improved Multiweapon Fighting" type="General" desc="Mythic Races page 137"/> <feat name="Improved Overrun" type="General" desc="Sword and Fist page 7"/> <feat name="Improved Psicrystal" type="Psionic" desc="Psionics Handbook page 26"/> <feat name="Improved Ranged Sneak Attack" type="General" desc="Traps and Treachery page 36"/> <feat name="Improved Rapid Shot" type="General" desc="Dragon Magazine 275 page 41"/> <feat name="Improved Regeneration" type="Infernal" desc="Evil page 25"/> <feat name="Improved Shield Bash" type="General" desc="Defenders of the Faith page 20"/> <feat name="Improved Sneak Attack" type="General" desc="Traps and Treachery page 36"/> <feat name="Improved Sunder" type="General" desc="Sword and Fist page 7"/> <feat name="Improved Trample" type="Kaiju" desc="Dragon Magazine 289 page 70"/> <feat name="Improved Trip" type="General" desc="Players Handbook page 83"/> <feat name="Improved Two-Weapon Fighting" type="General" desc="Players Handbook page 83"/> <feat name="Improved Unarmed Strike" type="General" desc="Players Handbook page 83"/> <feat name="Improvise Thieves Tools" type="General" desc="Traps and Treachery page 37"/> <feat name="Improvised Weapon" type="General" desc="Sovereign Stone Campaign Sourcebook page 62"/> <feat name="Increased Carrying Capacity" type="General" desc="Dungeons page 82"/> <feat name="Increased Movement" type="Infernal" desc="Evil page 26"/> <feat name="Inertial Armor" type="Psionic" desc="Psionics Handbook page 26"/> <feat name="Infernal Pact" type="Infernal" desc="Evil page 26"/> <feat name="Infernal Soul" type="Infernal" desc="Evil page 26"/> <feat name="Information Exchange" type="Special" desc="Touched by the Gods page 7"/> <feat name="Innate Spell" type="General" desc="Tome and Blood page 41"/> <feat name="Inner Peace" type="Mystic Warrior" desc="Mystic Warriors page 113"/> <feat name="Inner Strength" type="Psionic" desc="Psionics Handbook page 26"/> <feat name="Inscribe Magical Tattoo" type="Item Creation" desc="Relics and Rituals page 198"/> <feat name="Inscribe Rune" type="Item Creation" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Insidious Magic" type="Metamagic" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Invisibility" type="Infernal" desc="Evil page 27"/> <feat name="Ironbone" type="Special" desc="Creature Collection page 71"/> <feat name="Ironskin" type="Special" desc="Creature Collection page 72"/> <feat name="Iron Will" type="General" desc="Players Handbook page 83"/> <feat name="Item Image" type="Eldritch" desc="The Book of Eldritch Might page 4"/> <feat name="Jack of All Trades" type="General" desc="Song and Silence page 40"/> <feat name="Kamis Intuition" type="Ancestor" desc="Oriental Adventures page 63"/> <feat name="Karmic Strike" type="General" desc="Oriental Adventures page 63"/> <feat name="Karmic Twin" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Keen Intellect" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Keen Vision" type="General" desc="Traps and Treachery page 37"/> <feat name="Ki Projection" type="Special" desc="Creature Collection page 72"/> <feat name="Ki Shout" type="General" desc="Oriental Adventures page 64"/> <feat name="Knock-Down" type="General" desc="Sword and Fist page 7"/> <feat name="Knockout Attack" type="General" desc="Traps and Treachery page 37"/> <feat name="Knowledgeable" type="General" desc="Dungeons page 82"/> <feat name="Lace Spell: Elemental Energies" type="Eldritch" desc="The Book of Eldritch Might page 5"/> <feat name="Lace Spell: Enemy Bane" type="Eldritch" desc="The Book of Eldritch Might page 5"/> <feat name="Lace Spell: Holy/Unholy" type="Eldritch" desc="The Book of Eldritch Might page 5"/> <feat name="Lace Spell: Lawful/Chaotic" type="Eldritch" desc="The Book of Eldritch Might page 5"/> <feat name="Lead Missile Fire" type="General" desc="Evil page 59"/> <feat name="Leadership" type="General" desc="Players Handbook page 83 (rules: Dungeon Masters Guide page 45)"/> <feat name="Lightning Fists" type="General" desc="Sword and Fist page 7"/> <feat name="Lightning Reflexes" type="General" desc="Players Handbook page 83"/> <feat name="Light Sleeper" type="General" desc="Dungeons page 82"/> <feat name="Lingering Song" type="General" desc="Song and Silence page 40"/> <feat name="Lion Spy" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Lions Rage" type="Mystic Warrior Stance" desc="Mystic Warriors page 113"/> <feat name="Living Shield" type="General" desc="Evil page 58"/> <feat name="Low Blow" type="General" desc="Dragon Magazine 285 page 33"/> <feat name="Luck of Heroes" type="General" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Luck of Heroes" type="Ancestor" desc="Oriental Adventures page 64 "/> <feat name="Magekiss" type="Metamagic" desc="Traps and Treachery page 22"/> <feat name="Magic in the Blood" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Magic Item" type="Infernal" desc="Evil page 27"/> <feat name="Magical Artisan" type="General" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Magical Artisan" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Magical Talent" type="General" desc="The Book of Eldritch Might page 6"/> <feat name="Magical Training" type="General" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Magistrates Mind" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Mantis Leap" type="General" desc="Sword and Fist page 7"/> <feat name="Manufacture Magic Poison" type="Item Creation" desc="The Book of Eldritch Might page 6"/> <feat name="Many Masks" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Martial Weapon Proficiency" type="General" desc="Players Handbook page 83"/> <feat name="Master Dorje" type="Metapsionic" desc="Psionics Handbook page 26"/> <feat name="Maximize Power" type="Metapsionic" desc="Psionics Handbook page 26"/> <feat name="Maximize Spell" type="Metamagic" desc="Players Handbook page 83"/> <feat name="Mechanical Aptitude" type="General" desc="Traps and Treachery page 37"/> <feat name="Mental Adversary" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Mental Leap" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Mercantile Background" type="General" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Metacreative" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Militia" type="General" desc="Forgotten Realms Campaign Setting page 36"/> <feat name="Mind Blind" type="Psionic" desc="Dragon Magazine 287 page 55"/> <feat name="Mind Over Body" type="General" desc="Forgotten Realms Campaign Setting page 37"/> <feat name="Mind Trap" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Mirror Sight" type="Eldritch" desc="The Book of Eldritch Might page 6"/> <feat name="Mobility" type="General" desc="Players Handbook page 83"/> <feat name="Monkey Grip" type="General" desc="Sword and Fist page 7"/> <feat name="Mounted Archery" type="General" desc="Players Handbook page 83"/> <feat name="Mounted Combat" type="General" desc="Players Handbook page 83"/> <feat name="Moving Meditation" type="Special" desc="Creature Collection page 72"/> <feat name="Multiattack (Taan version)" type="General" desc="The Taan page 83"/> <feat name="Multicultural" type="General" desc="Song and Silence page 40"/> <feat name="Multiple Limbs" type="Infernal" desc="Evil page 27"/> <feat name="Multiweapon Fighting (Legends and Lairs version)" type="General" desc="Mythic Races page 137"/> <feat name="Natural Weaponry" type="General" desc="The Taan page 83"/> <feat name="Nature Sense" type="Special" desc="Touched by the Gods page 71"/> <feat name="Nerve Strikes" type="Special" desc="Creature Collection page 72"/> <feat name="Nobodys Fool" type="General" desc="Dragon Magazine 285 page 33"/> <feat name="Obscure Lore" type="General" desc="Song and Silence page 40"/> <feat name="Off-Hand Parry" type="General" desc="Sword and Fist page 7"/> <feat name="Off-Handed" type="General" desc="Evil page 59"/> <feat name="Onis Bane" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Pain Touch" type="General" desc="Sword and Fist page 8"/> <feat name="Pebble Underfoot" type="General" desc="Dragon Magazine 279 page 63"/> <feat name="Penetrate Hardness" type="Kaiju" desc="Dragon Magazine 289 page 70"/> <feat name="Perfect Memory" type="General" desc="Traps and Treachery page 37"/> <feat name="Permanent Control" type="Special" desc="Demonology page 41"/> <feat name="Pernicious Magic" type="Metamagic" desc="Forgotten Realms Campaign Setting page 37"/> <feat name="Persistent Power" type="Metapsionic" desc="Psionics Handbook page 27"/> <feat name="Persistent Spell" type="Metamagic" desc="Tome and Blood page 42"/> <feat name="Persuasive" type="General" desc="Song and Silence page 40"/> <feat name="Pin Shield" type="General" desc="Sword and Fist page 8"/> <feat name="Pinpoint Accuracy" type="General" desc="Sovereign Stone Campaign Sourcebook page 62"/> <feat name="Point Blank Shot" type="General" desc="Players Handbook page 84"/> <feat name="Poison Blood" type="Infernal" desc="Evil page 28"/> <feat name="Poison Immunity" type="General" desc="Traps and Treachery page 37"/> <feat name="Pounce" type="General" desc="Mythic Races page 77"/> <feat name="Pounce and Strike" type="General" desc="Dragon Lords of Melnibone page 50"/> <feat name="Power Attack" type="General" desc="Players Handbook page 84"/> <feat name="Power Attack--Iaijutsu" type="Ancestor" desc="Oriental Adventures page 64"/> <feat name="Power Attack--Shadowlands" type="Ancestor" desc="Oriental Adventures page 65"/> <feat name="Power Lunge" type="General" desc="Sword and Fist page 8"/> <feat name="Power Penetration" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Power Specialization" type="Psionic" desc="Dragon Magazine 287 page 56"/> <feat name="Power Touch" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Powerful Voice" type="Ancestor" desc="Oriental Adventures page 65"/> <feat name="Precise Shot" type="General" desc="Players Handbook page 84"/> <feat name="Primal Shout" type="General" desc="The Taan page 83"/> <feat name="Prone Attack" type="General" desc="Sword and Fist page 8"/> <feat name="Psionic Body" type="Psionic" desc="Psionics Handbook page 27"/> <feat name="Psionic Charge" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psionic Defense" type="Psionic" desc="Dragon Magazine 287 page 54"/> <feat name="Psionic Dodge" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psionic Energy Admixture" type="Metapsionic" desc="Dragon Magazine 287 page 55"/> <feat name="Psionic Energy Substitution" type="Metapsionic" desc="Dragon Magazine 287 page 54"/> <feat name="Psionic Fist" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psionic Focus" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psionic Metabolism" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psionic Shot" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psionic Weapon" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psychic Bastion" type="Psionic" desc="Psionics Handbook page 28"/> <feat name="Psychic Inquisitor" type="Psionic" desc="Psionics Handbook page 29"/> <feat name="Psychoanalyst" type="Psionic" desc="Psionics Handbook page 29"/> <feat name="Purifying Light" type="General" desc="Mythic Races page 69"/> <feat name="Pyro" type="General" desc="Song and Silence page 40"/> <feat name="Quick Draw" type="General" desc="Players Handbook page 84"/> <feat name="Quicken Power" type="Metapsionic" desc="Psionics Handbook page 29"/> <feat name="Quicken Spell" type="Metamagic" desc="Players Handbook page 84"/> <feat name="Quicken Summoning" type="Special" desc="Demonology page 41"/> <feat name="Quicken Turning" type="Special" desc="Defenders of the Faith page 20"/> <feat name="Quicker Than the Eye" type="General" desc="Song and Silence page 40"/> <feat name="Quickstrike" type="General" desc="Traps and Treachery page 37"/> <feat name="Rake" type="General" desc="Mythic Races page 77"/> <feat name="Raking Nails" type="General" desc="Mystic... [truncated message content] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:24
|
Update of /cvsroot/winopenrpg/openrpg1/orpg/dirpath In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg/dirpath Added Files: __init__.py dirpath_tools.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: dirpath_tools.py --- import sys import os from orpg.orpg_wx import * class tmpApp(wxApp): def OnInit(self): return true #------------------------------------------------------- # void load_paths( dir_struct_reference ) # moved structure loading from dirpath.py by Snowdog 3-8-05 #------------------------------------------------------- def load_paths(dir_struct, root_dir): dir_struct["home"] = root_dir + os.sep dir_struct["core"] = dir_struct["home"] + "orpg"+ os.sep dir_struct["data"] = dir_struct["home"]+ "data"+ os.sep dir_struct["d20"] = dir_struct["data"]+"d20"+ os.sep dir_struct["dnd3e"] = dir_struct["data"]+"dnd3e"+ os.sep dir_struct["SWd20"] = dir_struct["data"]+"SWd20"+ os.sep dir_struct["icon"] = dir_struct["home"]+"images"+ os.sep dir_struct["template"] = dir_struct["core"]+"templates"+ os.sep dir_struct["user"] = dir_struct["home"]+ "myfiles"+ os.sep dir_struct["plugins"] = dir_struct["home"]+ "plugins"+ os.sep dir_struct["nodes"] = dir_struct["template"]+"nodes"+ os.sep dir_struct["logs"] = dir_struct["user"]+"logs"+ os.sep # backward compatiablity dir_struct["addon"] = dir_struct["template"]+"nodes"+ os.sep #------------------------------------------------------- # int verify_home_path( directory_name ) # added by Snowdog 3-8-05 # updated with bailout code. Snowdog 7-25-05 #------------------------------------------------------- def verify_home_path( path ): """checks for key ORPG files in the openrpg tree and askes for user intervention if their is a problem""" try: #verify that the root dir (as supplied) exists if not verify_file(path): return 0 #These checks require that 'path' have a separator at the end. #Check and temporarily add one if needed if (path[(len(path)-len(os.sep)):] != os.sep): path=path+os.sep # These files should always exist at the root orpg dir check_files=["start.py","platform.py","pyver.py"] for n in range(len(check_files)): if not verify_file(path+check_files[n]): return 0 # These directories should always exist at the root orpg dir check_dirs=["orpg","data","images"] for n in range(len(check_dirs)): if not verify_file(path+check_dirs[n]): return 0 except: # an error occured while verifying the directory structure # bail out with error signal return 0 #all files and directories exist. write_approot(path) return 1 #------------------------------------------------------- # int verify_file( absolute_path ) # added by Snowdog 3-8-05 #------------------------------------------------------- def verify_file(abs_path): """Returns true if file or directory exists""" try: os.stat(abs_path) return 1 except OSError: #this exception signifies the file or dir doesn't exist return 0 #------------------------------------------------------- # pathname get_user_help() # added by Snowdog 3-8-05 # bug fix (SF #1242456) and updated with bailout code. Snowdog 7-25-05 #------------------------------------------------------- def get_user_located_root(): """Notify the user of directory problems and show directory selection dialog """ app = tmpApp(0) app.MainLoop() dir = None try: msg = "OpenRPG cannot locate critical files.\nPlease locate the openrpg1 directory in the following window" alert= wxMessageDialog(None,msg,"Warning",wxOK|wxICON_ERROR) alert.Show() if alert.ShowModal() == wxOK: alert.Destroy() dlg = wxDirDialog(None, "Locate the openrpg1 directory:",style=wxDD_DEFAULT_STYLE) if dlg.ShowModal() == wxID_OK: dir = dlg.GetPath() dlg.Destroy() app.Destroy() return dir except Exception, e: print e print "OpenRPG encountered a problem while attempting to load file dialog to locate the OpenRPG root directory." print "please delete the files ./openrpg/orpg/dirpath/aproot.py and ./openrpg/orpg/dirpath/aproot.pyc and try again." #------------------------------------------------------- # void write_approot( orpg_root_path ) # added by snowdog 3-10-05 #------------------------------------------------------- def write_approot( orpg_root_path): try: #if a trailing path separator is on the path string remove it. if (orpg_root_path[(len(orpg_root_path)-len(os.sep)):] == os.sep): orpg_root_path = orpg_root_path[:(len(orpg_root_path)-len(os.sep))] fn = orpg_root_path+os.sep+"orpg"+os.sep+"dirpath"+os.sep+"approot.py" f = open( fn, "w") #trim off the appended os.sep character(s) to avoid duplicating them on re-loading of path code="basedir = \""+orpg_root_path+"\"\n" #fix path string for windows boxes that lamely use an escape character for a path separator code = str.replace(code,'\\','\\\\') f.write(code) f.close() except IOError: print "[WARNING] Could not create approot file." print "[WARNING] Automatic directory resolution not configured." return --- NEW FILE: __init__.py --- # Old dirpath.py replaced with new dirpath 'package/module' to allow dynamic # checking on directory structure at dirpath import without requiring alteration # of almost every openrpg1 python file # # This module is functionally identical to the dirpath.py file it replaces. # All directory locations are now handled by the load_paths() function # in the dirpath_tools.py file -- Snowdog 3-8-05 # CHANGE LOG # ----------------------------- # * Reworked path verification process to attempt to fall back on the # current working directory if approot fails to verify before # asking the user to locate the root directory -- Snowdog 12-20-05 import sys import os from dirpath_tools import * root_dir = None try: import approot root_dir = approot.basedir except: #attempt to load default path root_dir = os.getcwd() #default ORPG root dir dir_struct = {} if not verify_home_path(root_dir): root_dir = os.getcwd() if not verify_home_path(root_dir): root_dir = get_user_located_root() while not verify_home_path(root_dir): root_dir = get_user_located_root() #switch backslashes to forward slashes just for display on screen only (avoids issues with escaped characters) clean = str(root_dir) clean = str.replace(clean,'\\','/') print "Rooting OpenRPG at: "+clean load_paths(dir_struct, root_dir) |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:23
|
Update of /cvsroot/winopenrpg/openrpg1/orpg In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/orpg Added Files: __init__.py main.py minidom.py orpg_version.py orpg_windows.py orpg_wx.py orpg_xml.py player_list.py plugindb.py pluginhandler.py plugins.py pulldom.py systempath.py xmltramp.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: systempath.py --- import sys if sys.platform != 'win32': sys.path.append( "/usr/local/lib/python2.2/site-packages/" ) sys.path.append( "/usr/lib/python2.2/site-packages/" ) --- NEW FILE: plugindb.py --- import xmltramp import orpg.dirpath class PluginDB: def __init__(self, filename="plugindb.xml"): self.filename = orpg.dirpath.dir_struct["user"] + filename orpg.tools.config_files.validate_config_file(filename,"default_plugindb.xml") self.xml_dom = self.LoadDoc() def GetString(self, plugname, strname, defaultval, verbose=0): strname = self.safe(strname) for plugin in self.xml_dom: if plugname == plugin._name: for child in plugin._dir: if child._name == strname: #str() on this to make sure it's ASCII, not unicode, since orpg can't handle unicode. if verbose: print "successfully found the value" if len(child): return str( self.normal(child[0]) ) else: return "" else: if verbose: print "plugindb: no value has been stored for " + strname + " in " + plugname + " so the default has been returned" return defaultval def SetString(self, plugname, strname, val): val = self.safe(val) strname = self.safe(strname) for plugin in self.xml_dom:##this isn't absolutely necessary, but it saves the trouble of sending a parsed object instead of a simple string. if plugname == plugin._name: plugin[strname] = val plugin[strname]._attrs["type"] = "string" self.SaveDoc() return "found plugin" else: self.xml_dom[plugname] = xmltramp.parse("<" + strname + " type=\"string\">" + val + "</" + strname + ">") self.SaveDoc() return "added plugin" def FetchList(self, parent): retlist = [] if not len(parent): return [] for litem in parent[0]._dir: if len(litem): if litem._attrs["type"] == "int": retlist += [int(litem[0])] elif litem._attrs["type"] == "long": retlist += [long(litem[0])] elif litem._attrs["type"] == "float": retlist += [float(litem[0])] elif litem._attrs["type"] == "list": retlist += [self.FetchList(litem)] elif litem._attrs["type"] == "dict": retlist += [self.FetchDict(litem)] else: retlist += [str( self.normal(litem[0]) )] else: retlist += [""] return retlist def GetList(self, plugname, listname, defaultval, verbose=0): listname = self.safe(listname) for plugin in self.xml_dom: if plugname == plugin._name: for child in plugin._dir: if child._name == listname and child._attrs["type"] == "list": retlist = self.FetchList(child) if verbose: print "successfully found the value" return retlist else: if verbose: print "plugindb: no value has been stored for " + listname + " in " + plugname + " so the default has been returned" return defaultval def BuildList(self, val): listerine = "<list>" for item in val: if type(item) == type(""):#it's a string listerine += "<lobject type=\"str\">" + self.safe(item) + "</lobject>" elif type(item) == type(0):#it's an int listerine += "<lobject type=\"int\">" + str(item) + "</lobject>" elif type(item) == type(0.0):#it's a float listerine += "<lobject type=\"float\">" + str(item) + "</lobject>" elif type(item) == type(255*255*255*255):#it's a long listerine += "<lobject type=\"long\">" + str(item) + "</lobject>" elif type(item) == type([]):#it's a list listerine += "<lobject type=\"list\">" + self.BuildList(item) + "</lobject>" elif type(item) == type({}):#it's a dictionary listerine += "<lobject type=\"dict\">" + self.BuildDict(item) + "</lobject>" else: return "type unknown" listerine += "</list>" return listerine def SetList(self, plugname, listname, val): listname = self.safe(listname) list = xmltramp.parse(self.BuildList(val)) for plugin in self.xml_dom: if plugname == plugin._name: plugin[listname] = list plugin[listname]._attrs["type"] = "list" self.SaveDoc() return "found plugin" else: self.xml_dom[plugname] = xmltramp.parse("<" + listname + "></" + listname + ">") self.xml_dom[plugname][listname] = list self.xml_dom[plugname][listname]._attrs["type"] = "list" self.SaveDoc() return "added plugin" def BuildDict(self, val): dictator = "<dict>" for item in val.keys(): if type(val[item]) == type(""): dictator += "<dobject name=\"" + self.safe(item) + "\" type=\"str\">" + self.safe(val[item]) + "</dobject>" elif type(val[item]) == type(0):#it's an int dictator += "<dobject name=\"" + self.safe(item) + "\" type=\"int\">" + str(val[item]) + "</dobject>" elif type(val[item]) == type(0.1):#it's a float dictator += "<dobject name=\"" + self.safe(item) + "\" type=\"float\">" + str(val[item]) + "</dobject>" elif type(val[item]) == type(255*255*255*255):#it's a long dictator += "<dobject name=\"" + self.safe(item) + "\" type=\"long\">" + str(val[item]) + "</dobject>" elif type(val[item]) == type({}):#it's a dictionary dictator += "<dobject name=\"" + self.safe(item) + "\" type=\"dict\">" + self.BuildDict(val[item]) + "</dobject>" elif type(val[item]) == type([]):#it's a list dictator += "<dobject name=\"" + self.safe(item) + "\" type=\"list\">" + self.BuildList(val[item]) + "</dobject>" else: return str(val[item]) + ": type unknown" dictator += "</dict>" return dictator def SetDict(self, plugname, dictname, val, file="plugindb.xml"): dictname = self.safe(dictname) dict = xmltramp.parse(self.BuildDict(val)) for plugin in self.xml_dom: if plugname == plugin._name: plugin[dictname] = dict plugin[dictname]._attrs["type"] = "dict" self.SaveDoc() return "found plugin" else: self.xml_dom[plugname] = xmltramp.parse("<" + dictname + "></" + dictname + ">") self.xml_dom[plugname][dictname] = dict self.xml_dom[plugname][dictname]._attrs["type"] = "dict" self.SaveDoc() return "added plugin" def FetchDict(self, parent): retdict = {} if not len(parent): return {} for ditem in parent[0]._dir: if len(ditem): ditem._attrs["name"] = self.normal(ditem._attrs["name"]) if ditem._attrs["type"] == "int": retdict[ditem._attrs["name"]] = int(ditem[0]) elif ditem._attrs["type"] == "long": retdict[ditem._attrs["name"]] = long(ditem[0]) elif ditem._attrs["type"] == "float": retdict[ditem._attrs["name"]] = float(ditem[0]) elif ditem._attrs["type"] == "list": retdict[ditem._attrs["name"]] = self.FetchList(ditem) elif ditem._attrs["type"] == "dict": retdict[ditem._attrs["name"]] = self.FetchDict(ditem) else: retdict[ditem._attrs["name"]] = str( self.normal(ditem[0]) ) else: retdict[ditem._attrs["name"]] = "" return retdict def GetDict(self, plugname, dictname, defaultval, verbose=0): dictname = self.safe(dictname) for plugin in self.xml_dom: if plugname == plugin._name: for child in plugin._dir: if child._name == dictname and child._attrs["type"] == "dict": return self.FetchDict(child) else: if verbose: print "plugindb: no value has been stored for " + dictname + " in " + plugname + " so the default has been returned" return defaultval def safe(self, string): return string.replace("<", "$$lt$$").replace(">", "$$gt$$").replace("&","$$amp$$").replace('"',"$$quote$$") def normal(self, string): return string.replace("$$lt$$", "<").replace("$$gt$$", ">").replace("$$amp$$","&").replace("$$quote$$",'"') def SaveDoc(self): f = open(self.filename, "w") f.write(self.xml_dom.__repr__(1, 1)) f.close() def LoadDoc(self): xml_file = open(self.filename) plugindb = xml_file.read() xml_file.close() return xmltramp.parse(plugindb) --- NEW FILE: player_list.py --- #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: player_list.py # Author: Chris Davis # Maintainer: # Version: # $Id: player_list.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: This is the main entry point of the oprg application # __version__ = "$Id: player_list.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" from orpg.orpg_windows import * import orpg.dirpath # global definitions global ROLE_GM; ROLE_GM = "GM" global ROLE_PLAYER; ROLE_PLAYER = "PLAYER" global ROLE_LURKER; ROLE_LURKER = "LURKER" ######################### #player frame window ######################### PLAYER_BOOT = wxNewId() PLAYER_WHISPER = wxNewId() PLAYER_IGNORE = wxNewId() PLAYER_ROLE_MENU = wxNewId() PLAYER_ROLE_LURKER = wxNewId() PLAYER_ROLE_PLAYER = wxNewId() PLAYER_ROLE_GM = wxNewId() PLAYER_MODERATE_MENU = wxNewId() PLAYER_MODERATE_ROOM_ON = wxNewId() PLAYER_MODERATE_ROOM_OFF = wxNewId() PLAYER_MODERATE_GIVE_VOICE = wxNewId() PLAYER_MODERATE_TAKE_VOICE = wxNewId() PLAYER_SHOW_VERSION = wxNewId() WG_LIST = {} #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- PLAYER_WG_MENU = wxNewId() PLAYER_WG_CREATE = wxNewId() PLAYER_WG_CLEAR_ALL = wxNewId() WG_MENU_LIST = {} #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- #--------------------------------------------------------- # [START] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- PLAYER_COMMAND_MENU = wxNewId() PLAYER_COMMAND_PASSWORD_ALTER = wxNewId() PLAYER_COMMAND_ROOM_RENAME = wxNewId() #--------------------------------------------------------- # [END] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- class player_list(wxListCtrl): def __init__( self, parent, openrpg ): ## wxListCtrl.__init__( self, parent, -1, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER|wxEXPAND ) wxListCtrl.__init__( self, parent, -1, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER|wxEXPAND|wxLC_HRULES ) self.myopenrpg = openrpg self.session = self.myopenrpg.get_component("session") self.settings = self.myopenrpg.get_component('settings') self.chat = self.myopenrpg.get_component('chat') self.password_manager = self.myopenrpg.get_component("password_manager") # Create in image list -- for whatever reason...guess it will be nice when we can tell which is a bot self.whisperCount = 0 self._imageList = wxImageList( 16, 16, false ) img = wxImage(orpg.dirpath.dir_struct["icon"]+"player.gif", wxBITMAP_TYPE_GIF).ConvertToBitmap() self._imageList.Add( img ) img = wxImage(orpg.dirpath.dir_struct["icon"]+"player-whisper.gif", wxBITMAP_TYPE_GIF).ConvertToBitmap() self._imageList.Add( img ) self.SetImageList( self._imageList, wxIMAGE_LIST_SMALL ) # Create our column headers self.InsertColumn( 0, "ID" ) self.InsertColumn( 1, "Player" ) self.InsertColumn( 2, "Status" ) #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- ##Main Menu self.wgMenu = wxMenu() #Add the Base Menu items, so they are always at the bottom self.wgMenu.Append(PLAYER_WG_CREATE, "Create") self.wgMenu.Append(PLAYER_WG_CLEAR_ALL, "Delete All Groups") #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- # Create the role menu self.roleMenu = wxMenu() self.roleMenu.SetTitle( "Assign Role" ) self.roleMenu.Append( PLAYER_ROLE_LURKER, "Lurker" ) self.roleMenu.Append( PLAYER_ROLE_PLAYER, "Player" ) self.roleMenu.Append( PLAYER_ROLE_GM, "GM" ) # Create the moderation menu self.moderateMenu = wxMenu() self.moderateMenu.SetTitle( "Moderate" ) self.moderateMenu.Append( PLAYER_MODERATE_ROOM_ON, "Room Moderation ON" ) self.moderateMenu.Append( PLAYER_MODERATE_ROOM_OFF, "Room Moderation OFF" ) self.moderateMenu.AppendSeparator() self.moderateMenu.Append( PLAYER_MODERATE_GIVE_VOICE, "Give Voice" ) self.moderateMenu.Append( PLAYER_MODERATE_TAKE_VOICE, "Take Voice" ) #--------------------------------------------------------- # [START] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- # Create the room control menu self.commandMenu = wxMenu() self.commandMenu.SetTitle( "Room Controls" ) self.commandMenu.Append( PLAYER_COMMAND_PASSWORD_ALTER, "Password" ) self.commandMenu.Append( PLAYER_COMMAND_ROOM_RENAME, "Room Name" ) self.commandMenu.AppendSeparator() #--------------------------------------------------------- # [END] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- # Create the pop up menu self.menu = wxMenu() self.menu.SetTitle( "Player Menu" ) self.menu.Append( PLAYER_BOOT, "Boot" ) self.menu.AppendSeparator() self.menu.Append( PLAYER_IGNORE, "Toggle &Ignore" ) self.menu.AppendSeparator() self.menu.Append( PLAYER_WHISPER, "Whisper" ) #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- self.menu.AppendMenu(PLAYER_WG_MENU, "Whisper Groups", self.wgMenu) #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- self.menu.AppendSeparator() self.menu.AppendMenu( PLAYER_MODERATE_MENU, "Moderate", self.moderateMenu ) self.menu.AppendMenu( PLAYER_COMMAND_MENU, "Room Control", self.commandMenu ) self.menu.AppendSeparator() self.menu.AppendMenu( PLAYER_ROLE_MENU, "Assign Role", self.roleMenu ) self.menu.AppendSeparator() self.menu.Append( PLAYER_SHOW_VERSION, "Version" ) # Event processing for our menu EVT_MENU( self, PLAYER_BOOT, self.on_menu_item ) EVT_MENU( self, PLAYER_IGNORE, self.on_menu_item ) EVT_MENU( self, PLAYER_WHISPER, self.on_menu_item ) EVT_MENU( self, PLAYER_MODERATE_ROOM_ON, self.on_menu_moderate ) EVT_MENU( self, PLAYER_MODERATE_ROOM_OFF, self.on_menu_moderate ) EVT_MENU( self, PLAYER_MODERATE_GIVE_VOICE, self.on_menu_moderate ) EVT_MENU( self, PLAYER_MODERATE_TAKE_VOICE, self.on_menu_moderate ) EVT_MENU( self, PLAYER_ROLE_LURKER, self.on_menu_role_change ) EVT_MENU( self, PLAYER_ROLE_PLAYER, self.on_menu_role_change ) EVT_MENU( self, PLAYER_ROLE_GM, self.on_menu_role_change ) EVT_MENU( self, PLAYER_SHOW_VERSION, self.on_menu_item ) #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- EVT_MENU( self, PLAYER_WG_CREATE, self.on_menu_whispergroup ) EVT_MENU( self, PLAYER_WG_CLEAR_ALL, self.on_menu_whispergroup ) #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- #--------------------------------------------------------- # [START] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- EVT_MENU( self, PLAYER_COMMAND_PASSWORD_ALTER, self.on_menu_password ) EVT_MENU( self, PLAYER_COMMAND_ROOM_RENAME, self.on_menu_room_rename ) #--------------------------------------------------------- # [END] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- EVT_LEFT_DCLICK(self,self.on_d_lclick) EVT_RIGHT_DOWN( self, self.on_menu ) self.sized = 1 #--------------------------------------------------------- # [START] Snowdog Password/Room Name altering code 12/02 # # Revised 8/03 to add support for password manager #--------------------------------------------------------- def on_menu_password( self, evt ): id = evt.GetId() self.session = self.myopenrpg.get_component("session") self.password_manager = self.myopenrpg.get_component("password_manager") self.chat = self.myopenrpg.get_component("chat") boot_pwd = self.password_manager.GetPassword("admin",int(session.group_id)) if boot_pwd != None: alter_pwd_dialog = wxTextEntryDialog(self,"Enter new room password: (blank for no password)","Alter Room Password") if alter_pwd_dialog.ShowModal() == wxID_OK: new_pass = alter_pwd_dialog.GetValue() self.chat.InfoPost( "Requesting password change on server..." ) self.session.set_room_pass(new_pass, boot_pwd) def on_menu_room_rename( self, evt ): id = evt.GetId() self.session = self.myopenrpg.get_component("session") self.password_manager = self.myopenrpg.get_component("password_manager") self.chat = self.myopenrpg.get_component("chat") boot_pwd = self.password_manager.GetPassword("admin",int(session.group_id)) if boot_pwd != None: alter_name_dialog = wxTextEntryDialog(self,"Enter new room name: ","Change Room Name") if alter_name_dialog.ShowModal() == wxID_OK: new_name = alter_name_dialog.GetValue() self.chat.InfoPost( "Requesting room name change on server..." ) loc = new_name.find("&") oldloc=0 while loc > -1: loc = new_name.find("&",oldloc) if loc > -1: b = new_name[:loc] e = new_name[loc+1:] new_name = b + "&" + e oldloc = loc +1 loc = new_name.find("'") oldloc=0 while loc > -1: loc = new_name.find("'",oldloc) if loc > -1: b = new_name[:loc] e = new_name[loc+1:] new_name = b + "'" + e oldloc = loc +1 loc = new_name.find('"') oldloc=0 while loc > -1: loc = new_name.find('"',oldloc) if loc > -1: b = new_name[:loc] e = new_name[loc+1:] new_name = b + ""e" + e oldloc = loc +1 self.session.set_room_name(new_name, boot_pwd) #--------------------------------------------------------- # [END] Snowdog Password/Room Name altering code 12/02 #--------------------------------------------------------- #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- def clean_sub_menus(self): for mid in WG_MENU_LIST: try: self.wgMenu.Remove(WG_MENU_LIST[mid]["menuid"]) WG_MENU_LIST[mid]["menu"].Destroy() except: self.wgMenu.UpdateUI() if self.wgMenu.GetMenuItemCount() == 2: WG_MENU_LIST.clear() return def on_menu_whispergroup( self, evt ): self.session = self.myopenrpg.get_component("session") self.settings = self.myopenrpg.get_component('settings') self.chat = self.myopenrpg.get_component('chat') "Add/Remove players from Whisper Groups" id = evt.GetId() item = self.GetItem( self.selected_item ) #See if it is the main menu if id == PLAYER_WG_CREATE: create_new_group_dialog = wxTextEntryDialog(self,"Enter Group Name","Create New Whisper Group") if create_new_group_dialog.ShowModal() == wxID_OK: group_name = create_new_group_dialog.GetValue() WG_LIST[group_name] = {} return elif id == PLAYER_WG_CLEAR_ALL: WG_LIST.clear() return #Check Sub Menus for mid in WG_MENU_LIST: if id == WG_MENU_LIST[mid]["add"]: WG_LIST[mid][int(item.GetText())] = int(item.GetText()) return elif id == WG_MENU_LIST[mid]["remove"]: del WG_LIST[mid][int(item.GetText())] return elif id == WG_MENU_LIST[mid]["clear"]: WG_LIST[mid].clear() return elif id == WG_MENU_LIST[mid]["whisper"]: self.chat.set_chat_text("/gw " + mid + "=") return return #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- def on_menu_moderate( self, evt ): "Change the moderated status of a room or player." id = evt.GetId() self.chat = self.myopenrpg.get_component( "chat" ) playerID = self.GetItemData( self.selected_item ) moderationString = None moderateRoomBase = "/moderate %s" moderatePlayerBase = "/moderate %d=%s" infoRoomBase = "Attempting to %s moderation in the current room..." infoPlayerBase = "Attempting to %s voice to %s (%d)..." if id == PLAYER_MODERATE_ROOM_ON: moderationString = (moderateRoomBase % "on") infoString = (infoRoomBase % "ENABLE") if id == PLAYER_MODERATE_ROOM_OFF: moderationString = (moderateRoomBase % "off") infoString = (infoRoomBase % "DISABLE") elif id == PLAYER_MODERATE_GIVE_VOICE: moderationString = (moderatePlayerBase % (playerID, "on")) infoString = (infoPlayerBase % ("GIVE", chat.session.get_player_by_player_id( str(playerID) )[0], playerID)) elif id == PLAYER_MODERATE_TAKE_VOICE: moderationString = (moderatePlayerBase % (playerID, "off")) infoString = (infoPlayerBase % ("TAKE", chat.session.get_player_by_player_id( str(playerID) )[0], playerID)) # Now, send it to the server if moderationString: self.chat.chat_cmds.docmd( moderationString ) # Now, provide local feedback as to what we requested self.chat.InfoPost( infoString ) def on_menu_role_change( self, evt ): self.session = self.myopenrpg.get_component("session") "Change the role of the selected id." id = evt.GetId() self.chat = self.myopenrpg.get_component( "chat" ) playerID = self.GetItemData( self.selected_item ) roleString = None roleBase = "/role %d=%s" infoBase = "Attempting to assign the role of %s to (%d) %s..." # Do type specific processing if id == PLAYER_ROLE_LURKER: roleName = ROLE_LURKER roleString = (roleBase % ( playerID, ROLE_LURKER )) elif id == PLAYER_ROLE_PLAYER: roleName = ROLE_PLAYER roleString = (roleBase % ( playerID, ROLE_PLAYER )) elif id == PLAYER_ROLE_GM: roleName = ROLE_GM roleString = (roleBase % ( playerID, ROLE_GM )) # Now, send it to the server if roleString: self.chat.chat_cmds.docmd( roleString ) # Now, provide local feedback as to what we requested displayName = self.session.get_player_by_player_id( str(playerID) )[0] infoString = (infoBase % ( roleName, playerID, displayName )) self.chat.InfoPost( infoString ) def on_d_lclick(self,evt): pos = wxPoint(evt.GetX(),evt.GetY()) (item, flag) = self.HitTest(pos) id = self.GetItemText(item) self.chat = self.myopenrpg.get_component("chat") self.chat.set_chat_text("/w " + id + "=") def on_menu_item(self,evt): id = evt.GetId() self.session = self.myopenrpg.get_component("session") self.password_manager = self.myopenrpg.get_component("password_manager") if id == PLAYER_BOOT: id = str(self.GetItemData(self.selected_item)) boot_pwd = self.password_manager.GetPassword("admin",int(session.group_id)) if boot_pwd != None: self.session.boot_player(id,boot_pwd) elif id == PLAYER_WHISPER: id = self.GetItemText(self.selected_item) self.chat = self.myopenrpg.get_component("chat") self.chat.set_chat_text("/w " + id + "=") elif id == PLAYER_IGNORE: id = str(self.GetItemData(self.selected_item)) self.chat = self.myopenrpg.get_component("chat") (result,id,name) = session.toggle_ignore(id) if result == 0: self.chat.Post(chat.colorize(chat.syscolor, "Player " + name + " with ID:" + id +" no longer ignored")) else: self.chat.Post(chat.colorize(chat.syscolor, "Player " + name + " with ID:" + id +" now being ignored")) elif id == PLAYER_SHOW_VERSION: id = str(self.GetItemData(self.selected_item)) version_string = self.session.players[id][4] if version_string: wxMessageBox("Running client version " + version_string,"Version") else: wxMessageBox("No client version available for this player","Version") def on_menu(self,evt): pos = wxPoint(evt.GetX(),evt.GetY()) (item, flag) = self.HitTest(pos) if item > -1: self.selected_item = item # This if-else block makes the menu item to boot players active or inactive, as appropriate if 0: #self.myopenrpg.get_component("session").group_id == "0": self.menu.Enable(PLAYER_BOOT,0) self.menu.SetLabel(PLAYER_BOOT,"Can't boot from Lobby") else: self.menu.Enable(PLAYER_BOOT,1) self.menu.SetLabel(PLAYER_BOOT,"Boot") #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- self.menu.Enable(PLAYER_WG_MENU, true) item = self.GetItem( self.selected_item ) if len(WG_MENU_LIST) > len(WG_LIST): self.clean_sub_menus() if len(WG_LIST) == 0: self.wgMenu.Enable(PLAYER_WG_CLEAR_ALL, false) else: self.wgMenu.Enable(PLAYER_WG_CLEAR_ALL, true) for gid in WG_LIST: if not WG_MENU_LIST.has_key(gid): WG_MENU_LIST[gid] = {} WG_MENU_LIST[gid]["menuid"] = wxNewId() WG_MENU_LIST[gid]["whisper"] = wxNewId() WG_MENU_LIST[gid]["add"] = wxNewId() WG_MENU_LIST[gid]["remove"] = wxNewId() WG_MENU_LIST[gid]["clear"] = wxNewId() WG_MENU_LIST[gid]["menu"] = wxMenu() WG_MENU_LIST[gid]["menu"].SetTitle(gid) WG_MENU_LIST[gid]["menu"].Append(WG_MENU_LIST[gid]["whisper"], "Whisper") WG_MENU_LIST[gid]["menu"].Append(WG_MENU_LIST[gid]["add"], "Add") WG_MENU_LIST[gid]["menu"].Append(WG_MENU_LIST[gid]["remove"], "Remove") WG_MENU_LIST[gid]["menu"].Append(WG_MENU_LIST[gid]["clear"], "Clear") self.wgMenu.PrependMenu(WG_MENU_LIST[gid]["menuid"], gid, WG_MENU_LIST[gid]["menu"]) if WG_LIST[gid].has_key(int(item.GetText())): WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["remove"], true) WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["add"], false) else: WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["remove"], false) WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["add"], true) if len(WG_LIST[gid]) == 0: WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["whisper"], false) WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["clear"], false) else: WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["whisper"], true) WG_MENU_LIST[gid]["menu"].Enable(WG_MENU_LIST[gid]["clear"], true) #Event Stuff EVT_MENU( self, WG_MENU_LIST[gid]["whisper"], self.on_menu_whispergroup ) EVT_MENU( self, WG_MENU_LIST[gid]["add"], self.on_menu_whispergroup ) EVT_MENU( self, WG_MENU_LIST[gid]["remove"], self.on_menu_whispergroup ) EVT_MENU( self, WG_MENU_LIST[gid]["clear"], self.on_menu_whispergroup ) #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- self.PopupMenu(self.menu,pos) def add_player(self,player): i = self.InsertImageStringItem(0,player[2],0) self.SetStringItem(i,1,self.strip_html(player)) self.SetItemData(i,int(player[2])) self.SetStringItem (i, 2,player[3]) self.colorize_player_list() self.Refresh() # play sound setobj = self.myopenrpg.get_component('settings') sound_file = setobj.get_setting("AddSound") sound_player = orpg.tools.orpg_sound.orpg_sound(setobj.get_setting("UnixSoundPlayer")); sound_player.play(sound_file) def del_player(self,player): i = self.FindItemData(-1,int(player[2])) self.DeleteItem(i) #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- for gid in WG_LIST: if WG_LIST[gid].has_key(int(player[2])): del WG_LIST[gid][int(player[2])] #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- # play sound setobj = self.myopenrpg.get_component('settings') sound_file = setobj.get_setting("DelSound") sound_player = orpg.tools.orpg_sound.orpg_sound(setobj.get_setting("UnixSoundPlayer")); sound_player.play(sound_file) ic = self.GetItemCount() self.whisperCount = 0 index = 0 while index < ic: item = self.GetItem( index ) if item.GetImage(): self.whisperCount += 1 index += 1 self.colorize_player_list() self.Refresh() # This method updates the player info # # self: reference to this PlayerList # player: reference to a player structure(list) # # Returns: None # def update_player(self,player): i = self.FindItemData(-1,int(player[2])) # finds the right list box index self.SetStringItem(i,1,self.strip_html(player)) self.SetStringItem(i,2,player[3]) item = self.GetItem(i) self.colorize_player_list() self.Refresh() def colorize_player_list(self): session = self.myopenrpg.get_component("session") settings = self.myopenrpg.get_component('settings') mode = settings.get_setting("ColorizeRoles") if mode.lower() == "off": return players = session.players for m in players.keys(): item_list_location = self.FindItemData(-1,int(m)) if item_list_location == -1: continue player_info = session.get_player_by_player_id(m) item = self.GetItem(item_list_location) role = player_info[7].lower() color = wxGREEN if role == "lurker": color = wxColour(red=160,green=160,blue=160) elif role == "player": color = wxBLACK elif role == "gm": color = wxRED if self.session.group_id != "0": item.SetTextColour(color) self.SetItem(item) def reset(self): self.whisperCount = 0 #--------------------------------------------------------- # [START] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- WG_LIST.clear() #--------------------------------------------------------- # [END] Digitalxero Multi Whisper Group 1/1/05 #--------------------------------------------------------- self.DeleteAllItems() def strip_html(self,player): ret_string = "" x = 0 in_tag = 0 for x in range(len(player[0])) : if player[0][x] == "<" or player[0][x] == ">" or in_tag == 1 : if player[0][x] == "<" : in_tag = 1 elif player[0][x] == ">" : in_tag = 0 else : pass else : ret_string = ret_string + player[0][x] return ret_string def size_cols(self): ## # moved skip here to see if it breaks ## w,h = self.GetClientSizeTuple() ## w /= 8 ## self.SetColumnWidth( 0, w*2 ) ## self.SetColumnWidth( 1, w*2 ) ## self.SetColumnWidth( 2, w*3 ) pass --- NEW FILE: main.py --- #!/usr/bin/env python # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- [...1114 lines suppressed...] self.called = False wxInitAllImageHandlers() self.splash = SplashScreen(None, bitmapfile=orpg.dirpath.dir_struct["icon"]+'splash13.jpg', duration=3000, callback=self.AfterSplash) wxYield() return true def AfterSplash(self,evt): if not self.called: self.called = True self.splash.HideWindow(1) self.splash.screen.Destroy() self.frame = orpgFrame(NULL, -1, "OpenRPG v"+VERSION) self.frame.Raise() self.frame.Refresh() self.frame.Show(true) self.SetTopWindow(self.frame) #self.frame.show_dlgs() self.frame.post_show_init() return true --- NEW FILE: orpg_windows.py --- # Copyright (C) 2000-2001 The OpenRPG Project # # ope...@li... # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -- # # File: orpg_windows.py # Author: Chris Davis # Maintainer: # Version: # $Id: orpg_windows.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $ # # Description: orpg custom windows # __version__ = "$Id: orpg_windows.py,v 1.1 2006/01/26 17:33:15 digitalxero Exp $" import os import systempath import re import string import urllib from orpg.orpg_wx import * import orpg.tools.rgbhex import orpg.orpg_xml import orpg.tools.rgbhex import orpg.dirpath class img_helper: def __init__(self): pass def load_url(self,path): img_type = self.get_type(path) try: data = urllib.urlretrieve(path) if data: img = wxBitmap(data[0], img_type) else: raise IOError, "Image refused to load!" except IOError, e: img = None return img def load_file(self,path): img_type = self.get_type(path) return wxBitmap(path, img_type) def get_type(self,file_name): pos = string.rfind(file_name,'.') ext = string.lower(file_name[pos+1:]) img_type = 0 if ext == "gif": img_type = wxBITMAP_TYPE_GIF elif (ext == "jpg") | (ext == "jpeg"): img_type = wxBITMAP_TYPE_JPEG elif ext == "bmp": img_type = wxBITMAP_TYPE_BMP elif ext == "png": img_type = wxBITMAP_TYPE_PNG else: imf_type = None return img_type ################################ ## Panels ################################ class wxBoxedSizer(wxPanel): def __init__(self, parent, txt): wxPanel.__init__(self, parent, -1) self.sizer = None self.outline = wxStaticBox(self,-1,txt) self.ctrl = None EVT_SIZE(self, self.on_size) def set_sizer(self,sizer): self.sizer = sizer self.SetSizer(self.sizer) def set_ctrl(self,ctrl): self.ctrl = ctrl def on_size(self,event): s = self.GetClientSizeTuple() if self.sizer: self.sizer.SetDimension(20,20,s[0]-40,s[1]-40) if self.ctrl: self.ctrl.SetDimensions(20,20,s[0]-40,s[1]-40) self.outline.SetDimensions(5,5,s[0]-10,s[1]-10) def SetMySize(self,size): self.SetMinSize(size) ##################### ## A text editor for openrpg related text ##################### class html_text_edit(wxPanel): """ a text ctrl with html helpers """ def __init__(self, parent, id, text, callback,home_dir): wxPanel.__init__(self, parent,-1) self.r_h = orpg.tools.rgbhex.RGBHex() self.text = orpgTextCtrl(self, id, text, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE ) EVT_SIZE(self, self.OnSize) EVT_TEXT(self, id, callback) self.callback = callback self.BOLD = wxNewId() self.ITALIC = wxNewId() self.UNDER = wxNewId() self.COLOR = wxNewId() self.DIE100 = wxNewId() self.DIE20 = wxNewId() self.DIE10 = wxNewId() self.DIE8 = wxNewId() self.DIE6 = wxNewId() self.DIE4 = wxNewId() self.DIE2 = wxNewId() self.DIE = wxNewId() self.sizer = wxBoxSizer(wxHORIZONTAL) gif = wxImage(orpg.dirpath.dir_struct["icon"]+"bold.gif", wxBITMAP_TYPE_GIF) self.sizer.Add(wxBitmapButton(self, self.BOLD, gif.ConvertToBitmap()), 0, wxEXPAND) gif = wxImage(orpg.dirpath.dir_struct["icon"]+"italic.gif", wxBITMAP_TYPE_GIF) self.sizer.Add(wxBitmapButton(self, self.ITALIC, gif.ConvertToBitmap()), 0, wxEXPAND) gif = wxImage(orpg.dirpath.dir_struct["icon"]+"underlined.gif", wxBITMAP_TYPE_GIF) self.sizer.Add(wxBitmapButton(self, self.UNDER, gif.ConvertToBitmap()), 0, wxEXPAND) self.color_button = wxButton(self, self.COLOR, "C",wxPoint(0,0),wxSize(22,0)) self.color_button.SetBackgroundColour(wxBLACK) self.color_button.SetForegroundColour(wxWHITE) self.sizer.Add(self.color_button, 0, wxEXPAND) EVT_BUTTON(self, self.BOLD, self.on_text_format) EVT_BUTTON(self, self.ITALIC, self.on_text_format) EVT_BUTTON(self, self.UNDER, self.on_text_format) EVT_BUTTON(self, self.COLOR, self.on_text_format) def on_text_format(self,event): id = event.GetId() if wxPlatform == '__WXMSW__': txt = self.text.GetLabel() else: txt = self.text.GetValue() (beg,end) = self.text.GetSelection() if beg != end: sel_txt = txt[beg:end] else: return print txt if id == self.BOLD: sel_txt = "<b>" + sel_txt + "</b>" elif id == self.ITALIC: sel_txt = "<i>" + sel_txt + "</i>" elif id == self.UNDER: sel_txt = "<u>" + sel_txt + "</u>" elif id == self.COLOR: hexcolor = self.r_h.do_hex_color_dlg(self) if hexcolor: sel_txt = "<font color='"+hexcolor+"'>"+sel_txt+"</font>" self.color_button.SetBackgroundColour(hexcolor) txt = txt[:beg] + sel_txt + txt[end:] # print txt if wxPlatform == '__WXMSW__': txt = self.text.SetLabel(txt) else: txt = self.text.SetValue(txt) self.text.SetInsertionPoint(beg) self.text.SetFocus() self.callback(wxEvent(self.text.GetId())) def set_text(self,txt): self.text.SetValue(txt) def get_text(self): return self.text.GetValue() def OnSize(self,event): (w,h) = self.GetClientSizeTuple() self.text.SetDimensions(0,0,w,h-25) self.sizer.SetDimension(0,h-25,w,25) ################################ ## controls ################################ TEXT_CUT = wxNewId() TEXT_PASTE = wxNewId() TEXT_COPY = wxNewId() TEXT_SELECT_ALL = wxNewId() TEXT_UNDO = wxNewId() TEXT_REDO = wxNewId() # Heroman TEXT_DISPLAY_HTML = wxNewId() TEXT_HTML_BOLD = wxNewId() TEXT_HTML_ITAL = wxNewId() TEXT_HTML_UNDER = wxNewId() TEXT_HTML_COLOR = wxNewId() TEXT_HTML_OTHER=wxNewId() TEXT_HTML_BORDER = wxNewId() TEXT_HTML_RULE = wxNewId() TEXT_HTML_URL = wxNewId() TEXT_HTML_IMG = wxNewId() class orpgTextCtrl(wxTextCtrl): def __init__(self, parent, id, value="",pos=wxDefaultPosition, size=wxDefaultSize, style=0, validator=None, name=""): if validator: wxTextCtrl.__init__(self, parent, id, value,pos, size, style, validator, name) else: wxTextCtrl.__init__(self, parent, id, value,pos, size, style) self.r_h = orpg.tools.rgbhex.RGBHex() self.edit_menu = wxMenu() self.edit_menu.Append(TEXT_CUT,"Cu&t") self.edit_menu.Append(TEXT_COPY,"&Copy") self.edit_menu.Append(TEXT_PASTE,"&Paste") self.edit_menu.AppendSeparator() self.edit_menu.Append(TEXT_SELECT_ALL,"Select &All") self.edit_menu.AppendSeparator() self.edit_menu.Append(TEXT_DISPLAY_HTML,"HTML Preview") self.edit_menu.AppendSeparator() self.edit_menu.Append(TEXT_HTML_BOLD,"Bold") self.edit_menu.Append(TEXT_HTML_ITAL,"Italics") self.edit_menu.Append(TEXT_HTML_UNDER,"Underline") self.edit_menu.Append(TEXT_HTML_COLOR,"Color") self.edit_menu_other=wxMenu() self.edit_menu_other.Append(TEXT_HTML_BORDER,"Border") self.edit_menu_other.Append(TEXT_HTML_RULE,"Horizantal Rule") self.edit_menu_other.Append(TEXT_HTML_IMG,"Image Tag") self.edit_menu_other.Append(TEXT_HTML_URL,"URL Tag","Adds empty tag or uses selection as a URL") self.edit_menu.AppendMenu(TEXT_HTML_OTHER,"Other",self.edit_menu_other,"Other HTML tags..."); EVT_RIGHT_DOWN(self, self.on_rclick) EVT_MENU(self, TEXT_COPY, self.on_menu) EVT_MENU(self, TEXT_CUT, self.on_menu) EVT_MENU(self, TEXT_PASTE, self.on_menu) EVT_MENU(self, TEXT_SELECT_ALL, self.on_menu) EVT_MENU(self, TEXT_DISPLAY_HTML,self.on_menu) EVT_MENU(self, TEXT_HTML_BOLD,self.on_menu) EVT_MENU(self, TEXT_HTML_ITAL,self.on_menu) EVT_MENU(self, TEXT_HTML_UNDER,self.on_menu) EVT_MENU(self, TEXT_HTML_COLOR,self.on_menu) EVT_MENU(self, TEXT_HTML_BORDER,self.on_menu) EVT_MENU(self, TEXT_HTML_RULE,self.on_menu) EVT_MENU(self, TEXT_HTML_IMG,self.on_menu) EVT_MENU(self, TEXT_HTML_URL,self.on_menu) def on_rclick(self,evt): pt = evt.GetPosition() self.PopupMenu(self.edit_menu,pt) def on_menu(self,evt): id = evt.GetId() if id == TEXT_COPY: self.Copy() elif id == TEXT_CUT: self.Cut() elif id == TEXT_PASTE: self.Paste() elif id == TEXT_SELECT_ALL: self.SetFocus() self.SetSelection(0,self.GetLastPosition()) elif id == TEXT_HTML_BOLD: sel=self.GetStringSelection(); self.Cut() self.WriteText("<b>"+sel+"</b>") elif id == TEXT_HTML_ITAL: sel=self.GetStringSelection(); self.Cut() self.WriteText("<i>"+sel+"</i>") elif id == TEXT_HTML_IMG: sel=self.GetStringSelection(); self.Cut() self.WriteText("<img src=\""+sel+"\">") elif id == TEXT_HTML_URL: sel=self.GetStringSelection(); self.Cut() if sel == "": self.WriteText("<a href=\"INSERT_URL_HERE\">INSERT_TEXT_HERE</a>") else: self.WriteText("<a href=\""+sel+"\">"+sel+"</a>") elif id == TEXT_HTML_UNDER: sel=self.GetStringSelection(); self.Cut() self.WriteText("<u>"+sel+"</u>") elif id == TEXT_HTML_RULE: sel=self.GetStringSelection(); self.Cut() self.WriteText("<hr>") elif id == TEXT_HTML_COLOR: sel=self.GetStringSelection(); self.Cut() data = wxColourData() data.SetChooseFull(true) dlg = wxColourDialog(self, data) if dlg.ShowModal() == wxID_OK: data = dlg.GetColourData() (red,green,blue) = data.GetColour().Get() hexcolor = self.r_h.hexstring(red, green, blue) self.WriteText("<font color=\""+hexcolor+"\">"+sel+"</font>") dlg.Destroy() elif id == TEXT_HTML_BORDER: sel=self.GetStringSelection(); self.Cut() self.WriteText("<table border=1><tr><td>"+sel+"</td></tr></table>") elif id == TEXT_DISPLAY_HTML: dlg=HTMLDialog(self,"HTML Preview",self.GetValue()); dlg.Show() #dlg.Destroy() #about = MyAboutBox(self,obj.about()) ################################ ### MISC WINDOWS AND FRAMES ################################ class HTMLDialog(wxDialog): page = """ <html> <body bgcolor="%s"> %s <p><wxp class="wxButton"> <param name="label" value="Okay"> <param name="id" value="wxID_OK"> </wxp></p> </body> </html> """ def __init__(self, parent, title, text, bgcolor="#FFFFFF"): wxDialog.__init__(self, parent, -1, title,pos=(-1,-1),size=(400,400),style=wxRESIZE_BORDER|wxDEFAULT_DIALOG_STYLE) self.html = http_html_window(self,-1) #self.html = wxHtmlWindow(self, -1, size=(600, 400)) self.html.SetPage(self.page % (bgcolor, text)) ir = self.html.GetInternalRepresentation() self.html.SetSize( (ir.GetWidth()+5, ir.GetHeight()+5) ) self.SetClientSize(self.html.GetSize()) self.SetSize(size=(600,400)) self.CentreOnParent(wxBOTH) #def OnOK(self,event): class MyAboutBox(wxDialog): page = """ <html> <body bgcolor="%s"><center> %s <p><wxp class="wxButton"> <param name="label" value="Okay"> <param name="id" value="wxID_OK"> </wxp></p> </center> </body> </html> """ def __init__(self, parent, text, bgcolor="#FFFFFF"): wxDialog.__init__(self, parent, -1, 'About',) html = wxHtmlWindow(self, -1, size=(420, -1)) html.SetPage(self.page % (bgcolor, text)) ir = html.GetInternalRepresentation() html.SetSize( (ir.GetWidth()+5, ir.GetHeight()+5) ) self.SetClientSize(html.GetSize()) self.CentreOnParent(wxBOTH) ########################### ## HTML related clasees ########################### class http_html_window(wxHtmlWindow): """ a wxHTMLwindow that will load links """ def __init__(self, parent, id): wxHtmlWindow.__init__(self, parent, id, wxDefaultPosition,wxDefaultSize,wxSUNKEN_BORDER | wxHW_SCROLLBAR_AUTO) self.path = "" self.local = 0 #self.title = title def OnLinkClicked(self, linkinfo): address = linkinfo.GetHref() if address[:4] == "http": self.load_url(address) self.local = 0 elif address[0] == "#" or self.local: self.base_OnLinkClicked(linkinfo) else: self.load_url(self.path+address) def load_url(self,path): print path dlg = wxProgressDialog("HTML Document","Loading...",3,self) dlg.Update(1) try: data = urllib.urlretrieve(path) file = open(data[0]) dlg.Update(2) self.SetPage(file.read()) i = string.rfind(path,"/") self.path = path[:i+1] except: wxMessageBox("Invalid URL","Browser Error",wxOK) #self.SetPage("<h3>Invalid URL</h3>") dlg.Update(3) dlg.Destroy() def load_file(self,path): self.LoadPage(path) self.local = 1 WEB_CLOSE = wxNewId() WEB_BACK = wxNewId() WEB_FORWARD = wxNewId() WEB_PRINT = wxNewId() WEB_SAVE = wxNewId() class wxHTMLpanel(wxPanel): """ A user friednly panel for the http_html_window """ def __init__(self, parent, id,): wxPanel.__init__(self, parent, id) self.html_wnd = http_html_window(self,-1) self.parent = parent self.sizer = wxBoxSizer(wxHORIZONTAL) self.sizer.Add(wxButton(self, WEB_CLOSE, "Close"), 1, wxEXPAND) self.sizer.Add(wxButton(self, WEB_BACK, "Back"), 1, wxEXPAND) self.sizer.Add(wxButton(self, WEB_FORWARD, "Forward"), 1, wxEXPAND) self.sizer.Add(wxButton(self, WEB_SAVE, "Save"), 1, wxEXPAND) self.sizer.Add(wxButton(self, WEB_PRINT, "Print"), 1, wxEXPAND) EVT_BUTTON(self, WEB_CLOSE, self.on_button) EVT_BUTTON(self, WEB_BACK, self.on_button) EVT_BUTTON(self, WEB_FORWARD, self.on_button) EVT_BUTTON(self, WEB_PRINT, self.on_button) EVT_BUTTON(self, WEB_SAVE, self.on_button) EVT_SIZE(self, self.on_size) EVT_CLOSE( self, self.OnClose ) self.printer = wxHtmlEasyPrinting() def on_button(self,evt): id = evt.GetId() if id == WEB_CLOSE: self.OnClose( evt ) elif id == WEB_FORWARD: if not self.html_wnd.HistoryForward(): wxMessageBox("No more items in history!") elif id==WEB_BACK: if not self.html_wnd.HistoryBack(): wxMessageBox("No more items in history!") elif id==WEB_PRINT: file = self.html_wnd.GetOpenedPage() if file == "": self.save_page("print.htm") file = "print.htm" self.printer.PreviewFile(file) elif id==WEB_SAVE: f =wxFileDialog(self,"Select a file",orpg.dirpath.dir_struct["user"],"","HTML (*.html)|*.html",wxSAVE) if f.ShowModal() == wxID_OK: self.save_page(f.GetPath()) f.Destroy() def save_page(self,path): data = self.html_wnd.GetParser().GetSource() file = open(path,"w") file.write(data) file.close() def load_file(self,path): self.html_wnd.load_file(path) def load_url(self,path): self.html_wnd.load_url(path) def on_size(self,event): (w,h) = self.GetClientSizeTuple() self.html_wnd.SetDimensions(0,0,w,h-25) self.sizer.SetDimension(0,h-25,w,25) def load_text(self,txt): self.html_wnd.SetPage(txt) def OnClose( self, evt ): self.parent.Close( true ) class wxPFrame(wxFrame): """ a framed window that holds a single panel """ def __init__(self, parent, caption, icon_file=orpg.dirpath.dir_struct["icon"]+'d20.ico', only_hide=0, pos = None, size = None, style=wxDEFAULT_FRAME_STYLE): if not pos: pos = wxDefaultPosition if not size: size = wxSize(500,300) wxFrame.__init__(self, parent, -1, caption, pos, size, style ) if wxPlatform == '__WXMSW__': icon = wxIcon(icon_file, wxBITMAP_TYPE_ICO) self.SetIcon(icon) self.panel = None self.only_hide = only_hide self.parent = parent self.destroyed = 0 EVT_SIZE(self, self.OnSize) EVT_CLOSE(self, self.OnCloseWindow) def OnCloseWindow(self, event): if self.only_hide: self.Show( false ) self.Raise() else: self.destroyed = 1 self.Destroy() #print "window destroyed!" def OnSize(self,event): (w,h) = self.GetClientSizeTuple() if self.panel: self.panel.SetDimensions(0,0,w,h) class wxHTMLFrame(wxFrame): """ a framed http_html_window """ def __init__(self, parent, caption, pos = None, size = None, style = wxDEFAULT_FRAME_STYLE): if not pos: pos = wxDefaultPosition if not size: size = wxSize(500,300) wxFrame.__init__(self, parent, -1, caption, pos, size,style) self.html_wnd = http_html_window(self,-1) self.ok = wxButton(self, wxID_OK, "OK") EVT_BUTTON(self, wxID_OK, self.OnCloseMe) EVT_SIZE(self, self.OnSize) def load_file(self,path): self.html_wnd.load_file(path) def load_url(self,path): self.html_wnd.load_url(path) def set_page(self,data): self.html_wnd.SetPage(data) def load_img(self,path): self.html_wnd.load_img(path) def OnCloseMe(self, event): self.Close(true) def OnCloseWindow(self, event): self.Destroy() def OnSize(self,event): (w,h) = self.GetClientSizeTuple() self.html_wnd.SetDimensions(0,0,w,h-25) self.ok.SetDimensions(0,h-25,w,25) ########################### ## Some misc dialogs ########################### class wxMultiCheckBoxDlg(wxDialog): """ notes """ def __init__(self,parent,opts,text,caption,selected=[],pos=wxDefaultPosition): wxDialog.__init__(self,parent,-1,caption,pos,wxDefaultSize) sizers = { 'ctrls' : wxBoxSizer(wxVERTICAL), 'buttons' : wxBoxSizer(wxHORIZONTAL) } sid = wxNewId() self.opts = opts self.list = wxCheckListBox(self, sid, wxDefaultPosition, wxDefaultSize,opts) for s in selected: self.list.Check(s,1) sizers['ctrls'].Add(wxStaticText(self, -1, text), 0, 0) sizers['ctrls'].Add(wxSize(10,10)) sizers['ctrls'].Add(self.list, 1, wxEXPAND) sizers['buttons'].Add(wxButton(self, wxID_OK, "OK"), 1, wxEXPAND) sizers['buttons'].Add(wxSize(10,10)) sizers['buttons'].Add(wxButton(self, wxID_CANCEL, "Cancel"), 1, wxEXPAND) width = 200 height = 200 self.SetClientSizeWH(width,height) sizers['ctrls'].SetDimension(10,5,width-20,160) sizers['buttons'].SetDimension(10,170,width-20,25) EVT_BUTTON(self, wxID_OK, self.on_ok) def on_ok(self,evt): checked = [] for i in range(len(self.opts)): if self.list.IsChecked(i): checked.append(i) self.checked = checked self.EndModal(wxID_OK) def get_selections(self): return self.checked class wxMultiChoiceDlg(wxDialog): """ test """ def __init__(self,parent,opts,text,caption,pos=wxDefaultPosition): wxDialog.__init__(self,parent,-1,caption,pos,wxDefaultSize) sizers = { 'ctrls' : wxBoxSizer(wxVERTICAL), 'buttons' : wxBoxSizer(wxHORIZONTAL) } self.list = wxListBox(self, -1, wxDefaultPosition, wxDefaultSize, opts, wxLB_EXTENDED) sizers['ctrls'].Add(wxStaticText(self, -1, text), 0, 0) sizers['ctrls'].Add(wxSize(10,10)) sizers['ctrls'].Add(self.list, 1, wxEXPAND) sizers['buttons'].Add(wxButton(self, wxID_OK, "OK"), 1, wxEXPAND) sizers['buttons'].Add(wxSize(10,10)) sizers['buttons'].Add(wxButton(self, wxID_CANCEL, "Cancel"), 1, wxEXPAND) width = 200 height = 200 self.SetClientSizeWH(width,height) sizers['ctrls'].SetDimension(10,5,width-20,160) sizers['buttons'].SetDimension(10,170,width-20,25) EVT_BUTTON(self, wxID_OK, self.on_ok) def on_ok(self,evt): self.EndModal(wxID_OK) def get_selections(self): pass class wxMultiTextEntry(wxDialog): """ a dialog that takes two lists (labels and values) and creates a 'label: value' style text edit control for each node in the dic""" def __init__(self,parent,tlist,vlist,caption,pos=wxDefaultPosition): wxDialog.__init__(self,parent,-1,caption,pos,wxDefaultSize) num = len(tlist) sizers = { 'ctrls' : wxFlexGridSizer(num,2,5,0), 'buttons' : wxBoxSizer(wxHORIZONTAL) } #keys = mlist.keys() self.tlist = ... [truncated message content] |
Update of /cvsroot/winopenrpg/openrpg1/images In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/images Added Files: 8ball.gif WAmisc7.ico WAmisc9.ico add_filter.gif apoc.gif b_d10.gif b_d100.gif b_d12.gif b_d20.gif b_d4.gif b_d6.gif b_d8.gif bold.gif book.gif bricktile.gif browser.gif bullet.gif car.gif ccmap.gif chess.gif close_wnd.bmp compass.gif compass.ico connect.gif cyborg.gif d10.gif d20.gif d20.ico d20.xpm d20_logo.gif d4.gif d8.gif delete_filter.gif dice.bmp die.gif divider.png draw.gif drugs.gif earth.gif edit_filter.gif fetching.png flask.gif flask.ico folder.gif form.png frame.bmp gear.gif goblin.gif goblin.ico grenade.gif grid.gif grid.ico gun1.gif gun2.gif help.gif html.gif html.ico icons.xml img.gif install.gif italic.gif knight.gif labtop.gif money.gif move.gif ninja.gif noplayer.gif note.gif note.ico open.bmp orc.gif oriental.gif pin.gif planet.gif player-whisper.gif player.gif python55.gif questionhead.gif r2d2.gif rome.gif save.bmp sflogo.png shades.gif skull.gif skull_16.gif smsword2.gif spears.gif splash.gif splash.jpg splash1.jpg splash13.jpg splitwin.bmp startrek.gif sword.gif tab.bmp tabber.png tank1.gif tank2.gif tape.gif thief.gif tiefighter.gif underlined.gif wizard1.gif wxPyButton.png wxWinButton.png zoom_in.gif zoom_out.gif Log Message: Initial commit of OpenRPG++ python --- NEW FILE: bullet.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: goblin.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: wizard1.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: apoc.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: flask.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: oriental.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: dice.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: html.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: install.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: underlined.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d20_logo.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: gun2.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: noplayer.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: drugs.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: divider.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d6.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: shades.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d8.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d4.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: grid.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: compass.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: grenade.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d100.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: player.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d10.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: tank2.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: splash1.jpg --- (This appears to be a binary file; contents omitted.) --- NEW FILE: pin.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: skull.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: zoom_in.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d20.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: sflogo.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: bricktile.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: sword.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: knight.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: img.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: questionhead.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: splash.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: splash13.jpg --- (This appears to be a binary file; contents omitted.) --- NEW FILE: WAmisc7.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: gun1.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: frame.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: move.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: form.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: browser.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: italic.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: splitwin.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: labtop.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: car.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: tiefighter.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: orc.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: cyborg.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: open.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: help.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: wxPyButton.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: note.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: note.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: connect.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: icons.xml --- <icons> <icon name='book' file='book.gif' /> <icon name='folder' file='folder.gif' /> <icon name='die' file='die.gif' /> <icon name='skull' file='skull.gif' /> <icon name='labtop' file='labtop.gif' /> <icon name='flask' file='flask.gif' /> <icon name='goblin' file='goblin.gif' /> <icon name='note' file='note.gif' /> <icon name='bullet' file='bullet.gif' /> <icon name='ccmap' file='ccmap.gif' /> <icon name='8ball' file='8ball.gif' /> <icon name='car' file='car.gif' /> <icon name='chess' file='chess.gif' /> <icon name='compass' file='compass.gif' /> <icon name='cyborg' file='cyborg.gif' /> <icon name='drugs' file='drugs.gif' /> <icon name='gear' file='gear.gif' /> <icon name='grenade' file='grenade.gif' /> <icon name='gun1' file='gun1.gif' /> <icon name='gun2' file='gun2.gif' /> <icon name='knight' file='knight.gif' /> <icon name='money' file='money.gif' /> <icon name='ninja' file='ninja.gif' /> <icon name='orc' file='orc.gif' /> <icon name='help' file='help.gif' /> <icon name='oriental' file='oriental.gif' /> <icon name='player' file='player.gif' /> <icon name='questionhead' file='questionhead.gif' /> <icon name='r2d2' file='r2d2.gif' /> <icon name='rome' file='rome.gif' /> <icon name='shades' file='shades.gif' /> <icon name='spears' file='spears.gif' /> <icon name='startrek' file='startrek.gif' /> <icon name='sword' file='sword.gif' /> <icon name='tank1' file='tank1.gif' /> <icon name='tank2' file='tank2.gif' /> <icon name='thief' file='thief.gif' /> <icon name='tiefighter' file='tiefighter.gif' /> <icon name='wizard1' file='wizard1.gif' /> <icon name='d20' file='d20.gif' /> <icon name='d10' file='d10.gif' /> <icon name='d8' file='d8.gif' /> <icon name='d4' file='d4.gif' /> <icon name='grid' file='grid.gif' /> <icon name='html' file='html.gif' /> <icon name='browser' file='browser.gif' /> <icon name='image' file='img.gif' /> <icon name='tabber' file='tabber.png' /> <icon name='divider' file='divider.png' /> <icon name='form' file='form.png' /> </icons> --- NEW FILE: add_filter.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: tabber.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: fetching.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d20.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d8.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: skull_16.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: money.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: bold.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: spears.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: splash.jpg --- (This appears to be a binary file; contents omitted.) --- NEW FILE: delete_filter.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: tank1.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: goblin.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: chess.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: 8ball.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: flask.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: thief.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: draw.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d4.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: WAmisc9.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d20.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: tab.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: r2d2.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: grid.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: compass.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: planet.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: d20.xpm --- /* XPM */ static char * d20_xpm[] = { "64 64 2381 2", " c None", ". c #8E8481", "+ c #6B615A", "@ c #6D635B", "# c #6B615D", "$ c #4E4443", "% c #514641", "& c #564B46", "* c #615954", "= c #6A6763", "- c #474344", "; c #5D5857", "> c #676163", ", c #7E777B", "' c #746B67", ") c #665C5A", [...2409 lines suppressed...] "U'V'W'X'Y'Z'`' ).)+)@)#)#)$)>;%)5&4&d$7=)&,&2&L&u&$*.*&)*)=)-)};;)L*>),)')4=n,5=))},!)~){)])^)/)()_):)<)[)})|)1)2)3)K$j&k&4)5)6)", "7)8)9)u%0)a)b)c)d)e)f)/=H=g)X&h)s&i)u&2&j)2&7=3&t&t*/'&)k)l)m)9&n)o)')p)p)q)r)G&5=!)},'=s)t)u)v)w)x)y)z)A)T%3)B)C)D)u#E)c$F)G)H)", "I)J)K)L),-M)N)O)P)Q)R)S)T)U)V)W)J&L&%*1&%*X)%*9&j)Y)Z)`) !a&.!+!E>@!v&Q%#!$!%!d$^'q,q,&!*!=!r'v&$,I&+*-!;!$'>!U>,!k-'!)!9 !!~!{!", "]!^!/!n&(!_!:!<![!}!|!S).&1!2!3!J&9&7*9&7*u&$*v*&)&)4!5!6!6!7!i)!)8!j)[*$!$!F@j)#*M-4=z=;)r,t&9&7*9&}*l&9!l-0!a!b!0$c!l,9 d!2'e!", "f!g!h!5;W@i!j!k!l!m!n!o!p!q!r!s!s,y*t!u!v!x*w!x!y!z!|*A!t!|$B!C!D!E!G&F!)>-'G!H!I!J!K!L!M!N!O!P!E>J&1*/;Q!R!S!T!U!#+V!H,W!X!Y!Z!", "`! ~.~+~@~#~$~%~&~*~=~-~;~>~,~'~)~!~<;<;~~{~]~^~/~(~h)N&<;B=s*X)K&_~:~<~[~}~|~1~2~3~4~a*5~6~7~8~9~0~a~b~c~d~e~f~g~h~i~g-j~k~l~m~", "n~o~p~q~a$r~s~t~u~v~w~x~y~z~A~B~C~D~H&;'E~+,]~F~G~H~4*h)u*u*Y&3*s*I~J~K~L~M~N~O~P~Q~R~S~T~U~V~W~X~Y~Z~`~ {.{+{$-@{Q'#{${%{&{*{={", "-{;{>{3=,{4$'{){!{~{{{]{^{/{({_{:{<{[{}{|{1{2{3{4{5{H 6{7{8{9{0{a{b{c{d{e{f{g{>;h{i{j{k{b>l{m{n{o{p{q{r{s{t{u{v{w{p%x{y{{=z{A{B{", "C{D{E{F{G{H{I{J{K{L{M{D@N{O{P{Q{R{S{T{U{V{W{X{Y{Z{`{ ].]+]@]#]$]%]&] !*]=]-];]>],]'])]!]~]{]]]-,^]/](]_]:]<][]}]|]1]y{5,.%2]3]4]", "5]6]7]@-8]9]0]a]X*b]V#c]d]e]f]g]h]i]j]k]l]m]n]o]p]q]r]s]t]u]v]w]x]y]6*z]A]B]C]D]E]F]G]H]I]J]K]L]M]N]O]P]Q]R]S]T]U]V]W]X]Y]Z]`] ^", ".^+^@^#^$^%^&^*^=^c]-^;^>^,^'^)^!^~^{^]^^^/^(^_^:^<^[^}^|^1^2^3^4^5^6^7^8^9^0^a^b^c^d^e^f^g^h^r'i^j^k^l^m^2*n^o^p^q^r^s^t^u^v^w^", "x^y^z^A^B^C^D^E^F^G^H^I^J^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^`^ /./+/@/#/$/%/&/*/=/-/;/>/N%,/'/0^E-)/!/~/{/]/^///(/_/:/</[/t^}/|/1/", "2/2/3/4/5/6/7/8/9/0/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/f&u/v/w/x/y/z/A/B/|)C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/1-t=U/V/W/X/", "Y/Y/Z/`/x$|- (.(+(@(q^y=s^#($(%(K-&(=&s%*(B-=(-(;(>(}#,=,('()(O'!(3)c~~({(](^(/(((_(:(<([(}(|(1(2(3(4(g%n$5(6(7(`;0$.$8(9(0(a(b(", "c(d(e(s*f(g(h(i(j(k(Z=l(r=m(n(o(q,p(q(P$r(s(i&t(u(v(w(x(y(z(A(.%K>c!B(+%j&+&C(D(E(X,F(G(<(H(I(J(K(X>2(1,g'L(M(N(O(8$9$P(Q(R(S(T(", "7]U(V(]*W(X(Y(Z(C*`( _B(._+_@_#_$_j~%_&_*_C)U%<*K$j~=_-_;_>_,_|,'_)_e&!_~_P.{_]_{$X,.%l-}#^_/_@'l-l-(_S*)(J'__:_<_2>X=Y,[_}_|_+(", "1_2_3_4_5_6_7_V/8_9_0_a_b_c_d_e_f_g_h_i_j_k_l_.>m_n_o_p_m_2',_q_O$r_s_t_u_v_w_U+9(x_Q(y_z_A_B_y)z)P'C_+&D_9_E_F_G_H_I_J_K_L_M_N_", "O_P_Q_R_S_T_U_V_W_X_Y_Z_`_ :.:+:@:#:$:%:&:*:=:-:;:>:,:':):!:e(~:{:]:^:/:(:_:::<:[:}:|:1:2:3:4:5:6:7:8:9:0:a:b:c:d:e:f:g:h:i:j:k:", "l:m:n:o:p:q:r:s:t:u:v:w:x:y:z:A:B:C:D:E:F:G:H:I:J:K:L:M:N:O:P:Q:R:S:T:U:V:W:X:Y:Z:`:|: <.<+<@<#<$<%<&<*<=<-<;<><,<Y+'<)<!<~<{<]<", "^</<(<_<:<<<[<}<|<1<2<3<4<5<6<7<8<9<0<a<b<c<d<e<f<g<3^h<i<j<k<l<m<n<R:o<p<q<r<s<t<u<v<w<x<y<z<A<B<C<D<E<F<G<H<I<J<K<L<M<N<M<O<P<"}; --- NEW FILE: html.ico --- (This appears to be a binary file; contents omitted.) --- NEW FILE: folder.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: close_wnd.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: book.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: gear.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: python55.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: ninja.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: startrek.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: wxWinButton.png --- (This appears to be a binary file; contents omitted.) --- NEW FILE: player-whisper.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: save.bmp --- (This appears to be a binary file; contents omitted.) --- NEW FILE: tape.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: rome.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: zoom_out.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: die.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: ccmap.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d10.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: earth.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: edit_filter.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: smsword2.gif --- (This appears to be a binary file; contents omitted.) --- NEW FILE: b_d12.gif --- (This appears to be a binary file; contents omitted.) |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:22
|
Update of /cvsroot/winopenrpg/openrpg1/data/d20 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/data/d20 Added Files: d20armor.xml d20classes.xml d20divine.xml d20feats.xml d20powers.xml d20spells.xml d20weapons.xml Log Message: Initial commit of OpenRPG++ python --- NEW FILE: d20armor.xml --- <ac> <armor name="Banded mail" cost="250" type="Heavy" maxdex="1" bonus="6" spellfailure="35" checkpenalty="-6" weight="35" speed="20" speed20="15" speed30="20" > <description >This armor is made of overlapping strips of metal sewn to a backing of leather and chainmail. The strips cover vulnerable areas, while the chain and leather protect the joints and provide freedom of movement. Straps and buckles distribute the weight evenly. It includes gauntlets. </description > </armor> <armor name="Breastplate" cost="200" type="Medium" maxdex="3" bonus="5" spellfailure="25" checkpenalty="-4" weight="30" speed="20" speed20="15" speed30="20" > <description >A breastplate covers the front and back. It comes with a helmet and matching greaves (plates to cover the lower legs). A light suit or skirt of studded leather beneath the breastplate protects limbs without restricting movement much. </description > </armor> <armor name="Buckler" cost="15" type="Shield" maxdex="100" bonus="1" spellfailure="5" checkpenalty="-1" weight="5" speed="30" speed20="20" speed30="30" > <description >This small metal shield is strapped to the forearm, allowing it to be worn and still use the hand. A bow or crossbow can be used without penalty. An off-hand weapon can be used, but a -1 penalty on attack rolls is imposed because of the extra weight on your arm. This penalty stacks with those for fighting with the off hand and, if appropriate, for fighting with two weapons. In any case, if a weapon is used in the off-hand, the character doesn't get the buckler's AC bonus for the rest of the round. </description > </armor> <armor name="Chainmail" cost="150" type="Medium" maxdex="2" bonus="5" spellfailure="30" checkpenalty="-5" weight="40" speed="20" speed20="15" speed30="20" > <description >This armor is made of interlocking metal rings. It includes a layer of quilted fabric underneath it to prevent chafing and to cushion the impact of blows. Several layers of mail are hung over vital areas. Most of the armor's weight hangs from the shoulders, making chainmail uncomfortable to wear for long periods of time. It includes gauntlets. </description > </armor> <armor name="Chainshirt" cost="100" type="Light" maxdex="4" bonus="4" spellfailure="20" checkpenalty="-2" weight="25" speed="30" speed20="20" speed30="30" > <description >A shirt of chainmail protects the torso while leaving the limbs free and mobile. A layer of quilted fabric underneath it prevents chafing and cushions the impact of blows. It comes with a steel cap. </description > </armor> <armor name="Full Plate" cost="1500" type="Heavy" maxdex="1" bonus="8" spellfailure="35" checkpenalty="-6" weight="50" speed="20" speed20="15" speed30="20" > <description >This armor consists of shaped and fitted metal plates riveted and interlocked to cover the entire body. It includes gauntlets, heavy leather boots, and a visored helmet. </description > </armor> <armor name="Half-Plate" cost="600" type="Heavy" maxdex="0" bonus="7" spellfailure="40" checkpenalty="-7" weight="50" speed="20" speed20="15" speed30="20" > <description >This armor is a combination of chainmail with metal plates (breastplate, epaulettes, elbow guards, gauntlets, tasses, and greaves) covering vital areas. Buckles and straps hold the whole suit together and distribute the weight, but the armor still hangs more loosely than full plate. It includes gauntlets. </description > </armor> <armor name="Hide" cost="15" type="Medium" maxdex="4" bonus="3" spellfailure="20" checkpenalty="-3" weight="25" speed="20" speed20="15" speed30="20" > <description >This armor is prepared from multiple layers of leather and animal hides. It is stiff and hard to move in. </description > </armor> <armor name="Large SteelShield" cost="20" type="Shield" maxdex="100" bonus="2" spellfailure="15" checkpenalty="-2" weight="15" speed="30" speed20="20" speed30="30" > <description >A large shield is too heavy to use the shield hand for anything else. </description > </armor> <armor name="Large Wooden Shield" cost="7" type="Shield" maxdex="100" bonus="2" spellfailure="15" checkpenalty="-2" weight="10" speed="30" speed20="20" speed30="30" > <description >A large shield is too heavy to use the shield hand for anything else. </description > </armor> <armor name="Leather" cost="10" type="Light" maxdex="6" bonus="2" spellfailure="10" checkpenalty="0" weight="10" speed="30" speed20="20" speed30="30" > <description >The breastplate and shoulder protectors of this armor are made of leather that has been stiffened by boiling in oil. The rest of the armor is softer and more flexible leather. </description > </armor> <armor name="Padded" cost="5" type="Light" maxdex="8" bonus="1" spellfailure="5" checkpenalty="0" weight="10" speed="30" speed20="20" speed30="30" > <description >Padded armor features quilted layers of cloth and batting. </description > </armor> <armor name="Samll Steel Shield" cost="9" type="Shield" maxdex="100" bonus="1" spellfailure="5" checkpenalty="-1" weight="6" speed="30" speed20="20" speed30="30" > <description >A small shield's light weight lets a character carry other items in that hand (although the character cannot use weapons). </description > </armor> <armor name="Scale Mail" cost="50" type="Medium" maxdex="3" bonus="4" spellfailure="25" checkpenalty="-4" weight="30" speed="20" speed20="15" speed30="20" > <description >This is a coat and leggings (and perhaps a separate skirt) of leather covered with overlapping pieces of metal, much like the scales of a fish. It includes gauntlets. </description > </armor> <armor name="Small Wooden Shield" cost="300" type="Shield" maxdex="100" bonus="1" spellfailure="5" checkpenalty="-1" weight="5" speed="30" speed20="20" speed30="30" > <description >A small shield's light weight lets a character carry other items in that hand (although the character cannot use weapons). </description > </armor> <armor name="Splint mail" cost="200" type="Heavy" maxdex="0" bonus="6" spellfailure="40" checkpenalty="-7" weight="45" speed="20" speed20="15" speed30="20" > <description >This armor is made of narrow vertical strips of metal riveted to a backing of leather that is worn over cloth padding. Flexible chainmail protects the joints. It includes gauntlets. </description > </armor> <armor name="Studded Leather" cost="25" type="Light" maxdex="5" bonus="3" spellfailure="15" checkpenalty="-1" weight="20" speed="30" speed20="20" speed30="30" > <description >This armor is made from tough but flexible leather (not hardened leather as with normal leather armor) reinforced with close-set metal rivets. </description > </armor> <armor name="Tower Shield" cost="30" type="Shield" maxdex="100" bonus="0" spellfailure="50" checkpenalty="-10" weight="45" speed="30" speed20="20" speed30="30" > <description >This massive wooden shield is nearly as tall as the wielder. Basically, it is a portable wall meant to provide cover. It can provide up to total cover, depending on how far a character comes out from behind it. A tower shield, however, does not provide cover against targeted spells; a spellcaster can cast a spell on a character by targeting the shield. A tower shield cannot be used for the shield bash action. </description > </armor> </ac> --- NEW FILE: d20weapons.xml --- <weapons> <weapon mod="0" name="Antimatter rifle" cost="-1" category="Futuristic Weapons-Ranged" size="Medium" damage="6d10" critical="x2" range="10" weight="10" type="Special" > <description ></description > </weapon> <weapon mod="0" name="Axe, orc double" cost="60" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d8" critical="x3" range="0" weight="25" type="S" > <description ></description > </weapon> <weapon mod="0" name="Axe, throwing" cost="8" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="x2" range="10" weight="4" type="S" > <description ></description > </weapon> <weapon mod="0" name="Battleaxe" cost="10" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x3" range="0" weight="7" type="S" > <description ></description > </weapon> <weapon mod="0" name="Blowgun" cost="1" category="Asian Weapons-Ranged" size="Small" damage="1" critical="x2" range="10" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" name="Chain, spiked" cost="25" category="Exotic Weapons-Melee" size="Large" damage="2d4" critical="x2" range="0" weight="15" type="P" > <description ></description > </weapon> <weapon mod="0" name="Club" cost="0" category="Simple Weapons-Melee" size="Medium" damage="1d6" critical="x2" range="10" weight="3" type="B" > <description ></description > </weapon> <weapon mod="0" name="Crossbow, hand" cost="100" category="Exotic Weapons-Ranged" size="Tiny" damage="1d4" critical="19-20/x2" range="30" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Crossbow, heavy" cost="50" category="Simple Weapons-Ranged" size="Medium" damage="1d10" critical="19-20/x2" range="120" weight="9" type="P" > <description ></description > </weapon> <weapon mod="0" name="Crossbow, light" cost="35" category="Simple Weapons-Ranged" size="Small" damage="1d8" critical="19-20/x2" range="80" weight="6" type="P" > <description ></description > </weapon> <weapon mod="0" name="Crossbow, repeating" cost="250" category="Exotic Weapons-Ranged" size="Medium" damage="1d8" critical="19-20/x2" range="80" weight="16" type="P" > <description ></description > </weapon> <weapon mod="0" name="Dagger" cost="2" category="Simple Weapons-Melee" size="Tiny" damage="1d4" critical="19-20/x2" range="10" weight="1" type="P" > <description ></description > </weapon> <weapon mod="0" name="Dagger, punching" cost="2" category="Simple Weapons-Melee" size="Tiny" damage="1d4" critical="x3" range="0" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" name="Dart" cost="0" category="Simple Weapons-Ranged" size="Small" damage="1d4" critical="x2" range="20" weight="0" type="P" > <description ></description > </weapon> <weapon mod="0" name="Falchion" cost="75" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="18-29/x2" range="0" weight="16" type="S" > <description ></description > </weapon> <weapon mod="0" name="Flail, dire" cost="90" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d8" critical="x2" range="0" weight="20" type="B" > <description ></description > </weapon> <weapon mod="0" name="Flail, heavy" cost="15" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="19-20/x2" range="0" weight="20" type="B" > <description ></description > </weapon> <weapon mod="0" name="Flail, light" cost="8" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="0" weight="5" type="B" > <description ></description > </weapon> <weapon mod="0" name="Flamer" cost="-1" category="Futuristic Weapons-Ranged" size="Medium" damage="3d6*" critical="-" range="20" weight="8" type="Special" > <description ></description > </weapon> <weapon mod="0" name="Gauntlet" cost="2 gp" category="Simple Weapons-Melee" size="Unarmed" damage="*" critical="*" range="0" weight="2" type="B" > <description ></description > </weapon> <weapon mod="0" name="Gauntlet, spiked" cost="5" category="Simple Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" name="Glaive" cost="8" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" name="Greataxe" cost="20" category="Martial Weapons-Melee" size="Large" damage="1d12" critical="x3" range="0" weight="20" type="S" > <description ></description > </weapon> <weapon mod="0" name="Greatclub" cost="5" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="x2" range="0" weight="10" type="B" > <description ></description > </weapon> <weapon mod="0" name="Greatsword" cost="50" category="Martial Weapons-Melee" size="Large" damage="2d6" critical="19-20/x2" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" name="Grenade launcher" cost="-1" category="Modern Weapons-Ranged" size="Large" damage="*" critical="*" range="200" weight="12" type="*" > <description ></description > </weapon> <weapon mod="0" name="Guisarme" cost="9" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" name="Halberd" cost="10" category="Martial Weapons-Melee" size="Large" damage="1d10" critical="x3" range="0" weight="15" type="P&S" > <description ></description > </weapon> <weapon mod="0" name="Halfspear" cost="1" category="Simple Weapons-Melee" size="Medium" damage="1d6" critical="x3" range="20" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Hammer, gnome hooked" cost="20" category="Exotic Weapons-Melee" size="Medium" damage="1d6/1d4" critical="x3/x4" range="0" weight="6" type="B&P" > <description ></description > </weapon> <weapon mod="0" name="Hammer, light" cost="1" category="Martial Weapons-Melee" size="Small" damage="1d4" critical="x2" range="20" weight="2" type="B" > <description ></description > </weapon> <weapon mod="0" name="Handaxe" cost="6" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="x3" range="0" weight="5" type="S" > <description ></description > </weapon> <weapon mod="0" name="Javelin" cost="1" category="Simple Weapons-Ranged" size="Medium" damage="1d6" critical="x2" range="30" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" name="Kama" cost="2" category="Exotic Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="2" type="S" > <description ></description > </weapon> <weapon mod="0" name="Kama, halfling" cost="2" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="1" type="S" > <description ></description > </weapon> <weapon mod="0" name="Katana" cost="400" category="Asian Weapons-Melee" size="Large" damage="1d10" critical="19-20/x2" range="0" weight="6" type="S" > <description ></description > </weapon> <weapon mod="0" name="Kukri" cost="8" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="18-29/x2" range="0" weight="3" type="S" > <description ></description > </weapon> <weapon mod="0" name="Kusari-gama" cost="10" category="Asian Weapons-Melee" size="Medium" damage="1d6" critical="x2" range="0" weight="3" type="S" > <description ></description > </weapon> <weapon mod="0" name="Lance, heavy" cost="10" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x3" range="0" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" name="Lance, light" cost="6" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="x3" range="0" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" name="Laser pistol" cost="-1" category="Futuristic Weapons-Ranged" size="Small" damage="2d10" critical="x2" range="100" weight="2" type="Special" > <description ></description > </weapon> <weapon mod="0" name="Laser rifle" cost="-1" category="Futuristic Weapons-Ranged" size="Medium" damage="3d20" critical="x2" range="200" weight="7" type="Special" > <description ></description > </weapon> <weapon mod="0" name="Longbow" cost="75" category="Martial Weapons-Ranged" size="Large" damage="1d8" critical="x3" range="100" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Longbow, composite" cost="100" category="Martial Weapons-Ranged" size="Large" damage="1d8" critical="x3" range="110" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Longspear" cost="5" category="Martial Weapons-Melee" size="Large" damage="1d8" critical="x3" range="0" weight="9" type="P" > <description ></description > </weapon> <weapon mod="0" name="Longsword" cost="15" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="19-20/x2" range="0" weight="4" type="S" > <description ></description > </weapon> <weapon mod="0" name="Mace, heavy" cost="12" category="Simple Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="0" weight="12" type="B" > <description ></description > </weapon> <weapon mod="0" name="Mace, light" cost="5" category="Simple Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="6" type="B" > <description ></description > </weapon> <weapon mod="0" name="Morningstar" cost="8" category="Simple Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="0" weight="8" type="B&P" > <description ></description > </weapon> <weapon mod="0" name="Musket" cost="500" category="Renaissance Weapons-Ranged" size="Medium" damage="1d12" critical="x3" range="150" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" name="Net" cost="20" category="Exotic Weapons-Ranged" size="Medium" damage="0" critical="0" range="10" weight="10" type="-" > <description ></description > </weapon> <weapon mod="0" name="Nunchaku" cost="2" category="Exotic Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="2" type="S" > <description > </description > </weapon> <weapon mod="0" name="Nunchaku, halfling" cost="2" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="1" type="B" > <description ></description > </weapon> <weapon mod="0" name="Pick, heavy" cost="8" category="Martial Weapons-Melee" size="Medium" damage="1d6" critical="x4" range="0" weight="6" type="P" > <description ></description > </weapon> <weapon mod="0" name="Pick, light" cost="4" category="Martial Weapons-Melee" size="Small" damage="1d4" critical="x4" range="0" weight="4" type="P" > <description ></description > </weapon> <weapon mod="0" name="Pistol" cost="250" category="Renaissance Weapons-Ranged" size="Small" damage="1d10" critical="x3" range="50" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Pistol, automatic" cost="-1" category="Modern Weapons-Ranged" size="Small" damage="1d10" critical="x3" range="150" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" name="Pistol, revolver" cost="-1" category="Modern Weapons-Ranged" size="Small" damage="1d10" critical="x3" range="100" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Quarterstaff" cost="0" category="Simple Weapons-Melee" size="Large" damage="1d6" critical="x2" range="0" weight="4" type="B" > <description ></description > </weapon> <weapon mod="0" name="Ranseur" cost="10" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="x3" range="0" weight="15" type="P" > <description ></description > </weapon> <weapon mod="0" name="Rapier" cost="20" category="Martial Weapons-Melee" size="Medium" damage="1d6" critical="18-20/x2" range="0" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Rifle, automatic" cost="-1" category="Modern Weapons-Ranged" size="Medium" damage="1d12" critical="x3" range="250" weight="12" type="P" > <description ></description > </weapon> <weapon mod="0" name="Rifle, repeater" cost="-1" category="Modern Weapons-Ranged" size="Medium" damage="1d12" critical="x3" range="200" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" name="Sap" cost="1" category="Martial Weapons-Melee" size="Small" damage="1d6s" critical="x2" range="0" weight="3" type="B" > <description ></description > </weapon> <weapon mod="0" name="Scattergun" cost="-1" category="Modern Weapons-Ranged" size="Medium" damage="*" critical="*" range="10" weight="10" type="P" > <description ></description > </weapon> <weapon mod="0" name="Scimitar" cost="15" category="Martial Weapons-Melee" size="Medium" damage="1d6" critical="18-20/x2" range="0" weight="4" type="S" > <description ></description > </weapon> <weapon mod="0" name="Scythe" cost="18" category="Martial Weapons-Melee" size="Large" damage="2d4" critical="x4" range="0" weight="12" type="P&S" > <description ></description > </weapon> <weapon mod="0" name="Shortbow" cost="30" category="Martial Weapons-Ranged" size="Medium" damage="1d6" critical="x3" range="60" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" name="Shortbow, composite" cost="75" category="Martial Weapons-Ranged" size="Medium" damage="1d6" critical="x3" range="70" weight="2" type="P" > <description ></description > </weapon> <weapon mod="0" name="Shortspear" cost="2" category="Simple Weapons-Melee" size="Large" damage="1d8" critical="x3" range="20" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" name="Shuriken" cost="1" category="Exotic Weapons-Ranged" size="Tiny" damage="1" critical="x2" range="100" weight="0" type="P" > <description ></description > </weapon> <weapon mod="0" name="Siangham" cost="3" category="Exotic Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Siangham, halfling" cost="2" category="Exotic Weapons-Melee" size="Tiny" damage="1d4" critical="x2" range="0" weight="1" type="P" > <description ></description > </weapon> <weapon mod="0" name="Sickle" cost="6" category="Simple Weapons-Melee" size="Small" damage="1d6" critical="x2" range="0" weight="3" type="S" > <description ></description > </weapon> <weapon mod="0" name="Sling" cost="1d4" category="Simple Weapons-Ranged" size="Small" damage="1d4" critical="x2" range="50" weight="0" type="B" > <description ></description > </weapon> <weapon mod="0" name="Sword, bastard" cost="35" category="Exotic Weapons-Melee" size="Medium" damage="1d10" critical="19-20/x2" range="0" weight="10" type="S" > <description ></description > </weapon> <weapon mod="0" name="Sword, short" cost="10" category="Martial Weapons-Melee" size="Small" damage="1d6" critical="19-20/x2" range="0" weight="3" type="P" > <description ></description > </weapon> <weapon mod="0" name="Sword, two-bladed" cost="100" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d8" critical="19-20/X2" range="0" weight="30" type="S" > <description ></description > </weapon> <weapon mod="0" name="Trident" cost="15" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x2" range="10" weight="5" type="P" > <description ></description > </weapon> <weapon mod="0" name="Urgrosh, dwarven" cost="50" category="Exotic Weapons-Melee" size="Large" damage="1d8/1d6" critical="x3" range="0" weight="15" type="S&P" > <description ></description > </weapon> <weapon mod="0" name="Wakizashi" cost="300" category="Asian Weapons-Melee" size="Small" damage="1d6" critical="19-20/x2" range="0" weight="3" type="S" > <description ></description > </weapon> <weapon mod="0" name="Waraxe, dwarven" cost="30" category="Exotic Weapons-Melee" size="Medium" damage="1d10" critical="x3" range="0" weight="15" type="S" > <description ></description > </weapon> <weapon mod="0" name="Warhammer" cost="12" category="Martial Weapons-Melee" size="Medium" damage="1d8" critical="x3" range="0" weight="8" type="B" > <description ></description > </weapon> <weapon mod="0" name="Whip" cost="1" category="Exotic Weapons-Ranged" size="Small" damage="1d2s" critical="x2" range="15" weight="2" type="S" > <description ></description > </weapon> </weapons> --- NEW FILE: d20feats.xml --- <feats> <feat name='Blank' type='None' /> <feat name='Alertness' type='General' /> <feat name='Ambidexterity' type='General' /> <feat name='Armor Proficiency (heavy)' type='General' /> <feat name='Armor Proficiency (light)' type='General' /> <feat name='Armor Proficiency (medium)' type='General' /> <feat name='Blind-Fight' type='General' /> <feat name='Cleave' type='General' /> <feat name='Combat Reflexes' type='General' /> <feat name='Deflect Arrows' type='General' /> <feat name='Dodge' type='General' /> <feat name='Endurance' type='General' /> <feat name='Exotic Weapon Proficiency' type='General' /> <feat name='Expertise' type='General' /> <feat name='Point Blank Shot.' type='General' /> <feat name='Great Cleave' type='General' /> <feat name='Great Fortitude' type='General' /> <feat name='Improved Bull Rush' type='General' /> <feat name='Improved Critical' type='General' /> <feat name='Improved Disarm' type='General' /> <feat name='Improved Initiative' type='General' mod='4' /> <feat name='Improved Trip' type='General' /> <feat name='Improved Two-Weapon Fighting' type='General' /> <feat name='Improved Unarmed Strike' type='General' /> <feat name='Iron Will' type='General' /> <feat name='Leadership' type='General' /> <feat name='Lightning Reflexes' type='General' /> <feat name='Martial Weapon Proficiency' type='General' /> <feat name='Mobility' type='General' /> <feat name='Mounted Archery' type='General' /> <feat name='Mounted Combat' type='General' /> <feat name='Point Blank Shot' type='General' /> <feat name='Power Attack' type='General' /> <feat name='Precise Shot' type='General' /> <feat name='Quick Draw' type='General' /> <feat name='Rapid Shot' type='General' /> <feat name='Ride-By Attack' type='General' /> <feat name='Run' type='General' /> <feat name='Shield Proficiency' type='General' /> <feat name='Shot on the Run' type='General' /> <feat name='Simple Weapon Proficiency' type='General' /> <feat name='Skill Focus' type='General' /> <feat name='Spirited Charge' type='General' /> <feat name='Spring Attack' type='General' /> <feat name='Stunning Fist' type='General' /> <feat name='Sunder' type='General' /> <feat name='Toughness' type='General' /> <feat name='Track' type='General' /> <feat name='Trample' type='General' /> <feat name='Two-Weapon Fighting' type='General' /> <feat name='Weapon Finesse' type='General' /> <feat name='Weapon Focus' type='General' /> <feat name='Weapon Specialization' type='General' /> <feat name='Brew Potion' type='General' /> <feat name='Combat Casting' type='General' /> <feat name='Craft Magic Arms and Armor' type='General' /> <feat name='Craft Rod' type='General' /> <feat name='Craft Staff' type='General' /> <feat name='Craft Wand' type='General' /> <feat name='Craft Wondrous Item' type='General' /> <feat name='Empower Spell' type='General' /> <feat name='Enlarge Spell' type='General' /> <feat name='Extend Spell' type='General' /> <feat name='Extra Turning' type='General' /> <feat name='Forge Ring' type='General' /> <feat name='Heighten Spell' type='General' /> <feat name='Maximize Spell' type='General' /> <feat name='Quicken Spell' type='General' /> <feat name='Scribe Scroll' type='General' /> <feat name='Silent Spell' type='General' /> <feat name='Spell Focus' type='General' /> <feat name='Spell Mastery' type='General' /> <feat name='Spell Penetration' type='General' /> <feat name='Still Spell' type='General' /> </feats> --- NEW FILE: d20classes.xml --- <classes> <class level="1" name="Arcane Archer" hd="d8" > <requirements>Race: Elf or half-elf. Base Attack Bonus: +6. Feats: Weapon Focus (any bow other than a crossbow), Point Blank Shot, Precise Shot. Spellcasting: Ability to cast 1st-level arcane spells.</requirements> <alignment>Any</alignment> <wa_proficiency>An arcane archer is proficient with all simple and martial weapons, light armor, medium armor, and shields.</wa_proficiency> <features>Enchant Arrow: At 1st level, every nonmagical arrow an arcane archer nocks and lets fly becomes enchanted, gaining a +1 enhancement bonus. An archer's magic arrows only function for her. For every two levels of arcane archer the character advances past 1st level in the prestige class, the magic arrows she creates gain +1 greater potency. Imbue Arrow: At 2nd level, an arcane archer gains this spell-like ability, allowing her to place an area spell upon an arrow. When the arrow is fired, the spell's area is centered upon where the arrow lands, even if the spell could normally be centered only on the caster. This ability allows the archer to use the bow's range rather than the spell's range. It takes a standard action to cast the spell and fire the arrow. The arrow must be fired in the round the spell is cast, or the spell is wasted. Seeker Arrow: At 4th level, the arcane archer can launch an arrow once per day at a target known to her within range, and the arrow travels to the target, even around corners. Only an unavoidable obstacle or the end of the arrow's range prevents the arrow's flight. This ability negates cover and concealment modifiers, but otherwise the attack is rolled normally. This is a spell-like ability. (Shooting the arrow is part of the action.) Phase Arrow: At 6th level, the arcane archer can launch an arrow once per day at a target known to her within range, and the arrow travels to the target in a straight path, passing through any nonmagical barrier or wall in its way. (A wall of force, a wall of fire, or the like stops the arrow.) This ability negates cover, concealment, and even armor modifiers, but otherwise the attack is rolled normally. This is a spell-like ability. (Shooting the arrow is part of the action.) Hail of Arrows: In lieu of her regular attacks, once per day the 8th-level arcane archer can fire an arrow at each and every target within range, to a maximum of one target for every arcane archer level she has earned. Each attack uses the archer's primary attack bonus, and each enemy may only be targeted by a single arrow. This is a spell-like ability. Arrow of Death: At 10th level, the arcane archer can enchant an arrow of death that forces the target, if damaged by the arrow's attack, to make a Fortitude save (DC 20) or be slain immediately. It takes one day to create an arrow of death, and the arrow only functions for the arcane archer who created it. The enchantment lasts no longer than one year, and the archer can only have one such arrow in existence at a time.</features> </class> <class level="1" name="Assassin" hd="d6" > <requirements>Move Silently: 8 ranks. Hide: 8 ranks. Disguise: 4 ranks. Special: In addition, he must kill someone for no other reason than to join the assassins.</requirements> <alignment>Evil</alignment> <wa_proficiency>Assassins are proficient with the crossbow (hand, light, or heavy), dagger (any type), dart, rapier, sap, shortbow (normal and composite), and short sword. Assassins are proficient with light armor but not with shields.</wa_proficiency> <features>Sneak Attack: Any time the assassin's target would be denied her Dexterity bonus to AC (whether she actually has a Dexterity bonus or not), the assassin's attack deals +1d6 points of damage. This extra damage increases by +1d6 points every other level (+2d6 at 3rd level, +3d6 at 5th level, and so on). Should the assassin score a critical hit with a sneak attack, this extra damage is not multiplied. It takes precision and penetration to hit a vital spot, so ranged attacks can only count as sneak attacks if the target is 30 feet away or less. With a sap or an unarmed strike, the assassin can make a sneak attack that deals subdual damage instead of normal damage. He cannot use a weapon that deals normal damage to deal subdual damage in a sneak attack, not even with the usual -4 penalty, because he must make optimal use of his weapon in order to execute the sneak attack. An assassin can only sneak attack living creatures with discernible anatomies-undead, constructs, oozes, plants, and incorporeal creatures lack vital areas to attack. Additionally, any creature immune to critical hits is similarly immune to sneak attacks. Also, the assassin must also be able to see the target well enough to pick out a vital spot and must be able to reach a vital spot. The assassin cannot sneak attack while striking at a creature with concealment or by striking the limbs of a creature whose vitals are beyond reach. If an assassin gets a sneak attack bonus from another source (such as rogue levels), the bonuses to damage stack. Death Attack: If the assassin studies his victim for 3 rounds and then makes a sneak attack with a melee weapon that successfully deals damage, the sneak attack has the additional effect of possibly either paralyzing or killing the target (assassin's choice). While studying the victim, the assassin can undertake other actions so long as his attention stays focused on the target and the target does not detect the assassin or recognize the assassin as an enemy. If the victim of such an attack fails her Fortitude saving throw (DC 10 + the assassin's class level + the assassin's Intelligence modifier) against the kill effect, she dies. If the saving throw fails against the paralysis effect, the victim's mind and body become enervated, rendering her completely helpless and unable to act for 1d6 rounds plus 1 round per level of the assassin. If the victim's saving throw succeeds, the attack is just a normal sneak attack. Once the assassin has completed the 3 rounds of study, he must make the death attack within the next 3 rounds. If a death attack is attempted and fails (the victim makes her save) or if the assassin does not launch the attack within 3 rounds of completing the study, 3 new rounds of study are required before he can attempt another death attack. Poison Use: Assassins are trained in the use of poison and never risk accidentally poisoning themselves when applying poison to a blade. Spells: Beginning at 1st level, an assassin gains the ability to cast a small number of arcane spells. To cast a spell, the assassin must have an Intelligence score of at least 10 + the spell's level, so an assassin with an Intelligence of 10 or lower cannot cast these spells. Assassin bonus spells are based on Intelligence, and saving throws against these spells have a DC of 10 + spell level + the assassin's Intelligence modifier (if any). When the assassin gets 0 spells of a given level, such as 0 1st-level spells at 1st level, the assassin gets only bonus spells. An assassin without a bonus spell for that level cannot yet cast a spell of that level. The assassin's spell list appears below. An assassin prepares and casts spells just as a wizard does. Saving Throw Bonus vs. Poison: Assassins train with poisons of all types and slowly grow more and more resistant to their effects. This is reflected by a natural saving throw bonus to all poisons gained at 2nd level that increases by +1 for every two levels the assassin gains (+1 at 2nd level, +2 at 4th level, +3 at 6th level, and so on). Uncanny Dodge: Starting at 2nd level, the assassin gains the extraordinary ability to react to danger before his senses would normally allow him to even be aware of it. At 2nd level and above, he retains his Dexterity bonus to AC (if any) regardless of being caught flat-footed or struck by an invisible attacker. (He still loses his Dexterity bonus to AC if immobilized.) At 5th level, the assassin can no longer be flanked, since he can react to opponents on opposite sides of him as easily as he can react to a single attacker. This defense denies rogues the ability to use flank attacks to sneak attack the assassin. The exception to this defense is that a rogue at least four levels higher than the assassin can flank him (and thus sneak attack him). At 10th level, the assassin gains an intuitive sense that alerts him to danger from traps, giving him a +1 bonus to Reflex saves made to avoid traps. If the assassin has another class that grants the uncanny dodge ability, add together all the class levels of the classes that grant the ability and determine the character's uncanny dodge ability on that basis. Assassins choose their spells from the following list: 1st level-change self, detect poison, ghost sound, obscuring mist, spider climb. 2nd level-alter self, darkness, pass without trace, undetectable alignment. 3rd level-deeper darkness, invisibility, misdirection, nondetection. 4th level-dimension door, freedom of movement, improved invisibility, poison.</features> </class> <class level="1" name="Barbarian" hd="d12" > <requirements>None</requirements> <alignment>Nonlawful</alignment> <wa_proficiency>A barbarian is proficient with all simple and martial weapons, light armor, medium armor, and shields.</wa_proficiency> <features>Barbarian Rage: Barbarian temporarily gains +4 to Strength, +4 to Constitution, and a +2 morale bonus on Will saves, but suffers a -2 penalty to AC. The increase in Constitution increases the barbarian's hit points by 2 points per level, but these hit points go away at the end of the rage when the Constitution score drops back to normal. While raging, a barbarian cannot use skills or abilities that require patience and concentration. (The only class skills he can't use while raging are Craft, Handle Animal, and Intuit Direction.) He can use any feat he might have except for Expertise, item creation feats, metamagic feats, and Skill Focus (if it's tied to a skill that requires patience or concentration). A fit of rage lasts for a number of rounds equal to 3 + the character's (newly improved) Constitution modifier. The barbarian may prematurely end the rage voluntarily. At the end of the rage, the barbarian is fatigued (-2 to Strength, -2 to Dexterity, can't charge or run) for the duration of that encounter (unless the barbarian is 20th level, when this limitation no longer applies). The barbarian can only fly into a rage once per encounter, and only a certain number of times per day (determined by level). Entering a rage takes no time itself, but the barbarian can only do it during his action. Starting at 15th level, the barbarian's rage bonuses become +6 to Strength, +6 to Constitution, and a +3 morale bonus to Will saves. (The AC penalty remains at -2.) Fast Movement: The barbarian has a speed faster than the norm for his race by +10 feet when wearing no armor, light armor, or medium armor (and not carrying a heavy load). Uncanny Dodge: At 2nd level and above, the barbarian retains his Dexterity bonus to AC (if any) if caught flat-footed or struck by an invisible attacker. At 5th level, the barbarian can no longer be flanked. The exception to this defense is that a rogue at least four levels higher than the barbarian can still flank. At 10th level, the barbarian gains a +1 bonus to Reflex saves made to avoid traps and a +1 dodge bonus to AC against attacks by traps. At 13th level, these bonuses rise to +2. At 16th, they rise to +3, and at 19th they rise to +4. Damage Reduction: Starting at 11th level, the barbarian gains the extraordinary ability to shrug off some amount of injury from each blow or attack. Subtract 1 from the damage the barbarian takes each time the barbarian is dealt damage. At 14th level, this damage reduction rises to 2. At 17th, it rises to 3. At 20th, it rises to 4. Damage reduction can reduce damage to 0 but not below 0. Illiteracy: Barbarians are the only characters that do not automatically know how to read and write. A barbarian must spend 2 skill points to gain the ability to read and write any language the barbarian is able to speak. Ex-Barbarians: A barbarian who becomes lawful loses the ability to rage and cannot gain more levels as a barbarian. The barbarian retains all the other benefits of the class.</features> </class> <class level="1" name="Bard" hd="d6" > <requirements>None</requirements> <alignment>Nonlawful</alignment> <wa_proficiency>A bard is proficient with all simple weapons. Additionally, the bard is proficient with one of the following weapons: longbow, composite longbow, longsword, rapier, sap, short composite bow, short sword, shortbow, or whip. Bards are proficient with light armor, medium armor, and shields.</wa_proficiency> <features>Spells: A bard casts arcane spells. The bard casts these spells without needing to memorize them beforehand or keep a spellbook. Bards receive bonus spells for high Charisma, and to cast a spell a bard must have a Charisma score at least equal to 10 + the level of the spell. The Difficulty Class for a saving throw against a bard's spell is 10 + the spell's level + the bard's Charisma modifier. Bardic Music: Once per day per level, a bard can use song or poetics to produce magical effects on those around him or her. While these abilities fall under the category of bardic music, they can include reciting poetry, chanting, singing lyrical songs, singing melodies, whistling, playing an instrument, or playing an instrument in combination with some spoken performance. As with casting a spell with a verbal component, a deaf bard suffers a 20% chance to fail with bardic music. If the bard fails, the attempt still counts against the daily limit. The Bardic Music effects are: * Inspire Courage: A bard with 3 or more ranks in Perform can to inspire courage in his or her allies. To be affected, an ally must hear the bard sing for a full round. The effect lasts as long as the bard sings and for 5 rounds after the bard stops singing (or 5 rounds after the ally can no longer hear the bard). While singing, the bard can fight but cannot cast spells, activate magic items by spell completion (such as scrolls), or activate magic items by magic word (such as wands). Affected allies receive a +2 morale bonus to saving throws against charm and fear effects and a +1 morale bonus to attack and weapon damage rolls. Inspire courage is a supernatural, mind-affecting ability. * Countersong: A bard with 3 or more ranks in Perform can counter magical effects that depend on sound (but not spells that simply have verbal components). As with inspire courage, a bard may sing, play, or recite a countersong while taking other mundane actions, but not magical actions. Each round of the countersong, the bard makes a Perform check. Any creature within 30 feet of the bard (including the bard) who is affected by a sonic or language-dependent magical attack may use the bard's Perform check result in place of his saving throw if, after rolling the saving throw, the Perform check result proves to be better. The bard may keep up the countersong for 10 rounds. Countersong is a supernatural ability. * Fascinate: A bard with 3 or more ranks in Perform can cause a single creature to become fascinated with him. The creature to be fascinated must be able to see and hear the bard and must be within 90 feet. The bard must also see the creature. The creature must be able to pay attention to the bard. The distraction of a nearby combat or other dangers prevents the ability from working. The bard makes a Perform check, and the target can negate the effect with a Will saving throw equal to or greater than the bard's check result. If the saving throw succeeds, the bard cannot attempt to fascinate that creature again for 24 hours. If the saving throw fails, the creature sits quietly and listens to the song for up to 1 round per level of the bard. While fascinated, the target's Spot and Listen checks suffer a -4 penalty. Any potential threat (such as an ally of the bard moving behind the fascinated creature) allows the fascinated creature a second saving throw against a new Perform check result. Any obvious threat, such as casting a spell, drawing a sword, or aiming, automatically breaks the effect. While fascinating (or attempting to fascinate) a creature, the bard must concentrate, as if casting or maintaining a spell. Fascinate is a spell-like, mind- affecting charm ability. * Inspire Competence: A bard with 6 or more ranks in Perform can help an ally succeed at a task. The ally must be able to see and hear the bard and must be within 30 feet. The bard must also see the creature. The ally gets a +2 competence bonus on his skill checks with a particular skill as long as he or she continues to hear the bard's music. The DM may rule that certain uses of this ability are infeasible. The bard can maintain the effect for 2 minutes (long enough for the ally to take 20). Inspire competence is a supernatural, mind-affecting ability. * Suggestion: A bard with 9 or more ranks in Perform can make a suggestion (as the spell) to a creature that he has already fascinated (see above). The suggestion doesn't count against the bard's daily limit on bardic music performances (one per day per level), but the fascination does. A Will saving throw (DC 13 + the bard's Charisma modifier) negates the effect. Suggestion is a spell-like, mind-affecting charm ability. * Inspire Greatness: A bard with 12 or more ranks in Perform can inspire greatness in another creature. For every three levels the bard attains beyond 9th, the bard can inspire greatness in one additional creature. To inspire greatness, the bard must sing and the creature must hear the bard sing for a full round, as with inspire courage. The creature must also be within 30 feet. A creature inspired with greatness gains temporary Hit Dice, attack bonuses, and saving throw bonuses as long as he or she hears the bard continue to sing and for 5 rounds thereafter. (All these bonuses are competence bonuses.) The target gains the following boosts: * +2 Hit Dice (d10s that grant temporary hit points). * +2 competence bonus on attacks. * +1 competence bonus on Fortitude saves. Apply the target's Constitution modifier, if any, to each bonus Hit Die. These extra Hit Dice count as regular Hit Dice for determining effects such as the sleep spell. Inspire greatness is a supernatural, mind-affecting enchantment ability. Bardic Knowledge: A bard may make a special bardic knowledge check with a bonus equal to his level + his Intelligence modifier to see whether he knows some relevant information about local notable people, legendary items, or noteworthy places. This check will not reveal the powers of a magic item but may give a hint as to its general function. The bard may not take 10 or take 20 on this check; this sort of knowledge is essentially random. The DM will determine the Difficulty Class of the check by referring to the table below. DC Type of Knowledge -- -----------------10 Common, known by at least a substantial minority of the local population. 20 Uncommon but available, known by only a few people in the area. 25 Obscure, known by few, hard to come by. 30 Extremely obscure, known by very few, possibly forgotten by most who once knew it, possibly known only by those who don't understand the significance of the knowledge. Ex-Bards: A bard who becomes lawful in alignment cannot progress in levels as a bard, though he retains all his bard abilities.</features> </class> <class level="1" name="Blackguard" hd="d10" > <requirements>Base Attack Bonus: +6. Knowledge (religion): 2 ranks. Hide: 5 ranks. Feats: Cleave, Sunder. Special: The blackguard must have made peaceful contact with an evil outsider who was summoned by him or someone else to have contracted the taint of true evil.</requirements> <alignment>Any Evil</alignment> <wa_proficiency>Blackguards are proficient with all simple and martial weapons, with all types of armor, and with shields.</wa_proficiency> <features>Detect Good: At will, the blackguard can detect good as a spell-like ability. This ability duplicates the effects of the spell detect good. Poison Use: Blackguards are skilled in the use of poison and never risk accidentally poisoning themselves when applying poison to a blade. Dark Blessing: A blackguard applies his Charisma modifier (if positive) as a bonus to all saving throws. Spells: Beginning at 1st level, a blackguard gains the ability to cast a small number of divine spells. To cast a spell, the blackguard must have a Wisdom score of at least 10 + the spell's level, so a blackguard with a Wisdom of 10 or lower cannot cast these spells. Blackguard bonus spells are based on Wisdom, and saving throws against these spells have a DC of 10 + spell level + the blackguard's Wisdom modifier. When the blackguard gets 0 spells of a given level, such as 0 1st-level spells at 1st level, he gets only bonus spells. (A blackguard without a bonus spell for that level cannot yet cast a spell of that level.) The blackguard's spell list appears below. A blackguard has access to any spell on the list and can freely choose which to prepare, just like a cleric. A blackguard prepares and casts spells just as a cleric does (though the blackguard cannot spontaneously cast cure or inflict spells). Smite Good: Once a day, a blackguard of 2nd level or higher may attempt to smite good with one normal melee attack. He adds his Charisma modifier (if positive) to his attack roll and deals 1 extra point of damage per class level. For example, a 9th-level blackguard armed with a longsword would deal 1d8+9 points of damage, plus any additional bonuses from high Strength or magical effects that normally apply. If the blackguard accidentally smites a creature that is not good, the smite has no effect but it is still used up for that day. Smite good is a supernatural ability. Aura of Despair: Beginning at 3rd level, the blackguard radiates a malign aura that causes enemies within 10 feet of him to suffer a -2 morale penalty on all saving throws. Aura of despair is a supernatural ability. Command Undead: When a blackguard reaches 3rd level, he gains the supernatural ability to command and rebuke undead. He commands undead as would a cleric of two levels lower. Sneak Attack: If a blackguard can catch an opponent when she is unable to defend herself effectively from his attack, he can strike a vital spot for extra damage. Basically, any time the blackguard's target would be denied her Dexterity bonus to AC (whether she actually has a Dexterity bonus or not), the blackguard's attack deals +1d6 points of damage at 4th level and an additional +1d6 points for every three levels thereafter (+2d6 at 7th level, +3d6 at 10th level, and so on). Should the blackguard score a critical hit with a sneak attack, this extra damage is not multiplied. Ranged attacks only count as sneak attacks if the target is 30 feet away or less. A blackguard cannot make a sneak attack to deal subdual damage. The blackguard must be able to see the target well enough to pick out a vital spot and must be able to reach a vital spot. He cannot sneak attack while striking at a creature with concealment or by striking the limbs of a creature whose vitals are beyond reach. A blackguard can only sneak attack living creatures with discernible anatomies. Undead, constructs, oozes, plants, and incorporeal creatures lack vital areas to attack. Additionally, any creature immune to critical hits is not subject to sneak attacks. If a blackguard gets a sneak attack bonus from another source (such as rogue levels), the bonuses to damage stack. Blackguards choose their spells from the following list: 1st level-cause fear, cure light wounds, doom, inflict light wounds, magic weapon, summon monster I*. 2nd level-bull's strength, cure moderate wounds, darkness, death knell, inflict moderate wounds, shatter, summon monster II*. 3rd level-contagion, cure serious wounds, deeper darkness, inflict serious wounds, protection from elements, summon monster III*. 4th level-cure critical wounds, freedom of movement, inflict critical wounds, poison, summon monster IV*. *Evil creatures only. Fallen Paladins Blackguards who possess levels of paladin (that is to say, are now ex-paladins) gain extra abilities the more levels of paladin they possess. Those who have tasted the light of goodness and justice and turned away make the foulest villains.</features> </class> <class level="1" name="Cleric" hd="d8" > <requirements>None</requirements> <alignment>Varies by deity. A cleric's alignment must be within one step of his deity's, and it may not be neutral unless the deity's alignment is neutral.</alignment> <wa_proficiency>Clerics are proficient with all simple weapons. Clerics are proficient with all types of armor (light, medium, and heavy) and with shields.</wa_proficiency> <features>Some deities have favored weapons, and clerics consider it a point of pride to wield them. A cleric whose deity's favored weapon is a martial weapon and who chooses War as one of his domains receives the Martial Weapon Proficiency feat related to that weapon for free, as well as the Weapon Focus feat related to that weapon. Spells: A cleric casts divine spells. A cleric may prepare and cast any spell on the cleric spell list, provided he can cast spells of that level. The Difficulty Class for a saving throw against a cleric's spell is 10 + the spell's level + the cleric's Wisdom modifier. Each cleric must choose a time at which he must spend an hour each day in quiet contemplation or supplication to regain his daily allotment of spells. Time spent resting has no effect on whether a cleric can prepare spells. In addition to his standard spells, a cleric gets one domain spell of each spell level, starting at 1st. When a cleric prepares a domain spell, it must come from one of his two domains. Deity, Domains, and Domain Spells: Choose a deity for your cleric. The cleric's deity influences his alignment, what magic he can perform, his values, and how others see him. Choose two from among the deity's domains for your cleric's domains. You can only select an alignment domain (such as Good) for your cleric if his alignment matches that domain. If your cleric is not devoted to a particular deity, you still select two domains to represent his spiritual inclinations and abilities (but the restriction on alignment domains still applies). Each domain gives your cleric access to a domain spell at each spell level, from 1st on up, as well as a granted power. Your cleric gets the granted powers of all the domains selected. With access to two domain spells at a given spell level, a cleric prepares one or the other each day. If a domain spell is not on the Cleric Spells list, a cleric can only prepare it in his domain slot. Spontaneous Casting: Good clerics (and neutral clerics of good deities) can channel stored spell energy into healing spells that they haven't prepared ahead of time. The cleric can "lose" a prepared spell in order to cast any cure spell of the same level or lower (a cure spell is any spell with "cure" in its name). An evil cleric (or a neutral cleric of an evil deity), on the other hand, can't convert prepared spells to cure spells but can convert them to inflict spells (an inflict spell is one with "inflict" in the title). A cleric who is neither good nor evil and whose deity is neither good nor evil can convert spells either to cure spells or to inflict spells (player's choice), depending on whether the cleric is more proficient at wielding positive or negative energy. Once the player makes this choice, it cannot be reversed. This choice also determines whether the neutral cleric turns or commands undead (see below). A cleric can't use spontaneous casting to convert domain spells into cure or inflict spells. These spells arise from the particular powers of the cleric's deity, not divine energy in general. Chaotic, Evil, Good, and Lawful Spells: A cleric can't cast spells of an alignment opposed to his own or to his deity's. Turn or Rebuke Undead: A good cleric (or a neutral cleric who worships a good deity) has the supernatural ability to turn undead. Evil clerics (and neutral clerics who worship evil deities) can rebuke such creatures. Neutral clerics of neutral deities can do one or the other (player's choice), depending on whether the cleric is more proficient at wielding positive or negative energy. Once the player makes this choice, it cannot be reversed. This choice also determines whether the neutral cleric can cast spontaneous cure or inflict spells (see above). A cleric may attempt to turn or rebuke undead a number of times per day equal to three plus his Charisma modifier. Extra Turning: As a feat, a cleric may take Extra Turning. This feat allows the cleric to turn undead four more times per day than normal. A cleric can take this feat multiple times, gaining four extra daily turning attempts each time. Bonus Languages: A cleric's list of bonus languages includes Celestial, Abyssal, and Infernal, in addition to the bonus languages available to the character because of his race. Ex-Clerics: A cleric who grossly violates the code of conduct expected by his god (generally acting in ways opposed to the god's alignment or purposes) loses all spells and class features and cannot gain levels as a cleric of that god until he atones.</features> </class> <class level="1" name="Druid" hd="d8" > <requirements>None</requirements> <alignment>Neutral good, lawful neutral, neutral, chaotic neutral, or neutral evil.</alignment> <wa_proficiency>Druids are proficient with the following weapons: club, dagger, dart, halfspear, longspear, quarterstaff, scimitar, sickle, shortspear, and sling. Their spiritual oaths prohibit them from using weapons other than these. They are proficient with light and medium armors but are prohibited from wearing metal armor (thus, they may wear only padded, leather, or hide armor). They are skilled with shields but must use only wooden ones. </wa_proficiency> <features>A druid who wears prohibited armor or wields a prohibited weapon is unable to use any of her magical powers while doing so and for 24 hours thereafter. (Note: A druid can use wooden items that have been altered by the ironwood spell so that they function as though they were steel.) Spells: A druid casts divine spells. A druid may prepare and cast any spell on the druid spell list provided she can cast spells of that level. She prepares and casts spells the way a cleric does (though she cannot lose a prepared spell to cast a cure spell in its place). To prepare or cast a spell, a druid must have a Wisdom score of at least 10 + the spell's level. The Difficulty Class for a saving throw against a druid's spell is 10 + the spell's level + the druid's Wisdom modifier. Bonus spells for druids are based on Wisdom. Chaotic, Evil, Good, and Lawful Spells: A druid can't cast spells of an alignment opposed to her own. Bonus Languages: A druid may substitute Sylvan for one of the bonus languages available to her. In addition, a druid knows the Druidic language. This secret language is known only to druids, and druids are forbidden from teaching it to nondruids. Druidic has its own alphabet. Nature Sense: A druid can identify plants and animals (their species and special traits) with perfect accuracy. The druid can determine whether water is safe to drink or dangerous. Animal Companion: A 1st-level druid may begin play with an animal companion. This animal is one that the druid has befriended with the spell animal friendship. Woodland Stride: Starting at 2nd level, a druid may move through natural thorns, briars, overgrown areas, and similar terrain at his or her normal speed and without suffering damage or other impairment. However, thorns, briars, and overgrown areas that are enchanted or magically manipulated to impede motion still affect the druid. Trackless Step: Starting at 3rd level, a druid leaves no trail in natural surroundings and cannot be tracked. Resist Nature's Lure: Starting at 4th level, a druid gains a +4 bonus to saving throws against the spell-like abilities of feys. Wild Shape: At 5th level, a druid gains the spell-like ability to polymorph self into a Small or Medium-size animal (but not a dire animal) and back again once per day. Unlike the standard use of the spell, however, the druid may only adopt one form. As stated in the spell description, the druid regains hit points as if he or she has rested for a day. The druid does not risk the standard penalty for being disoriented while in the wild shape. The druid can use this ability more times per day at 6th, 7th, 10th, 14th, and 18th level, as noted. In addition, the druid gains the ability to take the shape of a Large animal at 8th level, a Tiny animal at 11th level, and a Huge animal at 15th level. At 12th level or higher, she can take the form of a dire animal. At 16th level or higher, the druid may use wild shape to change into a Small, Medium-... [truncated message content] |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:30:28
|
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib/filter In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30002/filter Log Message: Directory /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib/filter added to the repository |
|
From: Digital X. <dig...@us...> - 2006-01-26 17:30:20
|
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv29947/lib Log Message: Directory /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib added to the repository |