~bosco/asciidoctor-p2e

ba5a0614d9c1d2bab8284ed5ed6429d3eab3094a — Ryan Tolboom 3 years ago 890a747 master
Added hazards
M README.adoc => README.adoc +231 -24
@@ 10,16 10,17 @@ https://asciidoctor.org/[Asciidoctor]! asciidoctor-p2e is an
https://docs.asciidoctor.org/asciidoctor/latest/extensions/[extension] for
AsciiDoctor that makes it easier to create stylized content for the
https://paizo.com/pathfinder[Pathfinder Second Edition roleplaying system].
Your years of searching are at an end, for this extension supports NPC/Creature
stat blocks, a stylized Table of Contents, decorated Sidebars, and
Pathfinder-style headers. All of which will be demonstrated in cloying detail!
Your years of searching are at an end, for this extension supports creature
stat blocks, item stat blocks, hazard stat blocks, a stylized Table of
Contents, decorated Sidebars, and Pathfinder-style headers. All of which will
be demonstrated in cloying detail!

You can find the git repo for this project in the mystical realm of SourceHut:
https://git.sr.ht/~bosco/asciidoctor-p2e, a fantastic forge of free software
also known to the old ones as
https://drewdevault.com/2018/11/15/sr.ht-general-availability.html[Sir Hat].

== Wielding (Usage)
== Usage

This extension is archived in the scrolls of
https://rubygems.org/gems/asciidoctor-p2e[RubyGems] and can be added to your


@@ 30,14 31,27 @@ inventory with the `gem install asciidoctor-p2e` command. Once the
NOTE: Images and assets will be installed/referenced in a `p2e` directory
wherever your output is created.

== Skills of Thine Enemies (Stat Blocks)
== Style

A new block of type `stat` can be used to create a stat block from
https://toml.io/en/[TOML input]. For example the following TOML:
Zero level headers will use a yellow outline Eczar font and first level headers
will use the Eczar font. This styling is from the
https://www.gmbinder.com/share/-LxQNtitjvBT2eCVkPt1[GM Binder Pathfinder 2nd
Edition Homebrew Template]. Fonts are included with `asciidoctor-p2e` and will
be installed in the `p2e` directory when your output is created. The body of
the document uses a parchment background.

As you can see on the left, if the Table of Contents is used (`:toc: left`) a
burnt parchment edge will be show with Pathfinder style header fonts for each
section.

== Enemies

A block of type `stat` can be used to create a creature stat block from
https://toml.io/en/[TOML input]. For example:

[source, asciidoc]
----
[stat]
[stat.center, type=creature]
--
name = "Hoo Man"
level = "1"


@@ 83,9 97,12 @@ text = """\
--
----

Will create the following stat block:
Notice that a role can be passed: in this case 'center'. If the block is floated
(left right) a 20px margin will be set on the opposite side.  If a type is not
specified, 'creature' is assumed.  The above listing creates the following stat
block:

[stat]
[stat.center, type=creature]
--
name = "Hoo Man"
level = "1"


@@ 130,21 147,209 @@ text = """\
  """
--

== Mystical Script (Fonts)
== Treasure

Zero level headers will use a yellow outline Eczar font and first level headers
will use the Eczar font. This styling is from the
https://www.gmbinder.com/share/-LxQNtitjvBT2eCVkPt1[GM Binder Pathfinder 2nd
Edition Homebrew Template]. Fonts are included with `asciidoctor-p2e` and will
be installed in the `p2e` directory when your output is created.
In a similar fashion to creature stat blocks, item stat blocks can be created:

== Spellbook Passages (Table of Contents)
[source, asciidoctor]
----
[stat.center, type=item]
--
name = "Pear of Silence"
level = "6+"
tags = ["uncommon"]
price = "100 gp" # optional
usage = "held in 1 hand"
bulk = "L"
description = """\
  A cruel device famed to be designed by Roderick the Malevolent. A seemingly \
  delicious fruit, when placed in someone's mouth it expands to render them \
  unable to speak. A magical phrase that disables the effect is bound to the \
  fruit at the time of creation (not that the victim would be able to utter \
  it).\
  """

