mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-09-11 09:32:33 +00:00
967 lines
35 KiB
JavaScript
967 lines
35 KiB
JavaScript
/* global getTranslationByKey:false */
|
|
|
|
import { getSetItems, isUndefinedOrEmpty, getIntValue } from './utilities';
|
|
import { CLASSES, ABILITIES, TOGGLE_VARS } from './constants';
|
|
import { Traits } from './Traits';
|
|
const traits = new Traits();
|
|
|
|
export class ClassFeatures {
|
|
extraAttack(v) {
|
|
if (v.fighter_level >= 5) {
|
|
let extraAttackTimes;
|
|
if (v.fighter_level >= 20) {
|
|
extraAttackTimes = getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_FOUR');
|
|
} else if (v.fighter_level >= 11) {
|
|
extraAttackTimes = getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_THREE');
|
|
} else {
|
|
extraAttackTimes = getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TWICE');
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TEXT').replace('NUMBER_OF_TIMES', extraAttackTimes),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK'),
|
|
storageName: 'Extra Attack',
|
|
});
|
|
} else if (v.barbarian_level >= 5 || v.monk_level >= 5 || v.paladin_level >= 5 || v.ranger_level >= 5) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TEXT').replace('NUMBER_OF_TIMES', getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TWICE')),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK'),
|
|
storageName: 'Extra Attack',
|
|
});
|
|
}
|
|
}
|
|
barbarian(v, finalSetAttrs) {
|
|
if (v.barbarian_level) {
|
|
let rageUses;
|
|
if (v.barbarian_level >= 20) {
|
|
rageUses = 999999;
|
|
} else if (v.barbarian_level >= 17) {
|
|
rageUses = 6;
|
|
} else if (v.barbarian_level >= 12) {
|
|
rageUses = 5;
|
|
} else if (v.barbarian_level >= 6) {
|
|
rageUses = 4;
|
|
} else if (v.barbarian_level >= 3) {
|
|
rageUses = 3;
|
|
} else {
|
|
rageUses = 2;
|
|
}
|
|
|
|
let rageDamage;
|
|
if (v.barbarian_level >= 16) {
|
|
rageDamage = 4;
|
|
} else if (v.barbarian_level >= 9) {
|
|
rageDamage = 3;
|
|
} else {
|
|
rageDamage = 2;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RAGE_TEXT').replace('RAGE_DAMAGE', rageDamage),
|
|
name: getTranslationByKey('CLASS_FEATURE_RAGE'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Rage',
|
|
type: 'Other',
|
|
uses_max: rageUses,
|
|
});
|
|
|
|
if (isUndefinedOrEmpty(v.ac_unarmored_ability)) {
|
|
finalSetAttrs.ac_unarmored_ability = 'constitution';
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE_BARBARIAN_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE'),
|
|
storageName: 'Unarmored Defense Barbarian',
|
|
});
|
|
|
|
if (v.barbarian_level >= 2) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RECKLESS_ATTACK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_RECKLESS_ATTACK'),
|
|
storageName: 'Reckless Attack',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DANGER_SENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DANGER_SENSE'),
|
|
storageName: 'Danger Sense',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 5) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FAST_MOVEMENT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FAST_MOVEMENT'),
|
|
storageName: 'Fast Movement',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 7) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FERAL_INSTINCT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FERAL_INSTINCT'),
|
|
storageName: 'Feral Instinct',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 9) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BRUTAL_CRITICAL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_BRUTAL_CRITICAL'),
|
|
storageName: 'Brutal Critical',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 11) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RELENTLESS_RAGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_RELENTLESS_RAGE'),
|
|
storageName: 'Relentless Rage',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 15) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PERSISTENT_RAGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PERSISTENT_RAGE'),
|
|
storageName: 'Persistent Rage',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 18) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_INDOMITABLE_MIGHT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_INDOMITABLE_MIGHT'),
|
|
storageName: 'Indomitable Might',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PRIMAL_CHAMPION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PRIMAL_CHAMPION'),
|
|
storageName: 'Primal Champion',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
bard(v, finalSetAttrs) {
|
|
if (v.bard_level) {
|
|
let die;
|
|
if (v.bard_level >= 15) {
|
|
die = 'd12';
|
|
} else if (v.bard_level >= 10) {
|
|
die = 'd10';
|
|
} else if (v.bard_level >= 5) {
|
|
die = 'd8';
|
|
} else {
|
|
die = 'd6';
|
|
}
|
|
let recharge = 'Long Rest';
|
|
if (v.bard_level >= 5) {
|
|
recharge = 'Short Rest';
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BARDIC_INSPIRATION_TEXT').replace('d6', die),
|
|
name: getTranslationByKey('CLASS_FEATURE_BARDIC_INSPIRATION'),
|
|
recharge,
|
|
storageName: 'Bardic Inspiration',
|
|
uses_max: Math.max(getIntValue(v.charisma_mod), 1),
|
|
});
|
|
|
|
if (v.bard_level >= 2) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_JACK_OF_ALL_TRADES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_JACK_OF_ALL_TRADES'),
|
|
storageName: 'Jack of All Trades',
|
|
});
|
|
finalSetAttrs.jack_of_all_trades_toggle = '@{jack_of_all_trades}';
|
|
|
|
let heal;
|
|
if (v.bard_level >= 17) {
|
|
heal = 'd12';
|
|
} else if (v.bard_level >= 13) {
|
|
heal = 'd10';
|
|
} else if (v.bard_level >= 9) {
|
|
heal = 'd8';
|
|
} else {
|
|
heal = 'd6';
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SONG_OF_REST_TEXT'),
|
|
heal,
|
|
name: getTranslationByKey('CLASS_FEATURE_SONG_OF_REST'),
|
|
storageName: 'Song of Rest',
|
|
});
|
|
}
|
|
if (v.bard_level >= 3) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXPERTISE_BARD_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXPERTISE'),
|
|
storageName: 'Expertise Bard',
|
|
});
|
|
}
|
|
if (v.bard_level >= 6) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_COUNTERCHARM_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_COUNTERCHARM'),
|
|
storageName: 'Countercharm',
|
|
});
|
|
}
|
|
if (v.bard_level >= 10) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_MAGICAL_SECRETS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_MAGICAL_SECRETS'),
|
|
storageName: 'Magical Secrets',
|
|
});
|
|
}
|
|
if (v.bard_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SUPERIOR_INSPIRATION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SUPERIOR_INSPIRATION'),
|
|
storageName: 'Superior Inspiration',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
cleric(v) {
|
|
if (v.cleric_level) {
|
|
if (v.cleric_level >= 2) {
|
|
let channelDivinityUses;
|
|
if (v.cleric_level >= 18) {
|
|
channelDivinityUses = 3;
|
|
} else if (v.cleric_level >= 6) {
|
|
channelDivinityUses = 2;
|
|
} else {
|
|
channelDivinityUses = 1;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_CLERIC_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Channel Divinity Cleric',
|
|
uses_max: channelDivinityUses,
|
|
});
|
|
|
|
let turnUndeadText = getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_TURN_UNDEAD_TEXT');
|
|
if (v.cleric_level >= 5) {
|
|
let turnUndeadDestroyCR;
|
|
if (v.cleric_level >= 17) {
|
|
turnUndeadDestroyCR = '4';
|
|
} else if (v.cleric_level >= 14) {
|
|
turnUndeadDestroyCR = '3';
|
|
} else if (v.cleric_level >= 11) {
|
|
turnUndeadDestroyCR = '2';
|
|
} else if (v.cleric_level >= 8) {
|
|
turnUndeadDestroyCR = '1';
|
|
} else {
|
|
turnUndeadDestroyCR = '1/2';
|
|
}
|
|
turnUndeadText += `\n${getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_TURN_UNDEAD_TEXT_PART_2').replace('CHALLENGE_RATING', turnUndeadDestroyCR)}`;
|
|
}
|
|
traits.set({
|
|
freetext: turnUndeadText,
|
|
name: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_TURN_UNDEAD'),
|
|
saving_throw_ability: 'wisdom',
|
|
saving_throw_vs_ability: 'WISDOM',
|
|
storageName: 'Turn Undead',
|
|
});
|
|
if (v.cleric_level >= 10) {
|
|
traits.set({
|
|
freeform: '{{text_top=[[d100]] > [[@{cleric_level}]]}}',
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_INTERVENTION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_INTERVENTION'),
|
|
storageName: 'Divine Intervention',
|
|
uses_max: 1,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
druid(v) {
|
|
if (v.druid_level) {
|
|
if (v.druid_level >= 2) {
|
|
const wildShapeHours = Math.floor(v.druid_level / 2);
|
|
let wildShapeUses;
|
|
let wildShapeCR;
|
|
let wildShapeLimitations;
|
|
|
|
if (v.druid_level >= 4) {
|
|
wildShapeCR = '1/2';
|
|
wildShapeLimitations = getTranslationByKey('CLASS_FEATURE_WILD_SHAPE_NO_FLYING');
|
|
} else if (v.druid_level >= 8) {
|
|
wildShapeCR = 1;
|
|
wildShapeLimitations = '';
|
|
} else {
|
|
wildShapeCR = '1/4';
|
|
wildShapeLimitations = getTranslationByKey('CLASS_FEATURE_WILD_SHAPE_NO_FLYING_OR_SWIMMING');
|
|
}
|
|
if (v.druid_level >= 20) {
|
|
wildShapeUses = 999999;
|
|
} else {
|
|
wildShapeUses = 2;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_WILD_SHAPE_TEXT')
|
|
.replace('WILD_SHAPE_HOURS', wildShapeHours)
|
|
.replace('CHALLENGE_RATING', wildShapeCR)
|
|
.replace('LIMITATIONS', ` ${wildShapeLimitations}`),
|
|
name: getTranslationByKey('CLASS_FEATURE_WILD_SHAPE'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Wild Shape',
|
|
uses_max: wildShapeUses,
|
|
});
|
|
}
|
|
if (v.druid_level >= 18) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY_DRUID_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY'),
|
|
storageName: 'Timeless Body',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BEAST_SPELLS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_BEAST_SPELLS'),
|
|
storageName: 'Beast Spells',
|
|
});
|
|
}
|
|
if (v.druid_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ARCHDRUID_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ARCHDRUID'),
|
|
storageName: 'Archdruid',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
fighter(v) {
|
|
if (v.fighter_level) {
|
|
traits.set({
|
|
freetext: `${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_TEXT')} ${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_FIGHTER_OPTIONS')}`,
|
|
name: getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE'),
|
|
storageName: 'Fighting Style',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SECOND_WIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SECOND_WIND'),
|
|
heal: 'd10 + @{fighter_level}',
|
|
recharge: 'Short Rest',
|
|
storageName: 'Second Wind',
|
|
uses_max: 1,
|
|
});
|
|
|
|
if (v.fighter_level >= 2) {
|
|
let actionSurgeUses;
|
|
if (v.fighter_level >= 17) {
|
|
actionSurgeUses = 2;
|
|
} else {
|
|
actionSurgeUses = 1;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ACTION_SURGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ACTION_SURGE'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Action Surge',
|
|
uses_max: actionSurgeUses,
|
|
});
|
|
}
|
|
if (v.fighter_level >= 9) {
|
|
let indomitableUses;
|
|
if (v.fighter_level >= 17) {
|
|
indomitableUses = 3;
|
|
} else if (v.fighter_level >= 13) {
|
|
indomitableUses = 2;
|
|
} else {
|
|
indomitableUses = 1;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_INDOMITABLE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_INDOMITABLE'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Indomitable',
|
|
uses_max: indomitableUses,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
monk(v, finalSetAttrs) {
|
|
if (v.monk_level) {
|
|
if (isUndefinedOrEmpty(v.ac_unarmored_ability)) {
|
|
finalSetAttrs.ac_unarmored_ability = 'wisdom';
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE_MONK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE'),
|
|
storageName: 'Unarmored Defense Monk',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_MARTIAL_ARTS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_MARTIAL_ARTS'),
|
|
storageName: 'Martial Arts',
|
|
});
|
|
if (v.monk_level >= 2) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_KI_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_KI'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Ki',
|
|
uses_max: v.monk_level,
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FLURRY_OF_BLOWS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FLURRY_OF_BLOWS'),
|
|
storageName: 'Flurry of Blows',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PATIENT_DEFENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PATIENT_DEFENSE'),
|
|
storageName: 'Patient Defense',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STEP_OF_THE_WIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STEP_OF_THE_WIND'),
|
|
storageName: 'Step of the Wind',
|
|
});
|
|
|
|
let unarmoredMovementFeet;
|
|
if (v.monk_level >= 18) {
|
|
unarmoredMovementFeet = 30;
|
|
} else if (v.monk_level >= 14) {
|
|
unarmoredMovementFeet = 25;
|
|
} else if (v.monk_level >= 10) {
|
|
unarmoredMovementFeet = 20;
|
|
} else if (v.monk_level >= 6) {
|
|
unarmoredMovementFeet = 15;
|
|
} else {
|
|
unarmoredMovementFeet = 10;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNARMORED_MOVEMENT_TEXT').replace('UNARMORED_MOVEMENT_FEET', unarmoredMovementFeet),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNARMORED_MOVEMENT'),
|
|
storageName: 'Unarmored Movement',
|
|
});
|
|
}
|
|
if (v.monk_level >= 3) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DEFLECT_MISSILES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DEFLECT_MISSILES'),
|
|
storageName: 'Deflect Missiles',
|
|
});
|
|
}
|
|
if (v.monk_level >= 4) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SLOW_FALL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SLOW_FALL'),
|
|
storageName: 'Slow Fall',
|
|
});
|
|
}
|
|
if (v.monk_level >= 5) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STUNNING_STRIKE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STUNNING_STRIKE'),
|
|
saving_throw_ability: 'wisdom',
|
|
saving_throw_failure: getTranslationByKey('CLASS_FEATURE_STUNNING_STRIKE_SAVING_THROW_FAILURE'),
|
|
saving_throw_vs_ability: 'CONSTITUTION',
|
|
storageName: 'Stunning Strike',
|
|
});
|
|
}
|
|
if (v.monk_level >= 6) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_KI_EMPOWERED_STRIKES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_KI_EMPOWERED_STRIKES'),
|
|
storageName: 'Ki-Empowered Strikes',
|
|
});
|
|
}
|
|
if (v.monk_level >= 7) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EVASION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EVASION'),
|
|
storageName: 'Evasion',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STILLNESS_OF_MIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STILLNESS_OF_MIND'),
|
|
storageName: 'Stillness of Mind',
|
|
});
|
|
}
|
|
if (v.monk_level >= 10) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PURITY_OF_BODY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PURITY_OF_BODY'),
|
|
storageName: 'Purity of Body',
|
|
});
|
|
}
|
|
if (v.monk_level >= 13) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_TONGUE_OF_THE_SUN_AND_MOON_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_TONGUE_OF_THE_SUN_AND_MOON'),
|
|
storageName: 'Tongue of the Sun and Moon',
|
|
});
|
|
}
|
|
if (v.monk_level >= 14) {
|
|
finalSetAttrs.death_saving_throw_prof = '@{pb}';
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIAMOND_SOUL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIAMOND_SOUL'),
|
|
storageName: 'Diamond Soul',
|
|
});
|
|
}
|
|
if (v.monk_level >= 15) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY_MONK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY'),
|
|
storageName: 'Timeless Body',
|
|
});
|
|
}
|
|
if (v.monk_level >= 15) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EMPTY_BODY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EMPTY_BODY'),
|
|
storageName: 'Empty Body',
|
|
});
|
|
}
|
|
if (v.monk_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PERFECT_SELF_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PERFECT_SELF'),
|
|
storageName: 'Perfect Self',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
paladin(v) {
|
|
if (v.paladin_level) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_SENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_SENSE'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Divine Sense',
|
|
uses_max: Math.max(1 + v.charisma_mod, 1),
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_LAY_ON_HANDS_TEXT'),
|
|
heal_query_toggle: TOGGLE_VARS.heal_query,
|
|
name: getTranslationByKey('CLASS_FEATURE_LAY_ON_HANDS'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Lay on Hands',
|
|
uses_max: 5 * v.paladin_level,
|
|
});
|
|
|
|
if (v.paladin_level >= 2) {
|
|
traits.set({
|
|
freetext: `${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_TEXT')} ${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_PALADIN_OPTIONS')}`,
|
|
name: getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE'),
|
|
storageName: 'Fighting Style',
|
|
});
|
|
traits.set({
|
|
damage: '(?{Spell Level|1|2|3|4+, 4} + 1)d8',
|
|
damage_type: getTranslationByKey('RADIANT'),
|
|
freeform: '{{subheader=(as ?{Spell Level|1|2|3|4+})}}',
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_SMITE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_SMITE'),
|
|
second_damage: 'd8',
|
|
second_damage_type: getTranslationByKey('VS_FIEND_OR_UNDEAD'),
|
|
storageName: 'Divine Smite',
|
|
type: 'Other',
|
|
});
|
|
}
|
|
if (v.paladin_level >= 3) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_HEALTH_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_HEALTH'),
|
|
storageName: 'Divine Health',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_PALADIN_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Channel Divinity Paladin',
|
|
uses_max: 1,
|
|
});
|
|
}
|
|
if (v.paladin_level >= 6) {
|
|
let auraRange;
|
|
if (v.paladin_level >= 18) {
|
|
auraRange = 30;
|
|
} else {
|
|
auraRange = 10;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_AURA_OF_PROTECTION_TEXT').replace('AURA_RANGE', auraRange),
|
|
name: getTranslationByKey('CLASS_FEATURE_AURA_OF_PROTECTION'),
|
|
storageName: 'Aura of Protection',
|
|
});
|
|
|
|
if (v.paladin_level >= 10) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_AURA_OF_COURAGE_TEXT').replace('AURA_RANGE', auraRange),
|
|
name: getTranslationByKey('CLASS_FEATURE_AURA_OF_COURAGE'),
|
|
storageName: 'Aura of Courage',
|
|
});
|
|
}
|
|
}
|
|
if (v.paladin_level >= 11) {
|
|
traits.set({
|
|
damage: '1d8',
|
|
damage_type: 'radiant',
|
|
freetext: getTranslationByKey('CLASS_FEATURE_IMPROVED_DIVINE_SMITE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_IMPROVED_DIVINE_SMITE'),
|
|
storageName: 'Improved Divine Smite',
|
|
type: 'Other',
|
|
});
|
|
}
|
|
if (v.paladin_level >= 14) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CLEANSING_TOUCH_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CLEANSING_TOUCH'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Cleaning Touch',
|
|
uses_max: Math.max(getIntValue(v.charisma_mod), 1),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
ranger(v) {
|
|
if (v.ranger_level) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FAVORED_ENEMY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FAVORED_ENEMY'),
|
|
storageName: 'Favored Enemy',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_NATURAL_EXPLORER_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_NATURAL_EXPLORER'),
|
|
storageName: 'Natural Explorer',
|
|
});
|
|
if (v.ranger_level >= 2) {
|
|
traits.set({
|
|
freetext: `${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_TEXT')} ${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_RANGER_OPTIONS')}`,
|
|
name: getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE'),
|
|
storageName: 'Fighting Style',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 3) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PRIMEVAL_AWARENESS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PRIMEVAL_AWARENESS'),
|
|
storageName: 'Primeval Awareness',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 8) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_LANDS_STRIDE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_LANDS_STRIDE'),
|
|
storageName: 'Land\'s Stride',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 10) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_HIDE_IN_PLAIN_SIGHT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_HIDE_IN_PLAIN_SIGHT'),
|
|
storageName: 'Hide in Plain Sight',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 14) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_VANISH_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_VANISH'),
|
|
storageName: 'Vanish',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 18) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FERAL_SENSES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FERAL_SENSES'),
|
|
storageName: 'Feral Senses',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FOE_SLAYER_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FOE_SLAYER'),
|
|
storageName: 'Foe Slayer',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
rogue(v) {
|
|
if (v.rogue_level) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXPERTISE_ROGUE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXPERTISE'),
|
|
storageName: 'Expertise Rogue',
|
|
});
|
|
traits.set({
|
|
damage: `${Math.ceil(getIntValue(v.rogue_level) / 2)}d6`,
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SNEAK_ATTACK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SNEAK_ATTACK'),
|
|
storageName: 'Sneak Attack',
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_THIEVES_CANT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_THIEVES_CANT'),
|
|
storageName: 'Thieves\' Cant',
|
|
});
|
|
if (v.rogue_level >= 2) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CUNNING_ACTION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CUNNING_ACTION'),
|
|
storageName: 'Cunning Action',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 5) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNCANNY_DODGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNCANNY_DODGE'),
|
|
storageName: 'Uncanny Dodge',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 7) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EVASION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EVASION'),
|
|
storageName: 'Evasion',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 11) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RELIABLE_TALENT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_RELIABLE_TALENT'),
|
|
storageName: 'Reliable Talent',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 14) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BLINDSENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_BLINDSENSE'),
|
|
storageName: 'Blindsense',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 15) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SLIPPERY_MIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SLIPPERY_MIND'),
|
|
storageName: 'Slippery Mind',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 18) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ELUSIVE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ELUSIVE'),
|
|
storageName: 'Elusive',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STROKE_OF_LUCK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STROKE_OF_LUCK'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Stroke of Luck',
|
|
uses_max: 1,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
sorcerer(v) {
|
|
if (v.sorcerer_level) {
|
|
if (v.sorcerer_level >= 2) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SORCERY_POINTS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SORCERY_POINTS'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Sorcery Points',
|
|
uses_max: v.sorcerer_level,
|
|
});
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FLEXIBLE_CASTING_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FLEXIBLE_CASTING'),
|
|
storageName: 'Flexible Casting',
|
|
});
|
|
if (v.sorcerer_level >= 3) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC'),
|
|
storageName: 'Metamagic',
|
|
});
|
|
}
|
|
if (v.sorcerer_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SORCEROUS_RESTORATION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SORCEROUS_RESTORATION'),
|
|
storageName: 'Sorcerous Restoration',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
warlock(v) {
|
|
if (v.warlock_level) {
|
|
let warlockSpellSlots;
|
|
if (v.warlock_level >= 17) {
|
|
warlockSpellSlots = 4;
|
|
} else if (v.warlock_level >= 11) {
|
|
warlockSpellSlots = 3;
|
|
} else if (v.warlock_level >= 2) {
|
|
warlockSpellSlots = 2;
|
|
} else {
|
|
warlockSpellSlots = 1;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_WARLOCK_SPELL_SLOTS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_WARLOCK_SPELL_SLOTS'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Warlock Spell Slots',
|
|
uses_max: warlockSpellSlots,
|
|
});
|
|
|
|
if (v.warlock_level >= 2) {
|
|
let eldritchInvocations;
|
|
if (v.warlock_level >= 17) {
|
|
eldritchInvocations = 8;
|
|
} else if (v.warlock_level >= 15) {
|
|
eldritchInvocations = 7;
|
|
} else if (v.warlock_level >= 12) {
|
|
eldritchInvocations = 6;
|
|
} else if (v.warlock_level >= 9) {
|
|
eldritchInvocations = 5;
|
|
} else if (v.warlock_level >= 7) {
|
|
eldritchInvocations = 4;
|
|
} else if (v.warlock_level >= 5) {
|
|
eldritchInvocations = 3;
|
|
} else {
|
|
eldritchInvocations = 2;
|
|
}
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ELDRITCH_INVOCATIONS_TEXT').replace('NUMBER_OF_INVOCATIONS_KNOWN', eldritchInvocations),
|
|
name: getTranslationByKey('CLASS_FEATURE_ELDRITCH_INVOCATIONS'),
|
|
storageName: 'Eldritch Invocations',
|
|
});
|
|
}
|
|
if (v.warlock_level >= 3) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PACT_BOON_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PACT_BOON'),
|
|
storageName: 'Pact Boon',
|
|
});
|
|
}
|
|
if (v.warlock_level >= 11) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_MYSTIC_ARCANUM_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_MYSTIC_ARCANUM'),
|
|
storageName: 'Mystic Arcanum',
|
|
});
|
|
}
|
|
if (v.warlock_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ELDRITCH_MASTER_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ELDRITCH_MASTER'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Eldritch Master',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
wizard(v) {
|
|
if (v.wizard_level) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ARCANE_RECOVERY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ARCANE_RECOVERY'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Arcane Recovery',
|
|
uses_max: 1,
|
|
});
|
|
if (v.wizard_level >= 18) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SPELL_MASTERY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SPELL_MASTERY'),
|
|
storageName: 'Spell Mastery',
|
|
});
|
|
}
|
|
if (v.wizard_level >= 20) {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SIGNATURE_SPELLS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SIGNATURE_SPELLS'),
|
|
storageName: 'Signature Spells',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
metamagic(v) {
|
|
if (v.careful_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_CAREFUL_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_CAREFUL_SPELL'),
|
|
storageName: 'Careful Spell',
|
|
});
|
|
}
|
|
if (v.distant_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_DISTANT_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_DISTANT_SPELL'),
|
|
storageName: 'Distant Spell',
|
|
});
|
|
}
|
|
if (v.empowered_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EMPOWERED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EMPOWERED_SPELL'),
|
|
storageName: 'Empowered Spell',
|
|
});
|
|
}
|
|
if (v.extended_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EXTENDED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EXTENDED_SPELL'),
|
|
storageName: 'Extended Spell',
|
|
});
|
|
}
|
|
if (v.heightened_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_HEIGHTENED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_HEIGHTENED_SPELL'),
|
|
storageName: 'Heightened Spell',
|
|
});
|
|
}
|
|
if (v.quickened_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_QUICKENED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_QUICKENED_SPELL'),
|
|
storageName: 'Quickened Spell',
|
|
});
|
|
}
|
|
if (v.subtle_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_SUBTLE_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_SUBTLE_SPELL'),
|
|
storageName: 'Subtle Spell',
|
|
});
|
|
}
|
|
if (v.twinned_spell_toggle === '1') {
|
|
traits.set({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_TWINNED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_TWINNED_SPELL'),
|
|
storageName: 'Twinned Spell',
|
|
});
|
|
}
|
|
}
|
|
|
|
set() {
|
|
const collectionArray = ['ac_unarmored_ability', 'jack_of_all_trades_toggle', 'careful_spell_toggle', 'distant_spell_toggle', 'empowered_spell_toggle', 'extended_spell_toggle', 'heightened_spell_toggle', 'quickened_spell_toggle', 'subtle_spell_toggle', 'twinned_spell_toggle'];
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
}
|
|
for (const className of CLASSES) {
|
|
collectionArray.push(`${className}_level`);
|
|
}
|
|
|
|
getSetItems('classFeatures.set', {
|
|
collectionArray,
|
|
callback: (v, finalSetAttrs) => {
|
|
this.extraAttack(v);
|
|
this.barbarian(v, finalSetAttrs);
|
|
this.bard(v, finalSetAttrs);
|
|
this.cleric(v);
|
|
this.druid(v);
|
|
this.fighter(v);
|
|
this.monk(v, finalSetAttrs);
|
|
this.paladin(v);
|
|
this.ranger(v);
|
|
this.rogue(v);
|
|
this.sorcerer(v);
|
|
this.warlock(v);
|
|
this.wizard(v);
|
|
this.metamagic(v);
|
|
},
|
|
});
|
|
}
|
|
}
|