Statblock output option

Discuss Lone Wolf's Hero Lab, the official licensed Mutants & Masterminds superhero creation and advancement tool.
User avatar
badpenny
Overlord
Overlord
Posts: 4406
Joined: Sat Jan 17, 2009 6:09 pm
Location: Under The Greenwood Tree

Statblock output option

Postby badpenny » Thu Dec 05, 2013 11:16 am

How about the option to output the cost of powers? The current output looks like this:

Powers:
Photon Beam (Blast 10) (DC 25)


What about the addition of the points for something like this:

Powers:
Photon Beam (Blast 10) (DC 25) · 20 points


I do this manually now, but wow, would it ever save time to have Hero Lab do this for both 2E and 3E versions :!:

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Wed Mar 05, 2014 9:11 pm

I'm working on it. I just got back into figuring out how to process the custom output. I'm as far as outputting skills. This is the output for one of my characters:

Whiplash

Power Level: 10 Power Points Spent: 150/150

Str: +0/+5(10/20) Dex: +3(16) Con: +2/+7(14/24) Int: +1(12) Wis: +2(14) Cha: +0/+7(10/24)

Tough: +7/+11 (Imp: +4) Fort: +7 Ref: +10 Will: +7
Skills:
  • Acrobatics 12 (+15) - [3 PP] You can flip, dive, roll, tumble, and perform other acrobatic maneuvers, and you're also adept at keeping your balance under difficult circumstances.
  • Bluff 0 (+7) - [0 PP] Bluff is the skill of making the outlandish seem credible. It covers acting, fast-talk, trickery, and subterfuge.
  • Climb 0 (+5) - [0 PP] You're skilled in scaling angled and uneven surfaces.
  • Concentration 4 (+6) - [1 PP] You can focus your mind and concentrate despite difficult conditions, including taking damage.
  • Diplomacy 0 (+7) - [0 PP] You're skilled in dealing with people, from etiquette and social graces to a way with words and public speaking. Use this skill to make a good impression, negotiate, and win people over.
  • Disguise 0 (+7) - [0 PP] You can use makeup, costumes, and other props to change your appearance.
  • Escape Artist 6 (+9/+12) - [1.5 PP] You're trained in escaping bonds and other restraints.
  • Gamble 0 (+2) - [0 PP] Use this skill to win games involving both skill and luck. Games based solely only luck - such as flipping a coin - or skill - such as chess - don't involve Gamble checks, unless the character cheats.
  • Gather Information 0 (+7) - [0 PP] You know how to make contacts, collect gossip and rumors, question informants, and otherwise get information from people.
  • Handle Animal 0 (+7) - [0 PP] You know how to handle, care for, and train various types of animals.
  • Intimidate 12 (+19) - [3 PP] You know how to use threats (real or implied) to get others to cooperate.
  • Medicine 2 (+4) - [0.5 PP] You're trained in treating injuries and illness.
  • Navigate 0 (+1) - [0 PP] You're trained in finding directions and plotting courses from place to place.
  • Notice 8 (+10) - [2 PP] Use this skill to notice things.
  • Profession (Librarian) 2 (+4) - [0.5 PP] You're trained in a profession such as accountant, doctor, engineer, game designer, lawyer, police officer, reporter, teacher, writer, and so forth.
  • Search 4 (+5) - [1 PP] You can search an area for clues, hidden items, traps, and other details. Notice allows you to immediately notice things, Search allows you to pick up on details with some effort. Search works in conjunction with all accurate senses (sight is the only accurate sense for normal humans).
  • Sense Motive 6 (+8) - [1.5 PP] You can tell someone's true intentions by paying attention to body language, inflection, and intuition.
  • Stealth 10 (+13) - [2.5 PP] You're skilled at going unnoticed.
  • Survival 2 (+4) - [0.5 PP] You use this skill to survive in the wilderness, including finding food and shelter, and safely guiding others.
  • Swim 0 (+5) - [0 PP] You can swim and maneuver underwater.


The Python script I used:

Code: Select all

import sys
# from xml.dom.minidom import parseString
import xml.etree.ElementTree as ET

# print (sys.argv[1])
print

xmlfile = open(sys.argv[1], 'r')
# xmlfile = open("Random.xml", 'r')
tree = ET.parse(xmlfile)
xmlfile.close()

root = tree.getroot()