As you can see on the left, if the Table of Contents is used (`:toc: left`) a
burnt parchment edge will be show with Pathfinder style header fonts for each
section.
[hardness] # optional
hardness = "9"
hp = "10"
bp = "5"

[[activations]] # optional
actions = 1
actionText = "Interact" # optional
trigger = "The pear is placed inside someone's mouth" # optional
# effect is optional too
effect = """\
  The pear rapidly expands, filling the victims mouth and rendering them \
  unable to speak until the pear is destroyed or disabled.\
  """

[[activations]]
actions = 2
actionText = "Interact"
trigger = """\
  The magic phrase that disables the pear is spoken loudly at a distance of \
  less than five feet from the pear.\
  """
effect = """\
  The pear resumes its normal shape and form. It can be spit out by the \
  victim.\
  """

[[types]] #optional
type = "lesser"
level = "6"
price = "100 gp" # optional
description = """\
  Can be destroyed by the victim with a DC 20 Fortitude saving throw.\
  """

[[types]]
type = "greater"
level = "10"
price = "200 gp"
description = """\
  Can be destroyed by the victim with a DC 25 Fortitude saving throw.\
  """

[[types]]
type = "major"
level = "12"
price = "300 gp"
description = """\
  Can be destroyed by the victim with a DC 30 Fortitude saving throw.\
  """
--
----

The above block yields:

[stat.center, type=item]
--
name = "Pear of Silence"
level = "6+"
tags = ["uncommon"]
price = "100 gp" # optional
usage = "held in 1 hand"
bulk = "L"
description = """\
  A cruel device famed to be designed by Roderick the Malevolent. A seemingly \
  delicious fruit, when placed in someone's mouth it expands to render them \
  unable to speak. A magical phrase that disables the effect is bound to the \
  fruit at the time of creation (not that the victim would be able to utter \
  it).\
  """

[hardness] # optional
hardness = "9"
hp = "10"
bp = "5"

[[activations]] # optional
actions = 1
actionText = "Interact" # optional
trigger = "The pear is placed inside someone's mouth" # optional
# effect is optional too
effect = """\
  The pear rapidly expands, filling the victims mouth and rendering them \
  unable to speak until the pear is destroyed or disabled.\
  """

[[activations]]
actions = 2
actionText = "Interact"
trigger = """\
  The magic phrase that disables the pear is spoken loudly at a distance of \
  less than five feet from the pear.\
  """
effect = """\
  The pear resumes its normal shape and form. It can be spit out by the \
  victim.\
  """

[[types]] #optional
type = "lesser"
level = "6"
price = "100 gp" # optional
description = """\
  Can be destroyed by the victim with a DC 20 Fortitude saving throw.\
  """

[[types]]
type = "greater"
level = "10"
price = "200 gp"
description = """\
  Can be destroyed by the victim with a DC 25 Fortitude saving throw.\
  """

[[types]]
type = "major"
level = "12"
price = "300 gp"
description = """\
  Can be destroyed by the victim with a DC 30 Fortitude saving throw.\
  """
--

== Traps

== Cryptic Texts (Sidebars)
Hazard stat blocks can also be created:

[source, asciidoctor]
----
[stat.center, type=hazard]
====
name = "Bed Spike"
level = "1"
tags = ["mechanical", "trap"]
stealth = "DC 15 (or 0 if they look under the bed)"
description = """\
  The support ropes for a bed are cut almost completely through. Under the bed \
  are several sharpened wooded spikes. Typically the sheets are hung over the \
  edge of the bed to disguise the spikes.\
  """
disable = """\
  Crafting DC 10 to remove the spikes. The bed will still not support weight \
  but there will be no damage caused by falling through.\
  """

[[triggers]]
name = "Fall Through"
text = """\
  A creature lays on the bed, causing the ropes to snap and dealing 1D6 piercing
  damage.\
  """
====
----

The above block yields:

