#!/usr/bin/python import string import sys import getopt # THACO charts for character classes and monstrous races # [class name, THAC0 at 1st level, 2nd level, .... 20th # level] brief = False thaco_charts = { "fighter": [ 20, 20, 18, 18, 16, 16, 14, 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2], "paladin" : [ 20, 20, 18, 18, 16, 16, 14, 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2], "ranger" : [ 20, 20, 18, 18, 16, 16, 14, 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2], "bard" : [ 20, 20, 18, 18, 16, 16, 14, 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2], "cleric" : [ 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 9, 9], "druid" : [ 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 9, 9], "monk" : [ 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 9, 9], "magic user" : [20, 20, 20, 20, 20, 19, 19, 19, 19, 19, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13], "illusionist" :[20, 20, 20, 20, 20, 19, 19, 19, 19, 19, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13], "thief" : [ 20, 20, 20, 20, 19, 19, 19, 19, 16, 16, 16, 16, 14, 14, 14, 14, 12, 12, 12, 12], "assassin" : [ 20, 20, 20, 20, 19, 19, 19, 19, 16, 16, 16, 16, 14, 14, 14, 14, 12, 12, 12, 12], "monster" : [ 19, 16, 16, 16, 15, 15, 13, 13, 12, 12, 10, 10, 9, 9, 8, 8, 7, 7, 7, 7]} melee_weapon_charts = [ # name AC 10 to 2 (from 10 to 2) ["unarmed", 4, 0, 2, 0, 0, -1, -3, -5, -7], ["battle axe", 2, 1, 1, 0, 0, -1, -2, -2, -2], ["hand axe", 1, 1, 1, 0, 0, -1, -2, -2, -2], ["bardiche", 3, 2, 2, 1, 1, 0, 0, -1, -2], ["bec de corbin", -1, 0, 0, 0, 0, 0, 2, 2, 2], ["bill-guisarme", 0, 0, 1, 0, 0, 0, 0, 0, 0], ["bo stick", 3, 0, 1, 0, -1, -3, -5, -7, -9], ["club", 1, 0, 0, -1, -1, -2, -3, -4, -5], ["dagger", 3, 1, 1, 0, 0, -2, -2, -3, -3], ["fauchard", -1, 1, 0, 0, 0, -1, -1, -2, -2], ["fauchard-fork", 1, 0, 1, 0, 0, 0, -1, -1, -1], ["footman's flail", -1, 1, 1, 1, 1, 2, 1, 2, 2], ["horseman's flail", 0, 1, 1, 1, 0, 0, 0, 0, 0], ["military fork", 1, 0, 1, 1, 0, 0, -1, -2, -2], ["glaive", 0, 0, 0, 0, 0, 0, 0, -1, -1], ["glaive-guisarme", 0, 0, 0, 0, 0, 0, 0, -1, -1], ["guisarme", -1, -1, 0, 0, 0, -1, -1, -2, -2], ["guisarme-voulge", 0, 0, 0, 1, 1, 1, 0, -1, -1], ["halberd", 0, 1, 1, 2, 2, 2, 1, 1, 1], ["lucern hammer", 0, 0, 1, 1, 2, 2, 2, 1, 1], ["hammer", 0, 0, 0, 0, 0, 1, 0, 1, 0], ["jo stick", 2, 0, 1, 0, -1, -2, -4, -6, -8], ["heavy lance", 0, 0, 1, 1, 2, 2, 2, 3, 3], ["light lance", 0, 0, 0, 0, 0, 0, -1, -2, -2], ["medium lance",0, 0, 0, 0, 1, 1, 1, 1, 0], ["footman's mace", -1, 1, 0, 0, 0, 0, 0, 1, 1], ["horseman's mace", 0, 0, 0, 0, 0, 0, 0, 1, 1], ["morning star",2, 2, 1, 1, 1, 1, 1, 1, 0], ["partisan", 0, 0, 0, 0, 0, 0, 0, 0, 0], ["military footman's pick", -2, -1, -1, -1, 0, 1, 1, 2, 2], ["military horseman's pick", -1, -1, -1, 0, 0, 1, 1, 1, 1], ["awl pike", -2, -1, 0, 0, 0, 0, 0, 0, -1], ["ranseur", 1, 0, 0, 0, 0, 0, -1, -1, -2], ["scimitar", 3, 1, 1, 0, 0, -1, -2, -2, -3], ["spear", 0, 0, 0, 0, 0, -1, -1, -1, -2], ["spetum", 2, 1, 0, 0, 0, 0, 0, -1, -2], ["quarter staff", 1, 1, 1, 0, 0, -1, -3, -5, -7], ["bastard sword", 0, 1, 1, 1, 1, 1, 1, 0, 0], ["broad sword", 2, 1, 1, 1, 0, 0, -1, -2, -3], ["long sword", 2, 1, 0, 0, 0, 0, 0, -1, -2], ["short sword", 2, 0, 1, 0, 0, 0, -1, -2, -3], ["two handed sword", 0, 1, 3, 3, 3, 2, 2, 2, 2], ["trident", 1, 0, 1, 0, 0, -1, -1, -2, -3], ["voulge", 0, 0, 0, 1, 1, 1, 0, -1, -1], # placeholder... ["", 0, 0, 0, 0, 0, 0, 0, 0, 0]] ranged_weapon_charts = [ ["hand axe", 1, 0, 0, 0, -1, -1, -2, -3, -4], ["composite long bow", 3, 3, 2, 2, 1, 0, 0, -1, -2], ["composite short bow", 3, 2, 2, 2, 1, 0, -1, -3, -3], ["long bow", 3, 3, 3, 3, 2, 1, 0, 0, -1], ["short bow", 2, 2, 2, 1, 0, 0, -1, -4, -5], ["club", 0, 0, -1, -1, -1, -2, -3, -5, -7], ["heavy crossbow", 4, 4, 4, 3, 3, 2, 1, 0, -1], ["light crossbow", 3, 3, 3, 2, 1, 0, 0, -1, -2], ["dagger", 1, 0, 0, -1, -1, -2, -3, -4, -5], ["dart", 1, 0, 1, 0, -1, -2, -3, -4, -5], ["hammer", 1, 0, 0, 0, 0, 0, 0, -2, -2], ["javelin", 1, 0, 1, 0, -1, -2, -3, -4, -5], ["sling w/bullet", 3, 1, 2, 0, 0, 0, -1, -2, -2], ["sling w/stone", 3, 1, 2, 0, 0, -1, -2, -4, -5], ["spear", 0, 0, 0, 0, -1, -2, -2, -3, -3]] strength_bonuses = [ 0, 0, 0, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] strength_bonuses_18 = { 50 : 1, 75: 2, 90 : 2, 99 : 2, 100: 3} dexterity_bonuses = [ 0, 0, 0, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3] def findWeapon(type, name): wlist = [] if ( type == "ranged" ): wlist = ranged_weapon_charts elif ( type == "melee" ): wlist = melee_weapon_charts else : return [] for weapon in wlist: #print "trying weapon %s (%s) against name %s" % (weapon, weapon[0], name) if ( weapon[0].lower() == name.lower() ): return weapon return [] def strBonus(strength): percent = 0 if ( string.find(str(type(strength)), "str") != -1) : if ( strength.find("/") != -1): tlist = strength.split("/") percent = int(tlist[1]) if ( percent == 0 ) : percent = 100 for perc in strength_bonuses_18.keys(): if ( perc >= percent ): return strength_bonuses_18[perc] else: # print "recursing with strength %d..." % int(strength) return strBonus(int(strength.strip())) else: if ( strength <= 17): # print "trying to return from strength_bonuses (len %d ) %d" % (len(strength_bonuses), strength) return strength_bonuses[strength-1] if ( strength >= 19): return strength_bonuses_18[100] + ((strength-19)/2) return 0 def dexBonus(score): # print "trying to return from dexterity_bonuses ( len %d ) %d" % (len(dexterity_bonuses), score) if ( score <= 18): return dexterity_bonuses[score] else : return dexterity_bonuses[18] + (score-18) class character : weapons = [] strength = 0 dexterity = 0 charts = {} chartSteps = {} pclass = "" level = 0 def __init__ (self, className, str, dex, level): self.pclass = className self.level = level self.weapons = [] self.strength = str self.dexterity = dex self.charts = {} self.chartSteps = {} # work out the THAC0 chart THAC0chart = [] baseTHAC0 = thaco_charts[self.pclass][self.level] for i in range(2, 11): THAC0chart.append(baseTHAC0 - i) #print "wrote thac0 chart %s" % THAC0chart self.charts["Base To Hit"] = THAC0chart def chartWeapon(self, weaponInfo): """chart a weapon for THACO given its type and name""" self.chartSteps[weaponInfo[1]] = {} weaponModifiers = findWeapon(weaponInfo[0], weaponInfo[1]) weaponModifiers.reverse() if ( len(weaponModifiers) == 0): self.chartSteps[weaponInfo[1]]["base weapon modifiers"] = "Unable to chart %s" % weaponInfo[1] return else: self.chartSteps[weaponInfo[1]]["base weapon modifiers"] = weaponModifiers[:9] newChart = [] thacoChart = self.charts["Base To Hit"] #print "weaponModifiers is : %s", str(weaponModifiers) for ac in range(0, 9): #print "trying ac of %d and modifier of %d : length of weaponModifiers is %d, THAC0 is %d" % (ac, ac, len(weaponModifiers), len(thacoChart)) newChart.append( thacoChart[ac] + -weaponModifiers[ac]) self.chartSteps[weaponInfo[1]]["applied to base THAC0"] = newChart newestChart = [] for ac in newChart: if ( weaponInfo[0] == "ranged"): newestChart.append(ac + -dexBonus(self.dexterity)) else: newestChart.append(ac + -strBonus(self.strength)) self.chartSteps[weaponInfo[1]]["after ability bonuses"] = newestChart self.charts[weaponInfo[1]] = newestChart weaponModifiers.reverse() return 0 def printChart(self, weapName, chart, fileHandle, makeDashes = False): printedChart = string.center(weapName, 30) # print "chart is %d elements long : %s" % (len(chart), str(chart)) for ac in range(0, 9): if ( chart[ac] <= 0 and makeDashes): chart[ac] = "-" printedChart += string.center(str(chart[ac]), 4) printedChart += "\n" fileHandle.write(printedChart) def writeCharts(self, fileHandle): global brief for weapon in self.weapons: self.chartWeapon(weapon) ACChart = string.center("1st Edition AD&D To Hit Charts", 70) + "\n" ACChart += string.center("%d level %s : strength %s (to hit +%d) dexterity %d ( RAA +%d)" % (self.level, self.pclass, self.strength, strBonus(self.strength), self.dexterity, dexBonus(self.dexterity)), 70) + "\n\n" ACChart += " "*30 + string.center("OPPONENT ARMOR CLASS", 40) + "\n" ACChart += string.center("WEAPON", 30) for ac in range(2, 11): ACChart += string.center(str(ac), 4) ACChart += "\n" fileHandle.write(ACChart) fileHandle.write("="*70 + "\n") thecharts = self.charts.keys() thecharts.sort() for chart in thecharts: self.printChart(chart.upper(), self.charts[chart], fileHandle, True) #print "brief was %s" % str(brief) if ( (chart != "Base To Hit") and (not brief)): self.printChart("(Base To Hit)", self.charts["Base To Hit"], fileHandle) self.printChart("(base modifiers)", self.chartSteps[chart]["base weapon modifiers"], fileHandle) self.printChart("(applied to Base To Hit)", self.chartSteps[chart]["applied to base THAC0"], fileHandle) self.printChart("(after ability bonuses)", self.chartSteps[chart]["after ability bonuses"], fileHandle) if ( not brief) : fileHandle.write("\n") if ( not brief): fileHandle.write("\n" + string.center("For ACs below 2, add that much to the indicated difficulty.", 70) + "\n") fileHandle.write("\n" + string.center("A difficulty of \"-\" indicates that you can only miss", 70) + "\n" + string.center("this armor class with the given weapon on a botch.", 70) + "\n") def main(argc, argv): global brief longopts = ["class=", "level=", "strength=", "dexterity=", "ranged=", "melee=", "help", "brief", "weaponlist"] shortopts = "c:l:s:d:r:m:hbw" usage = """ 1eHitChart.py (2006 Gamecube) usage: 1eHitChart.py options: --class | -c : class name --level | -l : level number --strength | -s : strength score --dexterity | -d : dexterity score --ranged | -r : ranged weapons; use a comma separated list --melee | -m : melee weapons; use a comma separated list --brief | -b : do not print an explanatory verbose summary; just print the charts. --weaponlist | -w : print out a list of all weapons this program recognises --help | -h : this help """ pclass = "" level = 0 strength = "" dexterity = 0 weapons = [] #print "got sys.argv %s" % str(sys.argv) opts, args = getopt.getopt(argv[1:], shortopts, longopts) #print "got opts %s args %s" % ( str(opts), str(args)) for pair in opts: #print "checking pair %s" % str(pair) if (pair[0] == "--class" or pair[0] == "-c"): pclass = pair[1] elif ( pair[0] == "--level" or pair[0] == "-l"): level = int(pair[1]) elif ( pair[0] == "--strength" or pair[0] == "-s"): strength = pair[1] elif ( pair[0] == "--dexterity" or pair[0] == "-d"): dexterity = int(pair[1]) elif ( pair[0] == "--ranged" or pair[0] == "-r"): for item in pair[1].split(","): weapons.append(["ranged", item.strip()]) elif ( pair[0] == "--melee" or pair[0] == "-m"): for item in pair[1].split(","): weapons.append(["melee", item.strip()]) elif ( pair[0] == "--help" or pair[0] == "-h"): print usage sys.exit(1) elif ( pair[0] == "--brief" or pair[0] == "-b"): brief = True elif ( pair[0] == "--weaponlist" or pair[0] == "-w"): print "Valid melee weapons:" for i in range(0, len(melee_weapon_charts), 3): print "%s%s%s" % ( string.center(melee_weapon_charts[i][0], 70/3), string.center(melee_weapon_charts[i+1][0], 70/3), string.center(melee_weapon_charts[i+2][0], 70/3)) print "\nValid ranged weapons:" for i in range(0, len(ranged_weapon_charts), 3): print "%s%s%s" % ( string.center(ranged_weapon_charts[i][0], 70/3), string.center(ranged_weapon_charts[i+1][0], 70/3), string.center(ranged_weapon_charts[i+2][0], 70/3)) sys.exit(0) if ( not strength or not dexterity or not pclass or not level): print usage sys.exit(1) #print "Trying with pclass %s level %d strength %s dex %d weapons %s" % (pclass, level, strength, dexterity, weapons) pc = character(pclass, strength, dexterity, level) pc.weapons = weapons pc.writeCharts(sys.stdout) #pc.weapons = [["ranged", "long bow"], ["melee", "long sword"]] #pc.writeCharts(sys.stdout) if ( __name__ == "__main__" ): main(len(sys.argv), sys.argv)