for character in root.findall("./*/character"):
   role = character.attrib['role']
   
   name = character.attrib.get('name')
   if name is None:
      name = "Unnamed Hero"

   print ('[SIZE=150]' + character.attrib['name'] + '[/SIZE]')
   print ()

   resources = character.find('resources')
   if resources is not None:
      currentPL = resources.attrib['currentpl']
      totalPP = resources.attrib['totalpp']

      # Next step is to pull in the resources themselves.
      totalPPSpent = 0
      resourceVals = resources.findall('resource')
      for resource in resourceVals:
        totalPPSpent += int(resource.attrib['spent'])

      print ('Power Level: ' + str(currentPL) + ' Power Points Spent: ' + str(totalPPSpent), end='')

      if role == 'pc':
         print ('/' + str(totalPP))
      else:
         print ()

      print ()
   #print the attributes
   attributesEl = character.find('attributes')

   if attributesEl is not None:
      attributes = attributesEl.findall('attribute')

      for attribute in attributes:
         print ('[b]' + attribute.attrib['name'][0:3] + ': ', end='')

         attrvalue = attribute.find('attrvalue')
         attrbonus = attribute.find('attrbonus')

         if attrbonus is not None:
            print (attrbonus.attrib['text'] + '[/b]', end='')
         else:
            print ('[/b]', end='')

         if attrvalue is not None:
            print ('(' + attrvalue.attrib['text'] + ')', end='')

         print (' ', end='')
      print ()
      print ()

   #Saves
   savesEl = character.find('saves')

   if savesEl is not None:
      saves = savesEl.findall('save')

      print ('[b]', end='')
      for save in saves:
         print (save.attrib['abbr'] + ': ' + save.attrib['text'] + ' ', end='')

         if int(save.attrib['impervious']) > 0:
            print ('[i](Imp: ' + save.attrib['impervious'] + ')[/i] ', end='')
      print ('[/b]')

   #Skills
   skillsEl = character.find('skills')
   if skillsEl is not None:
      skills = skillsEl.findall('skill')
      print ('[b]Skills: [/b][list]')
     
      for skill in skills:
         userRanks = int(skill.attrib['user'])
         if userRanks > 0 or skill.attrib.get('trainedonly') != 'yes':
           print ('[*]', skill.attrib['name'], userRanks, '([b]' + skill.attrib['value'] + '[/b])',
                  '[i][' + skill.find('cost').attrib['text'] + '][/i] - ', skill.find('description').text)
                                                   
      print ('[/list]')


It's a bit painful, as I don't have a lot of Python experience, and I keep getting hung up trying to make the script more robust, but it's a start. The skills have the description attached just so that I could show that I was doing it.

I'll see if I can't rough out a Powers output before heading off to bed.

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Wed Mar 05, 2014 9:29 pm

Alright. Rough version...

Powers:
  • Jaded (Immunity 5) (interaction skills) - [5 PP]
  • Leaping 1 (Jumping distance: x2) - [1 PP]
  • Leather Catsuit (Container, Passive 2) () - [10 PP]
    • Immunity 2 (environmental condition: Heat, environmental condition: Cold) - [2 PP]
    • Protection 4 (+4 Toughness; Impervious) - [8 PP]
  • Magically Enhanced Traits (Enhanced Trait 40) (Traits: Constitution +10 (24, +7), Strength +10 (20, +5), Charisma +14 (24, +7), Dodge Focus 6 +4 (+6), Feats: Attractive 2 (+8)) - [40 PP]
  • Speed 1 (Speed: 10 mph, 88 ft./rnd) - [1 PP]
  • Whip Powers (Container, Passive 6) () - [30 PP]
    • Elongation 3 (Elongation: 25 ft., range incr 30 ft., +3 Escape & Grapple; Projection; Limited (Limited to whip)) - [3 PP]
    • Enhanced Trait 11 (Feats: Attack Specialization 3 (Grapple), Chokehold, Defensive Throw, Fascinate (Intimidate), Improved Disarm, Improved Grab, Improved Grapple, Improved Pin, Improved Trip) - [11 PP]
    • Strike 6 (DC 26; Mighty) - [10 PP]
      • Blinding Strike (Dazzle 7) (affects: visual senses, DC 17; Range (touch)) - [1 PP]
      • Gimp (Paralyze 7) (DC 17; Alternate Save (Fortitude); Slow) - [1 PP]
      • Trip 5 (Knockback; Range (touch); Custom (Mighty - Add Strength bonus to trip attempt), Custom (Up in the Air (Choose direction of Knockback))) - [1 PP]
    • Super-Movement 3 (slow fall, swinging, wall-crawling 1 (half speed)) - [6 PP]


Right now, I have a generic function for output powers. I'll need to change it to accommodate input that a set of powers are alternate powers.

The power output function is the following, and takes in an XML element which contains <power> tags:

Code: Select all

def printPowers (powers):
   if powers is not None and len(powers) > 0:
      print ('[list]')
      for power in powers:
         print ('[*][b]' + power.attrib['name'], '[/b](' + power.attrib['summary'] + ') - [i][' + power.find('cost').attrib['text'] + '][/i]')

         otherpowersEl = power.find('otherpowers')
         printPowers(otherpowersEl)

         alternatePowersEl = power.find('alternatepowers')
         printPowers(alternatePowersEl)
      print('[/list]')

User avatar
badpenny
Overlord
Overlord
Posts: 4406
Joined: Sat Jan 17, 2009 6:09 pm
Location: Under The Greenwood Tree

Re: Statblock output option

Postby badpenny » Thu Mar 06, 2014 4:57 am

The power block output looks great, Fuzzy!

