mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-08-30 19:52:29 +00:00
3494 lines
261 KiB
HTML
3494 lines
261 KiB
HTML
<!--
|
|
ROLL20 CHARACTER SHEET FOR WARLOCK! OSR BY OOSHP
|
|
V 0.4.2
|
|
-->
|
|
|
|
<!-- START OF CHARACTER SHEET -->
|
|
<div class="war-outer">
|
|
<div class="war-header">
|
|
<div class="header-charname">
|
|
<span name="attr_character_name"></span>
|
|
</div>
|
|
<div class="header-logo">
|
|
<img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/images/warlocklogo.png" alt="warlocklogo">
|
|
</div>
|
|
<div class="war-tabs">
|
|
<input type="hidden" name="attr_activetab" value="character"/>
|
|
<div class="tab character"><button type="action" name="act_tab_character">Character</button></div>
|
|
<div class="tab settings"><button type="action" name="act_tab_settings">Settings</button></div>
|
|
<div class="tab help"><button type="action" name="act_tab_help">Help</button></div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" name="attr_activetab" value="character"/>
|
|
<div class="war-character">
|
|
<input type="hidden" name="attr_npc" value="0"/>
|
|
<div class="war-character-pc">
|
|
<div class="war-main">
|
|
<div class="war-group basic-info">
|
|
<div class="attribute input">
|
|
<label>Name: </label><input type="text" name="attr_character_name"/>
|
|
</div>
|
|
<input type="hidden" name="attr_unlocked"/>
|
|
<div class="attribute select community">
|
|
<label>Community: </label>
|
|
<select name="attr_community">
|
|
<option value="none">-</option>
|
|
<option value="human">Human</option>
|
|
<option value="dwarf">Dwarf</option>
|
|
<option value="elf">Elf</option>
|
|
<option value="goblin">Goblin</option>
|
|
<option value="halfling">Halfling</option>
|
|
</select>
|
|
</div>
|
|
<div class="attribute input community">
|
|
<label>Community: </label>
|
|
<input type="text" name="attr_custom_community"/>
|
|
</div>
|
|
<!-- SETUP FOR CHARACTER CREATION TUTORIAL STEPS -->
|
|
<input type="hidden" name="attr_disable_tutorial" value="0">
|
|
<input type="hidden" name="attr_disable_career_flag" value="1">
|
|
<div class="disable-career tutorial">
|
|
<span>Please spend your initial<br>Skill Points before<br>selecting your Career</span>
|
|
<button type="action" name="act_disable-tutorial" class="tutorial">Disable Tutorial</button>
|
|
<button type="action" name="act_npc" class="tutorial">Create NPC</button>
|
|
</div>
|
|
<div class="attribute select career">
|
|
<input type="hidden" name="attr_career_toggle" value="0">
|
|
<!-- BASIC CAREER SELECT -->
|
|
<div class="basic-career-select">
|
|
<input type="hidden" name="attr_community" value="none">
|
|
<label>Career: </label>
|
|
<input type="hidden" name="attr_unlocked"/>
|
|
<select name="attr_career" class="career blank">
|
|
<option value="none">-</option>
|
|
</select>
|
|
<!-- BASIC GENERATED CAREERS BY RACE -->
|
|
<select name="attr_career" class="career human"><option value="none">-</option><option value="agitator">Agitator</option><option value="beggar">Beggar</option><option value="boatman">Boatman</option><option value="bodyguard">Bodyguard</option><option value="bounty_hunter">Bounty Hunter</option><option value="entertainer">Entertainer</option><option value="footpad">Footpad</option><option value="gambler">Gambler</option><option value="grave_robber">Grave Robber</option><option value="hunter">Hunter</option><option value="initiate">Initiate</option><option value="mercenary">Mercenary</option><option value="militiaman">Militiaman</option><option value="miner">Miner</option><option value="noble">Noble</option><option value="outlaw">Outlaw</option><option value="pedlar">Pedlar</option><option value="raconteur">Raconteur</option><option value="rat_catcher">Rat Catcher</option><option value="road_warden">Road Warden</option><option value="soldier">Soldier</option><option value="thief">Thief</option><option value="tomb_robber">Tomb Robber</option><option value="wizard's_apprentice">Wizard's Apprentice</option><option value="docker">Docker</option><option value="fish_warden">Fish Warden</option><option value="mudlark">Mudlark</option><option value="night_watchman">Night Watchman</option><option value="publican">Publican</option><option value="servant">Servant</option></select>
|
|
<select name="attr_career" class="career dwarf"><option value="none">-</option><optgroup label="Racial"><option value="dwarf_inventor">Dwarf Inventor</option><option value="dwarf_tunnel_fighter">Dwarf Tunnel Fighter</option></optgroup><optgroup label="Generic"><option value="agitator">Agitator</option><option value="beggar">Beggar</option><option value="boatman">Boatman</option><option value="bodyguard">Bodyguard</option><option value="bounty_hunter">Bounty Hunter</option><option value="entertainer">Entertainer</option><option value="footpad">Footpad</option><option value="gambler">Gambler</option><option value="grave_robber">Grave Robber</option><option value="hunter">Hunter</option><option value="initiate">Initiate</option><option value="mercenary">Mercenary</option><option value="militiaman">Militiaman</option><option value="miner">Miner</option><option value="noble">Noble</option><option value="outlaw">Outlaw</option><option value="pedlar">Pedlar</option><option value="raconteur">Raconteur</option><option value="rat_catcher">Rat Catcher</option><option value="road_warden">Road Warden</option><option value="soldier">Soldier</option><option value="thief">Thief</option><option value="tomb_robber">Tomb Robber</option><option value="wizard's_apprentice">Wizard's Apprentice</option><option value="docker">Docker</option><option value="fish_warden">Fish Warden</option><option value="mudlark">Mudlark</option><option value="night_watchman">Night Watchman</option><option value="publican">Publican</option><option value="servant">Servant</option></optgroup></select>
|
|
<select name="attr_career" class="career elf"><option value="none">-</option><optgroup label="Racial"><option value="elf_astrologer">Elf Astrologer</option><option value="elf_kin_guard">Elf Kin Guard</option></optgroup><optgroup label="Generic"><option value="agitator">Agitator</option><option value="beggar">Beggar</option><option value="boatman">Boatman</option><option value="bodyguard">Bodyguard</option><option value="bounty_hunter">Bounty Hunter</option><option value="entertainer">Entertainer</option><option value="footpad">Footpad</option><option value="gambler">Gambler</option><option value="grave_robber">Grave Robber</option><option value="hunter">Hunter</option><option value="initiate">Initiate</option><option value="mercenary">Mercenary</option><option value="militiaman">Militiaman</option><option value="miner">Miner</option><option value="noble">Noble</option><option value="outlaw">Outlaw</option><option value="pedlar">Pedlar</option><option value="raconteur">Raconteur</option><option value="rat_catcher">Rat Catcher</option><option value="road_warden">Road Warden</option><option value="soldier">Soldier</option><option value="thief">Thief</option><option value="tomb_robber">Tomb Robber</option><option value="wizard's_apprentice">Wizard's Apprentice</option><option value="docker">Docker</option><option value="fish_warden">Fish Warden</option><option value="mudlark">Mudlark</option><option value="night_watchman">Night Watchman</option><option value="publican">Publican</option><option value="servant">Servant</option></optgroup></select>
|
|
<select name="attr_career" class="career halfling"><option value="none">-</option><optgroup label="Racial"><option value="halfling_gong_farmer">Halfling Gong Farmer</option><option value="halfling_pie_master">Halfling Pie Master</option></optgroup><optgroup label="Generic"><option value="agitator">Agitator</option><option value="beggar">Beggar</option><option value="boatman">Boatman</option><option value="bodyguard">Bodyguard</option><option value="bounty_hunter">Bounty Hunter</option><option value="entertainer">Entertainer</option><option value="footpad">Footpad</option><option value="gambler">Gambler</option><option value="grave_robber">Grave Robber</option><option value="hunter">Hunter</option><option value="initiate">Initiate</option><option value="mercenary">Mercenary</option><option value="militiaman">Militiaman</option><option value="miner">Miner</option><option value="noble">Noble</option><option value="outlaw">Outlaw</option><option value="pedlar">Pedlar</option><option value="raconteur">Raconteur</option><option value="rat_catcher">Rat Catcher</option><option value="road_warden">Road Warden</option><option value="soldier">Soldier</option><option value="thief">Thief</option><option value="tomb_robber">Tomb Robber</option><option value="wizard's_apprentice">Wizard's Apprentice</option><option value="docker">Docker</option><option value="fish_warden">Fish Warden</option><option value="mudlark">Mudlark</option><option value="night_watchman">Night Watchman</option><option value="publican">Publican</option><option value="servant">Servant</option></optgroup></select>
|
|
<select name="attr_career" class="career goblin"><option value="none">-</option><optgroup label="Racial"><option value="hedge_wizard">Hedge Wizard</option><option value="goblin_pedlar">Goblin Pedlar</option><option value="guttersnipe">Guttersnipe</option><option value="mushroom_farmer">Mushroom Farmer</option><option value="spider_rider">Spider Rider</option><option value="tinkerer">Tinkerer</option></optgroup><optgroup label="Generic"><option value="agitator">Agitator</option><option value="beggar">Beggar</option><option value="boatman">Boatman</option><option value="bodyguard">Bodyguard</option><option value="bounty_hunter">Bounty Hunter</option><option value="entertainer">Entertainer</option><option value="footpad">Footpad</option><option value="gambler">Gambler</option><option value="grave_robber">Grave Robber</option><option value="hunter">Hunter</option><option value="initiate">Initiate</option><option value="mercenary">Mercenary</option><option value="militiaman">Militiaman</option><option value="miner">Miner</option><option value="noble">Noble</option><option value="outlaw">Outlaw</option><option value="pedlar">Pedlar</option><option value="raconteur">Raconteur</option><option value="rat_catcher">Rat Catcher</option><option value="road_warden">Road Warden</option><option value="soldier">Soldier</option><option value="thief">Thief</option><option value="tomb_robber">Tomb Robber</option><option value="wizard's_apprentice">Wizard's Apprentice</option><option value="docker">Docker</option><option value="fish_warden">Fish Warden</option><option value="mudlark">Mudlark</option><option value="night_watchman">Night Watchman</option><option value="publican">Publican</option><option value="servant">Servant</option></optgroup></select>
|
|
|
|
</div>
|
|
<!-- ADVANCED CAREER SELECT -->
|
|
<div class="advanced-career-select">
|
|
<input type="hidden" name="attr_community" value="none">
|
|
<label>Advanced Career: </label>
|
|
<select name="attr_advanced_career" class="career blank">
|
|
<option value="none">-</option>
|
|
</select>
|
|
<!-- ADVANCED GENERATED CAREERS BY RACE -->
|
|
<select name="attr_advanced_career" class="career human"><option value="none">-</option><option value="assassin">Assassin</option><option value="bravo">Bravo</option><option value="watch_captain">Watch Captain</option><option value="charlatan">Charlatan</option><option value="explorer">Explorer</option><option value="freelance">Freelance</option><option value="highwayman">Highwayman</option><option value="mercenary_captain">Mercenary Captain</option><option value="merchant">Merchant</option><option value="minstrel">Minstrel</option><option value="outlaw_chief">Outlaw Chief</option><option value="priest">Priest</option><option value="scholar">Scholar</option><option value="scout">Scout</option><option value="spy">Spy</option><option value="veteran_soldier">Veteran Soldier</option><option value="wizard">Wizard</option></select>
|
|
<select name="attr_advanced_career" class="career dwarf"><option value="none">-</option><optgroup label="Racial"><option value="dwarf_battlesmith">Dwarf Battlesmith</option><option value="dwarf_slayer">Dwarf Slayer</option><option value="dwarf_runeforger">Dwarf Runeforger</option></optgroup><optgroup label="Generic"><option value="assassin">Assassin</option><option value="bravo">Bravo</option><option value="watch_captain">Watch Captain</option><option value="charlatan">Charlatan</option><option value="explorer">Explorer</option><option value="freelance">Freelance</option><option value="highwayman">Highwayman</option><option value="mercenary_captain">Mercenary Captain</option><option value="merchant">Merchant</option><option value="minstrel">Minstrel</option><option value="outlaw_chief">Outlaw Chief</option><option value="priest">Priest</option><option value="scholar">Scholar</option><option value="scout">Scout</option><option value="spy">Spy</option><option value="veteran_soldier">Veteran Soldier</option><option value="wizard">Wizard</option></optgroup></select>
|
|
<select name="attr_advanced_career" class="career elf"><option value="none">-</option><optgroup label="Racial"><option value="elf_agent">Elf Agent</option><option value="elf_champion">Elf Champion</option><option value="elf_druid">Elf Druid</option></optgroup><optgroup label="Generic"><option value="assassin">Assassin</option><option value="bravo">Bravo</option><option value="watch_captain">Watch Captain</option><option value="charlatan">Charlatan</option><option value="explorer">Explorer</option><option value="freelance">Freelance</option><option value="highwayman">Highwayman</option><option value="mercenary_captain">Mercenary Captain</option><option value="merchant">Merchant</option><option value="minstrel">Minstrel</option><option value="outlaw_chief">Outlaw Chief</option><option value="priest">Priest</option><option value="scholar">Scholar</option><option value="scout">Scout</option><option value="spy">Spy</option><option value="veteran_soldier">Veteran Soldier</option><option value="wizard">Wizard</option></optgroup></select>
|
|
<select name="attr_advanced_career" class="career halfling"><option value="none">-</option><optgroup label="Racial"><option value="halfling_burglar">Halfling Burglar</option><option value="halfling_gaffer">Halfling Gaffer</option><option value="halfling_conjurer">Halfling Conjurer</option></optgroup><optgroup label="Generic"><option value="assassin">Assassin</option><option value="bravo">Bravo</option><option value="watch_captain">Watch Captain</option><option value="charlatan">Charlatan</option><option value="explorer">Explorer</option><option value="freelance">Freelance</option><option value="highwayman">Highwayman</option><option value="mercenary_captain">Mercenary Captain</option><option value="merchant">Merchant</option><option value="minstrel">Minstrel</option><option value="outlaw_chief">Outlaw Chief</option><option value="priest">Priest</option><option value="scholar">Scholar</option><option value="scout">Scout</option><option value="spy">Spy</option><option value="veteran_soldier">Veteran Soldier</option><option value="wizard">Wizard</option></optgroup></select>
|
|
<select name="attr_advanced_career" class="career goblin"><option value="none">-</option><optgroup label="Racial"><option value="emissary">Emissary</option><option value="spelunker">Spelunker</option><option value="sporceror">Sporceror</option></optgroup><optgroup label="Generic"><option value="assassin">Assassin</option><option value="bravo">Bravo</option><option value="watch_captain">Watch Captain</option><option value="charlatan">Charlatan</option><option value="explorer">Explorer</option><option value="freelance">Freelance</option><option value="highwayman">Highwayman</option><option value="mercenary_captain">Mercenary Captain</option><option value="merchant">Merchant</option><option value="minstrel">Minstrel</option><option value="outlaw_chief">Outlaw Chief</option><option value="priest">Priest</option><option value="scholar">Scholar</option><option value="scout">Scout</option><option value="spy">Spy</option><option value="veteran_soldier">Veteran Soldier</option><option value="wizard">Wizard</option></optgroup></select>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="attribute input career">
|
|
<label>Career: </label>
|
|
<input type="text" name="attr_custom_career" value=""/>
|
|
</div>
|
|
<div class="attribute button">
|
|
<button type="action" class="pictos" name="act_retire" title="Retire Career">}</button>
|
|
<button type="action" name="act_undo-retire" class="pictos" title="Undo Retire Career">D</button>
|
|
<input type="hidden" name="attr_career_toggle"/>
|
|
<button type="action" name="act_career-toggle" class="career-toggle" title="Toggle Advanced Careers">ADV</button>
|
|
</div>
|
|
<div class="attribute span">
|
|
<label>Past Careers: </label>
|
|
<span name="attr_past_careers" placeholder=""></span>
|
|
</div>
|
|
</div>
|
|
<div class="war-group background">
|
|
<div class="attribute textarea">
|
|
<label>Background: </label><textarea name="attr_background"></textarea>
|
|
</div>
|
|
<div class="attribute textarea">
|
|
<label>Traits: </label><textarea name="attr_traits"rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<!-- BASE STATS -->
|
|
<div class="war-group stats">
|
|
<input type="hidden" name="attr_stats_options-flag" value="0"/>
|
|
<div class="attribute input max">
|
|
<button type="action" name="act_rest" class="stats-btn" title="Rest to recover Stamina">
|
|
<i class="ro health"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/health.svg"/></i>
|
|
</button>
|
|
<label>Stamina: </label>
|
|
<input type="hidden" name="attr_stamina_base" value="0"/>
|
|
<input type="hidden" name="attr_stamina_bonus" value="0"/>
|
|
<input type="number" name="attr_stamina" value="0"/>
|
|
<span>/</span>
|
|
<span name="attr_stamina_max" class="input-like" value="0"></span>
|
|
<button type="action" name="act_roll_stamina" class="dicefontd6 btn">k</button>
|
|
<input type="hidden" name="attr_store_stamina"/>
|
|
</div>
|
|
<div class="foldable stats-options stamina">
|
|
<label for="stamina_base">Stamina base:</label>
|
|
<input type="number" name="attr_stamina_base" min="0" value="0"/>
|
|
<span>+</span>
|
|
<div class="stamina-up">
|
|
<input type="hidden" name="attr_unlocked">
|
|
<input type="number" name="attr_stamina_increase" class="stamina-up custom" title="Increase due to Career Leveling"/>
|
|
<span name="attr_stamina_increase" class="input-like stamina-up auto" title="Increase due to Career Leveling"></span>
|
|
</div>
|
|
<span>+</span>
|
|
<span name="attr_stamina_bonus" class="input-like" title="Bonus from Items/Effects"></span>
|
|
<button type="action" name="act_apply_stamina" class="nodisplay"></button>
|
|
</div>
|
|
<div class="attribute input max">
|
|
<input type="hidden" name="attr_luckroll_bonus" value="0"/>
|
|
<button type="action" name="act_luckroll" class="stats-btn" title="Test your Luck">
|
|
<i class="ro clover"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/clover.svg"/></i>
|
|
</button>
|
|
<label>Luck: </label>
|
|
<input type="hidden" name="attr_luck_base" value="0">
|
|
<input type="hidden" name="attr_luck_bonus" value="0"/>
|
|
<input type="number" name="attr_luck" min="0" value="0"/>
|
|
<span>/</span>
|
|
<span name="attr_luck_max" class="input-like" value="0"></span>
|
|
<button type="action" name="act_roll_luck" class="dicefontd6 btn">h</button>
|
|
<input type="hidden" name="attr_store_luck"/>
|
|
</div>
|
|
<div class="foldable stats-options">
|
|
<label for="luck_base">Luck base:</label>
|
|
<input type="number" name="attr_luck_base" min="0" value="0"/>
|
|
<span>+</span>
|
|
<span name="attr_luck_bonus" class="input-like" title="Bonus from Items/Effects"></span>
|
|
<button type="action" name="act_apply_luck" class="nodisplay"></button>
|
|
</div>
|
|
<button type="action" name="act_stats_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<!-- START OF SKILLS LIST - USE GENERATORS.JS TO MAKE CHANGES -->
|
|
<div class="war-group adventuring-skills skills-left">
|
|
<div class="group-label">
|
|
<span>Adventuring Skills:</span>
|
|
</div>
|
|
<div class="skills-container header">
|
|
<div class="attribute table-header">
|
|
<span>Skill</span>
|
|
<span>Level</span>
|
|
<span>Max</span>
|
|
</div>
|
|
</div>
|
|
<div class="skills-container">
|
|
<!-- SKILLS LIST LEFT SIDE - GENERATED -->
|
|
<div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_appraise_custom_prof" value="2"/><input type="hidden" name="attr_appraise_prof"/><button type="action" name="act_appraise" tabindex="-1">Appraise</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_appraise_overmax" value="0"><input type="hidden" name="attr_appraise_boosted" value="0"/><input type="hidden" name="attr_appraise_max" value="6"/><input type="hidden" name="attr_appraise_bonus" value="0"/><input type="number" name="attr_appraise" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_appraise_bonus_display" value="0"></span>)</div><input type="number" name="attr_appraise_max" class="skill-limit no-spinner" value="6"/><span name="attr_appraise_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_athletics_custom_prof" value="2"/><input type="hidden" name="attr_athletics_prof"/><button type="action" name="act_athletics" tabindex="-1">Athletics</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_athletics_overmax" value="0"><input type="hidden" name="attr_athletics_boosted" value="0"/><input type="hidden" name="attr_athletics_max" value="6"/><input type="hidden" name="attr_athletics_bonus" value="0"/><input type="number" name="attr_athletics" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_athletics_bonus_display" value="0"></span>)</div><input type="number" name="attr_athletics_max" class="skill-limit no-spinner" value="6"/><span name="attr_athletics_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_bargain_custom_prof" value="2"/><input type="hidden" name="attr_bargain_prof"/><button type="action" name="act_bargain" tabindex="-1">Bargain</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_bargain_overmax" value="0"><input type="hidden" name="attr_bargain_boosted" value="0"/><input type="hidden" name="attr_bargain_max" value="6"/><input type="hidden" name="attr_bargain_bonus" value="0"/><input type="number" name="attr_bargain" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_bargain_bonus_display" value="0"></span>)</div><input type="number" name="attr_bargain_max" class="skill-limit no-spinner" value="6"/><span name="attr_bargain_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_blunt_custom_prof" value="2"/><input type="hidden" name="attr_blunt_prof"/><button type="action" name="act_blunt" tabindex="-1">Blunt</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_blunt_overmax" value="0"><input type="hidden" name="attr_blunt_boosted" value="0"/><input type="hidden" name="attr_blunt_max" value="6"/><input type="hidden" name="attr_blunt_bonus" value="0"/><input type="number" name="attr_blunt" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_blunt_bonus_display" value="0"></span>)</div><input type="number" name="attr_blunt_max" class="skill-limit no-spinner" value="6"/><span name="attr_blunt_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_bow_custom_prof" value="2"/><input type="hidden" name="attr_bow_prof"/><button type="action" name="act_bow" tabindex="-1">Bow</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_bow_overmax" value="0"><input type="hidden" name="attr_bow_boosted" value="0"/><input type="hidden" name="attr_bow_max" value="6"/><input type="hidden" name="attr_bow_bonus" value="0"/><input type="number" name="attr_bow" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_bow_bonus_display" value="0"></span>)</div><input type="number" name="attr_bow_max" class="skill-limit no-spinner" value="6"/><span name="attr_bow_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_brawling_custom_prof" value="2"/><input type="hidden" name="attr_brawling_prof"/><button type="action" name="act_brawling" tabindex="-1">Brawling</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_brawling_overmax" value="0"><input type="hidden" name="attr_brawling_boosted" value="0"/><input type="hidden" name="attr_brawling_max" value="6"/><input type="hidden" name="attr_brawling_bonus" value="0"/><input type="number" name="attr_brawling" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_brawling_bonus_display" value="0"></span>)</div><input type="number" name="attr_brawling_max" class="skill-limit no-spinner" value="6"/><span name="attr_brawling_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_command_custom_prof" value="2"/><input type="hidden" name="attr_command_prof"/><button type="action" name="act_command" tabindex="-1">Command</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_command_overmax" value="0"><input type="hidden" name="attr_command_boosted" value="0"/><input type="hidden" name="attr_command_max" value="6"/><input type="hidden" name="attr_command_bonus" value="0"/><input type="number" name="attr_command" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_command_bonus_display" value="0"></span>)</div><input type="number" name="attr_command_max" class="skill-limit no-spinner" value="6"/><span name="attr_command_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_crossbow_custom_prof" value="2"/><input type="hidden" name="attr_crossbow_prof"/><button type="action" name="act_crossbow" tabindex="-1">Crossbow</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_crossbow_overmax" value="0"><input type="hidden" name="attr_crossbow_boosted" value="0"/><input type="hidden" name="attr_crossbow_max" value="6"/><input type="hidden" name="attr_crossbow_bonus" value="0"/><input type="number" name="attr_crossbow" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_crossbow_bonus_display" value="0"></span>)</div><input type="number" name="attr_crossbow_max" class="skill-limit no-spinner" value="6"/><span name="attr_crossbow_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_diplomacy_custom_prof" value="2"/><input type="hidden" name="attr_diplomacy_prof"/><button type="action" name="act_diplomacy" tabindex="-1">Diplomacy</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_diplomacy_overmax" value="0"><input type="hidden" name="attr_diplomacy_boosted" value="0"/><input type="hidden" name="attr_diplomacy_max" value="6"/><input type="hidden" name="attr_diplomacy_bonus" value="0"/><input type="number" name="attr_diplomacy" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_diplomacy_bonus_display" value="0"></span>)</div><input type="number" name="attr_diplomacy_max" class="skill-limit no-spinner" value="6"/><span name="attr_diplomacy_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_disguise_custom_prof" value="2"/><input type="hidden" name="attr_disguise_prof"/><button type="action" name="act_disguise" tabindex="-1">Disguise</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_disguise_overmax" value="0"><input type="hidden" name="attr_disguise_boosted" value="0"/><input type="hidden" name="attr_disguise_max" value="6"/><input type="hidden" name="attr_disguise_bonus" value="0"/><input type="number" name="attr_disguise" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_disguise_bonus_display" value="0"></span>)</div><input type="number" name="attr_disguise_max" class="skill-limit no-spinner" value="6"/><span name="attr_disguise_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_dodge_custom_prof" value="2"/><input type="hidden" name="attr_dodge_prof"/><button type="action" name="act_dodge" tabindex="-1">Dodge</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_dodge_overmax" value="0"><input type="hidden" name="attr_dodge_boosted" value="0"/><input type="hidden" name="attr_dodge_max" value="6"/><input type="hidden" name="attr_dodge_bonus" value="0"/><input type="number" name="attr_dodge" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_dodge_bonus_display" value="0"></span>)</div><input type="number" name="attr_dodge_max" class="skill-limit no-spinner" value="6"/><span name="attr_dodge_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_endurance_custom_prof" value="2"/><input type="hidden" name="attr_endurance_prof"/><button type="action" name="act_endurance" tabindex="-1">Endurance</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_endurance_overmax" value="0"><input type="hidden" name="attr_endurance_boosted" value="0"/><input type="hidden" name="attr_endurance_max" value="6"/><input type="hidden" name="attr_endurance_bonus" value="0"/><input type="number" name="attr_endurance" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_endurance_bonus_display" value="0"></span>)</div><input type="number" name="attr_endurance_max" class="skill-limit no-spinner" value="6"/><span name="attr_endurance_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_history_custom_prof" value="2"/><input type="hidden" name="attr_history_prof"/><button type="action" name="act_history" tabindex="-1">History</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_history_overmax" value="0"><input type="hidden" name="attr_history_boosted" value="0"/><input type="hidden" name="attr_history_max" value="6"/><input type="hidden" name="attr_history_bonus" value="0"/><input type="number" name="attr_history" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_history_bonus_display" value="0"></span>)</div><input type="number" name="attr_history_max" class="skill-limit no-spinner" value="6"/><span name="attr_history_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_incantation_custom_prof" value="2"/><input type="hidden" name="attr_incantation_prof"/><button type="action" name="act_incantation" tabindex="-1">Incantation</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_incantation_overmax" value="0"><input type="hidden" name="attr_incantation_boosted" value="0"/><input type="hidden" name="attr_incantation_max" value="6"/><input type="hidden" name="attr_incantation_bonus" value="0"/><input type="number" name="attr_incantation" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_incantation_bonus_display" value="0"></span>)</div><input type="number" name="attr_incantation_max" class="skill-limit no-spinner" value="6"/><span name="attr_incantation_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_intimidate_custom_prof" value="2"/><input type="hidden" name="attr_intimidate_prof"/><button type="action" name="act_intimidate" tabindex="-1">Intimidate</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_intimidate_overmax" value="0"><input type="hidden" name="attr_intimidate_boosted" value="0"/><input type="hidden" name="attr_intimidate_max" value="6"/><input type="hidden" name="attr_intimidate_bonus" value="0"/><input type="number" name="attr_intimidate" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_intimidate_bonus_display" value="0"></span>)</div><input type="number" name="attr_intimidate_max" class="skill-limit no-spinner" value="6"/><span name="attr_intimidate_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_language_custom_prof" value="2"/><input type="hidden" name="attr_language_prof"/><button type="action" name="act_language" tabindex="-1">Language</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_language_overmax" value="0"><input type="hidden" name="attr_language_boosted" value="0"/><input type="hidden" name="attr_language_max" value="6"/><input type="hidden" name="attr_language_bonus" value="0"/><input type="number" name="attr_language" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_language_bonus_display" value="0"></span>)</div><input type="number" name="attr_language_max" class="skill-limit no-spinner" value="6"/><span name="attr_language_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div>
|
|
<!-- END OF GENERATED -->
|
|
</div>
|
|
</div>
|
|
<div class="war-group adventuring-skills skills-right">
|
|
<!-- TUTORIAL TIPS SECTION -->
|
|
<div class="group-label">
|
|
<input type="hidden" name="attr_disable_tutorial" value="0">
|
|
<input type="hidden" name="attr_tutorial_flag" value="1">
|
|
<div class="initial-skillpoints phase1 tutorial">
|
|
<span>Remaining Skill Increases</span>
|
|
<br><span>Level 5:</span>
|
|
<span name="attr_initial_fives" value="10"></span><span>/</span><span>10</span>
|
|
<span>Level 6:</span>
|
|
<span name="attr_initial_sixes" value="10"></span><span>/</span><span>10</span>
|
|
</div>
|
|
<div class="initial-skillpoints phase2 tutorial">
|
|
<span>Please select a Community and Career</span>
|
|
</div>
|
|
<div class="initial-skillpoints phase3 tutorial">
|
|
<span>Career Skillpoints remaining:</span>
|
|
<span name="attr_initial_skillpoints" value="10"></span><span>/</span><span>10</span>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="attribute table-header">
|
|
<span>Skill</span>
|
|
<span>Level</span>
|
|
<span>Max</span>
|
|
</div>
|
|
<div class="skills-container">
|
|
<!-- SKILLS LIST RIGHT SIDE - GENERATED -->
|
|
<div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_large_blade_custom_prof" value="2"/><input type="hidden" name="attr_large_blade_prof"/><button type="action" name="act_large_blade" tabindex="-1">Large Blade</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_large_blade_overmax" value="0"><input type="hidden" name="attr_large_blade_boosted" value="0"/><input type="hidden" name="attr_large_blade_max" value="6"/><input type="hidden" name="attr_large_blade_bonus" value="0"/><input type="number" name="attr_large_blade" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_large_blade_bonus_display" value="0"></span>)</div><input type="number" name="attr_large_blade_max" class="skill-limit no-spinner" value="6"/><span name="attr_large_blade_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_lie_custom_prof" value="2"/><input type="hidden" name="attr_lie_prof"/><button type="action" name="act_lie" tabindex="-1">Lie</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_lie_overmax" value="0"><input type="hidden" name="attr_lie_boosted" value="0"/><input type="hidden" name="attr_lie_max" value="6"/><input type="hidden" name="attr_lie_bonus" value="0"/><input type="number" name="attr_lie" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_lie_bonus_display" value="0"></span>)</div><input type="number" name="attr_lie_max" class="skill-limit no-spinner" value="6"/><span name="attr_lie_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_medicine_custom_prof" value="2"/><input type="hidden" name="attr_medicine_prof"/><button type="action" name="act_medicine" tabindex="-1">Medicine</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_medicine_overmax" value="0"><input type="hidden" name="attr_medicine_boosted" value="0"/><input type="hidden" name="attr_medicine_max" value="6"/><input type="hidden" name="attr_medicine_bonus" value="0"/><input type="number" name="attr_medicine" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_medicine_bonus_display" value="0"></span>)</div><input type="number" name="attr_medicine_max" class="skill-limit no-spinner" value="6"/><span name="attr_medicine_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_navigation_custom_prof" value="2"/><input type="hidden" name="attr_navigation_prof"/><button type="action" name="act_navigation" tabindex="-1">Navigation</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_navigation_overmax" value="0"><input type="hidden" name="attr_navigation_boosted" value="0"/><input type="hidden" name="attr_navigation_max" value="6"/><input type="hidden" name="attr_navigation_bonus" value="0"/><input type="number" name="attr_navigation" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_navigation_bonus_display" value="0"></span>)</div><input type="number" name="attr_navigation_max" class="skill-limit no-spinner" value="6"/><span name="attr_navigation_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_ostler_custom_prof" value="2"/><input type="hidden" name="attr_ostler_prof"/><button type="action" name="act_ostler" tabindex="-1">Ostler</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_ostler_overmax" value="0"><input type="hidden" name="attr_ostler_boosted" value="0"/><input type="hidden" name="attr_ostler_max" value="6"/><input type="hidden" name="attr_ostler_bonus" value="0"/><input type="number" name="attr_ostler" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_ostler_bonus_display" value="0"></span>)</div><input type="number" name="attr_ostler_max" class="skill-limit no-spinner" value="6"/><span name="attr_ostler_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_persuasion_custom_prof" value="2"/><input type="hidden" name="attr_persuasion_prof"/><button type="action" name="act_persuasion" tabindex="-1">Persuasion</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_persuasion_overmax" value="0"><input type="hidden" name="attr_persuasion_boosted" value="0"/><input type="hidden" name="attr_persuasion_max" value="6"/><input type="hidden" name="attr_persuasion_bonus" value="0"/><input type="number" name="attr_persuasion" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_persuasion_bonus_display" value="0"></span>)</div><input type="number" name="attr_persuasion_max" class="skill-limit no-spinner" value="6"/><span name="attr_persuasion_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_pole_arm_custom_prof" value="2"/><input type="hidden" name="attr_pole_arm_prof"/><button type="action" name="act_pole_arm" tabindex="-1">Pole Arm</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_pole_arm_overmax" value="0"><input type="hidden" name="attr_pole_arm_boosted" value="0"/><input type="hidden" name="attr_pole_arm_max" value="6"/><input type="hidden" name="attr_pole_arm_bonus" value="0"/><input type="number" name="attr_pole_arm" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_pole_arm_bonus_display" value="0"></span>)</div><input type="number" name="attr_pole_arm_max" class="skill-limit no-spinner" value="6"/><span name="attr_pole_arm_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_repair_custom_prof" value="2"/><input type="hidden" name="attr_repair_prof"/><button type="action" name="act_repair" tabindex="-1">Repair</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_repair_overmax" value="0"><input type="hidden" name="attr_repair_boosted" value="0"/><input type="hidden" name="attr_repair_max" value="6"/><input type="hidden" name="attr_repair_bonus" value="0"/><input type="number" name="attr_repair" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_repair_bonus_display" value="0"></span>)</div><input type="number" name="attr_repair_max" class="skill-limit no-spinner" value="6"/><span name="attr_repair_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_sleight_of_hand_custom_prof" value="2"/><input type="hidden" name="attr_sleight_of_hand_prof"/><button type="action" name="act_sleight_of_hand" tabindex="-1">Sleight Of Hand</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_sleight_of_hand_overmax" value="0"><input type="hidden" name="attr_sleight_of_hand_boosted" value="0"/><input type="hidden" name="attr_sleight_of_hand_max" value="6"/><input type="hidden" name="attr_sleight_of_hand_bonus" value="0"/><input type="number" name="attr_sleight_of_hand" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_sleight_of_hand_bonus_display" value="0"></span>)</div><input type="number" name="attr_sleight_of_hand_max" class="skill-limit no-spinner" value="6"/><span name="attr_sleight_of_hand_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_small_blade_custom_prof" value="2"/><input type="hidden" name="attr_small_blade_prof"/><button type="action" name="act_small_blade" tabindex="-1">Small Blade</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_small_blade_overmax" value="0"><input type="hidden" name="attr_small_blade_boosted" value="0"/><input type="hidden" name="attr_small_blade_max" value="6"/><input type="hidden" name="attr_small_blade_bonus" value="0"/><input type="number" name="attr_small_blade" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_small_blade_bonus_display" value="0"></span>)</div><input type="number" name="attr_small_blade_max" class="skill-limit no-spinner" value="6"/><span name="attr_small_blade_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_spot_custom_prof" value="2"/><input type="hidden" name="attr_spot_prof"/><button type="action" name="act_spot" tabindex="-1">Spot</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_spot_overmax" value="0"><input type="hidden" name="attr_spot_boosted" value="0"/><input type="hidden" name="attr_spot_max" value="6"/><input type="hidden" name="attr_spot_bonus" value="0"/><input type="number" name="attr_spot" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_spot_bonus_display" value="0"></span>)</div><input type="number" name="attr_spot_max" class="skill-limit no-spinner" value="6"/><span name="attr_spot_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_stealth_custom_prof" value="2"/><input type="hidden" name="attr_stealth_prof"/><button type="action" name="act_stealth" tabindex="-1">Stealth</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_stealth_overmax" value="0"><input type="hidden" name="attr_stealth_boosted" value="0"/><input type="hidden" name="attr_stealth_max" value="6"/><input type="hidden" name="attr_stealth_bonus" value="0"/><input type="number" name="attr_stealth" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_stealth_bonus_display" value="0"></span>)</div><input type="number" name="attr_stealth_max" class="skill-limit no-spinner" value="6"/><span name="attr_stealth_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_streetwise_custom_prof" value="2"/><input type="hidden" name="attr_streetwise_prof"/><button type="action" name="act_streetwise" tabindex="-1">Streetwise</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_streetwise_overmax" value="0"><input type="hidden" name="attr_streetwise_boosted" value="0"/><input type="hidden" name="attr_streetwise_max" value="6"/><input type="hidden" name="attr_streetwise_bonus" value="0"/><input type="number" name="attr_streetwise" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_streetwise_bonus_display" value="0"></span>)</div><input type="number" name="attr_streetwise_max" class="skill-limit no-spinner" value="6"/><span name="attr_streetwise_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_survival_custom_prof" value="2"/><input type="hidden" name="attr_survival_prof"/><button type="action" name="act_survival" tabindex="-1">Survival</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_survival_overmax" value="0"><input type="hidden" name="attr_survival_boosted" value="0"/><input type="hidden" name="attr_survival_max" value="6"/><input type="hidden" name="attr_survival_bonus" value="0"/><input type="number" name="attr_survival" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_survival_bonus_display" value="0"></span>)</div><input type="number" name="attr_survival_max" class="skill-limit no-spinner" value="6"/><span name="attr_survival_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_swimming_custom_prof" value="2"/><input type="hidden" name="attr_swimming_prof"/><button type="action" name="act_swimming" tabindex="-1">Swimming</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_swimming_overmax" value="0"><input type="hidden" name="attr_swimming_boosted" value="0"/><input type="hidden" name="attr_swimming_max" value="6"/><input type="hidden" name="attr_swimming_bonus" value="0"/><input type="number" name="attr_swimming" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_swimming_bonus_display" value="0"></span>)</div><input type="number" name="attr_swimming_max" class="skill-limit no-spinner" value="6"/><span name="attr_swimming_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div><div class="skill-item"><input type="hidden" name="attr_unlocked"/><input type="checkbox" name="attr_thrown_custom_prof" value="2"/><input type="hidden" name="attr_thrown_prof"/><button type="action" name="act_thrown" tabindex="-1">Thrown</button><span class="pictos skill-flag" title="This is a current Career Skill">^</span><input type="hidden" name="attr_thrown_overmax" value="0"><input type="hidden" name="attr_thrown_boosted" value="0"/><input type="hidden" name="attr_thrown_max" value="6"/><input type="hidden" name="attr_thrown_bonus" value="0"/><input type="number" name="attr_thrown" min="0" value="4"/><div class="skill-bonus" title="Item/Effect Bonus">(<span name="attr_thrown_bonus_display" value="0"></span>)</div><input type="number" name="attr_thrown_max" class="skill-limit no-spinner" value="6"/><span name="attr_thrown_max" class="skill-limit" value="6"></span><span class="pictos skill-cmax-flag" title="This skill was raised by a Past Career">{</span></div>
|
|
<!-- END OF GENERATED -->
|
|
</div>
|
|
</div>
|
|
<div class="war-double career-skills-possessions">
|
|
<div class="war-group career-skills">
|
|
<input type="hidden" name="attr_unlocked"/>
|
|
<div class="group-label">
|
|
<span>Career Skills:</span>
|
|
</div>
|
|
<div class="career-skill table-header">
|
|
<span>Skill</span>
|
|
<span>Level</span>
|
|
</div>
|
|
<div class="skill-item repeating auto">
|
|
<button type="action" name="act_career-skill" tabindex="-1"><span name="attr_career_skill_name"></span></button>
|
|
<span name="attr_career_skill_level"></span>
|
|
</div>
|
|
<div class="skill-item repeating custom">
|
|
<button type="action" name="act_custom-career-skill" tabindex="-1"><span name="attr_custom_career"></span></button>
|
|
<input type="number" name="attr_custom_career_skill_level"/>
|
|
</div>
|
|
<fieldset class="repeating_careerskill">
|
|
<div class="skill-item repeating">
|
|
<button type="action" name="act_past-skill" tabindex="-1"><span name="attr_name"></span></button>
|
|
<span name="attr_level"></span>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<div class="war-group possessions">
|
|
<!-- Put pre-built item selector here ??? -->
|
|
<div class="group-label">
|
|
<span>Possessions:</span>
|
|
</div>
|
|
<div class="possession table-header">
|
|
<span>Qty</span>
|
|
<span>Item</span>
|
|
<span class="war-hover-text items">Type</span>
|
|
<span>Equipped</span>
|
|
<div class="war-tt item-types">
|
|
<span><u>Type Icons:</u>
|
|
<i class="armour ro helmet"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/helmet.svg"/></i>: Armour
|
|
<i class="shield ro round-shield"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/round-shield.svg"/></i>: Shield
|
|
<i class="stats ro gem-pendant"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/gem-pendant.svg"/></i>: Stats
|
|
<i class="weapon ro broadsword"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/broadsword.svg"/></i>: Weapon
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<fieldset class="repeating_possession">
|
|
<div class="maingrid">
|
|
<input type="hidden" name="attr_options-flag" value="0"/>
|
|
<input type="hidden" name="attr_weapon_link" value=""/>
|
|
<input type="number" name="attr_quantity" class="no-spinner" placeholder="1" value="1"/>
|
|
<input type="text" name="attr_name" placeholder="Item name..."/>
|
|
<div class="itemflags">
|
|
<input type="hidden" name="attr_flags"/>
|
|
<!-- FLAGS FOR ITEM TYPE -->
|
|
<i class="armour ro helmet"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/helmet.svg"/></i>
|
|
<i class="shield ro round-shield"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/round-shield.svg"/></i>
|
|
<i class="stats ro gem-pendant"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/gem-pendant.svg"/></i>
|
|
<i class="weapon ro broadsword"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/broadsword.svg"/></i>
|
|
</div>
|
|
<input type="checkbox" name="attr_equipped" value="1"/>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<!-- FOLDS DOWN -->
|
|
<input type="hidden" name="attr_options-flag" value="0"/>
|
|
<div class="foldable possession-options">
|
|
<div class="repeating option">
|
|
<label>Item Mods:</label>
|
|
<textarea name="attr_itemmods" placeholder="Item properties..." value=""></textarea>
|
|
<span class="war-hover item-mods pictos">?</span>
|
|
<div class="war-tt item-mods">
|
|
<span><b><u>Valid Item Categories + Mods</u></b>
|
|
<b>weapon:</b> base | damage | skill | ranged | threat | dtype;
|
|
<b>armour:</b> light | modest | heavy | +X | +XdY;
|
|
<b>shield:</b> small | large | +X | +XdY;
|
|
<b>stats:</b> ['skill name' | stamina | luck | luckroll | attack | damage] +X | +XdY;
|
|
|
|
<u>Notes:</u>
|
|
- Categories must be followed by a colon ":"
|
|
- Separate multiple Categories with semi-colon ";"
|
|
- Separate Mod entries within the same Category with comma ","
|
|
- "stats: attack" and "stats: damage" are global modifiers to all attacks
|
|
|
|
<u>- Examples:</u>
|
|
<b>Extremely Big Magic Sword -</b>
|
|
<i>weapon: type 2h sword, damage custom 4d6 + 4; stats:stamina +5, large blade +5</i>
|
|
<b>Cursed Shield -</b>
|
|
<i>shield: -5; stats: luckroll -5, damage -1d4;</i>
|
|
<b>Ring of the Quick -</b>
|
|
<i>shield: +1d6; stats: dodge +5, stealth +5, sleight of hand +5</i></span>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Description:</label><textarea name="attr_description" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<!-- END OF FOLD -->
|
|
</fieldset>
|
|
<div class="possession currency-header">
|
|
Currency
|
|
</div>
|
|
<div class="possession currency">
|
|
<label>P:<input type="number" name="attr_pennies" min="0" value="0" title="Pennies"/></label>
|
|
<button type="action" name="act_convert-pennies-silver" tabindex="-1" class="pictos" title="Convert Pennies to Silver">;</button>
|
|
<label>S:<input type="number" name="attr_silver" min="0" value="0" title="Silver Coins"/></label>
|
|
<button type="action" name="act_convert-silver-gold" tabindex="-1" class="pictos" title="Convert Silver to Gold">;</button>
|
|
<label>G:<input type="number" name="attr_gold" min="0" value="0" title="Gold Coins"/></label>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="war-triple weapons-spells-effects">
|
|
<div class="war-group weapons">
|
|
<div class="group-label">
|
|
<span>Weapons:</span>
|
|
</div>
|
|
<div class="weapon table-header">
|
|
<input type="hidden" name="attr_react"/>
|
|
<span title="Readied weapon for reactive defence">Rdy</span>
|
|
<span>Attack</span>
|
|
<span>Def</span>
|
|
<span>Damage</span>
|
|
<span class="war-hover-text weapons">Type</span>
|
|
<div class="war-tt weapon-types">
|
|
<span><u>Type Icons:</u>
|
|
<i class="crushing ro hammer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/hammer.svg"/></i>: Crushing
|
|
<i class="piercing ro trident"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/trident.svg"/></i>: Piercing
|
|
<i class="slashing ro sickle"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/sickle.svg"/></i>: Slashing
|
|
<i class="blast ro bomb-explosion"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/bomb-explosion.svg"/></i>: Blast
|
|
<i class="ranged ro archer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/archer.svg"/></i>: Ranged
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<fieldset class="repeating_weapon">
|
|
<div class="maingrid">
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<input type="hidden" name="attr_possession_link"/>
|
|
<input type="hidden" name="attr_show_readied"/>
|
|
<input type="checkbox" name="attr_readied" value="1"/>
|
|
<button type="action" name="act_rollattack"><span name="attr_name" class="weapon-button" placeholder="Weapon name..."></span></button>
|
|
<button type="action" name="act_rolldefend" title="Defend with this weapon"><span class="pictos-custom">e</span></button>
|
|
<button type="action" name="act_rolldamage"><span name="attr_damage" class="weapon-button" placeholder="Damage..."></span></button>
|
|
<div class="itemflags">
|
|
<input type="hidden" name="attr_flags"/></span>
|
|
<!-- FLAGS FOR ITEM TYPE -->
|
|
<i class="crushing ro hammer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/hammer.svg"/></i>
|
|
<i class="piercing ro trident"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/trident.svg"/></i>
|
|
<i class="slashing ro sickle"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/sickle.svg"/></i>
|
|
<i class="blast ro bomb-explosion"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/bomb-explosion.svg"/></i>
|
|
<i class="ranged ro archer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/archer.svg"/></i>
|
|
</div>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<!-- FOLDS DOWN -->
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable weapon-options">
|
|
<div class="repeating option">
|
|
<label>Weapon Name:</label><input type="text" name="attr_name" placeholder="Weapon name..."/>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Attack Skill:</label>
|
|
<div class="attack-skill-select">
|
|
<select name="attr_attack_skill">
|
|
<option value="blunt">Blunt</option>
|
|
<option value="bow">Bow</option>
|
|
<option value="brawling">Brawling</option>
|
|
<option value="crossbow">Crossbow</option>
|
|
<option value="incantation">Incantation</option>
|
|
<option value="large_blade">Large Blade</option>
|
|
<option value="pole_arm">Pole Arm</option>
|
|
<option value="small_blade">Small Blade</option>
|
|
<option value="thrown">Thrown</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
<span>+ </span><input type="text" name="attr_attack_mod" placeholder="" value="0"/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<div class="attack-damage-select">
|
|
<input type="hidden" name="attr_damage_base">
|
|
<label>Damage Base:</label>
|
|
<select name="attr_damage_base">
|
|
<option value="custom">Custom</option><option value="unarmed">Unarmed</option><option value="club">Club</option><option value="knife">Knife</option><option value="dagger">Dagger</option><option value="short_sword">Short Sword</option><option value="arming_sword">Arming Sword</option><option value="hammer">Hammer</option><option value="axe">Axe</option><option value="mace">Mace</option><option value="spear">Spear</option><option value="pole_arm">Pole Arm</option><option value="2h_sword">2h Sword</option><option value="staff">Staff</option><option value="bow">Bow</option><option value="crossbow">Crossbow</option>
|
|
</select>
|
|
<span>/ </span><input type="text" name="attr_damage_custom" value="0"/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Damage Type:</label>
|
|
<select name="attr_damage_type">
|
|
<option value="">-</option>
|
|
<option value="slashing">Slashing</option>
|
|
<option value="crushing">Crushing</option>
|
|
<option value="piercing">Piercing</option>
|
|
<option value="blast">Blast</option>
|
|
</select>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option"><label>Ranged:</label><input type="checkbox" name="attr_ranged" value="1"/></div>
|
|
<div class="sub-option"><label>Threat:</label><select name="attr_threat"><option value="">-</option><option value="casual">Casual</option><option value="martial">Martial</option></select></div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Description:</label><textarea name="attr_description" rows="1"></textarea>
|
|
</div>
|
|
<button type="action" name="act_rollcrit" style="display:none">Crit</button>
|
|
</div>
|
|
<!-- END OF FOLD -->
|
|
</fieldset>
|
|
</div>
|
|
<div class="war-group spells">
|
|
<div class="group-label">
|
|
<span>Spells:</span>
|
|
</div>
|
|
<div class="spell table-header">
|
|
<span>Spell</span>
|
|
<span>Type</span>
|
|
<span>Cost</span>
|
|
<span class="cursor-help" title="Send spell description to chat">Link</span>
|
|
</div>
|
|
<fieldset class="repeating_spell">
|
|
<div class="hidden" style="display:none">
|
|
<button type="action" name="act_rollcast"></button>
|
|
</div>
|
|
<div class="maingrid">
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<button type="action" name="act_rollspell"><span name="attr_name" class="spell-button" value="New Spell"></span></button>
|
|
<span name="attr_type" placeholder="Damage..."></span>
|
|
<span name="attr_cost" placeholder="Type..."></span>
|
|
<button type="action" name="attr_link"><i class="ro bubble"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/speech-bubble.svg"/></i></button>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<!-- FOLDS DOWN -->
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable spell-options">
|
|
<input type="hidden" name="attr_type"/>
|
|
<div class="repeating option">
|
|
<label>Spell Name:</label><input type="text" name="attr_name" placeholder="Spell Name..."/>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option">
|
|
<label>Type: </label>
|
|
<select name="attr_type" value="Spellcard">
|
|
<option value="Attack">Attack</option>
|
|
<option value="Spellcard" selected>Spellcard</option>
|
|
</select>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Cost:</label>
|
|
<input type="number" name="attr_cost" max="99"/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option sub attack-only">
|
|
<div class="sub-option">
|
|
<label>Damage: </label>
|
|
<input type="text" name="attr_damage" value="1d6"/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Type:</label>
|
|
<select name="attr_damage_type" value="blast">
|
|
<option value="">-</option>
|
|
<option value="slashing">Slashing</option>
|
|
<option value="crushing">Crushing</option>
|
|
<option value="piercing">Piercing</option>
|
|
<option value="blast" selected>Blast</option>
|
|
</select>
|
|
</div>
|
|
<div class="repeating option double">
|
|
<label>Attack Skill:</label>
|
|
<div class="attack-skill-select">
|
|
<select name="attr_attack_skill">
|
|
<option value="blunt">Blunt</option>
|
|
<option value="bow">Bow</option>
|
|
<option value="brawling">Brawling</option>
|
|
<option value="crossbow">Crossbow</option>
|
|
<option value="incantation">Incantation</option>
|
|
<option value="large_blade">Large Blade</option>
|
|
<option value="pole_arm">Pole Arm</option>
|
|
<option value="small_blade">Small Blade</option>
|
|
<option value="thrown">Thrown</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
<span>+ </span><input type="text" name="attr_attack_mod" placeholder="" value="0"/>
|
|
</div>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Ranged:</label>
|
|
<input type="checkbox" name="attr_ranged" value="1" checked/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Show Description:</label>
|
|
<input type="checkbox" name="attr_show_desc" value="1" checked/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Effects:</label><textarea name="attr_mods" placeholder="Effect Mods..."></textarea>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option">
|
|
<label>Duration:</label><input type="text" name="attr_duration"/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<select name="attr_duration_type" value="rounds">
|
|
<option value="rounds" selected>Rounds</option>
|
|
<option value="minutes">Minutes</option>
|
|
<option value="hours">Hours</option>
|
|
<option value="days">Days</option>
|
|
<option value="weeks">Weeks</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Description:</label><textarea name="attr_description" rows="2" placeholder="Description..."></textarea>
|
|
</div>
|
|
</div>
|
|
<!-- END OF FOLD -->
|
|
</fieldset>
|
|
</div>
|
|
<div class="war-group effects">
|
|
<div class="group-label">Current Effects:</div>
|
|
<div class="group-label sub">Passive Effects:</div>
|
|
<div class="war-sub-group effects-passive">
|
|
<div class="effect table-header">
|
|
<span>Name</span>
|
|
<span>Details</span>
|
|
</div>
|
|
<div class="effect maingrid-like">
|
|
<div class="row-like">
|
|
<button type="action" name="act_link_armour">Armour Items</button>
|
|
<span name="attr_armour_items"></span>
|
|
</div>
|
|
<div class="row-like">
|
|
<button type="action" name="act_link_block">Shield Items</button>
|
|
<span name="attr_block_items"></span>
|
|
</div>
|
|
<div class="row-like">
|
|
<button type="action" name="act_link_globals">Globals</button>
|
|
<div>
|
|
<label>Attack:</label><span name="attr_global_attack_bonus"></span>
|
|
<label>Damage:</label><span name="attr_global_damage_bonus"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="war-sub-group effects-active">
|
|
<div class="group-label sub">Active Effects:</div>
|
|
<div class="effect table-header">
|
|
<span>Name</span>
|
|
<span class="war-hover-text effects">Type</span>
|
|
<span>Source</span>
|
|
<span>Active</span>
|
|
<div class="war-tt effect-types">
|
|
<span><u>Type Icons:</u>
|
|
<i class="armour ro helmet"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/helmet.svg"/></i>: Armour
|
|
<i class="shield ro round-shield"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/round-shield.svg"/></i>: Shield
|
|
<i class="stats ro gem-pendant"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/gem-pendant.svg"/></i>: Stats
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<fieldset class="repeating_effect">
|
|
<div class="maingrid">
|
|
<button type="action" name="act_link-effects"><span name="attr_name"></span></button>
|
|
<div class="itemflags">
|
|
<input type="hidden" name="attr_flags"/>
|
|
<!-- FLAGS FOR ITEM TYPE -->
|
|
<i class="armour ro helmet"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/helmet.svg"/></i>
|
|
<i class="shield ro round-shield"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/round-shield.svg"/></i>
|
|
<i class="stats ro gem-pendant"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/gem-pendant.svg"/></i>
|
|
<i class="weapon ro broadsword"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/broadsword.svg"/></i>
|
|
</div>
|
|
<span name="attr_source"></span>
|
|
<input type="checkbox" name="attr_active" value="1" checked/>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable effect-options">
|
|
<div class="repeating option">
|
|
<label>Name:</label><input type="text" name="attr_name" placeholder="Name..."/>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Effects:</label><textarea name="attr_mods" placeholder="Effect Mods..."></textarea>
|
|
<span class="war-hover effect-mods pictos">?</span>
|
|
<div class="war-tt effect-mods">
|
|
<span><b><u>Valid Effect Categories + Mods</u></b>
|
|
<b>armour:</b> light | modest | heavy | +X | +XdY;
|
|
<b>shield:</b> small | large | +X | +XdY;
|
|
<b>stats:</b> ['skill name' | stamina | luck | luckroll | attack | damage] +X | +XdY;
|
|
|
|
<u>Notes:</u>
|
|
- Categories must be followed by a colon ":"
|
|
- Separate multiple Categories with semi-colon ";"
|
|
- Separate Mod entries within the same Category with comma ","
|
|
- "stats: attack" and "stats: damage" are global modifiers to all attacks
|
|
|
|
<u>- Examples:</u>
|
|
<b>Armour Spell -</b>
|
|
<i>armour: heavy;</i>
|
|
<b>Fix Spell -</b>
|
|
<i>stats: attack -5</i></span>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option">
|
|
<label>Duration:</label><input type="number" name="attr_duration"/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<select name="attr_duration_type" value="rounds">
|
|
<option value="rounds" selected>Rounds</option>
|
|
<option value="minutes">Minutes</option>
|
|
<option value="hours">Hours</option>
|
|
<option value="days">Days</option>
|
|
<option value="weeks">Weeks</option>
|
|
</select>
|
|
<!-- NOT YET IMPLEMENTED
|
|
<div class="sub-sub-option">
|
|
<label title="Display an alert when the character takes an action - only works for 'Rounds' duration type">Alert: <input type="checkbox" name="alert" value="0"/></label>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Source:</label><textarea name="attr_source" placeholder="Effect Source..."></textarea>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- NPC SHEET -->
|
|
<div class="war-character-npc">
|
|
<div class="war-main">
|
|
<div class="war-double npc-basic-stats">
|
|
<div class="war-group basic-info">
|
|
<div class="attribute input">
|
|
<label>Name: </label><input type="text" name="attr_character_name"/>
|
|
</div>
|
|
<div class="attribute input max">
|
|
<label>Stamina:</label>
|
|
<input type="number" name="attr_stamina" value="0"/>
|
|
<span>/</span>
|
|
<input type="number" name="attr_stamina_max" value="0"/>
|
|
</div>
|
|
<div class="skill-item repeating custom">
|
|
<button type="action" name="act_npc_skill" tabindex="-1">Adventure Skill</button>
|
|
<input type="number" name="attr_npc_adventure_skill"/>
|
|
</div>
|
|
</div>
|
|
<div class="war-group npc-stats">
|
|
<div class="attribute input">
|
|
<label>Actions/Round:</label>
|
|
<input type="number" name="attr_npc_attacks" value="1"/>
|
|
</div>
|
|
<div class="attribute input">
|
|
<label>Armour Value: </label>
|
|
<input type="text" name="attr_npc_armour" value="0"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="war-group npc-description">
|
|
<div class="attribute input">
|
|
<label>Type:</label><input type="text" name="attr_npc_type">
|
|
</div>
|
|
<div class="attribute textarea">
|
|
<label>Description: </label><textarea name="attr_npc_description"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="war-group effects">
|
|
<div class="group-label">Current Effects:</div>
|
|
<div class="group-label sub">Passive Effects:</div>
|
|
<div class="war-sub-group effects-passive">
|
|
<div class="effect table-header">
|
|
<span>Name</span>
|
|
<span>Details</span>
|
|
</div>
|
|
<div class="effect maingrid-like">
|
|
<div class="row-like">
|
|
<button type="action" name="act_link_armour">Armour Items</button>
|
|
<span name="attr_armour_items"></span>
|
|
</div>
|
|
<div class="row-like">
|
|
<button type="action" name="act_link_block">Shield Items</button>
|
|
<span name="attr_block_items"></span>
|
|
</div>
|
|
<div class="row-like">
|
|
<button type="action" name="act_link_globals">Globals</button>
|
|
<div>
|
|
<label>Attack:</label><span name="attr_global_attack_bonus" placeholder="0" value="0"></span>
|
|
<label>Damage:</label><span name="attr_global_damage_bonus" placeholder="0" value="0"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="war-sub-group effects-active">
|
|
<div class="group-label sub">Active Effects:</div>
|
|
<div class="effect table-header">
|
|
<span>Name</span>
|
|
<span class="war-hover-text effects">Type</span>
|
|
<span>Source</span>
|
|
<span>Active</span>
|
|
<div class="war-tt effect-types">
|
|
<span><u>Type Icons:</u>
|
|
<i class="armour ro helmet"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/helmet.svg"/></i>: Armour
|
|
<i class="shield ro round-shield"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/round-shield.svg"/></i>: Shield
|
|
<i class="stats ro gem-pendant"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/gem-pendant.svg"/></i>: Stats
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<fieldset class="repeating_effect">
|
|
<div class="maingrid">
|
|
<button type="action" name="act_link-effects"><span name="attr_name"></span></button>
|
|
<div class="itemflags">
|
|
<input type="hidden" name="attr_flags"/>
|
|
<!-- FLAGS FOR ITEM TYPE -->
|
|
<i class="armour ro helmet"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/helmet.svg"/></i>
|
|
<i class="shield ro round-shield"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/round-shield.svg"/></i>
|
|
<i class="stats ro gem-pendant"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/gem-pendant.svg"/></i>
|
|
<i class="weapon ro broadsword"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/broadsword.svg"/></i>
|
|
</div>
|
|
<span name="attr_source"></span>
|
|
<input type="checkbox" name="attr_active" value="1" checked/>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable effect-options">
|
|
<div class="repeating option">
|
|
<label>Name:</label><input type="text" name="attr_name" placeholder="Name..."/>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Effects:</label><textarea name="attr_mods" placeholder="Effect Mods..."></textarea>
|
|
<span class="war-hover effect-mods pictos">?</span>
|
|
<div class="war-tt effect-mods">
|
|
<span><b><u>Valid Effect Types:</u></b>
|
|
<br>shield: large | small | [+|-] [X | XdY]
|
|
<br>stats: [adv.skill | stamina | luck | luckroll | attack | damage] [+|-] [X | XdY]
|
|
<br>armour: light | medium | heavy | [+|-] [X | XdY]
|
|
<br> <br><u>Notes:</u>
|
|
<br>- Separate Type entries with semi-colon ";"
|
|
<br>- Separate Mod entries with comma ","
|
|
<br>- Always start a mod with a Type and a ":"
|
|
<br>- Add multiple options to a mod Type with "," separator
|
|
<br>- "stats: attack" and "stats: damage" are global modifiers to all attacks
|
|
<br>- Examples:
|
|
<br>Armour Spell - <i>armour: heavy;</i>
|
|
<br>Fix Spell - <i>stats: attack -5</i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option">
|
|
<label>Duration:</label><input type="number" name="attr_duration"/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<select name="attr_duration_type" value="rounds">
|
|
<option value="rounds" selected>Rounds</option>
|
|
<option value="minutes">Minutes</option>
|
|
<option value="hours">Hours</option>
|
|
<option value="days">Days</option>
|
|
<option value="weeks">Weeks</option>
|
|
</select>
|
|
<!-- NOT YET IMPLEMENTED
|
|
<div class="sub-sub-option">
|
|
<label title="Display an alert when the character takes an action - only works for 'Rounds' duration type">Alert: <input type="checkbox" name="alert" value="0"/></label>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Source:</label><textarea name="attr_source" placeholder="Effect Source..."></textarea>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
<div class="war-triple monster_abilities-spells-weapons">
|
|
<!-- MONSTER ABILITIES REP SEC -->
|
|
<div class="war-group monster-abilities">
|
|
<div class="group-label">
|
|
<span>Monster Abilities:</span>
|
|
</div>
|
|
<div class="monster-ability wizard">
|
|
<span>Quick Add:</span>
|
|
<!-- MONSTER ABILITY ADDER -->
|
|
<select class="select-wizard" name="attr_ability_wizard_option">
|
|
<option value="brittle">Brittle</option><option value="dissolve">Dissolve</option><option value="ethereal">Ethereal</option><option value="flying">Flying</option><option value="formless">Formless</option><option value="intelligent">Intelligent</option><option value="poisonous">Poisonous</option><option value="puppet">Puppet</option><option value="regenerating">Regenerating</option><option value="spell_caster">Spell Caster</option><option value="tracking">Tracking</option>
|
|
</select>
|
|
<button type="action" name="act_create_monster_ability" class="confirm-wizard">OK</button>
|
|
</div>
|
|
<div class="monster-ability table-header">
|
|
<span>Name</span>
|
|
<span>Details</span>
|
|
</div>
|
|
<fieldset class="repeating_monster-ability">
|
|
<div class="maingrid">
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<button type="action" name="act_link-ability"><span name="attr_name" class="effect-button" placeholder="Ability name..."></span></button>
|
|
<span name="attr_details" class="monster-ability description"></span>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable ability-options">
|
|
<div class="repeating option">
|
|
<label>Ability Name:</label><input type="text" name="attr_name" placeholder="Ability name..."/>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Details:</label><textarea name="attr_details" rows="1"></textarea>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<div class="war-group weapons">
|
|
<div class="group-label">
|
|
<span>Weapons:</span>
|
|
</div>
|
|
<div class="weapon table-header">
|
|
<input type="hidden" name="attr_react"/>
|
|
<span title="Readied weapon for reactive defence">Rdy</span>
|
|
<span>Attack</span>
|
|
<span>Def</span>
|
|
<span>Damage</span>
|
|
<span class="war-hover-text weapons">Type</span>
|
|
<div class="war-tt weapon-types">
|
|
<span><u>Type Icons:</u>
|
|
<i class="crushing ro hammer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/hammer.svg"/></i>: Crushing
|
|
<i class="piercing ro trident"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/trident.svg"/></i>: Piercing
|
|
<i class="slashing ro sickle"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/sickle.svg"/></i>: Slashing
|
|
<i class="blast ro bomb-explosion"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/bomb-explosion.svg"/></i>: Blast
|
|
<i class="ranged ro archer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/archer.svg"/></i>: Ranged
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<fieldset class="repeating_weapon">
|
|
<div class="maingrid">
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<input type="hidden" name="attr_possession_link"/>
|
|
<input type="hidden" name="attr_show_readied"/>
|
|
<input type="checkbox" name="attr_readied" value="1"/>
|
|
<button type="action" name="act_rollattack"><span name="attr_name" class="weapon-button" placeholder="Weapon name..."></span></button>
|
|
<button type="action" name="act_rolldefend" title="Defend with this weapon"><span class="pictos-custom">e</span></button>
|
|
<button type="action" name="act_rolldamage"><span name="attr_damage" class="weapon-button" placeholder="Damage..."></span></button>
|
|
<div class="itemflags">
|
|
<input type="hidden" name="attr_flags"/></span>
|
|
<!-- FLAGS FOR ITEM TYPE -->
|
|
<i class="crushing ro hammer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/hammer.svg"/></i>
|
|
<i class="piercing ro trident"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/trident.svg"/></i>
|
|
<i class="slashing ro sickle"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/sickle.svg"/></i>
|
|
<i class="blast ro bomb-explosion"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/bomb-explosion.svg"/></i>
|
|
<i class="ranged ro archer"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/archer.svg"/></i>
|
|
</div>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<!-- FOLDS DOWN -->
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable weapon-options">
|
|
<div class="repeating option">
|
|
<label>Weapon Name:</label><input type="text" name="attr_name" placeholder="Weapon name..."/>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Attack Bonus:</label>
|
|
<input type="number" name="attr_attack_mod" placeholder="5" value="5"/>
|
|
</div>
|
|
<div class="repeating option">
|
|
<div class="attack-damage-select">
|
|
<label>Damage Base:</label>
|
|
<input type="hidden" name="attr_damage_base"/>
|
|
<select name="attr_damage_base" value="custom">
|
|
<option value="custom" selected>Custom</option><option value="unarmed">Unarmed</option><option value="club">Club</option><option value="knife">Knife</option><option value="dagger">Dagger</option><option value="short_sword">Short Sword</option><option value="arming_sword">Arming Sword</option><option value="hammer">Hammer</option><option value="axe">Axe</option><option value="mace">Mace</option><option value="spear">Spear</option><option value="pole_arm">Pole Arm</option><option value="2h_sword">2h Sword</option><option value="staff">Staff</option><option value="bow">Bow</option><option value="crossbow">Crossbow</option>
|
|
</select>
|
|
<span>- </span><input type="text" name="attr_damage_custom" placeholder="1d6"/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Damage Type:</label>
|
|
<select name="attr_damage_type">
|
|
<option value="">-</option>
|
|
<option value="slashing">Slashing</option>
|
|
<option value="crushing">Crushing</option>
|
|
<option value="piercing">Piercing</option>
|
|
<option value="blast">Blast</option>
|
|
</select>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option"><label>Ranged:</label><input type="checkbox" name="attr_ranged" value="1"/></div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Description:</label><textarea name="attr_description" rows="1"></textarea>
|
|
</div>
|
|
<button type="action" name="act_rollcrit" style="display:none">Crit</button>
|
|
</div>
|
|
<!-- END OF FOLD -->
|
|
</fieldset>
|
|
</div>
|
|
<div class="war-group spells">
|
|
<div class="group-label">
|
|
<span>Spells:</span>
|
|
</div>
|
|
<div class="spell table-header">
|
|
<span>Spell</span>
|
|
<span>Type</span>
|
|
<span>Cost</span>
|
|
<span class="cursor-help" title="Send spell description to chat">Link</span>
|
|
</div>
|
|
<fieldset class="repeating_spell">
|
|
<div class="hidden" style="display:none">
|
|
<button type="action" name="act_rollcast"></button>
|
|
</div>
|
|
<div class="maingrid">
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<button type="action" name="act_rollspell"><span name="attr_name" class="spell-button" value="New Spell"></span></button>
|
|
<span name="attr_type" placeholder="Damage..."></span>
|
|
<span name="attr_cost" placeholder="Type..."></span>
|
|
<button type="action" name="attr_link"><i class="ro bubble"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/speech-bubble.svg"/></i></button>
|
|
<button type="action" name="act_options-btn" class="fold-button"><i class="ro cog"><img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/icons/cog.svg"/></i></button>
|
|
</div>
|
|
<!-- FOLDS DOWN -->
|
|
<input type="hidden" name="attr_options-flag" value="1"/>
|
|
<div class="foldable spell-options">
|
|
<input type="hidden" name="attr_type"/>
|
|
<div class="repeating option">
|
|
<label>Spell Name:</label><input type="text" name="attr_name" placeholder="Spell Name..."/>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option">
|
|
<label>Type: </label>
|
|
<select name="attr_type" value="Spellcard">
|
|
<option value="Attack">Attack</option>
|
|
<option value="Spellcard" selected>Spellcard</option>
|
|
</select>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Cost:</label>
|
|
<input type="number" name="attr_cost" max="99"/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option sub attack-only">
|
|
<div class="sub-option">
|
|
<label>Damage: </label>
|
|
<input type="text" name="attr_damage" value="1d6"/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Type:</label>
|
|
<select name="attr_damage_type" value="blast">
|
|
<option value="">-</option>
|
|
<option value="slashing">Slashing</option>
|
|
<option value="crushing">Crushing</option>
|
|
<option value="piercing">Piercing</option>
|
|
<option value="blast" selected>Blast</option>
|
|
</select>
|
|
</div>
|
|
<div class="repeating option double">
|
|
<label>Attack Skill:</label>
|
|
<div class="attack-skill-select">
|
|
<select name="attr_attack_skill">
|
|
<option value="blunt">Blunt</option>
|
|
<option value="bow">Bow</option>
|
|
<option value="brawling">Brawling</option>
|
|
<option value="crossbow">Crossbow</option>
|
|
<option value="incantation">Incantation</option>
|
|
<option value="large_blade">Large Blade</option>
|
|
<option value="pole_arm">Pole Arm</option>
|
|
<option value="small_blade">Small Blade</option>
|
|
<option value="thrown">Thrown</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
<span>+ </span><input type="text" name="attr_attack_mod" placeholder="" value="0"/>
|
|
</div>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Ranged:</label>
|
|
<input type="checkbox" name="attr_ranged" value="1" checked/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<label>Show Description:</label>
|
|
<input type="checkbox" name="attr_show_desc" value="1" checked/>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Effects:</label><textarea name="attr_mods" placeholder="Effect Mods..."></textarea>
|
|
</div>
|
|
<div class="repeating option sub">
|
|
<div class="sub-option">
|
|
<label>Duration:</label><input type="text" name="attr_duration"/>
|
|
</div>
|
|
<div class="sub-option">
|
|
<select name="attr_duration_type" value="rounds">
|
|
<option value="rounds" selected>Rounds</option>
|
|
<option value="minutes">Minutes</option>
|
|
<option value="hours">Hours</option>
|
|
<option value="days">Days</option>
|
|
<option value="weeks">Weeks</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="repeating option">
|
|
<label>Description:</label><textarea name="attr_description" rows="2" placeholder="Description..."></textarea>
|
|
</div>
|
|
</div>
|
|
<!-- END OF FOLD -->
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="war-settings">
|
|
<div class="war-main">
|
|
<div class="war-group settings sheet">
|
|
<div class="settings-heading"><h1>Sheet Settings</h1></div>
|
|
<div class="settings option">
|
|
<label>Character Type: <select name="attr_npc" value="0">
|
|
<option value="0" selected>PC</option>
|
|
<option value="1">NPC</option>
|
|
</select></label>
|
|
</div>
|
|
<div class="settings option">
|
|
<label>Sheet Unlock: <input type="checkbox" name="attr_unlocked" value="1"/></label>
|
|
<span class="settings tip">Unlock the sheet to use custom races, classes and skill mods. This will disable automated career skill functions.</span>
|
|
</div>
|
|
</div>
|
|
<div class="war-group settings rolls">
|
|
<div class="settings-heading"><h1>Roll Settings</h1></div>
|
|
<div class="settings option">
|
|
<label>Reactive Rolls: <input type="checkbox" name="attr_react" value="1" checked/></label>
|
|
<span class="settings tip">Reactive Rolls uses buttons in the roll templates to quickly resolve combat actions by dynamically reacting to the outcomes of attacks and spells. See the Help documentation for more details.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="war-help">
|
|
<div class="war-main">
|
|
<div class="war-group help">
|
|
<div class="group-label">
|
|
Character Sheet for Warlock! OSR by Fire Ruby Designs:
|
|
<span>
|
|
<img src="https://raw.githubusercontent.com/ooshhub/roll20-character-sheets/WarlockOSR-newSheet/WarlockOSR/assets/images/frd_30px.png"/> <a href>https://firerubydesigns.co.uk/</a>
|
|
Credit to <a href="https://nagoshiashumari.github.io/Rpg-Awesome/">RPG-Awesome</a> for themed SVG icons.
|
|
</span>
|
|
</div>
|
|
<div class="help grid">
|
|
<label>version</label>
|
|
<span type="text" name="attr_version"></span>
|
|
</div>
|
|
<div class="help-main">
|
|
<div class="help-section">
|
|
<h3><u>Reactive Roll System:</u></h3>
|
|
<span>This leverages some new Roll20 functionality to attempt to resolve combat without characters needing to dig through their sheet.
|
|
When a player using Reactive Rolls clicks an attack, the roll template will display a button to defend the currently selected token. The defending player (or GM) will need to select the Token receiving the attack, then click on the "Defend Selected Token" button on the bottom of the roll template.
|
|
The defending player must have a weapon "Readied" in their Weapon section on the sheet, by selected one via the checkbox to the left of the Attack button. These checkboxes will only be visible on sheets with Reactive Rolls switched on, and only one weapon may be Readied at a time.
|
|
The defending character will then roll their defence. The roll will either use the Skill dictated by the Readied weapon, or Dodge if the attack was ranged or if the character has no Readied weapon.
|
|
|
|
The roll system will caclculate the winner & further required steps from the defence roll - who wins the exchange, whether anyone takes damage, and whether further actions are required.
|
|
The Spellcasting system also benefits from Reactive Rolls - after the Incantation check to cast the spell, the system will determine whether the spell has passed or failed, and whether a Wrath of the Otherworld check is required.
|
|
|
|
A successful spellcast will also prompt to apply any Spell Effects to a target. This will apply any valid Effects listed in the spell details to the Active Effects section of character sheet for the currently selected token.
|
|
|
|
The Reactive system can be turned off in the Settings tab - this will remove the extra buttons on the roll templates.</span>
|
|
</div>
|
|
<div class="help-section">
|
|
<h3><u>Unlocked Sheet:</u></h3>
|
|
<span>Unlocking the sheet via the Settings tab will allow the community, career, skills & stamina to be manually entered. This will disable the automated tracking of skill maxima, career skill value and stamina increases from career skill increases. This allows a custom Career/Community to be entered.
|
|
Item & Effect bonuses will still work, but most Skill-related functionality is disabled.</span>
|
|
</div>
|
|
<div class="help-section">
|
|
<h3><u>Careers:</u></h3>
|
|
<span>The sheet will automatically assign Skill Proficiencies & Maxima from the selected Career. Racial Careers are limited to their respective Communities. For a custom Career, the sheet will need to be Unlocked via settings, which will disable most of the functionality which follows.
|
|
Retiring a Career:
|
|
Click the down arrow icon to retire the current Career. This will snapshot the current Active Career Skill and add it to your Career Skills section, along with adding the Career name to the Past Careers field.
|
|
Upon selecting a new Career, your Skill Proficiencies & Maxima will recalculate. Any increased skill which is no longer an active Career skill will have its Maximum recalculated to its current value (if that value is legal!). A small "Boosted" icon will appear on the Skill to indicate that it was raised by a Past Career - it can no longer be increased without triggering the angry red colour which indicates an illegal value, unless it later becomes an Active Skill again through a Career change. Although the official rules do not express the Skill Maxima in the same way (the Maximum for a skill is "0" if it is not an Active Career Skill), this doesn't translate so well to the calculations on the sheet.\n Thus, on a Career change, the Maximum for a skill is calculated as (in order of priority
|
|
1. The Career Max - if this skill is a Proficiency for the newly selected Career
|
|
2. The current value of the skill - if the value is legal (i.e. equal to or below the current max)
|
|
3. The current maximum of the skill - if the current value is illegal
|
|
4. The value "4" as a baseline.</span>
|
|
|
|
<h4>Undo Retire:</h4>
|
|
<span>There is an undo button next to the Retire button if a mistake was made - however, if this is clicked at a later date after progressing through another career, it could lead to errors in Skill Maxima. The undo button is intended to be used immediately to correct a mistake - the Sheet does not track every progression change.</span>
|
|
|
|
<h4>Advanced Career:</h4>
|
|
<span>This button toggles the Career selection dropdowns to display Advanced (and only Advanced) Careers. To switch back to a Basic Career, just toggle the button again.</span>
|
|
</div>
|
|
<div class="help-section">
|
|
<h3><u>Stamina & Luck:</u></h3>
|
|
<span>Both base Stats have a D6 icon to the right when their max values are "0". These will roll a value to chat - click accept in the Roll Template to apply the value to the sheet. The buttons should then disappear.</span>
|
|
|
|
<h4>Stamina:</h4>
|
|
<span>This is a slight departure from the official sheet, to account for the Item/Effect system. The Max value of Stamina is locked - to increase or decrease your base Stamina maximum, click the Unfold button in the top right of the section. This provides a second row which show how your Stamina Max is calculated.
|
|
Base (editable) - this is the value entered when you roll a Character and is the only source of Stamina for a new character. This is the value to change for a permanent increase (or decrease) in Stamina Maximum.
|
|
Career Leveling (locked) - this is calculated from your Career advancement - one Stamina point per increase of a Career Skill.
|
|
Item/Effect bonus (locked) - the total +Stamina you are receiving from Items, Spells or other Effects.
|
|
Rest: the heart icon is a shortcut for a short rest or full rest, recovering 50% / 100% Stamina respectively.</span>
|
|
|
|
<h4>Luck:</h4>
|
|
<span>Luck functions similarly to Stamina above, except it does not have a Career Leveling increase. The value can be manually changed via the Base, or calculated changes via the Item/Effect system.
|
|
Luck Roll: the question mark icon will roll your luck, and subtract a point.</span>
|
|
</div>
|
|
<div class="help-section">
|
|
<h3><u>Background/Traits:</u></h3>
|
|
<span>A short section to add some flavour to your character. Bear in mind that Roll20 does not allow other players to view sheets they don't have control of - if you want other characters to be able to see your Bio, use the Bio tab in the top-left of the frame.
|
|
This section is best used as a quick summary for the benefit of the controlling player or GM.</span>
|
|
</div>
|
|
<div class="help-section">
|
|
<h3><u>Adventuring Skills:</u></h3>
|
|
<span>This section is largely automated unless the sheet is Unlocked. Use the number input to level your Skills, and click the Skill name to roll.
|
|
A green flag indicates a current Career Skill proficiency.
|
|
A blue number to the right of the input indicates that an Item or Effect is currently modifying the Skill. If your Item Mods include dice rolls, the number quoted here is an average representation. For example, if you have two items boosting your Dodge skill by (+1d6) and (+5), the bonus displayed will be the average of 1d6 plus 5, or (+8.5). This is only for display purposes - the dice will be rolled as normal when a skill check is made.
|
|
A red input box & red Max value indicates you have exceeded the Max value for the Skill.
|
|
An up arrow on the far right indicates that a Skill was increased via a Past Career - this mostly provides feedback for the GM as to why a Skill Max might be higher for a Skill which is not active under the current Active Career.</span>
|
|
</div>
|
|
<div class="help-section">
|
|
<h3><u>Possessions:</u></h3>
|
|
<span>Items can be added, removed & edited here. It's largely self-explanatory, except for the Item Mods system. This system can be used to create Items which provide actual Effects on a character and will be automatically applied to rolls.
|
|
This is a big section! It covers functionality which applies to Weapons, Spells and Active Effects.</span>
|
|
<u><h3>Item Mods:</h3></u>
|
|
<div class="important-text">
|
|
Item Mods are divided into four Categories. You can supply one or more Categories on an item, but the syntax is quite precise and must be followed for the mods to be picked up by the sheet.
|
|
A Mod Category *must* be supplied in the following format - do not forget the colon! If supplying more than one Category, they *must* be separated by a semi-colon.
|
|
|
|
<b>category1: mod1, mod2, mod3; category2: mod4</b>
|
|
|
|
The following four Categories are supported:
|
|
|
|
<u><h4>Weapon:</h4></u>
|
|
<span>The weapon Category, if entered correctly, will create an entry in the Weapons section corresponding to the item. You can supply a base weapon type here (e.g. dagger) to have the Weapon entry auto-populate with the base stats for that weapon.
|
|
These can be modified in the Attack section later, but be aware that if you change the Item Mods field again, it may overwrite your changes in the Attack section!
|
|
Weapons can be entered directly into the Weapon section (this is simpler), but using the Possessions Item Mods section has a couple of advantages:
|
|
The entries are linked - if you change the name of the Item, the Weapon Attack name will change. Deleting the Item from the Possessions section will remove the Weapon Attack entry.
|
|
The GM can create complicated items ahead of time - as no Compendium is available, this provides an easy way for the GM to quickly award a premade Weapon by pasting the Item Mods text string into a new Item entry.
|
|
|
|
The following mods are supported in the Weapon Category:
|
|
<b>base</b> - this will set the Weapon to one of the core damage bases in the game. See the "Damage Base" dropdown in the Weapon section (or the rules book!) for valid types. Providing a Base type will pre-fill the Attack Skill, Damage, Damage Type, Ranged and Threat categories. If a 'base' is provided, it will be processed *first*, so any other mods provided (like skill or damage) will overwrite the base values. For example: 'weapon: ranged 0, base bow' would create a weapon with all of the base stats of a Bow, but without the 'ranged' property.
|
|
<b>damage</b> - this provides a damage expression for the weapon. You can provide a core weapon type (e.g. 'dagger') or you can provide 'custom' followed by a roll expression e.g. 'custom 4d6 + 5'. The shorthand 'dmg' can be use instead of 'damage'.
|
|
<b>skill</b> - supply either an Adventure Skill (e.g. 'large blade') or 'custom' followed by a modifier.
|
|
<b>threat</b> - supply either 'casual' or 'martial' to change the Weapon's threat level. This is for RP purposes and doesn't affect any rolls. See the Rules for details on Casual vs. Martial types.
|
|
<b>ranged</b> - this setting flags whether the weapon is ranged or not. If 'ranged' is supplied as a mod, it will be processed as 'true', unless either '0' or 'false' are included. This affects Reactive Rolls - a ranged weapon will force a Dodge roll from the defender, while a non-ranged weapon will allow the defender to use a weapon to defend themselves.
|
|
<b>damagetype</b> - the damage type this weapon inflicts. Can be slashing/piercing/crushing/blast. This dictates the roll table used for Critcial Strike effects.</span>
|
|
|
|
<div class="example-block">
|
|
<u>Examples:</u>
|
|
A giant, blunt, wooden sword which fires bullets from the end -
|
|
<span class="example-mod">weapon: base 2h sword, skill blunt, ranged;</span>
|
|
Knuckledusters which use the damage of a Pole Arm:
|
|
<span class="example-mod">weapon: base pole arm, skill unarmed;</span>
|
|
</div>
|
|
|
|
|
|
<h4><u>Armour:</u></h4>
|
|
<span>This Category covers Damage Reduction items, but not blocking items (e.g. Shield). There are only a few preset values: light, medium/modest and heavy. The Official designation for medium armour is 'modest', but I've included 'medium' as an alternative, as modest is less commonly used. These correspond to the Damage Reduction values in the core rules. You can also define your own roll expression instead: '2d4 + 5' or '5' or '-10' (cursed armour???).
|
|
Also note the English spelling of 'armour' - I've used this for the sake of consistency with the Official rules. However, 'armor' will work in all cases on the character sheet, so don't stress about remembering the spelling if you're used to the US version.</span>
|
|
|
|
<div class="example-block">
|
|
<u>Examples:</u>
|
|
Plate armour -
|
|
<span class="example-mod">armour: heavy;</span>
|
|
American leather armor with the codpiece missing -
|
|
<span class="example-mod">armor: 1d3 - 1;</span>
|
|
</div>
|
|
|
|
<h3><u>Shield:</u></h3>
|
|
<span> Shield items provide a bonus to defence for a defending character. They *do not* provide damage reduction - use the Armour Category for that. Shield items will always be included in the defence roll, unless they are unequipped - so, if your character is suprised, you will need to unequip the shield before you make your defence roll to stop the sheet from including it in the defence roll.
|
|
Valid modifiers for a Shield item are: small, large, or a custom expression. A custom expression can be a number, or a die roll. </span>
|
|
|
|
<div class="example-block">
|
|
<u>Examples:</u>
|
|
Large Shield -
|
|
<span class="example-mod">shield: large;</span>
|
|
Medium Shield -
|
|
<span class="example-mod">shield: +4;</span>
|
|
</div>
|
|
|
|
<h3><u>Stats:</u></h3>
|
|
<span>This Category covers almost everything else - you can boost skills, stamina, luck, luck rolls or provide global attack/damage modifiers. Legal values are: any skill in the Adventuring Skills (e.g. crossbow, dodge, appraise), stamina, luck (modifies luck_max), luckroll (provies a bonus to luck rolls without modifying luck points), attack, damage (these two are global - they will add a bonus to *all* attack and damage rolls).</span>
|
|
|
|
<div class="example-block">
|
|
<u>Examples:</u>
|
|
Ring of Brute Force:
|
|
<span class="example-mod">stats: brawling +5, athletics +3, diplomacy -3, intimidate +2;</span>
|
|
Warm Cloak of True North:
|
|
<span class="example-mod">stats: stamina +2, navigation +10, survivial +2;</span>
|
|
</div>
|
|
|
|
<h3><u>These Categories can all be combined on an item!</u></h3>
|
|
|
|
<div class="example-block">
|
|
<u>Examples:</u>
|
|
Giant Sword of Concrete -
|
|
<span class="example-mod">weapon: base 2h sword, skill club, damage custom 5d6; stats: dodge -10;</span>
|
|
Blessed Mail -
|
|
<span class="example-mod">armour: 1d3+2; shield: +1; stats: dodge +3, incantation +3;</span>
|
|
</div>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h3><u>Current Effects:</u></h3>
|
|
<span>The top half of the effects section lists the current offensive and defensive bonuses present on the player. Clicking the name will post the active buffs to chat.
|
|
The bottom half is for adding temporary effects from non-items - Spells or Injuries for example. Again, clicking the Name will post the buff to chat.
|
|
The Effects mods function almost identically to the Possessions Item Mods, except that the Weapons Category does not function in the Effects section.</span>
|
|
</div>
|
|
|
|
|
|
<div class="help-section">
|
|
<h3><u>Career Skills:</u></h3>
|
|
<span>Unless the sheet is Unlocked for custom use, this section is automated. The top entry is your Active Career Skill, and is the average (rounded up) of your Proficient Skills. When a Career is retired, it will snapshot the current value, and add it to the following rows as a Past Career Skill.</span>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h3><u>Weapons:</u></h3>
|
|
<span>This section is fairly straight-forward. An attack bonus can be added for a Weapon in the Attack Skill row - this would allow you to use, for example, a Bow which provides +5 to hit without modifying the actual Bow skill.
|
|
The Custom Damage field is only active when the Damage Base is set to "Custom".
|
|
Currently there's no way to post the Item Description to chat - this will be added.</span>
|
|
</div>
|
|
|
|
<div class="help-section">
|
|
<h3><u>Spells:</u></h3>
|
|
<span>Spells are divided into two types - Attack and Spellcard. In either case, clicking the spell will initiate an Incantation skill check, to see if the Spell was cast successfully.
|
|
If Reactive Rolls are active, the rest of the process just involves following the prompt buttons on the roll templates. An Attack Spell will resolve to a ranged combat Attack, while a Spellcard spell will give an option to apply the listed Spell Effects to a selected token.
|
|
Spell Effects function exactly like Item Mods & Active Effects - see the Item Mods section for the required syntax. By filling in the Spell Effects section and using Reactive Rolls, temporary effects can quickly be applied in combat.</span>
|
|
|
|
<div class="example-block">
|
|
<u>Examples:</u>
|
|
Fix (spell) -
|
|
<span class="example-mod">stats: attack -5;</span>
|
|
Enchant (spell) -
|
|
<span class="example-mod">stats: damage +1d6;</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- HIDDEN STATS & BUTTONS -->
|
|
<div class="hidden" style="display:none; visibility: hidden">
|
|
<input type="hidden" name="attr_armour_items" value=""/>
|
|
<input type="hidden" name="attr_armour_value" value=""/>
|
|
<input type="hidden" name="attr_block_items" value=""/>
|
|
<input type="hidden" name="attr_block_value" value=""/>
|
|
<input type="hidden" name="attr_readied_weapon" value=""/>
|
|
<input type="hidden" name="attr_version" value="0.1.0"/>
|
|
<button type="action" name="act_react-defend"></button>
|
|
<button type="action" name="act_react-damage"></button>
|
|
<button type="action" name="act_react-apply-damage"></button>
|
|
<button type="action" name="act_apply-effect"></button>
|
|
<button type="action" name="act_react-roll-crit"></button>
|
|
<button type="action" name="act_react-luck"></button>
|
|
<button type="action" name="act_react-cast"></button>
|
|
<button type="action" name="act_react-miscast"></button>
|
|
<button type="action" name="act_rollmiscast"></button>
|
|
</div>
|
|
<!-- END OF HIDDEN STATS -->
|
|
</div>
|
|
<!-- END OF CHARACTER SHEET -->
|
|
|
|
<!-- ROLL TEMPLATES -->
|
|
<!-- ATTACK & SKILL ROLL TEMPLATE -->
|
|
<rolltemplate class="sheet-rolltemplate-wart-roll">
|
|
<div class="sheet-wart-outer{{#class}} sheet-{{class}}{{/class}}" style="border: 5px solid {{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}};">
|
|
<!-- NON-REACT HEADER/BODY -->
|
|
{{^react}}
|
|
<div class="sheet-wart-header">
|
|
<div class="sheet-wart-title" style="background-color: {{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}};"><span>{{title}}</span></div>
|
|
{{#subheader}}
|
|
<div class="sheet-wart-subheader{{#subheader-right}} sheet-double{{/subheader-right}}">
|
|
<div class="sheet-wart-subheader-left"><span>{{subheader}}</span></div>
|
|
{{#subheader-right}}<div class="sheet-wart-subheader-right"><span>{{subheader-right}}</span></div>{{/subheader-right}}
|
|
</div>
|
|
{{/subheader}}
|
|
</div>
|
|
<!-- BODY -->
|
|
<div class="sheet-wart-body">
|
|
<div class="sheet-wart-rolls{{#roll2}} sheet-double{{/roll2}}">
|
|
{{#roll1}}
|
|
<div class="sheet-wart-rollname"><span>{{roll1name}}</span></div>
|
|
<div class="sheet-wart-result"><span>{{roll1}}</span></div>
|
|
{{/roll1}}
|
|
{{#roll2}}
|
|
<div class="sheet-wart-rollname sheet-right"><span>{{roll2name}}</span></div>
|
|
<div class="sheet-wart-result sheet-right"><span>{{roll2}}</span></div>
|
|
{{/roll2}}
|
|
</div>
|
|
{{#roll3}}
|
|
<div class="sheet-wart-rolls sheet-flat">
|
|
<div class="sheet-wart-rollname"><span>{{roll3name}}</span></div>
|
|
<div class="sheet-wart-result"><span>{{roll3}}</span></div>
|
|
</div>
|
|
{{/roll3}}
|
|
{{^success}}{{#link}}<div class="sheet-wart-link">{{link}}</div>{{/link}}{{/success}}
|
|
{{#^rollTotal() computed::success 99}}
|
|
{{#^rollLess() computed::success 0}}{{#linksuccess}}<div class="sheet-wart-link">{{linksuccess}}</div>{{/linksuccess}}{{/^rollLess() computed::success 0}}
|
|
{{#rollLess() computed::success 0}}{{#linkfailure}}<div class="sheet-wart-link">{{linkfailure}}</div>{{/linkfailure}}{{/rollLess() computed::success 0}}
|
|
{{/^rollTotal() computed::success 99}}
|
|
{{#calclink}}{{#calcbutton}}
|
|
<div class="sheet-wart-calc-link">
|
|
{{calcbutton}}||{{computed::calclink}})
|
|
</div>
|
|
{{/calcbutton}}{{/calclink}}
|
|
</div>
|
|
{{/react}}
|
|
<!-- REACT HEADER/BODY -->
|
|
{{#react}}
|
|
<div class="sheet-wart-header">
|
|
<div class="sheet-wart-title" style="background-color: {{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}};"><span>{{title}}</span></div>
|
|
{{#reactsubheader}}
|
|
<div class="sheet-wart-subheader{{#subheader-right}} sheet-double{{/subheader-right}}">
|
|
<div class="sheet-wart-subheader-left"><span>{{reactsubheader}}</span></div>
|
|
{{#subheader-right}}<div class="sheet-wart-subheader-right"><span>{{reactsubheader-right}}</span></div>{{/subheader-right}}
|
|
</div>
|
|
{{/reactsubheader}}
|
|
</div>
|
|
<div class="sheet-wart-body sheet-react">
|
|
<div class="sheet-wart-rolls{{#reactroll2}} sheet-double{{/reactroll2}}">
|
|
{{#reactroll1}}
|
|
<div class="sheet-wart-rollname"><span>{{reactroll1name}}</span></div>
|
|
<div class="sheet-wart-result"><span>{{computed::reactroll1}}</span></div>
|
|
{{/reactroll1}}
|
|
{{#reactroll2}}
|
|
<div class="sheet-wart-rollname sheet-right"><span>{{reactroll2name}}</span></div>
|
|
<div class="sheet-wart-result sheet-right"><span>{{computed::reactroll2}}</span></div>
|
|
{{/reactroll2}}
|
|
</div>
|
|
{{#reactvs}}
|
|
<div class="sheet-wart-rolls sheet-flat">
|
|
<div class="sheet-wart-rollname"><span>{{reactvs}}</span></div>
|
|
</div>
|
|
{{/reactvs}}
|
|
{{#reactoutcome}}
|
|
<div class="sheet-wart-rolls sheet-outcome sheet-notooltip">
|
|
<div class="sheet-wart-notes"><span>{{computed::reactoutcome}}</span></div>
|
|
</div>
|
|
{{/reactoutcome}}
|
|
</div>
|
|
{{/react}}
|
|
<!-- FOOTER -->
|
|
<div class="sheet-wart-footer">
|
|
{{#notes}}
|
|
<div class="sheet-wart-notes">
|
|
{{notes}}
|
|
</div>
|
|
{{/notes}}
|
|
<div class="sheet-wart-charname" style="background-color:{{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}}">
|
|
<span>
|
|
{{#charname}}{{charname}}{{/charname}}
|
|
{{^charname}}<span>-=-</span>{{/charname}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{#react}}
|
|
<div class="sheet-wart-react-outer sheet-react">
|
|
<div class="sheet-wart-react-header sheet-react">
|
|
<span>Reactive rolls:</span>
|
|
</div>
|
|
{{#reactbutton1name}}{{#^rollTotal() computed::reactbutton1name 1}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton1name}}
|
|
</div>
|
|
{{#reactbutton1label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton1label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton1label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton1name 1}}{{/reactbutton1name}}
|
|
{{#reactbutton2name}}{{#^rollTotal() computed::reactbutton2name 2}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton2name}}
|
|
</div>
|
|
{{#reactbutton2label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton2label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton2label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton2name 2}}{{/reactbutton2name}}
|
|
{{#reactbutton3name}}{{#^rollTotal() computed::reactbutton3name 3}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton3name}}
|
|
</div>
|
|
{{#reactbutton3label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton3label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton3label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton3name 3}}{{/reactbutton3name}}
|
|
{{#reactbutton4name}}{{#^rollTotal() computed::reactbutton4name 4}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton4name}}
|
|
</div>
|
|
{{#reactbutton4label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton4label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton4label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton4name 4}}{{/reactbutton4name}}
|
|
{{#reactbutton5name}}{{#^rollTotal() computed::reactbutton5name 5}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton5name}}
|
|
</div>
|
|
{{#reactbutton5label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton5label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton5label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton5name 5}}{{/reactbutton5name}}
|
|
{{#reactbutton6name}}{{#^rollTotal() computed::reactbutton6name 6}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton6name}}
|
|
</div>
|
|
{{#reactbutton6label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton6label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton6label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton6name 6}}{{/reactbutton6name}}
|
|
</div>
|
|
{{/react}}
|
|
</rolltemplate>
|
|
|
|
<!-- DAMAGE TEMPLATE -->
|
|
<rolltemplate class="sheet-rolltemplate-wart-damage">
|
|
<div class="sheet-wart-outer{{#class}} sheet-{{class}}{{/class}}" style="border: 5px solid {{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}};">
|
|
<div class="sheet-wart-header">
|
|
<div class="sheet-wart-title" style="background-color:{{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}}">
|
|
{{title}}
|
|
</div>
|
|
{{#subheader}}
|
|
<div class="sheet-wart-subheader">{{subheader}}</div>
|
|
{{/subheader}}
|
|
{{#mighty}}
|
|
<div class="sheet-wart-subheader sheet-mighty">Mighty Blow! 2x damage</div>
|
|
{{/mighty}}
|
|
{{#crit}}
|
|
<div class="sheet-wart-subheader sheet-crit">Critical Strike!</div>
|
|
{{/crit}}
|
|
</div>
|
|
{{^react}}
|
|
<div class="sheet-wart-body">
|
|
<div class="sheet-wart-rolls">
|
|
<div class="sheet-wart-damageroll">
|
|
<div class="sheet-wart-damagename">{{damageroll1name}}</div>
|
|
<div class="sheet-wart-damageresult">{{damageroll1}}{{damagetype}}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{/react}}
|
|
{{#react}}
|
|
<div class="sheet-wart-body sheet-react">
|
|
<div class="sheet-wart-rolls sheet-react">
|
|
<div class="sheet-wart-damageroll sheet-react">
|
|
<div class="sheet-wart-damagename sheet-react">{{damageroll1name}}</div>
|
|
<div class="sheet-wart-damageresult">{{#mighty}}<span class="sheet-mighty-three">2</span>x{{/mighty}}{{damageroll1}}{{damagetype}}</div>
|
|
</div>
|
|
<div class="sheet-wart-defence sheet-react">
|
|
<div class="sheet-wart-defencename">{{defencename}}</div>
|
|
<div class="sheet-wart-defenceroll">{{defenceroll}}</div>
|
|
</div>
|
|
<div class="sheet-wart-outcome sheet-react">
|
|
<div class="sheet-wart-outcome">{{computed::outcome}}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{/react}}
|
|
{{#link}}
|
|
<div class="sheet-wart-link">{{link}}</div>
|
|
{{/link}}
|
|
{{#notes}}
|
|
<div class="sheet-wart-notes">{{notes}}</div>
|
|
{{/notes}}
|
|
<div class="sheet-wart-footer">
|
|
<div class="sheet-wart-charname" style="background-color:{{#color}}{{color}}{{/color}}{{^color}}#14197e{{/color}}">
|
|
<span>
|
|
{{#charname}}{{charname}}{{/charname}}
|
|
{{^charname}}<span>---</span>{{/charname}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{#react}}
|
|
<div class="sheet-wart-react-outer sheet-react">
|
|
<div class="sheet-wart-react-header sheet-react">
|
|
<span>Reactive rolls:</span>
|
|
</div>
|
|
{{#reactbutton1name}}{{#^rollTotal() computed::reactbutton1name 1}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton1name}}
|
|
</div>
|
|
{{#reactbutton1label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton1label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton1label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton1name 1}}{{/reactbutton1name}}
|
|
{{#reactbutton2name}}{{#^rollTotal() computed::reactbutton2name 2}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton2name}}
|
|
</div>
|
|
{{#reactbutton2label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton2label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton2label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton2name 2}}{{/reactbutton2name}}
|
|
{{#reactbutton3name}}{{#^rollTotal() computed::reactbutton3name 3}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton3name}}
|
|
</div>
|
|
{{#reactbutton3label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton3label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton3label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton3name 3}}{{/reactbutton3name}}
|
|
{{#reactbutton4name}}{{#^rollTotal() computed::reactbutton4name 4}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton4name}}
|
|
</div>
|
|
{{#reactbutton4label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton4label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton4label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton4name 4}}{{/reactbutton4name}}
|
|
{{#reactbutton5name}}{{#^rollTotal() computed::reactbutton5name 5}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton5name}}
|
|
</div>
|
|
{{#reactbutton5label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton5label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton5label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton5name 5}}{{/reactbutton5name}}
|
|
{{#reactbutton6name}}{{#^rollTotal() computed::reactbutton6name 6}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutto6name}}
|
|
</div>
|
|
{{#reactbutton6label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton6label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton6label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton6name 6}}{{/reactbutton6name}}
|
|
</div>
|
|
{{/react}}
|
|
</rolltemplate>
|
|
|
|
<!-- DESCRIPTION TEMPLATE -->
|
|
<rolltemplate class="sheet-rolltemplate-wart-description">
|
|
{{#title}}<div class="sheet-wart-spacer sheet-header"></div>{{/title}}
|
|
<div class="sheet-wart-outer{{#class}} sheet-{{class}}{{/class}}" style="border-color: {{#color}}{{color}}{{/color}}{{^color}}#363905{{/color}};">
|
|
{{#title}}
|
|
<div class="sheet-wart-header">
|
|
<div class="sheet-wart-title" style="background-color:{{#color}}{{color}}{{/color}}{{^color}}#121101{{/color}}">
|
|
{{title}}
|
|
</div>
|
|
{{#subheader}}
|
|
<div class="sheet-wart-subheader">{{subheader}}</div>
|
|
{{/subheader}}
|
|
</div>
|
|
{{/title}}
|
|
<div class="sheet-wart-body">
|
|
{{#optionroll}}
|
|
<div class="sheet-wart-rolltable">
|
|
{{#optionsubtitle}}
|
|
<div class="sheet-wart-subtitle sheet-rolltable">{{optionsubtitle}} - {{optionroll}}</div>
|
|
{{/optionsubtitle}}
|
|
{{#rollTotal() optionroll 1}}<div class="sheet-wart-row sheet-rolltable">{{option1}}</div>{{/rollTotal() optionroll 1}}
|
|
{{#rollTotal() optionroll 2}}<div class="sheet-wart-row sheet-rolltable">{{option2}}</div>{{/rollTotal() optionroll 2}}
|
|
{{#rollTotal() optionroll 3}}<div class="sheet-wart-row sheet-rolltable">{{option3}}</div>{{/rollTotal() optionroll 3}}
|
|
{{#rollTotal() optionroll 4}}<div class="sheet-wart-row sheet-rolltable">{{option4}}</div>{{/rollTotal() optionroll 4}}
|
|
{{#rollTotal() optionroll 5}}<div class="sheet-wart-row sheet-rolltable">{{option5}}</div>{{/rollTotal() optionroll 5}}
|
|
{{#rollTotal() optionroll 6}}<div class="sheet-wart-row sheet-rolltable">{{option6}}</div>{{/rollTotal() optionroll 6}}
|
|
{{#rollTotal() optionroll 7}}<div class="sheet-wart-row sheet-rolltable">{{option7}}</div>{{/rollTotal() optionroll 7}}
|
|
{{#rollTotal() optionroll 8}}<div class="sheet-wart-row sheet-rolltable">{{option8}}</div>{{/rollTotal() optionroll 8}}
|
|
{{#rollTotal() optionroll 9}}<div class="sheet-wart-row sheet-rolltable">{{option9}}</div>{{/rollTotal() optionroll 9}}
|
|
{{#rollTotal() optionroll 10}}<div class="sheet-wart-row sheet-rolltable">{{option10}}</div>{{/rollTotal() optionroll 10}}
|
|
{{#rollTotal() optionroll 11}}<div class="sheet-wart-row sheet-rolltable">{{option11}}</div>{{/rollTotal() optionroll 11}}
|
|
{{#rollTotal() optionroll 12}}<div class="sheet-wart-row sheet-rolltable">{{option12}}</div>{{/rollTotal() optionroll 12}}
|
|
{{#rollTotal() optionroll 13}}<div class="sheet-wart-row sheet-rolltable">{{option13}}</div>{{/rollTotal() optionroll 13}}
|
|
{{#rollTotal() optionroll 14}}<div class="sheet-wart-row sheet-rolltable">{{option14}}</div>{{/rollTotal() optionroll 14}}
|
|
{{#rollTotal() optionroll 15}}<div class="sheet-wart-row sheet-rolltable">{{option15}}</div>{{/rollTotal() optionroll 15}}
|
|
{{#rollTotal() optionroll 16}}<div class="sheet-wart-row sheet-rolltable">{{option16}}</div>{{/rollTotal() optionroll 16}}
|
|
{{#rollTotal() optionroll 17}}<div class="sheet-wart-row sheet-rolltable">{{option17}}</div>{{/rollTotal() optionroll 17}}
|
|
{{#rollTotal() optionroll 18}}<div class="sheet-wart-row sheet-rolltable">{{option18}}</div>{{/rollTotal() optionroll 18}}
|
|
{{#rollTotal() optionroll 19}}<div class="sheet-wart-row sheet-rolltable">{{option19}}</div>{{/rollTotal() optionroll 19}}
|
|
{{#rollTotal() optionroll 20}}<div class="sheet-wart-row sheet-rolltable">{{option20}}</div>{{/rollTotal() optionroll 20}}
|
|
</div>
|
|
{{/optionroll}}
|
|
{{#description}}
|
|
<div class="sheet-wart-desc">
|
|
{{#subtitle}}
|
|
<div class="sheet-wart-subtitle sheet-desc">{{subtitle}}</div>
|
|
{{/subtitle}}
|
|
<div class="sheet-wart-row sheet-desc{{#classdesc}} sheet-{{classdesc}}{{/classdesc}}">
|
|
{{description}}
|
|
</div>
|
|
{{#reactdescription}}
|
|
<div class="sheet-wart-row sheet-desc">
|
|
{{#^rollTotal() computed::reactdescription 0}}{{computed::reactdescription}}{{/^rollTotal() computed::reactdescription 0}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
{{#reactbutton1name}}{{#^rollTotal() computed::reactbutton1name 1}}<div class="sheet-wart-react-buttonname">{{computed::reactbutton1name}}</div>
|
|
{{#reactbutton1label}}<div class="sheet-wart-react-button">{{reactbutton1label}}||{{computed::reactdata}})</div>{{/reactbutton1label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton1name 1}}{{/reactbutton1name}}
|
|
</div>
|
|
{{/reactdescription}}
|
|
</div>
|
|
{{/description}}
|
|
<div class="sheet-wart-allprops">
|
|
{{#allprops() optionroll subheader class title reactbutton1name reactbutton1label reactdata optionsubtitle classdesc description reactdescription subtitle charname color option1 option2 option3 option4 option5 option6 option7 option8 option9 option10 option11 option12 option13 option14 option15 option16 option17 option18 option19 option20}}
|
|
<div class="sheet-wart-row sheet-allprops">
|
|
<div class="sheet-allprop sheet-key">{{key}}</div>
|
|
<div class="sheet-allprop sheet-value">{{value}}</div>
|
|
</div>
|
|
{{/allprops() optionroll subheader class title reactbutton1name reactbutton1label reactdata optionsubtitle classdesc description reactdescription subtitle charname color option1 option2 option3 option4 option5 option6 option7 option8 option9 option10 option11 option12 option13 option14 option15 option16 option17 option18 option19 option20}}
|
|
</div>
|
|
</div>
|
|
{{#charname}}
|
|
<div class="sheet-wart-footer">
|
|
<div class="sheet-wart-charname" style="background-color:{{#color}}{{color}}{{/color}}{{^color}}#121101{{/color}}">
|
|
<span>
|
|
{{#charname}}{{charname}}{{/charname}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{{/charname}}
|
|
</div>
|
|
{{#charname}}{{#react}}
|
|
<div class="sheet-wart-react-outer sheet-react">
|
|
<div class="sheet-wart-react-header sheet-react">
|
|
<span>Reactive rolls:</span>
|
|
</div>
|
|
{{#reactbutton1name}}{{#^rollTotal() computed::reactbutton1name 1}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton1name}}
|
|
</div>
|
|
{{#reactbutton1label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton1label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton1label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton1name 1}}{{/reactbutton1name}}
|
|
{{#reactbutton2name}}{{#^rollTotal() computed::reactbutton2name 2}}
|
|
<div class="sheet-wart-react-button sheet-row">
|
|
<div class="sheet-wart-react-buttonname">
|
|
{{computed::reactbutton2name}}
|
|
</div>
|
|
{{#reactbutton2label}}
|
|
<div class="sheet-wart-react-button">
|
|
{{reactbutton2label}}||{{computed::reactdata}})
|
|
</div>
|
|
{{/reactbutton2label}}
|
|
</div>
|
|
{{/^rollTotal() computed::reactbutton2name 2}}{{/reactbutton2name}}
|
|
</div>
|
|
{{/react}}
|
|
<div class="sheet-wart-spacer sheet-footer"></div>{{/charname}}
|
|
</rolltemplate>
|
|
<!-- END OF TEMPLATES -->
|
|
|
|
<!-- SHEETWORKERS -->
|
|
<script type="text/worker">/* globals getAttrs, setAttrs, generateRowID, startRoll, finishRoll, getSectionIDs, removeRepeatingRow, getActiveCharacterId */
|
|
/**
|
|
* ================
|
|
* CONTENTS:
|
|
*
|
|
* 1. GAME SYSTEM DATA - game system data for character creation & rolls
|
|
* 2. CONSTANTS & VERSION CONTROL - common constants for character sheet
|
|
* 3. ASYNC CORE - promisified sheetworkers for delicious async goodness
|
|
* 4. HELPER FUNCTIONS - does what it says on the tin
|
|
* 5. MAIN SHEET FUNCTIONS - non-repeating section functions
|
|
* 6. REPEATING SECTION FUNCTIONS - go on, have a guess
|
|
* 7. DICE ROLL FUNCTIONS - includes experimental Reactive Rolls
|
|
* 8. EVENT LISTENERS - ooooh, another mysterious title
|
|
*
|
|
* ================
|
|
*/
|
|
|
|
/**
|
|
* 1. GAME SYSTEM DATA
|
|
* Career Data for character creation
|
|
**/
|
|
const version = {
|
|
M: 0,
|
|
m: 4,
|
|
p: 2,
|
|
current() {return `${this.M}.${this.m}.${this.p}`},
|
|
integer() {return parseInt(`${this.M}${this.m}${this.p}`)}
|
|
};
|
|
|
|
const warlockData = {
|
|
getCareers(tier='', race='') { return this.careers.filter(c=>(new RegExp(`${tier}`, `i`)).test(c.type)).filter(c=>(new RegExp(`${race}`, `i`)).test(c.type)) },
|
|
getCareerNames(tier='', race='') { return this.getCareers(tier, race).map(c=>c.name) },
|
|
getSkills(career) { return (this.getCareerNames().includes(career)) ? this.careers.find(c=>(new RegExp(`^\\s*${career}\\s*$`, 'i')).test(c.name)).skills : null },
|
|
getDamageTypes() {return Object.keys(this.critTables)},
|
|
armourTypes: {light: '1d3', modest: '1d6', heavy: '2d6'},
|
|
shieldTypes: {small: '3', large: '5'},
|
|
weaponTypes: {
|
|
custom: {damage: '@{damage_custom}'},
|
|
unarmed: {damage: '1d6 - 2',type: 'crushing',threat: 'casual',skill: 'brawling'},
|
|
club: {damage: '1d6 - 1',type: 'crushing',threat: 'casual',skill: 'blunt'},
|
|
knife: {damage: '1d6 + 1',type: 'slashing',threat: 'casual',skill: 'small_blade'},
|
|
dagger: {damage: '1d6 + 2',type: 'piercing',threat: 'casual',skill: 'small_blade'},
|
|
short_sword: {damage: '1d6 + 3',type: 'slashing',threat: 'casual',skill: 'large_blade'},
|
|
arming_sword: {damage: '2d6',type: 'slashing',threat: 'casual',skill: 'large_blade'},
|
|
hammer: {damage: '2d6',type: 'crushing',threat: 'martial',skill: 'blunt',},
|
|
axe: {damage: '2d6 + 1',type: 'crushing',threat: 'martial',skill: 'pole_arm'},
|
|
mace: {damage: '2d6 + 2',type: 'crushing',threat: 'martial',skill: 'blunt'},
|
|
spear: {damage: '2d6 + 1',type: 'piercing',threat: 'martial',skill: 'pole_arm'},
|
|
pole_arm: {damage: '2d6 + 2',type: 'slashing',threat: 'martial',skill: 'pole_arm'},
|
|
['2h_sword']: {damage: '2d6 + 3',type: 'slashing',threat: 'martial',skill: 'large_blade'},
|
|
staff: {damage: '1d6',type: 'crushing',threat: 'casual',skill: 'blunt'},
|
|
bow: {damage: '2d6 + 1',type: 'piercing',threat: 'casual',skill: 'bow',ranged: 1},
|
|
crossbow: {damage: '2d6 + 2',type: 'piercing',threat: 'martial',skill: 'crossbow',ranged: 1},
|
|
},
|
|
critTables: {
|
|
slashing: {
|
|
2: "Flat of the blade across the skull, dazed for [[1d6]] rounds, all actions at a penalty of 2. Double vision for [[1d6]] days.",
|
|
3: "Slashed on the hip, falls over and can only crawl in the dirt for [[1d6]] rounds, all tests at a penalty of 3.",
|
|
4: "Cut on thigh, can only hobble for [[1d3]] days, Endurance test not to end up with a limp.",
|
|
5: "That was my foot! Can only hobble for [[1d6]] days. Toes loose in the boot.",
|
|
6: "Slash on the back, opening muscles, can't carry a pack for [[1d6]] days.",
|
|
7: "Whoops, [[1d3]] fingers sliced off, randomly determine hand, drop what you're carrying.",
|
|
8: "An ear is slashed! Permanent penalty of 2 to tests involving hearing.",
|
|
9: "Hacked in the shoulder! Determine which arm, tests involving that arm are at a penalty of 5 for [[1d6]] days.",
|
|
10: "Cut through an artery, dead.",
|
|
},
|
|
piercing: {
|
|
2: "A jab in the forearm, pass a sleight-of-hand test to keep hold of your weapon.",
|
|
3: "A skewered rump! Very painful, all tests at a penalty of 3 for the next [[1d6]] rounds.",
|
|
4: "A prod in the guts, forces dinner up and over everyone. Spend [[1d6]] rounds retching, all actions are at a penalty of 3.",
|
|
5: "Poked in the neck, can do nothing but gasp for breath and defend at a penalty of 2 for [[1d6]] rounds.",
|
|
6: "Run through the shoulder, arm immobilised for [[1d6]] days.",
|
|
7: "Poked in the mouth, teeth everywhere. Hard to talk through the blood, even uglier than before.",
|
|
8: "My eye! Permanent penalty of 2 to tests involving sight, ugly to boot.",
|
|
9: "Through my hand! Drops weapon in pain. Hand a useless claw for [[1d6]] days.",
|
|
10: "Stabbed through the heart or brain, dead.",
|
|
},
|
|
crushing: {
|
|
2: "A rap on the skull sends teeth chattering, dazed for [[1d6]] rounds.",
|
|
3: "Foot crushed, hops around in agony for [[1d6]] rounds.",
|
|
4: "Dead leg, all tests involving movement at a penalty of 3 for [[1d3]] days.",
|
|
5: "A whack in the guts, winded and wheezing, can only defend at a penalty of 3 for [[1d6]] rounds.",
|
|
6: "Hand crushed, determine which one (dominant/non-dominant). Drop what you were carrying and can't use hand for [[1d6]] days.",
|
|
7: "Thumped on the temples and seeing stars! Passes out for [[1d6]] rounds.",
|
|
8: "Right in the kidney! Peeing blood, all tests at a penalty of 5 for the next [[1d6]] days. Better hope there is not too much internal bleeding!",
|
|
9: "Smack on the chin. Jaw fractured, slurred and garbled speech for [[1d6]] days. Broken and unsymmetrical face now.",
|
|
10: "Smashed on the skull and brained, dead.",
|
|
},
|
|
blast: {
|
|
2: "Weapon too hot to touch! Drop it and draw something else, quick!",
|
|
3: "Clothing on fire! Spend [[1d6]] rounds doing nothing except putting it out.",
|
|
4: "You gear catches fire! Either put it out for [[1d6]] rounds, or let all that you own burn!",
|
|
5: "Breathe in the fumes, coughing and spluttering for [[1d6]] rounds, all tests at a penalty of 3.",
|
|
6: "Blinded by the blast, permanent penalty of 2 to tests involving sight.",
|
|
7: "Knocked off your feet and flung against the wall. Stunned and prone for [[1d6]] rounds, all tests at a penalty of 3.",
|
|
8: "Hair singed off, scalp red and raw. No hats or helmets for [[1d6]] days, all tests at penalty of 3 during this time.",
|
|
9: "Full in the face, the blast destroys your sense of smell. All tests involving smell at a penalty of 3, now your companions are more bearable.",
|
|
10: "Skin and bone seared, dead",
|
|
}
|
|
},
|
|
miscastTable: {
|
|
1: "The caster's hands catch fire causing them [[1d6]] damage.",
|
|
2: "Two small horns grow from the casters head.",
|
|
3: "One of the caster's eyes turns milky white.",
|
|
4: "The spell discharges incorrectly, hitting the nearest creature for [[2d6]] damage.",
|
|
5: "The caster's fingers elongate on one hand",
|
|
6: "Blood runs from the caster's eyes for [[1d6]] days.",
|
|
7: "The spell backfires, blasting the caster across the room for [[2d6]] damage.",
|
|
8: "An otherworldly being spies the caster, and appears in [[1d3]] rounds.",
|
|
9: "The caster loses the sense of taste, all food is like ash.",
|
|
10: "The caster's skin is bleached white.",
|
|
11: "All of the caster's hair falls out.",
|
|
12: "The caster's skin becomes translucent for [[2d6]] days.",
|
|
13: "The caster's arms are altered, growing small bone spurs.",
|
|
14: "The caster freezes like a statue for the next [[1d3]] days.",
|
|
15: "A miasma of magical mist fills a nearby space, causing [[1d6]] damage to all.",
|
|
16: "The caster's eyes turn jet black for [[1d6]] weeks.",
|
|
17: "The caster is possessed by an otherworldly being for [[1d6]] turns.",
|
|
18: "The caster loses the ability to cast spells for [[1d6]] days, every attempt miscasts.",
|
|
19: "The caster's face is frozen in a grimace of pain for [[1d6]] days.",
|
|
20: "The caster's arms become covered in small scales.",
|
|
},
|
|
creatureAbilities: {
|
|
brittle: "This creature is susceptible to impact damage. If maces, clubs or hammers attack them successfully, the damage is rolled twice and the higher value is applied.",
|
|
dissolve: "This being is able to dissolve matter. Any time it is successfully struck the attacker must test their luck. On a fail, their weapon is destroyed.",
|
|
ethereal: "This creature does not exist in the material plane. It can only be harmed by magic and magical weapons, and can pass through solid barriers such as doors and walls with ease. Flame",
|
|
flying: "Using wings or similar, this creature can fly. Treat this as normal movement, but ignoring barriers that can be flown over at the game master's discretion. This creature cannot be engaged in melee combat whilst flying unless it chooses to attack, in which case it can be targeted.",
|
|
formless: "The body of this creature has no real form, so it may slide under doors, squeeze through narrow spaces etc. as if it were a thick, viscous liquid.",
|
|
intelligent: "This being is intelligent, can speak (but perhaps not in a tongue the characters know) and use tools and weapons. It is also able to create strategies and plans.",
|
|
poisonous: "This creature carries a poison or can inject a venom. If it successfully hits a target, the victim must test their luck. If they fail, the victim is poisoned, and loses 1d6 stamina at the start of the next 1d3 rounds. Poison or venom effects from the same source are not cumulative, although of course the creature can still inflict its normal damage when it hits.",
|
|
puppet: "The creature has no will of its own and must be directed by another mind. In consequence, it never surrenders or flees and will fight until destroyed.",
|
|
regenerating: "The being is able to recover damage. Every round the being regains 1d6 stamina, up to a maximum of its starting value.",
|
|
spell_caster: "The being is able to cast spells. The games master should feel free to assign the creature spells that reflect its power. It uses stamina to cast spells and requires an incantation skill check.",
|
|
tracking: "The creature is excellent at tracking other creatures by scent or another sense. Treat its adventuring skill at three times the normal level for tests involving tracking.",
|
|
},//,\s*,*\s*\}\s*,\s*\}\s*,\s*
|
|
careers: [
|
|
// CORE - BASIC
|
|
{name: "agitator",type: "basic",skills: {small_blade: 10,intimidate: 10,persuasion: 12,streetwise: 12}},
|
|
{name: "beggar",type: "basic",skills: {appraise: 10,blunt: 10,spot: 12,streetwise: 12}},
|
|
{name: "boatman",type: "basic",skills: {navigation: 10,repair: 10,endurance: 12,swimming: 12}},
|
|
{name: "bodyguard",type: "basic",skills: {medicine: 10,thrown: 10,intimidate: 12,large_blade: 12}},
|
|
{name: "bounty_hunter",type: "basic",skills: {bargain: 10,crossbow: 10,spot: 12,streetwise: 12}},
|
|
{name: "entertainer",type: "basic",skills: {diplomacy: 10,history: 10,disguise: 12,persuasion: 12}},
|
|
{name: "footpad",type: "basic",skills: {stealth: 10,thrown: 10,intimidate: 12,streetwise: 12}},
|
|
{name: "gambler",type: "basic",skills: {bargain: 10,spot: 10,persuasion: 12,"sleight-of-hand": 12}},
|
|
{name: "grave_robber",type: "basic",skills: {intimidate: 10,ostler: 10,small_blade: 12,spot: 12}},
|
|
{name: "hunter",type: "basic",skills: {stealth: 10,swimming: 10,spot: 12,survival: 12}},
|
|
{name: "initiate",type: "basic",skills: {command: 10,ostler: 10,medicine: 12,persuasion: 12}},
|
|
{name: "mercenary",type: "basic",skills: {endurance: 10,streetwise: 10,dodge: 12,large_blade: 12}},
|
|
{name: "militiaman",type: "basic",skills: {command: 10,ostler: 10,"pole-arm": 12,thrown: 12}},
|
|
{name: "miner",type: "basic",skills: {survival: 10,swimming: 10,endurance: 12,navigation: 12}},
|
|
{name: "noble",type: "basic",skills: {language: 10,medicine: 10,diplomacy: 12,history: 12}},
|
|
{name: "outlaw",type: "basic",skills: {medicine: 10,"sleigh-of-hand": 10,large_blade: 12,thrown: 12}},
|
|
{name: "pedlar",type: "basic",skills: {ostler: 10,streetwise: 10,bargain: 12,repair: 12}},
|
|
{name: "raconteur",type: "basic",skills: {dodge: 10,history: 10,lie: 12,streetwise: 12}},
|
|
{name: "rat_catcher",type: "basic",skills: {athletics: 10,medicine: 10,stealth: 12,survival: 12}},
|
|
{name: "road_warden",type: "basic",skills: {crossbow: 10,dodge: 10,"pole-arm": 12,ostler: 12}},
|
|
{name: "soldier",type: "basic",skills: {command: 10,bow: 10,large_blade: 12,"pole-arm": 12}},
|
|
{name: "thief",type: "basic",skills: {lie: 10,streetwise: 10,small_blade: 12,spot: 12}},
|
|
{name: "tomb_robber",type: "basic",skills: {athletics: 10,intimidate: 10,blunt: 12,endurance: 12}},
|
|
{name: "wizard's_apprentice",type: "basic",skills: {command: 10,persuasion: 10,language: 12,incantation: 12}},
|
|
// CORE - ADVANCED
|
|
{name: "assassin",type: "advanced",skills: {blunt: 14,dodge: 14,streetwise: 14,small_blade: 16,stealth: 16}},
|
|
{name: "bravo",type: "advanced",skills: {blunt: 14,bow: 14,command: 14,large_blade: 16,streetwise: 16}},
|
|
{name: "watch_captain",type: "advanced",skills: {crossbow: 14,endurance: 14,ostler: 14,pole_arm: 16,streetwise: 16}},
|
|
{name: "charlatan",type: "advanced",skills: {appraise: 14,ostler: 14,spot: 14,persuasion: 16,streetwise: 16}},
|
|
{name: "explorer",type: "advanced",skills: {athletics: 14,small_blade: 14,swimming: 14,navigation: 16,spot: 16}},
|
|
{name: "freelance",type: "advanced",skills: {diplomacy: 14,medicine: 14,thrown: 14,ostler: 16,pole_arm: 16}},
|
|
{name: "highwayman",type: "advanced",skills: {dodge: 14,intimidate: 14,large_blade: 14,disguise: 16,ostler: 16}},
|
|
{name: "mercenary_captain",type: "advanced",skills: {bargain: 14,medicine: 14,survival: 14,crossbow: 16,large_blade: 16}},
|
|
{name: "merchant",type: "advanced",skills: {lie: 14,persuasion: 14,sleight_of_hand: 14,appraise: 16,bargain: 16,ostler: 16}},
|
|
{name: "minstrel",type: "advanced",skills: {history: 14,lie: 14,repair: 14,persuasion: 16,sleight_of_hand: 16}},
|
|
{name: "outlaw_chief",type: "advanced",skills: {endurance: 14,ostler: 14,swimming: 14,intimidate: 16,large_blade: 16}},
|
|
{name: "priest",type: "advanced",skills: {diplomacy: 14,ostler: 14,thrown: 14,incantation: 16,language: 16}},
|
|
{name: "scholar",type: "advanced",skills: {appraise: 14,brawling: 14,repair: 14,language: 16,medicine: 16}},
|
|
{name: "scout",type: "advanced",skills: {athletics: 14,ostler: 14,survival: 14,spot: 16,stealth: 16}},
|
|
{name: "spy",type: "advanced",skills: {athletics: 14,dodge: 14,small_blade: 14,persuasion: 16,sleight_of_hand: 16}},
|
|
{name: "veteran_soldier",type: "advanced",skills: {dodge: 14,pole_arm: 14,survival: 14,endurance: 16,large_blade: 16}},
|
|
{name: "wizard",type: "advanced",skills: {brawling: 14,command: 14,medicine: 14,history: 16,incantation: 16}},
|
|
// COMPENDIUM BASIC
|
|
{name: "dwarf_inventor",type: "basic, dwarf",skills: {brawling: 10,diplomacy: 10,repair: 12,sleight_of_hand: 12}},
|
|
{name: "dwarf_tunnel_fighter",type: "basic, dwarf",skills: {endurance: 10,intimidate: 10,stealth: 12,survival: 12}},
|
|
{name: "elf_astrologer",type: "basic, elf",skills: {appraise: 10,diplomacy: 10,navigation: 12,spot: 12}},
|
|
{name: "elf_kin_guard",type: "basic, elf",skills: {endurance: 10,large_blade: 10,navigation: 12,survival: 12}},
|
|
{name: "halfling_gong_farmer",type: "basic, halfling",skills: {bargain: 10,medicine: 10,small_blade: 12,streetwise: 12}},
|
|
{name: "halfling_pie_master",type: "basic, halfling",skills: {bargain: 10,small_blade: 10,persuasion: 12,streetwise: 12}},
|
|
// COMPENDIUM ADVANCED
|
|
{name: "dwarf_battlesmith",type: "advanced, dwarf",skills: {appraise: 14,endurance: 14,history: 14,intimidate: 16,repair: 16}},
|
|
{name: "dwarf_slayer",type: "advanced, dwarf",skills: {athletics: 14,brawling: 14,medicine: 14,intimidate: 16,large_blade: 16}},
|
|
{name: "elf_agent",type: "advanced, elf",skills: {diplomacy: 16,language: 16,persuasion: 16,sleight_of_hand: 14,streetwise: 14}},
|
|
{name: "elf_champion",type: "advanced, elf",skills: {spot: 14,survival: 14,thrown: 14,intimidate: 16,large_blade: 16}},
|
|
{name: "halfling_burglar",type: "advanced, halfling",skills: {appraise: 14,athletics: 14,dodge: 14,spot: 16,stealth: 16}},
|
|
{name: "halfling_gaffer",type: "advanced, halfling",skills: {brawling: 14,lie: 14,intimidate: 14,command: 16,streetwise: 16}},
|
|
// COMPENDIUM ADVANCED SPELLCASTERS
|
|
{name: "dwarf_runeforger",type: "advanced, dwarf",skills: {appraise: 14,command: 14,diplomacy: 14,incantation: 16,language: 16}},
|
|
{name: "halfling_conjurer",type: "advanced, halfling",skills: {disguise: 14,lie: 14,streetwise: 14,persuasion: 16,sleight_of_hand: 16}},
|
|
{name: "elf_druid",type: "advanced, elf",skills: {medicine: 14,small_blades: 14,spot: 14,navigation: 16,survival: 16}},
|
|
// KINGDOM BASIC
|
|
{name: "docker",type: "basic",skills: {appraise: 10,brawling: 10,intimidate: 10,streetwise: 12,swimming: 12}},
|
|
{name: "fish_warden",type: "basic",skills: {blunt: 10,repair: 10,thrown: 10,navigation: 12,swimming: 12}},
|
|
{name: "mudlark",type: "basic",skills: {spot: 10,survival: 10,thrown: 10,endurance: 12,swimming: 12}},
|
|
{name: "night_watchman",type: "basic",skills: {blunt: 10,lie: 10,navigation: 10,stealth: 12,streetwise: 12}},
|
|
{name: "publican",type: "basic",skills: {bargain: 10,history: 10,spot: 10,blunt: 12,persuasion: 12}},
|
|
{name: "servant",type: "basic",skills: {bargain: 10,endurance: 10,history: 10,spot: 12,stealth: 12}},
|
|
// GOBLINS BASIC
|
|
{name: "hedge_wizard",type: "basic, goblin",skills: {blunt: 10,navigation: 10,medicine: 12,survival: 12}},
|
|
{name: "goblin_pedlar",type: "basic, goblin",skills: {diplomacy: 10,sleight_of_hand: 10,blunt: 12,bargain: 12}},
|
|
{name: "guttersnipe",type: "basic, goblin",skills: {athletics: 10,blunt: 10,stealth: 12,sleight_of_hand: 12}},
|
|
{name: "mushroom_farmer",type: "basic, goblin",skills: {blunt: 10,brawling: 10,medicine: 12,navigation: 12,spot: 12}},
|
|
{name: "spider_rider",type: "basic, goblin",skills: {appraise: 10,bow: 10,small_blade: 12,ostler: 12,survival: 12}},
|
|
{name: "tinkerer",type: "basic, goblin",skills: {small_blades: 10,sleight_of_hand: 10,repair: 12,spot: 12}},
|
|
// GOBLINS ADVANCED
|
|
{name: "emissary",type: "advanced, goblin",skills: {history: 14,languages: 14,appraise: 14,persuasion: 16,diplomacy: 16}},
|
|
{name: "spelunker",type: "advanced. goblin",skills: {spot: 14,swimming: 14,stealth: 14,navigation: 16,athletics: 16}},
|
|
{name: "sporceror",type: "advanced, goblin",skills: {stealth: 14,navigation: 14,medicine: 14,spot: 16,incantation: 16}},
|
|
],
|
|
}
|
|
|
|
/**
|
|
* 2. CONSTANTS & VERSION CONTROL
|
|
*/
|
|
const rx = {
|
|
id: /-[A-Za-z0-9_-]{19}/, idOnly: /^-[A-Za-z0-9_-]{19}$/, rowId: /-[A-Za-z0-9-]{19}/, attrMod: /([-+]?\s*\d+|d\d+)/i, getRowId: /_(-[A-Za-z0-9-]{19})_/,
|
|
grabAttr: /^0\[(.*)?\]\s*$/,
|
|
};
|
|
const skills = ['appraise', 'athletics', 'bargain', 'blunt', 'bow', 'brawling', 'command', 'crossbow', 'diplomacy', 'disguise', 'dodge', 'endurance', 'history', 'incantation', 'intimidate', 'language', 'large_blade', 'lie', 'medicine', 'navigation', 'ostler', 'persuasion', 'pole_arm', 'repair', 'sleight_of_hand', 'small_blade', 'spot', 'stealth', 'streetwise', 'survival', 'swimming', 'thrown'];
|
|
const skillsMax = skills.map(sk => `${sk}_max`);
|
|
const skillsBoosted = skills.map(sk => `${sk}_boosted`);
|
|
const skillsBonus = skills.map(sk => `${sk}_bonus`);
|
|
const initialSkillpoints = 10;
|
|
const weaponAttributes = ['possession_link', 'name', 'damage', 'attack_skill', 'attack_mod', 'damage_type', 'ranged', 'description'];
|
|
const spellAttributes = ['type', 'cost', 'name', 'damage', 'damage_type', 'description', 'attack_skill', 'attack_mod', 'ranged', 'mods', 'duration', 'duration_type'];
|
|
|
|
const itemModTypes = ['weapon', 'armor', 'armour', 'shield', 'stats', 'stat'];
|
|
const rxModTypes = new RegExp(`(${itemModTypes.join('|')})\\s*:`, 'i');
|
|
|
|
const versionControl = async (gotVersion) => {
|
|
let latestVersion = version.current();
|
|
if (latestVersion === gotVersion) return h.log(`Sheet is up to date`, 'info', `versionControl`);
|
|
let currentVersion = /\d+\.\d+\.\d+/.test(gotVersion) ? parseInt(gotVersion.replace(/\D/g, '')) : 0;
|
|
|
|
if (currentVersion < 42) {
|
|
//NO ATTR CHANGES
|
|
}
|
|
|
|
await as.setAttrs({version: latestVersion});
|
|
};
|
|
|
|
/**
|
|
* 3. ASYNC CORE FUNCTIONS
|
|
* async sheetworker functions Modified from Scott C's version of Onyxring's code
|
|
**/
|
|
const as = (() => {
|
|
const setActiveCharacterId = function(charId){
|
|
let oldAcid=getActiveCharacterId();
|
|
let ev = new CustomEvent("message");
|
|
ev.data={"id":"0", "type":"setActiveCharacter", "data":charId};
|
|
self.dispatchEvent(ev);
|
|
return oldAcid;
|
|
};
|
|
const promisifyWorker = (worker, parameters) => {
|
|
let acid=getActiveCharacterId();
|
|
let prevAcid=null;
|
|
return new Promise((res,rej)=>{
|
|
prevAcid=setActiveCharacterId(acid);
|
|
try {if (worker===0) getAttrs(parameters[0]||[],(v)=>res(v));
|
|
else if (worker===1) setAttrs(parameters[0]||{}, parameters[1]||{},(v)=>res(v));
|
|
else if (worker===2) getSectionIDs(parameters[0]||'',(v)=>res(v));
|
|
} catch(err) {rej(console.error(err))}
|
|
}).finally(()=>setActiveCharacterId(prevAcid));
|
|
}
|
|
return {
|
|
getAttrs(attrArray) {return promisifyWorker(0, [attrArray])},
|
|
setAttrs(attrObj, options) {return promisifyWorker(1, [attrObj, options])},
|
|
getSectionIDs(section) {return promisifyWorker(2, [section])},
|
|
setActiveCharacterId,
|
|
}
|
|
})();
|
|
|
|
|
|
/**
|
|
* 4. HELPER FUNCTIONS
|
|
*/
|
|
const h = (() => {
|
|
const consoleStyle = {
|
|
scriptName: 'warlock!sheet',
|
|
log: `border: solid 1px cyan; line-height: 16px; text-align: center; padding: 1px 8px 2px 8px; border-radius: 8px; background-color: #333`,
|
|
info: `border: solid 2px orange; line-height: 16px; text-align: middle; padding: 1px 8px 2px 8px; border-radius: 8px; background-color: #444`,
|
|
warn: `border: solid 2px red; line-height: 16px; text-align: middle; padding: 1px 8px 2px 8px; border-radius: 8px; background-color: #444`,
|
|
error: `border: solid 2px red; line-height: 16px; color: red; font-weight: bold; text-align: middle; padding: 1px 8px 2px 8px; border-radius: 8px; background-color: #fff`
|
|
}
|
|
const log = (msgs, style='log', title) => {
|
|
msgs = (msgs.length === 1 && Array.isArray(msgs[0])) ? [msgs] : toArray(msgs)||[];
|
|
style = Object.keys(consoleStyle).includes(style) ? style : 'log';
|
|
console.log(`%c${consoleStyle.scriptName}.${style}${title ? ` -= ${title} =-` : ''}`, consoleStyle[style], ...msgs);
|
|
if (style === 'error') console.trace();
|
|
}
|
|
|
|
const checkId = (input, strict) => typeof(input) === 'string' ? strict ? rx.idOnly.test(input) : rx.id.test(input) : undefined;
|
|
|
|
const checkRowId = (input) => typeof(input) === 'string' ? rx.rowId.test(input) : undefined;
|
|
|
|
const getRowId = (input) => typeof(input) === 'string' ? (input.match(rx.getRowId)||[])[1] : undefined;
|
|
|
|
const emproper = (input) => `${input}`.split(/[_\s]+/g).map(w=>`${w[0].toUpperCase()}${w.slice(1)}`).join(' ');
|
|
|
|
const toArray = (inp) => (inp == null) ? null : Array.isArray(inp) ? inp : [inp];
|
|
|
|
const cloneObj = (inp) => {
|
|
if (typeof(inp) !== 'object') return;
|
|
let output;
|
|
try {output = JSON.parse(JSON.stringify(inp))}
|
|
catch(err) {h.log(err, 'error', 'cloneObj')}
|
|
return output;
|
|
}
|
|
|
|
const isEmptyObj = (inp) => {
|
|
if (!inp || typeof(inp) !== 'object') {h.log(inp, 'warn', `isEmptyObj: input is not an object!`); return undefined;}
|
|
return (Object.keys(inp).length) ? false : true;
|
|
}
|
|
|
|
const mergeObj = (parentObj, newObj) => {
|
|
if (typeof(parentObj) !== 'object' || typeof(newObj) !== 'object' || !Object.keys(newObj).length) return null;
|
|
try {Object.assign(parentObj, newObj)} catch(err) {h.log(err, 'error', `mergeObj spat the dummy`); return false}
|
|
return true;
|
|
}
|
|
|
|
// Use option {returnId: true} to return an Object with the rowId as the key, output as the value
|
|
// Use option {existingId: <id>} to use an existing rowId
|
|
const createRepRow = (section, suffixes, values, options={}) => {
|
|
values = toArray(values);
|
|
let rowId = checkRowId(options.existingId) ? options.existingId : generateRowID();
|
|
let output = {};
|
|
toArray(suffixes).map((suf, i) => {if (values[i] != null) output[`repeating_${section}_${rowId}_${suf}`] = values[i]||''});
|
|
return options.returnId ? {[rowId]: output} : output;
|
|
}
|
|
|
|
const toggleAttr = async (attr, log=false) => {
|
|
await as.getAttrs(h.toArray(attr)).then(async (a) => {
|
|
let outVal = (!a[attr] || a[attr] == '0') ? 1 : 0;
|
|
await as.setAttrs({[attr]: outVal});
|
|
if (log) h.log(`Toggle Helper: ${attr} switched to "${outVal}"`, 'log', 'toggleAttr');
|
|
});
|
|
}
|
|
|
|
const cleanRoll = (input) => {
|
|
let output = input
|
|
.replace(/[^\d+-d\s]/g, '')
|
|
.replace(/d[^\d+]/g, '')
|
|
.replace(/[\s]{2,}/g, ' ');
|
|
return output;
|
|
};
|
|
|
|
// Provide alternative roll labels with {base: 'myLabel'}, provide extras as {'Rollname': '4d6'}
|
|
const combineRollParts = (base, bonus, global, options={}, extras={}) => {
|
|
let labels = [options.base||'Base', options.bonus||'Bonus', options.global||'Global'].concat(Object.keys(extras));
|
|
let parts = [base, bonus, global].concat(Object.values(extras)).map((rp, i) => {
|
|
if (!rp || !/\d/.test(rp) || parseInt(rp) === 0) return null;
|
|
rp = `${rp}`.replace(/^\s*\+/, '').replace(/[^0-9d+*/-]/g, '');
|
|
rp = (/\d\s*[+*/-]\s*\d/.test(rp)) ? `(${rp})` : rp;
|
|
return `${rp}[${labels[i]}]`;
|
|
});
|
|
return parts.filter(v=>v).join(' + ');
|
|
};
|
|
|
|
const escapeRegex = (string) => {
|
|
return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
}
|
|
|
|
const timeout = async (ms) => {
|
|
return new Promise(res => {
|
|
setTimeout(() => res(), ms)
|
|
});
|
|
};
|
|
|
|
// example input 3d6 + 15 - 1d4
|
|
// +5
|
|
// - 1d4 - 2d20 + 5
|
|
// Expects an array of integers or XdY roll expressions. Returns a flat integer representing the average outcome
|
|
const getAverageOfMods = (inputArray) => {
|
|
let errorMarker = ``;
|
|
let result = inputArray.reduce((a,v) => {
|
|
v = v.replace(/^\s*(\d)/, (match, p1) => {
|
|
return `+${p1}`;
|
|
});
|
|
v = v.replace(/(\D)d/gi, (match, p1) => {
|
|
return `${p1}1d`;
|
|
});
|
|
let rxChunk = /\s*([+-][^+-]+)/g;
|
|
let expParts;
|
|
let subExpTotal = 0;
|
|
while ((expParts = rxChunk.exec(v)) !== null) {
|
|
let partValue = 0,
|
|
diceRoll = expParts[1].match(/([+-])\s*(\d+d\d+)/i);
|
|
if (diceRoll) {
|
|
let rollParts = diceRoll[2].match(/(\d+)d(\d+)/i);
|
|
let average = parseInt(rollParts[1]) * (parseInt(rollParts[2])+1)/2;
|
|
partValue = (/-/.test(diceRoll[1])) ? - average : average;
|
|
} else {
|
|
partValue = parseInt(expParts[1].replace(/[^\d+-]/g, ''));
|
|
}
|
|
if (isNaN(partValue)) {
|
|
errorMarker = `*`;
|
|
} else subExpTotal += partValue;
|
|
}
|
|
return a += subExpTotal;
|
|
}, 0);
|
|
return (result < 0) ? `${result}${errorMarker}` : `+${result}${errorMarker}`;
|
|
}
|
|
|
|
return {
|
|
log,
|
|
checkId,
|
|
checkRowId,
|
|
getRowId,
|
|
emproper,
|
|
toArray,
|
|
cloneObj,
|
|
mergeObj,
|
|
isEmptyObj,
|
|
createRepRow,
|
|
toggleAttr,
|
|
cleanRoll,
|
|
combineRollParts,
|
|
escapeRegex,
|
|
timeout,
|
|
getAverageOfMods,
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* 5. MAIN SHEET FUNCTIONS
|
|
*/
|
|
const warSheet = (() => {
|
|
// Respond to changes in Adventuring Skills
|
|
const handleSkillChange = async (ev) => {
|
|
let output = {};
|
|
let attrs = await as.getAttrs(skills.concat(skillsMax).concat(['unlocked', 'career', 'advanced_career', 'career_toggle', 'initial_skillpoints', 'tutorial_flag', 'initial_fives', 'initial_sixes']));
|
|
let targetCareer = (attrs.career_toggle == '1') ? attrs.advanced_career : attrs.career;
|
|
let careerSkills = warlockData.getSkills(targetCareer);
|
|
// Check if modified Skill effects Career Skill calculation
|
|
if (careerSkills && Object.keys(careerSkills).includes(ev.triggerName)) {
|
|
let cSkillValues = Object.keys(careerSkills).map(sk => parseInt(attrs[sk],10)||4);
|
|
let cSkill = await warSheet.calculateCareerSkill(targetCareer, cSkillValues, (attrs.unlocked == 1 ? false : true));
|
|
h.mergeObj(output, cSkill);
|
|
}
|
|
// Check if Skill limit has been breached
|
|
let triggerSkill = parseInt(ev.newValue);
|
|
let triggerMax = parseInt(attrs[`${ev.sourceAttribute}_max`])||99;
|
|
output[`${ev.sourceAttribute}_overmax`] = (triggerSkill > triggerMax) ? 1 : 0;
|
|
// Pass off to tutorial if required
|
|
if (attrs.tutorial_flag == '1') handleTutorial(1, attrs);
|
|
else if (attrs.tutorial_flag == '3') handleTutorial(3, attrs);
|
|
// Back to main function
|
|
h.log(output, 'info', `Skill change completed`);
|
|
as.setAttrs(output);
|
|
}
|
|
// Pass through CC Tutorial phases if running
|
|
const handleTutorial = async (phase, attrs) => {
|
|
let output = {};
|
|
if (phase === 1) {
|
|
let fives = 0, sixes = 0;
|
|
skills.forEach(sk => {
|
|
if (parseInt(attrs[sk]) > 5) sixes ++;
|
|
else if (parseInt(attrs[sk]) === 5) fives ++;
|
|
});
|
|
output.initial_fives = 10 - fives;
|
|
output.initial_sixes = 10 - sixes;
|
|
// If all skillpoints have been used, proceed to the next tutorial phase in on(change:career)
|
|
if (fives === 10 && sixes === 10) {
|
|
output.tutorial_flag = 2;
|
|
output.disable_career_flag = 0;
|
|
}
|
|
} else if (phase === 3) {
|
|
let totalPoints = skills.reduce((a,v) => a += parseInt(attrs[v])||4, 0);
|
|
let spent = totalPoints - (skills.length*4 + 30);
|
|
output.initial_skillpoints = initialSkillpoints - spent;
|
|
// If all skillpoints have been used, disable the tip
|
|
if (spent > 9) output.tutorial_flag = 0;
|
|
}
|
|
|
|
if (!h.isEmptyObj(output)) await as.setAttrs(output).then(() => h.log(output, 'log', `handleTutorial`));
|
|
}
|
|
|
|
// Handle Adventuring Skills list when Career is changed
|
|
const getNewCareerSkills = async (newCareer) => {
|
|
// Grab Career Skills
|
|
let careerSkills = warlockData.getSkills(newCareer)||null;
|
|
h.log(careerSkills, 'log', `Skills:`);
|
|
let careerSkillNames = Object.keys(careerSkills);
|
|
let attrs = await as.getAttrs(skills.concat(skillsBoosted).concat(skillsMax).concat(['unlocked', 'tutorial_flag']));
|
|
let output = {};
|
|
// Calculate the Career Skill
|
|
let careerSkillValues = careerSkillNames.map(sk => attrs[sk]||4);
|
|
let cSkill = await warSheet.calculateCareerSkill(newCareer, careerSkillValues, (attrs.unlocked == 1 ? false : true));
|
|
Object.assign(output, cSkill);
|
|
// Progress the Tutorial if running
|
|
if (attrs.tutorial_flag == 2) output.tutorial_flag = 3;
|
|
// Change the Adventuring Skill maxima where required
|
|
skills.map(sk => {
|
|
h.log(`${sk}: ${attrs[sk]}, parse ${parseInt(attrs[sk])}`)
|
|
let nonBoostedMax = Math.min(parseInt(attrs[sk])??0, 6),
|
|
careerMax = parseInt(careerSkills[sk])||0,
|
|
current = parseInt(attrs[sk]),
|
|
currentMax = parseInt(attrs[`${sk}_max`])||0,
|
|
isProf = (careerSkillNames.includes(sk)) ? true : false;
|
|
output[`${sk}_max`] = isProf ? careerMax : (attrs[`${sk}_boosted`] == 1) ? Math.min(currentMax, current) : nonBoostedMax;
|
|
output[`${sk}_prof`] = (isProf) ? 1 : 0;
|
|
if (output[`${sk}_max`] < current) output[`${sk}_overmax`] = 1;
|
|
h.log(`${sk} max set to: ${output[`${sk}_max`]}, nbm: ${nonBoostedMax}`);
|
|
});
|
|
h.log(output, 'info', `Career switch completed`);
|
|
as.setAttrs(output, {silent: true});
|
|
}
|
|
|
|
// Caculate Career Skill value, parameters are career=@{career}, careerSkills = Array of current career skill values
|
|
const calculateCareerSkill = async (career, careerSkills, calculateStaminaIncrease = false) => {
|
|
let output = {};
|
|
let sValues = Object.values(careerSkills);
|
|
if (!sValues || !sValues.length) {h.log(`No skill values found`, 'error', `calculateCareerSkill`); return null}
|
|
let average = Math.ceil(sValues.reduce((a, v) => a += parseInt(v)||4, 0)/sValues.length);
|
|
h.mergeObj(output, {career_skill_name: h.emproper(career), career_skill_level: average});
|
|
//h.log(average, 'info', `Calculated skill average`);
|
|
// Calculate Stamina Increase from career skills
|
|
if (calculateStaminaIncrease) {
|
|
let reqIDs = await as.getSectionIDs('careerskill'),
|
|
reqAttrs = reqIDs.map(row=>`repeating_careerskill_${row}_level`);
|
|
let attrs = await as.getAttrs(reqAttrs);
|
|
let stamIncrease = Object.values(attrs).reduce((a,v) => a = (parseInt(v) > 4) ? a + (v - 4) : a, 0) + (Math.max((average - 4), 0));
|
|
h.log(stamIncrease, 'info', `Stamina increase from Career Levels`);
|
|
h.mergeObj(output, {stamina_increase: stamIncrease});
|
|
}
|
|
//h.log(output, 'info', `calcCareerSkill return`);
|
|
return output;
|
|
}
|
|
|
|
// Set new maxima for Career Skills when a Career is retired
|
|
const setCareerMaxima = async (currentCareer, isCustomCareer=false) => {
|
|
// Grab either the custom_prof flag, or the _prof flag depending on if the sheet is unlocked
|
|
let profSuffix = (isCustomCareer) ? `custom_prof` : `prof`,
|
|
reqAttrs = skills.concat(skillsMax).concat(skills.map(sk=>`${sk}_${profSuffix}`));
|
|
let attrs = await as.getAttrs(reqAttrs);
|
|
let output = {},
|
|
currentCareerProfSkills = {};
|
|
skills.forEach(sk => {
|
|
if (parseInt(attrs[`${sk}_${profSuffix}`]) > 0) currentCareerProfSkills[sk] = attrs[`${sk}_max`]
|
|
});
|
|
for (let sk in currentCareerProfSkills) {
|
|
//let careerMax = currentCareerProfSkills[sk]; OLD VALUE - can probably delete, pulls value from warlockData instead of sheet
|
|
let careerMax = currentCareerProfSkills[sk];
|
|
let careerCurrentValue = parseInt(attrs[sk])||4;
|
|
let newMax = Math.min(careerCurrentValue, careerMax);
|
|
if (newMax > 6) {
|
|
output[`${sk}_max`] = newMax;
|
|
output[`${sk}_boosted`] = 1;
|
|
}
|
|
}
|
|
h.log(output, 'info', `New Maxima:`);
|
|
as.setAttrs(output, {silent: true});
|
|
}
|
|
|
|
// Set up Custom Career profs for unlocked sheet
|
|
|
|
|
|
// Retire current Career, add current Career Skill to Past Career Skill rep sec
|
|
const retireCareer = async () => {
|
|
let output = {};
|
|
let attrs = await as.getAttrs(['career', 'advanced_career', 'career_toggle', 'past_careers', 'career_skill_name', 'career_skill_level', 'unlocked', 'custom_career', 'custom_career_skill_level']);
|
|
// Push Career to Past Careers
|
|
let customCareer = (attrs.unlocked) == 1 ? true : false;
|
|
let targetCareer = (customCareer) ? attrs.custom_career : (attrs.career_toggle == '1') ? attrs.advanced_career : attrs.career;
|
|
if (!targetCareer || targetCareer === 'none') {h.log(attrs.career_toggle, 'error', `Invalid career toggle value`); return;}
|
|
let pastArray = (attrs.past_careers) ? attrs.past_careers.split(/\s*,\s*/g).filter(v=>!/^\s+$/.test(v)) : [];
|
|
pastArray.push(h.emproper(targetCareer));
|
|
pastArray = pastArray.length ? pastArray : [''];
|
|
h.log(pastArray, 'log', `Past Careers array: `);
|
|
if (pastArray.length) setCareerMaxima(targetCareer, customCareer);
|
|
output.past_careers = pastArray.join(', ');
|
|
// Assign current Career Skill to Past Career Skills rep sec
|
|
let careerSkillValues = (customCareer) ? [attrs.custom_career||'Unknown Career', attrs.custom_career_skill_level||0] : [attrs.career_skill_name||'Unknown Career', attrs.career_skill_level||0];
|
|
Object.assign(output, h.createRepRow('careerskill', ['name', 'level'], careerSkillValues));
|
|
await as.setAttrs(output);
|
|
h.log(output, 'info', `Retire completed:`);
|
|
}
|
|
// Undo a Career Retire (for misclicks/mistakes, not intended for future character editing)
|
|
const undoRetire = async () => {
|
|
let ids = await as.getSectionIDs('careerskill');
|
|
let reqAttrs = ids.map(row => `repeating_careerskill_${row}_name`).concat(['past_careers']);
|
|
h.log(reqAttrs, 'info', `undoRetire getAttrs list`);
|
|
let attrs = await as.getAttrs(reqAttrs)
|
|
if (attrs.past_careers) {
|
|
let pastArray = attrs.past_careers.split(/\s*,\s*/g).filter(v=>v);
|
|
let remove = pastArray.pop();
|
|
if (!pastArray.length) pastArray.push(' ');
|
|
// Remove Career Skill if found
|
|
let completed = false;
|
|
let rxCar = new RegExp(`${remove}`, 'i');
|
|
ids.map(row => {
|
|
if (!completed && attrs[`repeating_careerskill_${row}_name`] && rxCar.test(attrs[`repeating_careerskill_${row}_name`])) {
|
|
removeRepeatingRow(`repeating_careerskill_${row}`);
|
|
completed = true;
|
|
}
|
|
});
|
|
// Unsure if need to rectify any setCareerMaxima stuff here
|
|
await as.setAttrs({past_careers: pastArray.join(', ')});
|
|
h.log(pastArray, 'info', `Removed ${remove} from: `);
|
|
}
|
|
}
|
|
|
|
const convertCurrency = async (coin) => {
|
|
let reqAttrs = ['pennies', 'silver', 'gold'],
|
|
coins = await as.getAttrs(reqAttrs),
|
|
smallCoin = (/pen/i.test(coin)) ? 'pennies' : 'silver',
|
|
largeCoin = (smallCoin === 'pennies') ? 'silver' : 'gold',
|
|
origSmall = parseInt(coins[smallCoin])||null,
|
|
origLarge = parseInt(coins[largeCoin])||null,
|
|
newLarge = Math.floor(origSmall/10),
|
|
remainderSmall = origSmall%10,
|
|
totalLarge = newLarge + origLarge;
|
|
if (origSmall == null || origLarge == null) return h.log(coins, 'info', `Converter`);
|
|
let output = {[smallCoin]: remainderSmall, [largeCoin]: totalLarge};
|
|
let currencyBase = `&{template:wart-description} {{title=Currency Conversion}} {{charname=@{character_name}}} {{subtitle=${h.emproper(smallCoin)} to ${h.emproper(largeCoin)}}} {{description=@{character_name} converted [[${origSmall}]] ${smallCoin} into [[${newLarge}]] ${largeCoin} and [[${remainderSmall}]] ${smallCoin}.}}`;
|
|
let currencyRoll = await startRoll(currencyBase);
|
|
as.setAttrs(output);
|
|
finishRoll(currencyRoll.rollId);
|
|
};
|
|
|
|
// Post monster ability to chat
|
|
const linkEffect = async (rowId) => {
|
|
let linkType = (h.checkRowId(rowId)) ? 'r' : (/global/i.test(rowId)) ? 'g' : 'ab';
|
|
let reqAttrs = (linkType === 'r') ? ['name', 'mods', 'source', 'duration', 'duration_type'].map(a=>`repeating_effect_${rowId}_${a}`) : linkType === 'ab' ? [`${rowId}_items`] : linkType === 'g' ? ['global_attack_bonus', 'global_defence_bonus'] : null;
|
|
if (!reqAttrs) return h.log(rowId, 'error', `linkEffect: Illegal passthrough`);
|
|
let attrs = await as.getAttrs(reqAttrs);
|
|
let rep = `repeating_effect_${rowId}`
|
|
let sub = (linkType === 'r') ? `***${attrs[`${rep}_name`]} (${attrs[`${rep}_source`]})***\nDuration: ${attrs[`${rep}_duration`]||''} ${attrs[`${rep}_duration_type`]||''}` : (linkType === 'ab') ? `${h.emproper(rowId)} Items` : `Global Modifiers`;
|
|
let desc = (linkType === 'r') ? `${attrs[`${rep}_mods`]||''}` : (linkType === 'ab') ? `${attrs[`${rowId}_items`]||' '}` : `Attack: ${attrs.global_attack_bonus||0}\nDamage: ${attrs.global_damage_value||0}`;
|
|
let linkBase = `&{template:wart-description}{{title=Current Effect}}{{color=#363905}}{{charname=@{character_name}}} {{subtitle=${sub}}} {{description=${desc}}}`;
|
|
let linkRoll = await startRoll(linkBase);
|
|
finishRoll(linkRoll.rollId);
|
|
};
|
|
|
|
// Quick create monster ability from dropdown
|
|
const createAbility = async () => {
|
|
let ability = await as.getAttrs(['ability_wizard_option']).then(v=>v['ability_wizard_option']);
|
|
h.log(ability, 'info', `Creating monster ability`);
|
|
let details = warlockData.creatureAbilities[ability];
|
|
if (!details) return h.log(ability, 'error', `Monster ability not found`);
|
|
let output = h.createRepRow('monster-ability', ['name', 'details', 'options-flag'], [h.emproper(ability), details, '0']);
|
|
as.setAttrs(output, {silent:true});
|
|
}
|
|
|
|
return {
|
|
handleSkillChange,
|
|
calculateCareerSkill,
|
|
setCareerMaxima,
|
|
getNewCareerSkills,
|
|
retireCareer,
|
|
undoRetire,
|
|
convertCurrency,
|
|
linkEffect,
|
|
createAbility,
|
|
}
|
|
|
|
})();
|
|
|
|
/**
|
|
* 6. REPEATING SECTION FUNCTIONS
|
|
**/
|
|
const warRepeating = (() => {
|
|
// Possessions Section
|
|
|
|
const weaponFlagMap = {
|
|
martial: '',
|
|
casual: '',
|
|
slashing: 'S',
|
|
crushing: 'C',
|
|
piercing: 'P',
|
|
blast: 'B',
|
|
ranged: 'R'
|
|
}
|
|
const possessionAttributes = ['name', 'quantity', 'equipped', 'itemmods', 'weapon_link'];
|
|
const effectAttributes = ['name', 'duration', 'duration_type', 'mods', 'flags', 'active', 'details', 'alert'];
|
|
const itemFlagsMap = {
|
|
weapon: 'W',
|
|
armour: 'A',
|
|
stats: 'S',
|
|
shield: 'B',
|
|
}
|
|
|
|
const getAllModAttrs = async () => {
|
|
let allEffectIds = await as.getSectionIDs('effect'),
|
|
allPossessionIds = await as.getSectionIDs('possession'),
|
|
reqAttrs = ['npc', 'npc_armour'];
|
|
allEffectIds.forEach(row=>effectAttributes.forEach(a=>reqAttrs.push(`repeating_effect_${row}_${a}`)));
|
|
allPossessionIds.forEach(row => possessionAttributes.forEach(a => reqAttrs.push(`repeating_possession_${row}_${a}`)));
|
|
return await as.getAttrs(reqAttrs);
|
|
}
|
|
|
|
const handlePossessionChange = async (rowId, trigger, eventValues) => {
|
|
let output = {};
|
|
let attrs = await getAllModAttrs();
|
|
if (trigger === 'removed') { // Deal with a deleted row
|
|
if (h.checkId(eventValues[`repeating_possession_${rowId}_weapon_link`])) removeRepeatingRow(`repeating_weapon_${eventValues[`repeating_possession_${rowId}_weapon_link`]}`);
|
|
if (rxModTypes.test(eventValues[`repeating_possession_${rowId}_itemmods`])) h.mergeObj(output, await calculateEffectMods(attrs, rowId, {deleted: true}));
|
|
} else if ( // If itemmods changed, or something with itemmods was (un)equipped
|
|
(/equipped/i.test(trigger) && attrs[`repeating_possession_${rowId}_itemmods`])
|
|
|| /itemmods/i.test(trigger)) {
|
|
h.mergeObj(output, await calculateEffectMods(attrs, rowId));
|
|
} else if (/name$/i.test(trigger) && h.checkId(attrs[`repeating_possession_${rowId}_weapon_link`])) { // Name change linked weapon
|
|
output[`repeating_weapon_${attrs[`repeating_possession_${rowId}_weapon_link`]}_name`] = eventValues.newValue;
|
|
}
|
|
h.log(output, 'warn', 'handlePossessionChange finished, setting attrs');
|
|
if (!h.isEmptyObj(output)) as.setAttrs(output, {silent:true});
|
|
};
|
|
|
|
const handleEffectChange = async (rowId, trigger, eventValues) => {
|
|
let output = {};
|
|
let attrs = await getAllModAttrs();
|
|
if (trigger === 'removed' && rxModTypes.test(eventValues[`repeating_effect_${rowId}_mods`])) h.mergeObj(output, await calculateEffectMods(attrs, rowId, {deleted:true}));
|
|
else if (/active/i.test(trigger) && rxModTypes.test(attrs[`repeating_effect_${rowId}_mods`])
|
|
|| /mods/i.test(trigger)) h.mergeObj(output, await calculateEffectMods(attrs, rowId));
|
|
h.log(output, 'warn', 'handleEffectChange completed');
|
|
if (!h.isEmptyObj(output)) as.setAttrs(output, {silent:true});
|
|
}
|
|
|
|
const calculateEffectMods = async (attrs, triggerRow, options={}) => {
|
|
let output = {};
|
|
let validStats = skills.concat(['stamina', 'luck', 'luckroll', 'global_attack', 'global_damage']);
|
|
// Initialise output object with empty values for global fields options
|
|
validStats.map(st => output[`${st}_bonus`] = 0);
|
|
['armour_items', 'armour_values', 'shield_items', 'shield_values'].map(a=>output[a] = '');
|
|
//sectionIds.map(row=>output[`repeating_possession_${row}_flags`] = []);
|
|
let modRows = Object.entries(attrs).filter(e => /mods/i.test(e[0]) && rxModTypes.test(e[1]));
|
|
h.log(modRows, 'info', `handleItemMods function, trigger: "${triggerRow}"`);
|
|
let statBonus = {}, armourBonus = {}, blockBonus = {}, itemFlags = {possession: {}, effect: {}};
|
|
if (attrs.npc == 1 && attrs.npc_armour) armourBonus[`Base`] = h.cleanRoll(attrs.npc_armour);
|
|
await Promise.all(modRows.map(async (row) => {
|
|
h.log(row[0]);
|
|
let currentSection = (row[0].match(/repeating_([^_]*?)_/)||[])[1];
|
|
let currentRow = (row[0].match(rx.rowId)||[])[0];
|
|
let currentName = attrs[`repeating_${currentSection}_${currentRow}_name`];
|
|
h.log(row, 'log', `Processing ${currentName} - ${currentRow}...`);
|
|
if (!h.checkRowId(currentRow)) {h.log([row], 'error', `calculateEffectMods: bad row ID`); return null}
|
|
if ((/possess/i.test(currentSection) && attrs[`repeating_possession_${currentRow}_equipped`] != 1) ||
|
|
(/effect/i.test(currentSection) && attrs[`repeating_effect_${currentRow}_active`] != 1)) {
|
|
h.log(`${currentName} is not equipped/active, skipping`);
|
|
return;
|
|
}
|
|
let modTypes = row[1].split(/[\n\s]*[;]+[\n\s]*/g).filter(v=>rxModTypes.test(v));
|
|
modTypes.map(mt => {
|
|
let type = ((mt.match(rxModTypes)||[])[1]) || '';
|
|
let mods = mt.replace(rxModTypes, '').split(/\s*,\s*/);
|
|
h.log([type, mods], 'info', `Processing mod parts....`);
|
|
// Calcs to run only on the triggering rep row
|
|
if (/possess/i.test(currentSection) && triggerRow.toLowerCase() === currentRow.toLowerCase() && !options.deleted) {
|
|
let weaponLink = attrs[`repeating_possession_${currentRow}_weapon_link`] || null;
|
|
if (/weapon/i.test(type)) updateWeapon({type: 'possession', rowId: triggerRow}, mods, {possessionRowId: currentRow, possessionName: currentName, weaponRowId: weaponLink});
|
|
}
|
|
// Global calcs to always run
|
|
if (/weapon/i.test(type)) itemFlags[currentSection][currentRow] ? itemFlags[currentSection][currentRow].push(itemFlagsMap.weapon) : itemFlags[currentSection][currentRow] = [itemFlagsMap.weapon];
|
|
if (/(armor|armour)/i.test(type)) { // Process armour/DR items
|
|
itemFlags[currentSection][currentRow] ? itemFlags[currentSection][currentRow].push(itemFlagsMap.armour) : itemFlags[currentSection][currentRow] = [itemFlagsMap.armour];
|
|
mods.map(mod => {
|
|
let reductionExp = /heavy/i.test(mod) ? warlockData.armourTypes.heavy
|
|
: /(medium|modest)/i.test(mod) ? warlockData.armourTypes.modest
|
|
: /(light)/i.test(mod) ? warlockData.armourTypes.light
|
|
: rx.attrMod.test(mod) ? h.cleanRoll(mod) : null;
|
|
if (reductionExp) armourBonus[currentName] = reductionExp;
|
|
});
|
|
} else if (/shield/i.test(type)) { // Process shield/block items
|
|
itemFlags[currentSection][currentRow] ? itemFlags[currentSection][currentRow].push(itemFlagsMap.shield) : itemFlags[currentSection][currentRow] = [itemFlagsMap.shield];
|
|
h.log(`Processing ${type}...`);
|
|
mods.map(mod => {
|
|
let blockVal = /small/i.test(mod) ? warlockData.shieldTypes.small
|
|
: /large/i.test(mod) ? warlockData.shieldTypes.large
|
|
: rx.attrMod.test(mod) ? h.cleanRoll(mod) : null;
|
|
if (blockBonus) blockBonus[currentName] = blockVal;
|
|
});
|
|
} else if (/stat/i.test(type)) { // Process all stat bonuses
|
|
itemFlags[currentSection][currentRow] ? itemFlags[currentSection][currentRow].push(itemFlagsMap.stats) : itemFlags[currentSection][currentRow] = [itemFlagsMap.stats];
|
|
h.log(`Processing ${type}...`);
|
|
mods.map(mod => {
|
|
h.log(`Processing ${mod}...`);
|
|
let parts = mod.match(/^\s*([A-Za-z\s]+)([\dDd\s+-]+)/) || [];
|
|
let modValue = parts[2];
|
|
if (parts.length > 1) {
|
|
// Convert attack & damage to globals
|
|
parts[1] = (/^\s*(attack|damage)/i.test(parts[1])) ? `global_${parts[1]}` : parts[1];
|
|
let rxName = new RegExp(`${parts[1].trim().replace(/\s+/g, '_')}`, 'i');
|
|
h.log(rxName, 'log', `Finding name`);
|
|
let attrName = validStats.find(st => rxName.test(st));
|
|
if (attrName) {
|
|
attrName += `_bonus`;
|
|
statBonus[attrName] ? statBonus[attrName].push(modValue) : statBonus[attrName] = [modValue];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}));
|
|
h.log([statBonus, armourBonus, blockBonus, itemFlags], 'info', `Output from calculateEffectMods()`);
|
|
// Now process the results into sheet attributes to be set
|
|
await Promise.all([statBonus, armourBonus, blockBonus, itemFlags].map(async (o, i) => {
|
|
h.log(``, 'info', `Collating item mods data`);
|
|
//if (!h.isEmptyObj(o)) {
|
|
if (i === 0) {
|
|
h.log(`Processing stat bonuses`);
|
|
for (let stat in o) {
|
|
//output[stat] = o[stat].reduce((a, v) => a += v);
|
|
output[stat] = o[stat].join(' + ').replace(/^\s*\+\s*/, '').replace(/\+\s*\+\s*/g, '+ ');
|
|
output[`${stat}_display`] = h.getAverageOfMods(o[stat])||'???';
|
|
}
|
|
} else if (i === 1 || i === 2) {
|
|
let target = (i === 1) ? 'armour' : 'block';
|
|
h.log(`Processing ${target} bonuses`);
|
|
let items = [], value = [];
|
|
for (let item in o) {
|
|
items.push(`${h.emproper(item)}: ${o[item]}`);
|
|
value.push(o[item]);
|
|
}
|
|
output[`${target}_items`] = items.join(', ');
|
|
output[`${target}_value`] = value.join(' + ').replace(/^\s*\+\s*/, '').replace(/\+\s*\+\s*/g, '+ ');
|
|
} else if (i === 3) {
|
|
for (let section in o) {
|
|
for (let row in o[section]) {
|
|
output[`repeating_${section}_${row}_flags`] = o[section][row].join('');
|
|
}
|
|
}
|
|
}
|
|
//}
|
|
}));
|
|
h.log(output, 'warn', 'Final output of itemmods()');
|
|
return output;
|
|
}
|
|
|
|
// weapon: base 2h sword, damage custom 4d6, skill
|
|
// Update weapon from Possession link or damage_base change
|
|
//
|
|
const updateWeapon = async (trigger, itemMods, linkData={possessionRowId: null, possessionName: null, weaponRowId: null}) => {
|
|
const validWeaponMods = /(base|skill|damage|dmg|ranged|threat|dtype|damagetype)/i;
|
|
let row = trigger.rowId;
|
|
if (!h.checkRowId(row)) return h.log([trigger, itemMods], 'error', `updateWeapon: Invalid row ID`);
|
|
let output = {};
|
|
let flags = [];
|
|
h.log([trigger, itemMods, linkData], 'log', 'updating weapon link');
|
|
let weaponRowIds = await as.getSectionIDs('weapon');
|
|
// Process the item mods/value changes
|
|
let weaponData = {};
|
|
if (/possess/i.test(trigger.type)) {
|
|
weaponData = {damage: warlockData.weaponTypes.club.damage, attack_skill: 'blunt', damage_type: 'crushing', ranged: '0', attack_mod: '0'};
|
|
if (linkData) h.mergeObj(weaponData, {name: linkData.possessionName, possession_link: linkData.possessionRowId});
|
|
// If mod list contains a base weapon type, put it first so it can be overwritten
|
|
itemMods.sort((a,b) => {
|
|
if (/base/i.test(a)) return -1;
|
|
else if (/base/i.test(b)) return 1;
|
|
});
|
|
h.log([itemMods], 'log', `Processing sorted mods`);
|
|
await Promise.all(itemMods.map(async (mod) => {
|
|
let modSubtype = (mod.match(validWeaponMods)||[])[1];
|
|
if (!modSubtype) return h.log(mod, 'warn', `Weapon Mod cannot be processed!`);
|
|
// Scrub dice rolls & command words from the mod
|
|
let cleanMod = mod.replace(validWeaponMods, '').replace(/([\d+-]*d\d+|\b[+-]*\d+\b)/g, '').replace(/[+-]*/g, '').trim().replace(/\s+/g, '_'),
|
|
rxMod = new RegExp(`${h.escapeRegex(cleanMod)}`, 'i');
|
|
let weaponBase = (/base/i.test(modSubtype)) ? Object.keys(warlockData.weaponTypes).find(w => rxMod.test(w)) : null,
|
|
damageBase = (/(dmg|damage)/i.test(modSubtype)) ? Object.keys(warlockData.weaponTypes).find(w => rxMod.test(w)) : null,
|
|
skillBase = (/skill/i.test(modSubtype)) ? (/custom/i.test(rxMod)) ? 'custom' : skills.find(sk => rxMod.test(sk))||null : null,
|
|
threatBase = (/threat/i.test(modSubtype)) && mod.match(/(casual|martial)/i) ? mod.match(/(casual|martial)/i)[1] : null,
|
|
rangedBase = (/range/i.test(modSubtype)) ? (/(0|false)/i.test(mod)) ? '0' : '1' : null,
|
|
dTypeBase = ((/(dtype|damagetype)/i.test(modSubtype)) && mod.match(/(piercing|slashing|crushing|blast)/i)) ? mod.match(/(piercing|slashing|crushing|blast)/i)[1] : null,
|
|
rollExpression = mod.replace(/\s+/g, '_').replace(validWeaponMods, '').replace(rxMod, '').replace(/[^0-9d+-\s]/g, '')||``;
|
|
if (weaponBase) {
|
|
h.mergeObj(weaponData, {
|
|
damage: weaponBase === 'custom' ? null : warlockData.weaponTypes[weaponBase].damage||null,
|
|
damage_base: weaponBase,
|
|
damage_type: weaponBase === 'custom' ? null : warlockData.weaponTypes[weaponBase].type||null,
|
|
attack_skill: weaponBase === 'custom' ? null : warlockData.weaponTypes[weaponBase].skill||null,
|
|
ranged: weaponBase === 'custom' ? null : warlockData.weaponTypes[weaponBase].ranged||'0',
|
|
threat: weaponBase === 'custom' ? null : warlockData.weaponTypes[weaponBase].threat||'casual'
|
|
});
|
|
} else if (damageBase) {
|
|
h.mergeObj(weaponData, {
|
|
damage: damageBase === 'custom' ? rollExpression||null : warlockData.weaponTypes[weaponBase].damage,
|
|
damage_custom: damageBase === 'custom' ? rollExpression||null : null,
|
|
damage_base: damageBase,
|
|
});
|
|
} else if (skillBase) {
|
|
h.mergeObj(weaponData, {
|
|
attack_skill: skillBase,
|
|
attack_mod: rollExpression||null,
|
|
});
|
|
}
|
|
else if (threatBase) h.mergeObj(weaponData, {threat: threatBase});
|
|
else if (rangedBase) h.mergeObj(weaponData, {ranged: rangedBase});
|
|
else if (dTypeBase) h.mergeObj(weaponData, {damage_type: dTypeBase});
|
|
}));
|
|
}
|
|
if (/damage/i.test(trigger.type)) {
|
|
if (/^cdam/.test(trigger.type) || itemMods[0] === 'custom') {
|
|
let customDamage = await as.getAttrs([`repeating_weapon_${row}_damage_custom`]).then(v=>(Object.values(v)||[])[0]||null);
|
|
h.mergeObj(weaponData, {
|
|
damage: customDamage
|
|
});
|
|
} else if (/^dam/i.test(trigger.type)) {
|
|
let damageBase = itemMods[0];
|
|
if (!warlockData.weaponTypes[damageBase]) return h.log(damageBase, 'warn', 'weaponUpdate: illegal value received');
|
|
h.mergeObj(weaponData, {
|
|
damage: warlockData.weaponTypes[damageBase].damage||null,
|
|
damage_type: warlockData.weaponTypes[damageBase].type||null,
|
|
attack_skill: warlockData.weaponTypes[damageBase].skill||null,
|
|
ranged: warlockData.weaponTypes[damageBase].ranged||'0',
|
|
threat: warlockData.weaponTypes[damageBase].threat||'casual'
|
|
});
|
|
}
|
|
}
|
|
if (/(flags|cdam)/i.test(trigger.type)) {
|
|
let reqAttrs = ['damage_type', 'ranged', 'threat'];
|
|
let attrs = await as.getAttrs(reqAttrs.map(a => `repeating_weapon_${row}_${a}`));
|
|
reqAttrs.map(a => weaponData[a] = attrs[`repeating_weapon_${row}_${a}`]);
|
|
}
|
|
// Finish Item flags
|
|
flags.push(weaponData.threat === 'martial' ? weaponFlagMap.martial : weaponFlagMap.casual);
|
|
if (Object.keys(weaponFlagMap).includes(weaponData.damage_type)) flags.push(weaponFlagMap[weaponData.damage_type]);
|
|
if (weaponData.ranged == 1) flags.push(weaponFlagMap.ranged);
|
|
weaponData.flags = flags.join('');
|
|
// Process flag-only update
|
|
if (/^flag/i.test(trigger.type)) output[`repeating_weapon_${row}_flags`] = weaponData.flags;
|
|
// Process damage_base or possession weapon update
|
|
else {
|
|
let existingWeapon = (/damage/i.test(trigger.type)) ? row : (linkData.weaponRowId && weaponRowIds.includes(`${linkData.weaponRowId}`.toLowerCase())) ? linkData.weaponRowId : null;// || Object.entries(attrs).find(a => a[1].toLowerCase() === linkData.possessionRowId && /_link/i.test(a[0]));
|
|
if (!existingWeapon) {
|
|
let newAttrs = h.createRepRow('weapon', Object.keys(weaponData), Object.values(weaponData), {returnId: true});
|
|
h.mergeObj(output, Object.values(newAttrs)[0]);
|
|
let newRowId = Object.keys(newAttrs)[0];
|
|
output[`repeating_possession_${linkData.possessionRowId}_weapon_link`] = newRowId;
|
|
} else {
|
|
h.log(existingWeapon, 'log', 'Found existing weapon');
|
|
//let rowId = h.getRowId(existingWeapon[0]);
|
|
let newAttrs = h.createRepRow('weapon', Object.keys(weaponData), Object.values(weaponData), {existingId: existingWeapon});
|
|
h.mergeObj(output, newAttrs);
|
|
}
|
|
}
|
|
// Output object should be ready for setting
|
|
h.log(output, 'warn', `updateWeapon: final output`);
|
|
as.setAttrs(output, {silent:true});
|
|
};
|
|
|
|
const handleReadiedWeapon = async (readiedAttrs, trigger) => {
|
|
let output = {};
|
|
delete readiedAttrs.react;
|
|
for (let r in readiedAttrs) output[r] = 0;
|
|
output[trigger[0]] = trigger[1];
|
|
output.readied_weapon = (trigger[1] == 1) ? h.getRowId(trigger[0])||0 : 0;
|
|
h.log(output, 'warn', `handleReadiedWeapon output`);
|
|
as.setAttrs(output, {silent:true});
|
|
};
|
|
|
|
const applyEffect = async (effectData) => {
|
|
let attrs = ['name', 'mods', 'source', 'duration', 'duration_type', 'options-flag'];
|
|
let durationExpression = (effectData.duration.match(/(\d*d\d+)/)||[])[1];
|
|
let durationValue;
|
|
if (durationExpression) {
|
|
let durationBase = `&{template:wart-description}{{title=Duration Roll}}{{charname=@{character_name}}} {{subtitle=${effectData.name}}} {{description=${effectData.source} takes effect for [[${durationExpression}]] ${effectData.durationType}.}}`;
|
|
let durationRoll = await startRoll(durationBase);
|
|
durationValue = (isNaN(durationRoll.results.description.result)) ? 0 : durationRoll.results.description.result;
|
|
finishRoll(durationRoll.rollId);
|
|
} else {
|
|
durationValue = (effectData.duration.match(/\d+/)||[])[0] || 0;
|
|
}
|
|
let values = [effectData.name||'New Effect', effectData.effect||'', effectData.source||'Spell', durationValue, effectData.durationType||'rounds', '0'];
|
|
let output = h.createRepRow('effect', attrs, values);
|
|
as.setAttrs(output);
|
|
};
|
|
|
|
const linkAbility = async (rowId) => {
|
|
let abRow = `repeating_monster-ability_${rowId}`,
|
|
reqAttrs = ['name', 'details'];
|
|
let attrs = await as.getAttrs(reqAttrs.map(a=>`${abRow}_${a}`));
|
|
let abilityBase = `&{template:wart-description}{{title=Monster Ability}}{{color=#915e00}}{{charname=@{character_name}}} {{subtitle=${attrs[`${abRow}_name`]||'Ability'}}} {{description=${attrs[`${abRow}_details`]||'-'}}}`;
|
|
let abilityRoll = await startRoll(abilityBase);
|
|
finishRoll(abilityRoll.rollId);
|
|
};
|
|
|
|
return {
|
|
getAllModAttrs,
|
|
calculateEffectMods,
|
|
handlePossessionChange,
|
|
handleEffectChange,
|
|
updateWeapon,
|
|
handleReadiedWeapon,
|
|
applyEffect,
|
|
linkAbility,
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* 7. DICE ROLL FUNCTIONS
|
|
* Normal & Reactive roll functions
|
|
*/
|
|
const roll = (() => {
|
|
// Template-related constants
|
|
const t = {
|
|
roll: `&{template:wart-roll}`,
|
|
damage: `&{template:wart-damage}`,
|
|
desc: `&{template:wart-description}`
|
|
}
|
|
const color = {
|
|
skill: `{{color=#004f00}}`,
|
|
attack: `{{color=#a20000}}`,
|
|
defend: `{{color=#600404}}`,
|
|
spell: `{{color=#570257}}`,
|
|
damage: `{{color=#07043f}}`,
|
|
description: ``,
|
|
}
|
|
const prop = {
|
|
coreDie: `1d20`,
|
|
skillTarget: 20,
|
|
name: `{{charname=@{character_name}}}`,
|
|
reactInit: `{{react=1}}`,
|
|
skillAbbr(skill) {return skill.slice(0, 4).toUpperCase()},
|
|
async getWeaponAttrs(rowId, nonRepAttrs=[]) {
|
|
nonRepAttrs.push('npc', 'global_attack_bonus', 'global_damage_bonus');
|
|
let defWeapRow = `repeating_weapon_${rowId}`;
|
|
let rowAttrs = (h.checkRowId(rowId)) ? weaponAttributes.map(a=>`${defWeapRow}_${a}`) : [];
|
|
let wAttrs = await as.getAttrs(h.toArray(nonRepAttrs).concat(rowAttrs).concat(skillsBonus).concat(skills));
|
|
let output = {
|
|
name: wAttrs[`${defWeapRow}_name`],
|
|
ranged: (wAttrs[`${defWeapRow}_ranged`] == 1) ? 1 : 0,
|
|
skillName: (wAttrs.npc == 1) ? wAttrs[`${defWeapRow}_name`] : wAttrs[`${defWeapRow}_attack_skill`],
|
|
skill: (wAttrs.npc == 1 || /custom/i.test(wAttrs[`${defWeapRow}_attack_skill`])) ? parseInt(wAttrs[`${defWeapRow}_attack_mod`])
|
|
: wAttrs[wAttrs[`${defWeapRow}_attack_skill`]]||null,
|
|
skillMod: (wAttrs.npc ==1) ? null : parseInt(wAttrs[`${defWeapRow}_attack_mod`]) > 0 ? wAttrs[`${defWeapRow}_attack_mod`] : null,
|
|
skillBonus: (wAttrs.npc ==1) ? null : wAttrs[`${wAttrs[`${defWeapRow}_attack_skill`]}_bonus`]||null,
|
|
damage: wAttrs[`${defWeapRow}_damage`],
|
|
damageType: wAttrs[`${defWeapRow}_damage_type`],
|
|
react: (wAttrs.react == 1) ? prop.reactInit : null,
|
|
npc: wAttrs.npc == 1 ? true : false,
|
|
globalAttack: wAttrs.global_attack_bonus||null,
|
|
globalDamage: wAttrs.global_damage_bonus||null,
|
|
weaponId: rowId,
|
|
attrs: wAttrs,
|
|
}
|
|
h.log(output, 'info', `getWeaponAttrs output:`);
|
|
return output;
|
|
},
|
|
async getSpellAttrs(rowId, nonRepAttrs=[]) {
|
|
let defSpRow = `repeating_spell_${rowId}`;
|
|
let spAttrs = await as.getAttrs(nonRepAttrs.concat(spellAttributes.map(a=>`${defSpRow}_${a}`)));
|
|
let output = {
|
|
name: spAttrs[`${defSpRow}_name`]||'Spell',
|
|
type: (spAttrs[`${defSpRow}_type`]||'').toLowerCase()||'spellcard',
|
|
desc: spAttrs[`${defSpRow}_description`]||'',
|
|
effect: spAttrs[`${defSpRow}_mods`]||'',
|
|
cost: spAttrs[`${defSpRow}_cost`]||0,
|
|
skillName: spAttrs[`${defSpRow}_attack_skill`],
|
|
skill: /custom/i.test(spAttrs[`${defSpRow}_attack_skill`]) ? spAttrs[`${defSpRow}_attack_mod`]
|
|
: spAttrs[`${defSpRow}_attack_skill`]||null,
|
|
damage: spAttrs[`${defSpRow}_damage`]||null,
|
|
damageType: spAttrs[`${defSpRow}_damage_type`]||null,
|
|
ranged: (spAttrs[`${defSpRow}_ranged`] == 1) ? 1 : 0,
|
|
react: (spAttrs.react == 1) ? prop.reactInit : null,
|
|
duration: spAttrs[`${defSpRow}_duration`]||0,
|
|
durationType: spAttrs[`${defSpRow}_duration_type`]||'rounds',
|
|
npc: spAttrs.npc == 1 ? true : false,
|
|
globalAttack: spAttrs.global_attack_bonus||null,
|
|
globalDamage: spAttrs.global_damage_bonus||null,
|
|
weaponId: rowId,
|
|
attrs: spAttrs,
|
|
}
|
|
return output;
|
|
},
|
|
}
|
|
|
|
const rollEscape = {
|
|
chars: {
|
|
'"': '%quot;',
|
|
',': '%comma;',
|
|
':': '%colon;',
|
|
'}': '%rcub;',
|
|
'{': '%lcub;',
|
|
},
|
|
escape(str) {
|
|
str = (typeof(str) === 'object') ? JSON.stringify(str) : (typeof(str) === 'string') ? str : null;
|
|
return (str) ? `${str}`.replace(new RegExp(`[${Object.keys(this.chars)}]`, 'g'), (r) => this.chars[r]) : null;
|
|
},
|
|
unescape(str) {
|
|
str = `${str}`.replace(new RegExp(`(${Object.values(this.chars).join('|')})`, 'g'), (r) => Object.entries(this.chars).find(e=>e[1]===r)[0]);
|
|
//h.log(str);
|
|
return JSON.parse(str);
|
|
}
|
|
}
|
|
// ROLL HELPERS
|
|
// Outputs an array with [0] as the selected char ID, each other index will match input extra Attrs requested
|
|
const getSelected = async (extraAttributes) => {
|
|
extraAttributes = h.toArray(extraAttributes);
|
|
let attrsText = (extraAttributes) ? extraAttributes.map((a,i) => `{{extra${i}=[[0[@{selected|${a}}]]]}} `) : [];
|
|
let idGrab = await startRoll(`! {{charid=[[0[@{selected|character_id}]]]}} ${attrsText.join('')}`);
|
|
let selectId = (idGrab.results.charid.expression.match(rx.id)||[])[0];
|
|
let attrs = (extraAttributes) ? extraAttributes.map((a,i)=>((idGrab.results[`extra${i}`].expression.match(rx.grabAttr)||[])[1])||null) : [];
|
|
finishRoll(idGrab.rollId);
|
|
let output = [selectId].concat(attrs);
|
|
h.log(output, 'info', `getSelected results:`);
|
|
return output;
|
|
};
|
|
|
|
// Check the player has permission on the action button clicked. Workaround for Roll20 bug.
|
|
const checkSheetPermission = async (charname, attr='npc') => {
|
|
if (charname == null) {
|
|
h.log(charname, 'warn', `Sheet permission check skipped due to bad charName received:`);
|
|
return;
|
|
}
|
|
let res = await startRoll(`! &{noerror} {{grabVal=[[0[@{${charname}|${attr}}] ]] }}`);
|
|
let val = (res.results.grabVal.expression.match(rx.grabAttr)||[])[1];
|
|
let rxFail = new RegExp(`^${h.escapeRegex(charname)}|${attr}$`, 'i');
|
|
let result = true;
|
|
if (rxFail.test(val) || val == null) result = false;
|
|
h.log(result, 'log', 'checkSheetPermission result:');
|
|
finishRoll(res.rollId);
|
|
return result;
|
|
}
|
|
|
|
// ROLL FUNCTIONS
|
|
// Roll & Apply initial values for base stats
|
|
const stamLuck = async (targetStat, result) => {
|
|
let acId = getActiveCharacterId();
|
|
if (!result) { // Send roll to chat for approval
|
|
let formula = /stam/i.test(targetStat) ? `2d6+12` : `1d6+7`;
|
|
let rollBase = `${t.roll}${prop.name} {{title=New Character}} {{subheader=${targetStat}}} {{roll1name=${formula}}} {{roll1=[[${formula}]]}} {{calcbutton=[Accept](~${acId}|apply_${targetStat}}} {{calclink=[[0]]}} {{notes=Click "Accept" to apply roll to character sheet}}`;
|
|
let roll = await startRoll(rollBase);
|
|
let stat = roll.results.roll1.result;
|
|
await finishRoll(roll.rollId, {calclink: `${stat}`});
|
|
} else { // Apply roll once approved
|
|
as.setAttrs({[`${targetStat}_base`]: result, [targetStat]: result});
|
|
}
|
|
}
|
|
|
|
// Roll Skill Check
|
|
const skillCheck = async (skillName, attrName) => {
|
|
attrName = attrName||skillName;
|
|
let attrs = await as.getAttrs([attrName, `${attrName}_bonus`]),
|
|
skillExpression = h.combineRollParts(attrs[attrName]||0, attrs[`${attrName}_bonus`]);
|
|
let rollBase = `${t.roll}${prop.name}${color.skill} {{title=Skill Check}} {{subheader=${h.emproper(skillName)}}} {{roll1name=+${skillExpression}}} {{roll1=[[${prop.coreDie} + ${skillExpression}]]}}`;
|
|
let skillRoll = await startRoll(rollBase);
|
|
let rollData = {};
|
|
finishRoll(skillRoll.rollId, rollData);
|
|
}
|
|
|
|
const luckRoll = async () => {
|
|
let attrs = await as.getAttrs(['luck', 'luckroll_bonus']),
|
|
luckVal = parseInt(attrs.luck),
|
|
hasLuck = (luckVal > 0) ? true : false,
|
|
luckBonus = parseInt(attrs.luckroll_bonus) > 0 ? ` +${attrs.luckroll_bonus}[Bonus]` : ``;
|
|
let rollBase = hasLuck ? `${t.roll}${prop.name} {{title=Luck Check}} {{subheader=Luck}} {{roll1name=+@{luck}[Luck]${luckBonus}}} {{roll1=[[${prop.coreDie} + @{luck}}[Luck]${luckBonus}]]}} {{notes=1 Luck point subtracted, ${luckVal - 1} remaining}}` : `/w "@{character_name}" No luck points to spend!`
|
|
let luckRoll = await startRoll(rollBase);
|
|
// add success/failure in here?
|
|
await finishRoll(luckRoll.rollId);
|
|
if (hasLuck) setAttrs({luck: luckVal-1});
|
|
}
|
|
|
|
const rest = async () => {
|
|
let attrs = await as.getAttrs(['stamina', 'stamina_max']);
|
|
let hiddenQuery = `! {{rest=[[0[?{Short or Full Rest?|Short|Full}]]]}}`;
|
|
let query = await startRoll(hiddenQuery);
|
|
let restType = (query.results.rest.expression.match(/^0\[(.*)\]\s*$/)||[])[1];
|
|
if (!restType) return;
|
|
let restMultiplier = /short/i.test(restType) ? 0.5 : 1,
|
|
maxRestored = restMultiplier * parseInt(attrs.stamina_max),
|
|
missing = parseInt(attrs.stamina_max) - parseInt(attrs.stamina),
|
|
newStamina = Math.ceil(Math.min(maxRestored, missing)) + parseInt(attrs.stamina);
|
|
await finishRoll(query.rollId);
|
|
let restBase = `${t.damage}${prop.name} {{title=${restType} Rest}} {{subheader=Recover Stamina}} {{damageroll1name=Maximum: +${maxRestored}}} {{damageroll1=Restored [[${Math.ceil(Math.min(missing, maxRestored))}]] Stamina.}} {{notes=@{character_name} restored to ${newStamina}/${attrs.stamina_max} Stamina}}`;
|
|
let restRoll = await startRoll(restBase);
|
|
finishRoll(restRoll.rollId);
|
|
setAttrs({stamina: newStamina});
|
|
}
|
|
|
|
// Standard Defence roll
|
|
const defendRoll = async (rowId) => {
|
|
let wp = await prop.getWeaponAttrs(rowId),
|
|
rollExpression = h.combineRollParts(wp.skill, wp.skillBonus, null, {base: h.emproper(wp.skillName)||'Skill'});
|
|
let rollBase = `${t.roll}${prop.name}${color.defend}{{title=Defend Roll}}{{subheader=${h.emproper(wp.name)}}} {{roll1name=+${rollExpression}}} {{roll1=[[${prop.coreDie} + ${rollExpression}]]}}`;
|
|
let defendRoll = await startRoll(rollBase);
|
|
finishRoll(defendRoll.rollId);
|
|
}
|
|
|
|
const damageRoll = async (rowId) => {
|
|
let wp = await prop.getWeaponAttrs(rowId),
|
|
rollExpression = h.combineRollParts(wp.damage, null, wp.globalDamage);
|
|
let damageBase = `${t.damage}${prop.name}${color.damage}{{title=Damage Roll}}{{subheader=${wp.name}}} {{damageroll1name=${rollExpression}}} {{damageroll1=[[${rollExpression}]]}} {{damagetype= ${h.emproper(wp.damageType)}}} {{link=[Roll Crit](~repeating_weapon_${rowId}_rollcrit)}}`;
|
|
let damageRoll = await startRoll(damageBase);
|
|
finishRoll(damageRoll.rollId);
|
|
}
|
|
|
|
const critRoll = async (rowId) => {
|
|
let wp = await prop.getWeaponAttrs(rowId);
|
|
let damageType = wp.damageType.toLowerCase();
|
|
let critMod = await startRoll(`! {{mod=[[0[?{Bonus to crit roll?|0|1|2|3|4|5|6|7|8+,8}]]]}}`).then(async (r) =>{
|
|
let mod = (r.results.mod.expression.match(rx.grabAttr)||[])[1]||0;
|
|
finishRoll(r.rollId);
|
|
return mod;
|
|
});
|
|
if (!isNaN(critMod) && warlockData.getDamageTypes().includes(damageType)) {
|
|
let critTop = `${t.desc}{{title=Critical Strike!}} {{subtitle=${h.emproper(damageType)} - [[2d6 + ${critMod}]]}} {{description=@{character_name} rolls for a crit.}} {{classdesc=crit}}`;
|
|
let critRollTop = await startRoll(critTop);
|
|
let critResult = Math.min(10, Math.max(critRollTop.results.subtitle.result, 2));
|
|
let critString = warlockData.critTables[damageType][critResult];
|
|
let critBottom = `${t.desc}${prop.name}{{class=bottom}}{{description=${critString}}}`;
|
|
let critRollBottom = await startRoll(critBottom);
|
|
await finishRoll(critRollTop.rollId);
|
|
finishRoll(critRollBottom.rollId);
|
|
}
|
|
}
|
|
|
|
// Hybrid reactive/non-reactive roll
|
|
const spellRoll = async (rowId) => {
|
|
let sp = await prop.getSpellAttrs(rowId, ['incantation', 'incantation_bonus', 'stamina', 'react', 'character_name']),
|
|
stamina = parseInt(sp.attrs.stamina) > 0 ? parseInt(sp.attrs.stamina) : null;
|
|
h.log(sp, 'info', `spellRoll getAttrs`);
|
|
if (!stamina || stamina < sp.cost) {
|
|
let abort = startRoll(`${t.desc}{{description=@{character_name} doesn't have the ${sp.cost} Stamina to cast ${sp.name}}}`);
|
|
finishRoll(abort.rollId);
|
|
} else {
|
|
let acId = getActiveCharacterId(),
|
|
spellExpression = h.combineRollParts(sp.attrs.incantation||0, sp.attrs.incantation_bonus||0, null);
|
|
let spellBase = `${t.roll}${color.spell}${prop.name}${sp.react} {{title=${sp.name} Casting Check}} {{success=[[0]]}} {{reactdata=[[0]]}} {{reactbutton1name=[[1]]}} {{reactbutton2name=[[2]]}} {{outcome=[[0]]}} {{reactsubheader=Incantation}} {{reactroll1name=+${spellExpression}}} {{reactroll1=[[${sp.react ? `${prop.coreDie} + ${spellExpression}` : `0`}]]}} {{subheader=Incantation}} {{roll1name=+${spellExpression}}} {{roll1=[[${sp.react ? `0` : `${prop.coreDie}`} +${spellExpression}]]}} {{reactbutton3name=[[3]]}} {{linksuccess=[Cast](~repeating_spell_${rowId}_rollcast)}} {{linkfailure=On a [[1cf1cs0]] - [Miscast](~rollmiscast)}} ${sp.cost ? `{{notes=@{character_name}'s Stamina reduced by ${sp.cost}}}` : ``} {{reactbutton1label=[0](~${acId}|react-cast}} {{reactbutton2label=[0](~${acId}|react-miscast}}`;
|
|
let spellRoll = await startRoll(spellBase);
|
|
let rollResult = (sp.react) ? spellRoll.results.reactroll1.result : spellRoll.results.roll1.result;
|
|
let castSuccess = (rollResult - prop.skillTarget > -1);
|
|
let critFail = (sp.react && spellRoll.results.reactroll1.dice[0] < 2) ? true : null;
|
|
h.mergeObj(sp, {
|
|
roll: rollResult,
|
|
cast: castSuccess,
|
|
row: rowId,
|
|
id: acId,
|
|
charname: sp.attrs.character_name,
|
|
});
|
|
let rollData = {
|
|
success: rollResult - prop.skillTarget,
|
|
outcome: castSuccess ? `Spell cast successfully` : `Spell miscast!`,
|
|
reactdata: rollEscape.escape(sp),
|
|
};
|
|
if (castSuccess) rollData.reactbutton1name = `Cast Spell`;
|
|
else if (critFail) rollData.reactbutton2name = `Wrath of the Otherworld Roll`;
|
|
else rollData.reactbutton3name = `None, spellcast failed.`
|
|
as.setAttrs({stamina: stamina - sp.cost});
|
|
finishRoll(spellRoll.rollId, rollData);
|
|
}
|
|
};
|
|
|
|
// Wrath of the Otherworld check
|
|
const spellMiscast = async (rollData) => {
|
|
let spellname = (rollData) ? `${h.emproper(rollData.name)}` : 'Spell';
|
|
let attrs = await as.getAttrs(['incantation', 'incantation_bonus']),
|
|
rollExpression = h.combineRollParts(attrs.incantation, attrs.incantation_bonus, null),
|
|
miscastBase = `${t.roll}${color.spell}${prop.name} {{title=${spellname} Miscast Check}} {{subheader=Incantation}} {{roll1name=+${rollExpression }}} {{roll1=[[${prop.coreDie} + ${rollExpression}]]}} `;
|
|
let miscastRoll = await startRoll(miscastBase);
|
|
let mcResult = miscastRoll.results.roll1.result;
|
|
finishRoll(miscastRoll.rollId);
|
|
if (mcResult < prop.skillTarget) {
|
|
let wrathTop = `${t.desc}${color.spell} {{title=Wrath of the Otherworld}} {{subtitle=${prop.coreDie} - [[1d20]]}} {{description=***@{character_name}*** has attracted the Wrath of the Otherworld!}} {{classdesc=crit}}`;
|
|
let wrathTopRoll = await startRoll(wrathTop),
|
|
result = Math.min(Math.max(wrathTopRoll.results.subtitle.result, 1), 20),
|
|
wrathString = warlockData.miscastTable[result];
|
|
let wrathBottom = `${t.desc}${prop.name}${color.spell} {{description=${wrathString}}}`;
|
|
let wrathBottomRoll = await startRoll(wrathBottom);
|
|
await finishRoll(wrathTopRoll.rollId);
|
|
finishRoll(wrathBottomRoll.rollId);
|
|
}
|
|
};
|
|
|
|
// Reactive version
|
|
on('clicked:react-miscast', async (ev) => {
|
|
let spellData = rollEscape.unescape(ev.originalRollId);
|
|
if (!await checkSheetPermission(spellData.charname)) return h.log(`Failed action button permission check`, 'error', `react-apply-damage`);
|
|
h.log(spellData, 'info', 'Spell passthrough');
|
|
spellMiscast(spellData);
|
|
})
|
|
|
|
// Successful Cast
|
|
const spellCast = async (rowId) => {
|
|
let sp = await prop.getSpellAttrs(rowId);
|
|
if (sp.type === 'attack') {
|
|
attackRoll(rowId, true);
|
|
} else {
|
|
let castBase = `${t.desc}${prop.name}${color.spell}{{title=${sp.name}}} {{subtitle=Successfully cast for ${sp.cost} Stamina}} {{description=${sp.desc}}}`;
|
|
let castRoll = await startRoll(castBase);
|
|
finishRoll(castRoll.rollId);
|
|
}
|
|
}
|
|
|
|
// Reactive cast
|
|
on('clicked:react-cast', async (ev) => {
|
|
let spellData = rollEscape.unescape(ev.originalRollId);
|
|
if (!await checkSheetPermission(spellData.charname)) return h.log(`Failed action button permission check`, 'error', `react-apply-damage`);
|
|
h.log(spellData, 'info', 'Spell passthrough');
|
|
if (spellData.type === 'attack') {
|
|
attackRoll(spellData.row, true);
|
|
} else {
|
|
let fx = rxModTypes.test(spellData.effect) ? `\n***Effects:*** ${spellData.effect}` : ``;
|
|
let castBase = `${t.desc}${prop.name}${color.spell}{{reactdescription=[[0]]}} {{reactdata=[[0]]}} {{reactbutton1name=[[1]]}} {{title=${spellData.name}}} {{subtitle=Successfully cast for ${spellData.cost} Stamina}} {{description=${spellData.desc}${fx}}} {{reactbutton1label=[3](~selected|apply-effect}}`;
|
|
let castRoll = await startRoll(castBase);
|
|
let rollPassThrough = {
|
|
effect: spellData.effect,
|
|
source: 'Spell',
|
|
name: spellData.name||'New Effect',
|
|
duration: spellData.duration||0,
|
|
durationType: spellData.durationType||'rounds',
|
|
sourceCharacter: spellData.charId
|
|
};
|
|
let rollData = {
|
|
reactdata: rollEscape.escape(rollPassThrough),
|
|
};
|
|
if (fx) rollData.reactbutton1name = `Apply spell Effect to selected`;
|
|
finishRoll(castRoll.rollId, rollData);
|
|
}
|
|
})
|
|
|
|
const spellLink = async (rowId) => {
|
|
let sp = await prop.getSpellAttrs(rowId),
|
|
type = /attack/i.test(sp.type) ? `Attack Spell` : `Spell`,
|
|
desc = sp.desc||'No description.',
|
|
attackString = /attack/i.test(sp.type) ? `Skill: ${h.emproper(sp.skillName)||'Custom'}${sp.ranged ? `\nRanged Attack` : ``}\nDamage: ${sp.damage||`Unknown`} ${sp.damageType}\n-=-\n` : ``;
|
|
let linkBase = `${t.desc} {{color=grey}} {{charname=Spell not cast}} {{title=${sp.name}\nSpell Information}} {{subtitle=${type}}} {{description=${attackString}${desc}}};`;
|
|
let linkRoll = await startRoll(linkBase);
|
|
finishRoll(linkRoll.rollId);
|
|
}
|
|
|
|
// Hybrid function, does normal roll and initiates Attack Reaction chain if {{react=1}}
|
|
const attackRoll = async (rowId, spellAttack=false) => {
|
|
let acId = getActiveCharacterId();
|
|
let att = (!spellAttack) ? await prop.getWeaponAttrs(rowId, ['react', 'character_name']) : await prop.getSpellAttrs(rowId, ['react', 'character_name']),
|
|
rollExpression = h.combineRollParts(att.skill, att.skillBonus, att.globalAttack, {base: h.emproper(att.skillName)}, {Mod: att.skillMod, Attacker: `5`});
|
|
let attackRollBase = `${t.roll} ${prop.name} ${spellAttack ? color.spell : color.attack} ${att.react ? att.react : ''} {{link=[Roll Damage](~repeating_weapon_${rowId}_rolldamage)}} {{title=Attack Roll}} {{subheader=${h.emproper(att.name)}}} {{reactsubheader=${h.emproper(att.name)}}} {{roll1name=+ ${rollExpression}}} {{roll1=[[${att.react ? '0' : `${prop.coreDie} + ${rollExpression}`}]]}} {{ranged=[[${att.ranged}]]}} {{reactroll1name=+ ${rollExpression}}} {{reactroll1=[[${att.react ? `${prop.coreDie} + ${rollExpression}` : '0'}]]}} {{reactbutton1label=[0](~selected|react-defend}} {{reactbutton1name=[[0]]}} {{reactdata=[[1]]}}`;
|
|
//h.log(att, 'info', `attackRoll getAttrs data`);
|
|
let attackRoll = await startRoll(attackRollBase);
|
|
h.mergeObj(att, {
|
|
charname: att.attrs.character_name,
|
|
id: acId,
|
|
result: attackRoll.results.reactroll1.result,
|
|
spellAttack: spellAttack,
|
|
});
|
|
let rollString = rollEscape.escape(att);
|
|
await finishRoll(attackRoll.rollId, {
|
|
reactdata: rollString,
|
|
reactbutton1name: `Defend Selected Token`,
|
|
reactroll1: attackRoll.results.reactroll1.result,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* REACTION ROLLS BELOW - EXPERIMENTAL!
|
|
*/
|
|
|
|
|
|
// This is a big roll function - it needs to pick up data from whoever clicks the "defend selected token button", then find all their defend data, then do the roll, then work out which buttons to show depending on the outcome
|
|
on('clicked:react-defend', async (ev) => {
|
|
// Grab attack info from passthrough
|
|
//h.log(ev, 'log', 'react-defend passthrough');
|
|
let att = rollEscape.unescape(ev.originalRollId);
|
|
// Get defender info
|
|
let selectedAttrs = await getSelected('readied_weapon');
|
|
if (!selectedAttrs || !selectedAttrs.length) return h.log(selectedAttrs, 'error', `getSelected failed!`);
|
|
as.setActiveCharacterId(selectedAttrs[0]);
|
|
let def = await prop.getWeaponAttrs(selectedAttrs[1], ['character_name', 'block_value', 'block_items']);
|
|
def.id = selectedAttrs[0];
|
|
def.charname = def.attrs.character_name;
|
|
// If no weapon is readied, or if the attack was ranged, use DODGE
|
|
if (!def.skill || att.ranged) h.mergeObj(def, {
|
|
name: 'dodge',
|
|
skillName: 'dodge',
|
|
skill: def.attrs.dodge||0,
|
|
skillBonus: def.attrs.dodge_bonus||0,
|
|
globalAttack: null,
|
|
});
|
|
// Construct roll expressions & roll template
|
|
let defendExpression = h.combineRollParts(def.skill, def.skillBonus, null, {base: h.emproper(def.skillName)}),
|
|
blockExpression = /\d/.test(def.attrs.block_value) ? ` + (${def.attrs.block_value})[Block]` : ``,
|
|
blockString = blockExpression.length ? `\n${`${def.attrs.block_items}`.replace(/\s*,\s*/g, '\\n')}` : '';
|
|
h.log([att,def], 'log', `Attacker/Defender info`);
|
|
|
|
let defendRollBase = `${t.roll}${color.defend}${prop.reactInit} {{title=Defend Roll}} {{charname=@{selected|character_name}}} {{reactsubheader=${def.name}}} {{reactdata=[[0]]}} {{reactbutton1name=[[1]]}} {{reactbutton2name=[[2]]}} {{reactbutton3name=[[3]]}} {{reactbutton4name=[[4]]}} {{reactbutton5name=[[5]]}} {{reactbutton6name=[[6]]}} {{reactroll1name=+ ${defendExpression}${blockString}}} {{reactroll1=[[${prop.coreDie} + ${defendExpression}${blockExpression}]]}} {{reactbutton1label=[0](~${att.id}|react-damage}} {{reactbutton2label=[0](~${def.id}|react-damage}} {{reactbutton4label=[0](~${att.id}|react-luck}} {{reactbutton5label=[0](~${def.id}|react-luck}} {{reactvs=vs ${att.charname}'s attack roll: ${att.result}}} {{reactoutcome=[[8]]}}`;
|
|
// Start the roll
|
|
let defendRoll = await startRoll(defendRollBase);
|
|
//h.log(defendRoll, 'info');
|
|
// Find winner of contest, details & margin
|
|
def.result = defendRoll.results.reactroll1.result;
|
|
let winnerAD = (def.result === att.result) ? null : (def.result > att.result) ? 'defender' : 'attacker';
|
|
let mightyBlow = (Math.max(def.result, att.result) / Math.min(def.result, att.result) >= 3) ? true : false;
|
|
let dodgeOnly = (winnerAD === 'defender' && def.skillName === 'dodge') ? true : false;
|
|
// Assemble pass-through data
|
|
delete def.attrs;
|
|
let rollPassThrough = {
|
|
attacker: att,
|
|
defender: def,
|
|
result: {
|
|
winner: winnerAD,
|
|
loser: (winnerAD) ? (winnerAD === 'defender') ? 'attacker' : 'defender' : null,
|
|
mighty: mightyBlow,
|
|
dodge: dodgeOnly,
|
|
}
|
|
}
|
|
// Assemble the roll data for finishRoll()
|
|
let rollData = {
|
|
reactoutcome: (!winnerAD) ? `The contest was drawn! ${att.charname} or ${def.charname} can try their luck to break the stalemate.` : (dodgeOnly) ? `${def.charname} dodged the attack!` : `${rollPassThrough[winnerAD].charname} won the contest!`,
|
|
reactdata: rollEscape.escape(rollPassThrough),
|
|
};
|
|
// Enable the appropriate reactive roll buttons, depending on the outcome
|
|
if (winnerAD === 'attacker') {
|
|
h.mergeObj(rollData, {
|
|
reactbutton1name: `${att.charname} rolls damage:`
|
|
});
|
|
} else if (winnerAD === 'defender' && !dodgeOnly) {
|
|
h.mergeObj(rollData, {
|
|
reactbutton2name: `${def.charname} rolls damage:`
|
|
});
|
|
} else if (dodgeOnly) {
|
|
h.mergeObj(rollData, {
|
|
reactbutton3name: `None - ${def.charname} dodged the attack.`
|
|
});
|
|
} else if (!winnerAD) {
|
|
h.mergeObj(rollData, {
|
|
reactbutton4name: `${att.charname} tries their luck:`,
|
|
reactbutton5name: `${def.charname} tries their luck:`,
|
|
reactbutton6name: `Otherwise, the contest was a draw.`
|
|
});
|
|
}
|
|
//h.log(rollData, 'warn', `Finalising react-defend roll`);
|
|
await finishRoll(defendRoll.rollId, rollData);
|
|
});
|
|
|
|
on('clicked:react-luck', async (ev) => {
|
|
let drawData = rollEscape.unescape(ev.originalRollId),
|
|
att = drawData.attacker,
|
|
def = drawData.defender,
|
|
res = drawData.result;
|
|
h.log(drawData, 'info', 'REACT-LUCK: passthrough');
|
|
let attrs = await as.getAttrs(['luck', 'luckroll_bonus', 'character_id', 'character_name']);
|
|
if (!await checkSheetPermission(attrs.character_name)) return h.log(`Player lacks permission on button`, 'error', 'react-luck');
|
|
let luckVal = parseInt(attrs.luck)||null,
|
|
roller = (attrs.character_id === att.id) ? 'attacker' : (attrs.character_id === def.id) ? 'defender' : null,
|
|
bonus = parseInt(attrs.luckroll_bonus) > 0 ? ` + ${attrs.luckroll_bonus}[Bonus]` : ``;
|
|
if (!luckVal > 0 || !roller) {
|
|
let abortBase = (!roller) ? `Error: couldn't identify luck roller as defender or attacker` : `/w "@{character_name}" No luck points left to spend!`;
|
|
let abortRoll = await startRoll(abortBase);
|
|
finishRoll(abortRoll.rollId);
|
|
} else {
|
|
let luckBase = `${t.roll}${prop.name}${prop.reactInit} {{title=Luck Check}} {{reactsubheader=Luck}} {{reactdata=[[0]]}} {{reactbutton1name=[[1]]}} {{reactbutton2name=[[2]]}} {{reactbutton3name=[[3]]}} {{reactbutton1label=[0](~${att.id}|react-damage}} {{reactbutton2label=[0](~${def.id}|react-damage}} {{reactroll1name=+@{luck}[Luck]${bonus}}} {{reactroll1=[[${prop.coreDie} + @{luck}[Luck]${bonus}]]}} {{reactoutcome=[[0]]}} {{notes=1 Luck point subtracted, ${luckVal - 1} remaining}}`;
|
|
let luckRoll = await startRoll(luckBase);
|
|
let success = luckRoll.results.reactroll1.result >= prop.skillTarget ? 1 : 0,
|
|
winnerAD = (success) ? roller : (roller === 'attacker') ? 'defender' : 'attacker',
|
|
dodgeOnly = (winnerAD === 'defender' && def.skill === 'dodge') ? true : false;
|
|
let rollPassThrough = {
|
|
attacker: att,
|
|
defender: def,
|
|
result: {
|
|
winner: winnerAD,
|
|
loser: (winnerAD) ? (winnerAD === 'defender') ? 'attacker' : 'defender' : null,
|
|
mighty: res.mightyBlow,
|
|
dodge: dodgeOnly,
|
|
}
|
|
}
|
|
let rollData = {
|
|
reactoutcome: (success) ? `${attrs.character_name} succeeded on their luck check and won the exchange!` : `${attrs.character_name} failed their luck check and lost the exchange!`,
|
|
reactdata: rollEscape.escape(rollPassThrough),
|
|
}
|
|
if (winnerAD === 'attacker') {
|
|
h.mergeObj(rollData, {
|
|
reactbutton1name: `${att.charname} rolls damage:`
|
|
});
|
|
} else if (winnerAD === 'defender' && !dodgeOnly) {
|
|
h.mergeObj(rollData, {
|
|
reactbutton2name: `${def.charname} rolls damage:`
|
|
});
|
|
} else if (dodgeOnly) {
|
|
h.mergeObj(rollData, {
|
|
reactbutton3name: `None - ${def.charname} dodged the attack.`
|
|
});
|
|
}
|
|
finishRoll(luckRoll.rollId, rollData);
|
|
}
|
|
});
|
|
|
|
on('clicked:react-damage', async (ev) => {
|
|
//h.log(ev, 'info', `Click event`);
|
|
let attackResult = rollEscape.unescape(ev.originalRollId);
|
|
h.log(attackResult, 'info', `REACT-DAMAGE: rollData passthrough`);
|
|
let winner = attackResult[attackResult.result.winner],
|
|
loser = attackResult[attackResult.result.loser];
|
|
if (!winner || !loser) return h.log(attackResult, 'error', `react-damage: Couldn't determine winner from attack`);
|
|
if (!await checkSheetPermission(winner.charname)) return h.log(`Failed action button permission check`, 'error', `react-apply-damage`);
|
|
let mightyBlow = attackResult.result.mighty ? `{{mighty=1}}` : '';
|
|
as.setActiveCharacterId(winner.id);
|
|
as.setActiveCharacterId(loser.id);
|
|
let def = await as.getAttrs(['armour_value', 'armour_items', 'stamina']);
|
|
//h.log(d, 'info', `loser defence`);
|
|
let hasArmour = (!/\w+/.test(def.armour_items)) ? false : true;
|
|
let damageBase = `${t.damage}${color.damage}${prop.reactInit}{{charname=${winner.charname}}} {{title=Damage Roll}}{{subheader=${winner.name}}} {{damagetype= ${h.emproper(winner.damageType)}}} {{damageroll1name=${winner.damage}}} {{reactdata=[[0]]}} {{reactbutton1name=[[1]]}} {{reactbutton2name=[[2]]}} {{damageroll1=[[${winner.damage}]]}} {{defencename=${loser.charname}'s defence-\n ${hasArmour ? `${def.armour_items}`.replace(/\s*,\s*/g, '\\n') : 'none'}}} {{defenceroll=[[${def.armour_value||0}]] DR}} {{outcome=${loser.charname} takes [[4]] damage.}} {{reactbutton1label=[3](~${loser.id}|react-apply-damage}} {{reactbutton2label=[0](~${winner.id}|react-roll-crit}} ${mightyBlow}`;
|
|
h.log(damageBase, 'info', `Debug`);
|
|
let damageRoll = await startRoll(damageBase);
|
|
let damageTotal = parseInt(damageRoll.results.damageroll1.result)||0,
|
|
damageReduction = parseInt(damageRoll.results.defenceroll.result)||0,
|
|
damageNet = damageTotal - damageReduction,
|
|
damageFinal = attackResult.result.mighty ? Math.max(damageNet*2, 1) : Math.max(damageNet, 1),
|
|
loserStamina = parseInt(def.stamina) - damageFinal;
|
|
let rollData = {
|
|
outcome: damageFinal,
|
|
reactbutton1name: `${loser.charname} takes ${damageFinal} damage.`,
|
|
reactdata: rollEscape.escape({
|
|
damage: {
|
|
id: loser.id,
|
|
charname: loser.charname,
|
|
damageValue: damageFinal,
|
|
newStaminaValue: loserStamina,
|
|
},
|
|
crit: {
|
|
id: winner.id,
|
|
charname: winner.charname,
|
|
critMod: Math.abs(loserStamina),
|
|
critTable: winner.damageType,
|
|
}
|
|
}),
|
|
};
|
|
if (loserStamina < 1) rollData.reactbutton2name = `${winner.charname} rolls critical effect!`;
|
|
finishRoll(damageRoll.rollId, rollData);
|
|
});
|
|
|
|
on('clicked:react-apply-damage', async (ev) => {
|
|
let damageData = rollEscape.unescape(ev.originalRollId);
|
|
if (!await checkSheetPermission(damageData.damage.charname)) return h.log(`Failed action button permission check`, 'error', `react-apply-damage`);
|
|
//as.setActiveCharacterId(damageData.damage.id);
|
|
h.log(damageData, 'info', `Damage passed through`);
|
|
//let damageReport = `${t.desc}${color.damage}${prop.name}{{title=Stamina Reduced}}{{description=***${damageData.damage.charname}*** dropped to [[${damageData.damage.newStaminaValue}]] Stamina.}}`
|
|
let damageReport = `/w "${damageData.damage.charname}" ${damageData.damage.damageValue} damage applied, ${damageData.damage.newStaminaValue} Stamina remaining.`;
|
|
if (!isNaN(damageData.damage.newStaminaValue)) {
|
|
as.setAttrs({stamina: damageData.damage.newStaminaValue});
|
|
let reportRoll = await startRoll(damageReport);
|
|
finishRoll(reportRoll.rollId);
|
|
} else {
|
|
let abort = await startRoll(`/w "${damageData.damage.charname}" Error: bad Stamina value received, Stamina not automatically reduced.`);
|
|
finishRoll(abort.rollId);
|
|
}
|
|
});
|
|
|
|
on('clicked:react-roll-crit', async (ev) => {
|
|
let critData = rollEscape.unescape(ev.originalRollId);
|
|
if (!await checkSheetPermission(critData.crit.charname)) return h.log(`Failed sheet permission check`, 'error', `react-roll-crit`);
|
|
h.log(critData, 'warn', `Crit data passed through`);
|
|
as.setActiveCharacterId(critData.crit.id);
|
|
let damageType = critData.crit.critTable.toLowerCase();
|
|
if (!isNaN(critData.crit.critMod) && warlockData.getDamageTypes().includes(damageType)) {
|
|
let critTop = `${t.desc}{{title=Critical Strike!}} {{subtitle=${h.emproper(damageType)} - [[2d6 + ${critData.crit.critMod}]]}} {{description=***${critData.damage.charname}*** *recieves* a critical wound!}} {{classdesc=crit}}`;
|
|
let critRollTop = await startRoll(critTop);
|
|
let critResult = Math.min(10, Math.max(critRollTop.results.subtitle.result, 2));
|
|
let critString = warlockData.critTables[damageType][critResult];
|
|
let critBottom = `${t.desc}${prop.name}{{description=${critString}}}`;
|
|
let critRollBottom = await startRoll(critBottom);
|
|
await finishRoll(critRollTop.rollId);
|
|
finishRoll(critRollBottom.rollId);
|
|
} else {
|
|
let critAbort = await startRoll(`ReactRoll Error: bad data received for Crit`);
|
|
finishRoll(critAbort.rollId);
|
|
}
|
|
});
|
|
|
|
// Apply Effect button
|
|
on('clicked:apply-effect', (ev) => {
|
|
let effectData = rollEscape.unescape(ev.originalRollId);
|
|
warRepeating.applyEffect(effectData);
|
|
})
|
|
|
|
return {
|
|
stamLuck,
|
|
skillCheck,
|
|
luckRoll,
|
|
rest,
|
|
attackRoll,
|
|
defendRoll,
|
|
damageRoll,
|
|
critRoll,
|
|
spellRoll,
|
|
spellMiscast,
|
|
spellCast,
|
|
spellLink,
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* 8. EVENT LISTENERS
|
|
*/
|
|
{
|
|
on('sheet:opened', async () => {
|
|
const initZeroAttributes = ['npc', 'unlocked'];
|
|
const initTutorialAttributes = ['initial_fives', 'initial_sixes', 'initial_skillpoints'];
|
|
let attrs = await as.getAttrs(['version'].concat(initZeroAttributes).concat(initTutorialAttributes));
|
|
let output = {};
|
|
initTutorialAttributes.forEach(a => {if (!attrs[a] || isNaN(parseInt(attrs[a]))) output[a] = 10});
|
|
initZeroAttributes.forEach(a => {if (attrs[a] != 1) output[a] = '0'});
|
|
if (!attrs.version) output.version = version.current();
|
|
else await versionControl(attrs.version);
|
|
h.log(output, 'info', `sheetOpened init Output`);
|
|
if (Object.keys(output).length) await as.setAttrs(output);
|
|
});
|
|
|
|
// Active tab
|
|
on('clicked:tab_character clicked:tab_settings clicked:tab_help', async (ev) => {
|
|
let newTab = (ev.triggerName.match(/tab_(.*)/)||[])[1];
|
|
as.setAttrs({activetab: newTab});
|
|
});
|
|
|
|
// Disable Character Creation tutorial
|
|
on('clicked:disable-tutorial', () => as.setAttrs({disable_tutorial: 1}));
|
|
|
|
// NPC shortcut
|
|
on('clicked:npc', () => {
|
|
as.setAttrs({disable_tutorial: 1, npc: 1});
|
|
});
|
|
|
|
// Roll for Stamina/Luck on character creation
|
|
on('clicked:roll_stamina clicked:roll_luck', async (ev) => {
|
|
let targetStat = h.emproper(ev.triggerName.match(/roll_([^\s]*)/)[1]);
|
|
roll.stamLuck(targetStat);
|
|
});
|
|
|
|
// Apply the Stamina/Luck roll
|
|
on('clicked:apply_stamina clicked:apply_luck', (ev) => {
|
|
h.log(ev, 'warn');
|
|
let result = parseInt(ev.originalRollId)||0;
|
|
if (!result) return;
|
|
let targetStat = ev.triggerName.match(/apply_([^\s]*)/)[1];
|
|
roll.stamLuck(targetStat, result);
|
|
});
|
|
|
|
// Respond to Stamina & Luck changes
|
|
on('change:stamina_base change:stamina_bonus change:stamina_increase change:luck_base change:luck_bonus', async (ev) => {
|
|
let targetStat = (ev.triggerName.match(/(stamina|luck)/i)||[])[1];
|
|
let attrs = await as.getAttrs([`${targetStat}_base`, `${targetStat}_bonus`, `stamina_increase`]);
|
|
let newVal = (parseInt(attrs[`${targetStat}_base`])||0) + (parseInt(attrs[`${targetStat}_bonus`])||0);
|
|
newVal += (/stamina/i.test(targetStat)) ? parseInt(attrs.stamina_increase)||0 : 0;
|
|
as.setAttrs({[`${targetStat}_max`]: newVal});
|
|
})
|
|
|
|
// Luck Roll
|
|
on('clicked:luckroll', () => roll.luckRoll());
|
|
|
|
// Rest button
|
|
on('clicked:rest', () => roll.rest());
|
|
|
|
// Setup Career skills on selection
|
|
on('change:career change:advanced_career', (ev) => {
|
|
if (!warlockData.getCareerNames().includes(ev.newValue)) return;
|
|
warSheet.getNewCareerSkills(ev.newValue);
|
|
});
|
|
|
|
// Respond to Skill Level changes, includes Character Creation Tutorial
|
|
// && Skill Roll listener
|
|
skills.map(sk => {
|
|
on(`change:${sk}`, (ev) => {
|
|
h.log(ev, 'info', `Skillchange in "${ev.sourceAttribute}" detected:`);
|
|
if (ev.sourceType === 'sheetworker') return;
|
|
warSheet.handleSkillChange(ev);
|
|
});
|
|
on(`clicked:${sk}`, (ev) => {
|
|
let skill = ev.triggerName.replace(/^[^:]*?:/, '');
|
|
roll.skillCheck(skill);
|
|
});
|
|
});
|
|
// Add Career Skills sections to Skill Roll
|
|
on('clicked:repeating_careerskill:past-skill clicked:career-skill clicked:custom-career-skill', async (ev) => {
|
|
h.log(ev, 'info', `Career skill click`);
|
|
let rowid = (ev.sourceAttribute) ? (ev.sourceAttribute.match(/_(-[A-Za-z0-9-]{19})_/)||[])[1] : null;
|
|
let attrNames = (rowid) ? [`repeating_careerskill_${rowid}_name`, `repeating_careerskill_${rowid}_level`] : /custom/i.test(ev.triggerName) ? ['custom_career', 'custom_career_skill_level'] : ['career_skill_name', 'career_skill_level'];
|
|
await as.getAttrs(attrNames).then(v => roll.skillCheck(v[attrNames[0]], attrNames[1]));
|
|
});
|
|
// Add NPC Skill Check to Skill Roll
|
|
on('clicked:npc_skill', async (ev) => {
|
|
h.log(ev, 'info', `NPC Skill Check`);
|
|
// skillVal = await as.getAttrs(['npc_adventure_skill']).then(v=>v['npc_adventure_skill']||0);
|
|
roll.skillCheck('NPC Skill', 'npc_adventure_skill')
|
|
});
|
|
|
|
// Switch to Advanced Careers
|
|
on('clicked:career-toggle', () => {
|
|
h.toggleAttr('career_toggle');
|
|
});
|
|
|
|
// Retire a Career
|
|
on('clicked:retire', () => {
|
|
h.log(`Clicked Retire`);
|
|
warSheet.retireCareer();
|
|
});
|
|
|
|
// Undo a Career retire (for misclicks)
|
|
on('clicked:undo-retire', () => warSheet.undoRetire());
|
|
|
|
// Option gear-icon buttons - folding sections
|
|
on('clicked:repeating_monster-ability:options-btn clicked:repeating_weapon:options-btn clicked:repeating_possession:options-btn clicked:repeating_spell:options-btn clicked:stats_options-btn clicked:main_weapon_options-btn clicked:repeating_effect:options-btn', async (ev) => {
|
|
let trigger = ev.sourceAttribute || ev.triggerName;
|
|
let target = trigger.replace(/clicked:/, '').replace(/:/, '_').replace(/btn/, 'flag');
|
|
h.toggleAttr(target, true);
|
|
});
|
|
|
|
// Currency Converters
|
|
on('clicked:convert-pennies-silver clicked:convert-silver-gold', (ev) => {
|
|
h.log(ev, 'info', `Currency converter triggered`);
|
|
let baseCoin = /pennies/i.test(ev.triggerName) ? 'pennies' : 'silver';
|
|
warSheet.convertCurrency(baseCoin);
|
|
});
|
|
|
|
// Repeating Possessions & Effects - process Item Mods
|
|
on('change:repeating_possession remove:repeating_possession change:repeating_effect remove:repeating_effect', (ev) => {
|
|
//if (ev.sourceType === `sheetworker`) return;x
|
|
h.log(ev);
|
|
let parts = ev.sourceAttribute.match(/repeating_([^_]+?)_(-[A-Za-z0-9-]{19})[_]*(.*)/) || [];
|
|
if (ev.sourceType === 'sheetworker' && parts[3] === 'name') parts[3] = 'mods';
|
|
let supportingValues = ev.removedInfo || {newValue: ev.newValue||null, previousValue: ev.previousValue||null};
|
|
if (/options/.test(parts[3])) return;
|
|
if (/poss/i.test(parts[1])) {
|
|
if (ev.removedInfo) warRepeating.handlePossessionChange(parts[2], 'removed', supportingValues);
|
|
else warRepeating.handlePossessionChange(parts[2], parts[3], supportingValues);
|
|
} else if (/effect/i.test(parts[1])) warRepeating.handleEffectChange(parts[2], parts[3], supportingValues);
|
|
h.log(supportingValues, 'info', `${parts[1]} section - change row ${parts[2]}, attr "${parts[3]||'REMOVED ROW'}"`);
|
|
});
|
|
|
|
// NPC Armour Value change
|
|
on('change:npc_armour', async () => {
|
|
let attrs = await warRepeating.getAllModAttrs();
|
|
let output = await warRepeating.calculateEffectMods(attrs, `npcTrigger`);
|
|
as.setAttrs(output);
|
|
});
|
|
|
|
// Repeating Weapons
|
|
on('change:repeating_weapon:readied', async (ev) => {
|
|
let rows = await as.getSectionIDs('weapon');
|
|
let attrs = await as.getAttrs(rows.map(row=>`repeating_weapon_${row}_readied`).concat(['react']));
|
|
if (attrs.react) warRepeating.handleReadiedWeapon(attrs, [ev.sourceAttribute, ev.newValue??0]);
|
|
});
|
|
on('change:react', async (ev) => {
|
|
let output = {}
|
|
await as.getSectionIDs('weapon').then(v => v.map(row=>output[`repeating_weapon_${row}_show_readied`] = ev.newValue));
|
|
as.setAttrs(output);
|
|
});
|
|
|
|
// Repeating Weapons/Attacks
|
|
on('clicked:repeating_weapon:rollattack clicked:repeating_weapon:rolldefend clicked:repeating_weapon:rolldamage clicked:repeating_weapon:rollcrit', (ev) => {
|
|
h.log(ev, 'log', `Att/def rolled...`);
|
|
let rowId = h.getRowId(ev.sourceAttribute);
|
|
if (!rowId) return h.log(ev, 'error', `Couldn't find rowId!`);
|
|
let rType = ((ev.sourceAttribute.match(/(attack|defend|damage|crit)$/)||[])[1]);
|
|
if (rType === 'attack') roll.attackRoll(rowId);
|
|
else if (rType === 'defend') roll.defendRoll(rowId);
|
|
else if (rType === 'damage') roll.damageRoll(rowId);
|
|
else if (rType === 'crit') roll.critRoll(rowId);
|
|
});
|
|
on('change:repeating_weapon', (ev) => {
|
|
let tType = (/(_base)/i.test(ev.sourceAttribute)) ? 'damage' : (/(_custom)/i.test(ev.sourceAttribute)) ? 'cdamage' : (/(ranged|_type|threat)/i.test(ev.sourceAttribute)) ? 'flags' : null;
|
|
let tRow = h.getRowId(ev.sourceAttribute);
|
|
let itemMods = [ev.newValue];
|
|
if (tType) warRepeating.updateWeapon({type: tType, rowId: tRow}, itemMods);
|
|
});
|
|
|
|
// Repeating Spells
|
|
on('clicked:repeating_spell:rollspell clicked:rollmiscast clicked:repeating_spell:rollcast clicked:repeating_spell:link', (ev) => {
|
|
let rowId = h.getRowId(ev.sourceAttribute);
|
|
let rType = ((ev.triggerName.match(/(miscast|spell|rollcast|link)$/)||[])[1]);
|
|
if (rType === 'miscast') {
|
|
roll.spellMiscast();
|
|
} else if (rType === 'spell') {
|
|
roll.spellRoll(rowId);
|
|
} else if (rType === 'rollcast') {
|
|
roll.spellCast(rowId);
|
|
} else if (rType === 'link') {
|
|
roll.spellLink(rowId);
|
|
}
|
|
});
|
|
|
|
// Effects reporting
|
|
on('clicked:repeating_effect:link-effects clicked:link_armour clicked:link_block clicked:link_globals', (ev) => {
|
|
let rowId = h.getRowId(ev.sourceAttribute);
|
|
rowId = (rowId) ? rowId : (ev.triggerName.match(/link_(.*)/i)||[])[1];
|
|
h.log(rowId, 'log', 'Linking Effect');
|
|
warSheet.linkEffect(rowId);
|
|
});
|
|
|
|
// Monster Ability reporting
|
|
on('clicked:repeating_monster-ability:link-ability', (ev) => {
|
|
let rowId = h.getRowId(ev.sourceAttribute);
|
|
h.log(rowId, 'log', 'Linking Ability');
|
|
warRepeating.linkAbility(rowId);
|
|
});
|
|
|
|
// Monster Ability wizard
|
|
on('clicked:create_monster_ability', () => warSheet.createAbility());
|
|
|
|
}
|
|
</script>
|
|
<!-- END OF WORKERS -->
|
|
<!-- EOF -->
|
|
|
|
|
|
|
|
|