[stat.center, type=hazard]
====
name = "Bed Spike"
level = "1"
tags = ["mechanical", "trap"]
stealth = "DC 15 (or 0 if they look under the bed)"
description = """\
  The support ropes for a bed are cut almost completely through. Under the bed \
  are several sharpened wooded spikes. Typically the sheets are hung over the \
  edge of the bed to disguise the spikes.\
  """
disable = """\
  Crafting DC 10 to remove the spikes. The bed will still not support weight \
  but there will be no damage caused by falling through.\
  """

[[triggers]]
name = "Fall Through"
text = """\
  A creature lays on the bed, causing the ropes to snap and dealing 1D6 piercing
  damage.\
  """
====

== Cryptic Texts

When a sidebar is used to display information the background will use a styling
from https://pf2.easytool.es/[Pathfinder 2 easy Library].


@@ 154,15 359,17 @@ regarding the functioning of dangerous items:

.Wizard's Note
****
Warning! Reading this note will curse you for all time with a constant stone in
your shoe. No take backsies!
To Whom it May Concern,

Reading this note will curse you for all time with a constant stone in your
shoe. No take backs!

Sincerely,

Roderick the Malevolent
****

== Adventurer's Log (Credits)
== Adventurer's Log

* The layout, much of the styling, and the demonstration creature come from the
  https://slanguage.github.io/pf2monster/[Pathfinder 2E Monster Creator]

M asciidoctor-p2e.gemspec => asciidoctor-p2e.gemspec +1 -1
@@ 1,6 1,6 @@
Gem::Specification.new do |s|
  s.name = 'asciidoctor-p2e'
  s.version = '0.2.2'
  s.version = '0.3.0'
  s.authors = ['Ryan Tolboom']
  s.email = ['ryan@using.tech']
  s.description = 'Asciidoctor Pathfinder 2E Extension'

A lib/asciidoctor-p2e/assets/divider.png => lib/asciidoctor-p2e/assets/divider.png +0 -0
M lib/asciidoctor-p2e/assets/p2e.css => lib/asciidoctor-p2e/assets/p2e.css +62 -7
@@ 24,16 24,23 @@
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Statblocks */
/* Stat blocks */
.statblock {
  width: 500px;
  border: 1px solid black;
  margin: 20px;
/*  margin: 20px;*/
  padding: 20px;
  font-family: "Roboto", Geneva, sans-serif;
  font-size: 16px;
  line-height: 24px;
  display: inline-block;
  display: block;
  background: #f7f7f8;
}
.statblock.left {
  margin-right: 20px;
}
.statblock.right {
  margin-left: 20px;
}
.statblock-title {
  font-size: 23px;


@@ 44,7 51,7 @@
.statblock-name {
  float: left;
}
.statblock-creature {
.statblock-type {
  float: right;
  text-align: right;
}


@@ 154,7 161,28 @@
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAABHNCSVQICAgIfAhkiAAAA8BJREFUSImdlUlsHEUUht/rWrp6lp7unnHPjJ2MgxNWgyKWA6AEC5BsQUBRpCAOKBcIAoGPIG7ABS4sEgpwgguXnBIPxgbFCOUQcQBFiVgiDnC3PYsjHHdVLzNdXGxr0p6QOP/x1d/1Vb3X7xXADYSI1HPcDznjB27kGbTfgmdXIhXPOzXRGE8bY2N/M8YmbmRkjO2tV2sLlNK9twMyhsXKrvuRXSi+iYjIKDtQH/EXGWONrJFSWqv71XM5IY7U/eoPhJD6MAgiskK+8NIwHskGPMf9wLHttxBxO62EkIolrGml1Hyapuub8OqoX/2RMTa56fFzljUdSHlGax0MwkfK5a/cUuk9NIycCsOf/jcD/X7/H611ko1zxiZrI/4ipbQKAFAsFF5kjN1/vYc/UPer3xNCKlv8ilf+spgvnEBEwynab7uO8z4M/DPbGSgV7dcBQQVSLmnQbWGaM4h4XYYIIVVLiCelUk2p5HnKqGNy/uighxIyKkwxFUh5Vmsd5S3rOc75Q7gpYZqHAbAfRuGF7QOUbHu27LmfFXL5YzJUC1LKJQC4KoSYRkRjJ8A8LJVqbgTBt4wz32T8kcFbUUr3WEIcCqQ8I5VqUkoanPGDA4eY0hrCMIp+JnaxeLLieacQkBqIdj6Xe14p9V2g5DkEDCwhZrLloJTuEab5uFTqbBAETcbYHZzxgxlPQ5jmY0EQnJZKzXPO7uGMTwIAIKJhCfFUmqZdA3b2MAcAsVXDLBwAQGutt5YMwygwxu4cZot7yZVU65AQUuaU3Z3Z41oUJ5dJFMeXUq2vWpaYTvtpZ7m1OhMnyZ+OXZp1HefjbAm01jqK419W2q2jWut4tFpbMDk/lIWvb1z7uttdm6WEVmq+v2hy/uDWYpqm6yvt1rEwCi9QAIB/19c/B4B+GEa/xnH8e8m2X3NLpU8NRJqFx0lyabXTPqq1VnW/2jQ5fyLr2ZDBN521tTcAQdf8kTmT84e34TrdWOm0j6swPA8w0AVRFF3s9/vLpaL9suc4XxiGwbM5TZLkj5V265ler9dyHeedYj5/MnvzQMrT7W73lc1W1ohG3xLiCCISrbVabbePK6WWtj7YMYjyudwLlhBTg4MIACBOkisr7dazvV5vGQAgjKKLpmnexyi7dwA+1+p2TgzOkSiOftMaVi0hnm51O68GUs5lmVkZZdf7ZKIxnu4f36cnGuNpY3Tsr2GjGBH5aK02v398X1ob8ZuIKIZtCADAOb/rZuBB3fJjZBiG5bnuu4iY2w3gptrlc3zb+g/DZ3c9xGiesQAAAABJRU5ErkJggg==)
}

