Merge branch 'Foundryborne:main' into main

This commit is contained in:
Sasmira 2025-08-14 16:32:51 +02:00 committed by GitHub
commit e4db746e44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
210 changed files with 1453 additions and 902 deletions

View file

View file

@ -189,7 +189,11 @@
"title": "Multiclass Subclass", "title": "Multiclass Subclass",
"text": "Do you want to add this subclass as your multiclass subclass?" "text": "Do you want to add this subclass as your multiclass subclass?"
}, },
"cannotRemoveCoreExperience": "You are using Levelup Auto. You cannot remove an experience given to you by the rule progression." "cannotRemoveCoreExperience": "You are using Levelup Auto. You cannot remove an experience given to you by the rule progression.",
"companionLevelup": {
"confirmTitle": "Companion Levelup",
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
}
}, },
"Companion": { "Companion": {
"FIELDS": { "FIELDS": {
@ -233,13 +237,14 @@
}, },
"APPLICATIONS": { "APPLICATIONS": {
"CharacterCreation": { "CharacterCreation": {
"setupTabs": { "tabs": {
"ancestry": "Ancestry", "ancestry": "Ancestry",
"community": "Community", "community": "Community",
"class": "Class", "class": "Class",
"experience": "Experience", "experience": "Experience",
"traits": "Traits", "traits": "Traits",
"domainCards": "Domain Cards" "domainCards": "Domain Cards",
"equipment": "Equipment"
}, },
"ancestryNamePlaceholder": "Your ancestry's name", "ancestryNamePlaceholder": "Your ancestry's name",
"buttonTitle": "Character Setup", "buttonTitle": "Character Setup",
@ -816,6 +821,10 @@
"name": "Dead", "name": "Dead",
"description": "The character is dead" "description": "The character is dead"
}, },
"defeated": {
"name": "Defeated",
"description": "This adversary is defeated."
},
"hidden": { "hidden": {
"name": "Hidden", "name": "Hidden",
"description": "While Hidden, attacks cannot be made directly targeting them nd any rolls against them are at disadvantage.\nWhen a Hidden creature moves or attacks, they are no longer Hidden. However, if a creature is Hidden when they begin making an attack, the roll has advantage; the Hidden condition isnt cleared until after the attack is resolved." "description": "While Hidden, attacks cannot be made directly targeting them nd any rolls against them are at disadvantage.\nWhen a Hidden creature moves or attacks, they are no longer Hidden. However, if a creature is Hidden when they begin making an attack, the roll has advantage; the Hidden condition isnt cleared until after the attack is resolved."
@ -1865,7 +1874,8 @@
"tier3": "Tier 3", "tier3": "Tier 3",
"tier4": "tier 4", "tier4": "tier 4",
"domains": "Domains", "domains": "Domains",
"downtime": "Downtime" "downtime": "Downtime",
"rules": "Rules"
}, },
"Tiers": { "Tiers": {
"singular": "Tier", "singular": "Tier",
@ -1934,6 +1944,7 @@
"itemResource": "Item Resource", "itemResource": "Item Resource",
"label": "Label", "label": "Label",
"level": "Level", "level": "Level",
"levelShort": "Lv",
"levelUp": "Level Up", "levelUp": "Level Up",
"loadout": "Loadout", "loadout": "Loadout",
"max": "Max", "max": "Max",
@ -2081,7 +2092,12 @@
"FIELDS": { "FIELDS": {
"displayFear": { "label": "Fear Display" }, "displayFear": { "label": "Fear Display" },
"dualityColorScheme": { "label": "Chat Style" }, "dualityColorScheme": { "label": "Chat Style" },
"showGenericStatusEffects": { "label": "Show Foundry Status Effects" } "showGenericStatusEffects": { "label": "Show Foundry Status Effects" },
"expandedTitle": "Auto-expand Descriptions",
"extendCharacterDescriptions": { "label": "Characters" },
"extendAdversaryDescriptions": { "label": "Adversaries" },
"extendEnvironmentDescriptions": { "label": "Environments" },
"extendItemDescriptions": { "label": "Items" }
}, },
"fearDisplay": { "fearDisplay": {
"token": "Tokens", "token": "Tokens",
@ -2099,6 +2115,13 @@
"label": "Damage Reduction Rules Default", "label": "Damage Reduction Rules Default",
"hint": "Wether using armor and reductions has rules on by default" "hint": "Wether using armor and reductions has rules on by default"
}, },
"defeated": {
"enabled": { "label": "Enabled" },
"overlay": { "label": "Overlay Effect" },
"characterDefault": { "label": "Character Default Defeated Status" },
"adversaryDefault": { "label": "Adversary Default Defeated Status" },
"companionDefault": { "label": "Companion Default Defeated Status" }
},
"hopeFear": { "hopeFear": {
"label": "Hope & Fear", "label": "Hope & Fear",
"gm": { "label": "GM" }, "gm": { "label": "GM" },
@ -2130,6 +2153,9 @@
"label": "Players Can Manually Edit Character Settings", "label": "Players Can Manually Edit Character Settings",
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules." "hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
} }
},
"defeated": {
"title": "Defeated Handling"
} }
}, },
"Homebrew": { "Homebrew": {
@ -2369,7 +2395,8 @@
"rulesOn": "Rules On", "rulesOn": "Rules On",
"rulesOff": "Rules Off", "rulesOff": "Rules Off",
"remainingUses": "Uses refresh on {type}", "remainingUses": "Uses refresh on {type}",
"rightClickExtand": "Right-Click to extand" "rightClickExtand": "Right-Click to extand",
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up."
} }
} }
} }

View file

