672 lines
21 KiB
TeX
672 lines
21 KiB
TeX
\ExplSyntaxOn
|
|
|
|
\NewDocumentCommand {\RpgCRExp} { m }
|
|
{
|
|
\str_case_e:nnF {#1}
|
|
{
|
|
{0} {#1~(0~\xpname)}
|
|
{1/10} {0~(10~\xpname)}
|
|
{1/8} {#1~(25~\xpname)}
|
|
{1/4} {#1~(50~\xpname)}
|
|
{1/2} {#1~(100~\xpname)}
|
|
{1} {#1~(200~\xpname)}
|
|
{2} {#1~(450~\xpname)}
|
|
{3} {#1~(700~\xpname)}
|
|
{4} {#1~(\numprint{1100}~\xpname)}
|
|
{5} {#1~(\numprint{1800}~\xpname)}
|
|
{6} {#1~(\numprint{2300}~\xpname)}
|
|
{7} {#1~(\numprint{2900}~\xpname)}
|
|
{8} {#1~(\numprint{3900}~\xpname)}
|
|
{9} {#1~(\numprint{5000}~\xpname)}
|
|
{10} {#1~(\numprint{5900}~\xpname)}
|
|
{11} {#1~(\numprint{7200}~\xpname)}
|
|
{12} {#1~(\numprint{8400}~\xpname)}
|
|
{13} {#1~(\numprint{10000}~\xpname)}
|
|
{14} {#1~(\numprint{11500}~\xpname)}
|
|
{15} {#1~(\numprint{13000}~\xpname)}
|
|
{16} {#1~(\numprint{15000}~\xpname)}
|
|
{17} {#1~(\numprint{18000}~\xpname)}
|
|
{18} {#1~(\numprint{20000}~\xpname)}
|
|
{19} {#1~(\numprint{22000}~\xpname)}
|
|
{20} {#1~(\numprint{25000}~\xpname)}
|
|
{21} {#1~(\numprint{33000}~\xpname)}
|
|
{22} {#1~(\numprint{41000}~\xpname)}
|
|
{23} {#1~(\numprint{50000}~\xpname)}
|
|
{24} {#1~(\numprint{62000}~\xpname)}
|
|
{25} {#1~(\numprint{75000}~\xpname)}
|
|
{26} {#1~(\numprint{90000}~\xpname)}
|
|
{27} {#1~(\numprint{105000}~\xpname)}
|
|
{28} {#1~(\numprint{120000}~\xpname)}
|
|
{29} {#1~(\numprint{135000}~\xpname)}
|
|
{30} {#1~(\numprint{155000}~\xpname)}
|
|
}
|
|
{#1}
|
|
}
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Monster environments
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Stat block made to look like the Monster Manual NPC definitions
|
|
\DeclareTColorBox {RpgMonsterNoBg} { O{} m }
|
|
{
|
|
enhanced,
|
|
frame~hidden,
|
|
boxrule = 0pt,
|
|
breakable,
|
|
parbox = false,
|
|
boxsep = 0pt,
|
|
toptitle = 3mm,
|
|
left = 0pt,
|
|
right = 0pt,
|
|
sharp~corners,
|
|
opacityback = 0,
|
|
colframe = titlered,
|
|
fonttitle = \RpgFontStatBlockTitle,
|
|
fontupper = \RpgFontStatBlockBody,
|
|
fontlower = \RpgFontStatBlockBody,
|
|
title = {#2},
|
|
coltitle = titlered,
|
|
#1
|
|
}
|
|
|
|
% Stat block made to look like stat blocks in the PHB.
|
|
\DeclareTColorBox {RpgMonsterBg} { O{} m }
|
|
{
|
|
enhanced,
|
|
frame~hidden,
|
|
before~skip = 12pt plus 3pt minus 3pt,
|
|
boxrule = 0pt,
|
|
breakable,
|
|
parbox = false,
|
|
boxsep = 2pt,
|
|
toptitle = 8pt,
|
|
top = 0pt,
|
|
left = 2pt,
|
|
right = 2pt,
|
|
bottom = 7pt,
|
|
sharp~corners,
|
|
oversize = 0pt,
|
|
borderline~north = {3pt} {0pt} {titlered},
|
|
borderline~north = {2pt} {0.5pt} {statblockribbon},
|
|
borderline~south = {3pt} {0pt} {titlered},
|
|
borderline~south = {2pt} {0.5pt} {statblockribbon},
|
|
colback = statblockbg,
|
|
colbacktitle = statblockbg,
|
|
colframe = titlered,
|
|
fonttitle = \RpgFontStatBlockTitle,
|
|
coltitle = titlered,
|
|
fontupper = \RpgFontStatBlockBody,
|
|
fontlower = \RpgFontStatBlockBody,
|
|
title = {#2},
|
|
#1
|
|
}
|
|
|
|
% Alias RpgMonster to correct background type
|
|
\bool_if:NTF \l__rpg_show_background_bool
|
|
{
|
|
\let\RpgMonster\RpgMonsterBg
|
|
\let\endRpgMonster\endRpgMonsterBg
|
|
}{
|
|
\let\RpgMonster\RpgMonsterNoBg
|
|
\let\endRpgMonster\endRpgMonsterNoBg
|
|
}
|
|
|
|
% Italicized text appearing immediately after monster's name
|
|
\NewDocumentCommand {\RpgMonsterType} {m}
|
|
{
|
|
\begin {hangingpar}
|
|
\textit {#1}
|
|
\end {hangingpar}
|
|
}
|
|
|
|
% Fancy DnD 5e stat block rule
|
|
\NewDocumentCommand {\RpgMonsterLine} {}
|
|
{
|
|
\par \vspace{-2pt} \noindent
|
|
\begin {tikzpicture}
|
|
\draw [ rulered, fill = rulered ] ( 0, 0 ) -- ( 0, 0.1 ) -- ( \linewidth, 0.05 );
|
|
\end {tikzpicture}
|
|
\par
|
|
}
|
|
|
|
% A description variant used to list creature attributes.
|
|
\newlist {__rpg_monster_attributes} {description} {1}
|
|
\setlist [__rpg_monster_attributes]
|
|
{
|
|
before = \color {titlered},
|
|
font = \RpgFontStatBlockBody,
|
|
labelsep = \l__rpg_space_dim,
|
|
nosep,
|
|
}
|
|
|
|
% Only prints the item label if the value was supplied
|
|
\cs_new_protected_nopar:Npn \__rpg_if_monster_attribute:nn #1#2
|
|
{
|
|
\tl_if_empty:NF {#1}
|
|
{ \item [#2] #1 }
|
|
}
|
|
|
|
%% Monster basics
|
|
\keys_define:nn { rpg / monster / basics }
|
|
{
|
|
armor-class .tl_set:N = \l__armor_class_tl,
|
|
armor-class .initial:n = 10,
|
|
armor-class .value_required:n = true,
|
|
armorclass .meta:n = { armor-class = {#1} },
|
|
hit-points .tl_set:N = \l__hit_points_tl,
|
|
hit-points .initial:n = \RpgDice {1d8},
|
|
hit-points .value_required:n = true,
|
|
hitpoints .meta:n = { hit-points = {#1} },
|
|
speed .tl_set:N = \l__speed_tl,
|
|
speed .initial:n = 30 ~ \unitsname,
|
|
speed .value_required:n = true,
|
|
}
|
|
|
|
\cs_new_protected_nopar:Npn \__rpg_monster_basics:
|
|
{
|
|
\begin {__rpg_monster_attributes}
|
|
\item [\armorclassname] \l__armor_class_tl
|
|
\item [\hitpointsname] \l__hit_points_tl
|
|
\item [\speedname] \l__speed_tl
|
|
\end {__rpg_monster_attributes}
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterBasics} {o}
|
|
{
|
|
\group_begin:
|
|
\keys_set:nn { rpg / monster / basics } {#1}
|
|
\RpgMonsterLine
|
|
\__rpg_monster_basics:
|
|
\RpgMonsterLine
|
|
\group_end:
|
|
}
|
|
|
|
%% Monster CR/XP helper
|
|
% \l_tmpa_tl - CR
|
|
% \l_tmpb_tl - XP
|
|
\cs_new_protected_nopar:Npn \__rpg_cr_xp:N #1
|
|
{
|
|
\tl_set:Nn \l_tmpa_tl {#1}
|
|
\str_case_e:nnTF {#1}
|
|
{
|
|
{0} { \tl_set:Nn \l_tmpb_tl {0} }
|
|
{0/10}
|
|
{
|
|
\tl_set:Nn \l_tmpa_tl {0}
|
|
\tl_set:Nn \l_tmpb_tl {10}
|
|
}
|
|
{1/10}
|
|
{
|
|
\rpg@deprecate{ Challenge ~ 1/10 } {0.8} [ Use ~ challenge ~ 0/10 ~ instead. ]
|
|
\tl_set:Nn \l_tmpa_tl {0}
|
|
\tl_set:Nn \l_tmpb_tl {10}
|
|
}
|
|
{1/8} { \tl_set:Nn \l_tmpb_tl {25} }
|
|
{1/4} { \tl_set:Nn \l_tmpb_tl {50} }
|
|
{1/2} { \tl_set:Nn \l_tmpb_tl {100} }
|
|
{1} { \tl_set:Nn \l_tmpb_tl {200} }
|
|
{2} { \tl_set:Nn \l_tmpb_tl {450} }
|
|
{3} { \tl_set:Nn \l_tmpb_tl {700} }
|
|
{4} { \tl_set:Nn \l_tmpb_tl {1100} }
|
|
{5} { \tl_set:Nn \l_tmpb_tl {1800} }
|
|
{6} { \tl_set:Nn \l_tmpb_tl {2300} }
|
|
{7} { \tl_set:Nn \l_tmpb_tl {2900} }
|
|
{8} { \tl_set:Nn \l_tmpb_tl {3900} }
|
|
{9} { \tl_set:Nn \l_tmpb_tl {5000} }
|
|
{10} { \tl_set:Nn \l_tmpb_tl {5900} }
|
|
{11} { \tl_set:Nn \l_tmpb_tl {7200} }
|
|
{12} { \tl_set:Nn \l_tmpb_tl {8400} }
|
|
{13} { \tl_set:Nn \l_tmpb_tl {10000} }
|
|
{14} { \tl_set:Nn \l_tmpb_tl {11500} }
|
|
{15} { \tl_set:Nn \l_tmpb_tl {13000} }
|
|
{16} { \tl_set:Nn \l_tmpb_tl {15000} }
|
|
{17} { \tl_set:Nn \l_tmpb_tl {18000} }
|
|
{18} { \tl_set:Nn \l_tmpb_tl {20000} }
|
|
{19} { \tl_set:Nn \l_tmpb_tl {22000} }
|
|
{20} { \tl_set:Nn \l_tmpb_tl {25000} }
|
|
{21} { \tl_set:Nn \l_tmpb_tl {33000} }
|
|
{22} { \tl_set:Nn \l_tmpb_tl {41000} }
|
|
{23} { \tl_set:Nn \l_tmpb_tl {50000} }
|
|
{24} { \tl_set:Nn \l_tmpb_tl {62000} }
|
|
{25} { \tl_set:Nn \l_tmpb_tl {75000} }
|
|
{26} { \tl_set:Nn \l_tmpb_tl {90000} }
|
|
{27} { \tl_set:Nn \l_tmpb_tl {105000} }
|
|
{28} { \tl_set:Nn \l_tmpb_tl {120000} }
|
|
{29} { \tl_set:Nn \l_tmpb_tl {135000} }
|
|
{30} { \tl_set:Nn \l_tmpb_tl {155000} }
|
|
}
|
|
{ \l_tmpa_tl\ ( \numprint {\l_tmpb_tl} ~ \xpname ) }
|
|
{ #1 }
|
|
}
|
|
|
|
%% Monster CR/PB helper
|
|
% \l_tmpa_tl - CR
|
|
% \l_tmpb_tl - PB
|
|
\cs_new_protected_nopar:Npn \__rpg_cr_pb:N #1
|
|
{
|
|
\tl_set:Nn \l_tmpa_tl {#1}
|
|
\str_case_e:nnTF {#1}
|
|
{
|
|
{0} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{1/8} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{1/4} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{1/2} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{1} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{2} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{3} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{4} { \tl_set:Nn \l_tmpb_tl {+2} }
|
|
{5} { \tl_set:Nn \l_tmpb_tl {+3} }
|
|
{6} { \tl_set:Nn \l_tmpb_tl {+3} }
|
|
{7} { \tl_set:Nn \l_tmpb_tl {+3} }
|
|
{8} { \tl_set:Nn \l_tmpb_tl {+3} }
|
|
{9} { \tl_set:Nn \l_tmpb_tl {+4} }
|
|
{10} { \tl_set:Nn \l_tmpb_tl {+4} }
|
|
{11} { \tl_set:Nn \l_tmpb_tl {+4} }
|
|
{12} { \tl_set:Nn \l_tmpb_tl {+4} }
|
|
{13} { \tl_set:Nn \l_tmpb_tl {+5} }
|
|
{14} { \tl_set:Nn \l_tmpb_tl {+5} }
|
|
{15} { \tl_set:Nn \l_tmpb_tl {+5} }
|
|
{16} { \tl_set:Nn \l_tmpb_tl {+5} }
|
|
{17} { \tl_set:Nn \l_tmpb_tl {+6} }
|
|
{18} { \tl_set:Nn \l_tmpb_tl {+6} }
|
|
{19} { \tl_set:Nn \l_tmpb_tl {+6} }
|
|
{20} { \tl_set:Nn \l_tmpb_tl {+6} }
|
|
{21} { \tl_set:Nn \l_tmpb_tl {+7} }
|
|
{22} { \tl_set:Nn \l_tmpb_tl {+7} }
|
|
{23} { \tl_set:Nn \l_tmpb_tl {+7} }
|
|
{24} { \tl_set:Nn \l_tmpb_tl {+7} }
|
|
{25} { \tl_set:Nn \l_tmpb_tl {+8} }
|
|
{26} { \tl_set:Nn \l_tmpb_tl {+8} }
|
|
{27} { \tl_set:Nn \l_tmpb_tl {+8} }
|
|
{28} { \tl_set:Nn \l_tmpb_tl {+8} }
|
|
{29} { \tl_set:Nn \l_tmpb_tl {+9} }
|
|
{30} { \tl_set:Nn \l_tmpb_tl {+9} }
|
|
}
|
|
{ \l_tmpb_tl }
|
|
{ #1 }
|
|
}
|
|
|
|
%% Monster details
|
|
\keys_define:nn { rpg / monster / details }
|
|
{
|
|
saving-throws .tl_set:N = \l__saving_throws_tl,
|
|
saving-throws .value_required:n = true,
|
|
savingthrows .meta:n = { saving-throws = {#1} },
|
|
skills .tl_set:N = \l__skills_tl,
|
|
skills .value_required:n = true,
|
|
damage-vulnerabilities .tl_set:N = \l__damage_vulnerabilities_tl,
|
|
damage-vulnerabilities .value_required:n = true,
|
|
damagevulnerabilities .meta:n = { damage-vulnerabilities = {#1} },
|
|
damage-resistances .tl_set:N = \l__damage_resistances_tl,
|
|
damage-resistances .value_required:n = true,
|
|
damageresistances .meta:n = { damage-resistances = {#1} },
|
|
damage-immunities .tl_set:N = \l__damage_immunities_tl,
|
|
damage-immunities .value_required:n = true,
|
|
damageimmunities .meta:n = { damage-immunities = {#1} },
|
|
condition-immunities .tl_set:N = \l__condition_immunities_tl,
|
|
condition-immunities .value_required:n = true,
|
|
conditionimmunities .meta:n = { condition-immunities = {#1} },
|
|
senses .tl_set:N = \l__senses_tl,
|
|
senses .initial:n = \defaultsensesname,
|
|
senses .value_required:n = true,
|
|
languages .tl_set:N = \l__languages_tl,
|
|
languages .initial:n = ---,
|
|
languages .value_required:n = true,
|
|
challenge .tl_set:N = \l__challenge_tl,
|
|
challenge .initial:n = 1,
|
|
challenge .value_required:n = true,
|
|
proficiency-bonus .tl_set:N = \l__proficiency_tl,
|
|
proficiency-bonus .value_required:n = false,
|
|
proficiency-bonus .default:n = *,
|
|
proficiencybonus .meta:n = { proficiency-bonus = {#1} },
|
|
}
|
|
|
|
\cs_new_protected_nopar:Npn \__rpg_monster_details:
|
|
{
|
|
\begin {__rpg_monster_attributes}
|
|
\__rpg_if_monster_attribute:nn {\l__saving_throws_tl} {\savesname}
|
|
\__rpg_if_monster_attribute:nn {\l__skills_tl} {\skillsname}
|
|
\__rpg_if_monster_attribute:nn {\l__damage_vulnerabilities_tl} {\dvulname}
|
|
\__rpg_if_monster_attribute:nn {\l__damage_resistances_tl} {\dresname}
|
|
\__rpg_if_monster_attribute:nn {\l__damage_immunities_tl} {\dimmname}
|
|
\__rpg_if_monster_attribute:nn {\l__condition_immunities_tl} {\cimmname}
|
|
\item [\sensesname] \l__senses_tl
|
|
\item [\languagesname] \l__languages_tl
|
|
\item [\challengename] \__rpg_cr_xp:N {\l__challenge_tl}
|
|
\tl_if_empty:NF {\l__proficiency_tl}
|
|
{
|
|
{\enit@svlabel{\enit@format{\pbonname}}}
|
|
\hskip \labelsep
|
|
\str_if_eq:VnTF \l__proficiency_tl *
|
|
{\__rpg_cr_pb:N {\l__challenge_tl}}
|
|
{\l__proficiency_tl}
|
|
}
|
|
\end {__rpg_monster_attributes}
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterDetails} {o}
|
|
{
|
|
\group_begin:
|
|
\keys_set:nn { rpg / monster / details } {#1}
|
|
\RpgMonsterLine{}
|
|
\__rpg_monster_details:
|
|
\RpgMonsterLine{}
|
|
\group_end:
|
|
}
|
|
|
|
%% Ability scores
|
|
% Print ability score stats with auto-computed modifier
|
|
% e.g. \stat{12} prints "12 (+1)"
|
|
\cs_new_protected_nopar:Npn \__rpg_ability_score_modifier:N #1
|
|
{
|
|
\regex_match:NnTF \c__pos_int_regex {#1}
|
|
{
|
|
\int_set:Nn \l_tmpa_int { \fp_eval:n { floor ( ( #1 - 10 ) / 2 ) } }
|
|
|
|
#1 ~ (
|
|
\int_compare:nNnTF {\l_tmpa_int} < {0}
|
|
{ -- }
|
|
{+}
|
|
\int_abs:n \l_tmpa_int )
|
|
}
|
|
{#1}
|
|
}
|
|
|
|
\keys_define:nn { rpg / monster / ability_scores }
|
|
{
|
|
str .tl_set:N = \l__str_tl,
|
|
str .initial:n = 10,
|
|
str .value_required:n = true,
|
|
STR .meta:n = { str = #1 },
|
|
dex .tl_set:N = \l__dex_tl,
|
|
dex .initial:n = 10,
|
|
dex .value_required:n = true,
|
|
DEX .meta:n = { dex = #1 },
|
|
con .tl_set:N = \l__con_tl,
|
|
con .initial:n = 10,
|
|
con .value_required:n = true,
|
|
CON .meta:n = { con = #1 },
|
|
int .tl_set:N = \l__int_tl,
|
|
int .initial:n = 10,
|
|
int .value_required:n = true,
|
|
INT .meta:n = { int = #1 },
|
|
wis .tl_set:N = \l__wis_tl,
|
|
wis .initial:n = 10,
|
|
wis .value_required:n = true,
|
|
WIS .meta:n = { wis = #1 },
|
|
cha .tl_set:N = \l__cha_tl,
|
|
cha .initial:n = 10,
|
|
cha .value_required:n = true,
|
|
CHA .meta:n = { cha = #1 },
|
|
}
|
|
|
|
% Ability scores in a table
|
|
\cs_new_protected_nopar:Npn \__rpg_monster_ability_scores:
|
|
{
|
|
\color {titlered}
|
|
\par \vspace{1pt} \noindent
|
|
\begin{tabularx} {\linewidth} {YYYYYY}
|
|
\rule {0pt} {3.7mm} % adds space between hline and table
|
|
\textbf {\strstatname} &
|
|
\textbf {\dexstatname} &
|
|
\textbf {\constatname} &
|
|
\textbf {\intstatname} &
|
|
\textbf {\wisstatname} &
|
|
\textbf {\chastatname} \\
|
|
\exp_args:NV \__rpg_ability_score_modifier:N \l__str_tl &
|
|
\exp_args:NV \__rpg_ability_score_modifier:N \l__dex_tl &
|
|
\exp_args:NV \__rpg_ability_score_modifier:N \l__con_tl &
|
|
\exp_args:NV \__rpg_ability_score_modifier:N \l__int_tl &
|
|
\exp_args:NV \__rpg_ability_score_modifier:N \l__wis_tl &
|
|
\exp_args:NV \__rpg_ability_score_modifier:N \l__cha_tl
|
|
\end{tabularx}
|
|
|
|
\par \vspace{4pt}%
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterAbilityScores} {o}
|
|
{
|
|
\group_begin:
|
|
\keys_set:nn { rpg / monster / ability_scores } {#1}
|
|
\__rpg_monster_ability_scores:
|
|
\group_end:
|
|
}
|
|
|
|
% Inline header for monster actions - similar to a paragraph
|
|
\NewDocumentCommand {\RpgMonsterAction} {m}
|
|
{ \par \smallskip \noindent \textsl { \textbf {#1.} } }
|
|
|
|
% Inline header for monster sub actions - similar to a subparagraph
|
|
\NewDocumentCommand {\RpgMonsterSubAction} {m}
|
|
{ \par \textsl { \textbf {#1.} } }
|
|
|
|
% Monster subsection header style
|
|
\NewDocumentCommand {\RpgMonsterSection} {m}
|
|
{
|
|
\addvspace{6pt plus 2pt minus 2pt} \noindent
|
|
\group_begin:
|
|
\RpgFontStatBlockSection #1\nopagebreak[4]
|
|
% \rule is a horizontal command, so placing it under the title incurs extra
|
|
% line spacing. Use \hrule (a vertical command) instead.
|
|
\vspace {2pt}\nopagebreak[4]
|
|
\hrule height 0.6pt
|
|
\group_end:
|
|
\par \vspace{5pt}
|
|
\noindent \ignorespaces
|
|
}
|
|
|
|
% Spellcasting levels
|
|
\newlist {RpgMonsterSpells} {description} {1}
|
|
\setlist [RpgMonsterSpells]
|
|
{
|
|
font = \normalfont \RpgFontStatBlockBody,
|
|
labelsep = \l__rpg_space_dim,
|
|
noitemsep,
|
|
topsep = 6pt plus 2pt minus 2pt,
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgEmphSpellString} {m}
|
|
{
|
|
\group_begin:
|
|
\seq_set_from_clist:Nn \l_tmpa_seq {#1}
|
|
\seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { \exp_not:n { \emph {##1} } }
|
|
\seq_use:Nn \l_tmpb_seq { ,~ }
|
|
\group_end:
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgInnateSpellLevel} { O {\innateatwillname} m }
|
|
{
|
|
\item
|
|
[
|
|
\regex_match:NnTF \c__pos_int_regex {#1}
|
|
{
|
|
\str_if_in:NnTF {#2} {,}
|
|
{ \__rpg_caption:nn {\numberperdayeachname} {#1} }
|
|
{ \__rpg_caption:nn {\numberperdayname} {#1} }
|
|
}
|
|
{#1}
|
|
:
|
|
]
|
|
\RpgEmphSpellString {#2}
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterSpellLevel} { O{\spellcantripsname} O{\spellatwillname} m }
|
|
{
|
|
\item
|
|
[
|
|
\str_case_e:nnF {#1}
|
|
{
|
|
{1} {\spellfirstlevelname}
|
|
{2} {\spellsecondlevelname}
|
|
{3} {\spellthirdlevelname}
|
|
{4} {\spellfourthlevelname}
|
|
{5} {\spellfifthlevelname}
|
|
{6} {\spellsixthlevelname}
|
|
{7} {\spellseventhlevelname}
|
|
{8} {\spelleighthlevelname}
|
|
{9} {\spellninthlevelname}
|
|
}
|
|
{#1}
|
|
{~}(
|
|
\str_case_e:nnF {#2}
|
|
{
|
|
{1} {\spelloneslotname}
|
|
{2} {\spelltwoslotsname}
|
|
{3} {\spellthreeslotsname}
|
|
{4} {\spellfourslotsname}
|
|
}
|
|
{#2}
|
|
) :
|
|
]
|
|
\RpgEmphSpellString{#3}
|
|
}
|
|
|
|
%% Monster attacks
|
|
\tl_new:N \l__rpg_attack_distance_tl
|
|
\tl_new:N \l__rpg_attack_type_tl
|
|
|
|
\keys_define:nn { rpg / monster / attack }
|
|
{
|
|
name .tl_set:N = \l__name_tl,
|
|
name .value_required:n = true,
|
|
distance .choice:,
|
|
distance .choices:nn =
|
|
{ both, melee, ranged }
|
|
{ \tl_set:Nn \l__rpg_attack_distance_tl {\l_keys_choice_tl} },
|
|
distance .initial:n = both,
|
|
type .choice:,
|
|
type / weapon .code:n = { \tl_set:Nn \l__rpg_attack_type_tl {\weaponname} },
|
|
type / spell .code:n = { \tl_set:Nn \l__rpg_attack_type_tl {\spellname} },
|
|
type .initial:n = weapon,
|
|
mod .tl_set:N = \l__mod_tl,
|
|
mod .value_required:n = true,
|
|
reach .tl_set:N = \l__reach_tl,
|
|
reach .initial:n = 5,
|
|
reach .value_required:n = true,
|
|
range .tl_set:N = \l__range_tl,
|
|
range .initial:n = 20/60,
|
|
range .value_required:n = true,
|
|
targets .tl_set:N = \l__targets_tl,
|
|
targets .initial:n = \defaulttargetsname,
|
|
targets .value_required:n = true,
|
|
dmg .tl_set:N = \l__dmg_tl,
|
|
dmg .value_required:n = true,
|
|
dmg-type .tl_set:N = \l__dmg_type_tl,
|
|
dmg-type .value_required:n = true,
|
|
plus-dmg .tl_set:N = \l__plus_dmg_tl,
|
|
plus-dmg .value_required:n = true,
|
|
plus-dmg-type .tl_set:N = \l__plus_dmg_type_tl,
|
|
plus-dmg-type .value_required:n = true,
|
|
or-dmg .tl_set:N = \l__or_dmg_tl,
|
|
or-dmg .value_required:n = true,
|
|
or-dmg-when .tl_set:N = \l__or_dmg_when_tl,
|
|
or-dmg-when .value_required:n = true,
|
|
extra .tl_set:N = \l__extra_tl,
|
|
extra .value_required:n = true,
|
|
}
|
|
|
|
\cs_new_protected:Npn \__rpg_monster_reach:
|
|
{
|
|
\reachname\ \l__reach_tl\ \unitsname
|
|
}
|
|
|
|
\cs_new_protected:Npn \__rpg_monster_range:
|
|
{
|
|
\rangename\ \l__range_tl\ \unitsname
|
|
}
|
|
|
|
\cs_new_protected:Npn \__rpg_if_plus_dmg:
|
|
{
|
|
\tl_if_empty:NF {\l__plus_dmg_tl}
|
|
{ ~ \plusname\ \l__plus_dmg_tl\ \l__plus_dmg_type_tl\ \damagename }
|
|
}
|
|
|
|
\cs_new_protected_nopar:Npn \__rpg_if_or_dmg:
|
|
{
|
|
\tl_if_empty:NF {\l__or_dmg_tl}
|
|
{
|
|
, ~ \orname\ \l__or_dmg_tl\ \l__dmg_type_tl\ \damagename\ \l__or_dmg_when_tl
|
|
|
|
\tl_if_empty:NF {\l__plus_dmg_tl}
|
|
{,}
|
|
}
|
|
}
|
|
|
|
\cs_new_protected:Npn \__rpg_monster_attack:
|
|
{
|
|
\__rpg_check_for_key:Nnn \l__name_tl {\RpgMonsterAttack} {name}
|
|
\__rpg_check_for_key:Nnn \l__mod_tl {\RpgMonsterAttack} {mod}
|
|
|
|
\begin{RpgMonsterAction} {\l__name_tl}~
|
|
|
|
\str_case_e:nnF {\l__rpg_attack_distance_tl}
|
|
{
|
|
{ melee }
|
|
{
|
|
\textit{ \__rpg_caption:nn {\meleeattackname} {\l__rpg_attack_type_tl} : } ~ \__rpg_caption:nn {\tohitname} {\l__mod_tl}, ~ \__rpg_monster_reach:
|
|
}
|
|
{ ranged }
|
|
{
|
|
\textit{ \__rpg_caption:nn {\rangedattackname} {\l__rpg_attack_type_tl} : } ~ \__rpg_caption:nn {\tohitname} {\l__mod_tl}, ~ \__rpg_monster_range:
|
|
}
|
|
}
|
|
{% Melee and Ranged is the default
|
|
\textit{ \__rpg_caption:nn {\meleeorrangedattackname} {\l__rpg_attack_type_tl} : } ~ \__rpg_caption:nn {\tohitname} {\l__mod_tl}, ~ \__rpg_monster_reach:\ \orname\ \__rpg_monster_range:
|
|
}
|
|
, ~ \l__targets_tl. ~
|
|
\textit { \hitname : } ~
|
|
|
|
\str_if_empty:NF {\l__dmg_tl} % Don't show any damage if `dmg' is not set.
|
|
{
|
|
\l__dmg_tl\ \l__dmg_type_tl\ \damagename
|
|
\__rpg_if_or_dmg:
|
|
\__rpg_if_plus_dmg:
|
|
}
|
|
|
|
% `extra' is any special text that goes after the final damage; do not
|
|
% include the final full stop.
|
|
\l__extra_tl .
|
|
\end{RpgMonsterAction}
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterAttack} {o}
|
|
{
|
|
\group_begin:
|
|
\keys_set:nn { rpg / monster / attack } {#1}
|
|
\__rpg_monster_attack:
|
|
\group_end:
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterMelee} {o}
|
|
{
|
|
\group_begin:
|
|
\keys_set:nn { rpg / monster / attack } { #1, distance = melee }
|
|
\__rpg_monster_attack:
|
|
\group_end:
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterRanged} {o}
|
|
{
|
|
\group_begin:
|
|
\keys_set:nn { rpg / monster / attack } { #1, distance = ranged }
|
|
\__rpg_monster_attack:
|
|
\group_end:
|
|
}
|
|
|
|
%% Legendary Actions
|
|
|
|
\newlist {RpgMonsterLegendaryActions} {description} {1}
|
|
\setlist [RpgMonsterLegendaryActions]
|
|
{
|
|
font = \RpgFontStatBlockBody,
|
|
labelsep = \l__rpg_space_dim,
|
|
noitemsep,
|
|
topsep = 6pt plus 2pt minus 2pt,
|
|
}
|
|
|
|
\NewDocumentCommand {\RpgMonsterLegendaryAction} { m m }
|
|
{
|
|
\item [ #1 . ] #2
|
|
}
|
|
|