Beispiel #1
0
	def zipme(self):
		"""Zip the Campaing into a cmpgn file."""
		log.info("Zipping %s" % self)
		if not os.path.exists('build'): os.makedirs('build')
		with zipfile.ZipFile(os.path.join('build', '%s.cmpgn'%self.name), 'w') as zipme:
			zipme.writestr('content.xml', self.content_xml.encode('utf-8'))
			zipme.writestr('properties.xml', self.properties_xml)
			md5s = [] # record added assets
			for name, asset in self.assets:
				if asset.md5 in md5s: continue # dont zip the same file twice
				log.debug("adding asset %s: %s" % (name, asset))
				md5s.append(asset.md5)
				zipme.writestr('assets/%s' % asset.md5,
					jenv().get_template('md5.template').render(name=os.path.splitext(os.path.basename(asset.fp))[0], extension='png', md5=asset.md5))
				zipme.writestr('assets/%s.png' % asset.md5, asset.bytes)
Beispiel #2
0
	def zipme(self):
		"""Zip the token into a rptok file."""
		log.info("Zipping %s" % self)
		if not os.path.exists('build'): os.makedirs('build')
		with zipfile.ZipFile(os.path.join('build', '%s.rptok'%self.name), 'w') as zipme:
			zipme.writestr('content.xml', self.content_xml)
			zipme.writestr('properties.xml', self.properties_xml)
			# default image for the token, right now it's a brown bear
			# zip the xml file named with the md5 containing the asset properties
			for asset in self.assets.values():
				zipme.writestr('assets/%s' % asset.md5, jenv().get_template('md5.template').render(name=asset.fp, extension='png', md5=asset.md5))
				# zip the img itself
				zipme.writestr('assets/%s.png' % asset.md5, asset.bytes)
			# build thumbnails
			zipme.writestr('thumbnail', self.icon.thumbnail(50,50).getvalue())
			zipme.writestr('thumbnail_large', self.icon.thumbnail(500,500).getvalue())
Beispiel #3
0
	def zipme(self):
		"""Zip the token into a rptok file."""
		filename = os.path.join('build', '%s.rptok'%(self.name.replace(":","_")))
		# don't compress to avoid technical issue when sharing files
		# the gain is very small anyway
		with zipfile.ZipFile(filename, "w", zipfile.ZIP_STORED) as zipme:
			zipme.writestr('content.xml', self.content_xml.encode('utf-8'))
			zipme.writestr('properties.xml', self.properties_xml.encode('utf-8'))
			# default image for the token, right now it's a brown bear
			# zip the xml file named with the md5 containing the asset properties
			for name, asset in self.assets.iteritems():
				zipme.writestr('assets/%s' % asset.md5, jenv().get_template('md5.template').render(name=name, extension='png', md5=asset.md5).encode("utf-8"))
				zipme.writestr('assets/%s.png' % asset.md5, asset.bytes)
			# build thumbnails
			zipme.writestr('thumbnail', self.img.thumbnail(50,50).getvalue())
			#dont include the large thumbnail, it will double the token size for no benefit
			#zipme.writestr('thumbnail_large', self.img.thumbnail(500,500).getvalue())
		return filename
Beispiel #4
0
 def content_xml(self):
     return jenv().get_template('zone_content.template').render(
         zone=self) or u''
Beispiel #5
0
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)
Beispiel #6
0
	def properties_xml(self):
		return jenv().get_template('properties.template').render()
Beispiel #7
0
	def content_xml(self):
		return jenv().get_template('content.template').render(token=self) or u''
