def main(opt): configureLogger(opt.verbose) if opt.clean: log.warning("cleaning the build directory") shutil.rmtree('build') zone = Zone('Library') zone.build(pcs() + npcs() + morphs() + libTokens()) ecp = Campaign('eclipse') dmScreen = Zone('DM Screen') ecp.build([zone, dmScreen], propertySets(), [eclipseTable(), nameTable(), morphTable()]) log.warning('Done building %s with a total of %s macros, %s assets' % (ecp, len(list(ecp.macros)), len(list(ecp.assets)))) return ecp
def main(): parser = argparse.ArgumentParser(description='DnD 5e token builder') parser.add_argument('--verbose', '-v', action='count') parser.add_argument('--max-token', '-m', type=int) parser.add_argument('--delivery', '-d', action="store_true", default=False) global args args = parser.parse_args() if not os.path.exists('build'): os.makedirs('build') localMonsters = [] tob = '../open5e/legacy-source-content/monsters/tome-of-beasts/' sources = [ r'../5e-database/5e-SRD-Monsters-volo.json', r'../5e-database/5e-SRD-Monsters.json', ] sources += [os.path.join(dp, f) for dp, dn, filenames in os.walk(tob) for f in filenames if os.path.splitext(f)[1] == '.rst' and 'index' not in f] for f in sources: with codecs.open(f, 'r', encoding='utf8') as mfile: if f.endswith('json'): localMonsters += json.load(mfile) if f.endswith('rst'): localMonsters += [loadFromRst(mfile)] mLog = logging.getLogger() mLog.setLevel(logging.DEBUG) mLog.handlers[-1].setLevel(logging.WARNING-(args.verbose or 0)*10) fh = logging.FileHandler(os.path.join('build', 'tokens.log'), mode="w") # mode w will erase previous logs fh.setLevel(logging.DEBUG) fh.setFormatter(logging.Formatter('%(name)s : %(levelname)s : %(message)s')) mLog.addHandler(fh) # generate the lib addon token addon = LibToken('Lib:Addon5e') fromFile = lambda path: jenv().get_template(path).render().encode("utf-8") params = {'group': 'zLib', 'prefix': 'a5e'} addon.add(macros.Macro(addon, '', 'onCampaignLoad', ''' [h: defineFunction( "%(prefix)s.jget", "jget@this" )] [h: defineFunction( "%(prefix)s.debug", "debug@this" )] [h: defineFunction( "%(prefix)s.output", "output@this" )] [h: defineFunction( "%(prefix)s.rollDice", "rollDice@this",0,0)] ''' % params, **params)) addon.add(macros.Macro(addon, '', 'debug', '''[h: props = getPropertyNames()] [foreach(name, props, "<br>"), code: { [name]: [getProperty(name)]: [getRawProperty(name)]}] ''', **params)) addon.add(macros.Macro(addon, '', 'output', fromFile("output.mtmacro"), **params)) addon.add(macros.Macro(addon, '', 'rollDice', fromFile("rollDice.mtmacro"), **params)) addon.add(macros.Macro(addon, '', 'jget', ''' [h: '<!-- Like json.get, but will adapt if the requested reference cannot be made. By default, returns 0, or returns a default named (as a third parameter). -->'] [h: object = arg(0)] [h: key = arg(1)] [h, if( argCount() > 2 ): default = arg(2); default = 0] [h, if( json.type( object ) == "OBJECT" ), code: { [h: macro.return = if( json.contains( object, key ), json.get( object, key ), default )] };{ [if( json.type( object ) == "ARRAY" && isNumber( key ) ), code: { [h, if( json.length( object ) > key ): macro.return = json.get( object, key ) ; macro.return = default )] };{ [h: macro.return = default ] }] }] ''', **params)) params = {'group': 'dnd5e'} addon.add(macros.Macro(addon, '', 'Description', fromFile('description.mtmacro'), **params)) addon.add(macros.Macro(addon, '', 'CastSpell', fromFile('castSpell.mtmacro'), **params)) addon.add(macros.Macro(addon, '', 'NPCAttack', fromFile('npcAttack.mtmacro'), **params)) addon.add(macros.Macro(addon, '', 'Init', fromFile('init.mtmacro'), **params)) addon.add(macros.Macro(addon, '', 'getNPCInitBonus', '''[h, macro("getNPCSkills@Lib:Addon5e"):0] [h: jskills = macro.return] [h: initb = json.get(jskills, "Initiative")] [h, if (initb==""), code: {[h: initb=getProperty("bdex")]}] [h:macro.return=initb]''', **params)) # "Perception +5, Initiative +3" => {"Perception": 5, "Initiative": 3} addon.add(macros.Macro(addon, '', 'getNPCSkills', r'''<!-- Fetch skill bonuses--> <!-- Depending on the source (SRD, tome of the beast, MM) format of skill data may change--> <!-- Method 1: fetch skill entries like "skills: Perception +5, Stealth +4"--> [h: id = strfind(getProperty("skills"), "((\\w+) \\+(\\d+))")] [h: jskills = "{}"] [h: find = getFindCount(id)] [h, while (find != 0), code: { [h: sname = getGroup(id, find, 2)] [h: svalue = getGroup(id, find, 3)] [h: jskills = json.set(jskills, sname, svalue)] [h: find = find - 1] }] [h: all_skills= getLibProperty("all_skills", "Lib:Addon5e")] <!-- Most of the token don't specify a modifier for all skills--> <!-- for all skills missing a modifier, use the default one which is the attribute modifier --> [h, foreach(skill, all_skills), code: { [Attribute = json.get(all_skills, skill)] [att_ = lower(substring(Attribute, 0, 3))] [modifier = json.get(jskills, skill)] [default_mod = getProperty("b"+att_)] [no_mod = json.isEmpty(modifier) ] [if (no_mod): jskills = json.set(jskills, skill , default_mod)] }] [h: macro.return = jskills]''', **params)) # "Wis +3, Con +2" => {"Wis": 2, "Con": 2} addon.add(macros.Macro(addon, '', 'getNPCSaves', r'''[h: id = strfind(getProperty("saves"), "((\\w+) \\+(\\d+))")] [h: jsaves= "{}"] [h: find = getFindCount(id)] <!-- parse the prop "saves" which may contain some save modifiers--> <!-- "Wis +3, Con +2" => "Wis": 2, "Con": 2 --> [h, while (find != 0), code: { [h: sname = getGroup(id, find, 2)] [h: svalue = getGroup(id, find, 3)] [h: jsaves = json.set(jsaves, sname, svalue)] [h: find = find - 1] }] <!-- Most of the token don't specify a modifier for all saves --> <!-- for all saves missing a modifier, use the default one which is the attribute modifier --> [h, foreach(Attribute, getLibProperty("attributes", "Lib:Addon5e")), code: { [Att = substring(Attribute, 0, 3)] [att_ = lower(Att)] [modifier = json.get(jsaves, Att)] [default_mod = getProperty("b"+att_)] [no_mod = json.isEmpty(modifier) ] [if (no_mod): jsaves = json.set(jsaves, Att ,default_mod)] }] [h: macro.return = jsaves]''', **params)) addon.add(macros.Macro(addon, '', 'SaveMe', fromFile('saveme.mtmacro'), **params)) addon.add(macros.Macro(addon, '', 'CheckMe', fromFile('checkme.mtmacro'), **params)) params = {'group': 'aMenu'} # TODO: control panel is currently empty but it is a customized panel where I can add whatever macro, it act as a campaign panel # but is fully customizable, it's a html form # see http://forums.rptools.net/viewtopic.php?f=20&t=23208&p=236662&hilit=amsave#p236662 addon.add(macros.Macro(addon, '', 'ControlPanel', '''[dialog("A5e Panel", "width=215; height=700; temporary=0; input=1"): {[r,macro("cpanel@this"):0]}]''', **params)) params = {'group': 'Format'} addon.add(macros.Macro(addon, '', 'cpanel', fromFile('cpanel.mtmacro'), **params)) addon.add(macros.Macro(addon, '', 'HTMLMacroButton','''[h:bgColor = arg(1)] [h,if(argCount() > 5): shadow = arg(5); shadow = "")] [h,if(argCount() > 6): toolTip = arg(6); toolTip = "")] [h,if(argCount() > 7): args = arg(7); args = "[]")] [h,if(argCount() > 8): libType = arg(8); libType = "@this")] [h,if(argCount() > 9): output = arg(9); output = "none")] [h:btnformat = strformat("padding:1px; border-width:1pt; border-style:solid; border-color:black; text-align:center; white-space:nowrap; background-image:url(%{shadow}); background-color:%{bgColor};")] <td width='[r:arg(0)]%'> <table width='100%' cellpadding='0' cellspacing='0'> <tr> <td style='[r:btnformat]'> <span title='[r:toolTip]' style='text-decoration:none; color:[r:arg(2)]'> [r:macroLink(arg(3),arg(4)+libType,output,args)] </span> </td> </tr> </table> </td>''' , **params)) filename = addon.zipme() log.warning("Done generating 1 library token: %s", addon) poi = POI("POI") # fetch the monsters(token) and spells from dnd5Api or get them from the serialized file #tokens = itertools.chain((Token(m) for m in monsters), Token.load('build')) # dont use online api, use the fectched local database instead tokens = itertools.chain([poi], (Token(m) for m in itertools.chain(localMonsters))) # 5e-database is probably a link with open(r'../5e-database/5e-SRD-Spells.json', 'r') as mfile: localSpells = json.load(mfile) Spell.spellDB = [Spell(spell) for spell in localSpells] sTokens = [] # used for further serialization, because tokens is a generator and will be consumed cnt = 0 deliveryFilename = 'build/dnd5eTokens.zip' zfile = zipfile.ZipFile(deliveryFilename, "w", zipfile.ZIP_STORED) if args.delivery else None # add lib:addon5e to the zipfile if zfile: zfile.write(filename, os.path.relpath(filename, start='build')) for token in itertools.islice(tokens, args.max_token): log.info(token) log.debug(token.verbose()) filename = token.zipme() if zfile: zfile.write(filename, os.path.relpath(filename, start='build')) sTokens.append(token) if 'dft.png' in token.img.name: log.warning(str(token)) cnt += 1 log.warning("Done generating %s tokens"%cnt) log.warning("building campaign file") zone = Zone('Library') zone.build(sTokens + [addon]) cp = Campaign('demo5e') cp.build([zone], [PSet('Basic', [])], []) log.warning("Done building campaign file") if zfile: zfile.close() log.warning("Done writing delivery zip file '%s'" % deliveryFilename) Token.dump('build', sTokens) Spell.dump('build', Spell.spellDB)