Advertisement
Guest User

Split full discipline powers for Hero Lab

a guest
Mar 18th, 2012
190
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 34.71 KB | None | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use common::sense;
  4. use IO::File;
  5. use XML::Parser;
  6. use XML::Writer;
  7. use File::HomeDir;
  8. use File::Spec;
  9. use Data::Dumper;
  10. use Clone qw(clone);
  11. use Readonly;
  12.  
  13. #### Prototypes
  14. sub order_to_rank;
  15. sub build_word_maps;
  16. sub tag_to_word;
  17. sub tag_to_name;
  18. sub remember_element;
  19. my %thing_read;
  20. my %thing_write;
  21. sub StartThing;
  22. sub StartNested;
  23. sub StartPassthrough;
  24. sub StartTag;
  25. sub EndTag;
  26. sub Char;
  27. sub Text;
  28. sub ConvertFDPower;
  29. sub WriteThing;
  30. sub WriteElements;
  31.  
  32. #### Variables
  33. Readonly my $default_directory => File::Spec->catdir( File::HomeDir->my_data, q{Hero Lab}, q{Data}, q{4e} );
  34.  
  35. my @element_path;
  36.  
  37. my $thing_element;
  38. my $active_element;
  39. my @active_element;
  40.  
  41. my %tag_name;
  42. my %tag_abbrev;
  43.  
  44. my $active_text;
  45. my $next_pad;
  46.  
  47. my $xml_writer;
  48.  
  49. my %attribute_order = (
  50.     document => [ qw( signature ) ],
  51.     thing => [ qw( id compset name description summary isunique holdable maxlimit panellink stacking lotsize replaces buytemplate xactspecial isprivate ) ],
  52.     fieldval => [ qw( field value ) ],
  53.     arrayval => [ qw( field index column value ) ],
  54.     usesource => [ qw( source name parent ) ],
  55.     tag => [ qw( group tag name abbrev ) ],
  56.     bootstrap => [ qw( thing index ) ],
  57.     containerreq => [ qw( phase priority name ) ],
  58.     link => [ qw( linkage thing ) ],
  59.     eval => [ qw( phase priority index runlimit iseach sortas name isprimary ) ],
  60.     evalrule => [ qw( phase priority message summary index severity runlimit iseach reportlimit issilent sortas name isprimary ) ],
  61.     pickreq => [ qw( thing iserror ispreclude onlyonce issilent ) ],
  62.     exprreq => [ qw( message iserror onlyonce issilent ) ],
  63.     prereq => [ qw( message iserror onlyonce issilent ) ],
  64.     child => [ qw( entity ) ],
  65.     minion => [ qw( id ownmode isinherit ) ],
  66.  
  67.     before => [ qw( name ) ],
  68.     after => [ qw( name ) ],
  69.     autotag => [ qw( group tag ) ],
  70.     assignval => [ qw( field value behavior ) ],
  71. );
  72.  
  73. my %attribute_rank;
  74. order_to_rank( \%attribute_order, \%attribute_rank );
  75.  
  76. my %has_text = (
  77.     containerreq => 1,
  78.     holdlimit => 1,
  79.     gear => 1,
  80.     eval => 1,
  81.     evalrule => 1,
  82.     exprreq => 1,
  83.     match => 1,
  84.     test => 1,
  85.     validate => 1,
  86. );
  87.  
  88. my %element_order = (
  89.     document => [ qw( procedure thing portal template layout panel form sheet dossier hidden editthing faq ) ],
  90.     thing => [ qw( fieldval arrayval usesource tag bootstrap containerreq holdlimit gear link eval evalrule pickreq exprreq prereq child minion ) ],
  91.     bootstrap => [ qw( match containerreq autotag assignval ) ],
  92.     containerreq => [ qw( match before after ) ],
  93.     eval => [ qw( match before after ) ],
  94.     evalrule => [ qw( match before after ) ],
  95.     prereq => [ qw( match test validate ) ],
  96.     child => [ qw( tag bootstrap ) ],
  97.     minion => [ qw( tag bootstrap ) ],
  98. );
  99.  
  100. # We do not really need to worry about these elements:
  101. # procedure portal template layout panel form sheet dossier hidden editthing faq
  102.  
  103. my %element_rank;
  104. order_to_rank( \%element_order, \%element_rank );
  105.  
  106. my %full_discipline_tags = (
  107.     PowerType => 1,
  108.     ReqLevel => 1,
  109.     PowerClass => 1,
  110.     PowerDest => 1,
  111.     PowerPath => 1,
  112.     RecomClass => 1,
  113.     ReqSkill => 1,
  114.     User => {
  115.         NeedChosen => 1,
  116.         PwrAsClass => 1,
  117.     },
  118. # FeatureChk NeedChosen PwrAsClass
  119. # AttrMental AttrPhys RangeDex
  120. );
  121.  
  122. my %attack_technique_supress_tags = (
  123.     PowerClass => 1,
  124.     PowerDest => 1,
  125.     PowerPath => 1,
  126.     ReqSkill => 1,
  127.     User => {
  128.         NeedChosen => 1,
  129.         PwrAsClass => 1
  130.     },
  131. );
  132.  
  133. my %type_keywords = map { $_ => 1 } qw( PowerType PowerSrc PowerAcc DamageType EffectType );
  134. my %action_range = map { $_ => 1 } qw( ActionType AttackType );
  135. my %prefer_words = (
  136.     q{Move Action} => 1,
  137.     q{Free Action} => 1,
  138.     q{No Action} => 1,
  139.     q{Standard Action} => 1,
  140.     q{Minor Action} => 1,
  141.     q{Teleport} => 1,
  142.     q{At-Will} => 1,
  143.     q{Daily} => 1,
  144.     q{Encounter} => 1,
  145. );
  146.  
  147. my %word_to_tag_by_group = (
  148.     q{} => {
  149.         q{Aura}                  => q{Aura},
  150.         q{Elemental}             => q{Elemental}
  151.     },
  152.     q{ActionType} => {
  153.         q{Free}                  => q{Free},
  154.         q{Free Action}           => q{Free},
  155.         q{Free Action (Special)} => q{Free},
  156.         q{Immediate Interrupt}   => q{ImmedInt},
  157.         q{Immediate Reaction}    => q{ImmedReact},
  158.         q{Minor}                 => q{Minor},
  159.         q{Minor Action}          => q{Minor},
  160.         q{Move}                  => q{Move},
  161.         q{Move Action}           => q{Move},
  162.         q{No Action}             => q{None},
  163.         q{None}                  => q{None},
  164.         q{Opportunity Action}    => q{Opportun},
  165.         q{Standard}              => q{Standard},
  166.         q{Standard Action}       => q{Standard}
  167.     },
  168.     q{AttackType} => {
  169.         q{Area Burst}            => q{AreaBurst},
  170.         q{Area Wall}             => q{AreaWall},
  171.         q{Area}                  => q{Area},
  172.         q{Close Blast}           => q{CloseBlast},
  173.         q{Close Burst}           => q{CloseBurst},
  174.         q{Close}                 => q{Close},
  175.         q{Melee Touch}           => q{MeleeTouch},
  176.         q{Melee Weapon}          => q{MeleeWep},
  177.         q{Melee}                 => q{Melee},
  178.         q{Personal}              => q{Personal},
  179.         q{Ranged Sight}          => q{RangeSight},
  180.         q{Ranged Weapon}         => q{RangeWep},
  181.         q{Ranged}                => q{Range}
  182.     },
  183.     q{DamageType} => {
  184.         q{Acid}                  => q{Acid},
  185.         q{Cold}                  => q{Cold},
  186.         q{Fire}                  => q{Fire},
  187.         q{Force}                 => q{Force},
  188.         q{Lightning}             => q{Lightning},
  189.         q{Necrotic}              => q{Necrotic},
  190.         q{Psychic}               => q{Psychic},
  191.         q{Radiant}               => q{Radiant},
  192.         q{Thunder}               => q{Thunder}
  193.     },
  194.     q{EffectType} => {
  195.         q{Artifice}              => q{Artifice},
  196.         q{Augmentable}           => q{Augment},
  197.         q{Beast}                 => q{Beast},
  198.         q{Beast Form}            => q{BeastForm},
  199.         q{Charm}                 => q{Charm},
  200.         q{Conjuration}           => q{Conjur},
  201.         q{Fear}                  => q{Fear},
  202.         q{Full Discipline}       => q{FullDisc},
  203.         q{Healing}               => q{Healing},
  204.         q{Illusion}              => q{Illusion},
  205.         q{Invigorating}          => q{Invigorat},
  206.         q{Paralysis}             => q{Paralysis},
  207.         q{Poison}                => q{Poison},
  208.         q{Polymorph}             => q{Polymorph},
  209.         q{Psionic}               => q{Psionic},
  210.         q{Rage}                  => q{Rage},
  211.         q{Rattling}              => q{Rattling},
  212.         q{Reliable}              => q{Reliable},
  213.         q{Shadow}                => q{Shadow},
  214.         q{Sleep}                 => q{Sleep},
  215.         q{Special}               => q{Special},
  216.         q{Stance}                => q{Stance},
  217.         q{Summoning}             => q{Summoning},
  218.         q{Teleport}              => q{Teleport},
  219.         q{Teleportation}         => q{Teleport},
  220.         q{Zone}                  => q{Zone}
  221.     },
  222.     q{PowerAcc} => {
  223.         q{Implement}             => q{Implement},
  224.         q{Weapon}                => q{Weapon}
  225.     },
  226.     q{PowerSrc} => {
  227.         q{Arcane}                => q{Arcane},
  228.         q{Divine}                => q{Divine},
  229.         q{Martial}               => q{Martial},
  230.         q{Primal}                => q{Primal}
  231.     },
  232.     q{PowerType} => {
  233.         q{At-Will (Special)}     => q{AtWill},
  234.         q{At-Will}               => q{AtWill},
  235.         q{Daily (Special)}       => q{Daily},
  236.         q{Daily}                 => q{Daily},
  237.         q{Encounter (Special)}   => q{Encounter},
  238.         q{Encounter}             => q{Encounter}
  239.     }
  240. );
  241.  
  242. my %word_to_group;
  243. my %tag_to_word;
  244. build_word_maps;
  245.  
  246. # Functions
  247.  
  248. sub order_to_rank {
  249.     my ( $order_var, $rank_var ) = @_;
  250.     foreach my $key ( keys %{ $order_var } ) {
  251.         my $rank = 0;
  252.         $rank_var->{ $key } = { map { $_ => ++$rank } @{ $order_var->{ $key } } };
  253.     }
  254. }
  255.  
  256. sub build_word_maps {
  257.     while ( my ( $group, $records ) = each %word_to_tag_by_group ) {
  258.         while ( my ( $word, $tag ) = each %{ $records } ) {
  259.             $word_to_group{ $word } = $group;
  260.             my $old_word = $tag_to_word{ $tag };
  261.             if ( $prefer_words{ $word } || !defined $old_word ) {
  262.                 $tag_to_word{ $tag } = $word;
  263.             }
  264.             elsif ( ! $prefer_words{ $old_word } ) {
  265.                 warn qq{$old_word vs $word};
  266.             }
  267.         }
  268.     }
  269.     $tag_to_word{ defAC } = q{AC};
  270.     $tag_to_word{ defFort } = q{Fort};
  271.     $tag_to_word{ defRef } = q{Ref};
  272.     $tag_to_word{ defWill } = q{Will};
  273.     $tag_to_word{ attrStr } = q{Strength};
  274.     $tag_to_word{ attrCon } = q{Constitution};
  275.     $tag_to_word{ attrDex } = q{Dexterity};
  276.     $tag_to_word{ attrInt } = q{Intelligence};
  277.     $tag_to_word{ attrWis } = q{Wisdom};
  278.     $tag_to_word{ attrCha } = q{Charisma};
  279. }
  280.  
  281. sub tag_to_word {
  282.     my ( $tag ) = @_;
  283.     return $tag_abbrev{ $tag } // ( $tag_to_word{ $tag } //= $tag );
  284. }
  285.  
  286. sub tag_to_name {
  287.     my ( $tag ) = @_;
  288.     return $tag_name{ $tag } // ( $tag_to_word{ $tag } //= $tag );
  289. }
  290.  
  291. sub remember_element {
  292.     my ( $rec, $key ) = @_;
  293.  
  294.     my $array = $rec->{ $key };
  295.     if ( q{ARRAY} ne ref $array ) {
  296.         if ( ref $array ) {
  297.             $array = [ $array ];
  298.         }
  299.         else {
  300.             $array = [];
  301.         }
  302. $rec->{ $key } = $array;
  303.     }
  304.     push @{ $array }, $active_element;
  305. }
  306.  
  307. $thing_read{ fieldval } = sub {
  308.     my %attrs = @_;
  309.     my $field = $attrs{ field };
  310.     my $value = $attrs{ value };
  311.  
  312.     $thing_element->{ fieldval }->{ $field } = $value;
  313. };
  314.  
  315. $thing_read{ arrayval } = sub {
  316.     my %attrs = @_;
  317.     my $field = $attrs{ field };
  318.     my $value = $attrs{ value };
  319.  
  320.     my $index = $attrs{ index };
  321.     my $column = $attrs{ column };
  322.  
  323.     if ( defined $column ) {
  324.         $thing_element->{ matrixval }->{ $field }->[ $index ]->[ $column ] = $value;
  325.     }
  326.     else {
  327.         $thing_element->{ arrayval }->{ $field }->[ $index ] = $value;
  328.     }
  329. };
  330.  
  331. $thing_read{ usesource } = sub {
  332.     my %attrs = @_;
  333.     my $source = $attrs{ source };
  334.  
  335.     remember_element( $thing_element->{ usesource } //= {}, $source );
  336. };
  337.  
  338. $thing_read{ tag } = sub {
  339.     my %attrs = @_;
  340.     my $group = $attrs{ group };
  341.     my $tag = $attrs{ tag };
  342.     my $name = $attrs{ name };
  343.     my $abbrev = $attrs { abbrev };
  344.  
  345.     if ( defined $name ) {
  346.         $tag_name{ $tag } //= $name;
  347.     }
  348.     if ( defined $abbrev ) {
  349.         $tag_abbrev{ $tag } //= $abbrev;
  350.     }
  351.  
  352.     $thing_element->{ tag }->{ $group }->{ $tag } = 1;
  353. };
  354.  
  355. $thing_read{ bootstrap } = sub {
  356.     my %attrs = @_;
  357.     my $thing = $attrs{ thing };
  358.  
  359.     remember_element( $thing_element->{ bootstrap } //= {}, $thing );
  360. };
  361.  
  362. sub StartThing {
  363.     my ( $name, $pad, @attrs ) = @_;
  364.  
  365.     $thing_element = {
  366.         attr         => { @attrs },
  367.         pad          => $pad,
  368.     };
  369.  
  370.     $active_element = $thing_element;
  371. }
  372.  
  373. sub StartNested {
  374.     my ( $name, $pad, @attrs ) = @_;
  375.  
  376.     push @active_element, $active_element;
  377.     my $parent_element = $active_element;
  378.     my $is_thing_top = ( $parent_element == $thing_element );
  379.  
  380.     $active_element = {
  381.         attr         => { @attrs },
  382.         pad          => $pad,
  383.     };
  384.  
  385.     my $code;
  386.     if ( $is_thing_top ) {
  387.         $code = $thing_read{ $name };
  388.     }
  389.  
  390.     if ( $code ) {
  391.         $code->( @attrs );
  392.     }
  393.     else {
  394.         remember_element( $parent_element->{elements} //= {}, $name );
  395.     }
  396. }
  397.  
  398. sub StartPassthrough {
  399.     my ( $name, $pad, @attrs ) = @_;
  400. }
  401.  
  402. sub StartTag {
  403.     my ( $e, $name, @attrs ) = @_;
  404.  
  405.     Text();
  406.     my $pad = $next_pad;
  407.     undef $next_pad;
  408.  
  409.     push @element_path, $name;
  410.  
  411.     if ( $thing_element ) {
  412.         StartNested( $name, $pad, @attrs );
  413.     }
  414.     elsif (    2 == scalar @element_path
  415.             && $element_path[ 0 ] eq q{document}
  416.             && $element_path[ 1 ] eq q{thing} ) {
  417.         StartThing( $name, $pad, @attrs );
  418.     }
  419.     else {
  420.         StartPassthrough( $name, $pad, @attrs );
  421.     }
  422. }
  423.  
  424. sub EndTag {
  425.     pop @element_path;
  426.  
  427.     Text();
  428.     my $pad = $next_pad;
  429.     undef $next_pad;
  430.  
  431.     if ( $thing_element ) {
  432.         if ( $thing_element == $active_element ) {
  433.             if (    $thing_element->{ tag }->{ EffectType }->{ FullDisc }
  434.                  && $thing_element->{ attr }->{ compset } eq q{Power} ) {
  435.                 ConvertFDPower( $thing_element );
  436.             }
  437.             undef $thing_element;
  438.         }
  439.         $active_element = pop @active_element;
  440.     }
  441. }
  442.  
  443. sub Char {
  444.     my ( $e, $text ) = @_;
  445.  
  446.     $active_text .= $text;
  447. }
  448.  
  449. sub Text {
  450.     if ( $active_text =~ s{ \n ( \h* ) \z }{}xms ) {
  451.         $next_pad = $1;
  452.     }
  453.     else {
  454.         undef $next_pad;
  455.     }
  456.  
  457.     if ( $active_element  && $active_text ne q{} ) {
  458.         $active_element->{ text } .= $active_text;
  459.     }
  460.  
  461.     $active_text = q{};
  462. }
  463.  
  464. sub TagsForGroup {
  465.     my ( $power, @groups ) = @_;
  466.     my @res;
  467.  
  468.     foreach my $group ( @groups ) {
  469.         my $tags = $power->{ tag }->{ $group };
  470.         next unless $tags;
  471.         push @res, grep { $tags->{ $_ } } keys %{ $tags };
  472.     }
  473.     return @res;
  474. }
  475. sub InfoPower {
  476.     my ( $power, %args ) = @_;
  477.  
  478.     my $details = q{{align center}{b}} . $power->{ attr }->{ name } . q{{/b}{br}{align left}};
  479.  
  480.     if ( !$args{ no_flavor } && defined $power->{ fieldval }->{ pwFlavor } ) {
  481.         $details .= q{{i}} . $power->{ fieldval }->{ pwFlavor } . q{{/i}{br}{br}};
  482.     }
  483.  
  484.     if ( !$args{ no_usage } ) {
  485.         my $any;
  486.  
  487.         my @use;
  488.         foreach my $group ( qw( PowerUse PowerType ) ) {
  489.             @use = map { tag_to_word( $_ ) } TagsForGroup( $power, $group );
  490.             last if @use;
  491.         }
  492.  
  493.         my @keywords = map { tag_to_word( $_ ) } TagsForGroup( $power, qw( PowerSrc EffectType PowerAcc DamageType ) );
  494.  
  495.         if ( @use || @keywords ) {
  496.             $details .= q{{b}} . join( q{{/b} {font Wingdings}w{font revert} {b}}, @use, join( q{, }, @keywords ) ) . q{{/b}{br}};
  497.         }
  498.     }
  499.  
  500.     my ( $action ) = TagsForGroup( $power, q{ActionType} );
  501.     my ( $attack ) = TagsForGroup( $power, q{AttackType} );
  502.     if ( $action || $attack ) {
  503.         $details .= q{{b}};
  504.         if ( $action ) {
  505.             $details .= tag_to_name( $action );
  506.             if ( $attack ) {
  507.                 $details .= q{ - };
  508.             }
  509.         }
  510.         if ( $attack ) {
  511.             $details .= tag_to_name( $attack );
  512.         }
  513.         $details .= q{{/b}};
  514.         if ( $power->{ fieldval }->{ pwRange1 } ) {
  515.             $details .= q{ } . $power->{ fieldval }->{ pwRange1 };
  516.             if ( $power->{ fieldval }->{ pwRange2 } ) {
  517.                 $details .= q{ Within } . $power->{ fieldval }->{ pwRange2 } . q{ Square};
  518.                 if ( $power->{ fieldval }->{ pwRange2 } != 1 ) {
  519.                     $details .= q{s};
  520.                 }
  521.             }
  522.         }
  523.         $details .= q{{br}};
  524.     }
  525.  
  526.     my ( $link ) = TagsForGroup( $power, q{ PowerLink } );
  527.     if ( $link ) {
  528.         $details .= q{{b}} . tag_to_name( $link ) . q{{/b}};
  529.         if ( $link =~ m{(\d+)$} ) {
  530.             $details .= qq{ ($1 use} . ( $1 != 1 ? q{s} : q{} ) . q{ total)};
  531.         }
  532.         $details .= q{{br}};
  533.     }
  534.  
  535.     my $special = $power->{ fieldval }->{ pwSpecial };
  536.     if ( $special ) {
  537.         $details .= q{{b}Special:{/b} }. $special . q{{br}};
  538.     }
  539.  
  540.     my $require = $power->{ fieldval }->{ pwRequire };
  541.     if ( $require ) {
  542.         $details .= q{{b}Requirements:{/b} }. $require . q{{br}};
  543.     }
  544.  
  545.     my $target = $power->{ fieldval }->{ pwTarget };
  546.     if ( $target ) {
  547.         $details .= q{{b}Target:{/b} } . $target . q{{br}};
  548.     }
  549.  
  550.     ( $attack ) = TagsForGroup( $power, q{Attack} );
  551.     if ( $attack ) {
  552.         $details .= q{{b}Attack:{/b} };
  553.  
  554.         if ( $power->{ fieldval }->{ spcNotes } ) {
  555.             $details .= q{{b}} . $power->{ fieldval }->{ spcNotes } . q{{/b} (};
  556.         }
  557.  
  558.         $details .= tag_to_name( $attack );
  559.         my $atk_mode = 0 + $power->{ fieldval }->{ pwAtkMod };
  560.         if ( $atk_mode ) {
  561.             $details .= q{ } . $atk_mode;
  562.         }
  563.         my ( $vs ) = TagsForGroup( $power, q{AttackVs} );
  564.         if ( $vs ) {
  565.             $details .= q{ vs. } . tag_to_word( $vs );
  566.         }
  567.         my $extra = $power->{ fieldval }->{ pwAtkExtra };
  568.         if ( defined $extra ) {
  569.             $details .= $extra;
  570.         }
  571.  
  572.         if ( $power->{ fieldval }->{ spcNotes } ) {
  573.             $details .= q{)};
  574.         }
  575.  
  576.         $details .= q{{br}};
  577.     }
  578.  
  579.     $details =~ s{ (?: {br} )* \z}{{br}{br}}xms;
  580.  
  581.     return $details;
  582. }
  583.  
  584. sub SplitDescription {
  585.     my ( $orig_power, $fd_power, $at_power, $mt_power, $base_name ) = @_;
  586.  
  587.     my $mt_detail;
  588.     my @at_desc;
  589.     my @mt_desc;
  590.  
  591.     my $desc = $orig_power->{ attr }->{ description };
  592.     my @desc = split( m[ {br}{br} (?! {br} ) ]xms, $desc );
  593.     while ( @desc ) {
  594.         my $piece = shift @desc;
  595.         if ( $piece =~ s{ \A \Q${base_name}\E\s* \[ \w* \s Technique \] }{}xms ) {
  596.             $mt_detail = $piece;
  597.             @mt_desc = @desc;
  598.             last;
  599.         }
  600.         push @at_desc, $piece;
  601.     }
  602.  
  603.     # Hot fix Tiger Claw Rake
  604.     if ( $base_name eq q{Tiger Claw Rake} ) {
  605.         if ( !defined  $mt_detail ) {
  606.             $mt_detail = q{{b}Encounter{/b}  {b}{b}Full Discipline{/b}, {b}Psionic{/b}{br}{b}Move Action{/b}{b}Personal{/b} };
  607.             @mt_desc = ( q{{i}This power did not parse correctly. Should be something like:{/i}{br}Move 2+ speed, ignoring difficult terrain and mark ( until end of turn ) and do strength mod damage to any enemy that makes an op attack against you during this move.} );
  608.         }
  609.     }
  610.  
  611.     if ( !defined $mt_detail ) {
  612.         warn $base_name, q{ does not have information for movement technique};
  613.         return;
  614.     }
  615.  
  616.     if ( $mt_detail =~ m{ \A {i} (.*) {/i} \z }xms ) {
  617.         $mt_power->{ fieldvar }->{ pwFlavor } = $1;
  618.         $mt_detail = shift @mt_desc;
  619.     }
  620.  
  621.     my ( $type_keywords, $action_range ) = split( m{ {br} }xms, $mt_detail, 2 );
  622.     state $match_word = qr{ (?<= \{ b \} ) ( (?: [^\{]++ | \{ (?! /b \} ) )*+ ) (?= \{ /b \} ) }xms;
  623.  
  624.     $type_keywords =~ s[ {b}(?:{b})+ ][{b}]xms;
  625.     foreach my $keyword ( $type_keywords =~ m{ $match_word }xmsgo ) {
  626.         my $word_type = $word_to_group{ $keyword } // q{Unknown group};
  627.         next if ( $word_type eq q{} );
  628.         my $word_tag = $word_to_tag_by_group{ $word_type }->{ $keyword };
  629.         unless ( $type_keywords{ $word_type } && defined $word_tag ) {
  630.             warn q{Unable to process keyword: }, $keyword, q{ / group: }, $word_type, q{ skipping: }, $base_name, q{\n};
  631.             return;
  632.         }
  633.         $mt_power->{ tag }->{ $word_type }->{ $word_tag } = 1;
  634.     }
  635.  
  636.     $action_range =~ s[ {b}(?:{b})+ ][{b}]xms;
  637.     foreach my $keyword ( $action_range =~ m{ $match_word }xmsgo ) {
  638.         my $word_type = $word_to_group{ $keyword } // q{Unknown group};
  639.         next if ( $word_type eq q{} );
  640.         my $word_tag = $word_to_tag_by_group{ $word_type }->{ $keyword };
  641.         unless ( $action_range{ $word_type } && defined $word_tag ) {
  642.             warn q{Unable to process keyword: }, $keyword, q{ / group: }, $word_type, q{ skipping: }, $base_name, q{\n};
  643.             return;
  644.         }
  645.         $mt_power->{ tag }->{ $word_type }->{ $word_tag } = 1;
  646.     }
  647.     $action_range =~ s{ .* {/b} }{}xms;
  648.     $action_range =~ s{ \A \s* (?: : \s* )? }{}xms;
  649.     $action_range =~ s{ \s* \z }{}xms;
  650.  
  651.     my ( $attack_type ) = keys %{ $mt_power->{ tag }->{ AttackType } };
  652.     $attack_type //= q{Personal};
  653.     delete $mt_power->{ tag }->{ AttackType };
  654.  
  655.     if ( $action_range =~ s{ \s* Within \s+ ( \d+ ) \s+ squares? \z}{}xmsi ) {
  656.         $mt_power->{ fieldval }->{ pwRange2 } = $1;
  657.     }
  658.     if ( $action_range =~ s{ \s* ( \d+ ) \z}{}xmsi ) {
  659.         $mt_power->{ fieldval }->{ pwRange1 } = $1;
  660.     }
  661.  
  662.     given( $action_range ) {
  663.         when ( qr{\A Weapon \z}xmsi ) {
  664.             if ( $attack_type =~ m{ \A (?: Melee | Range ) \z } ) {
  665.                 $attack_type .= "Wep";
  666.             }
  667.             else {
  668.                 undef $attack_type;
  669.             }
  670.         }
  671.         when ( qr{\A Touch \z}xmsi ) {
  672.             if ( $attack_type eq q{Melee} ) {
  673.                 $attack_type = "MeleeTouch";
  674.             }
  675.             else {
  676.                 undef $attack_type;
  677.             }
  678.         }
  679.         when ( qr{\A Sight \z}xmsi ) {
  680.             if ( $attack_type eq q{Range} ) {
  681.                 $attack_type = "RangeSight";
  682.             }
  683.             else {
  684.                 undef $attack_type;
  685.             }
  686.         }
  687.         when ( qr{\A Burst \z}xmsi ) {
  688.             if ( $attack_type =~ m{ \A (?: Close | Area ) \z } ) {
  689.                 $attack_type .= q{Burst};
  690.             }
  691.             else {
  692.                 undef $attack_type;
  693.             }
  694.         }
  695.         when ( qr{\A Wall \z}xmsi ) {
  696.             if ( $attack_type eq q{Area} ) {
  697.                 $attack_type .= q{Burst};
  698.             }
  699.             else {
  700.                 undef $attack_type;
  701.             }
  702.         }
  703.         when ( qr{\A Blast \z}xmsi ) {
  704.             if ( $attack_type eq q{Close} ) {
  705.                 $attack_type .= q{Burst};
  706.             }
  707.             else {
  708.                 undef $attack_type;
  709.             }
  710.         }
  711.         default {
  712.             if ( $attack_type eq q{Close} ) {
  713.                 undef $attack_type;
  714.             }
  715.         }
  716.     }
  717.  
  718.     if ( defined $attack_type ) {
  719.         $mt_power->{ tag }->{ AttackType }->{ $attack_type } = 1;
  720.     }
  721.     else {
  722.         delete $mt_power->{ fieldval }->{ pwRange1 };
  723.         delete $mt_power->{ fieldval }->{ pwRange2 };
  724.     }
  725.  
  726.     unless ( TagsForGroup( $mt_power, q{PowerType} ) ) {
  727.         my ( $type ) = TagsForGroup( $orig_power, qw{PowerUse PowerType} );
  728.         $mt_power->{ tag }->{ PowerType }->{ $type } = 1;
  729.     }
  730.  
  731.     my $at_desc = $at_power->{ attr }->{ description } = join( q{{br}{br}}, @at_desc );
  732.     my $mt_desc = $mt_power->{ attr }->{ description } = join( q{{br}{br}}, @mt_desc );
  733.     $fd_power->{ attr }->{ description } = join( q{{br}{br}},
  734.         q{This is a full discipline power which consists of both a attack technique and movement technique.},
  735.         InfoPower( $orig_power, no_flavor => 1 ) . $at_desc,
  736.         InfoPower( $mt_power, no_flavor => 1 ) . $mt_desc,
  737.     );
  738.  
  739.     return 1;
  740. }
  741.  
  742. sub ConvertFDPower {
  743.     my ( $orig_power ) = @_;
  744.  
  745.     if ( $orig_power->{ elements }->{ eval } ) {
  746.         warn q{Can not process: }, $orig_power->{ attr }->{ name }, qq{\n};
  747.         return;
  748.     }
  749.  
  750.     my $fd_power = {};
  751.     my $at_power = {};
  752.     my $mt_power = {};
  753.  
  754.     # Attributes
  755.     $fd_power->{ attr } = clone( $orig_power->{ attr } // {} );
  756.     $at_power->{ attr } //= {};
  757.     $mt_power->{ attr } //= {};
  758.  
  759.     # Attribute( id )
  760.     my $orig_id = $orig_power->{ attr }->{ id };
  761.     my $post_id = $orig_id;
  762.     $post_id =~ s{\A (..) .. }{}xms;
  763.     my $pre_id = $1;
  764.     my $fd_id = $pre_id . q{FD} . $post_id;
  765.     my $at_id = $pre_id . q{AT} . $post_id;
  766.     my $mt_id = $pre_id . q{MT} . $post_id;
  767.     $fd_power->{ attr }->{ id } = $fd_id;
  768.     $fd_power->{ attr }->{ replaces } = $orig_id;
  769.     $at_power->{ attr }->{ id } = $at_id;
  770.     $mt_power->{ attr }->{ id } = $mt_id;
  771.  
  772.     # Fields
  773.     $at_power->{ fieldval } = clone( $orig_power->{ fieldval } // {} );
  774.     $at_power->{ arrayval } = clone( $orig_power->{ arrayval } // {} );
  775.     $at_power->{ matrixval } = clone( $orig_power->{ matrixval } // {} );
  776.     if ( defined $orig_power->{ fieldval }->{ pwFlavor } ) {
  777.         $mt_power->{ fieldval }->{ pwFlavor } //= $orig_power->{ fieldval }->{ pwFlavor };
  778.     }
  779.  
  780.     # Attribute( name )
  781.     my $orig_name = $orig_power->{ attr }->{ name };
  782.     ( my $base_name = $orig_name ) =~ s{ \s* \[ \w* \s Technique \] \s* \z}{}xms;
  783.     $fd_power->{ attr }->{ name } = $base_name . q{ [Full Discipline]};
  784.     $at_power->{ attr }->{ name } = $orig_name;
  785.     $mt_power->{ attr }->{ name } = $base_name . q{ [Movement Technique]};
  786.  
  787.     # Tags
  788.     my $groups = $orig_power->{ tag } // {};
  789.     foreach my $group ( keys %{ $groups } ) {
  790.         my $fd_status = $full_discipline_tags{ $group };
  791.         my $at_status = $attack_technique_supress_tags{ $group };
  792.         my $tags = $groups->{ $group };
  793.         foreach my $tag ( keys %{ $tags } ) {
  794.             if ( $fd_status ) {
  795.                 if ( !ref $fd_status || $fd_status->{ $tag } ) {
  796.                     $fd_power->{ tag }->{ $group }->{ $tag } = 1;
  797.                 }
  798.             }
  799.             if ( $at_status ) {
  800.                 if ( ref $at_status && !$at_status->{ $tag } ) {
  801.                     $at_power->{ tag }->{ $group }->{ $tag } = 1;
  802.                 }
  803.             }
  804.             else {
  805.                 $at_power->{ tag }->{ $group }->{ $tag } = 1;
  806.             }
  807.         }
  808.     }
  809.     $fd_power->{ tag }->{ Hide }->{ Special } = 1;
  810.  
  811.     # Tags ( PowerLink )
  812.     # Change the if ( 1 ) to an if ( 0 ) if you do not want the link between the powers
  813.     if ( 1 ) {
  814.         my $link_id = q{fd} . $post_id . q{1};
  815.         $tag_name{ $link_id } = $fd_power->{ attr }->{ name };
  816.         $tag_abbrev{ $link_id } = $base_name;
  817.         $at_power->{ tag }->{ PowerLink }->{ $link_id } = 1;
  818.         $mt_power->{ tag }->{ PowerLink }->{ $link_id } = 1;
  819.     }
  820.  
  821.     # Attribute ( compset )
  822.     $fd_power->{ attr }->{ compset } = q{Power};
  823.     $at_power->{ attr }->{ compset } = q{Power};
  824.     $mt_power->{ attr }->{ compset } = q{Power};
  825.  
  826.     # Attribute( description )
  827.     SplitDescription( $orig_power, $fd_power, $at_power, $mt_power, $base_name )
  828.         or return;
  829.  
  830.     # Attribute( isunique )
  831.     my $val = $orig_power->{ attr }->{ isunique };
  832.     if ( defined $val ) {
  833.         $fd_power->{ attr }->{ isunique } = $val;
  834.         $at_power->{ attr }->{ isunique } = $val;
  835.         $mt_power->{ attr }->{ isunique } = $val;
  836.     }
  837.  
  838.     # Source
  839.     $fd_power->{ usesource } = $orig_power->{ usesource };
  840.     $at_power->{ usesource } = $orig_power->{ usesource };
  841.     $mt_power->{ usesource } = $orig_power->{ usesource };
  842.  
  843.     # Bootstraps
  844.     $fd_power->{ bootstrap } = clone( $orig_power->{ bootstrap } // {} );
  845.     $fd_power->{ bootstrap }->{ $at_id } //= 1;
  846.     $fd_power->{ bootstrap }->{ $mt_id } //= 1;
  847.  
  848.     # Elements
  849.     $fd_power->{ elements } = clone( $orig_power->{ elements } // {} );
  850.  
  851.     #WriteThing( $orig_power );
  852.     WriteThing( $at_power );
  853.     WriteThing( $mt_power );
  854.     WriteThing( $fd_power );
  855. }
  856.  
  857. sub WriteThing {
  858.     my ( $thing ) = @_;
  859.  
  860.     my $pad = qq{\n  };
  861.     my $attrs = $thing->{ attr };
  862.     my @attrs = ();
  863.     my $attr_order = $attribute_order{ thing };
  864.     my $attr_rank = $attribute_rank{ thing };
  865.     foreach my $attr_name ( @{ $attr_order } ) {
  866.         if ( exists $attrs->{ $attr_name } ) {
  867.             push @attrs, $attr_name, $attrs->{ $attr_name };
  868.         }
  869.     }
  870.     foreach my $attr_name ( sort keys %{ $attrs } ) {
  871.         if ( ! $attr_rank->{ $attr_name } ) {
  872.             push @attrs, $attr_name, $attrs->{ $attr_name };
  873.         }
  874.     }
  875.  
  876.     $xml_writer->characters( $pad );
  877.     $xml_writer->startTag( q{thing}, @attrs );
  878.  
  879.     my $elements = $thing->{ elements };
  880.     my $elem_order = $element_order{ thing } //= [];
  881.     my $elem_rank = $element_rank{ thing } //= {};
  882.     foreach my $elem_name ( @{ $elem_order } ) {
  883.         my $code = $thing_write{ $elem_name };
  884.         if ( $code ) {
  885.             $code->( $thing );
  886.             next;
  887.         }
  888.         next unless $elements->{ $elem_name };
  889.         WriteElements( 0, $elem_name, @{ $elements->{ $elem_name } } );
  890.     }
  891.     foreach my $elem_name ( sort keys %{ $elements } ) {
  892.         next if $elem_rank->{ $elem_name };
  893.         my $code = $thing_write{ $elem_name };
  894.         if ( $code ) {
  895.             $code->( $thing );
  896.             next;
  897.         }
  898.  
  899.         WriteElements( 0, $elem_name, @{ $elements->{ $elem_name } } );
  900.     }
  901.  
  902.  
  903.     $xml_writer->characters( $pad );
  904.     $xml_writer->endTag( q{thing} );
  905. }
  906.  
  907. $thing_write{ fieldval } = sub {
  908.     my ( $thing ) = @_;
  909.  
  910.     my $fields = $thing->{ fieldval };
  911.     return unless $fields;
  912.  
  913.     my $pad = qq{\n    };
  914.  
  915.     foreach my $field ( sort keys %{ $fields } ) {
  916.         my $value = $fields->{ $field };
  917.         next unless defined $value;
  918.  
  919.         $xml_writer->characters( $pad );
  920.         $xml_writer->emptyTag( q{fieldval}, field => $field, value => $value );
  921.     }
  922. };
  923.  
  924. $thing_write{ arrayval } = sub {
  925.     my ( $thing ) = @_;
  926.  
  927.     my $pad = qq{\n    };
  928.  
  929.     my $arrays = $thing->{ arrayval };
  930.     my $matrices = $thing->{ maxtrixval };
  931.  
  932.     if ( $arrays ) {
  933.         foreach my $field ( sort keys %{ $arrays } ) {
  934.             my $array = $arrays->{ $field };
  935.             my $index_size = scalar @{ $array };
  936.             for ( my $index = 0; $index < $index_size; ++$index ) {
  937.                 my $value = $array->[ $index ];
  938.                 next unless defined $value;
  939.  
  940.                 $xml_writer->characters( $pad );
  941.                 $xml_writer->emptyTag( q{arrayval}, field => $field, index => $index, value => $value );
  942.             }
  943.         }
  944.     }
  945.  
  946.     if ( $matrices ) {
  947.         foreach my $field ( sort keys %{ $matrices } ) {
  948.             my $matrix = $matrices->{ $field };
  949.             my $index_size = scalar @{ $matrix };
  950.             for ( my $index = 0; $index < $index_size; ++$index ) {
  951.                 my $row = $matrix->[ $index ];
  952.                 next unless $row;
  953.  
  954.                 my $column_size = scalar @{ $row };
  955.                 for ( my $column = 0; $column < $column_size; ++$column ) {
  956.                     my $value = $row->[ $column ];
  957.                     next unless defined $value;
  958.  
  959.                     $xml_writer->characters( $pad );
  960.                     $xml_writer->emptyTag( q{arrayval}, field => $field, index => $index, column => $column, value => $value );
  961.                 }
  962.             }
  963.         }
  964.     }
  965. };
  966.  
  967. $thing_write{ usesource } = sub {
  968.     my ( $thing ) = @_;
  969.  
  970.     my $sources = $thing->{ usesource };
  971.     return unless $sources;
  972.  
  973.     my $pad = qq{\n    };
  974.  
  975.     foreach my $source ( sort keys %{ $sources } ) {
  976.         $xml_writer->characters( $pad );
  977.         $xml_writer->emptyTag( q{usesource}, source => $source );
  978.     }
  979. };
  980.  
  981. $thing_write{ tag } = sub {
  982.     my ( $thing ) = @_;
  983.  
  984.     my $groups = $thing->{ tag };
  985.     return unless $groups;
  986.  
  987.     my $pad = qq{\n    };
  988.  
  989.     foreach my $group ( sort keys %{ $groups } ) {
  990.         my $tags = $groups->{ $group };
  991.         next unless $tags;
  992.  
  993.         foreach my $tag ( sort keys %{ $tags } ) {
  994.             next unless $tags->{ $tag };
  995.  
  996.             my @extra;
  997.             state %tag_written;
  998.             if ( ! $tag_written{ $tag } ) {
  999.                 $tag_written{ $tag } = 1;
  1000.                 if ( exists $tag_name{ $tag } ) {
  1001.                     push @extra, name => $tag_name{ $tag };
  1002.                 }
  1003.                 if ( exists $tag_abbrev{ $tag } ) {
  1004.                     push @extra, abbrev => $tag_abbrev{ $tag };
  1005.                 }
  1006.             }
  1007.  
  1008.             $xml_writer->characters( $pad );
  1009.             $xml_writer->emptyTag( q{tag}, group => $group, tag => $tag, @extra );
  1010.         }
  1011.     }
  1012. };
  1013.  
  1014. $thing_write{ bootstrap } = sub {
  1015.     my ( $thing ) = @_;
  1016.  
  1017.     my $bootstraps = $thing->{ bootstrap };
  1018.     return unless $bootstraps;
  1019.  
  1020.     my $pad = qq{\n    };
  1021.  
  1022.     foreach my $bootstrap ( keys %{ $bootstraps } ) {
  1023.         my $value = $bootstraps->{ $bootstrap };
  1024.         next unless $value;
  1025.  
  1026.         if ( ref $value ) {
  1027.             if ( @{ $value } ) {
  1028.                 WriteElements( 0, q{bootstrap}, @{ $value } );
  1029.                 next;
  1030.             }
  1031.         }
  1032.  
  1033.         $xml_writer->characters( $pad );
  1034.         $xml_writer->emptyTag( q{bootstrap}, thing => $bootstrap );
  1035.     }
  1036. };
  1037.  
  1038. sub WriteElements {
  1039.     my ( $depth, $name, @records ) = @_;
  1040.  
  1041.     my $pad = qq{\n} . ( ' ' x ( 4 + 2 * $depth ) );
  1042.  
  1043.     foreach my $record ( @records ) {
  1044.         my $attrs = $record->{ attr };
  1045.         my @attrs = ();
  1046.         my $attr_order = $attribute_order{ $name } //= [];
  1047.         my $attr_rank = $attribute_rank{ $name } //= {};
  1048.         foreach my $attr_name ( @{ $attr_order } ) {
  1049.             next unless exists $attrs->{ $attr_name };
  1050.             push @attrs, $attr_name, $attrs->{ $attr_name };
  1051.         }
  1052.         foreach my $attr_name ( sort keys %{ $attrs } ) {
  1053.             next if $attr_rank->{ $attr_name };
  1054.             push @attrs, $attr_name, $attrs->{ $attr_name };
  1055.         }
  1056.  
  1057.  
  1058.         $xml_writer->characters( $pad );
  1059.         if ( exists $record->{ elements } || exists $record->{ text } || $has_text{ $name } ) {
  1060.             $xml_writer->startTag( $name, @attrs );
  1061.             if ( exists $record->{ text } ) {
  1062.                 $xml_writer->characters( $record->{ text } );
  1063.             }
  1064.             my $elements = $record->{ elements };
  1065.             my $elem_order = $element_order{ $name } //= [];
  1066.             my $elem_rank = $element_rank{ $name } //= {};
  1067.             foreach my $elem_name ( @{ $elem_order } ) {
  1068.                 next unless $elements->{ $elem_name };
  1069.                 WriteElements( $depth+1, $elem_name, @{ $elements->{ $elem_name } } );
  1070.             }
  1071.             foreach my $elem_name ( sort keys %{ $elements } ) {
  1072.                 next if $elem_rank->{ $elem_name };
  1073.                 WriteElements( $depth+1, $elem_name, @{ $elements->{ $elem_name } } );
  1074.             }
  1075.  
  1076.             $xml_writer->characters( $pad );
  1077.             $xml_writer->endTag( $name );
  1078.         }
  1079.         else {
  1080.             $xml_writer->emptyTag( $name, @attrs );
  1081.         }
  1082.     }
  1083. }
  1084.  
  1085. Readonly my $powers_filename => shift // q{ddi_powers.dat};
  1086. Readonly my $fd_filename => shift // q{fd_powers.user};
  1087.  
  1088. Readonly my $powers_in_path => ( -e $powers_filename ? $powers_filename : File::Spec->catfile( $default_directory, $powers_filename ) );
  1089. Readonly my $powers_out_path => $fd_filename eq q{-}? $fd_filename : ( -e $powers_filename ? $fd_filename : File::Spec->catfile( $default_directory, $fd_filename ) );
  1090.  
  1091. unless ( -e $powers_in_path ) {
  1092.     die "Unable to find: $powers_filename\n";
  1093. }
  1094.  
  1095. my $parser = XML::Parser->new( Handlers => {
  1096.     Start => \&StartTag,
  1097.     End   => \&EndTag,
  1098.     Char  => \&Char
  1099. } );
  1100.  
  1101. my $output = IO::File->new( q{>} . $powers_out_path )
  1102.     or die "Unable to create output file: $powers_out_path: $!\n";
  1103. $xml_writer = XML::Writer->new( ENCODING => q{utf-8}, OUTPUT => $output );
  1104. $xml_writer->xmlDecl();
  1105. $xml_writer->startTag( q{document}, signature => q{Hero Lab Data} );
  1106. $parser->parsefile( $powers_in_path );
  1107. $xml_writer->characters( qq{\n} );
  1108. $xml_writer->endTag( q{document} );
  1109. $xml_writer->end();
  1110. $output
  1111.     or die "Error writing: $powers_out_path: $!\n";
  1112.  
  1113. ####
  1114. 1;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement