summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-09-16 14:42:22 -0500
committersanine <sanine.not@pm.me>2023-09-16 14:42:22 -0500
commite5daa18bc2aef0a15c6e706d5705f98f9c1fb8ea (patch)
tree9271f896d4d724f9a14d95caa1d5d2362d31aa03
parent5a444d65c20f1caf5e64722308fb5c2f558978cc (diff)
add traits and actions
-rwxr-xr-xdemo.lua17
-rw-r--r--draw.lua167
2 files changed, 153 insertions, 31 deletions
diff --git a/demo.lua b/demo.lua
index 2463290..a855cbc 100755
--- a/demo.lua
+++ b/demo.lua
@@ -18,6 +18,23 @@ local stats = {
int = 12,
wis = 13,
cha = 10,
+
+ saving_throws = 'Con +4',
+ immunities = 'fire, poison',
+ condition_immunities = 'poisoned',
+ senses = 'passive Perception 11',
+ languages = 'Ignan',
+ cr = '2 (450 XP)',
+
+ traits = {
+ { name='Heated Body', value="A creature that touches the azer or hits it with a melee attack while within 5 feet of it takes 5 (1d10) fire damage." },
+ { name="Heated Weapons", value="When the azer hits with a metal melee weapon, it deals and extra 3 (1d6) fire damage (included in the attack)." },
+ { name="Illumination", value="The azer sheds bright light in a 10=foot radius and dim light for an additional 10 feet." },
+ },
+
+ actions = {
+ { name='Warhammer', value="*Melee Weapon Attack:* +5 to hit, reach 5 ft., one target. *Hit:* 7 (1d8 + 3) bludgeoning damage or 8 (1d10 + 3) bludgeoning damage if used with two hands to make a melee attack, plus 3 (1d6) fire damage." },
+ },
}
diff --git a/draw.lua b/draw.lua
index f80d565..b0aa3b1 100644
--- a/draw.lua
+++ b/draw.lua
@@ -47,33 +47,85 @@ end
local divider = function() return pad(0, 10, _divider()) end
---===== basic stats =====--
+local function empty()
+ return function(y) return m.g{} end, 0
+end
+
+
+local function wrap_text(str, max_len, tbl)
+ tbl = tbl or {}
+ local len = 0
+ local count = 0
+ local in_tag = false
+ for i = 1,#str do
+ count = count+1
+ local c = string.sub(str, i, i)
+ if c == '>' then in_tag = false
+ elseif c == '<' then in_tag = true
+ end
-local function name(stats)
- local size = 40
- return function(y)
- return m.text(stats.name, {
- x=10, y=y+size,
- style="font-size:"..size.."px;font-variant-caps:small-caps;stroke:black;fill:darkred;font-family:serif",
- })
- end, size
+ if not in_tag then len = len+1 end
+ if len >= max_len then break end
+ end
+ local line = string.sub(str, 1,count)
+ if len < max_len then
+ table.insert(tbl, line)
+ return tbl
+ end
+
+ -- remove word-end from line
+ local start = string.find(line, '[^ ]+$')
+ local word = string.sub(line, start)
+ local line = string.sub(line, 1, start-1)
+ local rest = word .. string.sub(str, count+1)
+ table.insert(tbl, line)
+ return wrap_text(rest, max_len, tbl)
end
-local function subheading(stats)
- local size = 14
- return function(y)
- return m.text(
- string.format("%s %s, %s", capitalize(stats.size), stats.type, stats.alignment),
- {
- x=10, y=y+size, style="font-size:"..size.."px;font-style:italic;"
- }
- )
- end, size
+local function wrapped_text(str, max_len, x, size, line_spacing, style)
+ local lines = wrap_text(str, max_len)
+ local transform = function(index, line)
+ return function(y) return m.text(line, {
+ x=x, y=y+size,
+ style="font-size:"..size.."px;"..(style or ''),
+ textLength=(index ~= #lines and 480) or nil,
+ }) end, size+line_spacing
+ end
+ local tbl = {}
+ for index, line in ipairs(lines) do
+ table.insert(tbl, {transform(index, line)})
+ end
+ return stack(unpack(tbl))
+end
+
+
+local function format(str)
+ local str = string.gsub(str, "%*%*(.-)%*%*", '<tspan style="font-weight:bold">%1</tspan>')
+ str = string.gsub(str, "%*(.-)%*", '<tspan style="font-style:italic">%1</tspan>')
+ return str
end
+
+local function text(str, size, style)
+ return function(y) return m.text(
+ str,
+ { style="font-size:"..size.."px;" .. (style or ''), x=10, y=y+size }
+ ) end, size
+end
+
+
+--===== basic stats =====--
+
local function header(stats)
local f, h = stack(
- {pad(10, 10, name(stats))},
- {pad(0, 10, subheading(stats))}
+ --{pad(10, 10, name(stats))},
+ {pad(10, 10, text(
+ stats.name, 40,
+ 'font-variant-caps:small-caps;stroke:black;fill:darkred;font-family:serif'
+ ))},
+ {pad(0, 10, text(
+ string.format("%s %s, %s", capitalize(stats.size), stats.type, stats.alignment),
+ 14, 'font-style:italic'
+ ))}
)
return f, h
end
@@ -85,7 +137,13 @@ local function _statline(name, value)
{ x=10, y=y+size, style="font-size:"..size.."px;fill:darkred;" }
) end, 14
end
-local statline = function(name, value) return pad(0, 2, _statline(name, value)) end
+--local statline = function(name, value) return pad(0, 2, _statline(name, value)) end
+local function statline(name, value)
+ return pad(0, 2, wrapped_text(
+ string.format('<tspan style="font-weight:bold">%s</tspan> %s', name, value),
+ 80, 10, 14, 2, 'fill:darkred'
+ ))
+end
local function armor_hp(stats)
local f, h = pad(0, 10, stack(
@@ -118,6 +176,35 @@ local function ability_scores(stats)
} end, 40
end
+local function optional_attribute(stats, name, key)
+ local value = stats[key]
+ if value then
+ local f, h = statline(name, value)
+ return f, h
+ else
+ local f, h = empty()
+ return f, h
+ end
+end
+local function misc_attributes(stats)
+ local f, h = stack(
+ {optional_attribute(stats, 'Saving Throws', 'saving_throws')},
+ {optional_attribute(stats, 'Skills', 'skills')},
+ {optional_attribute(stats, 'Damage Vulnerabilities', 'vulnerabilities')},
+ {optional_attribute(stats, 'Damage Resistances', 'resistances')},
+ {optional_attribute(stats, 'Damage Immunities', 'immunities')},
+ {optional_attribute(stats, 'Condition Immunities', 'condition_immunities')},
+ {optional_attribute(stats, 'Senses', 'senses')},
+ {optional_attribute(stats, 'Languages', 'languages')},
+ {optional_attribute(stats, 'Challenge', 'cr')}
+ )
+ if h > 0 then return stack({pad(0, 10, f, h)}, {divider()})
+ else return empty(), 0 end
+end
+
+
+
+
local function base(stats)
local f, h = stack(
{header(stats)},
@@ -125,33 +212,51 @@ local function base(stats)
{armor_hp(stats)},
{divider()},
{ability_scores(stats)},
- {divider()}
+ {divider()},
+ {misc_attributes(stats)}
)
return f, h
end
---===== attributes =====--
+--===== traits =====--
-local function attribs(stats)
- return function(y) return m.rect{
- x=0, y=y, width=500, height=20,
- style="fill:blue",
- } end, 20
+local function trait(t)
+ return pad(0, 10, wrapped_text(
+ string.format('<tspan style="font-weight:bold;font-style:italic">%s.</tspan> %s', t.name, format(t.value)),
+ 80, 10, 14, 5
+ ))
+end
+local function traits(stats)
+ if not stats.traits then return empty() end
+
+ local tbl = {}
+ for _, t in ipairs(stats.traits) do
+ table.insert(tbl, {trait(t)})
+ end
+ return stack(unpack(tbl))
end
--===== actions =====--
local function actions(stats)
- return function(y) return m.g{} end, 0
+ if not stats.actions then return empty() end
+
+ local tbl = {}
+ for _, action in ipairs(stats.actions) do
+ table.insert(tbl, {trait(action)})
+ end
+ return stack(
+ unpack(tbl)
+ )
end
--===== draw =====--
function draw(stats)
- local f, h = stack({base(stats)}, {attribs(stats)}, {actions(stats)})
+ local f, h = stack({base(stats)}, {traits(stats)}, {actions(stats)})
return m.render(m.svg{
viewBox = string.format("0 0 500 %d", h),
width = 500,