As to the skills, I have to wonder if that's a bit too much? Generally skill blocks are easy to read, and with the descriptions present it feels a little cluttered.

How will this be implemented?

I don't understand a lick of the code, but it's cool what you can do with it!

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Thu Mar 06, 2014 5:11 am

Probably is a bit much. I started out doing it more conventionally, then figured that producing output virtually identical to the original format wasn't proving much. :D As regards integration into Hero Lab, I still need to figure that out. I've got Hero Lab invoking the script using the Save Custom Output dialog, but my current method just prints things out. I suspect that I'll need to store the text of the output and either export it to a file or spawn a dialog box to display it to the user so that they can copy and paste it. I'll probably have to put a wrapper around it in case people don't have a Python interpreter. :lol: As it is, with an interpreter, you just send the filename in as a command-line parameter and it prints the stat block.

I'm open to suggestions as to what exactly to add or remove. I intend to have it automatically export some of the other missing fields, such as the Height, Weight, and Eye Color. I'm debating adding some sort of verbose flag to add descriptions to feats and powers. Ideally, I'd like to only do that with non-core items, but the XML output doesn't include source information (although it is in Hero Lab itself, so perhaps I can make a bug fix request).

User avatar
badpenny
Overlord
Overlord
Posts: 4406
Joined: Sat Jan 17, 2009 6:09 pm
Location: Under The Greenwood Tree

Re: Statblock output option

Postby badpenny » Thu Mar 06, 2014 6:11 am

I only ever use File :arrow: Output Hero Statblock :arrow: BBCode

So for my purposes I just want points added to the Powers block because that's all I ever modify. You can see what I'm talking about on one of my character sheets in my sig.

I didn't feel Skills needed the points modification since the above output already lists the points spent in the following format: Athletics 4 (+10).

I don't have any kind of compiler, so I was hoping that a Code Wizard at Lone Wolf could implement it as a nice shiny radio button for the likes of me to use! :mrgreen:

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Thu Mar 06, 2014 10:28 pm

More updates. Once I get a good prototype together, I'll release it as an EXE with associated files so that you should be able to do a one-click from Save Custom Output.

Whiplash

Power Level: 10 Power Points Spent: 150/150

Str: +0/+5(10/20) Dex: +3(16) Con: +2/+7(14/24) Int: +1(12) Wis: +2(14) Cha: +0/+7(10/24)

