[Winopenrpg-developer] openrpg1/orpg/dieroller HOWTO.txt,NONE,1.1 __init__.py,NONE,1.1 d20.py,NONE,1
Status: Inactive
Brought to you by:
digitalxero
|
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] |