mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-08-30 11:42:29 +00:00
2789 lines
159 KiB
HTML
2789 lines
159 KiB
HTML
|
|
<button name="act_push" hidden="" type="action" title="%{push}">Push Button
|
|
</button>
|
|
<button name="act_execute-push" hidden="" type="action" title="%{execute-push}">execute push
|
|
</button>
|
|
<div class="bladerunner compendium-drop-target npcs vehicles">
|
|
<input name="attr_drop_name" accept="Name" value="" type="hidden" title="@{drop_name}"/>
|
|
<input name="attr_drop_data" accept="data" value="" type="hidden" title="@{drop_data}"/>
|
|
<input name="attr_tabs_tab" value="nav-tabs-tabs--character" type="hidden" title="@{tabs_tab}"/>
|
|
<div class="tabs tabs--tabs outer">
|
|
<div class="skill_toggle">
|
|
<input name="attr_skill_toggle" value="normal" type="hidden" title="@{skill_toggle}"/>
|
|
<label>Advantage
|
|
<input name="attr_skill_toggle" value="advantage" label="Advantage" type="radio" title="@{skill_toggle}"/>
|
|
</label>
|
|
<label>Normal
|
|
<input name="attr_skill_toggle" value="normal" label="Normal" type="radio" title="@{skill_toggle}"/>
|
|
</label>
|
|
<label>Disadvantage
|
|
<input name="attr_skill_toggle" value="disadvantage" label="Disadvantage" type="radio" title="@{skill_toggle}"/>
|
|
</label>
|
|
<label>Helping
|
|
<input name="attr_skill_toggle" value="helping" label="Helping" type="radio" title="@{skill_toggle}"/>
|
|
</label>
|
|
</div><img src="https://s3.amazonaws.com/files.d20.io/images/304284738/jAqK_uR6cB2p2lCFKoDTOA/max.png?1663001456" style="width: 200px; float: right; z-index: 99;"/>
|
|
<nav class="tabs__header tabs--tabs__header">
|
|
<button class="tabs__button tabs--tabs__button tabs--tabs__button--character" name="act_nav-tabs-tabs--character" data-container-button="tabs" data-button="character" data-i18n="tabs-tabs-character" type="action" title="%{nav-tabs-tabs--character}" data-container-tab="tabs" data-tab="character">
|
|
</button>
|
|
<button class="tabs__button tabs--tabs__button tabs--tabs__button--shift-log" name="act_nav-tabs-tabs--shift-log" data-container-button="tabs" data-button="shift-log" data-i18n="tabs-tabs-shift-log" type="action" title="%{nav-tabs-tabs--shift-log}" data-container-tab="tabs" data-tab="shift-log">
|
|
</button>
|
|
<button class="tabs__button tabs--tabs__button tabs--tabs__button--vehicle" name="act_nav-tabs-tabs--vehicle" data-container-button="tabs" data-button="vehicle" data-i18n="tabs-tabs-vehicle" type="action" title="%{nav-tabs-tabs--vehicle}" data-container-tab="tabs" data-tab="vehicle">
|
|
</button>
|
|
</nav>
|
|
<div class="tabs__body tabs--tabs__body">
|
|
<div class="tabs__container tabs--tabs__container tabs--tabs__container--character" data-container-tab="tabs" data-tab="character">
|
|
<div class="section__basic">
|
|
<h1 class="logo-lapd">Profile Database </h1>
|
|
<label class="inputRow">
|
|
<h3 data-i18n="name"></h3>
|
|
<input name="attr_character_name" type="text" title="@{character_name}"/>
|
|
</label>
|
|
<label class="inputRow">
|
|
<h3 data-i18n="archetype"> </h3>
|
|
<input name="attr_archetype" data-i18n-placeholder="requires input" type="text" title="@{archetype}"/>
|
|
</label>
|
|
<div class="option_replicant">
|
|
<input name="attr_option_replicant" type="hidden" title="@{option_replicant}"/>
|
|
<label><span class="capitalize" data-i18n="human"></span>
|
|
<input name="attr_option_replicant" value="human" label="Human" type="radio" title="@{option_replicant}"/>
|
|
</label>
|
|
<label><span class="capitalize" data-i18n="replicant"></span>
|
|
<input name="attr_option_replicant" value="replicant" label="Replicant" type="radio" title="@{option_replicant}"/>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="container__top">
|
|
<div class="section__keyMemory">
|
|
<h2 data-i18n="key memory"></h2>
|
|
<textarea name="attr_key_memory" title="@{key_memory}"></textarea>
|
|
</div>
|
|
<div class="section__keyRelationship">
|
|
<h2 data-i18n="key relationship"></h2>
|
|
<textarea name="attr_key_relationship" title="@{key_relationship}"></textarea>
|
|
</div>
|
|
<div class="section__home">
|
|
<h2 data-i18n="home"></h2>
|
|
<textarea name="attr_home" label="Home" title="@{home}"></textarea>
|
|
<label class="inputRow">
|
|
<h2 data-i18n="years on the force"></h2>
|
|
<input name="attr_years_on_force" type="number" title="@{years_on_force}"/>
|
|
</label>
|
|
</div>
|
|
<div class="section__appearance">
|
|
<h2 data-i18n="appearance"></h2>
|
|
<textarea name="attr_appearance" label="Appearance" title="@{appearance}"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="section__attributes">
|
|
<h2 data-i18n="attributes & skills"></h2>
|
|
<div class="content__attribute">
|
|
<div class="attribute_wrapper">
|
|
<div class="attribute subtle-button button"></div>
|
|
<h3 class="attribute__name" data-i18n="strength">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_strength" value="C" type="hidden" title="@{strength}"/>
|
|
<input name="attr_strength_dice" value="d8" type="hidden" title="@{strength_dice}"/>
|
|
<input class="grade" name="attr_strength" value="C" type="text" title="@{strength}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_strength" value="A" label="A" type="radio" title="@{strength}"/>
|
|
<input name="attr_strength" value="B" label="B" type="radio" title="@{strength}"/>
|
|
<input name="attr_strength" value="C" label="C" checked="checked" type="radio" title="@{strength}"/>
|
|
<input name="attr_strength" value="D" label="D" type="radio" title="@{strength}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_strength_dice" title="@{strength_dice}">d8</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_force" value="@{force_action}" title="%{force}" type="roll">
|
|
</button>
|
|
<button name="act_force-action" hidden="" type="action" title="%{force-action}">
|
|
</button>
|
|
<input name="attr_force_action" type="hidden" title="@{force_action}"/>
|
|
<h3 class="attribute__name" data-i18n="force">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_force" value="D" type="hidden" title="@{force}"/>
|
|
<input name="attr_force_dice" value="d6" type="hidden" title="@{force_dice}"/>
|
|
<input class="grade" name="attr_force" value="D" type="text" title="@{force}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_force" value="A" label="A" type="radio" title="@{force}"/>
|
|
<input name="attr_force" value="B" label="B" type="radio" title="@{force}"/>
|
|
<input name="attr_force" value="C" label="C" type="radio" title="@{force}"/>
|
|
<input name="attr_force" value="D" label="D" checked="checked" type="radio" title="@{force}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_force_dice" title="@{force_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_hand_to_hand" value="@{hand_to_hand_action}" title="%{hand_to_hand}" type="roll">
|
|
</button>
|
|
<button name="act_hand-to-hand-action" hidden="" type="action" title="%{hand-to-hand-action}">
|
|
</button>
|
|
<input name="attr_hand_to_hand_action" type="hidden" title="@{hand_to_hand_action}"/>
|
|
<h3 class="attribute__name" data-i18n="hand to hand">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_hand_to_hand" value="D" type="hidden" title="@{hand_to_hand}"/>
|
|
<input name="attr_hand_to_hand_dice" value="d6" type="hidden" title="@{hand_to_hand_dice}"/>
|
|
<input class="grade" name="attr_hand_to_hand" value="D" type="text" title="@{hand_to_hand}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_hand_to_hand" value="A" label="A" type="radio" title="@{hand_to_hand}"/>
|
|
<input name="attr_hand_to_hand" value="B" label="B" type="radio" title="@{hand_to_hand}"/>
|
|
<input name="attr_hand_to_hand" value="C" label="C" type="radio" title="@{hand_to_hand}"/>
|
|
<input name="attr_hand_to_hand" value="D" label="D" checked="checked" type="radio" title="@{hand_to_hand}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_hand_to_hand_dice" title="@{hand_to_hand_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_stamina" value="@{stamina_action}" title="%{stamina}" type="roll">
|
|
</button>
|
|
<button name="act_stamina-action" hidden="" type="action" title="%{stamina-action}">
|
|
</button>
|
|
<input name="attr_stamina_action" type="hidden" title="@{stamina_action}"/>
|
|
<h3 class="attribute__name" data-i18n="stamina">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_stamina" value="D" type="hidden" title="@{stamina}"/>
|
|
<input name="attr_stamina_dice" value="d6" type="hidden" title="@{stamina_dice}"/>
|
|
<input class="grade" name="attr_stamina" value="D" type="text" title="@{stamina}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_stamina" value="A" label="A" type="radio" title="@{stamina}"/>
|
|
<input name="attr_stamina" value="B" label="B" type="radio" title="@{stamina}"/>
|
|
<input name="attr_stamina" value="C" label="C" type="radio" title="@{stamina}"/>
|
|
<input name="attr_stamina" value="D" label="D" checked="checked" type="radio" title="@{stamina}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_stamina_dice" title="@{stamina_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
<div class="content__attribute">
|
|
<div class="attribute_wrapper">
|
|
<div class="attribute subtle-button button"></div>
|
|
<h3 class="attribute__name" data-i18n="agility">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_agility" value="C" type="hidden" title="@{agility}"/>
|
|
<input name="attr_agility_dice" value="d8" type="hidden" title="@{agility_dice}"/>
|
|
<input class="grade" name="attr_agility" value="C" type="text" title="@{agility}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_agility" value="A" label="A" type="radio" title="@{agility}"/>
|
|
<input name="attr_agility" value="B" label="B" type="radio" title="@{agility}"/>
|
|
<input name="attr_agility" value="C" label="C" checked="checked" type="radio" title="@{agility}"/>
|
|
<input name="attr_agility" value="D" label="D" type="radio" title="@{agility}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_agility_dice" title="@{agility_dice}">d8</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_firearms" value="@{firearms_action}" title="%{firearms}" type="roll">
|
|
</button>
|
|
<button name="act_firearms-action" hidden="" type="action" title="%{firearms-action}">
|
|
</button>
|
|
<input name="attr_firearms_action" type="hidden" title="@{firearms_action}"/>
|
|
<h3 class="attribute__name" data-i18n="firearms">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_firearms" value="D" type="hidden" title="@{firearms}"/>
|
|
<input name="attr_firearms_dice" value="d6" type="hidden" title="@{firearms_dice}"/>
|
|
<input class="grade" name="attr_firearms" value="D" type="text" title="@{firearms}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_firearms" value="A" label="A" type="radio" title="@{firearms}"/>
|
|
<input name="attr_firearms" value="B" label="B" type="radio" title="@{firearms}"/>
|
|
<input name="attr_firearms" value="C" label="C" type="radio" title="@{firearms}"/>
|
|
<input name="attr_firearms" value="D" label="D" checked="checked" type="radio" title="@{firearms}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_firearms_dice" title="@{firearms_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_mobility" value="@{mobility_action}" title="%{mobility}" type="roll">
|
|
</button>
|
|
<button name="act_mobility-action" hidden="" type="action" title="%{mobility-action}">
|
|
</button>
|
|
<input name="attr_mobility_action" type="hidden" title="@{mobility_action}"/>
|
|
<h3 class="attribute__name" data-i18n="mobility">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_mobility" value="D" type="hidden" title="@{mobility}"/>
|
|
<input name="attr_mobility_dice" value="d6" type="hidden" title="@{mobility_dice}"/>
|
|
<input class="grade" name="attr_mobility" value="D" type="text" title="@{mobility}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_mobility" value="A" label="A" type="radio" title="@{mobility}"/>
|
|
<input name="attr_mobility" value="B" label="B" type="radio" title="@{mobility}"/>
|
|
<input name="attr_mobility" value="C" label="C" type="radio" title="@{mobility}"/>
|
|
<input name="attr_mobility" value="D" label="D" checked="checked" type="radio" title="@{mobility}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_mobility_dice" title="@{mobility_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_stealth" value="@{stealth_action}" title="%{stealth}" type="roll">
|
|
</button>
|
|
<button name="act_stealth-action" hidden="" type="action" title="%{stealth-action}">
|
|
</button>
|
|
<input name="attr_stealth_action" type="hidden" title="@{stealth_action}"/>
|
|
<h3 class="attribute__name" data-i18n="stealth">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_stealth" value="D" type="hidden" title="@{stealth}"/>
|
|
<input name="attr_stealth_dice" value="d6" type="hidden" title="@{stealth_dice}"/>
|
|
<input class="grade" name="attr_stealth" value="D" type="text" title="@{stealth}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_stealth" value="A" label="A" type="radio" title="@{stealth}"/>
|
|
<input name="attr_stealth" value="B" label="B" type="radio" title="@{stealth}"/>
|
|
<input name="attr_stealth" value="C" label="C" type="radio" title="@{stealth}"/>
|
|
<input name="attr_stealth" value="D" label="D" checked="checked" type="radio" title="@{stealth}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_stealth_dice" title="@{stealth_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
<div class="content__attribute">
|
|
<div class="attribute_wrapper">
|
|
<div class="attribute subtle-button button"></div>
|
|
<h3 class="attribute__name" data-i18n="intelligence">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_intelligence" value="C" type="hidden" title="@{intelligence}"/>
|
|
<input name="attr_intelligence_dice" value="d8" type="hidden" title="@{intelligence_dice}"/>
|
|
<input class="grade" name="attr_intelligence" value="C" type="text" title="@{intelligence}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_intelligence" value="A" label="A" type="radio" title="@{intelligence}"/>
|
|
<input name="attr_intelligence" value="B" label="B" type="radio" title="@{intelligence}"/>
|
|
<input name="attr_intelligence" value="C" label="C" checked="checked" type="radio" title="@{intelligence}"/>
|
|
<input name="attr_intelligence" value="D" label="D" type="radio" title="@{intelligence}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_intelligence_dice" title="@{intelligence_dice}">d8</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_medical_aid" value="@{medical_aid_action}" title="%{medical_aid}" type="roll">
|
|
</button>
|
|
<button name="act_medical-aid-action" hidden="" type="action" title="%{medical-aid-action}">
|
|
</button>
|
|
<input name="attr_medical_aid_action" type="hidden" title="@{medical_aid_action}"/>
|
|
<h3 class="attribute__name" data-i18n="medical aid">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_medical_aid" value="D" type="hidden" title="@{medical_aid}"/>
|
|
<input name="attr_medical_aid_dice" value="d6" type="hidden" title="@{medical_aid_dice}"/>
|
|
<input class="grade" name="attr_medical_aid" value="D" type="text" title="@{medical_aid}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_medical_aid" value="A" label="A" type="radio" title="@{medical_aid}"/>
|
|
<input name="attr_medical_aid" value="B" label="B" type="radio" title="@{medical_aid}"/>
|
|
<input name="attr_medical_aid" value="C" label="C" type="radio" title="@{medical_aid}"/>
|
|
<input name="attr_medical_aid" value="D" label="D" checked="checked" type="radio" title="@{medical_aid}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_medical_aid_dice" title="@{medical_aid_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_observation" value="@{observation_action}" title="%{observation}" type="roll">
|
|
</button>
|
|
<button name="act_observation-action" hidden="" type="action" title="%{observation-action}">
|
|
</button>
|
|
<input name="attr_observation_action" type="hidden" title="@{observation_action}"/>
|
|
<h3 class="attribute__name" data-i18n="observation">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_observation" value="D" type="hidden" title="@{observation}"/>
|
|
<input name="attr_observation_dice" value="d6" type="hidden" title="@{observation_dice}"/>
|
|
<input class="grade" name="attr_observation" value="D" type="text" title="@{observation}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_observation" value="A" label="A" type="radio" title="@{observation}"/>
|
|
<input name="attr_observation" value="B" label="B" type="radio" title="@{observation}"/>
|
|
<input name="attr_observation" value="C" label="C" type="radio" title="@{observation}"/>
|
|
<input name="attr_observation" value="D" label="D" checked="checked" type="radio" title="@{observation}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_observation_dice" title="@{observation_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_tech" value="@{tech_action}" title="%{tech}" type="roll">
|
|
</button>
|
|
<button name="act_tech-action" hidden="" type="action" title="%{tech-action}">
|
|
</button>
|
|
<input name="attr_tech_action" type="hidden" title="@{tech_action}"/>
|
|
<h3 class="attribute__name" data-i18n="tech">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_tech" value="D" type="hidden" title="@{tech}"/>
|
|
<input name="attr_tech_dice" value="d6" type="hidden" title="@{tech_dice}"/>
|
|
<input class="grade" name="attr_tech" value="D" type="text" title="@{tech}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_tech" value="A" label="A" type="radio" title="@{tech}"/>
|
|
<input name="attr_tech" value="B" label="B" type="radio" title="@{tech}"/>
|
|
<input name="attr_tech" value="C" label="C" type="radio" title="@{tech}"/>
|
|
<input name="attr_tech" value="D" label="D" checked="checked" type="radio" title="@{tech}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_tech_dice" title="@{tech_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
<div class="content__attribute">
|
|
<div class="attribute_wrapper">
|
|
<div class="attribute subtle-button button"></div>
|
|
<h3 class="attribute__name" data-i18n="empathy">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_empathy" value="C" type="hidden" title="@{empathy}"/>
|
|
<input name="attr_empathy_dice" value="d8" type="hidden" title="@{empathy_dice}"/>
|
|
<input class="grade" name="attr_empathy" value="C" type="text" title="@{empathy}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_empathy" value="A" label="A" type="radio" title="@{empathy}"/>
|
|
<input name="attr_empathy" value="B" label="B" type="radio" title="@{empathy}"/>
|
|
<input name="attr_empathy" value="C" label="C" checked="checked" type="radio" title="@{empathy}"/>
|
|
<input name="attr_empathy" value="D" label="D" type="radio" title="@{empathy}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_empathy_dice" title="@{empathy_dice}">d8</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_connections" value="@{connections_action}" title="%{connections}" type="roll">
|
|
</button>
|
|
<button name="act_connections-action" hidden="" type="action" title="%{connections-action}">
|
|
</button>
|
|
<input name="attr_connections_action" type="hidden" title="@{connections_action}"/>
|
|
<h3 class="attribute__name" data-i18n="connections">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_connections" value="D" type="hidden" title="@{connections}"/>
|
|
<input name="attr_connections_dice" value="d6" type="hidden" title="@{connections_dice}"/>
|
|
<input class="grade" name="attr_connections" value="D" type="text" title="@{connections}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_connections" value="A" label="A" type="radio" title="@{connections}"/>
|
|
<input name="attr_connections" value="B" label="B" type="radio" title="@{connections}"/>
|
|
<input name="attr_connections" value="C" label="C" type="radio" title="@{connections}"/>
|
|
<input name="attr_connections" value="D" label="D" checked="checked" type="radio" title="@{connections}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_connections_dice" title="@{connections_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_insight" value="@{insight_action}" title="%{insight}" type="roll">
|
|
</button>
|
|
<button name="act_insight-action" hidden="" type="action" title="%{insight-action}">
|
|
</button>
|
|
<input name="attr_insight_action" type="hidden" title="@{insight_action}"/>
|
|
<h3 class="attribute__name" data-i18n="insight">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_insight" value="D" type="hidden" title="@{insight}"/>
|
|
<input name="attr_insight_dice" value="d6" type="hidden" title="@{insight_dice}"/>
|
|
<input class="grade" name="attr_insight" value="D" type="text" title="@{insight}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_insight" value="A" label="A" type="radio" title="@{insight}"/>
|
|
<input name="attr_insight" value="B" label="B" type="radio" title="@{insight}"/>
|
|
<input name="attr_insight" value="C" label="C" type="radio" title="@{insight}"/>
|
|
<input name="attr_insight" value="D" label="D" checked="checked" type="radio" title="@{insight}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_insight_dice" title="@{insight_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_manipulation" value="@{manipulation_action}" title="%{manipulation}" type="roll">
|
|
</button>
|
|
<button name="act_manipulation-action" hidden="" type="action" title="%{manipulation-action}">
|
|
</button>
|
|
<input name="attr_manipulation_action" type="hidden" title="@{manipulation_action}"/>
|
|
<h3 class="attribute__name" data-i18n="manipulation">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_manipulation" value="D" type="hidden" title="@{manipulation}"/>
|
|
<input name="attr_manipulation_dice" value="d6" type="hidden" title="@{manipulation_dice}"/>
|
|
<input class="grade" name="attr_manipulation" value="D" type="text" title="@{manipulation}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_manipulation" value="A" label="A" type="radio" title="@{manipulation}"/>
|
|
<input name="attr_manipulation" value="B" label="B" type="radio" title="@{manipulation}"/>
|
|
<input name="attr_manipulation" value="C" label="C" type="radio" title="@{manipulation}"/>
|
|
<input name="attr_manipulation" value="D" label="D" checked="checked" type="radio" title="@{manipulation}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_manipulation_dice" title="@{manipulation_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
<div class="content__attribute driving">
|
|
<div class="attribute_wrapper">
|
|
<div class="attribute subtle-button button"></div>
|
|
<h3 class="attribute__name" data-i18n="driving">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_driving" value="D" type="hidden" title="@{driving}"/>
|
|
<input name="attr_driving_dice" value="d6" type="hidden" title="@{driving_dice}"/>
|
|
<input class="grade" name="attr_driving" value="D" type="text" title="@{driving}"/>
|
|
<div class="gradeMenu">
|
|
<input name="attr_driving" value="A" label="A" type="radio" title="@{driving}"/>
|
|
<input name="attr_driving" value="B" label="B" type="radio" title="@{driving}"/>
|
|
<input name="attr_driving" value="C" label="C" type="radio" title="@{driving}"/>
|
|
<input name="attr_driving" value="D" label="D" checked="checked" type="radio" title="@{driving}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_driving_dice" title="@{driving_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container__shifts">
|
|
<button name="act_next-shift" data-i18n="next shift" type="action" title="%{next-shift}">
|
|
</button>
|
|
<button class="flex-box gap" name="act_downtime" type="action" title="%{downtime}"><span class="capitalize" data-i18n="downtime"></span><span name="attr_shift_counter" title="@{shift_counter}"></span>
|
|
</button>
|
|
</div>
|
|
<div class="container__middle">
|
|
<div class="section__weapons">
|
|
<h2 data-i18n="weapons"></h2>
|
|
<h3 data-i18n="hand to hand"></h3>
|
|
<fieldset class="repeating_weapons-hand">
|
|
<div class="inputRow">
|
|
<input class="title_input" name="attr_name" data-i18n-placeholder="weapon name" type="text" title="@{repeating_weapons-hand_$X_name}"/>
|
|
<button class="roller" name="roll_roll" value="@{action}" title="%{repeating_weapons-hand_$X_roll}" type="roll">
|
|
</button>
|
|
<button name="act_action" hidden="" type="action" title="%{repeating_weapons-hand_$X_action}">
|
|
</button>
|
|
<input name="attr_action" type="hidden" title="@{repeating_weapons-hand_$X_action}"/>
|
|
<button hidden="" name="act_damage" type="action" title="%{repeating_weapons-hand_$X_damage}">
|
|
</button>
|
|
<button hidden="" name="act_crit" type="action" title="%{repeating_weapons-hand_$X_crit}">
|
|
</button>
|
|
<input name="attr_damage" data-i18n-placeholder="damage" type="number" title="@{repeating_weapons-hand_$X_damage}"/>
|
|
<select name="attr_crit_die" trigger='{"triggeredFuncs":["setActionCalls"]}' title="@{repeating_weapons-hand_$X_crit_die}">
|
|
<option value="6" selected="">d6
|
|
</option>
|
|
<option value="8">d8
|
|
</option>
|
|
<option value="10">d10
|
|
</option>
|
|
<option value="12">d12
|
|
</option>
|
|
</select>
|
|
<select name="attr_type" trigger='{"triggeredFuncs":["setActionCalls"]}' title="@{repeating_weapons-hand_$X_type}">
|
|
<option value="crushing" selected="">Crushing
|
|
</option>
|
|
<option value="piercing">Piercing
|
|
</option>
|
|
</select>
|
|
<input name="attr_min_range" data-i18n-placeholder="min. range" type="text" title="@{repeating_weapons-hand_$X_min_range}"/>
|
|
<input name="attr_max_range" data-i18n-placeholder="max. range" type="text" title="@{repeating_weapons-hand_$X_max_range}"/>
|
|
</div>
|
|
</fieldset>
|
|
<h3 data-i18n="firearms"></h3>
|
|
<fieldset class="repeating_weapons-firearm">
|
|
<div class="inputRow">
|
|
<input class="title_input" name="attr_name" data-i18n-placeholder="weapon name" type="text" title="@{repeating_weapons-firearm_$X_name}"/>
|
|
<button class="roller" name="roll_roll" value="@{action}" title="%{repeating_weapons-firearm_$X_roll}" type="roll">
|
|
</button>
|
|
<button name="act_action" hidden="" type="action" title="%{repeating_weapons-firearm_$X_action}">
|
|
</button>
|
|
<input name="attr_action" type="hidden" title="@{repeating_weapons-firearm_$X_action}"/>
|
|
<button hidden="" name="act_damage" type="action" title="%{repeating_weapons-firearm_$X_damage}">
|
|
</button>
|
|
<button hidden="" name="act_crit" type="action" title="%{repeating_weapons-firearm_$X_crit}">
|
|
</button>
|
|
<input name="attr_damage" data-i18n-placeholder="damage" type="number" title="@{repeating_weapons-firearm_$X_damage}"/>
|
|
<select name="attr_crit_die" trigger='{"triggeredFuncs":["setActionCalls"]}' title="@{repeating_weapons-firearm_$X_crit_die}">
|
|
<option value="6" selected="">d6
|
|
</option>
|
|
<option value="8">d8
|
|
</option>
|
|
<option value="10">d10
|
|
</option>
|
|
<option value="12">d12
|
|
</option>
|
|
</select>
|
|
<select name="attr_type" trigger='{"triggeredFuncs":["setActionCalls"]}' title="@{repeating_weapons-firearm_$X_type}">
|
|
<option value="crushing">Crushing
|
|
</option>
|
|
<option value="piercing" selected="">Piercing
|
|
</option>
|
|
</select>
|
|
<input name="attr_min_range" data-i18n-placeholder="min. range" type="text" title="@{repeating_weapons-firearm_$X_min_range}"/>
|
|
<input name="attr_max_range" data-i18n-placeholder="max. range" type="text" title="@{repeating_weapons-firearm_$X_max_range}"/>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<div class="section__specialties">
|
|
<h2>Specialties</h2>
|
|
<fieldset class="repeating_specialties">
|
|
<input class="collapse" name="attr_collapse" value="1" type="checkbox" title="@{repeating_specialties_$X_collapse}"/>
|
|
<input class="title_input" name="attr_name" label="Specialties" data-i18n-placeholder="new specialty" type="text" title="@{repeating_specialties_$X_name}"/>
|
|
<input class="br-checkbox" name="attr_shift" value="1" label="Used this shift" type="checkbox" title="@{repeating_specialties_$X_shift}"/>
|
|
<div class="adaptive adaptive--text expanded span-all"><span class="adaptive--text__span" name="attr_description" title="@{repeating_specialties_$X_description}"></span>
|
|
<textarea class="adaptive--text__textarea" name="attr_description" data-i18n-placeholder="specialty description" title="@{repeating_specialties_$X_description}"></textarea>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<div class="section__armor collapse-container">
|
|
<input class="collapse" name="attr_armor_collapse" value="1" type="checkbox" title="@{armor_collapse}"/>
|
|
<h2>Armor</h2>
|
|
<div class="content__attribute collapsed span-all">
|
|
<div class="attribute_wrapper button-wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_armor" value="@{armor_action}" title="%{armor}" type="roll">
|
|
</button>
|
|
<button name="act_armor-action" hidden="" type="action" title="%{armor-action}">
|
|
</button>
|
|
<input name="attr_armor_action" type="hidden" title="@{armor_action}"/>
|
|
<h3 class="attribute__name"><span name="attr_armor_name" title="@{armor_name}"></span>
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_armor" value="-" type="hidden" title="@{armor}"/>
|
|
<input name="attr_armor_dice" value="d6" type="hidden" title="@{armor_dice}"/>
|
|
<input class="grade" name="attr_armor" value="-" type="text" title="@{armor}"/>
|
|
<div class="gradeMenu has-null">
|
|
<input name="attr_armor" value="A" label="A" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="B" label="B" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="C" label="C" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="D" label="D" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="-" label="-" checked="checked" type="radio" title="@{armor}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_armor_dice" title="@{armor_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
<input class="expanded" name="attr_armor_name" data-i18n-placeholder="armor name" type="text" title="@{armor_name}"/>
|
|
<div class="attribute__value expanded">
|
|
<input name="attr_armor" value="-" type="hidden" title="@{armor}"/>
|
|
<input name="attr_armor_dice" value="d6" type="hidden" title="@{armor_dice}"/>
|
|
<input class="grade" name="attr_armor" value="-" type="text" title="@{armor}"/>
|
|
<div class="gradeMenu has-null">
|
|
<input name="attr_armor" value="A" label="A" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="B" label="B" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="C" label="C" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="D" label="D" type="radio" title="@{armor}"/>
|
|
<input name="attr_armor" value="-" label="-" checked="checked" type="radio" title="@{armor}"/>
|
|
</div>
|
|
</div>
|
|
<div class="adaptive adaptive--text expanded span-all"><span class="adaptive--text__span" name="attr_armor_effect" title="@{armor_effect}"></span>
|
|
<textarea class="adaptive--text__textarea" name="attr_armor_effect" data-i18n-placeholder="armor description" title="@{armor_effect}"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="section__signatureItem collapse-container gap">
|
|
<input class="collapse" name="attr_collapse" value="1" type="checkbox" title="@{collapse}"/>
|
|
<h2>Signature Item</h2>
|
|
<div class="inputRow">
|
|
<input name="attr_signature_item" data-i18n-placeholder="signature item name" type="text" title="@{signature_item}"/>
|
|
<input class="br-checkbox" name="attr_shift" value="1" label="Used this shift" type="checkbox" title="@{shift}"/>
|
|
</div>
|
|
<div class="adaptive adaptive--text expanded"><span class="adaptive--text__span" name="attr_signature_description" title="@{signature_description}"></span>
|
|
<textarea class="adaptive--text__textarea" name="attr_signature_description" data-i18n-placeholder="signature item description" title="@{signature_description}"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="section__gear">
|
|
<h2>Gear</h2>
|
|
<fieldset class="repeating_gear">
|
|
<input class="collapse" name="attr_collapse" value="1" type="checkbox" title="@{repeating_gear_$X_collapse}"/>
|
|
<div class="inputRow">
|
|
<input name="attr_name" data-i18n-placeholder="item name" type="text" title="@{repeating_gear_$X_name}"/>
|
|
</div>
|
|
<div class="adaptive adaptive--text expanded"><span class="adaptive--text__span" name="attr_description" title="@{repeating_gear_$X_description}"></span>
|
|
<textarea class="adaptive--text__textarea" name="attr_description" data-i18n-placeholder="item description" title="@{repeating_gear_$X_description}"></textarea>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<div class="injuries">
|
|
<div class="section__health">
|
|
<h2 data-i18n="health"></h2>
|
|
<div class="inputRow">
|
|
<h3 data-i18n="maximum health"></h3>
|
|
<input name="attr_health_max" type="number" title="@{health|max}"/>
|
|
</div>
|
|
<input class="dynamic_fill" name="attr_health_max" value="0" type="hidden" title="@{health|max}"/>
|
|
<div class="fill-left radio_health">
|
|
<input class="fill-left__radio fill-left__radio--clearer" name="attr_health" value="0" checked="" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="1" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="2" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="3" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="4" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="5" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="6" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="7" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="8" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="9" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="10" type="radio" title="@{health}"/>
|
|
<input class="fill-left__radio" name="attr_health" value="11" type="radio" title="@{health}"/>
|
|
</div>
|
|
<textarea name="attr_health_injuries" label="Critical Injuries" title="@{health_injuries}"></textarea>
|
|
</div>
|
|
<div class="section__resolve">
|
|
<h2 data-i18n="resolve"></h2>
|
|
<div class="inputRow">
|
|
<h3 data-i18n="maximum resolve"></h3>
|
|
<input name="attr_resolve_max" type="number" title="@{resolve|max}"/>
|
|
</div>
|
|
<input class="dynamic_fill" name="attr_resolve_max" value="0" type="hidden" title="@{resolve|max}"/>
|
|
<div class="fill-left radio_resolve">
|
|
<input class="fill-left__radio fill-left__radio--clearer" name="attr_resolve" value="0" checked="" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="1" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="2" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="3" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="4" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="5" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="6" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="7" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="8" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="9" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="10" type="radio" title="@{resolve}"/>
|
|
<input class="fill-left__radio" name="attr_resolve" value="11" type="radio" title="@{resolve}"/>
|
|
</div>
|
|
<textarea name="attr_resolve_injuries" label="Critical Injuries" title="@{resolve_injuries}"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container__bottom">
|
|
<div class="section__promotion">
|
|
<h2 data-i18n="promotion points"></h2>
|
|
<div class="fill-left radio_promotion">
|
|
<input class="fill-left__radio fill-left__radio--clearer" name="attr_promotion" value="0" checked="" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="1" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="2" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="3" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="4" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="5" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="6" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="7" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="8" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="9" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="10" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="11" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="12" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="13" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="14" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="15" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="16" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="17" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="18" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="19" type="radio" title="@{promotion}"/>
|
|
<input class="fill-left__radio" name="attr_promotion" value="20" type="radio" title="@{promotion}"/>
|
|
</div>
|
|
</div>
|
|
<div class="section__humanity">
|
|
<h2 data-i18n="humanity points"></h2>
|
|
<div class="fill-left radio_humanity">
|
|
<input class="fill-left__radio fill-left__radio--clearer" name="attr_humanity" value="0" checked="" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="1" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="2" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="3" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="4" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="5" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="6" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="7" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="8" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="9" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="10" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="11" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="12" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="13" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="14" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="15" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="16" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="17" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="18" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="19" type="radio" title="@{humanity}"/>
|
|
<input class="fill-left__radio" name="attr_humanity" value="20" type="radio" title="@{humanity}"/>
|
|
</div>
|
|
</div>
|
|
<div class="section__chinyen">
|
|
<h2 data-i18n="chinyen points"></h2>
|
|
<div class="fill-left radio_chinyen">
|
|
<input class="fill-left__radio fill-left__radio--clearer" name="attr_chinyen" value="0" checked="" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="1" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="2" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="3" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="4" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="5" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="6" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="7" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="8" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="9" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="10" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="11" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="12" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="13" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="14" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="15" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="16" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="17" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="18" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="19" type="radio" title="@{chinyen}"/>
|
|
<input class="fill-left__radio" name="attr_chinyen" value="20" type="radio" title="@{chinyen}"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tabs__container tabs--tabs__container tabs--tabs__container--shift-log" data-container-tab="tabs" data-tab="shift-log">
|
|
<div class="section__basic">
|
|
<h1 class="logo-lapd" data-i18n="shift log"> </h1>
|
|
<label class="inputRow">
|
|
<h3 data-i18n="Name"></h3>
|
|
<input name="attr_character_name" type="text" title="@{character_name}"/>
|
|
</label>
|
|
<label class="inputRow">
|
|
<h3 data-i18n="Archetype"></h3>
|
|
<input name="attr_archetype" data-i18n-placeholder="requires input" type="text" title="@{archetype}"/>
|
|
</label>
|
|
</div>
|
|
<div class="section__shifts">
|
|
<h2 data-i18n="Shifts"></h2>
|
|
<button class="repcontrol-button repcontrol-button--add" name="act_add-shift" type="action" title="%{add-shift}">
|
|
</button><span class="shifts_calendar" hidden=""></span>
|
|
<fieldset class="repeating_shift">
|
|
<button name="act_display-log" type="action" title="%{repeating_shift_$X_display-log}">
|
|
</button>
|
|
<input name="attr_morning" value="" type="hidden" title="@{repeating_shift_$X_morning}"/>
|
|
<input name="attr_day" value="" type="hidden" title="@{repeating_shift_$X_day}"/>
|
|
<input name="attr_evening" value="" type="hidden" title="@{repeating_shift_$X_evening}"/>
|
|
<input name="attr_night" value="" type="hidden" title="@{repeating_shift_$X_night}"/>
|
|
<input name="attr_name" placeholder="Log Name" type="text" title="@{repeating_shift_$X_name}"/>
|
|
</fieldset>
|
|
<div class="shift_display">
|
|
<input name="attr_day_id" value="" type="hidden" title="@{day_id}"/>
|
|
<input name="attr_day_label" type="hidden" title="@{day_label}"/>
|
|
<h3 data-i18n="Log File"><span name="attr_day_label"></span></h3>
|
|
<div class="shift-panel">
|
|
<h3 data-i18n="morning"></h3>
|
|
<input class="br-checkbox" name="attr_active_shift" value="morning" checked="" type="radio" title="@{active_shift}"/>
|
|
<textarea class="span-all" name="attr_morning_shift" title="@{morning_shift}"></textarea>
|
|
</div>
|
|
<div class="shift-panel">
|
|
<h3 data-i18n="day"></h3>
|
|
<input class="br-checkbox" name="attr_active_shift" value="day" type="radio" title="@{active_shift}"/>
|
|
<textarea class="span-all" name="attr_day_shift" title="@{day_shift}"></textarea>
|
|
</div>
|
|
<div class="shift-panel">
|
|
<h3 data-i18n="evening"></h3>
|
|
<input class="br-checkbox" name="attr_active_shift" value="evening" type="radio" title="@{active_shift}"/>
|
|
<textarea class="span-all" name="attr_evening_shift" title="@{evening_shift}"></textarea>
|
|
</div>
|
|
<div class="shift-panel">
|
|
<h3 data-i18n="night"></h3>
|
|
<input class="br-checkbox" name="attr_active_shift" value="night" type="radio" title="@{active_shift}"/>
|
|
<textarea class="span-all" name="attr_night_shift" title="@{night_shift}"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tabs__container tabs--tabs__container tabs--tabs__container--vehicle" data-container-tab="tabs" data-tab="vehicle">
|
|
<div class="section__basic">
|
|
<h1 class="logo-lapd" data-i18n="vehicle"></h1>
|
|
<label class="inputRow">
|
|
<h3 data-i18n="name"></h3>
|
|
<input name="attr_character_name" type="text" title="@{character_name}"/>
|
|
</label>
|
|
<label class="inputRow">
|
|
<h3 data-i18n="archetype"></h3>
|
|
<input name="attr_archetype" data-i18n-placeholder="requires input" type="text" title="@{archetype}"/>
|
|
</label>
|
|
</div>
|
|
<div class="section__stats">
|
|
<div class="inputRow">
|
|
<h3 data-i18n="passengers"></h3>
|
|
<input name="attr_passengers" type="number" title="@{passengers}"/>
|
|
</div>
|
|
<div class="inputRow">
|
|
<h3 data-i18n="hull"></h3>
|
|
<input class="dynamic_fill" name="attr_hull_max" value="10" type="hidden" title="@{hull|max}"/>
|
|
<div class="fill-left radio_hull">
|
|
<input class="fill-left__radio fill-left__radio--clearer" name="attr_hull" value="0" checked="" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="1" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="2" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="3" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="4" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="5" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="6" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="7" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="8" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="9" type="radio" title="@{hull}"/>
|
|
<input class="fill-left__radio" name="attr_hull" value="10" type="radio" title="@{hull}"/>
|
|
</div>
|
|
<input name="attr_hull_max" value="10" type="number" title="@{hull|max}"/>
|
|
</div>
|
|
<div class="content__attribute">
|
|
<div class="attribute_wrapper button-wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_vehicle_armor" value="@{vehicle_armor_action}" title="%{vehicle_armor}" type="roll">
|
|
</button>
|
|
<button name="act_vehicle-armor-action" hidden="" type="action" title="%{vehicle-armor-action}">
|
|
</button>
|
|
<input name="attr_vehicle_armor_action" type="hidden" title="@{vehicle_armor_action}"/>
|
|
<h3 class="attribute__name" data-i18n="vehicle armor">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_vehicle_armor" value="D" type="hidden" title="@{vehicle_armor}"/>
|
|
<input name="attr_vehicle_armor_dice" value="d6" type="hidden" title="@{vehicle_armor_dice}"/>
|
|
<input class="grade" name="attr_vehicle_armor" value="D" type="text" title="@{vehicle_armor}"/>
|
|
<div class="gradeMenu has-null">
|
|
<input name="attr_vehicle_armor" value="A" label="A" type="radio" title="@{vehicle_armor}"/>
|
|
<input name="attr_vehicle_armor" value="B" label="B" type="radio" title="@{vehicle_armor}"/>
|
|
<input name="attr_vehicle_armor" value="C" label="C" type="radio" title="@{vehicle_armor}"/>
|
|
<input name="attr_vehicle_armor" value="D" label="D" checked="checked" type="radio" title="@{vehicle_armor}"/>
|
|
<input name="attr_vehicle_armor" value="-" label="-" type="radio" title="@{vehicle_armor}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_vehicle_armor_dice" title="@{vehicle_armor_dice}">d6</span>
|
|
</div>
|
|
<div class="attribute_wrapper">
|
|
<button class="attribute subtle-button roller" name="roll_maneuverability" value="@{maneuverability_action}" title="%{maneuverability}" type="roll">
|
|
</button>
|
|
<button name="act_maneuverability-action" hidden="" type="action" title="%{maneuverability-action}">
|
|
</button>
|
|
<input name="attr_maneuverability_action" type="hidden" title="@{maneuverability_action}"/>
|
|
<h3 class="attribute__name" data-i18n="maneuverability">
|
|
</h3>
|
|
<div class="attribute__value">
|
|
<input name="attr_maneuverability" value="C" type="hidden" title="@{maneuverability}"/>
|
|
<input name="attr_maneuverability_dice" value="d6" type="hidden" title="@{maneuverability_dice}"/>
|
|
<input class="grade" name="attr_maneuverability" value="C" type="text" title="@{maneuverability}"/>
|
|
<div class="gradeMenu has-null">
|
|
<input name="attr_maneuverability" value="A" label="A" type="radio" title="@{maneuverability}"/>
|
|
<input name="attr_maneuverability" value="B" label="B" type="radio" title="@{maneuverability}"/>
|
|
<input name="attr_maneuverability" value="C" label="C" checked="checked" type="radio" title="@{maneuverability}"/>
|
|
<input name="attr_maneuverability" value="D" label="D" type="radio" title="@{maneuverability}"/>
|
|
<input name="attr_maneuverability" value="-" label="-" type="radio" title="@{maneuverability}"/>
|
|
</div>
|
|
</div><span class="attribute__dice" name="attr_maneuverability_dice" title="@{maneuverability_dice}">d6</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<rolltemplate class="sheet-rolltemplate-bladerunner">{{#character_name}}{{#character_id}}
|
|
<h1 class="character_name">[{{character_name}}](http://journal.roll20.net/character/{{character_id}})</h1>{{/character_id}}{{^character_id}}
|
|
<h1 class="character_name">{{character_name}}</h1>{{/character_id}}{{/character_name}}
|
|
<h2>{{label}}</h2>{{#successes}}
|
|
<div class="successes">{{computed::successes}}</div>{{/successes}}
|
|
<div class="rolls">{{#computed::roll1}}{{#rollTotal() roll1 1}}<span class="force-fumble">{{computed::roll1}}</span>{{/rollTotal() roll1 1}}{{#^rollTotal() roll1 1}}{{computed::roll1}}{{/^rollTotal() roll1 1}}{{/computed::roll1}}{{#computed::roll2}}{{#rollTotal() roll2 1}}<span class="force-fumble">{{computed::roll2}}</span>{{/rollTotal() roll2 1}}{{#^rollTotal() roll2 1}}{{computed::roll2}}{{/^rollTotal() roll2 1}}{{/computed::roll2}}{{#computed::roll3}}{{#rollTotal() roll3 1}}<span class="force-fumble">{{computed::roll3}}</span>{{/rollTotal() roll3 1}}{{#^rollTotal() roll3 1}}{{computed::roll3}}{{/^rollTotal() roll3 1}}{{/computed::roll3}}{{#roll1}}{{^computed::roll1}}
|
|
<h3>{{roll1}}</h3>{{/computed::roll1}}{{/roll1}}{{#damage}}
|
|
<h3>damage</h3>{{damage}}{{/damage}}
|
|
</div>{{#^rollTotal() computed::push 0}}
|
|
<div class="push">[{{push_label}}](~{{character_name}}|push||{{computed::push_through}})</div>{{/^rollTotal() computed::push 0}}{{#character_name}}{{#attack_id}}
|
|
<div class="damage-buttons">{{#damage_label}}<span>[{{damage_label}}](~{{character_name}}|{{attack_id}}_damage)</span>{{/damage_label}}{{^damage_label}}<span>[damage](~{{character_name}}|{{attack_id}}_damage)</span>{{/damage_label}}{{#critical_label}}<span>[{{critical_label}}](~{{character_name}}|{{attack_id}}_crit)</span>{{/critical_label}}{{^critical_label}}<span>[critical](~{{character_name}}|{{attack_id}}_crit)</span>{{/critical_label}}
|
|
</div>{{/attack_id}}{{/character_name}}{{#effect}}
|
|
<p class="effect description span-all">{{effect}}</p>{{/effect}}{{#description}}
|
|
<p class="description span-all">{{description}}</p>{{/description}}
|
|
</rolltemplate>
|
|
<rolltemplate class="sheet-rolltemplate-bladerunner-push">{{#character_name}}{{#character_id}}
|
|
<h1 class="character_name">[{{character_name}}](http://journal.roll20.net/character/{{character_id}})</h1>{{/character_id}}{{^character_id}}
|
|
<h1 class="character_name">{{character_name}}</h1>{{/character_id}}{{/character_name}}
|
|
<h2>{{label}}</h2>
|
|
<div class="{{#roll3}}grid-3{{/roll3}} choice-table">
|
|
<div class="head-back"></div>
|
|
<div class="side-back"></div>{{#push_all}}<span class="corner-label">[All](~{{character_name}}|execute-push||{{computed::push_all}})</span>{{/push_all}}{{^push_all}}<span class="corner-label"></span>{{/push_all}}{{#result1}}
|
|
<h3 class="top-label">{{result1}}</h3>
|
|
<h3 class="side-label">{{result1}}</h3>{{#result1}}{{#roll1}}<span class="x-1 y-1 toRoll">[{{roll1}}](~{{character_name}}|execute-push||{{computed::push1_1}})</span>{{/roll1}}{{/result1}}{{#result2}}{{/result2}}{{#result3}}{{/result3}}{{/result1}}{{#result2}}
|
|
<h3 class="top-label">{{result2}}</h3>
|
|
<h3 class="side-label">{{result2}}</h3>{{#result1}}{{#roll2}}{{#roll1}}<span class="x-2 y-1 toRoll">[{{roll2}} + {{roll1}}](~{{character_name}}|execute-push||{{computed::push2_1}})</span>{{/roll1}}{{/roll2}}{{/result1}}{{#result2}}{{#roll2}}<span class="x-2 y-2 toRoll">[{{roll2}}](~{{character_name}}|execute-push||{{computed::push2_2}})</span>{{/roll2}}{{/result2}}{{#result3}}{{/result3}}{{/result2}}{{#result3}}
|
|
<h3 class="top-label">{{result3}}</h3>
|
|
<h3 class="side-label">{{result3}}</h3>{{#result1}}{{#roll3}}{{#roll1}}<span class="x-3 y-1 toRoll">[{{roll3}} + {{roll1}}](~{{character_name}}|execute-push||{{computed::push3_1}})</span>{{/roll1}}{{/roll3}}{{/result1}}{{#result2}}{{#roll3}}{{#roll2}}<span class="x-3 y-2 toRoll">[{{roll3}} + {{roll2}}](~{{character_name}}|execute-push||{{computed::push3_2}})</span>{{/roll2}}{{/roll3}}{{/result2}}{{#result3}}{{#roll3}}<span class="x-3 y-3 toRoll">[{{roll3}}](~{{character_name}}|execute-push||{{computed::push3_3}})</span>{{/roll3}}{{/result3}}{{/result3}}
|
|
</div>
|
|
</rolltemplate>
|
|
<input name="attr_template_start" value="&{template:bladerunner} {{character_name=@{character_name}}} {{character_id=@{character_id}}}" type="hidden" title="@{template_start}"/>
|
|
<input name="attr_push_start" value="&{template:bladerunner-push} {{character_name=@{character_name}}} {{character_id=@{character_id}}}" type="hidden" title="@{push_start}"/>
|
|
<script type="text/worker">
|
|
const k = (function(){
|
|
const kFuncs = {};
|
|
|
|
const cascades = {"attr_character_name":{"name":"character_name","type":"text","defaultValue":"","affects":[],"triggeredFuncs":["setActionCalls"],"listenerFunc":"accessSheet","listener":"change:character_name"},"attr_armor_rating":{"name":"armor_rating","type":"string","defaultValue":"D","affects":[],"triggerFuncs":[]},"act_push":{"triggeredFuncs":["pushRoll"],"name":"push","listener":"clicked:push","listenerFunc":"accessSheet","type":"action"},"act_execute-push":{"triggeredFuncs":["executePush"],"name":"execute-push","listener":"clicked:execute-push","listenerFunc":"accessSheet","type":"action"},"attr_drop_name":{"triggeredFuncs":["handleCompendiumDrop"],"name":"drop_name","listener":"change:drop_name","listenerFunc":"accessSheet","type":"hidden","defaultValue":"","affects":[]},"attr_drop_data":{"name":"drop_data","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_tabs_tab":{"triggeredFuncs":["kTabOnOpen"],"name":"tabs_tab","listener":"change:tabs_tab","listenerFunc":"accessSheet","type":"hidden","defaultValue":"nav-tabs-tabs--character","affects":[]},"attr_skill_toggle":{"name":"skill_toggle","type":"hidden","defaultValue":"normal","triggeredFuncs":[],"affects":[]},"act_nav-tabs-tabs--character":{"triggeredFuncs":["kSwitchTab"],"name":"nav-tabs-tabs--character","listener":"clicked:nav-tabs-tabs--character","listenerFunc":"accessSheet","type":"action"},"act_nav-tabs-tabs--shift-log":{"triggeredFuncs":["kSwitchTab"],"name":"nav-tabs-tabs--shift-log","listener":"clicked:nav-tabs-tabs--shift-log","listenerFunc":"accessSheet","type":"action"},"act_nav-tabs-tabs--vehicle":{"triggeredFuncs":["kSwitchTab"],"name":"nav-tabs-tabs--vehicle","listener":"clicked:nav-tabs-tabs--vehicle","listenerFunc":"accessSheet","type":"action"},"attr_archetype":{"name":"archetype","type":"text","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_option_replicant":{"name":"option_replicant","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_key_memory":{"name":"key_memory","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_key_relationship":{"name":"key_relationship","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_home":{"name":"home","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_years_on_force":{"name":"years_on_force","type":"number","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_appearance":{"name":"appearance","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_strength":{"name":"strength","type":"hidden","defaultValue":"C","triggeredFuncs":[],"affects":["strength_dice"],"listener":"change:strength","listenerFunc":"accessSheet"},"attr_strength_dice":{"calculation":"calcAttributeDice","name":"strength_dice","type":"hidden","defaultValue":"d8","triggeredFuncs":[],"affects":[]},"act_force-action":{"triggeredFuncs":["rollAttribute"],"name":"force-action","listener":"clicked:force-action","listenerFunc":"accessSheet","type":"action"},"attr_force_action":{"name":"force_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_force":{"name":"force","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["force_dice"],"listener":"change:force","listenerFunc":"accessSheet"},"attr_force_dice":{"calculation":"calcAttributeDice","name":"force_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_hand-to-hand-action":{"triggeredFuncs":["rollAttribute"],"name":"hand-to-hand-action","listener":"clicked:hand-to-hand-action","listenerFunc":"accessSheet","type":"action"},"attr_hand_to_hand_action":{"name":"hand_to_hand_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_hand_to_hand":{"name":"hand_to_hand","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["hand_to_hand_dice"],"listener":"change:hand_to_hand","listenerFunc":"accessSheet"},"attr_hand_to_hand_dice":{"calculation":"calcAttributeDice","name":"hand_to_hand_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_stamina-action":{"triggeredFuncs":["rollAttribute"],"name":"stamina-action","listener":"clicked:stamina-action","listenerFunc":"accessSheet","type":"action"},"attr_stamina_action":{"name":"stamina_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_stamina":{"name":"stamina","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["stamina_dice"],"listener":"change:stamina","listenerFunc":"accessSheet"},"attr_stamina_dice":{"calculation":"calcAttributeDice","name":"stamina_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"attr_agility":{"name":"agility","type":"hidden","defaultValue":"C","triggeredFuncs":[],"affects":["agility_dice"],"listener":"change:agility","listenerFunc":"accessSheet"},"attr_agility_dice":{"calculation":"calcAttributeDice","name":"agility_dice","type":"hidden","defaultValue":"d8","triggeredFuncs":[],"affects":[]},"act_firearms-action":{"triggeredFuncs":["rollAttribute"],"name":"firearms-action","listener":"clicked:firearms-action","listenerFunc":"accessSheet","type":"action"},"attr_firearms_action":{"name":"firearms_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_firearms":{"name":"firearms","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["firearms_dice"],"listener":"change:firearms","listenerFunc":"accessSheet"},"attr_firearms_dice":{"calculation":"calcAttributeDice","name":"firearms_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_mobility-action":{"triggeredFuncs":["rollAttribute"],"name":"mobility-action","listener":"clicked:mobility-action","listenerFunc":"accessSheet","type":"action"},"attr_mobility_action":{"name":"mobility_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_mobility":{"name":"mobility","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["mobility_dice"],"listener":"change:mobility","listenerFunc":"accessSheet"},"attr_mobility_dice":{"calculation":"calcAttributeDice","name":"mobility_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_stealth-action":{"triggeredFuncs":["rollAttribute"],"name":"stealth-action","listener":"clicked:stealth-action","listenerFunc":"accessSheet","type":"action"},"attr_stealth_action":{"name":"stealth_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_stealth":{"name":"stealth","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["stealth_dice"],"listener":"change:stealth","listenerFunc":"accessSheet"},"attr_stealth_dice":{"calculation":"calcAttributeDice","name":"stealth_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"attr_intelligence":{"name":"intelligence","type":"hidden","defaultValue":"C","triggeredFuncs":[],"affects":["intelligence_dice"],"listener":"change:intelligence","listenerFunc":"accessSheet"},"attr_intelligence_dice":{"calculation":"calcAttributeDice","name":"intelligence_dice","type":"hidden","defaultValue":"d8","triggeredFuncs":[],"affects":[]},"act_medical-aid-action":{"triggeredFuncs":["rollAttribute"],"name":"medical-aid-action","listener":"clicked:medical-aid-action","listenerFunc":"accessSheet","type":"action"},"attr_medical_aid_action":{"name":"medical_aid_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_medical_aid":{"name":"medical_aid","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["medical_aid_dice"],"listener":"change:medical_aid","listenerFunc":"accessSheet"},"attr_medical_aid_dice":{"calculation":"calcAttributeDice","name":"medical_aid_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_observation-action":{"triggeredFuncs":["rollAttribute"],"name":"observation-action","listener":"clicked:observation-action","listenerFunc":"accessSheet","type":"action"},"attr_observation_action":{"name":"observation_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_observation":{"name":"observation","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["observation_dice"],"listener":"change:observation","listenerFunc":"accessSheet"},"attr_observation_dice":{"calculation":"calcAttributeDice","name":"observation_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_tech-action":{"triggeredFuncs":["rollAttribute"],"name":"tech-action","listener":"clicked:tech-action","listenerFunc":"accessSheet","type":"action"},"attr_tech_action":{"name":"tech_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_tech":{"name":"tech","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["tech_dice"],"listener":"change:tech","listenerFunc":"accessSheet"},"attr_tech_dice":{"calculation":"calcAttributeDice","name":"tech_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"attr_empathy":{"name":"empathy","type":"hidden","defaultValue":"C","triggeredFuncs":[],"affects":["empathy_dice"],"listener":"change:empathy","listenerFunc":"accessSheet"},"attr_empathy_dice":{"calculation":"calcAttributeDice","name":"empathy_dice","type":"hidden","defaultValue":"d8","triggeredFuncs":[],"affects":[]},"act_connections-action":{"triggeredFuncs":["rollAttribute"],"name":"connections-action","listener":"clicked:connections-action","listenerFunc":"accessSheet","type":"action"},"attr_connections_action":{"name":"connections_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_connections":{"name":"connections","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["connections_dice"],"listener":"change:connections","listenerFunc":"accessSheet"},"attr_connections_dice":{"calculation":"calcAttributeDice","name":"connections_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_insight-action":{"triggeredFuncs":["rollAttribute"],"name":"insight-action","listener":"clicked:insight-action","listenerFunc":"accessSheet","type":"action"},"attr_insight_action":{"name":"insight_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_insight":{"name":"insight","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["insight_dice"],"listener":"change:insight","listenerFunc":"accessSheet"},"attr_insight_dice":{"calculation":"calcAttributeDice","name":"insight_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_manipulation-action":{"triggeredFuncs":["rollAttribute"],"name":"manipulation-action","listener":"clicked:manipulation-action","listenerFunc":"accessSheet","type":"action"},"attr_manipulation_action":{"name":"manipulation_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_manipulation":{"name":"manipulation","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["manipulation_dice"],"listener":"change:manipulation","listenerFunc":"accessSheet"},"attr_manipulation_dice":{"calculation":"calcAttributeDice","name":"manipulation_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"attr_driving":{"name":"driving","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["driving_dice"],"listener":"change:driving","listenerFunc":"accessSheet"},"attr_driving_dice":{"calculation":"calcAttributeDice","name":"driving_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_next-shift":{"triggeredFuncs":["incrementShift"],"name":"next-shift","listener":"clicked:next-shift","listenerFunc":"accessSheet","type":"action"},"act_downtime":{"triggeredFuncs":["takeDowntime"],"name":"downtime","listener":"clicked:downtime","listenerFunc":"accessSheet","type":"action"},"attr_shift_counter":{"type":"span","defaultValue":0,"name":"shift_counter","triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-hand_$X_name":{"triggeredFuncs":["setActionCalls"],"name":"repeating_weapons-hand_$X_name","listener":"change:repeating_weapons-hand:name","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"act_repeating_weapons-hand_$X_action":{"triggeredFuncs":["rollAttack","setActionCalls"],"name":"repeating_weapons-hand_$X_action","listener":"clicked:repeating_weapons-hand:action","listenerFunc":"accessSheet","type":"action"},"attr_repeating_weapons-hand_$X_action":{"name":"repeating_weapons-hand_$X_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"act_repeating_weapons-hand_$X_damage":{"triggeredFuncs":["rollDamage","setActionCalls"],"name":"repeating_weapons-hand_$X_damage","listener":"clicked:repeating_weapons-hand:damage","listenerFunc":"accessSheet","type":"action"},"act_repeating_weapons-hand_$X_crit":{"triggeredFuncs":["rollCrit"],"name":"repeating_weapons-hand_$X_crit","listener":"clicked:repeating_weapons-hand:crit","listenerFunc":"accessSheet","type":"action"},"attr_repeating_weapons-hand_$X_damage":{"name":"repeating_weapons-hand_$X_damage","type":"number","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-hand_$X_crit_die":{"name":"repeating_weapons-hand_$X_crit_die","type":"select","defaultValue":6,"triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-hand_$X_type":{"name":"repeating_weapons-hand_$X_type","type":"select","defaultValue":"crushing","triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-hand_$X_min_range":{"triggeredFuncs":["setActionCalls"],"name":"repeating_weapons-hand_$X_min_range","listener":"change:repeating_weapons-hand:min_range","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"attr_repeating_weapons-hand_$X_max_range":{"triggeredFuncs":["setActionCalls"],"name":"repeating_weapons-hand_$X_max_range","listener":"change:repeating_weapons-hand:max_range","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"attr_repeating_weapons-firearm_$X_name":{"triggeredFuncs":["setActionCalls"],"name":"repeating_weapons-firearm_$X_name","listener":"change:repeating_weapons-firearm:name","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"act_repeating_weapons-firearm_$X_action":{"triggeredFuncs":["rollAttack","setActionCalls"],"name":"repeating_weapons-firearm_$X_action","listener":"clicked:repeating_weapons-firearm:action","listenerFunc":"accessSheet","type":"action"},"attr_repeating_weapons-firearm_$X_action":{"name":"repeating_weapons-firearm_$X_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"act_repeating_weapons-firearm_$X_damage":{"triggeredFuncs":["rollDamage","setActionCalls"],"name":"repeating_weapons-firearm_$X_damage","listener":"clicked:repeating_weapons-firearm:damage","listenerFunc":"accessSheet","type":"action"},"act_repeating_weapons-firearm_$X_crit":{"triggeredFuncs":["rollCrit"],"name":"repeating_weapons-firearm_$X_crit","listener":"clicked:repeating_weapons-firearm:crit","listenerFunc":"accessSheet","type":"action"},"attr_repeating_weapons-firearm_$X_damage":{"name":"repeating_weapons-firearm_$X_damage","type":"number","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-firearm_$X_crit_die":{"name":"repeating_weapons-firearm_$X_crit_die","type":"select","defaultValue":6,"triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-firearm_$X_type":{"name":"repeating_weapons-firearm_$X_type","type":"select","defaultValue":"crushing","triggeredFuncs":[],"affects":[]},"attr_repeating_weapons-firearm_$X_min_range":{"triggeredFuncs":["setActionCalls"],"name":"repeating_weapons-firearm_$X_min_range","listener":"change:repeating_weapons-firearm:min_range","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"attr_repeating_weapons-firearm_$X_max_range":{"triggeredFuncs":["setActionCalls"],"name":"repeating_weapons-firearm_$X_max_range","listener":"change:repeating_weapons-firearm:max_range","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"attr_repeating_specialties_$X_collapse":{"name":"repeating_specialties_$X_collapse","type":"checkbox","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_repeating_specialties_$X_name":{"name":"repeating_specialties_$X_name","type":"text","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_specialties_$X_shift":{"name":"repeating_specialties_$X_shift","type":"checkbox","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_repeating_specialties_$X_description":{"name":"repeating_specialties_$X_description","type":"span","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_armor_collapse":{"name":"armor_collapse","type":"checkbox","defaultValue":0,"triggeredFuncs":[],"affects":[]},"act_armor-action":{"triggeredFuncs":["rollAttribute"],"name":"armor-action","listener":"clicked:armor-action","listenerFunc":"accessSheet","type":"action"},"attr_armor_action":{"name":"armor_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_armor_name":{"name":"armor_name","type":"span","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_armor":{"name":"armor","type":"hidden","defaultValue":"-","triggeredFuncs":[],"affects":["armor_dice"],"listener":"change:armor","listenerFunc":"accessSheet"},"attr_armor_dice":{"calculation":"calcAttributeDice","name":"armor_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"attr_armor_effect":{"name":"armor_effect","type":"span","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_collapse":{"name":"collapse","type":"checkbox","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_signature_item":{"name":"signature_item","type":"text","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_shift":{"name":"shift","type":"checkbox","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_signature_description":{"name":"signature_description","type":"span","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_gear_$X_collapse":{"name":"repeating_gear_$X_collapse","type":"checkbox","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_repeating_gear_$X_name":{"name":"repeating_gear_$X_name","type":"text","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_gear_$X_description":{"name":"repeating_gear_$X_description","type":"span","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_health_max":{"triggeredFuncs":["limitHealthResolve"],"name":"health_max","listener":"change:health_max","listenerFunc":"accessSheet","type":"number","defaultValue":0,"affects":[]},"attr_health":{"triggeredFuncs":["limitHealthResolve"],"name":"health","listener":"change:health","listenerFunc":"accessSheet","type":"radio","defaultValue":0,"affects":[]},"attr_health_injuries":{"name":"health_injuries","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_resolve_max":{"triggeredFuncs":["limitHealthResolve"],"name":"resolve_max","listener":"change:resolve_max","listenerFunc":"accessSheet","type":"number","defaultValue":0,"affects":[]},"attr_resolve":{"triggeredFuncs":["limitHealthResolve"],"name":"resolve","listener":"change:resolve","listenerFunc":"accessSheet","type":"radio","defaultValue":0,"affects":[]},"attr_resolve_injuries":{"name":"resolve_injuries","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_promotion":{"name":"promotion","type":"radio","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_humanity":{"name":"humanity","type":"radio","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_chinyen":{"name":"chinyen","type":"radio","defaultValue":0,"triggeredFuncs":[],"affects":[]},"act_add-shift":{"listenerFunc":"addItem","name":"add-shift","listener":"clicked:add-shift","type":"action"},"fieldset_repeating_shift":{"triggeredFuncs":["deleteShift"],"addFuncs":["addCalendar"],"name":"repeating_shift","listener":"remove:repeating_shift","listenerFunc":"accessSheet","type":"fieldset"},"act_repeating_shift_$X_display-log":{"triggeredFuncs":["displayLog"],"name":"repeating_shift_$X_display-log","listener":"clicked:repeating_shift:display-log","listenerFunc":"accessSheet","type":"action"},"attr_repeating_shift_$X_morning":{"name":"repeating_shift_$X_morning","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_shift_$X_day":{"name":"repeating_shift_$X_day","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_shift_$X_evening":{"name":"repeating_shift_$X_evening","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_shift_$X_night":{"name":"repeating_shift_$X_night","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_repeating_shift_$X_name":{"triggeredFuncs":["displayLog"],"name":"repeating_shift_$X_name","listener":"change:repeating_shift:name","listenerFunc":"accessSheet","type":"text","defaultValue":"","affects":[]},"attr_day_id":{"name":"day_id","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_day_label":{"name":"day_label","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_active_shift":{"name":"active_shift","type":"radio","defaultValue":"morning","triggeredFuncs":[],"affects":[]},"attr_morning_shift":{"triggeredFuncs":["storeShift"],"name":"morning_shift","listener":"change:morning_shift","listenerFunc":"accessSheet","defaultValue":"","affects":[]},"attr_day_shift":{"triggeredFuncs":["storeShift"],"name":"day_shift","listener":"change:day_shift","listenerFunc":"accessSheet","defaultValue":"","affects":[]},"attr_evening_shift":{"triggeredFuncs":["storeShift"],"name":"evening_shift","listener":"change:evening_shift","listenerFunc":"accessSheet","defaultValue":"","affects":[]},"attr_night_shift":{"triggeredFuncs":["storeShift"],"name":"night_shift","listener":"change:night_shift","listenerFunc":"accessSheet","defaultValue":"","affects":[]},"attr_passengers":{"name":"passengers","type":"number","defaultValue":0,"triggeredFuncs":[],"affects":[]},"attr_hull_max":{"name":"hull_max","type":"hidden","defaultValue":10,"triggeredFuncs":["limitHealthResolve"],"affects":[],"listener":"change:hull_max","listenerFunc":"accessSheet"},"attr_hull":{"triggeredFuncs":["limitHealthResolve"],"name":"hull","listener":"change:hull","listenerFunc":"accessSheet","type":"radio","defaultValue":0,"affects":[]},"act_vehicle-armor-action":{"triggeredFuncs":["rollAttribute"],"name":"vehicle-armor-action","listener":"clicked:vehicle-armor-action","listenerFunc":"accessSheet","type":"action"},"attr_vehicle_armor_action":{"name":"vehicle_armor_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_vehicle_armor":{"name":"vehicle_armor","type":"hidden","defaultValue":"D","triggeredFuncs":[],"affects":["vehicle_armor_dice"],"listener":"change:vehicle_armor","listenerFunc":"accessSheet"},"attr_vehicle_armor_dice":{"calculation":"calcAttributeDice","name":"vehicle_armor_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"act_maneuverability-action":{"triggeredFuncs":["rollAttribute"],"name":"maneuverability-action","listener":"clicked:maneuverability-action","listenerFunc":"accessSheet","type":"action"},"attr_maneuverability_action":{"name":"maneuverability_action","type":"hidden","defaultValue":"","triggeredFuncs":[],"affects":[]},"attr_maneuverability":{"name":"maneuverability","type":"hidden","defaultValue":"C","triggeredFuncs":[],"affects":["maneuverability_dice"],"listener":"change:maneuverability","listenerFunc":"accessSheet"},"attr_maneuverability_dice":{"calculation":"calcAttributeDice","name":"maneuverability_dice","type":"hidden","defaultValue":"d6","triggeredFuncs":[],"affects":[]},"attr_template_start":{"name":"template_start","type":"hidden","defaultValue":"&{template:bladerunner} {{character_name=@{character_name}}} {{character_id=@{character_id}}}","triggeredFuncs":[],"affects":[]},"attr_push_start":{"name":"push_start","type":"hidden","defaultValue":"&{template:bladerunner-push} {{character_name=@{character_name}}} {{character_id=@{character_id}}}","triggeredFuncs":[],"affects":[]}};
|
|
|
|
kFuncs.cascades = cascades;
|
|
|
|
const repeatingSectionDetails = [{"section":"repeating_weapons-hand","fields":["name","action","damage","crit_die","type","min_range","max_range"]},{"section":"repeating_weapons-firearm","fields":["name","action","damage","crit_die","type","min_range","max_range"]},{"section":"repeating_specialties","fields":["collapse","name","shift","description"]},{"section":"repeating_gear","fields":["collapse","name","description"]},{"section":"repeating_shift","fields":["morning","day","evening","night","name"]}];
|
|
|
|
kFuncs.repeatingSectionDetails = repeatingSectionDetails;
|
|
/**
|
|
* This stores the name of your sheet for use in the logging functions {@link log} and {@link debug}. Accessible by `k.sheetName`
|
|
* @var
|
|
* @type {string}
|
|
*/
|
|
let sheetName = 'kScaffold Powered Sheet';
|
|
kFuncs.sheetName = sheetName;
|
|
/**
|
|
* This stores the version of your sheet for use in the logging functions{@link log} and {@link debug}. It is also stored in the sheet_version attribute on your character sheet. Accessible via `k.version`
|
|
* @var
|
|
* @type {number}
|
|
*/
|
|
let version = 0;
|
|
kFuncs.version = version;
|
|
/**
|
|
* A boolean flag that tells the script whether to enable or disable {@link debug} calls. If the version of the sheet is `0`, or an attribute named `debug_mode` is found on opening this is set to true for your entire session. Otherwise, it remains false.
|
|
* @var
|
|
* @type {boolean}
|
|
*/
|
|
let debugMode = false;
|
|
kFuncs.debugMode = debugMode;
|
|
const funcs = {};
|
|
kFuncs.funcs = funcs;
|
|
const updateHandlers = {};
|
|
const openHandlers = {};
|
|
const initialSetups = {};
|
|
const allHandlers = {};
|
|
const addFuncs = {};
|
|
|
|
const kscaffoldJSVersion = '0.0.4';
|
|
const kscaffoldPUGVersion = '0.0.4';
|
|
/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
/**
|
|
* Replaces problem characters to use a string as a regex
|
|
* @param {string} text - The text to replace characters in
|
|
* @returns {string}
|
|
* @example
|
|
* const textForRegex = k.sanitizeForRegex('.some thing[with characters]');
|
|
* console.log(textForRegex);// => "\.some thing\[with characters\]"
|
|
*/
|
|
const sanitizeForRegex = function(text){
|
|
return text.replace(/\.|\||\(|\)|\[|\]|\-|\+|\?|\/|\{|\}|\^|\$|\*/g,'\\$&');
|
|
};
|
|
kFuncs.sanitizeForRegex = sanitizeForRegex;
|
|
|
|
/**
|
|
* Converts a value to a number, it\'s default value, or `0` if no default value passed.
|
|
* @param {string|number} val - Value to convert to a number
|
|
* @param {number} def - The default value, uses 0 if not passed
|
|
* @returns {number|undefined}
|
|
* @example
|
|
* const num = k.value('100');
|
|
* console.log(num);// => 100
|
|
*/
|
|
const value = function(val,def){
|
|
return (+val||def||0);
|
|
};
|
|
kFuncs.value = value;
|
|
|
|
/**
|
|
* Extracts the section (e.g. `repeating_equipment`), rowID (e.g `-;lkj098J:LKj`), and field name (e.g. `bulk`) from a repeating attribute name.
|
|
* @param {string} string - The string to parse
|
|
* @returns {array} - Array of matches. Index 0: the section name, e.g. repeating_equipment | Index 1:the row ID | index 2: The name of the attribute
|
|
* @returns {string[]}
|
|
* @example
|
|
* //Extract info from a full repeating name
|
|
* const [section,rowID,attrName] = k.parseRepeatName('repeating_equipment_-8908asdflkjZlkj23_name');
|
|
* console.log(section);// => "repeating_equipment"
|
|
* console.log(rowID);// => "-8908asdflkjZlkj23"
|
|
* console.log(attrName);// => "name"
|
|
*
|
|
* //Extract info from just a row name
|
|
* const [section,rowID,attrName] = k.parseRepeatName('repeating_equipment_-8908asdflkjZlkj23');
|
|
* console.log(section);// => "repeating_equipment"
|
|
* console.log(rowID);// => "-8908asdflkjZlkj23"
|
|
* console.log(attrName);// => undefined
|
|
*/
|
|
const parseRepeatName = function(string){
|
|
let match = string.match(/(repeating_[^_]+)_([^_]+)(?:_(.+))?/);
|
|
match.shift();
|
|
return match;
|
|
};
|
|
kFuncs.parseRepeatName = parseRepeatName;
|
|
|
|
/**
|
|
* Parses out the components of a trigger name similar to [parseRepeatName](#parserepeatname). Aliases: parseClickTrigger.
|
|
*
|
|
* Aliases: `k.parseClickTrigger`
|
|
* @param {string} string The triggerName property of the
|
|
* @returns {array} - For a repeating button named `repeating_equipment_-LKJhpoi98;lj_roll`, the array will be `['repeating_equipment','-LKJhpoi98;lj','roll']`. For a non repeating button named `roll`, the array will be `[undefined,undefined,'roll']`
|
|
* @returns {string[]}
|
|
* @example
|
|
* //Parse a non repeating trigger
|
|
* const [section,rowID,attrName] = k.parseTriggerName('clicked:some-button');
|
|
* console.log(section);// => undefined
|
|
* console.log(rowID);// => undefined
|
|
* console.log(attrName);// => "some-button"
|
|
*
|
|
* //Parse a repeating trigger
|
|
* const [section,rowID,attrName] = k.parseTriggerName('clicked:repeating_attack_-234lkjpd8fu8usadf_some-button');
|
|
* console.log(section);// => "repeating_attack"
|
|
* console.log(rowID);// => "-234lkjpd8fu8usadf"
|
|
* console.log(attrName);// => "some-button"
|
|
*
|
|
* //Parse a repeating name
|
|
* const [section,rowID,attrName] = k.parseTriggerName('repeating_attack_-234lkjpd8fu8usadf_some-button');
|
|
* console.log(section);// => "repeating_attack"
|
|
* console.log(rowID);// => "-234lkjpd8fu8usadf"
|
|
* console.log(attrName);// => "some-button"
|
|
*/
|
|
const parseTriggerName = function(string){
|
|
let match = string.replace(/^clicked:/,'').match(/(?:(repeating_[^_]+)_([^_]+)_)?(.+)/);
|
|
match.shift();
|
|
return match;
|
|
};
|
|
kFuncs.parseTriggerName = parseTriggerName;
|
|
const parseClickTrigger = parseTriggerName;
|
|
kFuncs.parseClickTrigger = parseClickTrigger;
|
|
|
|
/**
|
|
* Parses out the attribute name from the htmlattribute name.
|
|
* @param {string} string - The triggerName property of the [event](https://wiki.roll20.net/Sheet_Worker_Scripts#eventInfo_Object).
|
|
* @returns {string}
|
|
* @example
|
|
* //Parse a name
|
|
* const attrName = k.parseHtmlName('attr_attribute_1');
|
|
* console.log(attrName);// => "attribute_1"
|
|
*/
|
|
const parseHTMLName = function(string){
|
|
let match = string.match(/(?:attr|act|roll)_(.+)/);
|
|
match.shift();
|
|
return match[0];
|
|
};
|
|
kFuncs.parseHTMLName = parseHTMLName;
|
|
|
|
/**
|
|
* Capitalize each word in a string
|
|
* @param {string} string - The string to capitalize
|
|
* @returns {string}
|
|
* @example
|
|
* const capitalized = k.capitalize('a word');
|
|
* console.log(capitalized);// => "A Word"
|
|
*/
|
|
const capitalize = function(string){
|
|
return string.replace(/(?:^|\s+|\/)[a-z]/ig,(letter)=>letter.toUpperCase());
|
|
};
|
|
kFuncs.capitalize = capitalize;
|
|
|
|
/**
|
|
* Extracts a roll query result for use in later functions. Must be awaited as per [startRoll documentation](https://wiki.roll20.net/Sheet_Worker_Scripts#Roll_Parsing.28NEW.29). Stolen from [Oosh\'s Adventures with Startroll thread](https://app.roll20.net/forum/post/10346883/adventures-with-startroll).
|
|
* @param {string} query - The query should be just the text as the `?{` and `}` at the start/end of the query are added by the function.
|
|
* @returns {Promise} - Resolves to the selected value from the roll query
|
|
* @example
|
|
* const rollFunction = async function(){
|
|
* //Get the result of a choose from list query
|
|
* const queryResult = await extractQueryResult('Prompt Text Here|Option 1|Option 2');
|
|
* console.log(queryResult);//=> "Option 1" or "Option 2" depending on what the user selects
|
|
*
|
|
* //Get free from input from the user
|
|
* const freeResult = await extractQueryResult('Prompt Text Here');
|
|
* consoel.log(freeResult);// => Whatever the user entered
|
|
* }
|
|
*/
|
|
const extractQueryResult = async function(query){
|
|
debug('entering extractQueryResult');
|
|
let queryRoll = await startRoll(`!{{query=[[0[response=?{${query}}]]]}}`);
|
|
finishRoll(queryRoll.rollId);
|
|
return queryRoll.results.query.expression.replace(/^.+?response=|\]$/g,'');
|
|
};
|
|
kFuncs.extractQueryResult = extractQueryResult;
|
|
|
|
/**
|
|
* Simulates a query for ensuring that async/await works correctly in the sheetworker environment when doing conditional startRolls. E.g. if you have an if/else and only one of the conditions results in `startRoll` being called (and thus an `await`), the sheetworker environment would normally crash. Awaiting this in the condition that does not actually need to call `startRoll` will keep the environment in sync.
|
|
* @param {string|number} [value] - The value to return. Optional.
|
|
* @returns {Promise} - Resolves to the value passed to the function
|
|
* @example
|
|
* const rollFunction = async function(){
|
|
* //Get the result of a choose from list query
|
|
* const queryResult = await pseudoQuery('a value');
|
|
* console.log(queryResult);//=> "a value"
|
|
* }
|
|
*/
|
|
const pseudoQuery = async function(value){
|
|
debug('entering pseudoQuery');
|
|
let queryRoll = await startRoll(`!{{query=[[0[response=${value}]]]}}`);
|
|
finishRoll(queryRoll.rollId);
|
|
return queryRoll.results.query.expression.replace(/^.+?response=|\]$/g,'');
|
|
};
|
|
kFuncs.pseudoQuery = pseudoQuery;
|
|
|
|
/**
|
|
* An alias for console.log.
|
|
* @param {any} msg - The message can be a straight string, an object, or an array. If it is an object or array, the object will be broken down so that each key is used as a label to output followed by the value of that key. If the value of the key is an object or array, it will be output via `console.table`.
|
|
*/
|
|
const log = function(msg){
|
|
if(typeof msg === 'string'){
|
|
console.log(`%c${kFuncs.sheetName} log| ${msg}`,"background-color:#159ccf");
|
|
}else if(typeof msg === 'object'){
|
|
Object.keys(msg).forEach((m)=>{
|
|
if(typeof msg[m] === 'string'){
|
|
console.log(`%c${kFuncs.sheetName} log| ${m}: ${msg[m]}`,"background-color:#159ccf");
|
|
}else{
|
|
console.log(`%c${kFuncs.sheetName} log| ${typeof msg[m]} ${m}`,"background-color:#159ccf");
|
|
console.table(msg[m]);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
kFuncs.log = log;
|
|
|
|
/**
|
|
* Alias for console.log that only triggers when debug mode is enabled or when the sheet\'s version is `0`. Useful for entering test logs that will not pollute the console on the live sheet.
|
|
* @param {any} msg - 'See {@link k.log}
|
|
* @param {boolean} force - Pass as a truthy value to force the debug output to be output to the console regardless of debug mode.
|
|
* @returns {void}
|
|
*/
|
|
const debug = function(msg,force){
|
|
if(!kFuncs.debugMode && !force && kFuncs.version > 0) return;
|
|
if(typeof msg === 'string'){
|
|
console.log(`%c${kFuncs.sheetName} DEBUG| ${msg}`,"background-color:tan;color:red;");
|
|
}else if(typeof msg === 'object'){
|
|
Object.keys(msg).forEach((m)=>{
|
|
if(typeof msg[m] === 'string'){
|
|
console.log(`%c${kFuncs.sheetName} DEBUG| ${m}: ${msg[m]}`,"background-color:tan;color:red;");
|
|
}else{
|
|
console.log(`%c${kFuncs.sheetName} DEBUG| ${typeof msg[m]} ${m}`,"background-color:tan;color:red;font-weight:bold;");
|
|
console.table(msg[m]);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
kFuncs.debug = debug;
|
|
|
|
/**
|
|
* Orders the section id arrays for all sections in the `sections` object to match the repOrder attribute.
|
|
* @param {attributesProxy} attributes - The attributes object that must have a value for the reporder for each section.
|
|
* @param {[object]} sections - Object containing the IDs for the repeating sections, indexed by repeating section name.
|
|
*/
|
|
const orderSections = function(attributes,sections){
|
|
Object.keys(sections).forEach((section)=>{
|
|
attributes.attributes[`_reporder_${section}`] = commaArray(attributes[`_reporder_${section}`]);
|
|
orderSection(attributes.attributes[`_reporder_${section}`],sections[section]);
|
|
});
|
|
};
|
|
kFuncs.orderSections = orderSections;
|
|
|
|
/**
|
|
* Orders a single ID array.
|
|
* @param {[string]} repOrder - Array of IDs in the order they are in on the sheet.
|
|
* @param {[string]} IDs - Array of IDs to be ordered.
|
|
*/
|
|
const orderSection = function(repOrder,IDs=[]){
|
|
IDs.sort((a,b)=>{
|
|
return repOrder.indexOf(a.toLowerCase()) - repOrder.indexOf(b.toLowerCase());
|
|
});
|
|
};
|
|
kFuncs.orderSection = orderSection;
|
|
|
|
/**
|
|
* Splits a comma delimited string into an array
|
|
* @param {string} string - The string to split.
|
|
* @returns {array} - The string segments of the comma delimited list.
|
|
*/
|
|
const commaArray = function(string=''){
|
|
return string.toLowerCase().split(/\s*,\s*/);
|
|
};
|
|
kFuncs.commaArray = commaArray;/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
//# Attribute Obj Proxy handler
|
|
const createAttrProxy = function(attrs){
|
|
//creates a proxy for the attributes object so that values can be worked with more easily.
|
|
const getCascObj = function(event,casc){
|
|
const eventName = event.triggerName || event.sourceAttribute;
|
|
let typePrefix = eventName.startsWith('clicked:') ?
|
|
'act_' :
|
|
event.removedInfo ?
|
|
'fieldset_' :
|
|
'attr_';
|
|
let cascName = `${typePrefix}${eventName.replace(/(?:removed|clicked):/,'')}`;
|
|
let cascObj = casc[cascName];
|
|
k.debug({[cascName]:cascObj});
|
|
if(event){
|
|
if(typePrefix === 'attr_'){
|
|
cascObj.previousValue = event.previousValue;
|
|
}else if(typePrefix === 'act_'){
|
|
cascObj.originalRollId = event.originalRollId;
|
|
}
|
|
}
|
|
return cascObj;
|
|
};
|
|
|
|
const triggerFunctions = function(obj,attributes,sections,casc){
|
|
if(obj.triggeredFuncs && obj.triggeredFuncs.length){
|
|
debug(`triggering functions for ${obj.name}`);
|
|
obj.triggeredFuncs && obj.triggeredFuncs.forEach(func=>funcs[func] ?
|
|
funcs[func]({trigger:obj,attributes,sections,casc}) :
|
|
debug(`!!!Warning!!! no function named ${func} found. Triggered function not called for ${obj.name}`,true));
|
|
}
|
|
};
|
|
|
|
const initialFunction = function(obj,attributes,sections,casc){
|
|
if(obj.initialFunc){
|
|
debug(`initial functions for ${obj.name}`);
|
|
funcs[obj.initialFunc] ?
|
|
funcs[obj.initialFunc]({trigger:obj,attributes,sections,casc}) :
|
|
debug(`!!!Warning!!! no function named ${obj.initialFunc} found. Initial function not called for ${obj.name}`,true);
|
|
}
|
|
};
|
|
const alwaysFunctions = function(trigger,attributes,sections,casc){
|
|
Object.values(allHandlers).forEach((handler)=>{
|
|
handler({trigger,attributes,sections,casc});
|
|
});
|
|
};
|
|
const processChange = function({event,trigger,attributes,sections,casc}){
|
|
if(event && !trigger){
|
|
debug(`${event.sourceAttribute} change detected. No trigger found`);
|
|
return;
|
|
}
|
|
if(!attributes || !sections || !casc){
|
|
debug(`!!! Insufficient arguments || attributes > ${!!attributes} | sections > ${!!sections} | casc > ${!!casc} !!!`);
|
|
return;
|
|
}
|
|
debug({trigger});
|
|
if(event){
|
|
debug('checking for initial & always functions');
|
|
alwaysFunctions(trigger,attributes,sections,casc);//Functions that should be run for all events.
|
|
initialFunction(trigger,attributes,sections,casc);//functions that should only be run if the attribute was the thing changed by the user
|
|
}
|
|
if(trigger){
|
|
debug(`processing ${trigger.name}`);
|
|
triggerFunctions(trigger,attributes,sections,casc);
|
|
if(!event && trigger.calculation && funcs[trigger.calculation]){
|
|
attributes[trigger.name] = funcs[trigger.calculation]({trigger,attributes,sections,casc});
|
|
}else if(trigger.calculation && !funcs[trigger.calculation]){
|
|
debug(`K-Scaffold Error: No function named ${trigger.calculation} found`);
|
|
}
|
|
if(Array.isArray(trigger.affects)){
|
|
attributes.queue.push(...trigger.affects);
|
|
}
|
|
}
|
|
attributes.set({attributes,sections,casc});
|
|
};
|
|
const attrTarget = {
|
|
updates:{},
|
|
attributes:{...attrs},
|
|
repOrders:{},
|
|
queue: [],
|
|
casc:{},
|
|
alwaysFunctions,
|
|
processChange,
|
|
triggerFunctions,
|
|
initialFunction,
|
|
getCascObj
|
|
};
|
|
const attrHandler = {
|
|
get:function(obj,prop){//gets the most value of the attribute.
|
|
//If it is a repeating order, returns the array, otherwise returns the update value or the original value
|
|
if(prop === 'set'){
|
|
return function(){
|
|
let {attributes,sections,casc,callback,vocal} = arguments[0] ? arguments[0] : {};
|
|
if(attributes && attributes.queue.length && sections && casc){
|
|
let triggerName = attributes.queue.shift();
|
|
let trigger = getCascObj({sourceAttribute:triggerName},casc);
|
|
attributes.processChange({trigger,attributes,sections,casc});
|
|
}else{
|
|
debug({updates:obj.updates});
|
|
let trueCallback = Object.keys(obj.repOrders).length ?
|
|
function(){
|
|
Object.entries(obj.repOrders).forEach(([section,order])=>{
|
|
_setSectionOrder(section,order,)
|
|
});
|
|
callback && callback();
|
|
}:
|
|
callback;
|
|
Object.keys(obj.updates).forEach((key)=>obj.attributes[key] = obj.updates[key]);
|
|
const update = obj.updates;
|
|
obj.updates = {};
|
|
set(update,vocal,trueCallback);
|
|
}
|
|
}
|
|
}else if(Object.keys(obj).some(key=>key===prop)){
|
|
return Reflect.get(...arguments)
|
|
}else{
|
|
let retValue;
|
|
switch(true){
|
|
case obj.repOrders.hasOwnProperty(prop):
|
|
retValue = obj.repOrders[prop];
|
|
break;
|
|
case obj.updates.hasOwnProperty(prop):
|
|
retValue = obj.updates[prop];
|
|
break;
|
|
default:
|
|
retValue = obj.attributes[prop];
|
|
break;
|
|
}
|
|
let cascRef = `attr_${prop.replace(/(repeating_[^_]+_)[^_]+/,'$1\$X')}`;
|
|
let numRetVal = +retValue;
|
|
if(!Number.isNaN(numRetVal) && retValue !== ''){
|
|
retValue = numRetVal;
|
|
}else if(cascades[cascRef] && (typeof cascades[cascRef].defaultValue === 'number' || cascades[cascRef].type === 'number')){
|
|
retValue = cascades[cascRef].defaultValue;
|
|
}
|
|
return retValue;
|
|
}
|
|
},
|
|
set:function(obj,prop,value){
|
|
//Sets the value. Also verifies that the value is a valid attribute value
|
|
//e.g. not undefined, null, or NaN
|
|
if(value || value===0 || value===''){
|
|
if(/reporder|^repeating_[^_]+$/.test(prop)){
|
|
let section = prop.replace(/_reporder_/,'');
|
|
obj.repOrders[section] = value;
|
|
}else if(`${obj.attributes[prop]}` !== `${value}` ||
|
|
(obj.updates[prop] && `${obj.updates[prop]}` !== `${value}`)
|
|
){
|
|
obj.updates[prop] = value;
|
|
}
|
|
}else{
|
|
debug(`!!!Warning: Attempted to set ${prop} to an invalid value:${value}; value not stored!!!`);
|
|
}
|
|
return true;
|
|
},
|
|
deleteProperty(obj,prop){
|
|
//removes the property from the original attributes, updates, and the reporders
|
|
Object.keys(obj).forEach((key)=>{
|
|
delete obj[key][prop.toLowerCase()];
|
|
});
|
|
}
|
|
};
|
|
return new Proxy(attrTarget,attrHandler);
|
|
};
|
|
|
|
|
|
/**
|
|
* Function that registers a function for being called via the funcs object. Returns true if the function was successfully registered, and false if it could not be registered for any reason.
|
|
* @param {object} funcObj - Object with keys that are names to register functions under and values that are functions.
|
|
* @param {object} optionsObj - Object that contains options to use for this registration.
|
|
* @param {string[]} optionsObj.type - Array that contains the types of specialized functions that apply to the functions being registered. Valid types are `"opener"`, `"updater"`, and `"default"`. `"default"` is always used, and never needs to be passed.
|
|
* @returns {boolean} - True if the registration succeeded, false if it failed.
|
|
* @example
|
|
* //Basic Registration
|
|
* const myFunc = function({trigger,attributes,sections,casc}){};
|
|
* k.registerFuncs({myFunc});
|
|
*
|
|
* //Register a function to run on sheet open
|
|
* const openFunc = function({trigger,attributes,sections,casc}){};
|
|
* k.registerFuncs({openFunc},{type:['opener']})
|
|
*
|
|
* //Register a function to run on all events
|
|
* const allFunc = function({trigger,attributes,sections,casc}){};
|
|
* k.registerFuncs({allFunc},{type:['all']})
|
|
*/
|
|
const registerFuncs = function(funcObj,optionsObj = {}){
|
|
if(typeof funcObj !== 'object' || typeof optionsObj !== 'object'){
|
|
debug(`!!!! K-scaffold error: Improper arguments to register functions !!!!`);
|
|
return false;
|
|
}
|
|
const typeArr = optionsObj.type ? ['default',...optionsObj.type] : ['default'];
|
|
const typeSwitch = {
|
|
'opener':openHandlers,
|
|
'updater':updateHandlers,
|
|
'new':initialSetups,
|
|
'all':allHandlers,
|
|
'default':funcs
|
|
};
|
|
let setState;
|
|
Object.entries(funcObj).map(([prop,value])=>{
|
|
typeArr.forEach((type)=>{
|
|
if(typeSwitch[type][prop]){
|
|
debug(`!!! Duplicate function name for ${prop} as ${type}!!!`);
|
|
setState = false;
|
|
}else if(typeof value === 'function'){
|
|
typeSwitch[type][prop] = value;
|
|
setState = setState !== false ? true : false;
|
|
}else{
|
|
debug(`!!! K-scaffold error: Function registration requires a function. Invalid value to register as ${type} !!!`);
|
|
setState = false;
|
|
}
|
|
});
|
|
});
|
|
return setState;
|
|
};
|
|
kFuncs.registerFuncs = registerFuncs;
|
|
|
|
const setActionCalls = function({attributes,sections}){
|
|
actionAttributes.forEach((base)=>{
|
|
let [section,,field] = k.parseTriggerName(base);
|
|
let fieldAction = field.replace(/_/g,'-');
|
|
if(section){
|
|
sections[section].forEach((id)=>{
|
|
attributes[`${section}_${id}_${field}`] = `%{${attributes.character_name}|${section}_${id}_${fieldAction}}`;
|
|
});
|
|
}else{
|
|
attributes[`${field}`] = `%{${attributes.character_name}|${fieldAction}}`;
|
|
}
|
|
});
|
|
};
|
|
funcs.setActionCalls = setActionCalls;
|
|
kFuncs.setActionCalls = setActionCalls;
|
|
|
|
/**
|
|
* Function to call a function previously registered to the funcs object. May not be used that much. Either returns the function or null if no function exists.
|
|
* @param {string} funcName - The name of the function to invoke.
|
|
* @param {...any} args - The arguments to call the function with.
|
|
* @returns {any}
|
|
* @example
|
|
* //Call myFunc with two arguments
|
|
* k.callFunc('myFunc','an argument','another argument');
|
|
*/
|
|
const callFunc = function(funcName,...args){
|
|
if(funcs[funcName]){
|
|
debug(`calling ${funcName}`);
|
|
return funcs[funcName](...args);
|
|
}else{
|
|
debug(`Invalid function name: ${funcName}`);
|
|
return null;
|
|
}
|
|
};
|
|
kFuncs.callFunc = callFunc;/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
//Sheet Updaters and styling functions
|
|
const updateSheet = function(){
|
|
log('updating sheet');
|
|
getAllAttrs({props:['debug_mode',...baseGet],callback:(attributes,sections,casc)=>{
|
|
kFuncs.debugMode = kFuncs.debugMode || !!attributes.debug_mode;
|
|
debug({sheet_version:attributes.sheet_version});
|
|
if(!attributes.sheet_version){
|
|
Object.entries(initialSetups).forEach(([funcName,handler])=>{
|
|
if(typeof funcs[funcName] === 'function'){
|
|
debug(`running ${funcName}`);
|
|
funcs[funcName]({attributes,sections,casc});
|
|
}else{
|
|
debug(`!!!Warning!!! no function named ${funcName} found. Initial sheet setup not performed.`);
|
|
}
|
|
});
|
|
}else{
|
|
Object.entries(updateHandlers).forEach(([ver,handler])=>{
|
|
if(attributes.sheet_version < +ver){
|
|
handler({attributes,sections,casc});
|
|
}
|
|
});
|
|
}
|
|
Object.entries(openHandlers).forEach(([funcName,func])=>{
|
|
if(typeof funcs[funcName] === 'function'){
|
|
debug(`running ${funcName}`);
|
|
funcs[funcName]({attributes,sections,casc});
|
|
}else{
|
|
debug(`!!!Warning!!! no function named ${funcName} found. Sheet open handling not performed.`);
|
|
}
|
|
});
|
|
setActionCalls({attributes,sections});
|
|
attributes.sheet_version = kFuncs.version;
|
|
log(`Sheet Update applied. Current Sheet Version ${kFuncs.version}`);
|
|
attributes.set();
|
|
log('Sheet ready for use');
|
|
}});
|
|
};
|
|
|
|
const initialSetup = function(attributes,sections){
|
|
debug('Initial sheet setup');
|
|
};
|
|
|
|
/**
|
|
* This is the default listener function for attributes that the K-Scaffold uses. It utilizes the `triggerFuncs`, `listenerFunc`, `calculation`, and `affects` properties of the K-scaffold trigger object (see the Pug section of the scaffold for more details).
|
|
* @param {Roll20Event} event
|
|
* @returns {void}
|
|
* @example
|
|
* //Call from an attribute change
|
|
* on('change:an_attribute',k.accessSheet);
|
|
*/
|
|
const accessSheet = function(event){
|
|
debug({funcs:Object.keys(funcs)});
|
|
debug({event});
|
|
getAllAttrs({callback:(attributes,sections,casc)=>{
|
|
let trigger = attributes.getCascObj(event,casc);
|
|
attributes.processChange({event,trigger,attributes,sections,casc});
|
|
}});
|
|
};
|
|
funcs.accessSheet = accessSheet;/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
/*
|
|
Cascade Expansion functions
|
|
*/
|
|
//Expands the repeating section templates in cascades to reflect the rows actually available
|
|
const expandCascade = function(cascade,sections,attributes){
|
|
return _.keys(cascade).reduce((memo,key)=>{//iterate through cascades and replace references to repeating attributes with correct row ids.
|
|
if(/^(?:act|attr)_repeating_/.test(key)){//If the attribute is a repeating attribute, do special logic
|
|
expandRepeating(memo,key,cascade,sections,attributes);
|
|
}else if(key){//for non repeating attributes do this logic
|
|
expandNormal(memo,key,cascade,sections);
|
|
}
|
|
return memo;
|
|
},{});
|
|
};
|
|
|
|
const expandRepeating = function(memo,key,cascade,sections,attributes){
|
|
key.replace(/((?:attr|act)_)(repeating_[^_]+)_[^_]+?_(.+)/,(match,type,section,field)=>{
|
|
(sections[section]||[]).forEach((id)=>{
|
|
memo[`${type}${section}_${id}_${field}`]=_.clone(cascade[key]);//clone the details so that each row's attributes have correct ids
|
|
memo[`${type}${section}_${id}_${field}`].name = `${section}_${id}_${field}`;
|
|
if(key.startsWith('attr_')){
|
|
memo[`${type}${section}_${id}_${field}`].affects = memo[`${type}${section}_${id}_${field}`].affects.reduce((m,affected)=>{
|
|
if(section === affected){//otherwise if the affected attribute is in the same section, simply set the affected attribute to have the same row id.
|
|
m.push(applyID(affected,id));
|
|
}else if(/repeating/.test(affected)){//If the affected attribute isn't in the same repeating section but is still a repeating attribute, add all the rows of that section
|
|
addAllRows(affected,m,sections);
|
|
}else{//otherwise the affected attribute is a non repeating attribute. Simply add it to the computed affected array
|
|
m.push(affected);
|
|
}
|
|
return m;
|
|
},[]);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const applyID = function(affected,id){
|
|
return affected.replace(/(repeating_[^_]+_)[^_]+(.+)/,`$1${id}$2`);
|
|
};
|
|
|
|
const expandNormal = function(memo,key,cascade,sections){
|
|
memo[key] = _.clone(cascade[key]);
|
|
if(key.startsWith('attr_')){
|
|
memo[key].affects = memo[key].affects || [];
|
|
memo[key].affects = memo[key].affects.reduce((m,a)=>{
|
|
if(/^repeating/.test(a)){
|
|
addAllRows(a,m,sections);
|
|
}else{
|
|
m.push(a);
|
|
}
|
|
return m;
|
|
},[]);
|
|
}
|
|
};
|
|
|
|
const addAllRows = function(affected,memo,sections){
|
|
affected.replace(/(repeating_[^_]+?)_[^_]+?_(.+)/,(match,section,field)=>{
|
|
sections[section].forEach(id=>memo.push(`${section}_${id}_${field}`));
|
|
});
|
|
};/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
/**
|
|
* Alias for [setSectionOrder()](https://wiki.roll20.net/Sheet_Worker_Scripts#setSectionOrder.28.3CRepeating_Section_Name.3E.2C_.3CSection_Array.3E.2C_.3CCallback.3E.29) that allows you to use the section name in either `repeating_section` or `section` formats. Note that the Roll20 sheetworker [setSectionOrder](https://wiki.roll20.net/Sheet_Worker_Scripts#setSectionOrder.28.3CRepeating_Section_Name.3E.2C_.3CSection_Array.3E.2C_.3CCallback.3E.29) currently causes some display issues on sheets.
|
|
* @param {string} section
|
|
* @param {string[]} order
|
|
* @returns {void}
|
|
* @example
|
|
* //Set the order of a repeating_weapon section
|
|
* k.setSectionOrder('repeating_equipment',['id1','id2','id3']);
|
|
* //Can also specify the section name without the repeating_ prefix
|
|
* k.setSectionOrder('equipment',['id1','id2','id3']);
|
|
*/
|
|
const _setSectionOrder = function(section,order){
|
|
let trueSection = section.replace(/repeating_/,'');
|
|
setSectionOrder(trueSection,order);
|
|
};
|
|
kFuncs.setSectionOrder = _setSectionOrder;
|
|
|
|
/**
|
|
* Alias for [removeRepeatingRow](https://wiki.roll20.net/Sheet_Worker_Scripts#removeRepeatingRow.28_RowID_.29) that also removes the row from the current object of attribute values and array of section IDs to ensure that erroneous updates are not issued.
|
|
* @param {string} row - The row id to be removed
|
|
* @param {attributesProxy} attributes - The attribute values currently in memory
|
|
* @param {object} sections - Object that contains arrays of all the IDs in sections on the sheet indexed by repeating name.
|
|
* @returns {void}
|
|
* @example
|
|
* //Remove a repeating Row
|
|
* k.getAllAttrs({
|
|
* callback:(attributes,sections)=>{
|
|
* const rowID = sections.repeating_equipment[0];
|
|
* k.removeRepeatingRow(`repeating_equipment_${rowID}`,attributes,sections);
|
|
* console.log(sections.repeating_equipment); // => rowID no longer exists in the array.
|
|
* console.log(attributes[`repeating_equipment_${rowID}_name`]); // => undefined
|
|
* }
|
|
* })
|
|
*/
|
|
const _removeRepeatingRow = function(row,attributes,sections){
|
|
debug(`removing ${row}`);
|
|
Object.keys(attributes.attributes).forEach((key)=>{
|
|
if(key.startsWith(row)){
|
|
delete attributes[key];
|
|
}
|
|
});
|
|
let [,section,rowID] = row.match(/(repeating_[^_]+)_(.+)/,'');
|
|
sections[section] = sections[section].filter((id)=>id!==rowID);
|
|
removeRepeatingRow(row);
|
|
};
|
|
kFuncs.removeRepeatingRow = _removeRepeatingRow;
|
|
|
|
/**
|
|
* Alias for [getAttrs()](https://wiki.roll20.net/Sheet_Worker_Scripts#getAttrs.28attributeNameArray.2C_callback.29) that converts the default object of attribute values into an {@link attributesProxy} and passes that back to the callback function.
|
|
* @param {Object} args
|
|
* @param {string[]} [args.props=baseGet] - Array of attribute names to get the value of. Defaults to {@link baseGet} if not passed.
|
|
* @param {function(attributesProxy)} args.callback - The function to call after the attribute values have been gotten. An {@link attributesProxy} is passed to the callback.
|
|
* @example
|
|
* //Gets the attributes named in props.
|
|
* k.getAttrs({
|
|
* props:['attribute_1','attribute_2'],
|
|
* callback:(attributes)=>{
|
|
* //Work with the attributes as you would in a normal getAttrs, or use the superpowers of the K-scaffold attributes object like so:
|
|
* attributes.attribute_1 = 'new value';
|
|
* attributes.set();
|
|
* }
|
|
* })
|
|
*/
|
|
const _getAttrs = function({props=baseGet,callback}){
|
|
getAttrs(props,(values)=>{
|
|
const attributes = createAttrProxy(values);
|
|
callback(attributes);
|
|
});
|
|
};
|
|
kFuncs.getAttrs = _getAttrs;
|
|
|
|
/**
|
|
* Alias for [getAttrs()](https://wiki.roll20.net/Sheet_Worker_Scripts#getAttrs.28attributeNameArray.2C_callback.29) and [getSectionIDs](https://wiki.roll20.net/Sheet_Worker_Scripts#getSectionIDs.28section_name.2Ccallback.29) that combines the actions of both sheetworker functions and converts the default object of attribute values into an {@link attributesProxy}. Also gets the details on how to handle all attributes from the master {@link cascades} object and.
|
|
* @param {Object} args
|
|
* @param {string[]} [args.props=baseGet] - Array of attribute names to get the value of. Defaults to {@link baseGet} if not passed.
|
|
* @param {repeatingSectionDetails} sectionDetails - Array of details about a section to get the IDs for and attributes that need to be gotten.
|
|
* @param {function(attributesProxy,sectionObj,expandedCascade):void} args.callback - The function to call after the attribute values have been gotten. An {@link attributesProxy} is passed to the callback along with a {@link sectionObj} and {@link expandedCascade}.
|
|
* @example
|
|
* //Get every K-scaffold linked attribute on the sheet
|
|
* k.getAllAttrs({
|
|
* callback:(attributes,sections,casc)=>{
|
|
* //Work with the attributes as you please.
|
|
* attributes.some_attribute = 'a value';
|
|
* attributes.set();//Apply our change
|
|
* }
|
|
* })
|
|
*/
|
|
const getAllAttrs = function({props=baseGet,sectionDetails=repeatingSectionDetails,callback}){
|
|
getSections(sectionDetails,(repeats,sections)=>{
|
|
getAttrs([...props,...repeats],(values)=>{
|
|
const attributes = createAttrProxy(values);
|
|
orderSections(attributes,sections);
|
|
const casc = expandCascade(cascades,sections,attributes);
|
|
callback(attributes,sections,casc);
|
|
})
|
|
});
|
|
};
|
|
kFuncs.getAllAttrs = getAllAttrs;
|
|
|
|
/**
|
|
* Alias for [getSectionIDs()](https://wiki.roll20.net/Sheet_Worker_Scripts#getSectionIDs.28section_name.2Ccallback.29) that allows you to iterate through several functions at once. Also assembles an array of repeating attributes to get.
|
|
* @param {object[]} sectionDetails - Array of details about a section to get the IDs for and attributes that need to be gotten.
|
|
* @param {string} sectionDetails.section - The full name of the repeating section including the `repeating_` prefix.
|
|
* @param {string[]} sectionDetails.fields - Array of field names that need to be gotten from the repeating section
|
|
* @param {function(string[],sectionObj)} callback - The function to call once all IDs have been gotten and the array of repating attributes to get has been assembled. The callback is passed the array of repating attributes to get and a {@link sectionObj}.
|
|
* @example
|
|
* // Get some section details
|
|
* const sectionDetails = {
|
|
* {section:'repeating_equipment',fields:['name','weight','cost']},
|
|
* {section:'repeating_weapon',fields:['name','attack','damage']}
|
|
* };
|
|
* k.getSections(sectionDetails,(attributeNames,sections)=>{
|
|
* console.log(attributeNames);// => Array containing all row specific attribute names
|
|
* console.log(sections);// => Object with arrays containing the row ids. Indexed by section name (e.g. repeating_eqiupment)
|
|
* })
|
|
*/
|
|
const getSections = function(sectionDetails,callback){
|
|
let queueClone = _.clone(sectionDetails);
|
|
const worker = (queue,repeatAttrs=[],sections={})=>{
|
|
let detail = queue.shift();
|
|
getSectionIDs(detail.section,(IDs)=>{
|
|
sections[detail.section] = IDs;
|
|
IDs.forEach((id)=>{
|
|
detail.fields.forEach((f)=>{
|
|
repeatAttrs.push(`${detail.section}_${id}_${f}`);
|
|
});
|
|
});
|
|
repeatAttrs.push(`_reporder_${detail.section}`);
|
|
if(queue.length){
|
|
worker(queue,repeatAttrs,sections);
|
|
}else{
|
|
callback(repeatAttrs,sections);
|
|
}
|
|
});
|
|
};
|
|
if(!queueClone[0]){
|
|
callback([],{});
|
|
}else{
|
|
worker(queueClone);
|
|
}
|
|
};
|
|
kFuncs.getSections = getSections;
|
|
|
|
// Sets the attributes while always calling with {silent:true}
|
|
// Can be awaited to get the values returned from _setAttrs
|
|
/**
|
|
* Alias for [setAttrs()](https://wiki.roll20.net/Sheet_Worker_Scripts#setAttrs.28values.2Coptions.2Ccallback.29) that sets silently by default.
|
|
* @param {object} obj - The object containting attributes to set
|
|
* @param {boolean} [vocal=false] - Whether to set silently (default value) or not.
|
|
* @param {function()} [callback] - The callback function to invoke after the setting has been completed. No arguments are passed to the callback function.
|
|
* @example
|
|
* //Set some attributes silently
|
|
* k.setAttrs({attribute_1:'new value'})
|
|
* //Set some attributes and triggers listeners
|
|
* k.setAttrs({attribute_1:'new value',true})
|
|
* //Set some attributes and call a callback function
|
|
* k.setAttrs({attribute_1:'new value'},null,()=>{
|
|
* //Do something after the attribute is set
|
|
* })
|
|
*/
|
|
const set = function(obj,vocal=false,callback){
|
|
setAttrs(obj,{silent:!vocal},callback);
|
|
};
|
|
kFuncs.setAttrs = set;
|
|
|
|
const generateCustomID = function(string){
|
|
if(!string.startsWith('-')){
|
|
string = `-${string}`;
|
|
}
|
|
rowID = generateRowID();
|
|
let re = new RegExp(`^.{${string.length}}`);
|
|
return `${string}${rowID.replace(re,'')}`;
|
|
};
|
|
|
|
|
|
/**
|
|
* Alias for generateRowID that adds the new id to the {@link sectionObj}. Also allows for creation of custom IDs that conform to the section ID requirements.
|
|
* @param {sectionObj} sections
|
|
* @param {string} [customText] - Custom text to start the ID with. This text should not be longer than the standard repeating section ID format.
|
|
* @returns {string} - The created ID
|
|
* @example
|
|
* k.getAllAttrs({
|
|
* callback:(attributes,sections,casc)=>{
|
|
* //Create a new row ID
|
|
* const rowID = k.generateRowID('repeating_equipment',sections);
|
|
* console.log(rowID);// => -p8rg908ug0suzz
|
|
* //Create a custom row ID
|
|
* const customID = k.generateRowID('repeating_equipment',sections,'custom');
|
|
* console.log(customID);// => -custom98uadj89kj
|
|
* }
|
|
* });
|
|
*/
|
|
const _generateRowID = function(section,sections,customText){
|
|
let rowID = customText ?
|
|
generateCustomID(customText) :
|
|
generateRowID();
|
|
section = section.match(/^repeating_[^_]+$/) ?
|
|
section :
|
|
`repeating_${section}`;
|
|
sections[section] = sections[section] || [];
|
|
sections[section].push(rowID);
|
|
return `${section}_${rowID}`;
|
|
};
|
|
kFuncs.generateRowID = _generateRowID;/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
const listeners = {};
|
|
const baseGet = Object.entries(cascades).reduce((memo,[attrName,detailObj])=>{
|
|
if(!/repeating/.test(attrName) && detailObj.type !== 'action'){
|
|
memo.push(detailObj.name);
|
|
}
|
|
if(detailObj.listener){
|
|
listeners[detailObj.listener] = detailObj.listenerFunc;
|
|
}
|
|
return memo;
|
|
},['sheet_version']);
|
|
kFuncs.baseGet = baseGet;
|
|
const registerEventHandlers = function(){
|
|
on('sheet:opened',updateSheet);
|
|
debug({funcKeys:Object.keys(funcs),funcs});
|
|
//Roll20 change and click listeners
|
|
Object.entries(listeners).forEach(([event,funcName])=>{
|
|
if(funcs[funcName]){
|
|
on(event,funcs[funcName]);
|
|
}else{
|
|
debug(`!!!Warning!!! no function named ${funcName} found. No listener created for ${event}`,true);
|
|
}
|
|
});
|
|
log(`kScaffold Loaded`);
|
|
};
|
|
setTimeout(registerEventHandlers,0);//Delay the execution of event registration to ensure all event properties are present.
|
|
|
|
const addItem = function(event){
|
|
let [,,section] = parseClickTrigger(event.triggerName);
|
|
section = section.replace(/add-/,'');
|
|
getAllAttrs({
|
|
callback:(attributes,sections,casc) => {
|
|
let row = _generateRowID(section,sections);
|
|
debug({row});
|
|
attributes[`${row}_name`] = '';
|
|
setActionCalls({attributes,sections});
|
|
const trigger = cascades[`fieldset_repeating_${section}`];
|
|
if(trigger && trigger.addFuncs){
|
|
trigger.addFuncs.forEach((funcName) => {
|
|
if(funcs[funcName]){
|
|
funcs[funcName]({attributes,sections,casc,trigger,newRow:row});
|
|
}
|
|
});
|
|
}
|
|
attributes.set({attributes,sections,casc});
|
|
}
|
|
});
|
|
};
|
|
funcs.addItem = addItem;
|
|
|
|
const editSection = function(event){
|
|
let [,,section] = parseClickTrigger(event.triggerName);
|
|
section = section.replace(/edit-/,'');
|
|
let target = `fieldset.repeating_${section} + .repcontainer`;
|
|
$20(target).toggleClass('ui-sortable');
|
|
$20(target).toggleClass('editmode');
|
|
};
|
|
registerFuncs({editSection});/**
|
|
* The default tab navigation function of the K-scaffold. Courtesy of Riernar. It will add `k-active-tab` to the active tab-container and `k-active-button` to the active button. You can either write your own CSS to control display of these, or use the default CSS included in `scaffold/_k.scss`. Note that `k-active-button` has no default CSS as it is assumed that you will want to style the active button to match your system.
|
|
* @param {Object} trigger - The trigger object
|
|
*/
|
|
const kSwitchTab = function ({ trigger, attributes }) {
|
|
const [container, tab] = (
|
|
trigger.name.match(/nav-tabs-(.+)--(.+)/) ||
|
|
[]
|
|
).slice(1);
|
|
$20(`[data-container-tab="${container}"]`).removeClass('k-active-tab');
|
|
$20(`[data-container-tab="${container}"][data-tab="${tab}"]`).addClass('k-active-tab');
|
|
$20(`[data-container-button="${container}"]`).removeClass('k-active-button');
|
|
$20(`[data-container-button="${container}"][data-button="${tab}"]`).addClass('k-active-button');
|
|
const tabInputName = `${container.replace(/\-/g,'_')}_tab`;
|
|
if(persistentTabs.indexOf(tabInputName) > -1){
|
|
attributes[tabInputName] = trigger.name;
|
|
}
|
|
}
|
|
|
|
registerFuncs({ kSwitchTab });
|
|
|
|
/**
|
|
* Sets persistent tabs to their last active state
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const kTabOnOpen = function({trigger,attributes,sections,casc}){
|
|
persistentTabs.forEach((tabInput) => {
|
|
const pseudoTrigger = {name:attributes[tabInput]};
|
|
kSwitchTab({trigger:pseudoTrigger, attributes});
|
|
});
|
|
};
|
|
registerFuncs({ kTabOnOpen },{type:['opener']});
|
|
return kFuncs;
|
|
}());
|
|
const actionAttributes = ["force_action","hand_to_hand_action","stamina_action","firearms_action","mobility_action","stealth_action","medical_aid_action","observation_action","tech_action","connections_action","insight_action","manipulation_action","repeating_weapons-hand_$X_action","repeating_weapons-firearm_$X_action","armor_action","vehicle_armor_action","maneuverability_action"];const criticalEffects = {"crushing":["Teeth Knocked Out","Broken Nose","Broken Fingers","Gouged Eye","Cracked Ribs","Concussion","Broken Leg","Broken Arm","Internal Bleeding","broken neck","shattered pelvis","crushed skull"],"piercing":["ear torn off","hand impaled","pierced eye","shoulder hit","bleeding gut","cracked skull","punctured lung","brains blown out","massive internal organ damage","pierced heart","severed leg","shattered head"]};const persistentTabs = ["tabs_tab"];const abilitySkill = {"force":"strength","hand to hand":"strength","stamina":"strength","firearms":"agility","mobility":"agility","stealth":"agility","medical aid":"intelligence","observation":"intelligence","tech":"intelligence","connections":"empathy","insight":"empathy","manipulation":"empathy","maneuverability":"driving"};const shifts = ["morning","day","evening","night"];
|
|
k.version = 1.01;/**
|
|
*
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const update1x01 = function({trigger,attributes,sections,casc}){
|
|
if(attributes.armor_rating){
|
|
if(/^(?:a|b|c|d)$/i.test(attributes.armor_rating)){
|
|
attributes.armor = attributes.armor_rating.toUpperCase();
|
|
}else{
|
|
const dieRatingSwitch = {
|
|
'6':'D',
|
|
'8':'C',
|
|
'10':'B',
|
|
'12':'A'
|
|
};
|
|
const dieSize = attributes.armor_rating.replace(/\D/g,'');
|
|
const rating = dieRatingSwitch[dieSize];
|
|
if(rating){
|
|
attributes.armor = rating;
|
|
}
|
|
}
|
|
if(attributes.updates.armor){
|
|
attributes.queue.push('armor');
|
|
}
|
|
attributes.armor_rating = '';
|
|
}
|
|
};
|
|
k.registerFuncs({'1.01':update1x01},{type:['updater']});/**
|
|
*
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const calcAttributeDice = function({trigger,attributes,sections,casc}){
|
|
const [section,rowID,diceName] = k.parseTriggerName(trigger.name);
|
|
const attrName = diceName.replace(/_dice$/,'');
|
|
const ratingDieSwitch = {
|
|
A:'d12',
|
|
B:'d10',
|
|
C:'d8',
|
|
D:'d6'
|
|
};
|
|
return ratingDieSwitch[attributes[attrName]] || '-';
|
|
};
|
|
k.registerFuncs({calcAttributeDice});/**
|
|
* Limites the health and resolve values to the current maxiumum. Triggerd by changing the health or resolve (or their maximums)
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const limitHealthResolve = function({trigger,attributes,sections,casc}){
|
|
const baseName = trigger.name.replace(/_max$/,'');//Convert to the base health attribute name
|
|
attributes[baseName] = Math.min(attributes[baseName],attributes[`${baseName}_max`]);
|
|
};
|
|
k.registerFuncs({limitHealthResolve});const createShift = (attributes,sections,row) => {
|
|
const isNew = !!row;
|
|
row = row || k.generateRowID('repeating_shift',sections);
|
|
const dayTranslation = getTranslationByKey('day {{day number}}').replace(/\{\{0\}\}/,sections.repeating_shift.length);
|
|
attributes[`${row}_name`] = isNew && attributes.day_label ?
|
|
attributes.day_label :
|
|
k.capitalize(dayTranslation);
|
|
return row;
|
|
}
|
|
|
|
/**
|
|
* Stores the shift log in the day's database entry
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const storeShift = function({trigger,attributes,sections,casc}){
|
|
const existingLog = attributes.day_id && sections.repeating_shift.includes(attributes.day_id)
|
|
const row = existingLog ?
|
|
`repeating_shift_${attributes.day_id}` :
|
|
k.generateRowID('repeating_shift',sections);
|
|
|
|
// Store the day_id if it was changed and update the day name if it was changed
|
|
if(!existingLog){
|
|
createShift(attributes,sections,row);
|
|
}
|
|
attributes.day_label = attributes.day_label || attributes[`${row}_name`];
|
|
attributes.day_id = existingLog ?
|
|
attributes.day_id :
|
|
k.parseRepeatName(row)[1];
|
|
shifts.forEach(shift =>
|
|
attributes[`${row}_${shift}`] = attributes[`${shift}_shift`]
|
|
)
|
|
}
|
|
k.registerFuncs({storeShift});
|
|
|
|
/**
|
|
* Displays the selected log details and stores the previously displayed log details in their log
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const displayLog = function({trigger,attributes,sections,casc}){
|
|
const [section,rowID] = k.parseTriggerName(trigger.name);
|
|
attributes.day_id = rowID;
|
|
attributes.day_label = attributes[`${section}_${rowID}_name`];
|
|
shifts.forEach(shift => {
|
|
attributes[`${shift}_shift`] = attributes[`${section}_${rowID}_${shift}`] || '';
|
|
});
|
|
};
|
|
k.registerFuncs({displayLog});
|
|
|
|
/**
|
|
* Triggers display updates for adding a new calendar entry
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const addCalendar = function({trigger,attributes,sections,casc,newRow}){
|
|
displayLog({trigger:{name:`${newRow}_display-log`},attributes});
|
|
};
|
|
k.registerFuncs({addCalendar});
|
|
|
|
/**
|
|
* Increments the shift.
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const incrementShift = function({trigger,attributes,sections,casc}){
|
|
attributes.shift_counter += 1;
|
|
const rawNextShiftIndex = shifts.indexOf(attributes.active_shift) + 1;
|
|
if(rawNextShiftIndex > 3){
|
|
const row = createShift(attributes,sections);
|
|
displayLog({trigger:{name:`${row}_display-log`},attributes,sections,casc});
|
|
}
|
|
const nextShiftIndex = rawNextShiftIndex % 4;//Limit the shift index to be between 0 and 3, looping back to 0 on overflow
|
|
attributes.active_shift = shifts[nextShiftIndex];
|
|
sections.repeating_specialties.forEach(id => {
|
|
attributes[`repeating_specialties_${id}_shift`] = 0;
|
|
});
|
|
};
|
|
k.registerFuncs({incrementShift});
|
|
|
|
/**
|
|
* Clears the active shift tracker
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const takeDowntime = function({trigger,attributes,sections,casc}){
|
|
incrementShift({attributes,sections,casc})
|
|
attributes.shift_counter = 0;
|
|
};
|
|
k.registerFuncs({takeDowntime});/**
|
|
* Compares the die sizes of two dice and returns the lower one.
|
|
* @param {object} dieObj - object describing the potential dice to roll
|
|
* @returns {string} - The key that matches the lower die
|
|
*/
|
|
const lowerDie = (dieObj) => {
|
|
return Object.entries(dieObj).reduce((str,[key,die]) => {
|
|
const dieSize = +die.replace(/\D/g,'') || 20;
|
|
const storedSize = dieObj[str] && +dieObj[str].replace(/\D/g,'') || 20;
|
|
if(dieSize < storedSize){
|
|
str = key;
|
|
}
|
|
return str;
|
|
},'')
|
|
}
|
|
|
|
/**
|
|
* Applies the effects of advantage/disadvantage
|
|
* @param {string} skillName - The name of the skill being rolled
|
|
* @param {string} die1 - The die to use for the base attribute
|
|
* @param {string|null} die2 - The die to use for the ability
|
|
* @param {AttributesProxy} attributes - The R20 attributes of the character
|
|
* @returns {object} - The roll fields to add to the roll
|
|
*/
|
|
const applyRollType = (skillName,die1,die2,attributes) => {
|
|
const dieObj = {
|
|
roll1: die1 && die1 !== '-' ? `[[${die1}]]` : '',
|
|
roll2: die2 && die2 !== '-' ? `[[${die2}]]` : ''
|
|
};
|
|
if(!dieObj.roll1 && !dieObj.roll2){
|
|
dieObj.roll1 = '^{no dice to roll}';
|
|
dieObj.pushable = 0;
|
|
return dieObj;
|
|
}
|
|
if(skillName.includes('armor')){
|
|
return dieObj;
|
|
}
|
|
if(attributes.skill_toggle === 'normal'){
|
|
return dieObj;
|
|
}
|
|
const lowKey = lowerDie(dieObj);
|
|
if(attributes.skill_toggle === 'advantage'){
|
|
dieObj.roll3 = dieObj[lowKey];
|
|
}else{
|
|
delete dieObj[lowKey];
|
|
}
|
|
return dieObj;
|
|
}
|
|
|
|
/**
|
|
* Gets the dice for a given skill
|
|
* @param {string} skillName - The skill name to get dice for
|
|
* @param {AttributesProxy} attributes - The character attributes
|
|
* @returns {Object} - Object describing the dice to be used
|
|
*/
|
|
const getSkillDice = (skillName,attributes) => {
|
|
const attrDice = attributes[`${skillName}_dice`];
|
|
const baseAbi = skillName.includes('armor') ?
|
|
skillName :
|
|
abilitySkill[skillName.replace(/_|-/g,' ')];
|
|
const abiDice = baseAbi ?
|
|
attributes[`${baseAbi}_dice`] :
|
|
null;
|
|
return applyRollType(skillName,attrDice,abiDice,attributes);
|
|
}
|
|
|
|
/**
|
|
* Rolls a basic attribute check
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const rollAttribute = function({trigger,attributes,sections,casc}){
|
|
debugger;
|
|
const rollName = trigger.name.replace(/-action/,'').replace(/-/g,' ');
|
|
|
|
const rollObj = {
|
|
label:rollName === 'armor' ?
|
|
attributes.armor_name :
|
|
`^{${rollName}}`,
|
|
pushable: rollName === 'armor' ?
|
|
0 :
|
|
(
|
|
attributes.option_replicant === 'replicant' ?
|
|
2 :
|
|
1
|
|
),
|
|
...getSkillDice(rollName.replace(/\s+/g,'_'),attributes),
|
|
};
|
|
executeRoll(rollObj,attributes);
|
|
};
|
|
k.registerFuncs({rollAttribute});
|
|
|
|
/**
|
|
*
|
|
* @param {object} rollObj - Object describing the fields to send in the roll. Keys are field names; values are field contents
|
|
* @returns {string} - The assembled roll
|
|
*/
|
|
const assembleRoll = (rollObj,rollStart = '@{template_start} {{successes=[[0[computed value]]]}}') => {
|
|
return Object.entries(rollObj).reduce((str,[field,content]) => {
|
|
return str += ` {{${field}=${content}}}`
|
|
},rollStart);
|
|
}
|
|
|
|
/**
|
|
* Actually executes the rolls assembled by the specific rolling functions.
|
|
* @param {object} rollObj - The object describing the message to send to chat
|
|
*/
|
|
const executeRoll = async (rollObj,attributes,templateStart) => {
|
|
if(rollObj.pushable){
|
|
rollObj.push = `[[0[computed value]]]`;
|
|
rollObj.push_through = `[[0[computed value]]]`;
|
|
rollObj.push_label = '^{push}';
|
|
}
|
|
const rollString = assembleRoll(rollObj,templateStart);
|
|
const roll = await startRoll(rollString);
|
|
const computedObj = {};
|
|
computedObj.push = 0;
|
|
computedObj.successes = [1,2,3].reduce((total,n) => {
|
|
const result = roll.results[`roll${n}`] && roll.results[`roll${n}`].result;
|
|
if(result >= 6){
|
|
total+=1;
|
|
}
|
|
if(result >= 10){
|
|
total += 1;
|
|
}
|
|
if(result && result !== 1){
|
|
computedObj.push++;
|
|
}
|
|
return total;
|
|
},0);
|
|
if(rollObj.push){
|
|
const toEscape = {
|
|
pushable:rollObj.pushable - 1,
|
|
result1:roll.results.roll1 && roll.results.roll1.result,
|
|
result2:roll.results.roll2 && roll.results.roll2.result,
|
|
result3:roll.results.roll3 && roll.results.roll3.result,
|
|
label:rollObj.label
|
|
};
|
|
Object.entries(rollObj).forEach(([key,val]) => {
|
|
if(!toEscape.hasOwnProperty(key)){
|
|
toEscape[key] = val;
|
|
}
|
|
});
|
|
computedObj.push_through = RE.escape(toEscape);
|
|
}
|
|
|
|
finishRoll(roll.rollId,computedObj);
|
|
debugger;
|
|
};
|
|
|
|
/**
|
|
* Pushes a roll using data passedthrough from the previous roll.
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const pushRoll = async function({trigger,attributes,sections,casc}){
|
|
if(!trigger.originalRollId) return;
|
|
try{
|
|
const rollData = RE.unescape(trigger.originalRollId);
|
|
debugger;
|
|
rollData.roll1 = rollData.original1 || rollData.roll1;
|
|
rollData.roll2 = rollData.original2 || rollData.roll2;
|
|
rollData.roll3 = rollData.original3 || rollData.roll3;
|
|
/**
|
|
* The fields that should be propagated through to the pushed roll.
|
|
*/
|
|
|
|
const baseRoll = {
|
|
label:rollData.label,
|
|
pushable:rollData.pushable,
|
|
// propagate the original roll expressions to maintain them if additional pushes are allowed.
|
|
original1: rollData[`roll1`],
|
|
original2: rollData[`roll2`],
|
|
original3: rollData[`roll3`],
|
|
damage_label: rollData.damage_label,
|
|
critical_label: rollData.critical_label,
|
|
};
|
|
if(rollData.attack_id){
|
|
baseRoll.attack_id = rollData.attack_id;
|
|
}
|
|
const passThroughs = {};
|
|
const allRolls = [];
|
|
[1,2,3].forEach(n => {
|
|
if(rollData[`result${n}`]){
|
|
baseRoll[`roll${n}`] = `[[${rollData[`result${n}`]}]]`;
|
|
}
|
|
if(rollData[`result${n}`] === 1){
|
|
delete rollData[`result${n}`];
|
|
}
|
|
if(rollData[`result${n}`]){
|
|
allRolls.push(rollData[`roll${n}`]);
|
|
}
|
|
});
|
|
[1,2,3].forEach(n => {
|
|
[1,2,3].forEach(sn => {
|
|
if(n < sn) return;
|
|
if(rollData[`result${n}`] && rollData[`result${sn}`]){
|
|
const newObj = n === sn ?
|
|
{
|
|
...baseRoll,
|
|
[`roll${n}`]: rollData[`roll${n}`]
|
|
} :
|
|
{
|
|
...baseRoll,
|
|
[`roll${n}`]: rollData[`roll${n}`],
|
|
[`roll${sn}`]: rollData[`roll${sn}`]
|
|
};
|
|
rollData[`push${n}_${sn}`] = '[[0[computed value]]]';
|
|
passThroughs[`push${n}_${sn}`] = RE.escape(newObj);
|
|
}
|
|
});
|
|
});
|
|
if(allRolls.length === 3){
|
|
const allObj = allRolls.reduce((memo,content,index) => {
|
|
memo[`roll${index + 1}`] = content;
|
|
return memo;
|
|
},{...baseRoll});
|
|
rollData[`push_all`] = '[[0[computed value]]]';
|
|
passThroughs['push_all'] = RE.escape(allObj);
|
|
}
|
|
[1,2,3].forEach(n => {
|
|
if(rollData[`roll${n}`]){
|
|
rollData[`roll${n}`] = rollData[`roll${n}`].replace(/\W/g,'');
|
|
}
|
|
if(rollData[`result${n}`] === 1){
|
|
delete rollData[`result${n}`];
|
|
}
|
|
});
|
|
Object.keys(rollData).forEach(key =>{
|
|
if(rollData[key] === undefined || rollData[key] === null){
|
|
delete rollData[key];
|
|
}
|
|
});
|
|
const rollString = assembleRoll(rollData,`/w "@{character_name}" @{push_start}`);
|
|
const roll = await startRoll(rollString);
|
|
finishRoll(roll.rollId,passThroughs);
|
|
}catch(err){
|
|
// Do nothing if the parsing fails
|
|
}
|
|
};
|
|
k.registerFuncs({pushRoll});
|
|
|
|
/**
|
|
* Executes the chosen push option
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const executePush = function({trigger,attributes,sections,casc}){
|
|
const rollData = RE.unescape(trigger.originalRollId);
|
|
if(rollData.pushable || attributes.option_replicant === 'human'){
|
|
rollData.label = getTranslationByKey('pushed {{label}}').replace(/\{\{0\}\}/,rollData.label);
|
|
}
|
|
executeRoll(rollData,attributes);
|
|
};
|
|
k.registerFuncs({executePush});
|
|
|
|
// Rollescape function from Oosh for Passthrough trick
|
|
// https://app.roll20.net/forum/post/10346883/adventures-with-startroll
|
|
// Helper - the data passed through in an action button needs these characters escaped
|
|
// I've used a mongrel escape sequence to avoid triggering anything internal in Roll20
|
|
// This may not be a complete list of characters that need escaping! If you have heavily
|
|
// punctuated text, it might break the passthrough!
|
|
const RE = {
|
|
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]);
|
|
return JSON.parse(str);
|
|
}
|
|
}/**
|
|
* Rolls attack rolls
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const rollAttack = function({trigger,attributes,sections,casc}){
|
|
const [section,rowID] = k.parseTriggerName(trigger.name);
|
|
const skill = section.endsWith('hand') ?
|
|
'hand_to_hand' :
|
|
'firearms';
|
|
const row = `${section}_${rowID}`;
|
|
const rollObj = {
|
|
...getSkillDice(skill,attributes),
|
|
label:attributes[`${row}_name`],
|
|
attack_id:`${row}`,
|
|
critical_label:'^{critical}',
|
|
damage_label:'^{damage}',
|
|
pushable: attributes.option_replicant === 'replicant' ?
|
|
2 :
|
|
1
|
|
};
|
|
executeRoll(rollObj,attributes);
|
|
};
|
|
k.registerFuncs({rollAttack});
|
|
|
|
/**
|
|
* Rolls base damage for an attack
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const rollDamage = function({trigger,attributes,sections,casc}){
|
|
const [section,rowID] = k.parseTriggerName(trigger.name);
|
|
const row = `${section}_${rowID}`;
|
|
const rollObj = {
|
|
label: getTranslationByKey('{{weapon name}} - damage').replace(/\{\{0\}\}/,attributes[`${row}_name`]),
|
|
damage:`[[${attributes[`${row}_damage`]}]]`,
|
|
};
|
|
executeRoll(rollObj,attributes,'@{template_start}');
|
|
};
|
|
k.registerFuncs({rollDamage});
|
|
|
|
/**
|
|
* Rolls the damage and effect for a critical hit
|
|
* @param {object} trigger - The trigger that caused the function to be called
|
|
* @param {object} attributes - The attribute values of the character
|
|
* @param {object[]} sections - All the repeating section IDs
|
|
* @param {object} casc - Expanded cascade object
|
|
*/
|
|
const rollCrit = async function({trigger,attributes,sections,casc}){
|
|
const [section,rowID] = k.parseTriggerName(trigger.name);
|
|
const row = `${section}_${rowID}`;
|
|
const critRolls = await startRoll(`!{{roll=[[?{How many extra success}d${attributes[`${row}_crit_die`]}]]}}`);
|
|
finishRoll(critRolls.rollId);
|
|
const critResults = critRolls.results.roll.dice;
|
|
|
|
const rollObj = {
|
|
label: getTranslationByKey('{{weapon name}} - critical').replace(/\{\{0\}\}/,attributes[`${row}_name`]),
|
|
damage:`[[${attributes[`${row}_damage`]} + ${critResults.length}[Crit Damage]]]`,
|
|
effect: critResults.map(n => {
|
|
return `^{${criticalEffects[attributes[`${row}_type`]][n - 1]}}`;
|
|
}).join('\n')
|
|
};
|
|
executeRoll(rollObj,attributes,'@{template_start}');
|
|
};
|
|
k.registerFuncs({rollCrit});/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
//Initiate a compendium lookup
|
|
const specialCategories = {
|
|
Weapon:'Weapons'
|
|
};
|
|
//These categories are the part after the data- in a bestiary entry. E.g. a monster has a data-Armors JSON, but the actual compendium category is "Armor". These are converted to the proper compendium category via the categoryLookup Proxy
|
|
const itemCategories = ['Armor','Weapons','Weapon','Vehicles','Gear','Specialties'];
|
|
|
|
//Start of the actual compendium lookup functions
|
|
//Function to initiate the lookup. Parses the statblock to figure out what needs to be looked up in the compendium, and what attribute should be filtered for based on the category.
|
|
//ARGUMENTS
|
|
//statblock (object): base object dropped from the compendium
|
|
//callback (function): A function that will use the parsed compendium lookup data to finish the drag and drop of the item
|
|
//The callback function receives the parsed statblock as an argument
|
|
const compendiumLookup = function({queue, callback, origBlock, attributes, sections, casc, pass = 0}) {
|
|
pass++;
|
|
origBlock = origBlock || queue[0];
|
|
queue = queue || [origBlock];
|
|
let searchArray = [];
|
|
queue.forEach((statblock)=>{
|
|
searchArray = itemCategories.reduce((m, type) => {//Construct an array of items to search for.
|
|
let searchType = specialCategories[type] || type;
|
|
if (statblock[`data-${type}`]){
|
|
let searchNames = statblock[`data-${type}`].map((obj) => obj.name || obj.display_name || obj['data-cs-name'] || '')
|
|
.join('|')
|
|
.replace(/\|{2,}/g,'')
|
|
.replace(/\|$/,'');
|
|
if(searchNames.length > 0){
|
|
let searchString = `Category:${searchType} data-cs-name:${searchNames}`;
|
|
m.push(searchString);
|
|
}
|
|
}
|
|
return m;
|
|
}, searchArray);
|
|
k.debug({searchArray});
|
|
if(searchArray.length){
|
|
lookupBurndown(statblock, searchArray, callback, origBlock, attributes, sections, casc,pass);
|
|
}else{
|
|
callback(origBlock);
|
|
}
|
|
});
|
|
};
|
|
|
|
//Actually queries the compendium to get details on the items in each category using the searchArray assembled in compendiumLookup
|
|
//ARGUMENTS
|
|
//statblock (object): the original object dropped from the compendium
|
|
//callback (function): A function that will use the parsed compendium lookup data to finish the drag and drop of the item
|
|
//The callback function receives the parsed statblock as an argument
|
|
const lookupBurndown = function(statblock, searchArray, callback, origBlock, attributes, sections, casc,pass) {
|
|
getCompendiumQuery(searchArray, (dataArr) => {
|
|
k.debug({dataArr});
|
|
const expansionIDs = dataArr
|
|
.reduce( (arr,obj)=>{
|
|
arr.push(k.value(obj.data.Expansion));
|
|
return arr;
|
|
},[])
|
|
.sort((a,b) => b - a);
|
|
itemCategories.forEach((category) => {
|
|
if (statblock[`data-${category}`]) {
|
|
origBlock[`data-${category}`] = statblock[`data-${category}`].map((obj) => {
|
|
const fullItem = getItemDetails(obj, dataArr, expansionIDs);
|
|
fullItem['data-cs-name'] = fullItem['data-cs-name'] || fullItem.name;
|
|
return fullItem;
|
|
});
|
|
}
|
|
});
|
|
const queue = additionalLookups(statblock,origBlock);
|
|
if(queue.length /*&& pass < 2*/){
|
|
compendiumLookup({queue,callback,origBlock, attributes, sections, casc,pass});
|
|
}else{
|
|
callback(origBlock);
|
|
}
|
|
});
|
|
};
|
|
|
|
const additionalLookups = function(statblock,origBlock){
|
|
return Object.entries(statblock).reduce((memo,[prop,arr])=>{
|
|
if(prop.startsWith('data-') && Array.isArray(arr)){
|
|
let nestedData = arr.reduce((m,obj)=>{
|
|
Object.entries(obj).forEach(([nProp,nVal])=>{
|
|
/*commented code shouldn't be needed
|
|
if(
|
|
nProp.startsWith('data-') &&
|
|
typeof nVal === 'string' &&
|
|
nVal.startsWith('[{')
|
|
){
|
|
try{
|
|
let jArr = JSON.parse(nVal);
|
|
if(Array.isArray(jArr)){
|
|
jArr = jArr.map((o)=>{
|
|
o.datasource = obj['cs-name'] || obj.Category;
|
|
if(!o.datasource){
|
|
delete o.datasource;
|
|
}
|
|
return o;
|
|
});
|
|
origBlock[nProp] = origBlock[nProp] ?
|
|
[...origBlock[nProp],...jArr] :
|
|
[...jArr];
|
|
m.data[nProp] = jArr;
|
|
m.nested = true;
|
|
}
|
|
}catch(err){
|
|
//if the parse failed, do nothing
|
|
}
|
|
}else */
|
|
if(nProp === 'data-features'){
|
|
try{
|
|
if(typeof nVal === 'string'){
|
|
k.debug('string data-feature detected');
|
|
nVal = JSON.parse(nVal);
|
|
}
|
|
nVal.forEach((feature)=>{
|
|
if(!origBlock[`data-${feature.category}`] || origBlock[`data-${feature.category}`].every((o)=>o['data-cs-name'].toLowerCase() !== feature.name.toLowerCase())){
|
|
m.nested = true;
|
|
m.data[`data-${feature.category}`] = m.data[`data-${feature.category}`] || [];
|
|
m.data[`data-${feature.category}`].push({['data-cs-name']:feature.name});
|
|
}
|
|
});
|
|
}catch(err){
|
|
k.debug({[`Invalid data-feature on ${obj['data-cs-name']}`]:err,'data-feature':nVal},true);
|
|
}
|
|
}
|
|
});
|
|
return m;
|
|
},{data:{},nested:false});
|
|
if(nestedData && nestedData.nested){
|
|
memo.push(nestedData.data);
|
|
}
|
|
}
|
|
return memo;
|
|
},[]);
|
|
};
|
|
|
|
//Finds the compendium item that most precisely matches the needs of the compendium item based on expansion ID and name property.
|
|
//If nothing matches based on expansion ID, simply returns the first item with appropriate name property.
|
|
//ARGUMENTS
|
|
//itemObj (object): the data on the Item to return that is included in the base statblock
|
|
//dataArr (array): The compendium items that were returned from getCompendiumQuery
|
|
const getItemDetails = function(itemObj, dataArr, expansionIDs) {
|
|
let objName = itemObj['data-cs-name'] || itemObj.name;
|
|
let compendiumObj;
|
|
expansionIDs.find((id) => {
|
|
compendiumObj = dataArr.find((dataObj) => k.value(dataObj.data.Expansion) === id &&
|
|
dataObj.data['data-cs-name'] === objName);
|
|
if (compendiumObj) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
if (!compendiumObj) { //If we didn't find an object matching the expansion pathways and the object name, just find the first item with a matching name
|
|
compendiumObj = dataArr.find((dataObj) =>
|
|
dataObj.data['data-cs-name'] === objName);
|
|
if (!compendiumObj) { //If there still isn't a matching compendium object, return the original Item
|
|
return itemObj;
|
|
}
|
|
}
|
|
Object.keys(compendiumObj.data).forEach((key) => {
|
|
itemObj[key] = itemObj[key] || compendiumObj.data[key];
|
|
});
|
|
return itemObj;
|
|
};/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033*/
|
|
const maxAttr = ['health','resolve','hull'];
|
|
|
|
const convertAttributeNames = (key) => {
|
|
key = key.replace(/^(?:data-)?cs(?:list)?-/,'');
|
|
const conversionChart = {
|
|
armor_rating:'armor'
|
|
};
|
|
return conversionChart[key] || key;
|
|
}
|
|
|
|
const handleCompendiumDrop = function({attributes,sections,casc}){
|
|
let data = parseDropData(attributes.drop_data);
|
|
if(!data) return;
|
|
k.debug({data});
|
|
clearDropAttributes();
|
|
compendiumLookup({
|
|
origBlock:data,
|
|
attributes,sections,casc,
|
|
callback: async (lookupObj)=>{
|
|
if(data.Category === 'NPCs'){
|
|
debugger;
|
|
clearSheet(attributes,sections,casc);
|
|
attributes.queue.push('character_name');
|
|
attributes.character_name = data['data-cs-name'];
|
|
delete data['data-cs-name'];
|
|
}
|
|
|
|
attributes['tabs_tab'] =
|
|
data.Category === 'Vehicles' ?
|
|
'nav-tabs-tabs--vehicle':
|
|
'nav-tabs-tabs--character';
|
|
k.debug({parsedData:lookupObj});
|
|
const sectionsToQuery = [];
|
|
processDropData({data:lookupObj,attributes,sections,casc,sectionsToQuery});
|
|
const protoQueue = Object.keys(attributes.updates)
|
|
.flatMap( key => {
|
|
const cascRef = key.replace(/(repeating_[^_]+)_[^_]+_(.+)/,'$1_\$X_$2');
|
|
casc[`attr_${cascRef}`] = casc[`attr_${cascRef}`] || k.cascades[`attr_${cascRef}`];
|
|
return k.cascades[`attr_${cascRef}`] ?
|
|
casc[`attr_${cascRef}`].affects
|
|
: [];
|
|
});
|
|
k.setActionCalls({attributes,sections});
|
|
attributes.queue.push(...[...new Set(protoQueue)]);
|
|
k.debug('Drop processing complete');
|
|
attributes.set({attributes,sections,casc});
|
|
}
|
|
});
|
|
};
|
|
k.registerFuncs({handleCompendiumDrop});
|
|
|
|
const parseDropData = function(drop_data){
|
|
k.debug('parsing drop data');
|
|
k.debug({drop_data:`"${drop_data}"`});
|
|
try{
|
|
let data = JSON.parse(drop_data);
|
|
data = Object.entries(data)
|
|
.reduce((memo,[key,val])=>{
|
|
if(val){
|
|
if(typeof val === 'string' && val.startsWith('[{')){
|
|
val = JSON.parse(val);
|
|
}
|
|
memo[key] = val;
|
|
}
|
|
return memo;
|
|
},{});
|
|
if(data.Category !== 'NPCs' && data.Category !== 'Vehicles' && data.Category !== 'Armor'){
|
|
let category = data.Category.replace(/\s.+/,'');
|
|
data = {[`data-${category}`]:[data]};
|
|
if(data[`data-${category}`]['data-features']){
|
|
data[category]['data-features'].forEach((feature)=>{
|
|
let cat = k.capitalize(feature.category);
|
|
data[`data-${cat}`] = data[`data-${cat}`] || [];
|
|
data[`data-${cat}`].push({name:feature.name});
|
|
});
|
|
}
|
|
}
|
|
return data;
|
|
}catch(err){
|
|
k.debug({'Invalid Drop Data! Drop aborted. Error:':err,drop_data},true);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const clearSheet = function(attributes,sections,casc){
|
|
Object.entries(sections).forEach(([section,idArray])=>{
|
|
idArray.forEach((id)=>{
|
|
k.removeRepeatingRow(`${section}_${id}`,attributes,sections);
|
|
});
|
|
});
|
|
Object.keys(attributes.attributes).forEach((attr)=>{
|
|
if(casc[`attr_${attr}`] && attr !== 'character_name'){
|
|
attributes.updates[attr] = casc[`attr_${attr}`].hasOwnProperty('defaultValue') && casc[`attr_${attr}`].defaultValue !== 0 ?
|
|
casc[`attr_${attr}`].defaultValue :
|
|
'';
|
|
}
|
|
});
|
|
};
|
|
|
|
const clearDropAttributes = function(){
|
|
k.setAttrs({drop_name:'',drop_data:''});
|
|
};
|
|
|
|
const processDropData = function({data, attributes, sections, casc,sectionsToQuery,section=''}){
|
|
const repeatProcessors = {
|
|
Specialties:dropBasic,
|
|
Gear:dropBasic,
|
|
Weapons:dropWeapon,
|
|
Weapon:dropWeapon
|
|
};
|
|
section = section ? `${section}_` : '';
|
|
if(data.Category === 'NPCs'){
|
|
const tokenSize = 70;
|
|
const tokenObj = {
|
|
width:tokenSize,
|
|
height:tokenSize,
|
|
};
|
|
setDefaultToken(tokenObj);
|
|
}
|
|
const worker = (queue) => {
|
|
let [key,val] = queue.shift();
|
|
try{
|
|
const setKey = convertAttributeNames(key);
|
|
if(typeof val === 'string' && val.startsWith('[{')){
|
|
val = JSON.parse(val);
|
|
}
|
|
if(!Array.isArray(val) && (key.startsWith('cs-') || key.startsWith('data-cs-'))){
|
|
attributes[`${section}${setKey}`] = val;
|
|
if(maxAttr.includes(setKey)){
|
|
attributes[`${section}${setKey}_max`] = val;
|
|
}
|
|
}else if(Array.isArray(val) && key.startsWith('data-')){
|
|
let lookupKey = key.replace(/data-(?:cs(?:list)?-)?/,'');
|
|
k.debug({lookupKey});
|
|
if(repeatProcessors[lookupKey]){
|
|
repeatProcessors[lookupKey](lookupKey,val,attributes,sections,casc,sectionsToQuery);
|
|
}else if(key.startsWith('data-cslist-')){
|
|
attributes[`${section}${setKey}`] = val.map((o)=>o.name).join(', ');
|
|
}else if(key === 'data-content'){
|
|
attributes[`${section}description`] = val.reduce((textArr,content)=>{
|
|
if(content.heading){
|
|
textArr.push(content.heading);
|
|
}
|
|
if(content.content){
|
|
let text = /list/.test(content.type) ? `• ${content.content}` : content.content;
|
|
textArr.push(text);
|
|
}
|
|
return textArr;
|
|
},[]).join('\n')
|
|
.replace(/\n{2,}/,'\n')
|
|
.replace(/\n+$/,'');
|
|
}else if(Array.isArray(val)){
|
|
val.forEach(v=>{
|
|
queue.push(...Object.entries(v));
|
|
})
|
|
}
|
|
}
|
|
}catch(err){
|
|
k.debug({[`Invalid JSON entered for ${key} on ${data.display_name}`]:err,JSON:val});
|
|
}
|
|
if(queue.length){
|
|
return worker(queue);
|
|
}else{
|
|
return true;
|
|
}
|
|
};
|
|
|
|
return worker(Object.entries(data));
|
|
};
|
|
|
|
|
|
|
|
const sectionLookup = {
|
|
Gear:'repeating_gear',
|
|
Specialties:'repeating_specialties',
|
|
'Weapon-range':'repeating_weapons-firearm',
|
|
'Weapon-hand':'repeating_weapons-hand'
|
|
};
|
|
|
|
const dropBasic = function(lookupKey,arr,attributes,sections,casc,sectionsToQuery){
|
|
if(!Array.isArray(arr)){
|
|
k.debug({'Invalid section details passed. Exiting. Object passed was:':arr},true);
|
|
return;
|
|
}
|
|
return repeatWorker(lookupKey,arr,sections,casc,(itemObj,section)=>{
|
|
processDropData({data:itemObj,attributes,sections,casc,sectionsToQuery,section});
|
|
});
|
|
};
|
|
|
|
const dropWeapon = function(lookupKey,arr,attributes,sections,casc,sectionsToQuery){
|
|
if(!Array.isArray(arr)){
|
|
k.debug({'Invalid section details passed. Exiting. Object passed was:':arr},true);
|
|
return;
|
|
}
|
|
|
|
const handArr = arr.filter((o) => !o['data-cs-max_range'] && !o['data-cs-min_range']);
|
|
const rangeArr = arr.filter((o) => o['data-cs-max_range'] || o['data-cs-min_range']);
|
|
if(handArr.length){
|
|
repeatWorker('Weapon-hand',handArr,sections,casc,(itemObj,section)=>{
|
|
processDropData({data:itemObj,attributes,sections,casc,sectionsToQuery,section});
|
|
});
|
|
}
|
|
if(rangeArr.length){
|
|
repeatWorker('Weapon-range',rangeArr,sections,casc,(itemObj,section)=>{
|
|
processDropData({data:itemObj,attributes,sections,casc,sectionsToQuery,section});
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Drops a Hack or action repeating item
|
|
* @param {string} lookupKey - The lookupKey that triggered the drop
|
|
* @param {object} arr The drop data for this item
|
|
* @param {object} attributes The attributes object from the K-Scaffold
|
|
* @param {object} sections The sections object from the K-Scaffold
|
|
* @param {object} casc The expanded cascade object from the K-Scaffold
|
|
*/
|
|
const dropHack = function(lookupKey,arr,attributes,sections,casc,sectionsToQuery){
|
|
if(!Array.isArray(arr)){
|
|
k.debug({'Invalid section details passed. Exiting. Object passed was:':arr},true);
|
|
return;
|
|
}
|
|
return repeatWorker(lookupKey,arr,sections,casc,(itemObj,section)=>{
|
|
k.debug({section,itemObj});
|
|
if(itemObj['data-cs-attack_ability']){
|
|
itemObj['data-cs-attack_enable_item'] = 1;
|
|
}
|
|
if(itemObj['data-cs-save_ability'] || itemObj['data-cs-save_type']){
|
|
itemObj['data-cs-save_enable_item'] = `${lookupKey === 'Actions' ? `{{save_type=^{@{save_type}}}} ` : ''}@{save_macro}`;
|
|
}
|
|
[1,2].forEach(n => {
|
|
if(itemObj[`data-cs-damage_${n}`]){
|
|
itemObj[`data-cs-damage_${n}_enable_item`] = `{{damage_type_${n}=@{damage_type_${n}}}} @{damage_${n}_macro}`;
|
|
}
|
|
});
|
|
if(itemObj.linked_section){
|
|
attributes[`${itemObj.linked_section}_linked_row`] = section;
|
|
}
|
|
processDropData({data:itemObj,attributes,sections,casc,sectionsToQuery,section});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Adds the cascade objects for the indicated row to the casc object.
|
|
* @param {string} section - The row information (repeating section, and rowID)
|
|
* @param {object} casc - The cascade object for this particular character
|
|
* @param {number|string} [previousValue] - What to set the previous value to
|
|
*/
|
|
const addRowToCasc = function(section,casc,previousValue){
|
|
k.debug(`Adding ${section} to cascades`);
|
|
const match = section.match(/(.+?_.+?)_(.+)/);
|
|
match.shift();
|
|
const [repSection,rowID] = match;
|
|
k.debug({repSection,rowID});
|
|
Object
|
|
.entries(k.cascades)
|
|
.forEach(([key,obj]) => {
|
|
if(key.startsWith(`attr_${repSection}`)){
|
|
const newObj = {...obj};
|
|
newObj.name = newObj.name.replace(/\$X/,rowID)
|
|
const newKey = key.replace(/\$X/,rowID);
|
|
newObj.previousValue = previousValue === undefined ?
|
|
newObj.defaultValue :
|
|
previousValue;
|
|
newObj.affects = newObj.affects.map(a => a.replace(/\$X/,rowID));
|
|
casc[newKey] = newObj;
|
|
k.debug({[`casc[${newKey}]`]:casc[newKey]});
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Does the basic prepwork for repeating section drops by creating the rowID and grabbing the item to work on. Passes the selected itemObj and the created section details to the callback function
|
|
* @param {string} lookupKey
|
|
* @param {array} arr
|
|
* @param {object} sections
|
|
* @param {function} callback
|
|
*/
|
|
const repeatWorker = function(lookupKey,arr,sections,casc,callback){
|
|
const type = sectionLookup[lookupKey];
|
|
const worker = (queue)=>{
|
|
const itemObj = queue.shift();
|
|
const section = k.generateRowID(type,sections);
|
|
addRowToCasc(section,casc);
|
|
callback(itemObj,section);
|
|
if(queue.length){
|
|
return worker(queue);
|
|
}else{
|
|
return true;
|
|
}
|
|
};
|
|
return worker([...arr]);
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
|