Files
roll20-character-sheets/5eShaped/precompiled/scripts/Character.js
T
2016-07-25 17:04:58 +02:00

315 lines
10 KiB
JavaScript

/* global on:false */
import { ABILITIES, CLASSES } from './constants';
import { ClassFeatures } from './ClassFeatures';
const classFeatures = new ClassFeatures();
import { Spells } from './Spells';
const spells = new Spells();
import { getSetItems, getSetRepeatingItems, isUndefinedOrEmpty, getIntValue, exists, capitalize, getRepeatingInfo, updateHD } from './utilities';
export class Character {
updateLevels(repeatingInfo) {
const defaultClassDetails = {
barbarian: {
hd: 'd12',
},
bard: {
hd: 'd8',
spellcasting: 'full',
},
cleric: {
hd: 'd8',
spellcasting: 'full',
},
druid: {
hd: 'd8',
spellcasting: 'full',
},
fighter: {
hd: 'd10',
},
monk: {
hd: 'd8',
},
paladin: {
hd: 'd10',
spellcasting: 'half',
},
ranger: {
hd: 'd10',
spellcasting: 'half',
},
rogue: {
hd: 'd8',
},
sorcerer: {
hd: 'd6',
spellcasting: 'full',
},
warlock: {
hd: 'd8',
spellcasting: 'warlock',
},
wizard: {
hd: 'd6',
spellcasting: 'full',
},
};
const hd = {
d20: 0,
d12: 0,
d10: 0,
d8: 0,
d6: 0,
d4: 0,
d2: 0,
d0: 0,
};
const spellcasting = {
full: 0,
half: 0,
third: 0,
warlock: 0,
};
let totalLevel = 0;
const classLevels = {};
let classesWithSpellcasting = 0;
const xpTable = [0, 300, 900, 2700, 6500, 14000, 23000, 34000, 48000, 64000, 85000, 100000, 120000, 140000, 165000, 195000, 225000, 265000, 305000, 355000, 385000, 405000, 435000, 465000, 495000, 525000, 555000, 585000, 605000, 635000, 665000];
const collectionArray = ['is_npc', 'caster_level', 'caster_type', 'class_and_level', 'level', 'xp_next_level'];
for (const className of CLASSES) {
collectionArray.push(`${className}_level`);
collectionArray.push(`has_${className}_levels`);
}
for (const key in hd) {
if (hd.hasOwnProperty(key)) {
collectionArray.push(`hd_${key}_max`);
collectionArray.push(`hd_${key}_query`);
collectionArray.push(`hd_${key}_toggle`);
}
}
getSetRepeatingItems('character.updateLevels', {
repeatingItems: ['repeating_class'],
collectionArray,
collectionArrayAddItems: ['level', 'name', 'custom_name', 'hd', 'spellcasting', 'custom_class_toggle'],
callback: (v, finalSetAttrs, ids, repeatingItem) => {
for (const className of CLASSES) {
finalSetAttrs[`${className}_level`] = 0;
}
for (const id of ids) {
const repeatingString = `${repeatingItem}_${id}_`;
let className = v[`${repeatingString}name`];
let classLevel = v[`${repeatingString}level`];
if (isUndefinedOrEmpty(className) && isUndefinedOrEmpty(classLevel)) {
continue;
}
if (isUndefinedOrEmpty(className)) {
className = 'barbarian';
}
if (className === 'custom') {
finalSetAttrs[`${repeatingString}custom_class_toggle`] = 'on';
const customName = v[`${repeatingString}custom_name`];
if (exists(customName)) {
className = customName;
} else {
className = 'custom';
}
} else if (v[`${repeatingString}custom_class_toggle`]) {
finalSetAttrs[`${repeatingString}custom_class_toggle`] = 0;
}
if (isUndefinedOrEmpty(classLevel)) {
classLevel = 1;
finalSetAttrs[`${repeatingString}level`] = classLevel;
finalSetAttrs[`${className}_level`] = classLevel;
} else {
classLevel = getIntValue(classLevel);
totalLevel += classLevel;
if (classLevels[className]) {
classLevels[className] += classLevel;
} else {
classLevels[className] = classLevel;
}
}
let classHd = v[`${repeatingString}hd`];
if (isUndefinedOrEmpty(classHd) || (repeatingInfo && repeatingInfo.field === 'name')) {
if (defaultClassDetails.hasOwnProperty(className)) {
classHd = defaultClassDetails[className].hd;
} else {
classHd = 'd12';
}
finalSetAttrs[`${repeatingString}hd`] = classHd;
}
if (classHd && classLevel) {
hd[classHd] += classLevel;
}
let classSpellcasting = v[`${repeatingString}spellcasting`];
if (isUndefinedOrEmpty(classSpellcasting)) {
if (defaultClassDetails.hasOwnProperty(className)) {
classSpellcasting = defaultClassDetails[className].spellcasting;
if (classSpellcasting) {
finalSetAttrs[`${repeatingString}spellcasting`] = classSpellcasting;
}
} else {
finalSetAttrs[`${repeatingString}spellcasting`] = 'none';
}
} else if (classSpellcasting === 'warlock') {
spellcasting[classSpellcasting] += classLevel;
} else {
classesWithSpellcasting += 1;
spellcasting[classSpellcasting] += classLevel;
}
}
finalSetAttrs.number_of_classes = 0;
finalSetAttrs.class_and_level = '';
for (const prop in classLevels) {
if (classLevels.hasOwnProperty(prop)) {
finalSetAttrs.number_of_classes = finalSetAttrs.number_of_classes + 1;
if (classLevels.hasOwnProperty(prop)) {
finalSetAttrs[`${prop}_level`] = classLevels[prop];
if (finalSetAttrs.class_and_level !== '') {
finalSetAttrs.class_and_level += ', ';
}
finalSetAttrs.class_and_level += `${capitalize(prop)} ${classLevels[prop]}`;
}
}
}
finalSetAttrs.level = totalLevel;
let xpForNextLevel = 0;
if (!totalLevel) {
totalLevel = 0;
}
if (totalLevel > 30) {
xpForNextLevel = xpTable[30];
} else {
xpForNextLevel = xpTable[totalLevel];
}
finalSetAttrs.xp_next_level = xpForNextLevel;
for (const className of CLASSES) {
if (finalSetAttrs[`${className}_level`] > 0) {
finalSetAttrs[`has_${className}_levels`] = 1;
} else if (!isUndefinedOrEmpty(v[`has_${className}_levels`])) {
finalSetAttrs[`has_${className}_levels`] = 0;
}
}
updateHD(v, finalSetAttrs, hd);
let casterLevel = 0;
if (!v.is_npc || v.is_npc === '0' || v.is_npc === 0) {
if (classesWithSpellcasting > 1) {
casterLevel += spellcasting.full;
casterLevel += Math.floor(spellcasting.half / 2);
casterLevel += Math.floor(spellcasting.third / 3);
} else {
casterLevel = spellcasting.full + spellcasting.half + spellcasting.third;
}
finalSetAttrs.caster_level = casterLevel;
}
if (classesWithSpellcasting > 1 || spellcasting.full) {
finalSetAttrs.caster_type = 'full';
} else if (spellcasting.half) {
finalSetAttrs.caster_type = 'half';
} else if (spellcasting.third) {
finalSetAttrs.caster_type = 'third';
}
},
setFinalAttrsCallback: () => {
classFeatures.set();
spells.updateSlots();
},
});
}
watchForClassLevelChanges() {
const classFeatureWatch = [];
for (const ability of ABILITIES) {
classFeatureWatch.push(`change:${ability}_mod`);
}
classFeatureWatch.push('change:careful_spell_toggle');
classFeatureWatch.push('change:distant_spell_toggle');
classFeatureWatch.push('change:empowered_spell_toggle');
classFeatureWatch.push('change:extended_spell_toggle');
classFeatureWatch.push('change:heightened_spell_toggle');
classFeatureWatch.push('change:quickened_spell_toggle');
classFeatureWatch.push('change:subtle_spell_toggle');
classFeatureWatch.push('change:twinned_spell_toggle');
on(classFeatureWatch.join(' '), () => {
classFeatures.set();
});
}
updateJackOfAllTrades() {
getSetItems('character.updateJackOfAllTrades', {
collectionArray: ['pb', 'jack_of_all_trades'],
callback: (v, finalSetAttrs) => {
finalSetAttrs.jack_of_all_trades = Math.floor(getIntValue(v.pb) / 2);
},
});
}
updateRemarkableAthlete() {
getSetItems('character.updateRemarkableAthlete', {
collectionArray: ['pb', 'remarkable_athlete'],
callback: (v, finalSetAttrs) => {
finalSetAttrs.remarkable_athlete = Math.ceil(getIntValue(v.pb) / 2);
},
});
}
updateD20Mod() {
getSetItems('character.updateD20Mod', {
collectionArray: ['halfling_luck'],
callback: (v, finalSetAttrs) => {
if (v.halfling_luck === 'on') {
finalSetAttrs.d20_mod = 'ro<1';
} else {
finalSetAttrs.d20_mod = '';
}
},
});
}
updateAlignment() {
getSetItems('character.updateAlignment', {
collectionArray: ['alignment', 'is_npc'],
callback: (v, finalSetAttrs) => {
if (v.alignment && v.is_npc === '1') {
finalSetAttrs.alignment = v.alignment.toLowerCase();
}
},
});
}
setup() {
on('change:repeating_class', (eventInfo) => {
const repeatingInfo = getRepeatingInfo('repeating_class', eventInfo);
if (repeatingInfo) {
this.updateLevels(repeatingInfo);
}
});
on('remove:repeating_class', () => {
this.updateLevels();
});
on('change:halfling_luck', () => {
this.updateD20Mod();
});
on('change:alignment', () => {
this.updateAlignment();
});
on('change:pb change:remarkable_athlete_toggle', () => {
this.updateRemarkableAthlete();
});
on('change:pb change:jack_of_all_trades_toggle', () => {
this.updateJackOfAllTrades();
});
this.watchForClassLevelChanges();
}
}