@ -13,7 +13,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.setup = { this.setup = {
traits: this.character.system.traits, traits: this.character.system.traits,
ancestryName: '', ancestryName: {
primary: '',
secondary: ''
},
mixedAncestry: false, mixedAncestry: false,
primaryAncestry: this.character.system.ancestry ?? {}, primaryAncestry: this.character.system.ancestry ?? {},
secondaryAncestry: {}, secondaryAncestry: {},
@ -83,131 +86,70 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static PARTS = { static PARTS = {
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' }, tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' }, ancestry: { template: 'systems/daggerheart/templates/characterCreation/tabs/ancestry.hbs' },
ancestry: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/ancestry.hbs' }, community: { template: 'systems/daggerheart/templates/characterCreation/tabs/community.hbs' },
community: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/community.hbs' }, class: { template: 'systems/daggerheart/templates/characterCreation/tabs/class.hbs' },
class: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/class.hbs' }, traits: { template: 'systems/daggerheart/templates/characterCreation/tabs/traits.hbs' },
traits: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/traits.hbs' }, experience: { template: 'systems/daggerheart/templates/characterCreation/tabs/experience.hbs' },
experience: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/experience.hbs' }, domainCards: { template: 'systems/daggerheart/templates/characterCreation/tabs/domainCards.hbs' },
domainCards: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/domainCards.hbs' }, equipment: { template: 'systems/daggerheart/templates/characterCreation/equipment.hbs' },
equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' }, // story: { template: 'systems/daggerheart/templates/characterCreation/story.hbs' },
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' },
footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' } footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' }
}; };
static TABS = { static TABS = {
setup: {
active: true,
cssClass: '',
group: 'primary',
id: 'setup',
label: 'DAGGERHEART.GENERAL.Tabs.setup'
},
equipment: {
active: false,
cssClass: '',
group: 'primary',
id: 'equipment',
label: 'DAGGERHEART.GENERAL.Tabs.equipment',
optional: true
}
// story: {
// active: false,
// cssClass: '',
// group: 'primary',
// id: 'story',
// label: 'DAGGERHEART.GENERAL.Tabs.story',
// optional: true
// }
};
static SETUPTABS = {
ancestry: { ancestry: {
active: true, active: true,
cssClass: '', cssClass: '',
group: 'setup', group: 'setup',
id: 'ancestry', id: 'ancestry',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.ancestry' label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.ancestry'
}, },
community: { community: {
active: false, active: false,
cssClass: '', cssClass: '',
group: 'setup', group: 'setup',
id: 'community', id: 'community',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.community' label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.community'
}, },
class: { class: {
active: false, active: false,
cssClass: '', cssClass: '',
group: 'setup', group: 'setup',
id: 'class', id: 'class',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.class' label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.class'
}, },
traits: { traits: {
active: false, active: false,
cssClass: '', cssClass: '',
group: 'setup', group: 'setup',
id: 'traits', id: 'traits',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.traits' label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.traits'
}, },
experience: { experience: {
active: false, active: false,
cssClass: '', cssClass: '',
group: 'setup', group: 'setup',
id: 'experience', id: 'experience',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.experience' label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.experience'
}, },
domainCards: { domainCards: {
active: false, active: false,
cssClass: '', cssClass: '',
group: 'setup', group: 'setup',
id: 'domainCards', id: 'domainCards',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.domainCards' label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.domainCards'
},
equipment: {
active: false,
cssClass: '',
group: 'setup',
id: 'equipment',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.equipment'
} }
}; };
_getTabs(tabs) { _getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
switch (v.id) {
case 'setup':
const ancestryFinished = this.setup.primaryAncestry.uuid;
const communityFinished = this.setup.community.uuid;
const classFinished = this.setup.class.uuid && this.setup.subclass.uuid;
const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null);
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.name);
const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid);
v.finished =
ancestryFinished &&
communityFinished &&
classFinished &&
traitsFinished &&
experiencesFinished &&
domainCardsFinished;
break;
case 'equipment':
const armorFinished = this.equipment.armor?.uuid;
const primaryFinished = this.equipment.primaryWeapon?.uuid;
const secondaryFinished =
this.equipment.secondaryWeapon?.uuid ||
(primaryFinished && this.equipment.primaryWeapon.system.burden == burden.twoHanded.value);
const choiceAFinished = this.equipment.inventory.choiceA?.uuid;
const choiceBFinished = this.equipment.inventory.choiceB?.uuid;
v.finished =
armorFinished && primaryFinished && secondaryFinished && choiceAFinished && choiceBFinished;
}
}
tabs.equipment.cssClass = tabs.setup.finished ? tabs.equipment.cssClass : 'disabled';
// tabs.story.cssClass = tabs.setup.finished ? tabs.story.cssClass : 'disabled';
return tabs;
}
_getSetupTabs(tabs) {
for (const v of Object.values(tabs)) { for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] v.active = this.tabGroups[v.group]
? this.tabGroups[v.group] === v.id ? this.tabGroups[v.group] === v.id
@ -232,37 +174,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
case 'domainCards': case 'domainCards':
v.disabled = this.setup.visibility < 6; v.disabled = this.setup.visibility < 6;
break; break;
case 'equipment':
v.disabled = this.setup.visibility < 7;
break;
} }
} }
return tabs; return tabs;
} }
changeTab(tab, group, options) {
super.changeTab(tab, group, options);
if (group === 'primary') {
for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) {
marker.classList.add('active');
} else {
marker.classList.remove('active');
}
}
if (tab === 'equipment') {
this.tabGroups.setup = null;
this.element.querySelector('section[data-group="setup"].active')?.classList?.remove?.('active');
} else {
this.tabGroups.setup = 'domainCards';
this.element
.querySelector('section[data-group="setup"][data-tab="domainCards"]')
?.classList?.add?.('active');
}
}
}
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
@ -274,44 +194,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
}); });
} }
async _preFirstRender(_context, _options) {
this.tabGroups.primary = 'setup';
this.tabGroups.setup = 'ancestry';
}
async _prepareContext(_options) { async _prepareContext(_options) {
this.tabGroups.setup = this.tabGroups.setup ?? 'ancestry';
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.tabs = this._getTabs(this.constructor.TABS); context.tabs = this._getTabs(this.constructor.TABS);
return context;
}
async _preparePartContext(partId, context) {
switch (partId) {
case 'footer':
context.isLastTab = this.tabGroups.setup === 'domainCards' || this.tabGroups.primary !== 'setup';
switch (this.tabGroups.setup) {
case null:
case 'ancestry':
context.nextDisabled = this.setup.visibility === 1;
break;
case 'community':
context.nextDisabled = this.setup.visibility === 2;
break;
case 'class':
context.nextDisabled = this.setup.visibility === 3;
break;
case 'traits':
context.nextDisabled = this.setup.visibility === 4;
break;
case 'experience':
context.nextDisabled = this.setup.visibility === 5;
break;
}
break;
case 'setup':
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
const availableTraitModifiers = game.settings const availableTraitModifiers = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew) .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait })); .traitArray.map(trait => ({ key: trait, name: trait }));
@ -353,7 +240,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
}; };
context.mixedAncestry = Number(this.setup.mixedAncestry); context.mixedAncestry = Number(this.setup.mixedAncestry);
context.ancestryName = this.setup.ancestryName;
const { primary, secondary, overwrite } = this.setup.ancestryName;
context.ancestryName = overwrite ?? (primary && secondary ? `${primary}/${secondary}` : primary);
context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' }; context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' };
context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' }; context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' };
context.community = { ...this.setup.community, compendium: 'communities' }; context.community = { ...this.setup.community, compendium: 'communities' };
@ -370,6 +259,36 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
}, {}); }, {});
context.visibility = this.setup.visibility; context.visibility = this.setup.visibility;
return context;
}
async _preparePartContext(partId, context) {
switch (partId) {
case 'footer':
context.isLastTab = this.tabGroups.setup === 'equipment';
switch (this.tabGroups.setup) {
case null:
case 'ancestry':
context.nextDisabled = this.setup.visibility === 1;
break;
case 'community':
context.nextDisabled = this.setup.visibility === 2;
break;
case 'class':
context.nextDisabled = this.setup.visibility === 3;
break;
case 'traits':
context.nextDisabled = this.setup.visibility === 4;
break;
case 'experience':
context.nextDisabled = this.setup.visibility === 5;
break;
case 'domainCards':
context.nextDisabled = this.setup.visibility === 6;
break;
}
break; break;
case 'equipment': case 'equipment':
const suggestions = await this.getEquipmentSuggestions( const suggestions = await this.getEquipmentSuggestions(
@ -438,8 +357,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
getUpdateVisibility() { getUpdateVisibility() {
switch (this.setup.visibility) { switch (this.setup.visibility) {
case 7:
return 7;
case 6: case 6:
return 6; return Object.values(this.setup.domainCards).every(x => x.uuid) ? 7 : 6;
case 5: case 5:
return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5; return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
case 4: case 4:
@ -505,7 +426,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
const presets = { const presets = {
compendium: 'daggerheart', compendium: 'daggerheart',
folder: equipment.includes(type) ? "equipments" : type, folder: equipment.includes(type) ? 'equipments' : type,
render: { render: {
noFolder: true noFolder: true
} }
@ -565,6 +486,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
case 6: case 6:
this.tabGroups.setup = 'domainCards'; this.tabGroups.setup = 'domainCards';
break; break;
case 7:
this.tabGroups.setup = 'equipment';
break;
} }
this.render(); this.render();
@ -576,9 +500,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
? this.setup.secondaryAncestry.system.secondaryFeature ? this.setup.secondaryAncestry.system.secondaryFeature
: this.setup.primaryAncestry.system.secondaryFeature; : this.setup.primaryAncestry.system.secondaryFeature;
const { primary, secondary, overwrite } = this.setup.ancestryName;
const ancestry = { const ancestry = {
...this.setup.primaryAncestry, ...this.setup.primaryAncestry,
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name, name: overwrite ?? (primary && secondary ? `${primary}/${secondary}` : primary),
system: { system: {
...this.setup.primaryAncestry.system, ...this.setup.primaryAncestry.system,
features: [ features: [
@ -650,13 +575,14 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid); const item = await foundry.utils.fromUuid(data.uuid);
if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) { if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
this.setup.ancestryName = item.name; this.setup.ancestryName.primary = item.name;
this.setup.primaryAncestry = { this.setup.primaryAncestry = {
...item, ...item,
effects: Array.from(item.effects).map(x => x.toObject()), effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid uuid: item.uuid
}; };
} else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) { } else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) {
this.setup.ancestryName.secondary = item.name;
this.setup.secondaryAncestry = { this.setup.secondaryAncestry = {
...item, ...item,
effects: Array.from(item.effects).map(x => x.toObject()), effects: Array.from(item.effects).map(x => x.toObject()),

View file

@ -95,8 +95,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
if (x.system.actions) { if (x.system.actions) {
const recoverable = x.system.actions.reduce((acc, action) => { const recoverable = x.system.actions.reduce((acc, action) => {
if ( if (
(action.uses.recovery && (action.uses.recovery === 'longRest') === !this.shortrest) || action.uses.recovery &&
action.uses.recovery === 'shortRest' ((action.uses.recovery === 'longRest' && !this.shortrest) ||
action.uses.recovery === 'shortRest')
) { ) {
acc.push({ acc.push({
title: x.name, title: x.name,

View file

@ -49,12 +49,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
const experienceIncreases = Object.values(advancementChoices.experience ?? {}); const experienceIncreases = Object.values(advancementChoices.experience ?? {});
const experienceIncreaseValues = experienceIncreases const experienceIncreaseValues = experienceIncreases
.filter(exp => exp.data.length > 0) .filter(exp => exp.data.length > 0)
.flatMap(exp => .flatMap(exp => exp.data);
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);
context.experienceIncreases = { context.experienceIncreases = {
values: experienceIncreaseValues, values: experienceIncreaseValues,
active: experienceIncreases.length > 0, active: experienceIncreases.length > 0,

View file

@ -37,12 +37,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
const experienceIncreases = Object.values(advancementChoices.experience ?? {}); const experienceIncreases = Object.values(advancementChoices.experience ?? {});
const experienceIncreaseValues = experienceIncreases const experienceIncreaseValues = experienceIncreases
.filter(exp => exp.data.length > 0) .filter(exp => exp.data.length > 0)
.flatMap(exp => .flatMap(exp => exp.data);
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);
context.experienceIncreases = { context.experienceIncreases = {
values: experienceIncreaseValues, values: experienceIncreaseValues,
active: experienceIncreases.length > 0, active: experienceIncreases.length > 0,
@ -72,6 +67,28 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
const levelKeys = Object.keys(this.levelup.levels); const levelKeys = Object.keys(this.levelup.levels);
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice; const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
const actorRange = this.actor.system.attack.range; const actorRange = this.actor.system.attack.range;
let achievementExperiences = [];
for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey];
if (Number(levelKey) < this.levelup.startLevel) continue;
achievementExperiences = level.achievements.experiences
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
if (experience.name) acc.push(experience);
return acc;
}, [])
: [];
}
context.achievements = {
experiences: {
values: achievementExperiences,
shown: achievementExperiences.length > 0
}
};
context.achievements = context.achievements.experiences.shown ? context.achievements : undefined;
const advancement = {}; const advancement = {};
for (var levelKey of levelKeys) { for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey]; const level = this.levelup.levels[levelKey];

View file

@ -31,8 +31,19 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
}; };
static PARTS = { static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
};
/** @inheritdoc */
static TABS = {
main: { main: {
template: 'systems/daggerheart/templates/settings/automation-settings.hbs' tabs: [{ id: 'general' }, { id: 'rules' }],
initial: 'general',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
}; };

View file

@ -1,5 +1,4 @@
import { GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs'; import { GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
import DhCompanionlevelUp from '../levelup/companionLevelup.mjs';
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -11,8 +10,7 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
position: { width: 455, height: 'auto' }, position: { width: 455, height: 'auto' },
actions: { actions: {
addExperience: DHCompanionSettings.#addExperience, addExperience: DHCompanionSettings.#addExperience,
removeExperience: DHCompanionSettings.#removeExperience, removeExperience: DHCompanionSettings.#removeExperience
levelUp: DHCompanionSettings.#levelUp
} }
}; };
@ -121,12 +119,4 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null }); await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
} }
/**
* Opens the companion level-up dialog for the associated actor.
* @type {ApplicationClickAction}
*/
static async #levelUp() {
new DhCompanionlevelUp(this.actor).render({ force: true });
}
} }

View file

@ -639,14 +639,20 @@ export default class CharacterSheet extends DHBaseActorSheet {
}) })
}); });
setTimeout(() => {
this.consumeResource(result?.costs); this.consumeResource(result?.costs);
}, 50);
} }
// Remove when Action Refactor part #2 done
async consumeResource(costs) { async consumeResource(costs) {
if (!costs?.length) return; if (!costs?.length) return;
const usefulResources = foundry.utils.deepClone(this.actor.system.resources); const usefulResources = {
...foundry.utils.deepClone(this.actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => { const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
const resource = usefulResources[c.key]; const resource = usefulResources[c.key];
return { return {

View file

@ -1,3 +1,4 @@
import DhCompanionLevelUp from '../../levelup/companionLevelup.mjs';
import DHBaseActorSheet from '../api/base-actor.mjs'; import DHBaseActorSheet from '../api/base-actor.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -5,8 +6,10 @@ import DHBaseActorSheet from '../api/base-actor.mjs';
export default class DhCompanionSheet extends DHBaseActorSheet { export default class DhCompanionSheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['actor', 'companion'], classes: ['actor', 'companion'],
position: { width: 300 }, position: { width: 340 },
actions: {} actions: {
levelManagement: DhCompanionSheet.#levelManagement
}
}; };
static PARTS = { static PARTS = {
@ -25,4 +28,25 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
labelPrefix: 'DAGGERHEART.GENERAL.Tabs' labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
}; };
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
this.element
.querySelector('.level-value')
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Opens the companions level management window.
* @type {ApplicationClickAction}
*/
static #levelManagement() {
new DhCompanionLevelUp(this.document).render({ force: true });
}
} }

View file

@ -2,6 +2,23 @@ const { HandlebarsApplicationMixin } = foundry.applications.api;
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs'; import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
import { ItemBrowser } from '../../ui/itemBrowser.mjs'; import { ItemBrowser } from '../../ui/itemBrowser.mjs';
const typeSettingsMap = {
character: 'extendCharacterDescriptions',
adversary: 'extendAdversaryDescriptions',
environment: 'extendEnvironmentDescriptions',
ancestry: 'extendItemDescriptions',
community: 'extendItemDescriptions',
class: 'extendItemDescriptions',
subclass: 'extendItemDescriptions',
feature: 'extendItemDescriptions',
domainCard: 'extendItemDescriptions',
loot: 'extendItemDescriptions',
consumable: 'extendItemDescriptions',
weapon: 'extendItemDescriptions',
armor: 'extendItemDescriptions',
beastform: 'extendItemDescriptions'
};
/** /**
* @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction
*/ */
@ -137,6 +154,8 @@ export default function DHApplicationMixin(Base) {
docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this)); docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this));
if (!!this.options.contextMenus.length) this._createContextMenus(); if (!!this.options.contextMenus.length) this._createContextMenus();
this.#autoExtendDescriptions(context);
} }
/** @inheritDoc */ /** @inheritDoc */
@ -149,6 +168,7 @@ export default function DHApplicationMixin(Base) {
async _onRender(context, options) { async _onRender(context, options) {
await super._onRender(context, options); await super._onRender(context, options);
this._createTagifyElements(this.options.tagifyConfigs); this._createTagifyElements(this.options.tagifyConfigs);
await this.#prepareInventoryDescription(context);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -162,13 +182,7 @@ export default function DHApplicationMixin(Base) {
const { actionId, itemUuid } = el.parentElement.dataset; const { actionId, itemUuid } = el.parentElement.dataset;
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`; const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
const newExtensible = newElement.querySelector(selector); const newExtensible = newElement.querySelector(selector);
newExtensible?.classList.add('extended');
if (!newExtensible) continue;
newExtensible.classList.add('extended');
const descriptionElement = newExtensible.querySelector('.invetory-description');
if (descriptionElement) {
this.#prepareInventoryDescription(newExtensible, descriptionElement);
}
} }
} }
@ -395,6 +409,7 @@ export default function DHApplicationMixin(Base) {
context.source = this.document; context.source = this.document;
context.fields = this.document.schema.fields; context.fields = this.document.schema.fields;
context.systemFields = this.document.system.schema.fields; context.systemFields = this.document.system.schema.fields;
context.settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
return context; return context;
} }
@ -404,24 +419,28 @@ export default function DHApplicationMixin(Base) {
/** /**
* Prepares and enriches an inventory item or action description for display. * Prepares and enriches an inventory item or action description for display.
* @param {HTMLElement} extensibleElement - The parent element containing the description.
* @param {HTMLElement} descriptionElement - The element where the enriched description will be rendered.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async #prepareInventoryDescription(extensibleElement, descriptionElement) { async #prepareInventoryDescription(context) {
const parent = extensibleElement.closest('[data-item-uuid], [data-action-id]'); // Get all inventory item elements with a data-item-uuid attribute
const { actionId, itemUuid } = parent?.dataset || {}; const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]');
if (!actionId && !itemUuid) return; for (const el of inventoryItems) {
// Get the doc uuid from the element
const { itemUuid } = el?.dataset || {};
if (!itemUuid) continue;
const doc = itemUuid //get doc by uuid
? await getDocFromElement(extensibleElement) const doc = await fromUuid(itemUuid);
: this.document.system.attack?.id === actionId
? this.document.system.attack
: this.document.system.actions?.get(actionId);
if (!doc) return;
//get inventory-item description element
const descriptionElement = el.querySelector('.invetory-description');
if (!doc || !descriptionElement) continue;
// localize the description (idk if it's still necessary)
const description = game.i18n.localize(doc.system?.description ?? doc.description); const description = game.i18n.localize(doc.system?.description ?? doc.description);
const isAction = !!actionId;
// Enrich the description and attach it;
const isAction = doc.documentName === 'Action';
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML( descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description, description,
{ {
@ -431,14 +450,45 @@ export default function DHApplicationMixin(Base) {
} }
); );
} }
}
/* -------------------------------------------- */
/* Extend Descriptions by Settings */
/* -------------------------------------------- */
/**
* Extend inventory description when enabled in settings.
* @returns {Promise<void>}
*/
async #autoExtendDescriptions(context) {
const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]');
for (const el of inventoryItems) {
// Get the doc uuid from the element
const { itemUuid } = el?.dataset || {};
if (!itemUuid) continue;
//get doc by uuid
const doc = await fromUuid(itemUuid);
//check the type of the document
const actorType =
doc?.type === 'adversary' && context.document?.type === 'environment'
? typeSettingsMap[doc?.type]
: doc.actor?.type;
// If the actor type is defined and the setting is enabled, extend the description
if (typeSettingsMap[actorType]) {
const settingKey = typeSettingsMap[actorType];
if (context.settings[settingKey]) this.#activeExtended(el);
}
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Application Clicks Actions */ /* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */
static async #addNewItem(event, target) { static async #addNewItem(event, target) {
const { type } = target.dataset;
const createChoice = await foundry.applications.api.DialogV2.wait({ const createChoice = await foundry.applications.api.DialogV2.wait({
classes: ['dh-style', 'two-big-buttons'], classes: ['dh-style', 'two-big-buttons'],
buttons: [ buttons: [
@ -606,10 +656,12 @@ export default function DHApplicationMixin(Base) {
static async #toggleExtended(_, target) { static async #toggleExtended(_, target) {
const container = target.closest('.inventory-item'); const container = target.closest('.inventory-item');
const extensible = container?.querySelector('.extensible'); const extensible = container?.querySelector('.extensible');
const t = extensible?.classList.toggle('extended'); extensible?.classList.toggle('extended');
}
const descriptionElement = extensible?.querySelector('.invetory-description'); async #activeExtended(element) {
if (t && !!descriptionElement) await this.#prepareInventoryDescription(extensible, descriptionElement); const extensible = element?.querySelector('.extensible');
extensible?.classList.add('extended');
} }
} }

View file

@ -24,7 +24,7 @@ export default function ItemAttachmentSheet(Base) {
...super.TABS, ...super.TABS,
primary: { primary: {
...super.TABS?.primary, ...super.TABS?.primary,
tabs: [...(super.TABS?.primary?.tabs || []), { id: 'attachments' }], tabs: [...(super.TABS?.primary?.tabs || []) /*{ id: 'attachments' }*/], // Disabled until fixed
initial: super.TABS?.primary?.initial || 'description', initial: super.TABS?.primary?.initial || 'description',
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs' labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
} }

View file

@ -43,11 +43,6 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
}); });
} }
async _prepareTurnContext(combat, combatant, index) {
const turn = await super._prepareTurnContext(combat, combatant, index);
return { ...turn, isNPC: combatant.isNPC, system: combatant.system.toObject() };
}
_getCombatContextOptions() { _getCombatContextOptions() {
return [ return [
{ {
@ -65,6 +60,57 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
]; ];
} }
getDefeatedId(combatant) {
if (!combatant.actor) return CONFIG.specialStatusEffects.DEFEATED;
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
return settings[`${combatant.actor.type}Default`];
}
/** @inheritdoc */
async _onToggleDefeatedStatus(combatant) {
const isDefeated = !combatant.isDefeated;
await combatant.update({ defeated: isDefeated });
await combatant.actor?.toggleStatusEffect(this.getDefeatedId(combatant), { overlay: true, active: isDefeated });
}
/** @inheritdoc */
async _prepareTurnContext(combat, combatant, index) {
const { id, name, isOwner, isDefeated, hidden, initiative, permission } = combatant;
const resource = permission >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER ? combatant.resource : null;
const hasDecimals = Number.isFinite(initiative) && !Number.isInteger(initiative);
const turn = {
hasDecimals,
hidden,
id,
isDefeated,
initiative,
isOwner,
name,
resource,
active: index === combat.turn,
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
img: await this._getCombatantThumbnail(combatant)
};
turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin(
' '
);
const defeatedId = this.getDefeatedId(combatant);
const effects = [];
for (const effect of combatant.actor?.temporaryEffects ?? []) {
if (effect.statuses.has(defeatedId)) turn.isDefeated = true;
else if (effect.img) effects.push({ img: effect.img, name: effect.name });
}
turn.effects = {
icons: effects,
tooltip: this._formatEffectsTooltip(effects)
};
return { ...turn, isNPC: combatant.isNPC, system: combatant.system.toObject() };
}
async setCombatantSpotlight(combatantId) { async setCombatantSpotlight(combatantId) {
const update = { const update = {
system: { system: {

View file

@ -15,11 +15,12 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
acc.push(effect); acc.push(effect);
const currentStatusActiveEffects = acc.filter( const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first()).name) x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
); );
for (var status of effect.statuses) { for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) { if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
const statusData = statusMap.get(status); const statusData = statusMap.get(status);
if (statusData) {
acc.push({ acc.push({
name: game.i18n.localize(statusData.name), name: game.i18n.localize(statusData.name),
statuses: [status], statuses: [status],
@ -28,6 +29,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
}); });
} }
} }
}
return acc; return acc;
}, []); }, []);

View file

@ -164,39 +164,49 @@ export const healingTypes = {
} }
}; };
export const conditions = { export const defeatedConditions = {
vulnerable: { defeated: {
id: 'vulnerable', id: 'defeated',
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name', name: 'DAGGERHEART.CONFIG.Condition.defeated.name',
icon: 'icons/magic/control/silhouette-fall-slip-prone.webp', img: 'icons/magic/control/fear-fright-mask-orange.webp',
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description' description: 'DAGGERHEART.CONFIG.Condition.defeated.description'
},
hidden: {
id: 'hidden',
name: 'DAGGERHEART.CONFIG.Condition.hidden.name',
icon: 'icons/magic/perception/silhouette-stealth-shadow.webp',
description: 'DAGGERHEART.CONFIG.Condition.hidden.description'
},
restrained: {
id: 'restrained',
name: 'DAGGERHEART.CONFIG.Condition.restrained.name',
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
}, },
unconscious: { unconscious: {
id: 'unconscious', id: 'unconscious',
name: 'DAGGERHEART.CONFIG.Condition.unconscious.name', name: 'DAGGERHEART.CONFIG.Condition.unconscious.name',
icon: 'icons/magic/control/sleep-bubble-purple.webp', img: 'icons/magic/control/sleep-bubble-purple.webp',
description: 'DAGGERHEART.CONFIG.Condition.unconscious.description' description: 'DAGGERHEART.CONFIG.Condition.unconscious.description'
}, },
dead: { dead: {
id: 'dead', id: 'dead',
name: 'DAGGERHEART.CONFIG.Condition.dead.name', name: 'DAGGERHEART.CONFIG.Condition.dead.name',
icon: 'icons/magic/death/grave-tombstone-glow-teal.webp', img: 'icons/magic/death/grave-tombstone-glow-teal.webp',
description: 'DAGGERHEART.CONFIG.Condition.dead.description' description: 'DAGGERHEART.CONFIG.Condition.dead.description'
} }
}; };
export const conditions = {
vulnerable: {
id: 'vulnerable',
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description'
},
hidden: {
id: 'hidden',
name: 'DAGGERHEART.CONFIG.Condition.hidden.name',
img: 'icons/magic/perception/silhouette-stealth-shadow.webp',
description: 'DAGGERHEART.CONFIG.Condition.hidden.description'
},
restrained: {
id: 'restrained',
name: 'DAGGERHEART.CONFIG.Condition.restrained.name',
img: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
},
...defeatedConditions
};
export const defaultRestOptions = { export const defaultRestOptions = {
shortRest: () => ({ shortRest: () => ({
tendToWounds: { tendToWounds: {

View file

@ -42,4 +42,32 @@ export default class DHAttackAction extends DHDamageAction {
return result; return result;
} }
/**
* Generate a localized label array for this item subtype.
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/
_getLabels() {
const labels = [];
const { roll, range, damage } = this;
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`))
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
for (const { value, type } of damage.parts) {
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
const icons = Array.from(type)
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
.filter(Boolean);
if (icons.length === 0) {
labels.push(str);
} else {
labels.push({ value: str, icons });
}
}
return labels;
}
} }

View file

@ -1,19 +1,13 @@
import DHBaseAction from './baseAction.mjs'; import DHBaseAction from './baseAction.mjs';
export default class DHMacroAction extends DHBaseAction { export default class DHMacroAction extends DHBaseAction {
static defineSchema() { static extraSchemas = [...super.extraSchemas, 'macro'];
const fields = foundry.data.fields;
return {
...super.defineSchema(),
documentUUID: new fields.DocumentUUIDField({ type: 'Macro' })
};
}
async trigger(event, ...args) { async trigger(event, ...args) {
const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID, const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
macro = await fromUuid(fixUUID); macro = await fromUuid(fixUUID);
try { try {
if (!macro) throw new Error(`No macro found for the UUID: ${this.documentUUID}.`); if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`);
macro.execute(); macro.execute();
} catch (error) { } catch (error) {
ui.notifications.error(error); ui.notifications.error(error);

View file

@ -106,6 +106,28 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
}, []); }, []);
options.scrollingTextData = textData; options.scrollingTextData = textData;
} }
if (changes.system?.resources) {
const defeatedSettings = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Automation
).defeated;
const typeForDefeated = ['character', 'adversary', 'companion'].find(x => x === this.parent.type);
if (defeatedSettings.enabled && typeForDefeated) {
const resource = typeForDefeated === 'companion' ? 'stress' : 'hitPoints';
if (changes.system.resources[resource]) {
const becameMax = changes.system.resources[resource].value === this.resources[resource].max;
const wasMax =
this.resources[resource].value === this.resources[resource].max &&
this.resources[resource].value !== changes.system.resources[resource].value;
if (becameMax) {
this.parent.toggleDefeated(true);
} else if (wasMax) {
this.parent.toggleDefeated(false);
}
}
}
}
} }
_onUpdate(changes, options, userId) { _onUpdate(changes, options, userId) {

View file

@ -103,7 +103,7 @@ export default class DhCharacter extends BaseDataActor {
}), }),
attack: new ActionField({ attack: new ActionField({
initial: { initial: {
name: 'Attack', name: 'Unarmed Attack',
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp', img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
_id: foundry.utils.randomID(), _id: foundry.utils.randomID(),
systemPath: 'attack', systemPath: 'attack',
@ -394,19 +394,22 @@ export default class DhCharacter extends BaseDataActor {
return this.parent.effects.find(x => x.type === 'beastform'); 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() { get usedUnarmed() {
const primaryWeaponEquipped = this.primaryWeapon?.system?.equipped; if (this.primaryWeapon?.system?.equipped || this.secondaryWeapon?.system?.equipped) return null;
const secondaryWeaponEquipped = this.secondaryWeapon?.system?.equipped;
return !primaryWeaponEquipped && !secondaryWeaponEquipped const attack = foundry.utils.deepClone(this.attack);
? { if (this.activeBeastform) {
...this.attack, attack.name = 'DAGGERHEART.ITEMS.Beastform.attackName';
uuid: this.attack.uuid, attack.img = 'icons/creatures/claws/claw-straight-brown.webp';
id: this.attack.id,
name: this.activeBeastform ? 'DAGGERHEART.ITEMS.Beastform.attackName' : this.attack.name,
img: this.activeBeastform ? 'icons/creatures/claws/claw-straight-brown.webp' : this.attack.img,
actor: this.parent
} }
: null; return attack;
} }
get sheetLists() { get sheetLists() {
@ -599,7 +602,20 @@ export default class DhCharacter extends BaseDataActor {
} }
prepareDerivedData() { prepareDerivedData() {
const baseHope = this.resources.hope.value + (this.companion?.system?.resources?.hope ?? 0); 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.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait; this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;

View file

@ -40,12 +40,14 @@ export default class DhCompanion extends BaseDataActor {
experiences: new fields.TypedObjectField( experiences: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
name: new fields.StringField({}), name: new fields.StringField({}),
value: new fields.NumberField({ integer: true, initial: 0 }) value: new fields.NumberField({ integer: true, initial: 0 }),
description: new fields.StringField(),
core: new fields.BooleanField({ initial: false })
}), }),
{ {
initial: { initial: {
experience1: { value: 2 }, experience1: { value: 2, core: true },
experience2: { value: 2 } experience2: { value: 2, core: true }
} }
} }
), ),
@ -134,6 +136,23 @@ export default class DhCompanion extends BaseDataActor {
} }
} }
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() { async _preDelete() {
if (this.partner) { if (this.partner) {
await this.partner.update({ 'system.companion': null }); await this.partner.update({ 'system.companion': null });

View file

@ -110,6 +110,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
if (this.hasTarget) { if (this.hasTarget) {
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0; this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
this.currentTargets = this.getTargetList(); this.currentTargets = this.getTargetList();
// this.registerTargetHook();
if (this.targetMode === true && this.hasRoll) { if (this.targetMode === true && this.hasRoll) {
this.targetShort = this.targets.reduce( this.targetShort = this.targets.reduce(

View file

@ -8,4 +8,10 @@ export default class DhCombatant extends foundry.abstract.TypeDataModel {
actionTokens: new fields.NumberField({ required: true, integer: true, initial: 3 }) actionTokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
}; };
} }
get isDefeated() {
const { unconscious, defeated, dead } = CONFIG.DH.GENERAL.conditions;
const defeatedConditions = new Set([unconscious.id, defeated.id, dead.id]);
return this.defeated || this.actor?.statuses.intersection(defeatedConditions)?.size;
}
} }

View file

@ -8,3 +8,4 @@ export { default as BeastformField } from './beastformField.mjs';
export { default as DamageField } from './damageField.mjs'; export { default as DamageField } from './damageField.mjs';
export { default as HealingField } from './healingField.mjs'; export { default as HealingField } from './healingField.mjs';
export { default as RollField } from './rollField.mjs'; export { default as RollField } from './rollField.mjs';
export { default as MacroField } from './macroField.mjs';

View file

@ -29,7 +29,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }), bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
custom: new fields.SchemaField({ custom: new fields.SchemaField({
enabled: new fields.BooleanField({ label: 'Custom Formula' }), enabled: new fields.BooleanField({ label: 'Custom Formula' }),
formula: new FormulaField({ label: 'Formula' }) formula: new FormulaField({ label: 'Formula', initial: "" })
}) })
}; };
} }

View file

@ -0,0 +1,7 @@
const fields = foundry.data.fields;
export default class MacroField extends fields.DocumentUUIDField {
constructor(context = {}) {
super({ type: "Macro" }, context);
}
}

View file

@ -82,9 +82,10 @@ export class ActionsField extends MappingField {
*/ */
export class ActionField extends foundry.data.fields.ObjectField { export class ActionField extends foundry.data.fields.ObjectField {
getModel(value) { getModel(value) {
if(value && !value.type) value.type = 'attack';
return ( return (
game.system.api.models.actions.actionsTypes[value.type] ?? game.system.api.models.actions.actionsTypes[value.type] ??
game.system.api.models.actions.actionsTypes.attack null
); );
} }
@ -93,7 +94,6 @@ export class ActionField extends foundry.data.fields.ObjectField {
/** @override */ /** @override */
_cleanType(value, options) { _cleanType(value, options) {
if (!(typeof value === 'object')) value = {}; if (!(typeof value === 'object')) value = {};
const cls = this.getModel(value); const cls = this.getModel(value);
if (cls) return cls.cleanData(value, options); if (cls) return cls.cleanData(value, options);
return value; return value;

View file

@ -144,7 +144,8 @@ export default class DHArmor extends AttachableItem {
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/ */
_getLabels() { _getLabels() {
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`]; const labels = [];
if(this.baseScore) labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`)
return labels; return labels;
} }

View file

@ -77,6 +77,7 @@ export default class DHDomainCard extends BaseDataItem {
const tags = [ const tags = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`), game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
this.domainLabel, this.domainLabel,
`${game.i18n.localize('DAGGERHEART.GENERAL.levelShort')}: ${this.level}`,
`${game.i18n.localize('DAGGERHEART.ITEMS.DomainCard.recallCost')}: ${this.recallCost}` `${game.i18n.localize('DAGGERHEART.ITEMS.DomainCard.recallCost')}: ${this.recallCost}`
]; ];
@ -88,15 +89,16 @@ export default class DHDomainCard extends BaseDataItem {
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/ */
_getLabels() { _getLabels() {
const labels = [ const labels = [];
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
this.domainLabel, if (this.type) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`));
{ if (this.domainLabel) labels.push(this.domainLabel);
if (this.recallCost) {
labels.push({
value: `${this.recallCost}`, //converts the number to a string value: `${this.recallCost}`, //converts the number to a string
icons: ['fa-bolt'] icons: ['fa-bolt']
});
} }
];
return labels; return labels;
} }
} }

View file

@ -1,5 +1,5 @@
import AttachableItem from './attachableItem.mjs'; import AttachableItem from './attachableItem.mjs';
import { ActionsField, ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
export default class DHWeapon extends AttachableItem { export default class DHWeapon extends AttachableItem {
/** @inheritDoc */ /** @inheritDoc */
@ -18,12 +18,23 @@ export default class DHWeapon extends AttachableItem {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
...super.defineSchema(), ...super.defineSchema(),
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1, label: "DAGGERHEART.GENERAL.Tiers.singular" }), tier: new fields.NumberField({
required: true,
integer: true,
initial: 1,
min: 1,
label: 'DAGGERHEART.GENERAL.Tiers.singular'
}),
equipped: new fields.BooleanField({ initial: false }), equipped: new fields.BooleanField({ initial: false }),
//SETTINGS //SETTINGS
secondary: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ITEMS.Weapon.secondaryWeapon" }), secondary: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }),
burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded', label: "DAGGERHEART.GENERAL.burden" }), burden: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.burden,
initial: 'oneHanded',
label: 'DAGGERHEART.GENERAL.burden'
}),
weaponFeatures: new fields.ArrayField( weaponFeatures: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.StringField({ value: new fields.StringField({
@ -209,26 +220,23 @@ export default class DHWeapon extends AttachableItem {
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/ */
_getLabels() { _getLabels() {
const labels = [];
const { roll, range, damage } = this.attack; const { roll, range, damage } = this.attack;
const labels = [ if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`), if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`)
];
for (const { value, type } of damage.parts) { for (const { value, type } of damage.parts) {
const str = [value.dice]; const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
if (value.bonus) str.push(value.bonus.signedString());
const icons = Array.from(type) const icons = Array.from(type)
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
.filter(Boolean); .filter(Boolean);
const labelValue = str.join('');
if (icons.length === 0) { if (icons.length === 0) {
labels.push(labelValue); labels.push(str);
} else { } else {
labels.push({ value: labelValue, icons }); labels.push({ value: str, icons });
} }
} }

View file

@ -404,7 +404,27 @@ export const defaultCompanionTier = {
start: 2, start: 2,
end: 10 end: 10
}, },
initialAchievements: {}, initialAchievements: {
experience: {
nr: 1,
modifier: 2
}
},
/* Improved this. Quick solution for companions */
extraAchievements: {
5: {
experience: {
nr: 1,
modifier: 2
}
},
8: {
experience: {
nr: 1,
modifier: 2
}
}
},
availableOptions: 1, availableOptions: 1,
domainCardByLevel: 0, domainCardByLevel: 0,
options: { options: {

View file

@ -26,6 +26,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
return acc; return acc;
}, {}) }, {})
: {}; : {};
const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => { const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => {
const id = foundry.utils.randomID(); const id = foundry.utils.randomID();
acc[id] = { uuid: null, itemUuid: null, level: i }; acc[id] = { uuid: null, itemUuid: null, level: i };
@ -42,6 +43,20 @@ export class DhLevelup extends foundry.abstract.DataModel {
belongingLevels.push(i); belongingLevels.push(i);
} }
/* Improve. Temporary handling for Companion new experiences */
Object.keys(tier.extraAchievements ?? {}).forEach(key => {
const level = Number(key);
if (level >= startLevel && level <= endLevel) {
const levelExtras = tier.extraAchievements[level];
if (levelExtras.experience) {
levels[level].achievements.experiences[foundry.utils.randomID()] = {
name: '',
modifier: levelExtras.experience.modifier
};
}
}
});
tiers[key] = { tiers[key] = {
name: tier.name, name: tier.name,
belongingLevels: belongingLevels, belongingLevels: belongingLevels,

View file

@ -55,6 +55,22 @@ export default class DhAppearance extends foundry.abstract.DataModel {
showGenericStatusEffects: new fields.BooleanField({ showGenericStatusEffects: new fields.BooleanField({
initial: true, initial: true,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label' label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label'
}),
extendCharacterDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendCharacterDescriptions.label'
}),
extendAdversaryDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendAdversaryDescriptions.label'
}),
extendEnvironmentDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendEnvironmentDescriptions.label'
}),
extendItemDescriptions: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendItemDescriptions.label'
}) })
}; };
} }

View file

@ -50,6 +50,36 @@ export default class DhAutomation extends foundry.abstract.DataModel {
required: true, required: true,
initial: false, initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.playerCanEditSheet.label' label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.playerCanEditSheet.label'
}),
defeated: new fields.SchemaField({
enabled: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.enabled.label'
}),
overlay: new fields.BooleanField({
required: true,
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.overlay.label'
}),
characterDefault: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.defeatedConditions,
initial: CONFIG.DH.GENERAL.defeatedConditions.unconscious.id,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.characterDefault.label'
}),
adversaryDefault: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.defeatedConditions,
initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.adversaryDefault.label'
}),
companionDefault: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.defeatedConditions,
initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label'
})
}) })
}; };
} }

View file

@ -137,9 +137,10 @@ export default class DamageRoll extends DHRoll {
} }
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
const tmpRoll = Roll.fromTerms(part.roll.terms)._evaluateSync({ maximize: true }), const total = part.roll.dice.reduce((acc, term) => acc + term._faces*term._number, 0);
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); if (total > 0) {
part.roll.terms.push(...this.formatModifier(criticalBonus)); part.roll.terms.push(...this.formatModifier(total));
}
} }
/* To Remove When Reaction System */ /* To Remove When Reaction System */

View file

@ -222,26 +222,27 @@ export const registerRollDiceHooks = () => {
) )
return; return;
const actor = await fromUuid(config.source.actor), const actor = await fromUuid(config.source.actor);
updates = []; let updates = [];
if (!actor) return; if (!actor) return;
if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1 }); if (config.roll.isCritical || config.roll.result.duality === 1)
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 }); updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 }); if (config.roll.isCritical) updates.push({ key: 'stress', value: 1, total: -1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
if (config.rerolledRoll) { if (config.rerolledRoll) {
if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1) if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
updates.push({ key: 'hope', value: -1 }); updates.push({ key: 'hope', value: -1, total: 1, enabled: true });
if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: 1 }); if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.rerolledRoll.result.duality === -1) updates.push({ key: 'fear', value: -1 }); if (config.rerolledRoll.result.duality === -1)
updates.push({ key: 'fear', value: -1, total: 1, enabled: true });
} }
if (updates.length) { if (updates.length) {
const target = actor.system.partner ?? actor; const target = actor.system.partner ?? actor;
if (!['dead', 'unconscious'].some(x => actor.statuses.has(x))) { if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
setTimeout(() => { if (config.rerolledRoll) target.modifyResource(updates);
target.modifyResource(updates); else config.costs = [...(config.costs ?? []), ...updates];
}, 50);
} }
} }
@ -254,5 +255,7 @@ export const registerRollDiceHooks = () => {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId); const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id); if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
} }
return;
}); });
}; };

View file

@ -118,7 +118,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
for (const statusId of this.statuses) { for (const statusId of this.statuses) {
const status = CONFIG.statusEffects.find(s => s.id === statusId); const status = CONFIG.statusEffects.find(s => s.id === statusId);
tags.push(game.i18n.localize(status.name)); if (status) tags.push(game.i18n.localize(status.name));
} }
return tags; return tags;

View file

@ -2,6 +2,7 @@ import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
import { damageKeyToNumber } from '../helpers/utils.mjs'; import { damageKeyToNumber } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
export default class DhpActor extends Actor { export default class DhpActor extends Actor {
/** /**
@ -142,9 +143,6 @@ export default class DhpActor extends Actor {
}, {}) }, {})
}); });
this.update(getUpdate()); this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
}
} }
if (subclassFeatureState.class) { if (subclassFeatureState.class) {
@ -195,10 +193,6 @@ export default class DhpActor extends Actor {
} }
}); });
this.sheet.render(); this.sheet.render();
if (this.system.companion) {
this.system.companion.updateLevel(newLevel);
}
} }
} }
@ -219,16 +213,6 @@ export default class DhpActor extends Actor {
core: true core: true
} }
}); });
if (this.system.companion) {
await this.system.companion.update({
[`system.experiences.${experienceKey}`]: {
name: '',
value: experience.modifier,
core: true
}
});
}
} }
} }
@ -405,6 +389,7 @@ export default class DhpActor extends Actor {
}; };
} }
const levelChange = this.system.levelData.level.changed - this.system.levelData.level.current;
await this.update({ await this.update({
system: { system: {
levelData: { levelData: {
@ -417,8 +402,21 @@ export default class DhpActor extends Actor {
}); });
this.sheet.render(); this.sheet.render();
if (this.system.companion) { if (this.system.companion && !this.system.companion.system.levelData.canLevelUp) {
this.system.companion.updateLevel(this.system.levelData.level.changed); const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionLevelup.confirmTitle')
},
content: game.i18n.format('DAGGERHEART.ACTORS.Character.companionLevelup.confirmText', {
name: this.system.companion.name,
levelChange: levelChange
})
});
if (!confirmed) return;
await this.system.companion.updateLevel(this.system.companion.system.levelData.level.current + levelChange);
new DhCompanionLevelUp(this.system.companion).render({ force: true });
} }
} }
@ -720,4 +718,21 @@ export default class DhpActor extends Actor {
value: 1 value: 1
}); });
} }
async toggleDefeated(defeatedState) {
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
const { unconscious, defeated, dead } = CONFIG.DH.GENERAL.conditions;
const defeatedConditions = new Set([unconscious.id, defeated.id, dead.id]);
if (!defeatedState) {
for (let defeatedId of defeatedConditions) {
await this.toggleStatusEffect(defeatedId, { overlay: settings.overlay, active: defeatedState });
}
} else {
const noDefeatedConditions = this.statuses.intersection(defeatedConditions).size === 0;
if (noDefeatedConditions) {
const condition = settings[`${this.type}Default`];
await this.toggleStatusEffect(condition, { overlay: settings.overlay, active: defeatedState });
}
}
}
} }

View file

@ -1,6 +1,5 @@
export default class DhpChatMessage extends foundry.documents.ChatMessage { export default class DhpChatMessage extends foundry.documents.ChatMessage {
targetHook = null; targetHook = null;
targetSelection = null;
async renderHTML() { async renderHTML() {
const actor = game.actors.get(this.speaker.actor); const actor = game.actors.get(this.speaker.actor);
@ -24,7 +23,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
/** @inheritDoc */ /** @inheritDoc */
prepareData() { prepareData() {
if (this.isAuthor && this.targetSelection === null) this.targetSelection = this.system.targets?.length > 0; if (this.isAuthor && this.targetSelection === undefined) this.targetSelection = this.system.targets?.length > 0;
super.prepareData(); super.prepareData();
} }
@ -70,11 +69,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
} }
} }
if(!game.user.isGM && !this.isAuthor && !this.speakerActor?.isOwner) { if(!game.user.isGM) {
const applyButtons = html.querySelector(".apply-buttons");
applyButtons?.remove();
if(!this.isAuthor && !this.speakerActor?.isOwner) {
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button"); const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
buttons.forEach(b => b.remove()); buttons.forEach(b => b.remove());
} }
} }
}
addChatListeners(html) { addChatListeners(html) {
html.querySelectorAll('.damage-button').forEach(element => html.querySelectorAll('.damage-button').forEach(element =>

View file

@ -72,4 +72,8 @@ export default class DHToken extends TokenDocument {
} }
return attributes; return attributes;
} }
_shouldRecordMovementHistory() {
return false;
}
} }

View file

@ -49,11 +49,11 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
const longRest = element.dataset.tooltip?.startsWith('#longRest#'); const longRest = element.dataset.tooltip?.startsWith('#longRest#');
if (shortRest || longRest) { if (shortRest || longRest) {
const key = element.dataset.tooltip.slice(shortRest ? 11 : 10); const key = element.dataset.tooltip.slice(shortRest ? 11 : 10);
const downtimeOptions = shortRest
? CONFIG.DH.GENERAL.defaultRestOptions.shortRest()
: CONFIG.DH.GENERAL.defaultRestOptions.longRest();
const move = downtimeOptions[key]; const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[
element.dataset.restType
].moves;
const move = moves[key];
const description = await TextEditor.enrichHTML(move.description); const description = await TextEditor.enrichHTML(move.description);
html = await foundry.applications.handlebars.renderTemplate( html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`, `systems/daggerheart/templates/ui/tooltip/downtime.hbs`,

View file

@ -251,13 +251,15 @@ export const adjustRange = (rangeVal, decrease) => {
}; };
export const updateActorTokens = async (actor, update) => { export const updateActorTokens = async (actor, update) => {
await actor.prototypeToken.update(update); await actor.prototypeToken.update({ ...update });
/* Update the tokens in all scenes belonging to Actor */ /* Update the tokens in all scenes belonging to Actor */
for (let token of actor.getDependentTokens()) { for (let token of actor.getDependentTokens()) {
const tokenActor = token.baseActor ?? token.actor; const tokenActor = token.baseActor ?? token.actor;
if (tokenActor?.id === actor.id) { if (tokenActor?.id === actor.id) {
await token.update(update); await token.update({
...update
});
} }
} }
}; };
@ -341,7 +343,7 @@ export const itemAbleRollParse = (value, actor, item) => {
const model = isItemTarget ? item : actor; const model = isItemTarget ? item : actor;
try { try {
return Roll.replaceFormulaData(slicedValue, model?.getRollData?.() ?? model); return Roll.replaceFormulaData(slicedValue, isItemTarget || !model?.getRollData ? model : model.getRollData());
} catch (_) { } catch (_) {
return ''; return '';
} }
@ -370,7 +372,7 @@ export function getScrollTextData(resources, resource, key) {
export function createScrollText(actor, optionsData) { export function createScrollText(actor, optionsData) {
if (actor && optionsData?.length) { if (actor && optionsData?.length) {
actor.getDependentTokens().forEach(token => { actor.getActiveTokens().forEach(token => {
optionsData.forEach(data => { optionsData.forEach(data => {
const { text, ...options } = data; const { text, ...options } = data;
canvas.interface.createScrollingText(token.getCenterPoint(), data.text, { canvas.interface.createScrollingText(token.getCenterPoint(), data.text, {

View file

@ -15,9 +15,8 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs',
'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs', 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs',
'systems/daggerheart/templates/actionTypes/damage.hbs', 'systems/daggerheart/templates/actionTypes/damage.hbs',
'systems/daggerheart/templates/actionTypes/healing.hbs',
'systems/daggerheart/templates/actionTypes/resource.hbs', 'systems/daggerheart/templates/actionTypes/resource.hbs',
'systems/daggerheart/templates/actionTypes/uuid.hbs', 'systems/daggerheart/templates/actionTypes/macro.hbs',
'systems/daggerheart/templates/actionTypes/uses.hbs', 'systems/daggerheart/templates/actionTypes/uses.hbs',
'systems/daggerheart/templates/actionTypes/roll.hbs', 'systems/daggerheart/templates/actionTypes/roll.hbs',
'systems/daggerheart/templates/actionTypes/save.hbs', 'systems/daggerheart/templates/actionTypes/save.hbs',

View file

@ -86,7 +86,7 @@ const registerMenus = () => {
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.variantRules.hint'), hint: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.variantRules.hint'),
icon: CONFIG.DH.SETTINGS.menu.VariantRules.Icon, icon: CONFIG.DH.SETTINGS.menu.VariantRules.Icon,
type: DhVariantRuleSettings, type: DhVariantRuleSettings,
restricted: false restricted: true
}); });
}; };

View file

@ -122,7 +122,7 @@
}, },
"_id": "TCKVaVweyJzhEArX", "_id": "TCKVaVweyJzhEArX",
"systemPath": "actions", "systemPath": "actions",
"type": "", "type": "attack",
"description": "", "description": "",
"img": "icons/creatures/claws/claw-curved-jagged-yellow.webp", "img": "icons/creatures/claws/claw-curved-jagged-yellow.webp",
"chatDisplay": true, "chatDisplay": true,

View file

@ -103,7 +103,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -102,7 +102,8 @@
} }
] ]
}, },
"img": "icons/weapons/daggers/dagger-bone-black.webp" "img": "icons/weapons/daggers/dagger-bone-black.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -114,7 +114,8 @@
} }
] ]
}, },
"img": "icons/magic/unholy/beam-ringed-impact-purple.webp" "img": "icons/magic/unholy/beam-ringed-impact-purple.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
} }
] ]
}, },
"img": "icons/weapons/bows/longbow-recurve-leather-brown.webp" "img": "icons/weapons/bows/longbow-recurve-leather-brown.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"roll": { "roll": {
"bonus": 0, "bonus": 0,
"type": "attack" "type": "attack"
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
} }
] ]
}, },
"range": "" "range": "",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -111,7 +111,8 @@
} }
] ]
}, },
"img": "icons/creatures/claws/claw-straight-brown.webp" "img": "icons/creatures/claws/claw-straight-brown.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -107,7 +107,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -112,7 +112,8 @@
} }
] ]
}, },
"img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp" "img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
] ]
}, },
"name": "Club", "name": "Club",
"img": "icons/weapons/clubs/club-banded-barbed-black.webp" "img": "icons/weapons/clubs/club-banded-barbed-black.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
} }
] ]
}, },
"img": "icons/magic/light/beam-rays-magenta.webp" "img": "icons/magic/light/beam-rays-magenta.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -96,7 +96,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -102,7 +102,8 @@
] ]
}, },
"name": "Fist Slam", "name": "Fist Slam",
"img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp" "img": "icons/skills/melee/unarmed-punch-fist-yellow-red.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -113,7 +113,8 @@
} }
] ]
}, },
"img": "icons/weapons/daggers/dagger-straight-cracked.webp" "img": "icons/weapons/daggers/dagger-straight-cracked.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -107,7 +107,8 @@
} }
] ]
}, },
"img": "icons/weapons/daggers/dagger-twin-green.webp" "img": "icons/weapons/daggers/dagger-twin-green.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -114,7 +114,8 @@
] ]
}, },
"range": "far", "range": "far",
"img": "icons/weapons/staves/staff-ornate-purple.webp" "img": "icons/weapons/staves/staff-ornate-purple.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
} }
] ]
}, },
"range": "" "range": "",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -95,7 +95,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
} }
] ]
}, },
"img": "icons/magic/nature/root-vines-grow-brown.webp" "img": "icons/magic/nature/root-vines-grow-brown.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
} }
] ]
}, },
"img": "icons/magic/symbols/rune-sigil-rough-white-teal.webp" "img": "icons/magic/symbols/rune-sigil-rough-white-teal.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"bonus": 0, "bonus": 0,
"type": "attack" "type": "attack"
}, },
"range": "" "range": "",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -107,7 +107,8 @@
} }
] ]
}, },
"img": "icons/creatures/claws/claw-hooked-curved.webp" "img": "icons/creatures/claws/claw-hooked-curved.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -107,7 +107,8 @@
} }
] ]
}, },
"img": "icons/creatures/claws/claw-straight-brown.webp" "img": "icons/creatures/claws/claw-straight-brown.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"bonus": 0, "bonus": 0,
"type": "attack" "type": "attack"
}, },
"range": "" "range": "",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -96,7 +96,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -87,7 +87,7 @@
"img": "icons/weapons/polearms/spear-flared-steel.webp", "img": "icons/weapons/polearms/spear-flared-steel.webp",
"_id": "jmrgFi8AUL6LTbtU", "_id": "jmrgFi8AUL6LTbtU",
"systemPath": "actions", "systemPath": "actions",
"type": "", "type": "attack",
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",