Tough: +7/+11 (Imp: +4) Fort: +7 Ref: +10 Will: +7
Skills:
Acrobatics +12 (+15, Bluff +0 (+7, Climb +0 (+5, Concentration +4 (+6, Diplomacy +0 (+7, Disguise +0 (+7, Escape Artist +6 (+9/+12, Gamble +0 (+2, Gather Information +0 (+7, Handle Animal +0 (+7, Intimidate +12 (+19, Medicine +2 (+4, Navigate +0 (+1, Notice +8 (+10, Profession (Librarian) +2 (+4, Search +4 (+5, Sense Motive +6 (+8, Stealth +10 (+13, Survival +2 (+4, Swim +0 (+5

Feats:
  • Acrobatic Bluff - You can use your Acrobatics skill instead of your Bluff skill to feint and trick in combat.
  • Attack Focus (melee) 2 - You gain a +1 bonus to melee attacks per rank in this feat.
  • Attack Specialization 2 (Whip Powers (Container, Passive 6)) - You gain a +2 bonus to a specific attack or weapon per rank in this feat.
  • Attack Specialization 3 (Grapple) - You gain a +2 bonus to a specific attack or weapon per rank in this feat.
  • Attractive 2 (+8) - You're particularly attractive, giving you a +4 bonus per rank on Bluff and Diplomacy checks to deceive, seduce, or change the attitude of anyone who might find you appealing. This bonus cannot increase your total effective skill rank higher than the campaign's power level limit.
  • Chokehold - If you pin an opponent, you can apply a chokehold, causing your opponent to begin suffocating for as long as you maintain the pin.
  • Defensive Throw - If an opponent attacks you in melee combat and misses, you can make an immediate trip attack against them as a free action at your full attack bonus. This counts towards your attacks of opportunity for that round.
  • Dodge Focus 6 - You gain a +1 dodge bonus to your defense bonus per rank in this feat.
  • Elusive Target - While you are fighting an opponent in melee combat, others attempting to target you with ranged attacks are at a -8 penalty rather than the usual -4 penalty for shooting into melee combat.
  • Evasion - If you make your Reflex save against an area effect, you suffer no damage. If you have two ranks in this feat, you only take half damage from an area effect even if you fail the Reflex save, and no damage if you succeed.
  • Fascinate (Intimidate) - Your Intimidate skill is so effective you can capture and hold someone's attention with it. You are subject to the normal guidelines for interaction skills, and combat or other immediate danger makes this feat ineffective.
  • Fearless - You are immune to Fear Effects. This is equivalent to the power Immunity 1 (Fear Effects).
  • Grappling Finesse - You can use your Dexterity bonus, rather than your Strength bonus, to make grapple checks. You retain your dodge bonus to Defense against all opponents while grappling.
  • Hide in Plain Sight - You can make Stealth checks even while being observed and even if you do not have cover or concealment.
  • Improved Disarm - You have a +2 bonus per rank on attack rolls when attempting to disarm an opponent and they do not get the opportunity to disarm you.
  • Improved Grab - When you hit with an unarmed attack, you can immediately start a grapple as a free action if your opponent is no larger than you.
  • Improved Grapple - You can make grapple attacks with only one hand, leaving the other free.
  • Improved Pin - Opponents suffer a -4 penalty on grappling checks to escape a grapple or pin.
  • Improved Trip - You have a +4 bonus on checks to trip an opponent (normally or using a power) and they do not get the opportunity to trip you. If you use this feat with a ranged trip attack it's only half as effective (a +2 bonus).
  • Startle - You can use your Intimidate skill instead of your Bluff skill to feint in combat.
Powers:
  • Jaded (Immunity 5) (interaction skills) - [5 PP]
  • Leaping 1 (Jumping distance: x2) - [1 PP]
  • Leather Catsuit (Container, Passive 2) - [10 PP]
    • Immunity 2 (environmental condition: Heat, environmental condition: Cold) - [2 PP]
    • Protection 4 (+4 Toughness; Impervious) - [8 PP]
  • Magically Enhanced Traits (Enhanced Trait 40) (Traits: Constitution +10 (24, +7), Strength +10 (20, +5), Charisma +14 (24, +7), Dodge Focus 6 +4 (+6), Feats: Attractive 2 (+8)) - [40 PP]
  • Speed 1 (Speed: 10 mph, 88 ft./rnd) - [1 PP]
  • Whip Powers (Container, Passive 6) - [30 PP]
    • Elongation 3 (Elongation: 25 ft., range incr 30 ft., +3 Escape & Grapple; Projection; Limited (Limited to whip)) - [3 PP]
    • Enhanced Trait 11 (Feats: Attack Specialization 3 (Grapple), Chokehold, Defensive Throw, Fascinate (Intimidate), Improved Disarm, Improved Grab, Improved Grapple, Improved Pin, Improved Trip) - [11 PP]
    • Strike 6 (DC 26; Mighty) - [10 PP]
      • AP:Blinding Strike (Dazzle 7) (affects: visual senses, DC 17; Range (touch)) - [1 PP]
      • AP:Gimp (Paralyze 7) (DC 17; Alternate Save (Fortitude); Slow) - [1 PP]
      • AP:Trip 5 (Knockback; Range (touch); Custom (Mighty - Add Strength bonus to trip attempt), Custom (Up in the Air (Choose direction of Knockback))) - [1 PP]
    • Super-Movement 3 (slow fall, swinging, wall-crawling 1 (half speed)) - [6 PP]


Code: Select all

import sys
# from xml.dom.minidom import parseString
import xml.etree.ElementTree as ET

def printPowers (powers, prefix=""):
   if powers is not None and len(powers) > 0:
      print ('[list]')
      for power in powers:
         powerSummary = power.attrib['summary'].strip()
         if powerSummary:
            powerSummary = '(' + powerSummary + ')'
         
         print ('[*]' + prefix + '[b]' + power.attrib['name'], '[/b]' + powerSummary + ' - [i][' + power.find('cost').attrib['text'] + '][/i]')

         otherpowersEl = power.find('otherpowers')
         printPowers(otherpowersEl)

         alternatePowersEl = power.find('alternatepowers')
         printPowers(alternatePowersEl, 'AP:')
      print('[/list]')   

def printItems (items):
   if items is not None and len(items) > 0:
      print ('[list]')
      for item in items:
         print ('[*]' + item.attrib['name'], end='')
         componentPowers = item.find('componentpowers')
         if componentPowers is not None:
            powers = componentPowers.findall('power')
            powerList = []
            for power in powers:
                powerList.append(power.attrib['name'] + '[' + power.attrib['summary'] + ']')

            if len(powerList) > 0:
                print ('(' + '; '.join(powerList) + ')', end='')
                       
         print ('[i][' + item.find('cost').attrib['text'] + '][/i]')

         componentItems = item.find('componentitems')
         printItems(componentItems)
      print ('[/list]')
           
           

xmlfile = open(sys.argv[1], 'r')
# xmlfile = open("Random.xml", 'r')
tree = ET.parse(xmlfile)
xmlfile.close()

root = tree.getroot()

for character in root.findall("./*/character"):
   role = character.attrib['role']
   
   name = character.attrib.get('name')
   if name is None:
      name = "Unnamed Hero"

   print ('[SIZE=150]' + character.attrib['name'] + '[/SIZE]')
   print ()

   resources = character.find('resources')
   if resources is not None:
      currentPL = resources.attrib['currentpl']
      totalPP = resources.attrib['totalpp']

      # Next step is to pull in the resources themselves.
      totalPPSpent = 0
      resourceVals = resources.findall('resource')
      for resource in resourceVals:
        totalPPSpent += int(resource.attrib['spent'])

      print ('Power Level: ' + str(currentPL) + ' Power Points Spent: ' + str(totalPPSpent), end='')

      if role == 'pc':
         print ('/' + str(totalPP))
      else:
         print ()

      print ()
   #print the attributes
   attributesEl = character.find('attributes')

   if attributesEl is not None:
      attributes = attributesEl.findall('attribute')

      for attribute in attributes:
         print ('[b]' + attribute.attrib['name'][0:3] + ': ', end='')

         attrvalue = attribute.find('attrvalue')
         attrbonus = attribute.find('attrbonus')

         if attrbonus is not None:
            print (attrbonus.attrib['text'] + '[/b]', end='')
         else:
            print ('[/b]', end='')

         if attrvalue is not None:
            print ('(' + attrvalue.attrib['text'] + ')', end='')

         print (' ', end='')
      print ()
      print ()

   #Saves
   savesEl = character.find('saves')

   if savesEl is not None:
      saves = savesEl.findall('save')

      print ('[b]', end='')
      for save in saves:
         print (save.attrib['abbr'] + ': ' + save.attrib['text'] + ' ', end='')

         if int(save.attrib['impervious']) > 0:
            print ('[i](Imp: ' + save.attrib['impervious'] + ')[/i] ', end='')
      print ('[/b]')

   #Skills
   skillsEl = character.find('skills')
   if skillsEl is not None:
      skills = skillsEl.findall('skill')
      print ('[b]Skills: [/b]')

      skill_list = []
      for skill in skills:
         userRanks = int(skill.attrib['user'])
         if userRanks > 0 or skill.attrib.get('trainedonly') != 'yes':
           skill_list.append('{0} {1} ([b]{2}[/b]'.format(skill.attrib['name'], skill.attrib['user'], skill.attrib['value']))
                                                   
      print (', '.join(skill_list))
      print ()


   #Feats
   featsEl = character.find('feats')
   if featsEl is not None:
      feats = featsEl.findall('feat')
      print ('[b]Feats: [/b][list]')
     
      for feat in feats:
         print ('[*]', feat.attrib['name'], '-', feat.find('description').text)
                                                   
      print ('[/list]')
     
   #Powers
   powersEl = character.find('powers')
   if powersEl is not None:
      print ('[B]Powers:[/B]')
      printPowers(powersEl)
         
   #Equipment
   equipmentEl = character.find('gear')
   if equipmentEl is not None and equipmentEl.find('item') is not None:
      items = equipmentEl.findall('item')
      print ('[B]Equipment:[/B]')

      printItems(items)

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Fri Mar 07, 2014 2:03 pm

Ah-hah! I've figured out the trick to spawning a graphical window so the user can cut and paste from it. Once I get home, I'll be able to set this up as an executable so people can try it out. Still working out those little corner cases like how to display multiple powers in a piece of equipment, but this is getting more and more concrete as I go.

I am also learning that there are some minor errors in the XML output such as Personalizations not getting exported.

User avatar
badpenny
Overlord
Overlord
Posts: 4406
Joined: Sat Jan 17, 2009 6:09 pm
Location: Under The Greenwood Tree

Re: Statblock output option

Postby badpenny » Fri Mar 07, 2014 2:23 pm

It's looking sharp, Fuzzy. For my own taste, I'd not want the skill summary/description. Any way to format it for a block output, rather than a list? It's more compact that way.

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Fri Mar 07, 2014 6:03 pm

badpenny wrote:It's looking sharp, Fuzzy. For my own taste, I'd not want the skill summary/description. Any way to format it for a block output, rather than a list? It's more compact that way.

I've changed the Skills back. The feats, I can set it up either way.

User avatar
badpenny
Overlord
Overlord
Posts: 4406
Joined: Sat Jan 17, 2009 6:09 pm
Location: Under The Greenwood Tree

Re: Statblock output option

Postby badpenny » Fri Mar 07, 2014 6:09 pm

Oops, I meant no description for the Feats.

Did you notice there's no closing parenthesis on the skills bonus?

Acrobatics +12 (+15, Bluff +0 (+7, Climb +0 (+5, Concentration +4 (+6, Diplomacy +0 (+7, Disguise +0 (+7, Escape Artist +6 (+9/+12, Gamble +0 (+2, Gather Information +0 (+7, Handle Animal +0 (+7, Intimidate +12 (+19, Medicine +2 (+4, Navigate +0 (+1, Notice +8 (+10, Profession (Librarian) +2 (+4, Search +4 (+5, Sense Motive +6 (+8, Stealth +10 (+13, Survival +2 (+4, Swim +0 (+5

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Fri Mar 07, 2014 7:20 pm

Ah, yeah, I corrected that one. I suppose I can remove the description from the feats. :) Once I get things a bit fancier, there will be the option to add such things back in.

There are some odd missing bits in the XML output such as "multiple powers" for an array slot, personalizations, and the dodge bonus. I've filed bug reports, but goodness knows when we'll get feedback on that, since I'm currently working on 2E.

By the by, I can make a game attempt at a 3E sheet, but I can't save portfolios, so there will be a bit more guess-work if the stock portfolios don't have an example of a given power structure.

User avatar
FuzzyBoots
Cosmic Entity
Cosmic Entity
Posts: 9719
Joined: Sun Aug 05, 2007 10:15 am
Location: Pittsburgh, PA
Contact:

Re: Statblock output option

Postby FuzzyBoots » Fri Mar 07, 2014 8:22 pm

Alright. That's fairly decent.

Hosea "Ace" Skaggs

Power Level: 8 Power Points Spent: 120/120

Str: +3 (16) Dex: +4 (18) Con: +3 (16) Int: +1 (12) Wis: +1 (12) Cha: +1 (12)

Tough: +3/+7 Fort: +8 Ref: +9 Will: +5

Skills:
Acrobatics +4 (+8), Bluff +0 (+1), Climb +2 (+5), Concentration +6 (+7), Diplomacy +0 (+1), Disable Device +4 (+5), Disguise +0 (+1), Drive +2 (+6), Escape Artist +3 (+7), Gamble +0 (+1), Gather Information +0 (+1), Handle Animal +0 (+1), Intimidate +6 (+7), Investigate +2 (+3), Knowledge (current events) +1 (+2), Knowledge (popular culture) +1 (+2), Knowledge (streetwise) +1 (+2), Knowledge (tactics) +6 (+7), Knowledge (technology) +2 (+3), Medicine +2 (+3), Navigate +0 (+1), Notice +6 (+7), Profession (Agent) +2 (+3), Search +2 (+3), Sense Motive +7 (+8), Stealth +7 (+11), Survival +6 (+7), Swim +8 (+11)

Feats:
All-Out Attack, Attack Focus (ranged) 2, Attack Specialization (Assault Rifle), Benefit (FBI Agent), Chokehold, Diehard, Dodge Focus 4, Equipment 5, Favored Opponent 2 2 (Aces) (+2), Fighting Style: Krav Maga, Grappling Finesse, Improved Aim, Improved Block, Improved Critical (Assault Rifle), Improved Disarm, Improved Grapple, Improved Sunder, Improved Trip, Luck 2, Move-by Action, Power Attack, Precise Shot, Quick Draw 2, Startle, Swift

Powers:
  • Parkour (Super-Movement 3) (training, sure-footed 3 (75% penalty reduction)) - [6 PP]

Equipment:
  • Arsenal[19 ep]
    • Assault Rifle[16 ep]
    • Flash-Bang[1 ep*]
    • Fragmentation Grenade[1 ep*]
    • Taser[1 ep*]
  • FBI Standard issue [No Cost][free!]
    • Commlink[1 ep*]
    • Handcuffs[1 ep*]
    • ID Card(Enhanced Trait 1[Feats: Benefit (FBI Agent)])[1 ep*]
    • Light Pistol[1 ep*]
    • Stun Gun[14 ep]
    • Undercover Shirt[1 ep*]
  • Multi-Tool[1 ep]
  • Night Vision Goggles[1 ep]
  • Tactical Vest[4 ep]

Attack Bonus: +5 (Ranged: +7, Melee: +5, Grapple: +9)

Attacks: Assault Rifle, +9 (DC 20) Crit: 19-20 [50ft. range incr.]; Flash-Bang, +7 (DC 14) [20ft. Radius]; Fragmentation Grenade, +7 (DC 15) [50ft. Radius]; Light Pistol, +7 (DC 18) [30ft. range incr.]; Stun Gun, +5 (DC 17); Taser, +7 (DC 15) [5ft. range incr.]; Unarmed Attack, +5 (DC 18)

Defense: +9 (Flat-footed: ?), Knockback: -3

Initiative: +4

Encumbrance: Light: 76 lbs, Medium: 153 lbs, Heavy: 230 lbs, Maximum: 460 lbs, Push / Drag: 1.2k lbs

Gender: Male Age: 25
Eyes: Hair:
Height: 5' 8" Weight: 175 lb.

Totals: Abilities 26 + Skills 20 + Feats 34 + Powers 6 + Combat 20 + Saves 14 + Drawbacks 0 = 120


Code: Select all

from tkinter import *
from tkinter import ttk
import sys
import xml.etree.ElementTree as ET
import io

def printPowers (powers, prefix=""):
   if powers is not None and len(powers) > 0:
      print ('[list]')
      for power in powers:
         powerSummary = power.attrib['summary'].strip()
         if powerSummary:
            powerSummary = '(' + powerSummary + ')'
         
         print ('[*]' + prefix + '[b]' + power.attrib['name'], '[/b]' + powerSummary + ' - [i][' + power.find('cost').attrib['text'] + '][/i]')
         
         otherpowersEl = power.find('otherpowers')
         printPowers(otherpowersEl)
         
         alternatePowersEl = power.find('alternatepowers')
         printPowers(alternatePowersEl, 'AP:')
      print('[/list]')   

def printItems (items):
   if items is not None and len(items) > 0:
      print ('[list]')
      for item in items:
         print ('[*]' + item.attrib['name'], end='')
         componentPowers = item.find('componentpowers')
         if componentPowers is not None:
            powers = componentPowers.findall('power')
            powerList = []
            for power in powers:
                powerList.append(power.attrib['name'] + '[' + power.attrib['summary'] + ']')
            if len(powerList) > 0:
                print ('(' + '; '.join(powerList) + ')', end='')
                       
         print ('[i][' + item.find('cost').attrib['text'] + '][/i]')
         componentItems = item.find('componentitems')
         printItems(componentItems)
      print ('[/list]')
           
# Store the reference, in case you want to show things again in standard output
old_stdout = sys.stdout
 
# This variable will store everything that is sent to the standard output
printOutput = io.StringIO()
sys.stdout = printOutput

xmlfile = open(sys.argv[1], 'r')
tree = ET.parse(xmlfile)
xmlfile.close()
root = tree.getroot()
for character in root.findall("./*/character"):
   role = character.attrib['role']
   
   name = character.attrib.get('name')
   if name is None:
      name = "Unnamed Hero"
   print ('[SIZE=150]' + character.attrib['name'] + '[/SIZE]')
   print ()
   resources = character.find('resources')
   if resources is not None:
      currentPL = resources.attrib['currentpl']
      totalPP = resources.attrib['totalpp']
      # Next step is to pull in the resources themselves.
      totalPPSpent = 0
      resourceVals = resources.findall('resource')
      resourceList = []
      for resource in resourceVals:
        totalPPSpent += int(resource.attrib['spent'])
        resourceList.append('{0} {1}'.format(resource.attrib['name'], resource.attrib['spent']))
       
      print ('Power Level: ' + str(currentPL) + ' Power Points Spent: ' + str(totalPPSpent), end='')
      if role == 'pc':
         print ('/' + str(totalPP))
      else:
         print ()
      print ()

   #print the attributes
   attributesEl = character.find('attributes')
   if attributesEl is not None:
      attributes = attributesEl.findall('attribute')
      for attribute in attributes:
         print ('[b]' + attribute.attrib['name'][0:3] + ': ', end='')
         attrvalue = attribute.find('attrvalue')
         attrbonus = attribute.find('attrbonus')
         if attrbonus is not None:
            print (attrbonus.attrib['text'] + '[/b]', end='')
         else:
            print ('[/b]', end='')
         if attrvalue is not None:
            print ('(' + attrvalue.attrib['text'] + ')', end='')
         print (' ', end='')
      print ()
      print ()

   #Saves
   savesEl = character.find('saves')
   if savesEl is not None:
      saves = savesEl.findall('save')
      print ('[b]', end='')
      for save in saves:
         print (save.attrib['abbr'] + ': ' + save.attrib['text'] + ' ', end='')
         if int(save.attrib['impervious']) > 0:
            print ('[i](Imp: ' + save.attrib['impervious'] + ')[/i] ', end='')
      print ('[/b]')
      print ()

   #Skills
   skillsEl = character.find('skills')
   if skillsEl is not None:
      skills = skillsEl.findall('skill')
      print ('[b]Skills: [/b]')
      skill_list = []
      for skill in skills:
         userRanks = int(skill.attrib['user'])
         if userRanks > 0 or skill.attrib.get('trainedonly') != 'yes':
           skill_list.append('{0} {1} ([b]{2}[/b])'.format(skill.attrib['name'], skill.attrib['user'], skill.attrib['value']))
                                                   
      print (', '.join(skill_list))
      print ()

   verboseFeats = False
   #Feats
   featsEl = character.find('feats')
   if featsEl is not None:
      feats = featsEl.findall('feat')
      print ('[b]Feats: [/b]')
      if verboseFeats:
         print ('[list]')
         for feat in feats:
            print ('[*]', feat.attrib['name'], '-', feat.find('description').text)
         print ('[/list]')
      else:
         featList = []
         for feat in feats:
            featList.append(feat.attrib['name'])

         print (', '.join(featList))
         
      print ()
     
   #Powers
   powersEl = character.find('powers')
   if powersEl is not None and powersEl.find('power') is not None:
      print ('[B]Powers:[/B]')
      printPowers(powersEl)
      print ()
         
   #Equipment
   equipmentEl = character.find('gear')
   if equipmentEl is not None and equipmentEl.find('item') is not None:
      items = equipmentEl.findall('item')
      print ('[B]Equipment:[/B]')
      printItems(items)
      print ()

   #Combat
   combatEl = character.find('attacks')
   if combatEl is not None:
      print ('[b]Attack Bonus: {0} [/b](Ranged: {1}, Melee: {2}, Grapple: {3})'.format(combatEl.attrib['attackbonus'], combatEl.attrib['rangedattack'], combatEl.attrib['meleeattack'], combatEl.attrib['grapple']))
      print ()
     
      attacks = combatEl.findall('attack')
      attackList = []
      for attack in attacks:
         attackText = '{0}, {1} (DC {2})'.format(attack.attrib['name'], attack.attrib['attack'], attack.attrib['dc'])
         if attack.attrib['crit'] != '20':
            attackText += ' Crit: ' + attack.attrib['crit']
         if attack.attrib['special']:
            attackText += ' [{0}]'.format(attack.attrib['special'])
         if attack.attrib['rangeincr']:
            attackText += ' [{0} range incr.]'.format(attack.attrib['rangeincr'])

         attackList.append(attackText)       
     
      if len(attackList) > 0:
         print ('[b]Attacks: [/b]', '; '.join(attackList))
         print ()

   #Defense
   defenseEl = character.find('defense')
   if defenseEl is not None:
      print ('[b]Defense: {0}[/b]  (Flat-footed: {1}), Knockback: {2}'.format(defenseEl.attrib['defense'], '?', defenseEl.attrib['knockbackresist']))
      print ()

   #Initiative
   initiativeEl = character.find('initiative')
   if initiativeEl is not None:
      print ('[b]Initiative: {0}[/b]'.format(initiativeEl.attrib['total']))
      print ()

   #Encumbrance
   encumbranceEl = character.find('encumbrance')
   if encumbranceEl is not None:
      encumlevels = encumbranceEl.findall('encumlevel')
      enumlevelList = []
      for encumlevel in encumlevels:
         enumlevelList.append('{0}: {1}'.format(encumlevel.attrib['name'], encumlevel.attrib['text']))

      if len(enumlevelList) > 0:
         print('[b]Encumbrance: [/b]' + ', '.join(enumlevelList))
         print ()
         
   #Languages
   languagesEl = character.find('language')
   if languagesEl is not None:
      languages = languagesEl.findall('language')
      languageList = []
      for language in languages:
         languageList.append(language.attrib['name'])

      if len(languageList) > 0:
         print ('[b]Languages: [/b]' + ', '.join(languageList))
         print()
   
   #Personal information
   personalEl = character.find('personal')
   if personalEl is not None:
      print ('[b]Gender: [/b]{0}\t [b]Age:[/b] {1}'.format(personalEl.attrib['gender'], personalEl.attrib['age']))
      print ('[b]Eyes: [/b]{0}\t [b]Hair:[/b] {1}'.format(personalEl.attrib['eyes'], personalEl.attrib['hair']))

      heightEl = personalEl.find('charheight')
      weightEl = personalEl.find('charweight')

      if heightEl is not None:
         print ('[b]Height:[/b] ' + heightEl.attrib['text'] + '\t', end='')
      if weightEl is not None:
         print ('[b]Weight:[/b] ' + weightEl.attrib['text'])
      print()

      descriptionEl = personalEl.find('description')
      if descriptionEl is not None and descriptionEl.text:
         print ('[B]Background:[/B]')
         print (descriptionEl.text)
         print ()

   #Complications
   complicationsEl = character.find('complications')
   if complicationsEl is not None and complicationsEl.text:
      print ('[b]Complications[/b]:')
      print (complicationsEl.text)
      print ()
   
   

   #Summary
   print ('[b]Totals: [/b]' + ' + '.join(resourceList) + ' = ' + str(totalPPSpent))
   
#re-enable standard out
sys.stdout = old_stdout

#Display the results
root = Tk()
root.title("BBCode Output")
label = Text(root)
label.insert(1.0, printOutput.getvalue())
label.grid(column=0, row=0, sticky=(N,W,E,S))
s = ttk.Scrollbar(root, orient=VERTICAL, command=label.yview)
s.grid(column=1, row=0, sticky=(N,S))
label['yscrollcommand'] = s.set
ttk.Sizegrip().grid(column=1, row=1, sticky=(S,E))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
label.configure(state="disabled")

root.mainloop()


Still working on setting up the executable and have it be called directly from HeroLab. Apparently, this was easy before Python 3.3 and is supposed to be easy now, but I'm just not getting it.

User avatar
badpenny
Overlord
Overlord
Posts: 4406
Joined: Sat Jan 17, 2009 6:09 pm
Location: Under The Greenwood Tree

Re: Statblock output option

Postby badpenny » Sat Mar 08, 2014 9:36 am

That's a great looking sheet, Fuzzy. The addition of encumbrance is a nice touch.

Well done!

User avatar
Aerlock
Heavyweight
Heavyweight
Posts: 517
Joined: Mon Nov 20, 2006 3:03 pm

Re: Statblock output option

Postby Aerlock » Sat Mar 08, 2014 1:22 pm

This looks real good and I'm interested in using it, but I don't quite understand where I put the code to use it. Any pointers?

- Aerlock
awww...I don't have a signature :(
But I do have character(s)!


Return to “Hero Lab”



Who is online

Users browsing this forum: No registered users and 2 guests