mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
* Updated the background image for the system * Fixed so Weapon/Armor features are added again * Fixed so fear is available as a resource to be deducted by actions (#757) * Changed to use the config labels and src * Updated Weapons * Fixed so the decrease button of simple fear tracker is not visible when not hovered * Fixed so armor preUpdate doesn't fail if no system changes are made * Updated .gitignore and author details (#777) * Add author details and name mapping for chrisryan10 (#773) * Add build to ignore for my linux dev (#775) --------- * Corrected sneak attack active effect (#780) * Fixed a spelling error (#779) * Fix bardic rally showing in damage dialog when it should not (#783) * update spelling (#786) * Translating inventory descriptions (#782) * updated credits for 1.0.1 release (#797) * updated credits for 1.0.1 release * further updated artwork credits * Chagned handlebarhelper rollparsed to be more defensive (#794) * Added missing scene refreshType (#790) * Remove ability use buttons for not owned abilities (#795) * [Fix] PrayerDice Fixed (#799) * Fixed prayer dice, and wheelchair images * Fixed -settings data sources * Dragging features from one adversary to another (#788) * [Fix] Levelup Fixes (#787) * Fixed crash on experience selection. Fixed subclass error on multiclassing * Fixed so multiclasses do not gain the hope feature for the class * Fixed so Class/Subclass features are properly deleted on delevel * Removed automatic deletion of features on delevel when not using levelup auto * Fixed so custom domains can be selected in levelup when multiclassing * Changed so encounter countdowns is a button (#804) * Fixed so that dropping on class/subclass...creates the item on the character (#803) * [BUG] - Importing All Adversaries/Environments (#814) Fixes #774 * Bug/671 reaction roll chat title (#809) * Update Reaction Roll Chat Message Title * Removed console log --------- * Improve Trait tooltip display (#817) Fixes #806 * [BUG] - Combat Tracker d12 logo not found (#812) Fixes #764 * Compendium Browser (#821) * Corrected timbending description localization (#816) * [Fix] Compendium Item (#810) * Corrected Emberwoven Armor * Fixed subclass regression * Fixed so character's with wildcard images don't break beastform (#815) * Fix roll result based duality damage (#822) * Impproved Adversary Sheet Data Display (#751) * Impproved Adversary Sheet Data Display Fixes #604 * FIX: formula lables for attacks and weapons --------- * Fixed so exp increases can be selected normally (#835) * Update localization of rollSelection.hbs (#841) The Fear label lacked a localization path * renamed .md files (#834) * Removed the unintended icons that came from merge conflicts (#838) * Sheet image position (#861) * Remove extra result text from reaction chat message (#860) * Remove extra result text from reaction chat message * Remove log * [Fix] 850 - Downtime Refreshes (#859) * Fixed the filtering of refreshable features * Raised version * [Fix] 691 - CharacterCreation Improvement (#863) * Removed main creation tab. Added equipment to remaining tab * Fixed MixedAncestry name setting * Fixed tab after first creation * Fix/857 generic roll buttons (#866) * Action Macro working again * Remove buttons from generic roll for non-gm * Update sidebar.less Armor Slots label padding, for translations (#872) Equalizes the vertical and horizontal padding on the Armor Slots status label, to allow for more (much needed) room for the upcoming French translation (and others in the future). Doesn't change anything for the display in English. * Hid item attachments for now (#876) * Fixed so effects supposed to use item data use the model directly, since items have no rolldata (#883) * Made sure the beastform user isn't moved onto other duplicate actors. Fixed scrolling text duplication (#882) * Change the critical damage max calculation (#890) * Change the critical damage max calculation to avoid setting the rolled portion to maximum all the time. * Change to the more neater code * Restored package.json --------- * Fix movement (#885) * Changed so companion can level up on its own (#879) * Fix the missing ancestries in the SRD (#888) * Removed the old, now unused companion levelup button (#893) * Fixed so custom downtime moves will display their descriptions in the tooltip (#894) * Made coinflip icons in chat round (#895) * Fixed undefined case (#898) * Fix/877 hope update order (#896) * Action Macro working again * k * Fix Hope gain on Duality Roll * [Feature] Descriptions enhancements (#887) * add style to hover items and add start setting to features be expanded by default * REFACTOR: now prepare description onRender and simply the other methods * add setting to extend description from items and add molilo contacts in system.json --------- * Fixed traits container styling in light-mode (#899) * [BUG] - Movement Button selector not showing current mode (#912) Fixes #910 * Fix/864 chat targeting (#911) * Fix targeting * Fix chat target selection * Uncomment chatDisplay * Removed unessecary arg * [Feature] 648 - Mark Defeated Actors (#914) * Improved death marking styling * Added automation for defeated status * Fixed so the tracker recognises and sets the correct defeated statuses depending on type * Fixed so missing statuses doesn't cause crashes * Increased companion sheet width by 40 pixels * Added missing inheritDoc * Removed fas * FIX: use img instead of icons con statusEffectConfigns (#917) Authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> --------- Co-authored-by: Chris Ryan <73275196+chrisryan10@users.noreply.github.com> Co-authored-by: Chris Ryan <chrisr@blackhole> Co-authored-by: Dapoulp <74197441+Dapoulp@users.noreply.github.com> Co-authored-by: IrkTheImp <41175833+IrkTheImp@users.noreply.github.com> Co-authored-by: CPTN_Cosmo <cptncosmo@gmail.com> Co-authored-by: Josh Q. <jshqntnr13@gmail.com> Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com> Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> Co-authored-by: SunnySunSun <snysun@pm.me> Co-authored-by: Murilo Brito <91566541+moliloo@users.noreply.github.com>
664 lines
29 KiB
JavaScript
664 lines
29 KiB
JavaScript
import { burden } from '../../config/generalConfig.mjs';
|
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
|
import DhLevelData from '../levelData.mjs';
|
|
import BaseDataActor from './base.mjs';
|
|
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
|
import { ActionField } from '../fields/actionField.mjs';
|
|
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
|
|
|
export default class DhCharacter extends BaseDataActor {
|
|
/**@override */
|
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
|
|
|
/**@inheritdoc */
|
|
static get metadata() {
|
|
return foundry.utils.mergeObject(super.metadata, {
|
|
label: 'TYPES.Actor.character',
|
|
type: 'character',
|
|
settingSheet: DHCharacterSettings,
|
|
isNPC: false
|
|
});
|
|
}
|
|
|
|
/**@inheritdoc */
|
|
static defineSchema() {
|
|
const fields = foundry.data.fields;
|
|
|
|
return {
|
|
...super.defineSchema(),
|
|
resources: new fields.SchemaField({
|
|
hitPoints: resourceField(
|
|
0,
|
|
0,
|
|
'DAGGERHEART.GENERAL.HitPoints.plural',
|
|
true,
|
|
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
|
),
|
|
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
|
hope: resourceField(6, 2, 'DAGGERHEART.GENERAL.hope')
|
|
}),
|
|
traits: new fields.SchemaField({
|
|
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
|
strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
|
|
finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'),
|
|
instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'),
|
|
presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'),
|
|
knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name')
|
|
}),
|
|
proficiency: new fields.NumberField({
|
|
initial: 1,
|
|
integer: true,
|
|
label: 'DAGGERHEART.GENERAL.proficiency'
|
|
}),
|
|
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
|
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
|
|
damageThresholds: new fields.SchemaField({
|
|
severe: new fields.NumberField({
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
|
}),
|
|
major: new fields.NumberField({
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
|
})
|
|
}),
|
|
experiences: new fields.TypedObjectField(
|
|
new fields.SchemaField({
|
|
name: new fields.StringField(),
|
|
value: new fields.NumberField({ integer: true, initial: 0 }),
|
|
description: new fields.StringField(),
|
|
core: new fields.BooleanField({ initial: false })
|
|
})
|
|
),
|
|
gold: new fields.SchemaField({
|
|
coins: new fields.NumberField({ initial: 0, integer: true }),
|
|
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
|
bags: new fields.NumberField({ initial: 0, integer: true }),
|
|
chests: new fields.NumberField({ initial: 0, integer: true })
|
|
}),
|
|
scars: new fields.TypedObjectField(
|
|
new fields.SchemaField({
|
|
name: new fields.StringField({}),
|
|
description: new fields.StringField()
|
|
})
|
|
),
|
|
biography: new fields.SchemaField({
|
|
background: new fields.HTMLField(),
|
|
connections: new fields.HTMLField(),
|
|
characteristics: new fields.SchemaField({
|
|
pronouns: new fields.StringField({}),
|
|
age: new fields.StringField({}),
|
|
faith: new fields.StringField({})
|
|
})
|
|
}),
|
|
class: new fields.SchemaField({
|
|
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
|
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
|
}),
|
|
multiclass: new fields.SchemaField({
|
|
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
|
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
|
}),
|
|
attack: new ActionField({
|
|
initial: {
|
|
name: 'Unarmed Attack',
|
|
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
|
|
_id: foundry.utils.randomID(),
|
|
systemPath: 'attack',
|
|
chatDisplay: false,
|
|
type: 'attack',
|
|
range: 'melee',
|
|
target: {
|
|
type: 'any',
|
|
amount: 1
|
|
},
|
|
roll: {
|
|
type: 'attack',
|
|
trait: 'strength'
|
|
},
|
|
damage: {
|
|
parts: [
|
|
{
|
|
type: ['physical'],
|
|
value: {
|
|
custom: {
|
|
enabled: true,
|
|
formula: '@profd4'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}),
|
|
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
|
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
|
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
|
}),
|
|
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
|
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
|
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
|
}),
|
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
|
bonuses: new fields.SchemaField({
|
|
roll: new fields.SchemaField({
|
|
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
|
spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'),
|
|
trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'),
|
|
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
|
|
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'),
|
|
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'),
|
|
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack')
|
|
}),
|
|
damage: new fields.SchemaField({
|
|
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
|
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
|
|
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
|
|
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.secondaryWeapon')
|
|
}),
|
|
healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
|
|
range: new fields.SchemaField({
|
|
weapon: new fields.NumberField({
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Range.weapon'
|
|
}),
|
|
spell: new fields.NumberField({
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Range.spell'
|
|
}),
|
|
other: new fields.NumberField({
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Range.other'
|
|
})
|
|
}),
|
|
rally: new fields.ArrayField(new fields.StringField(), {
|
|
label: 'DAGGERHEART.CLASS.Feature.rallyDice'
|
|
}),
|
|
rest: new fields.SchemaField({
|
|
shortRest: new fields.SchemaField({
|
|
shortMoves: new fields.NumberField({
|
|
required: true,
|
|
integer: true,
|
|
min: 0,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
|
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
|
|
}),
|
|
longMoves: new fields.NumberField({
|
|
required: true,
|
|
integer: true,
|
|
min: 0,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
|
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
|
|
})
|
|
}),
|
|
longRest: new fields.SchemaField({
|
|
shortMoves: new fields.NumberField({
|
|
required: true,
|
|
integer: true,
|
|
min: 0,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
|
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
|
|
}),
|
|
longMoves: new fields.NumberField({
|
|
required: true,
|
|
integer: true,
|
|
min: 0,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
|
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
|
|
})
|
|
})
|
|
}),
|
|
maxLoadout: new fields.NumberField({
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Bonuses.maxLoadout.label'
|
|
})
|
|
}),
|
|
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
|
rules: new fields.SchemaField({
|
|
damageReduction: new fields.SchemaField({
|
|
maxArmorMarked: new fields.SchemaField({
|
|
value: new fields.NumberField({
|
|
required: true,
|
|
integer: true,
|
|
initial: 1,
|
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
|
}),
|
|
stressExtra: new fields.NumberField({
|
|
required: true,
|
|
integer: true,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
|
})
|
|
}),
|
|
stressDamageReduction: new fields.SchemaField({
|
|
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
|
|
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
|
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
|
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
|
}),
|
|
increasePerArmorMark: new fields.NumberField({
|
|
integer: true,
|
|
initial: 1,
|
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
|
}),
|
|
magical: new fields.BooleanField({ initial: false }),
|
|
physical: new fields.BooleanField({ initial: false }),
|
|
thresholdImmunities: new fields.SchemaField({
|
|
minor: new fields.BooleanField({ initial: false })
|
|
}),
|
|
disabledArmor: new fields.BooleanField({ intial: false })
|
|
}),
|
|
attack: new fields.SchemaField({
|
|
damage: new fields.SchemaField({
|
|
diceIndex: new fields.NumberField({
|
|
integer: true,
|
|
min: 0,
|
|
max: 5,
|
|
initial: 0,
|
|
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.label',
|
|
hint: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.hint'
|
|
}),
|
|
bonus: new fields.NumberField({
|
|
required: true,
|
|
initial: 0,
|
|
min: 0,
|
|
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.bonus.label'
|
|
})
|
|
}),
|
|
roll: new fields.SchemaField({
|
|
trait: new fields.StringField({
|
|
required: true,
|
|
choices: CONFIG.DH.ACTOR.abilities,
|
|
nullable: true,
|
|
initial: null,
|
|
label: 'DAGGERHEART.GENERAL.Rules.attack.roll.trait.label'
|
|
})
|
|
})
|
|
}),
|
|
runeWard: new fields.BooleanField({ initial: false }),
|
|
burden: new fields.SchemaField({
|
|
ignore: new fields.BooleanField()
|
|
})
|
|
})
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
get tier() {
|
|
const currentLevel = this.levelData.level.current;
|
|
return currentLevel === 1
|
|
? 1
|
|
: Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find(
|
|
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
|
).tier;
|
|
}
|
|
|
|
get ancestry() {
|
|
return this.parent.items.find(x => x.type === 'ancestry') ?? null;
|
|
}
|
|
|
|
get community() {
|
|
return this.parent.items.find(x => x.type === 'community') ?? null;
|
|
}
|
|
|
|
get features() {
|
|
return this.parent.items.filter(x => x.type === 'feature') ?? [];
|
|
}
|
|
|
|
get companionFeatures() {
|
|
return this.companion ? this.companion.items.filter(x => x.type === 'feature') : [];
|
|
}
|
|
|
|
get needsCharacterSetup() {
|
|
return !(this.class.value || this.class.subclass || this.ancestry || this.community);
|
|
}
|
|
|
|
get spellcastModifierTrait() {
|
|
const subClasses = this.parent.items.filter(x => x.type === 'subclass') ?? [];
|
|
const modifiers = subClasses
|
|
?.map(sc => ({ ...this.traits[sc.system.spellcastingTrait], key: sc.system.spellcastingTrait }))
|
|
.filter(x => x);
|
|
return modifiers.sort((a, b) => a.value - b.value)[0];
|
|
}
|
|
|
|
get spellcastModifier() {
|
|
return this.spellcastModifierTrait?.value ?? 0;
|
|
}
|
|
|
|
get spellcastingModifiers() {
|
|
return {
|
|
main: this.class.subclass?.system?.spellcastingTrait,
|
|
multiclass: this.multiclass.subclass?.system?.spellcastingTrait
|
|
};
|
|
}
|
|
|
|
get domains() {
|
|
const classDomains = this.class.value ? this.class.value.system.domains : [];
|
|
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];
|
|
return [...classDomains, ...multiclassDomains];
|
|
}
|
|
|
|
get domainData() {
|
|
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
|
|
return this.domains.map(key => {
|
|
const domain = allDomainData[key];
|
|
return {
|
|
...domain,
|
|
label: game.i18n.localize(domain.label)
|
|
};
|
|
});
|
|
}
|
|
|
|
get domainCards() {
|
|
const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
|
|
const loadout = domainCards.filter(x => !x.system.inVault);
|
|
const vault = domainCards.filter(x => x.system.inVault);
|
|
|
|
return {
|
|
loadout: loadout,
|
|
vault: vault,
|
|
total: [...loadout, ...vault]
|
|
};
|
|
}
|
|
|
|
get loadoutSlot() {
|
|
const loadoutCount = this.domainCards.loadout?.length ?? 0,
|
|
worldSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxLoadout,
|
|
max = !worldSetting ? null : worldSetting + this.bonuses.maxLoadout;
|
|
|
|
return {
|
|
current: loadoutCount,
|
|
available: !max ? true : Math.max(max - loadoutCount, 0),
|
|
max
|
|
};
|
|
}
|
|
|
|
get armor() {
|
|
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
|
|
}
|
|
|
|
get activeBeastform() {
|
|
return this.parent.effects.find(x => x.type === 'beastform');
|
|
}
|
|
|
|
/**
|
|
* Gets the unarmed attackwhen no primary or secondary weapon is equipped.
|
|
* Returns `null` if either weapon is equipped.
|
|
* If the actor is in beastform, overrides the attack's name and image.
|
|
*
|
|
* @returns {DHAttackAction|null}
|
|
*/
|
|
get usedUnarmed() {
|
|
if (this.primaryWeapon?.system?.equipped || this.secondaryWeapon?.system?.equipped) return null;
|
|
|
|
const attack = foundry.utils.deepClone(this.attack);
|
|
if (this.activeBeastform) {
|
|
attack.name = 'DAGGERHEART.ITEMS.Beastform.attackName';
|
|
attack.img = 'icons/creatures/claws/claw-straight-brown.webp';
|
|
}
|
|
return attack;
|
|
}
|
|
|
|
get sheetLists() {
|
|
const ancestryFeatures = [],
|
|
communityFeatures = [],
|
|
classFeatures = [],
|
|
subclassFeatures = [],
|
|
companionFeatures = [],
|
|
features = [];
|
|
|
|
for (let item of this.parent.items) {
|
|
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
|
ancestryFeatures.push(item);
|
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
|
|
communityFeatures.push(item);
|
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
|
|
classFeatures.push(item);
|
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
|
if (this.class.subclass) {
|
|
const subclassState = this.class.subclass.system.featureState;
|
|
const subclass =
|
|
item.system.identifier === 'multiclass' ? this.multiclass.subclass : this.class.subclass;
|
|
const featureType = subclass
|
|
? (subclass.system.features.find(x => x.item?.uuid === item.uuid)?.type ?? null)
|
|
: null;
|
|
|
|
if (
|
|
featureType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
|
(featureType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
|
|
(featureType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
|
|
) {
|
|
subclassFeatures.push(item);
|
|
}
|
|
}
|
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
|
companionFeatures.push(item);
|
|
} else if (item.type === 'feature' && !item.system.type) {
|
|
features.push(item);
|
|
}
|
|
}
|
|
|
|
return {
|
|
ancestryFeatures: {
|
|
title: `${game.i18n.localize('TYPES.Item.ancestry')} - ${this.ancestry?.name}`,
|
|
type: 'ancestry',
|
|
values: ancestryFeatures
|
|
},
|
|
communityFeatures: {
|
|
title: `${game.i18n.localize('TYPES.Item.community')} - ${this.community?.name}`,
|
|
type: 'community',
|
|
values: communityFeatures
|
|
},
|
|
classFeatures: {
|
|
title: `${game.i18n.localize('TYPES.Item.class')} - ${this.class.value?.name}`,
|
|
type: 'class',
|
|
values: classFeatures
|
|
},
|
|
subclassFeatures: {
|
|
title: `${game.i18n.localize('TYPES.Item.subclass')} - ${this.class.subclass?.name}`,
|
|
type: 'subclass',
|
|
values: subclassFeatures
|
|
},
|
|
companionFeatures: {
|
|
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'),
|
|
type: 'companion',
|
|
values: companionFeatures
|
|
},
|
|
features: { title: game.i18n.localize('DAGGERHEART.GENERAL.features'), type: 'feature', values: features }
|
|
};
|
|
}
|
|
|
|
get primaryWeapon() {
|
|
return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && !x.system.secondary);
|
|
}
|
|
|
|
get secondaryWeapon() {
|
|
return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && x.system.secondary);
|
|
}
|
|
|
|
get getWeaponBurden() {
|
|
return this.primaryWeapon?.system?.burden === burden.twoHanded.value ||
|
|
(this.primaryWeapon && this.secondaryWeapon)
|
|
? burden.twoHanded.value
|
|
: this.primaryWeapon || this.secondaryWeapon
|
|
? burden.oneHanded.value
|
|
: null;
|
|
}
|
|
|
|
get deathMoveViable() {
|
|
return this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
|
}
|
|
|
|
get armorApplicableDamageTypes() {
|
|
return {
|
|
physical: !this.rules.damageReduction.magical,
|
|
magical: !this.rules.damageReduction.physical
|
|
};
|
|
}
|
|
|
|
get basicAttackDamageDice() {
|
|
const diceTypes = Object.keys(CONFIG.DH.GENERAL.diceTypes);
|
|
const attackDiceIndex = Math.max(Math.min(this.rules.attack.damage.diceIndex, 5), 0);
|
|
return diceTypes[attackDiceIndex];
|
|
}
|
|
|
|
static async unequipBeforeEquip(itemToEquip) {
|
|
const primary = this.primaryWeapon,
|
|
secondary = this.secondaryWeapon;
|
|
if (itemToEquip.system.secondary) {
|
|
if (primary && primary.burden === CONFIG.DH.GENERAL.burden.twoHanded.value) {
|
|
await primary.update({ 'system.equipped': false });
|
|
}
|
|
|
|
if (secondary) {
|
|
await secondary.update({ 'system.equipped': false });
|
|
}
|
|
} else {
|
|
if (secondary && itemToEquip.system.burden === CONFIG.DH.GENERAL.burden.twoHanded.value) {
|
|
await secondary.update({ 'system.equipped': false });
|
|
}
|
|
|
|
if (primary) {
|
|
await primary.update({ 'system.equipped': false });
|
|
}
|
|
}
|
|
}
|
|
|
|
prepareBaseData() {
|
|
this.evasion += this.class.value?.system?.evasion ?? 0;
|
|
|
|
const currentLevel = this.levelData.level.current;
|
|
const currentTier =
|
|
currentLevel === 1
|
|
? null
|
|
: Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find(
|
|
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
|
|
).tier;
|
|
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto) {
|
|
for (let levelKey in this.levelData.levelups) {
|
|
const level = this.levelData.levelups[levelKey];
|
|
|
|
this.proficiency += level.achievements.proficiency;
|
|
|
|
for (let selection of level.selections) {
|
|
switch (selection.type) {
|
|
case 'trait':
|
|
selection.data.forEach(data => {
|
|
this.traits[data].value += 1;
|
|
this.traits[data].tierMarked = selection.tier === currentTier;
|
|
});
|
|
break;
|
|
case 'hitPoint':
|
|
this.resources.hitPoints.max += selection.value;
|
|
break;
|
|
case 'stress':
|
|
this.resources.stress.max += selection.value;
|
|
break;
|
|
case 'evasion':
|
|
this.evasion += selection.value;
|
|
break;
|
|
case 'proficiency':
|
|
this.proficiency += selection.value;
|
|
break;
|
|
case 'experience':
|
|
selection.data.forEach(id => {
|
|
const experience = this.experiences[id];
|
|
if (experience) {
|
|
experience.value += selection.value;
|
|
experience.leveledUp = true;
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const armor = this.armor;
|
|
this.armorScore = armor ? armor.system.baseScore : 0;
|
|
this.damageThresholds = {
|
|
major: armor
|
|
? armor.system.baseThresholds.major + this.levelData.level.current
|
|
: this.levelData.level.current,
|
|
severe: armor
|
|
? armor.system.baseThresholds.severe + this.levelData.level.current
|
|
: this.levelData.level.current * 2
|
|
};
|
|
this.resources.hope.max -= Object.keys(this.scars).length;
|
|
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
|
}
|
|
|
|
prepareDerivedData() {
|
|
let baseHope = this.resources.hope.value;
|
|
if (this.companion) {
|
|
for (let levelKey in this.companion.system.levelData.levelups) {
|
|
const level = this.companion.system.levelData.levelups[levelKey];
|
|
for (let selection of level.selections) {
|
|
switch (selection.type) {
|
|
case 'hope':
|
|
this.resources.hope.max += selection.value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
|
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
|
|
|
this.resources.armor = {
|
|
value: this.armor?.system?.marks?.value ?? 0,
|
|
max: this.armorScore,
|
|
isReversed: true
|
|
};
|
|
|
|
this.attack.damage.parts[0].value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
|
}
|
|
|
|
getRollData() {
|
|
const data = super.getRollData();
|
|
|
|
return {
|
|
...data,
|
|
basicAttackDamageDice: this.basicAttackDamageDice,
|
|
tier: this.tier,
|
|
level: this.levelData.level.current
|
|
};
|
|
}
|
|
|
|
async _preUpdate(changes, options, userId) {
|
|
const allowed = await super._preUpdate(changes, options, userId);
|
|
if (allowed === false) return;
|
|
|
|
/* The first two experiences are always marked as core */
|
|
if (changes.system?.experiences && Object.keys(this.experiences).length < 2) {
|
|
const experiences = new Set(Object.keys(this.experiences));
|
|
const changeExperiences = new Set(Object.keys(changes.system.experiences));
|
|
const newExperiences = Array.from(changeExperiences.difference(experiences));
|
|
|
|
for (var i = 0; i < Math.min(newExperiences.length, 2 - experiences.size); i++) {
|
|
const experience = newExperiences[i];
|
|
changes.system.experiences[experience].core = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
async _preDelete() {
|
|
if (this.companion) {
|
|
this.companion.updateLevel(1);
|
|
}
|
|
}
|
|
}
|