View file

@ -109,7 +109,8 @@
] ]
}, },
"img": "icons/creatures/claws/claw-hooked-barbed.webp", "img": "icons/creatures/claws/claw-hooked-barbed.webp",
"range": "" "range": "",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -96,7 +96,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
} }
] ]
}, },
"img": "icons/weapons/staves/staff-animal-skull-bull.webp" "img": "icons/weapons/staves/staff-animal-skull-bull.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -87,7 +87,7 @@
"img": "icons/creatures/claws/claw-talons-glowing-orange.webp", "img": "icons/creatures/claws/claw-talons-glowing-orange.webp",
"_id": "W2KpXQNCg6Nnorbz", "_id": "W2KpXQNCg6Nnorbz",
"systemPath": "actions", "systemPath": "actions",
"type": "", "type": "attack",
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",

View file

@ -108,7 +108,8 @@
"bonus": -2, "bonus": -2,
"type": "attack" "type": "attack"
}, },
"range": "" "range": "",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -100,7 +100,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -96,7 +96,8 @@
"roll": { "roll": {
"bonus": 1, "bonus": 1,
"type": "attack" "type": "attack"
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -107,7 +107,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
} }
] ]
}, },
"img": "icons/weapons/bows/shortbow-recurve-yellow.webp" "img": "icons/weapons/bows/shortbow-recurve-yellow.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -107,7 +107,8 @@
} }
] ]
}, },
"img": "icons/creatures/slimes/slime-movement-dripping-pseudopods-green.webp" "img": "icons/creatures/slimes/slime-movement-dripping-pseudopods-green.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -95,7 +95,8 @@
} }
] ]
}, },
"img": "icons/skills/melee/sword-shield-stylized-white.webp" "img": "icons/skills/melee/sword-shield-stylized-white.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
} }
] ]
}, },
"img": "icons/weapons/polearms/spear-hooked-rounded.webp" "img": "icons/weapons/polearms/spear-hooked-rounded.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -111,7 +111,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -109,7 +109,8 @@
} }
] ]
}, },
"img": "icons/skills/melee/strike-blade-hooked-orange-blue.webp" "img": "icons/skills/melee/strike-blade-hooked-orange-blue.webp",
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -108,7 +108,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

View file

@ -103,7 +103,8 @@
"base": false "base": false
} }
] ]
} },
"type": "attack"
} }
}, },
"flags": {}, "flags": {},

Some files were not shown because too many files have changed in this diff Show more