/* Headings and TOC */
/* Headings, TOC, dividers, and background */
body {
  background: url("parchment.jpg");
  background-repeat: repeat-y;
  background-size: contain;
}
body.toc2 #header > h1:nth-last-child(2) {
  border-bottom: none;
  text-align: center;
}
body.toc2 #header > h1:nth-last-child(2)::after, .sect1 + .sect1::before {
  content: "";
  display: block;
  height: 40px;
  background-image: url("divider.png");
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: cover;
}
.sect1 + .sect1 {
  border-top: none;
}
h1, h2, #toctitle, #toc a {
  font-family: 'Eczar', sans-serif !important;
  color:#4E0707 !important;


@@ 165,10 193,10 @@ h1, #toctitle {
  text-shadow: 1px 1px 0 #EFCD98, -1px 1px 0 #EFCD98, 1px -1px 0 #EFCD98, -1px -1px 0 #EFCD98, 0px 1px 0 #EFCD98, 0px -1px 0 #EFCD98, -1px 0px 0 #EFCD98, 1px 0px 0 #EFCD98, 2px 2px 0 #EFCD98, -2px 2px 0 #EFCD98, 2px -2px 0 #EFCD98, -2px -2px 0 #EFCD98, 0px 2px 0 #EFCD98, 0px -2px 0 #EFCD98, -2px 0px 0 #EFCD98, 2px 0px 0 #EFCD98, 1px 2px 0 #EFCD98, -1px 2px 0 #EFCD98, 1px -2px 0 #EFCD98, -1px -2px 0 #EFCD98, 2px 1px 0 #EFCD98, -2px 1px 0 #EFCD98, 2px -1px 0 #EFCD98, -2px -1px 0 #EFCD98;
}
h1 {
  font-size: 7em;
  font-size: 6em;
}
h2 {
  font-size: 5em;
  font-size: 3.5em;
}
h3 {
  color:#02256E;


@@ 194,3 222,30 @@ h3 {
  background-repeat: no-repeat;
  background-size: cover;
}

/* Tables */
table {
  background-color: #F5EFE0;
  margin-bottom:0px;
}
table tr th {
  background-color: #5E0000;
  color: white !important;
  padding-top:4px;
}
table tbody tr:nth-child(odd) {
  background-color: #EDE3C8
}
table tbody, table th {
  font-family: 'Roboto Condensed', sans-serif;
}
.tablefooter {
  background-color:#E6D8B0 !important;
}
table.left {
  margin-right: 35px;
}
table.right {
  margin-left: 35px;
}


A lib/asciidoctor-p2e/assets/parchment.jpg => lib/asciidoctor-p2e/assets/parchment.jpg +0 -0
R lib/asciidoctor-p2e/statblock.html.erb => lib/asciidoctor-p2e/creatureblock.html.erb +2 -2
@@ 1,7 1,7 @@
<div class="statblock">
<div class="statblock <%= defined?(role) ? role : '' %>">
  <div class="statblock-title">
    <span class="statblock-name"><%= name %></span>
    <span class="statblock-creature"> CREATURE <%= level %></span>
    <span class="statblock-type"> CREATURE <%= level %></span>
  </div>
  <div class="statblock-info-block">
    <div class="statblock-tags">

M lib/asciidoctor-p2e/extension.rb => lib/asciidoctor-p2e/extension.rb +19 -6
@@ 23,20 23,33 @@ class P2EStatblockProcessor < Extensions::BlockProcessor
  use_dsl

  named :stat
  on_context :open
  on_context :open, :example
  parse_content_as :raw

  def process parent, reader, attrs
    # parse TOML
    lines = reader.lines
    creature = Tomlrb.parse(lines * "\n")
    data = Tomlrb.parse(lines * "\n")

    # get/set attributes
    if attrs.key?('type')
      type = attrs['type']
    else
      type = 'creature'
    end
    if attrs.key?('role')
      data['role'] = attrs['role']
    end

    # create HTML from template
    extdir = ::File.join(::File.dirname __FILE__)
    template_name = 'statblock.html.erb'
    template = File.read("#{extdir}/#{template_name}")
    html = ERB.new(template).result_with_hash(creature)
    templates = {
      'creature' => "#{extdir}/creatureblock.html.erb",
      'item' => "#{extdir}/itemblock.html.erb",
      'hazard' => "#{extdir}/hazardblock.html.erb"
    }
    template = File.read(templates[type])
    html = ERB.new(template).result_with_hash(data)
    create_pass_block parent, html, attrs, subs: nil
  end
end
        

A lib/asciidoctor-p2e/hazardblock.html.erb => lib/asciidoctor-p2e/hazardblock.html.erb +43 -0
@@ 0,0 1,43 @@
<div class="statblock <%= defined?(role) ? role : '' %>">
  <div class="statblock-title">
    <span class="statblock-name"><%= name %></span>
    <span class="statblock-type">HAZARD <%= level %></span>
  </div>
  <div class="statblock-info-block">
    <div class="statblock-tags">
      <% tags.each do |tag| %>
        <% if tag == "uncommon" %>
          <div class="statblock-tag-rarity-uncommon"><%= tag %></div>
        <% elsif tag == "rare" %>
          <div class="statblock-tag-rarity-rare"><%= tag %></div>
        <% elsif tag == "unique" %>
          <div class="statblock-tag-rarity-unique"><%= tag %></div>
        <% else %>
          <div class="statblock-tag-other"><%= tag %></div>
        <% end %>
      <% end %>
    </div>
    <div class="statblock-info-other">
      <div class="statblock-info-line">
        <span class="statblock-line-heavy">Stealth </span><%= stealth %>
      </div>
      <div class="statblock-info-line">
        <span class="statblock-line-heavy">Description </span><%= description %>
      </div>
    </div>
  </div>
  <div class="statblock-info-block">
    <div class="statblock-info-line">
      <span class="statblock-line-heavy">Disable </span><%= disable %>
    </div>
    <% if defined?(triggers) %>
      <% triggers.each do |trigger| %>
        <div class="statblock-info-line">
          <span class="statblock-line-heavy"><%= trigger['name'] %></span>
          <span class="statblock-reaction"></span>
          <span class="statblock-line-heavy">Trigger </span><%= trigger['text'] %>
        </div>
      <% end %>
    <% end %>
  </div>
</div>

A lib/asciidoctor-p2e/itemblock.html.erb => lib/asciidoctor-p2e/itemblock.html.erb +86 -0
@@ 0,0 1,86 @@
<div class="statblock <%= defined?(role) ? role : '' %>">
  <div class="statblock-title">
    <span class="statblock-name"><%= name %></span>
    <span class="statblock-type">ITEM <%= level %></span>
  </div>
  <div class="statblock-info-block">
    <div class="statblock-tags">
      <% tags.each do |tag| %>
        <% if tag == "uncommon" %>
          <div class="statblock-tag-rarity-uncommon"><%= tag %></div>
        <% elsif tag == "rare" %>
          <div class="statblock-tag-rarity-rare"><%= tag %></div>
        <% elsif tag == "unique" %>
          <div class="statblock-tag-rarity-unique"><%= tag %></div>
        <% else %>
          <div class="statblock-tag-other"><%= tag %></div>
        <% end %>
      <% end %>
    </div>
    <div class="statblock-info-other">
      <% if defined?(price) %>
        <div class="statblock-info-line">
          <span class="statblock-line-heavy">Price </span><%= price %>
        </div>
      <% end %>
      <div class="statblock-info-line">
        <span class="statblock-line-heavy">Usage </span><%= usage %>&#59;
        <span class="statblock-line-heavy"> Bulk </span><%= bulk %>&#59;
      </div>
    </div>
    <% if defined?(activations) %>
      <div class="statblock-info-other">
        <% activations.each do |activation| %>
          <div class="statblock-info-line">
            <span class="statblock-line-heavy">  Activate </span>
            <% if activation['actions'] == 0 %>
              <span class="statblock-free-action"></span>
            <% elsif activation['actions'] == 1 %>
              <span class="statblock-one-action"></span>
            <% elsif activation['actions'] == 2 %>
              <span class="statblock-two-actions"></span>
            <% elsif activation['actions'] == 3 %>
              <span class="statblock-three-actions"></span>
            <% end %>
            <% if activation.key?('actionText') %>
              <%= activation['actionText'] %>
            <% end %>
            <% if activation.key?('frequency') %>
              <span class="statblock-line-heavy"> Frequency </span><%= activation['frequency'] %>
            <% end %>
            <% if activation.key?('trigger') %>
              <span class="statblock-line-heavy"> Trigger </span><%= activation['trigger'] %>
            <% end %>
            <% if activation.key?('effect') %>
              <span class="statblock-line-heavy"> Effect </span><%= activation['effect'] %>
            <% end %>
          </div>
        <% end %>
      </div>
    <% end %>
  </div>
  <div class="statblock-info-block">
    <% if defined?(hardness) %>
      <div class="statblock-info-line">
        <span class="statblock-line-heavy">Hardness </span><%= hardness['hardness'] %>&#59;
        <span class="statblock-line-heavy">HP </span><%= hardness['hp'] %>&#59;
        <span class="statblock-line-heavy">BT </span><%= hardness['bt'] %>&#59;
      </div>
    <% end %>
    <div><%= description %></div>
  </div>
  <% if defined?(types) %>
    <% types.each do |type| %>
      <div class="statblock-info-block">
        <div class="statblock-info-line">
          <span class="statblock-line-heavy">Type </span><em><%= type['type'] %></em>
          <span class="statblock-line-heavy">Level </span><%= type['level'] %>
          <% if type.key?('price') %>
            <span class="statblock-line-heavy">Price </span><%= type['price'] %>
          <% end %>
        </div>
        <div><%= type['description'] %></div>
      </div>
    <% end %>
  <% end %>
</div>