Beispiel #8
0
class Token(Dnd5ApiObject):
	sentinel = object()
	sfile_name = 'tokens.pickle'
	category = 'monsters'
	pngFiles = sentinel
	def __init__(self, js):
		self.js = js
		self._assets = None
		self.x, self.y = 0, 0
		# for cached properties
		self._guid = self.sentinel
		self._macros = []

	def __repr__(self):
		return 'Token<name=%s,attr=%s,hp=%s(%s),ac=%s,CR%s,img=%s>' % (self.name, [
			self.strength, self.dexterity, self.constitution,
			self.intelligence, self.wisdom, self.charisma
			], self.hit_points, self.roll_max_hp, self.armor_class,
			self.challenge_rating, self.img.name)

	# The 2 following methods are use by pickle to serialize a token
	def __setstate__(self, state):
		Dnd5ApiObject.__setstate__(self, state)
		self._guid = self.sentinel
		self._assets = None

	@property
	def assets(self):
		if self._assets is None:
			self._assets = {}
			# try to fetch an appropriate image from the imglib directory
			# using a stupid heuristic: the image / token.name match ratio
			# compute the diff ratio for the given name compared to the token name
			ratio = lambda name: difflib.SequenceMatcher(None, name.lower(), self.name.lower()).ratio()
			# morph "/abc/def/anyfile.png" into "anyfile"
			short_name = lambda full_path: os.path.splitext(os.path.basename(full_path))[0]
			bratio=0
			if self.pngs:
				# generate the diff ratios
				ratios = ((f, ratio(short_name(f))) for f in self.pngs)
				# pickup the best match, it's a tuple (fpath, ratio)
				bfpath, bratio = max(itertools.chain(ratios, [('', 0)]), key = lambda i: i[1])
				log.debug("Best match from the img lib is %s(%s)" % (bfpath, bratio))
			# in delivery mode, do not add the tome of beast art, per author request
			if bratio > 0.8 and (self.js.get("ref", "")!="Tome of Beast" or not args.delivery):
				self._assets['null'] = Img(bfpath)
			else:
				self._assets['null'] = Img(imglib+'/dft.png')
		return self._assets

	@property
	def guid(self):
		if self._guid is self.sentinel:
			self._guid = guid()
		return self._guid

	@property
	def content_xml(self):
		return jenv().get_template('content.template').render(token=self) or u''

	@property
	def properties_xml(self):
		return jenv().get_template('properties.template').render()

	def render(self): return self.content_xml

	@property
	def portrait(self): return None

	def abonus(self, attribute):
		return (getattr(self, attribute.lower())-10)/2

	@property
	def bcon(self): return self.abonus('constitution')

	@property
	def bdex(self): return self.abonus('dexterity')

	@property
	def bwis(self): return self.abonus('wisdom')

	@property
	def roll_max_hp(self):
		dice, value = map(int, self.hit_dice.split('d'))
		return '%sd%s+%s' % (dice, value, dice*self.bcon)

	@property
	def max_hit_dice(self):
		dice, value = map(int, self.hit_dice.split('d'))
		hd = {'1d12':0, '1d10':0, '1d8':0, '1d6':0}
		hd.update({'1d%s'%value:dice})
		return hd

	# spellcasting
	@property
	def sc(self): return next((spe for spe in self.specials if spe['name'] == 'Spellcasting'), None)

	# wisdom, charisma ot intelligence
	@property
	def scAttributes(self):  # spellcasting attribute
		if self.sc is None: return None
		desc = self.sc['desc'].lower() if self.sc else ''
		attr = next((attr for attr in ['intelligence', 'charisma', 'wisdom'] if attr in desc), None)
		match = re.search(r'save dc (\d+)', desc, re.IGNORECASE)
		dc = match and int(match.group(1))
		match = re.search(r'([+-]\d+) to hit with spell', desc)
		# extrack the spell hit bonus, otherwise use the spell castin attribute bonus.
		attack = match and match.group(1)
		if attack is None and attr: attack = self.abonus(attr)
		return (attr, dc, attack) if desc and attr and dc and attack else None

	@property
	def actions(self): return self.js.get('actions', [])

	@property
	def specials(self): return self.js.get('special_abilities', [])

	@property
	def legends(self): return self.js.get('legendary_actions', [])

	@property
	def lair_actions(self): return self.js.get('lair_actions', [])

	@property
	def regional_effects(self): return self.js.get('regional_effects', [])

	@property
	def passive_perception(self): return 10+self.js.get('perception', self.bwis)

	@property
	def vulnerabilities(self): return self.js.get('damage_vulnerabilities', "")

	@property
	def type(self): 
		foo =  self.js.get('type', 'unknown type')
		if foo[-1] == 's':
			print self
		return foo

	@property
	def skills(self):
		skills = self.js.get('skills', "")
		if skills=="":
			skills = ", ".join(["%s +%d" % (sk,self.js[sk]) for sk in all_skills().keys() if self.js.get(sk, None)])
		return skills

	# saves can be specified in different ways:
	# either a field "saves": "Saving Throws Int +5, Wis +5, Cha +4"
	# or respective field like "wisdom_save": 5
	@property
	def saves(self):
		saves = self.js.get('saves', "")
		if saves == "":
			saves = ", ".join([ '%s %+d'% (attr[:3], self.js["%s_save"%attr.lower()]) for attr in self.attributes if "%s_save" % attr.lower()  in self.js])
		return saves

	@property
	def note(self): return ''

	@property
	def immunities(self): return self.js.get('damage_immunities', '')

	@property
	def resistances(self): return self.js.get('damage_resistances','')

	@property
	def size_guid(self):
		# XXX may depend on the maptool version
		return {
			'tiny':       'fwABAc5lFSoDAAAAKgABAA==',
			'small':      'fwABAc5lFSoEAAAAKgABAA==',
			'medium':     'fwABAc9lFSoFAAAAKgABAQ==',
			'large':      'fwABAdBlFSoGAAAAKgABAA==',
			'huge':       'fwABAdBlFSoHAAAAKgABAA==',
			'gargantuan': 'fwABAdFlFSoIAAAAKgABAQ==',
		}[self.size.lower()]

	@property
	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

	@property
	def slots(self): # current spendable slots
		slots = collections.OrderedDict()
		if self.sc is not None:
			sc = self.sc['desc']
			match = re.search(r'1st level \((\d) slot', sc)
			slots['First'] = int(match.group(1)) if match else 0
			match = re.search(r'2nd level \((\d) slot', sc)
			slots['Second'] = int(match.group(1)) if match else 0
			match = re.search(r'3rd level \((\d) slot', sc)
			slots['Third'] = int(match.group(1)) if match else 0
			match = re.search(r'4th level \((\d) slot', sc)
			slots['Fourth'] = int(match.group(1)) if match else 0
			match = re.search(r'5th level \((\d) slot', sc)
			slots['Fifth'] = int(match.group(1)) if match else 0
			match = re.search(r'6th level \((\d) slot', sc)
			slots['Sixth'] = int(match.group(1)) if match else 0
			match = re.search(r'7th level \((\d) slot', sc)
			slots['Seventh'] = int(match.group(1)) if match else 0
			match = re.search(r'8th level \((\d) slot', sc)
			slots['Eighth'] = int(match.group(1)) if match else 0
			match = re.search(r'9th level \((\d) slot', sc)
			slots['Ninth'] = int(match.group(1)) if match else 0
		return slots

	@property
	def spell_slots(self): #max slots available
		spells={}
		for i, (k,v) in enumerate(self.slots.iteritems()):
			spells["%s"%(i+1)] = v
		return spells

	@property
	def spells(self):
		spells = []
		for ability in (a for a in self.specials if 'spellcasting' in a['name'].lower()):
			spells.extend([s for s in Spell.spellDB if s.name.lower() in ability['desc']])
		return spells

	@property
	def attributes(self):
		return ['Strength', 'Dexterity', 'Constitution', 'Intelligence', 'Wisdom', 'Charisma']

	@property
	def props(self):
		return (Prop(name, value) for name, value in [
			('mname', self.name),
			('AC', self.armor_class),
			('MaxHp', self.hit_points),
			('Hp', self.hit_points),
			('HitDice', self.hit_dice),
			('Strength', self.strength),
			('Dexterity', self.dexterity),
			('Constitution', self.constitution),
			('Wisdom', self.wisdom),
			('Intelligence', self.intelligence),
			('Charisma', self.charisma),
			('Initiative', '[h,macro("getNPCInitBonus@Lib:Addon5e"):0][r: macro.return]'),
			('Immunities', self.immunities), # XXX add condition immunities ?
			('Resistances', self.resistances),
			('CreatureType', self.type + ', CR ' + str(self.challenge_rating)),
			('Alignment', self.alignment),
			('Speed', self.speed),
			('Saves', self.saves),
			('Skills', self.skills),
			('jSkills', '[h,macro("getNPCSkills@Lib:Addon5e"):0][r: macro.return]'),
			('Senses', self.senses),
			('Vulnerabilities', self.vulnerabilities),
			('Resistances', self.resistances),
			('Immunities', self.immunities),
			('Languages', self.languages),
			('passive perception', self.passive_perception),
			('ImageName', self.img.name),
			('SpellSlots', self.spell_slots),
			# do ('bstr', '{floor((getProperty("Strength")-10)/2)}') for all attributes
			] + [('b%s' % a[:3].lower(), '{floor((getProperty("%s")-10)/2)}' % a) for a in self.attributes] +
			[(k, v) for k,v in self.slots.iteritems()]
			)

	@property
	def pngs(self):
		if self.pngFiles is self.sentinel:
			Token.pngFiles = list(itertools.chain(*(glob.glob(os.path.join(os.path.expanduser(imglib), '*.png')) for imglib in imglibs)))
		return iter(self.pngFiles) if self.pngFiles else None

	@property
	def img(self): return self.assets.get('null', None)

	@property
	def states(self): return [s for s in [State('Concentrating', 'false')]]

	def zipme(self):
		"""Zip the token into a rptok file."""
		# directory hierarchy uses the monster type as subfolder
		root = os.path.join('build', self.type)
		try:
			os.makedirs(root)
		except OSError, e:
			pass
		filename = os.path.join(root, '%s.rptok'%(self.name.replace(":","_")))
		# don't compress to avoid technical issue when sharing files
		# the gain is very small anyway
		with zipfile.ZipFile(filename, "w", zipfile.ZIP_STORED) as zipme:
			zipme.writestr('content.xml', self.content_xml.encode('utf-8'))
			zipme.writestr('properties.xml', self.properties_xml.encode('utf-8'))
			# default image for the token, right now it's a brown bear
			# zip the xml file named with the md5 containing the asset properties
			for name, asset in self.assets.iteritems():
				zipme.writestr('assets/%s' % asset.md5, jenv().get_template('md5.template').render(name=name, extension='png', md5=asset.md5).encode("utf-8"))
				zipme.writestr('assets/%s.png' % asset.md5, asset.bytes)
			# build thumbnails
			zipme.writestr('thumbnail', self.img.thumbnail(50,50).getvalue())
			#dont include the large thumbnail, it will double the token size for no benefit
			#zipme.writestr('thumbnail_large', self.img.thumbnail(500,500).getvalue())
		return filename
Beispiel #9
0
 def properties_xml(self):
     return jenv().get_template('cmpgn_properties.template').render(
         cmpgn=self) or u''
Beispiel #10
0
	def properties_xml(self):
		return jenv().get_template('token_properties.template').render(token=self) or u''
Beispiel #11
0
	def content_xml(self):
		if self._content is self.sentinel:
			self._content = jenv().get_template('token_content.template').render(token=self)
		return self._content or ''
Beispiel #12
0
	def properties_xml(self):
		content = jenv().get_template('cmpgn_properties.template').render(cmpgn=self)
		return content or ''
Beispiel #13
0
	def content_xml(self):
		content = jenv().get_template('cmpgn_content.template').render(cmpgn=self)
		return content or ''
Beispiel #14
0
	def render(self):
		return jenv().get_template('table_content.template').render(table=self)
Beispiel #15
0
	def content_xml(self):
		content = jenv().get_template('zone_content.template').render(zone=self)
		return content or ''
Beispiel #16
0
	def template(self):
		if self._template is None:
			self._template = jenv().get_template(self.tmpl)
		return self._template
Beispiel #17
0
 def content_xml(self):
     return jenv().get_template('cmpgn_content.template').render(
         cmpgn=self) or u''