[SWADE] Savage Worlds - Graph Paper Sheet (#7510)

* uploaded images

* add sheet

* updated readme

* updated readme

* updated readme

* Update readme.md

* upload gifs

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* fixed unskilled

* Update readme.md

* updated sheet.json

* resized gifs to 400px width

* pre-release fixes

* pre-release fixes

* removed obsolete image

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* renamed to SavageWorldsGraphPaper

* renamed folder

* Update readme.md

* fixed image paths

* skill lock does no longer lock mod/wd inputs

* removed charset declaration

* added wild die to roll templates

* changed secondary container height for list items

* removed style variable

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* Update readme.md

* extended Allies block and multiple fixes

* removed default values for renaming options

* added translation.json and some basic strings

* Update readme.md

* added more translation strings

* added more translation hooks

* added renaming option for integrity drawbacks

* fixed wrong description

* even more translations

* remaining translation hooks, new mixin for vehicle weapons, some css reductions

* Update readme.md

* Update readme.md

* updated translations

* added  

* Update readme.md

* Update readme.md

typo

* Update readme.md

spelling error

* Update readme.md

added link

* removed custom add item labels due to being not translatable

* removed @charset UTF-8;

* Update readme.md

* yet even more translation stuff

* dice parser includes renamed abbreviations

* removed placeholder translations from hexfields

* circumvented translation error for height

* removed untranslatable label from empty list

* removed die label from untrained bubble

* circumvented translation error for height

* updated settings strings

* removed transition from damage roll toggle

* updated readme

* updated translations

* Update readme.md

* Update readme.md

* ordered translations, removed UTF-8 declaration from CSS

* added string interpolation

* added preview image

* Update readme.md

* added extra sheet preview image

* optimized image

* Update readme.md

* fixed meta field and extra preview image

* string interpolation, missing translations, hyphens to underscore (fml)

* fixed introduced errors from refactoring

* translation fixes

* resolved conflict due to underscore naming scheme

* removed console log commands

* resolved conflict due to underscore naming scheme #2

* fixed missing skill track labels

* updated helper text

* Update readme.md

* removed obsolete leading whitespaces

* prepared scss file for translation overrides

* Update readme.md

* some JS clean-up

* added style option to disable skill die numbers

* added settings.gif

* Update readme.md

* added roll preview

* improved roll preview

* Update readme.md

* Update readme.md

* updated list item label

* reduced roll preview size

* reduced extra sheet preview size

* dice parser now captures X+ and X- references as well

* made lightest text color slightly darker

* switched global transitions to local ones to boost performance

* added missing transition

* updated readme.md

* updated readme.md and sheet.json

* added missing color transition

* Update readme.md

* fixed dice parser bug

* added missing opacity transition

* Update readme.md

* added comment to BS script function

* fixed positioning of hexfield

* prepared rolltemplates for higher rate of fires

* made max rof a global variable

* increases rate of fire range to 8

* updated readme.md

* added ammunition block

* added new help section for ammunition

* Update readme.md

* Update readme.md

* updated sheet.json and translations

* Update readme.md

* Update readme.md

* fixed button color

* added missing/changed translations, improved roll templates, extended skill settings

* streamlined roll template hooks

* added reroll button to roll templates

* cleaned up directory, updated sheet.json and readme.md

* optimized preview image

* Update readme.md

* Update readme.md

* Update readme.md

* added missing reroll, prepared skill rolls for referencing

* removed test button

* css fixes, added roll button and options to repeating lists

* fixed critical bug in dice parser

* added set max capacity to ammunition block

* removed redundant css

* updated sheet.json and readme.md

* Update readme.md

* updated sheet.json

* updated/added preview gifs

* Update readme.md

* Update readme.md

* Update readme.md

* removed obsolete/redundant css (575 lines), improved performance

* fixed button font-size

* removed obsolete characters from css

* removed more obsolete characters from css

* further css reductions

* pluralized label

* add line ending

* fixed attribute font-size

* added dice parser to helper roll buttons

* improved rate of fire handling

* added rof toggle to repeating lists

* Update readme.md

* updated sheet.json

* fixed sheet default options

* refactored running

* Update readme.md

* updated help text for dice roller

* fixed list rolls

* added override for wild die in lists

* added multiplier to augmentations

* added damage button to weapon rolls

* reduced html/css/translations

* html reduction

* updated readme.md

* updated readme.md

* added damage roll button to power list rolls

* added help texts for raise damage, list item options, and extra sheet

* updated readme.md

* added setting toggles and injection options

* added help entry for roll injections

* updated readme.md and sheet.json

* css fix for roll templates

* another css fix

* css fixes

* updated readme

* cleanup

* icon position for lower resolutions

* icon css reduction

* added settings to hide silhouette/skill markers, improved css performance

* rearranged some settings

* added setting to hide augmentation markers

* updated sheet.json

* updated readme

* alphabetically ordered settings

* fixed critical bug in rolls, added toggles for global queries

* updated readme

* bugfix

* small style adjusments

* small style adjusments

* new weight icon

* small icon adjustment

* updated sheet.json and preview image

* fixed renaming feature

* reduced file size of preview image

* fixed some Firefox css issues

* added three configurable custom buttons

* updated readme and sheet.json

* rof toggle is now applied to list damage rolls

* fixed font-size for damage roll templates

* small skill button width adjustment

* text overflow fix for firefox

* fixed missing silhouette switch

* added gm roll toggle

* fixed minor attribute inconsistency between wildcard and extras

* shortened sheetworkers, added toggle for damage dice exploding

* fixed exploding damage dice toggle default

* fixed exploding damage dice toggle default for powers

* refactored all skill rolls

* updated readme and sheet.json

* caught error with empty query modifier

* extended rol template

* extended settings

* added abilities block

* updated readme and sheet.json

* fixed unskilled rolls
This commit is contained in:
Sebastian
2020-10-13 17:42:07 -04:00
committed by GitHub
parent dabb082acb
commit 83f1ca7ebb
137 changed files with 34477 additions and 0 deletions
@@ -0,0 +1,822 @@
/* #############################################################################
VARIABLES
############################################################################# */
const dice = ['', 'd4', 'd6', 'd8', 'd10', 'd12'];
const renamedAbbr = ['rename_agi', 'rename_sma', 'rename_spi', 'rename_str', 'rename_vig'];
var parseCodes = { agi: '@{agility}!', sma: '@{smarts}!', spi: '@{spirit}!',
str: '@{strength}!', vig: '@{vigor}!' };
const parseDieCodes = { d4: 'd4!', d6: 'd6!', d8: 'd8!', d10: 'd10!', d12: 'd12!' };
/* #############################################################################
TRANSLATION SETUP
############################################################################# */
on("sheet:opened", function(e){
// Translations for query strings
setAttrs({
'query_rate_of_fire': getTranslationByKey('rate-of-fire'),
'query_modifier': getTranslationByKey('modifier'),
'query_wild_die': getTranslationByKey('wild-die'),
'query_roll_result': getTranslationByKey('roll-result'),
'query_target_number': getTranslationByKey('target-number'),
'query_damage_modifier': getTranslationByKey('damage-modifier'),
'query_bonus_wild_die': getTranslationByKey('bonus-wild-die'),
'none': getTranslationByKey('none')
});
// Include renamed Trait abbreviations
getAttrs(renamedAbbr, (values) => {
parseCodes = { agi: '@{agility}!', sma: '@{smarts}!', spi: '@{spirit}!',
str: '@{strength}!', vig: '@{vigor}!' };
_.each(renamedAbbr, (a) => {
// Skip if not defined
if (_.isUndefined(values[a])) return;
// Remove erroneous whitsapces
let v = values[a].replaceAll(' ', '').toLowerCase();
// Skip if empty string
if (v.length < 1) return;
parseCodes[v] = parseCodes[a.replace('rename_', '')];
});
});
});
/* #############################################################################
CUSTOM SHEET FUNCTIONS
############################################################################# */
var TETRA = TETRA || ( function() {
'use strict';
var version = '1.0',
// Parse and evaluate float number
toFloat = function(number, old = 0.0) {
let n = parseFloat(number);
n = _.isUndefined(n) ? old : n;
return isNaN(n) ? old : n;
},
// Parse and evaluate integer number
toInt = function(number, old = 0) {
let n = parseInt(number);
n = _.isUndefined(n) ? old : n;
return isNaN(n) ? old : n;
},
parseDiceCode = function(code) {
if (_.isUndefined(code)) return '';
code = code.toLowerCase();
// Replace abbreviations with attribute references (naive)
_.each(_.keys(parseCodes), (k) => {
code = code.replaceAll(`+${k}`, `+${parseCodes[k]}`);
code = code.replaceAll(`-${k}`, `-${parseCodes[k]}`);
code = code.replaceAll(`${k}+`, `${parseCodes[k]}+`);
code = code.replaceAll(`${k}-`, `${parseCodes[k]}-`);
});
// Add exclamation mark (!) to make dice explode (naive)
_.each(_.keys(parseDieCodes), (k) => {
code = code.replaceAll(`${k}`, `${parseDieCodes[k]}`);
});
return code;
},
// Update the roll queries for a trait
updateTraitRoll = function(trait) {
let traitAttributes = [trait, `${trait}_wd`, `${trait}_mod`];
// Get trait values: die, Wild Die, and modifier
getAttrs(traitAttributes, (values) => {
let die = values[trait],
wd = values[`${trait}_wd`],
ewd = '-10000', // Extra Wild Die
mod = values[`${trait}_mod`] || '0',
untrained = '0',
code = toInt(mod);
// Set up Wild Die
if (dice.includes(wd) && wd != '') {
ewd = wd;
} else if (wd == '') {
wd = '1d6';
} else {
wd = '-10000'; // Rolltemplate will not show results below -1000
}
// Account for untrained skill
if (die == 'd4-2') {
die = 'd4';
code -= 2;
untrained = '-2';
}
// Stringify code
if (code != 0) {
code = code > 0 ? `+${code}` : code.toString();
} else {
code = '';
}
// Build base code
code = die + code;
// Add custom Wild Die to code
code = ewd != '-10000' ? `${code} [${wd}]` : code;
setAttrs({ [`${trait}_roll`]: die,
[`${trait}_wd_roll`]: wd,
[`${trait}_extra_wd_roll`]: ewd,
[`${trait}_untrained_mod`]: untrained,
[`${trait}_code`]: code });
});
},
// Apply custom function to attributes in all rows of repeating section
doWithRepList = function(section, attributes, func = (v) => { return v }) {
let fullSection = section.includes('repeating_') ? section : `repeating_${section}`;
attributes = _.isArray(attributes) ? attributes : [attributes];
function process(idArray, attributes) {
let attributeArray = [];
// List of all requested attributes from all rows
_.each(idArray, (id) => {
_.each(attributes, (a) => {
attributeArray.push(`${fullSection}_${id}_${a}`);
});
});
getAttrs(attributeArray, (values) => {
func(values); // Pass all attribute values to custom function
});
}
getSectionIDs(fullSection, (idArray) => {
process(idArray, attributes);
});
};
return {
updateTraitRoll: updateTraitRoll,
doWithRepList: doWithRepList,
toFloat: toFloat,
toInt: toInt,
parseDiceCode: parseDiceCode
};
}());
/* #############################################################################
ATTRIBUTE DICE (AND SOAK)
############################################################################# */
const attributes = ['agility', 'smarts', 'spirit', 'strength', 'vigor'];
on('change:soak change:' + attributes.join(' change:'), (e) => {
// Derive target attribute and check if input value is a valid die
let value = _.isUndefined(e.newValue) ? 'd4' : e.newValue;
value = dice.includes(value) && value != '' ? value : 'd4';
// Set attributes to new value, silent to avoid triggering worker again
// but with a callback to update the attribute's roll query
setAttrs({ [e.sourceAttribute]: value },
{ silent: true },
TETRA.updateTraitRoll(e.sourceAttribute));
});
/* #############################################################################
SKILL DICE
############################################################################# */
const skills = ['academics', 'athletics', 'battle', 'boating', 'common_knowledge',
'driving', 'electronics', 'fighting', 'healing', 'intimidation',
'magic', 'notice', 'performance', 'persuasion', 'piloting',
'repair', 'research', 'riding', 'science', 'shooting', 'stealth',
'survival', 'taunt', 'thievery', 'gambling', 'hacking', 'faith',
'focus', 'language', 'occult', 'psionics', 'spellcasting',
'weird_science', 'custom_skill_1', 'custom_skill_2', 'unskilled'];
const skillDice = ['d4-2', 'd4', 'd6', 'd8', 'd10', 'd12'];
on('change:' + skills.join(' change:'), (e) => {
// Derive target attribute and check if input value is a valid die
let value = _.isUndefined(e.newValue) ? 'd4-2' : e.newValue;
value = skillDice.includes(value) ? value : 'd4-2';
// Set attributes to new value, silent to avoid triggering worker again
// but with a callback to update the attribute's roll query
setAttrs({ [e.sourceAttribute]: value },
{ silent: true },
TETRA.updateTraitRoll(e.sourceAttribute));
});
/* #############################################################################
WILD DICE
############################################################################# */
const wildTraits = ['soak'].concat(attributes).concat(skills).map((s) => { return `${s}_wd` });
on('change:' + wildTraits.join(' change:'), (e) => {
// Derive target attribute and check if input value is a valid die
let value = _.isUndefined(e.newValue) ? '' : e.newValue;
value = dice.includes(value) ? value : 'n/a';
// Set attributes to new value, silent to avoid triggering worker again
// but with a callback to update the attribute's roll query
setAttrs({ [e.sourceAttribute]: value },
{ silent: true },
TETRA.updateTraitRoll(e.sourceAttribute.replace('_wd', '')));
});
/* #############################################################################
NATURAL DICE
############################################################################# */
const naturalTraits = attributes.map((s) => { return `${s}_natural` });
on('change:' + naturalTraits.join(' change:'), (e) => {
// Check if new value is valid
let value = dice.includes(e.newValue) ? e.newValue : '';
value = _.isUndefined(e.newValue) ? 'd4' : value;
// Set attributes to new value, silent to avoid triggering worker again
setAttrs({ [e.sourceAttribute]: value }, { silent: true });
});
/* #############################################################################
RUN DIE
############################################################################# */
on('change:run', (e) => {
// Check if run value is a valid die
let value = dice.includes(e.newValue) ? e.newValue : '';
// Update run die
value = _.isUndefined(e.newValue) ? '' : value;
// Update run roll
let runRoll = value != '' ? value : 'd6';
// Set attributes to new values, silent to avoid triggering worker again
setAttrs({ [e.sourceAttribute]: value, ['run_roll']: runRoll },
{ silent: true });
});
/* #############################################################################
MODIFIERS
############################################################################# */
const modifiers = ['soak'].concat(attributes).concat(skills).map((s) => { return `${s}_mod` });
on('change:' + modifiers.join(' change:'), (e) => {
// Parse input value as integer
let value = TETRA.toInt(e.newValue, '');
// Set 0 to empty
value = value == 0 ? '' : value;
// Positive or negative prefix (required for roll query)
if (value != '') {
let s = String(value);
value = value > -1 && s.charAt(0) != '+' ? `+${s}` : s;
}
// Set attribute to new value, silent to avoid triggering worker again
setAttrs({ [e.sourceAttribute]: value },
{ silent: true },
TETRA.updateTraitRoll(e.sourceAttribute.replace('_mod', '')));
});
/* #############################################################################
SIMPLE STATISTICS
############################################################################# */
const simple = ['pace', 'flight', 'rads', 'power', 'power_consumed',
'power_maximum', 'armor_head', 'armor_torso', 'armor_arms',
'armor_legs', 'parry', 'toughness', 'integrity_maximum',
'carry_weight_maximum'];
on('change:' + simple.join(' change:'), (e) => {
// Parse input value as integer unless it is empty
let value = TETRA.toInt(e.newValue, '');
// Cannot be smaller than zero
if (value != '') {
value = value < 0 ? 0 : value;
}
// Set attribute to new value, silent to avoid triggering worker again
setAttrs({ [e.sourceAttribute]: value }, { silent: true });
});
/* #############################################################################
WOUND BOXES
############################################################################# */
const woundToggles = _.range(1, 7).map((n) => { return `wound_toggle_${n.toString()}` });
on('change:wounds change:wound_mitigation change:' + woundToggles.join(' change:'), (e) => {
let attributes = ['wounds', 'wound_mitigation'].concat(woundToggles);
// Get all relevant attributes
getAttrs(attributes, (values) => {
let setters = {};
// Get first inactive wound box
let firstInactiveWound = _.find(woundToggles, (ws) => {
return values[ws] == 'on';
});
// Get number of first inactive wound box
let firstInactiveNumber = (() => {
if (_.isUndefined(firstInactiveWound)) {
return 100;
}
let number = parseInt(firstInactiveWound.split('').pop());
return _.isUndefined(number) ? 100 : number;
})();
// Deactive all subsequent wound boxes
for (var i = firstInactiveNumber + 1; i < 7; ++i) {
setters[`wound_toggle_${i}`] = 'on';
};
// Limit active wounds to active wound boxes
if (values['wounds'] >= firstInactiveNumber) {
setters['wounds'] = firstInactiveNumber - 1;
} else {
setters['wounds'] = values['wounds'];
};
// Update wound-mitigation
let wm = isNaN(parseInt(values['wound_mitigation'])) ? '' : parseInt(values['wound_mitigation']);
if (wm != '') {
wm = wm < 0 ? 0 : wm;
}
setters['wound_mitigation'] = wm;
wm = isNaN(wm) ? 0 : wm;
// Update wound_mod
let wmod = setters['wounds'] - wm;
setters['wound_mod'] = wmod < 0 ? 0 : wmod;
setAttrs(setters, { silent: true });
});
});
/* #############################################################################
FATIGUE BOXES
############################################################################# */
const fatigueToggles = _.range(2, 4).map((n) => { return `fatigue_toggle_${n.toString()}` });
on('change:fatigue change:' + fatigueToggles.join(' change:'), (e) => {
let attributes = ['fatigue'].concat(fatigueToggles);
// Get all relevant attributes
getAttrs(attributes, (values) => {
let setters = {};
// Get first inactive box
let firstInactiveBox = _.find(fatigueToggles, (fs) => {
return values[fs] == 'on';
});
// Get number of first inactive box
let firstInactiveNumber = (() => {
if (_.isUndefined(firstInactiveBox)) {
return 100;
}
let number = parseInt(firstInactiveBox.split('').pop());
return _.isUndefined(number) ? 100 : number;
})();
// Deactive all subsequent boxes
for (var i = firstInactiveNumber + 1; i < 4; ++i) {
setters[`fatigue_toggle_${i}`] = 'on';
};
// Limit active fatigue to active boxes
if (values['fatigue'] >= firstInactiveNumber) {
setters['fatigue'] = firstInactiveNumber - 1;
} else {
setters['fatigue'] = values['fatigue'];
};
// Update fatigue_mod
let fmod = TETRA.toInt(setters['fatigue']);
setters['fatigue_mod'] = fmod;
setAttrs(setters, { silent: true });
});
});
/* #############################################################################
AUTO SIZE EDGES + HINDRANCES
############################################################################# */
// 10px Roboto
// Since Roll20 does not allow DOM access, yes, this BS is necessary.
// Don't take this as something to learn from! Stars above, no!
const letters = { 'A': 6.53, 'a': 5.45, 'B': 6.23, 'b': 5.63, 'C': 6.52,
'c': 5.23, 'D': 6.56, 'd': 5.64, 'E': 6.69, 'e': 5.31,
'F': 5.53, 'f': 3.48, 'G': 6.81, 'g': 5.63, 'H': 7.14,
'h': 5.52, 'I': 2.72, 'i': 2.44, 'J': 5.53, 'j': 2.39,
'K': 6.28, 'k': 5.08, 'L': 5.39, 'l': 2.44, 'M': 8.73,
'm': 8.77, 'N': 7.14, 'n': 5.53, 'O': 6.88, 'o': 5.70,
'P': 6.31, 'p': 5.63, 'Q': 6.88, 'q': 5.69, 'R': 6.17,
'r': 3.39, 'S': 5.94, 's': 5.16, 'T': 5.97, 't': 3.28,
'U': 6.48, 'u': 5.52, 'V': 6.38, 'v': 4.84, 'W': 8.88,
'w': 7.52, 'X': 6.28, 'x': 4.97, 'Y': 8.88, 'y': 7.52,
'Z': 6.28, 'z': 4.97, '#': 6.17, '&': 6.22, '(': 3.42,
')': 3.48, '+': 5.67, '-': 2.77, '=': 5.50, '*': 4.31,
'!': 2.58, '.': 2.64, ',': 1.97, '@': 8.98, ' ': 2.48,
'?': 4.73, '_': 4.52, '%': 7.33, 'Ä': 6.53, 'ä': 5.45,
'Ö': 6.88, 'ö': 5.70, 'Ü': 6.48, 'ü': 5.52, 'ß': 5.95,
'°': 3.75, '<': 5.09, '>': 5.23, '[': 2.66, ']': 2.66,
'{': 3.39, '}': 3.39, '$': 5.63, '"': 3.20 };
on('change:repeating_features:feature_name', (e) => {
let target = e.sourceAttribute.replace('_feature_name', '_feature_size');
getAttrs([e.sourceAttribute], (value) => {
// Split string into array
let chars = value[Object.keys(value)[0]].split(''),
stringWidth = 0.0;
// Sum up the width of each character
_.each(chars, (c) => {
stringWidth += _.has(letters, c) ? letters[c] : 5.0;
});
// Remove decimal points by rounding up
stringWidth = Math.ceil(stringWidth);
let offset = 4 - (stringWidth % 4),
elementWidth = stringWidth + offset;
// Maximum/minimum width
elementWidth = elementWidth > 180 ? 180 : elementWidth;
elementWidth = elementWidth < 32 ? 32 : elementWidth;
setAttrs({ [target]: elementWidth }, { silent: true });
});
});
/* #############################################################################
LOSS PROGRESS
############################################################################# */
on('change:integrity', (e) => {
// Parse as float or keep old value if invalid
let value = TETRA.toFloat(e.newValue, e.previousValue),
progress = Math.ceil(value),
setters = { integrity: value.toFixed(1) };
// Bar can only take numbers between 0 and 80
progress = progress > 80 ? 80 : progress;
progress = progress < 0 ? 0 : progress;
setters['integrity_bar_size'] = progress;
// Set corresponding psychological states
setters['discord'] = value < 70 ? 'on' : 'off';
setters['dissonance'] = value < 60 ? 'on' : 'off';
setters['psychosis'] = value < 50 ? 'on' : 'off';
setters['alienation'] = value < 40 ? 'on' : 'off';
setters['seizures'] = value < 30 ? 'on' : 'off';
setters['derealization'] = value < 20 ? 'on' : 'off';
setters['depersonalization'] = value < 10 ? 'on' : 'off';
setters['ego_death'] = value < 1 ? 'on' : 'off';
setAttrs(setters, { silent: true });
});
/* #############################################################################
REPEATING WEIGHT
############################################################################# */
function twoColSum(values, target, multi = false) {
let sum = 0.0;
// Turn into two-dimensional array
values = Object.entries(values);
// Continue to remove items from the array
while (values.length > 0) {
// Values must be in order of weight, toggle, multiplier!
let value = TETRA.toFloat(values.shift()[1]),
toggle = values.shift()[1],
multiplier = 1;
if (multi) {
multiplier = TETRA.toFloat(values.shift()[1]);
}
sum += toggle == 'on' ? value * multiplier : 0.0;
}
setAttrs({ [target]: sum.toFixed(1) });
}
on('remove:repeating_weapons change:repeating_weapons:weapon_weight change:repeating_weapons:weapon_weight_toggle', (e) => {
TETRA.doWithRepList('weapons',
['weapon_weight', 'weapon_weight_toggle'],
(v) => { twoColSum(v, 'carry_weight_weapons') });
});
on('remove:repeating_apparel change:repeating_apparel:apparel_weight change:repeating_apparel:apparel_weight_toggle', (e) => {
TETRA.doWithRepList('apparel',
['apparel_weight', 'apparel_weight_toggle'],
(v) => { twoColSum(v, 'carry_weight_apparel') });
});
on('remove:repeating_inventory change:repeating_inventory:inventory_weight change:repeating_inventory:inventory_weight_toggle change:repeating_inventory:inventory_amount', (e) => {
TETRA.doWithRepList('inventory',
['inventory_weight', 'inventory_weight_toggle', 'inventory_amount'],
(v) => { twoColSum(v, 'carry_weight_inventory', true) });
});
const carryWeights = ['carry_weight_apparel', 'carry_weight_weapons',
'carry_weight_inventory', 'carry_weight_maximum'];
on('change:' + carryWeights.join(' change:'), (e) => {
getAttrs(carryWeights, (value) => {
let weapons = TETRA.toFloat(value['carry_weight_weapons']),
apparel = TETRA.toFloat(value['carry_weight_apparel']),
inventory = TETRA.toFloat(value['carry_weight_inventory']),
total = (weapons + apparel + inventory).toFixed(1);
setAttrs({ ['carry_weight']: total }, { silent: true });
});
});
/* #############################################################################
LOSS
############################################################################# */
on('change:repeating_augmentations remove:repeating_augmentations', (e) => {
TETRA.doWithRepList('augmentations',
['augmentation_loss', 'augmentation_loss_toggle', 'augmentation_multiplier'],
(v) => { twoColSum(v, 'augmentations_total_loss', true) });
});
/* #############################################################################
INTEGRITY MOD
############################################################################# */
on('change:integrity_mod', (e) => {
setAttrs({ ['integrity_mod']: TETRA.toInt(e.newValue, '') });
});
/* #############################################################################
INTEGRITY CALCULATION
############################################################################# */
on('change:augmentations_total_loss change:integrity_maximum change:integrity_mod', (e) => {
getAttrs(['augmentations_total_loss', 'integrity_maximum', 'integrity_mod'], (values) => {
let loss = TETRA.toFloat(values['augmentations_total_loss']),
max = TETRA.toFloat(values['integrity_maximum'], 80),
mod = TETRA.toFloat(values['integrity_mod']);
setAttrs({ integrity: (max - loss + mod).toFixed(1) });
});
});
/* #############################################################################
REAPTING VEHICLE WEAPONS & SILHOUETTES
############################################################################# */
on('change:repeating_vehicles change:repeating_powerarmors', (e) => {
// Weapon count was changed
if (e.sourceAttribute.includes('weapons_count')) {
let value = TETRA.toInt(e.newValue, 0);
// Value must be between 0 and 10
value = value < 0 ? 0 : value;
value = value > 10 ? 10 : value;
setAttrs({ [e.sourceAttribute]: value }, { silent: true });
}
// Silhouette was changed
if (e.sourceAttribute.includes('silhouette')) {
let target = e.sourceAttribute.replace('_vehicle_silhouette_cycle', '_vehicle_silhouette');
getAttrs([target], (values) => {
let silhouette = TETRA.toInt(_.values(values)[0], 0);
setAttrs({ [target]: (silhouette + 1) % 17 }, { silent: true });
});
}
});
/* #############################################################################
AMMUNITION & BUTTONS
############################################################################# */
const ammoMods = ['1', '3', '5', '10'];
function changeAmmo(triggerName) {
let mod = TETRA.toInt(triggerName.replace('clicked:ammunition_', '')),
attributes = ['ammunition_select',
'ammunition_container_1',
'ammunition_container_2',
'ammunition_container_3'];
getAttrs(attributes, (values) => {
let target = values['ammunition_select'],
current = TETRA.toInt(values[target]); // The selector value is the attribute name of the box
// Add ammunition modifier
updated = current + mod;
// No negative ammunition
updated = updated < 0 ? 0 : updated;
setAttrs({ [target]: updated }, { silent: true });
});
}
on(`clicked:${ammoMods.map((s) => { return `ammunition_+${s}` }).join(' clicked:')}`, (e) => {
changeAmmo(e.triggerName);
});
on(`clicked:${ammoMods.map((s) => { return `ammunition_-${s}` }).join(' clicked:')}`, (e) => {
changeAmmo(e.triggerName);
});
on(`clicked:ammunition_set`, (e) => {
getAttrs(['ammunition_select'], (value) => {
let target = `${value['ammunition_select']}_max`;
getAttrs([value['ammunition_select']], (current) => {
update = TETRA.toInt(current[value['ammunition_select']]);
setAttrs({ [target]: update }, { silent: true });
});
});
});
on(`clicked:ammunition_reset`, (e) => {
getAttrs(['ammunition_select'], (value) => {
let target = value['ammunition_select'];
getAttrs([`${target}_max`], (max) => {
update = TETRA.toInt(max[`${target}_max`]);
setAttrs({ [target]: update }, { silent: true });
});
});
});
/* #############################################################################
REPEATING LISTS & OPTIONS
############################################################################# */
const listItems = ['engram', 'power', 'weapon'];
on(listItems.map(s => `change:repeating_${s}s:${s}_damage`).join(' '), (e) => {
let code = TETRA.parseDiceCode(e.newValue);
code = code.replaceAll('!', '@{explode_damage}'); // Toggle exploding damage dice
code = code == '' ? '0' : code;
setAttrs({ [`${e.sourceAttribute}_roll`]: code }, { silent: true });
});
on(listItems.map(s => `change:repeating_${s}s:skill_name`).join(' '), (e) => {
let array = skills.map((s) => { return `rename_${s}` }),
id = e.sourceAttribute.replace('skill_name', ''); // All rename_<skill> attributes
getAttrs(array, (values) => {
// Find corresponding attribute
let renameAttribute = Object.keys(values).find(key => values[key] === e.newValue),
update = {};
if (_.isUndefined(renameAttribute)) {
update[e.sourceAttribute] = e.previousValue; // Reset if input is invalid
} else {
update[`${id}skill_wd`] = `@{${renameAttribute.replace('rename_', '')}_wd}`;
update[`${id}skill_mod`] = `@{${renameAttribute.replace('rename_', '')}_mod}`;
update[`${id}skill_untrained_mod`] = `@{${renameAttribute.replace('rename_', '')}_untrained_mod}`;
update[`${id}skill_roll`] = `@{${renameAttribute.replace('rename_', '')}_roll}`;
update[`${id}skill_wd_roll`] = `@{${renameAttribute.replace('rename_', '')}_wd_roll}`;
update[`${id}skill_extra_wd_roll`] = `@{${renameAttribute.replace('rename_', '')}_extra_wd_roll}`;
}
setAttrs(update, { silent: true });
});
});
on(listItems.map(s => `change:repeating_${s}s:skill_bonus`).join(' '), (e) => {
let update = TETRA.toInt(e.newValue);
update = update == 0 ? '' : update > 0 ? `+${update}` : update;
setAttrs({ [e.sourceAttribute]: update }, { silent: true });
});
on(listItems.map(s => `change:repeating_${s}s:rof_override_toggle`).join(' '), (e) => {
let d = e.sourceAttribute.includes('weapon') ? '@{weapon_rof}' : '1',
update = e.newValue == 'on' ? `?{@{query_rate_of_fire}|${d}}` : '1',
target = e.sourceAttribute.replace('_toggle', '');
setAttrs({ [target]: update }, { silent: true });
});
on(listItems.map(s => `change:repeating_${s}s:wd_override_toggle`).join(' '), (e) => {
let update = e.newValue == 'on' ? `{{wd=@{wd_override_die}}} {{wdroll=[[ @{wd_override_die}!cs2 + (@{skill_untrained_mod})[Untrained] + (@{skill_mod}+0)[Flat] + (@{skill_bonus}+0)[Bonus] - (@{fatigue_mod})[Fatigue] - (@{wound_mod})[Wounds] + (?{@{query_modifier}|0})[Modifier] ]]}}` : '',
target = e.sourceAttribute.replace('_toggle', '');
setAttrs({ [target]: update }, { silent: true });
});
on(listItems.map(s => `change:repeating_${s}s:roll_description_toggle`).join(' '), (e) => {
let update = e.newValue == 'on' ? `@{roll_description_input}` : '',
target = e.sourceAttribute.replace('_toggle', '');
setAttrs({ [target]: update }, { silent: true });
});
on(listItems.map(s => `change:repeating_${s}s:roll_injection_toggle`).join(' '), (e) => {
let update = e.newValue == 'on' ? `@{roll_injection_input}` : '',
target = e.sourceAttribute.replace('_toggle', '');
setAttrs({ [target]: update }, { silent: true });
});
on(listItems.map(s => `change:repeating_${s}s:explode_damage_toggle`).join(' '), (e) => {
let update = e.newValue == 'on' ? '!' : ' ',
target = e.sourceAttribute.replace('_toggle', '');
setAttrs({ [target]: update }, { silent: true });
});
/* #############################################################################
HELPER BUTTONS
############################################################################# */
on('change:dice_roller_input', (e) => {
let code = TETRA.parseDiceCode(e.newValue);
setAttrs({ ['dice_roller_code']: code }, { silent: true });
});
/* #############################################################################
SETTINGS
############################################################################# */
on('change:toggle_global_rof', (e) => {
let update = e.newValue == 'on' ? '?{@{query_rate_of_fire}|1}' : '1';
setAttrs({ ['query_global_rof']: update }, { silent: true });
});
on('change:toggle_global_wd', (e) => {
let update = e.newValue == 'on' ? '?{@{query_wild_die}|d8|d4|d6|d10|d12|@{none},-10000-d20}' : '-10000-d20';
setAttrs({ ['query_global_wd']: update }, { silent: true });
});
on('change:toggle_global_bonus_wd', (e) => {
let update = e.newValue == 'on' ? '?{@{query_bonus_wild_die}|d4|d6|d8|d10|d12|@{none},-10000-d20}' : '-10000-d20';
setAttrs({ ['query_global_bonus_wd']: update }, { silent: true });
});
on('change:toggle_run_wounds', (e) => {
let update = e.newValue == 'on' ? '0' : '@{wound_mod}';
setAttrs({ ['run_wounds']: update }, { silent: true });
});
on('change:toggle_run_fatigue', (e) => {
let update = e.newValue == 'on' ? '@{fatigue_mod}' : '0';
setAttrs({ ['run_fatigue']: update }, { silent: true });
});
on('change:toggle_run_explode', (e) => {
let update = e.newValue == 'on' ? '@{run_roll}!' : '@{run_roll}';
setAttrs({ ['run_code']: update }, { silent: true });
});