def macros(self): if self._macros: return self._macros # get optinal macros related to the token actions actions = (macros.ActionMacro(self, action) for action in self.actions if action["name"]) lairs = (macros.LairMacro(self, action) for action in self.lair_actions if action["name"]) reg = (macros.RegionalEffectMacro(self, action) for action in self.regional_effects if action["name"]) legends= (macros.LegendaryMacro(self, leg) for leg in self.legends if leg["name"]) attributes = self.scAttributes if self.sc and attributes is None: log.warning("Token %s has malformed spellcasting info: %s", self, self.sc) spellCast = [] if attributes: attr, dc, attack = attributes groupName = 'Spells(%s) save DC%s attack %s' % (attr[:3], dc, attack) spellCast = (macros.SpellCastingMacro(self, spe, groupName) for spe in self.specials if spe['name'].lower()=="spellcasting") specials = (macros.SpecialMacro(self, spe) for spe in self.specials if spe['name'] and spe['name'].lower()!="spellcasting") spells = (macros.SpellMacro(self, spell) for spell in self.spells) commons = [ macros.SheetMacro(self), macros.Macro(self, None, 'Init', '[macro("Init@Lib:Addon5e"):0]', **{'group': 'Rolls', 'colors': ('white', 'green'), 'tooltip': 'Roll and add to the init panel'}), macros.Macro(self, None, 'SaveMe', '[macro("SaveMe@Lib:Addon5e"):0]', **{'group': 'Rolls', 'colors': ('white', 'green'), 'tooltip': 'Roll Saving Throws'}), macros.Macro(self, None, 'CheckMe', '[macro("CheckMe@Lib:Addon5e"):0]', **{'group': 'Rolls', 'colors': ('white', 'green'), 'tooltip': 'Roll Skill Checks'}), ] if not args.delivery: commons.append(macros.Macro(self, None, 'Debug', '[r: a5e.debug()]', **{'group': 'zDebug', 'colors': ('white', 'black')})) self._macros = list(itertools.chain(actions, spellCast, specials, legends, lairs, reg, commons, spells)) return self._macros
def macros(self): if not self._macros: for name,asset in self.assets.iteritems(): # add the name as comment so the macro are sorted by the name => increasing number label = '<!--%s--><img height=40 width=40 src="asset://%s"></img>' % (name, asset.md5) self._macros.append(macros.Macro(self, '', label, ''' [h: setTokenImage("asset://%s")] ''' % asset.md5, group='icons' if (not name.startswith('rn_')) else 'IDs', colors=('black', 'white'))) self._macros.append(macros.Macro(self, '', 'bigger', ''' [h, if (getSize() == "large"), code : {[setSize("huge")]};{}] [h, if (getSize() == "medium"), code : {[setSize("large")]};{}] [h, if (getSize() == "small"), code : {[setSize("medium")]};{}] [h, if (getSize() == "tiny"), code : {[setSize("small")]};{}] ''', group='aSettings',)) self._macros.append(macros.Macro(self, '', 'smaller', ''' [h, if (getSize() == "small"), code : {[setSize("tiny")]};{}] [h, if (getSize() == "medium"), code : {[setSize("small")]};{}] [h, if (getSize() == "large"), code : {[setSize("medium")]};{}] [h, if (getSize() == "huge"), code : {[setSize("large")]};{}] ''', group='aSettings',)) self._macros.append(macros.Macro(self, '', "fromHandout", ''' [h: gmNotes = ""] [h: notes = getNotes()] [h: notes = notes +"This is a test"] [h: pcNotes = ""] [h: pcNotes = pcNotes + "<FONT COLOR=BLACK SIZE=4>" + notes + "</FONT><HR>"] [h: pid = getTokenPortrait()] [h: hid = getTokenHandout()] [h, if (pid != ""), code: { [h: gmNotes = gmNotes + "<img src='" + pid + "'/><br>"] };{}] [h, if (hid != ""), code: { [h: gmNotes = gmNotes + "<img src='" + hid + "'/>"] };{}] [h: setNotes("")] [h: setGMNotes(pcNotes + gmNotes)] [h: setSize("large")] [h: setLayer("GM")] [h: setTokenSnapToGrid(0)] ''', group='aSettings')) return self._macros
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)