[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:
@@ -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 });
|
||||
});
|
||||
Reference in New Issue
Block a user