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",
"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": {
"FIELDS": {
@ -233,13 +237,14 @@
},
"APPLICATIONS": {
"CharacterCreation": {
"setupTabs": {
"tabs": {
"ancestry": "Ancestry",
"community": "Community",
"class": "Class",
"experience": "Experience",
"traits": "Traits",
"domainCards": "Domain Cards"
"domainCards": "Domain Cards",
"equipment": "Equipment"
},
"ancestryNamePlaceholder": "Your ancestry's name",
"buttonTitle": "Character Setup",
@ -816,6 +821,10 @@
"name": "Dead",
"description": "The character is dead"
},
"defeated": {
"name": "Defeated",
"description": "This adversary is defeated."
},
"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."
@ -1865,7 +1874,8 @@
"tier3": "Tier 3",
"tier4": "tier 4",
"domains": "Domains",
"downtime": "Downtime"
"downtime": "Downtime",
"rules": "Rules"
},
"Tiers": {
"singular": "Tier",
@ -1934,6 +1944,7 @@
"itemResource": "Item Resource",
"label": "Label",
"level": "Level",
"levelShort": "Lv",
"levelUp": "Level Up",
"loadout": "Loadout",
"max": "Max",
@ -2081,7 +2092,12 @@
"FIELDS": {
"displayFear": { "label": "Fear Display" },
"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": {
"token": "Tokens",
@ -2099,6 +2115,13 @@
"label": "Damage Reduction Rules 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": {
"label": "Hope & Fear",
"gm": { "label": "GM" },
@ -2130,6 +2153,9 @@
"label": "Players Can Manually Edit Character Settings",
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
}
},
"defeated": {
"title": "Defeated Handling"
}
},
"Homebrew": {
@ -2369,7 +2395,8 @@
"rulesOn": "Rules On",
"rulesOff": "Rules Off",
"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 = {
traits: this.character.system.traits,
ancestryName: '',
ancestryName: {
primary: '',
secondary: ''
},
mixedAncestry: false,
primaryAncestry: this.character.system.ancestry ?? {},
secondaryAncestry: {},
@ -83,131 +86,70 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' },
ancestry: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/ancestry.hbs' },
community: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/community.hbs' },
class: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/class.hbs' },
traits: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/traits.hbs' },
experience: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/experience.hbs' },
domainCards: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/domainCards.hbs' },
equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' },
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' },
ancestry: { template: 'systems/daggerheart/templates/characterCreation/tabs/ancestry.hbs' },
community: { template: 'systems/daggerheart/templates/characterCreation/tabs/community.hbs' },
class: { template: 'systems/daggerheart/templates/characterCreation/tabs/class.hbs' },
traits: { template: 'systems/daggerheart/templates/characterCreation/tabs/traits.hbs' },
experience: { template: 'systems/daggerheart/templates/characterCreation/tabs/experience.hbs' },
domainCards: { template: 'systems/daggerheart/templates/characterCreation/tabs/domainCards.hbs' },
equipment: { template: 'systems/daggerheart/templates/characterCreation/equipment.hbs' },
// story: { template: 'systems/daggerheart/templates/characterCreation/story.hbs' },
footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' }
};
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: {
active: true,
cssClass: '',
group: 'setup',
id: 'ancestry',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.ancestry'
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.ancestry'
},
community: {
active: false,
cssClass: '',
group: 'setup',
id: 'community',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.community'
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.community'
},
class: {
active: false,
cssClass: '',
group: 'setup',
id: 'class',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.class'
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.class'
},
traits: {
active: false,
cssClass: '',
group: 'setup',
id: 'traits',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.traits'
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.traits'
},
experience: {
active: false,
cssClass: '',
group: 'setup',
id: 'experience',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.experience'
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.experience'
},
domainCards: {
active: false,
cssClass: '',
group: 'setup',
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) {
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)) {
v.active = this.tabGroups[v.group]
? this.tabGroups[v.group] === v.id
@ -232,37 +174,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
case 'domainCards':
v.disabled = this.setup.visibility < 6;
break;
case 'equipment':
v.disabled = this.setup.visibility < 7;
break;
}
}
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) {
super._attachPartListeners(partId, htmlElement, options);
@ -274,14 +194,71 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
});
}
async _preFirstRender(_context, _options) {
this.tabGroups.primary = 'setup';
this.tabGroups.setup = 'ancestry';
}
async _prepareContext(_options) {
this.tabGroups.setup = this.tabGroups.setup ?? 'ancestry';
const context = await super._prepareContext(_options);
context.tabs = this._getTabs(this.constructor.TABS);
const availableTraitModifiers = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait }));
for (let trait of Object.values(this.setup.traits).filter(x => x.value !== null)) {
const index = availableTraitModifiers.findIndex(x => x.key === trait.value);
if (index !== -1) {
availableTraitModifiers.splice(index, 1);
}
}
context.suggestedTraits = this.setup.class.system
? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => {
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
})
: [];
context.traits = {
values: Object.keys(this.setup.traits).map(traitKey => {
const trait = this.setup.traits[traitKey];
const options = [...availableTraitModifiers];
if (trait.value !== null && !options.some(x => x.key === trait.value))
options.push({ key: trait.value, name: trait.value });
return {
...trait,
key: traitKey,
name: game.i18n.localize(abilities[traitKey].label),
options: options
};
})
};
context.traits.nrTotal = Object.keys(context.traits.values).length;
context.traits.nrSelected = this.getNrSelectedTrait();
context.experience = {
values: this.setup.experiences,
nrTotal: Object.keys(this.setup.experiences).length,
nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 1 : 0), 0)
};
context.mixedAncestry = Number(this.setup.mixedAncestry);
const { primary, secondary, overwrite } = this.setup.ancestryName;
context.ancestryName = overwrite ?? (primary && secondary ? `${primary}/${secondary}` : primary);
context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' };
context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' };
context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
context.classDomains = context.class.uuid
? context.class.system.domains.map(key => game.i18n.localize(allDomainData[key].label))
: [];
context.domainCards = Object.keys(this.setup.domainCards).reduce((acc, x) => {
acc[x] = { ...this.setup.domainCards[x], compendium: 'domains' };
return acc;
}, {});
context.visibility = this.setup.visibility;
return context;
}
@ -289,7 +266,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _preparePartContext(partId, context) {
switch (partId) {
case 'footer':
context.isLastTab = this.tabGroups.setup === 'domainCards' || this.tabGroups.primary !== 'setup';
context.isLastTab = this.tabGroups.setup === 'equipment';
switch (this.tabGroups.setup) {
case null:
case 'ancestry':
@ -307,69 +284,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
case 'experience':
context.nextDisabled = this.setup.visibility === 5;
break;
case 'domainCards':
context.nextDisabled = this.setup.visibility === 6;
break;
}
break;
case 'setup':
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
const availableTraitModifiers = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait }));
for (let trait of Object.values(this.setup.traits).filter(x => x.value !== null)) {
const index = availableTraitModifiers.findIndex(x => x.key === trait.value);
if (index !== -1) {
availableTraitModifiers.splice(index, 1);
}
}
context.suggestedTraits = this.setup.class.system
? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => {
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
})
: [];
context.traits = {
values: Object.keys(this.setup.traits).map(traitKey => {
const trait = this.setup.traits[traitKey];
const options = [...availableTraitModifiers];
if (trait.value !== null && !options.some(x => x.key === trait.value))
options.push({ key: trait.value, name: trait.value });
return {
...trait,
key: traitKey,
name: game.i18n.localize(abilities[traitKey].label),
options: options
};
})
};
context.traits.nrTotal = Object.keys(context.traits.values).length;
context.traits.nrSelected = this.getNrSelectedTrait();
context.experience = {
values: this.setup.experiences,
nrTotal: Object.keys(this.setup.experiences).length,
nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 1 : 0), 0)
};
context.mixedAncestry = Number(this.setup.mixedAncestry);
context.ancestryName = this.setup.ancestryName;
context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' };
context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' };
context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
context.classDomains = context.class.uuid
? context.class.system.domains.map(key => game.i18n.localize(allDomainData[key].label))
: [];
context.domainCards = Object.keys(this.setup.domainCards).reduce((acc, x) => {
acc[x] = { ...this.setup.domainCards[x], compendium: 'domains' };
return acc;
}, {});
context.visibility = this.setup.visibility;
break;
case 'equipment':
const suggestions = await this.getEquipmentSuggestions(
@ -438,8 +357,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
getUpdateVisibility() {
switch (this.setup.visibility) {
case 7:
return 7;
case 6:
return 6;
return Object.values(this.setup.domainCards).every(x => x.uuid) ? 7 : 6;
case 5:
return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
case 4:
@ -505,7 +426,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
const presets = {
compendium: 'daggerheart',
folder: equipment.includes(type) ? "equipments" : type,
folder: equipment.includes(type) ? 'equipments' : type,
render: {
noFolder: true
}
@ -565,6 +486,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
case 6:
this.tabGroups.setup = 'domainCards';
break;
case 7:
this.tabGroups.setup = 'equipment';
break;
}
this.render();
@ -576,9 +500,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
? this.setup.secondaryAncestry.system.secondaryFeature
: this.setup.primaryAncestry.system.secondaryFeature;
const { primary, secondary, overwrite } = this.setup.ancestryName;
const ancestry = {
...this.setup.primaryAncestry,
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name,
name: overwrite ?? (primary && secondary ? `${primary}/${secondary}` : primary),
system: {
...this.setup.primaryAncestry.system,
features: [
@ -650,13 +575,14 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
this.setup.ancestryName = item.name;
this.setup.ancestryName.primary = item.name;
this.setup.primaryAncestry = {
...item,
effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid
};
} else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) {
this.setup.ancestryName.secondary = item.name;
this.setup.secondaryAncestry = {
...item,
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) {
const recoverable = x.system.actions.reduce((acc, action) => {
if (
(action.uses.recovery && (action.uses.recovery === 'longRest') === !this.shortrest) ||
action.uses.recovery === 'shortRest'
action.uses.recovery &&
((action.uses.recovery === 'longRest' && !this.shortrest) ||
action.uses.recovery === 'shortRest')
) {
acc.push({
title: x.name,

View file

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

View file

@ -37,12 +37,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
const experienceIncreases = Object.values(advancementChoices.experience ?? {});
const experienceIncreaseValues = experienceIncreases
.filter(exp => exp.data.length > 0)
.flatMap(exp =>
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);
.flatMap(exp => exp.data);
context.experienceIncreases = {
values: experienceIncreaseValues,
active: experienceIncreases.length > 0,
@ -72,6 +67,28 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
const levelKeys = Object.keys(this.levelup.levels);
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
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 = {};
for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey];

View file

@ -31,8 +31,19 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
};
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: {
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 DhCompanionlevelUp from '../levelup/companionLevelup.mjs';
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -11,8 +10,7 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
position: { width: 455, height: 'auto' },
actions: {
addExperience: DHCompanionSettings.#addExperience,
removeExperience: DHCompanionSettings.#removeExperience,
levelUp: DHCompanionSettings.#levelUp
removeExperience: DHCompanionSettings.#removeExperience
}
};
@ -121,12 +119,4 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
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

@ -638,15 +638,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
ability: abilityLabel
})
});
setTimeout(() => {
this.consumeResource(result?.costs);
}, 50);
this.consumeResource(result?.costs);
}
// Remove when Action Refactor part #2 done
async consumeResource(costs) {
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 resource = usefulResources[c.key];
return {

View file

@ -1,3 +1,4 @@
import DhCompanionLevelUp from '../../levelup/companionLevelup.mjs';
import DHBaseActorSheet from '../api/base-actor.mjs';
/**@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 {
static DEFAULT_OPTIONS = {
classes: ['actor', 'companion'],
position: { width: 300 },
actions: {}
position: { width: 340 },
actions: {
levelManagement: DhCompanionSheet.#levelManagement
}
};
static PARTS = {
@ -25,4 +28,25 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
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 { 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
*/
@ -137,6 +154,8 @@ export default function DHApplicationMixin(Base) {
docs.filter(doc => doc).forEach(doc => (doc.apps[this.id] = this));
if (!!this.options.contextMenus.length) this._createContextMenus();
this.#autoExtendDescriptions(context);
}
/** @inheritDoc */
@ -149,6 +168,7 @@ export default function DHApplicationMixin(Base) {
async _onRender(context, options) {
await super._onRender(context, options);
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 selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
const newExtensible = newElement.querySelector(selector);
if (!newExtensible) continue;
newExtensible.classList.add('extended');
const descriptionElement = newExtensible.querySelector('.invetory-description');
if (descriptionElement) {
this.#prepareInventoryDescription(newExtensible, descriptionElement);
}
newExtensible?.classList.add('extended');
}
}
@ -395,6 +409,7 @@ export default function DHApplicationMixin(Base) {
context.source = this.document;
context.fields = this.document.schema.fields;
context.systemFields = this.document.system.schema.fields;
context.settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
return context;
}
@ -404,32 +419,69 @@ export default function DHApplicationMixin(Base) {
/**
* 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>}
*/
async #prepareInventoryDescription(extensibleElement, descriptionElement) {
const parent = extensibleElement.closest('[data-item-uuid], [data-action-id]');
const { actionId, itemUuid } = parent?.dataset || {};
if (!actionId && !itemUuid) return;
async #prepareInventoryDescription(context) {
// Get all inventory item elements with a data-item-uuid attribute
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;
const doc = itemUuid
? await getDocFromElement(extensibleElement)
: this.document.system.attack?.id === actionId
? this.document.system.attack
: this.document.system.actions?.get(actionId);
if (!doc) return;
//get doc by uuid
const doc = await fromUuid(itemUuid);
const description = game.i18n.localize(doc.system?.description ?? doc.description);
const isAction = !!actionId;
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description,
{
relativeTo: isAction ? doc.parent : doc,
rollData: doc.getRollData?.(),
secrets: isAction ? doc.parent.isOwner : doc.isOwner
//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);
// Enrich the description and attach it;
const isAction = doc.documentName === 'Action';
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description,
{
relativeTo: isAction ? doc.parent : doc,
rollData: doc.getRollData?.(),
secrets: isAction ? doc.parent.isOwner : doc.isOwner
}
);
}
}
/* -------------------------------------------- */
/* 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);
}
);
}
}
/* -------------------------------------------- */
@ -437,8 +489,6 @@ export default function DHApplicationMixin(Base) {
/* -------------------------------------------- */
static async #addNewItem(event, target) {
const { type } = target.dataset;
const createChoice = await foundry.applications.api.DialogV2.wait({
classes: ['dh-style', 'two-big-buttons'],
buttons: [
@ -606,10 +656,12 @@ export default function DHApplicationMixin(Base) {
static async #toggleExtended(_, target) {
const container = target.closest('.inventory-item');
const extensible = container?.querySelector('.extensible');
const t = extensible?.classList.toggle('extended');
extensible?.classList.toggle('extended');
}
const descriptionElement = extensible?.querySelector('.invetory-description');
if (t && !!descriptionElement) await this.#prepareInventoryDescription(extensible, descriptionElement);
async #activeExtended(element) {
const extensible = element?.querySelector('.extensible');
extensible?.classList.add('extended');
}
}

View file

@ -24,7 +24,7 @@ export default function ItemAttachmentSheet(Base) {
...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',
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() {
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) {
const update = {
system: {

View file

@ -15,17 +15,19 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
acc.push(effect);
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) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
const statusData = statusMap.get(status);
acc.push({
name: game.i18n.localize(statusData.name),
statuses: [status],
img: statusData.icon,
tint: effect.tint
});
if (statusData) {
acc.push({
name: game.i18n.localize(statusData.name),
statuses: [status],
img: statusData.icon,
tint: effect.tint
});
}
}
}

View file

@ -164,39 +164,49 @@ export const healingTypes = {
}
};
export const conditions = {
vulnerable: {
id: 'vulnerable',
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
icon: 'icons/magic/control/silhouette-fall-slip-prone.webp',
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.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'
export const defeatedConditions = {
defeated: {
id: 'defeated',
name: 'DAGGERHEART.CONFIG.Condition.defeated.name',
img: 'icons/magic/control/fear-fright-mask-orange.webp',
description: 'DAGGERHEART.CONFIG.Condition.defeated.description'
},
unconscious: {
id: 'unconscious',
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'
},
dead: {
id: 'dead',
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'
}
};
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 = {
shortRest: () => ({
tendToWounds: {

View file

@ -42,4 +42,32 @@ export default class DHAttackAction extends DHDamageAction {
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';
export default class DHMacroAction extends DHBaseAction {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
documentUUID: new fields.DocumentUUIDField({ type: 'Macro' })
};
}
static extraSchemas = [...super.extraSchemas, 'macro'];
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);
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();
} catch (error) {
ui.notifications.error(error);

View file

@ -106,6 +106,28 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
}, []);
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) {

View file

@ -103,7 +103,7 @@ export default class DhCharacter extends BaseDataActor {
}),
attack: new ActionField({
initial: {
name: 'Attack',
name: 'Unarmed Attack',
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
_id: foundry.utils.randomID(),
systemPath: 'attack',
@ -394,19 +394,22 @@ export default class DhCharacter extends BaseDataActor {
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() {
const primaryWeaponEquipped = this.primaryWeapon?.system?.equipped;
const secondaryWeaponEquipped = this.secondaryWeapon?.system?.equipped;
return !primaryWeaponEquipped && !secondaryWeaponEquipped
? {
...this.attack,
uuid: this.attack.uuid,
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;
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() {
@ -599,7 +602,20 @@ export default class DhCharacter extends BaseDataActor {
}
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.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(
new fields.SchemaField({
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: {
experience1: { value: 2 },
experience2: { value: 2 }
experience1: { value: 2, core: true },
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() {
if (this.partner) {
await this.partner.update({ 'system.companion': null });

View file

@ -110,6 +110,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
if (this.hasTarget) {
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
this.currentTargets = this.getTargetList();
// this.registerTargetHook();
if (this.targetMode === true && this.hasRoll) {
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 })
};
}
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 HealingField } from './healingField.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' }),
custom: new fields.SchemaField({
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 {
getModel(value) {
if(value && !value.type) value.type = 'attack';
return (
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 */
_cleanType(value, options) {
if (!(typeof value === 'object')) value = {};
const cls = this.getModel(value);
if (cls) return cls.cleanData(value, options);
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.
*/
_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;
}

View file

@ -77,6 +77,7 @@ export default class DHDomainCard extends BaseDataItem {
const tags = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
this.domainLabel,
`${game.i18n.localize('DAGGERHEART.GENERAL.levelShort')}: ${this.level}`,
`${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.
*/
_getLabels() {
const labels = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
this.domainLabel,
{
const labels = [];
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
icons: ['fa-bolt']
}
];
});
}
return labels;
}
}

View file

@ -1,5 +1,5 @@
import AttachableItem from './attachableItem.mjs';
import { ActionsField, ActionField } from '../fields/actionField.mjs';
import { ActionField } from '../fields/actionField.mjs';
export default class DHWeapon extends AttachableItem {
/** @inheritDoc */
@ -18,12 +18,23 @@ export default class DHWeapon extends AttachableItem {
const fields = foundry.data.fields;
return {
...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 }),
//SETTINGS
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" }),
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'
}),
weaponFeatures: new fields.ArrayField(
new fields.SchemaField({
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.
*/
_getLabels() {
const labels = [];
const { roll, range, damage } = this.attack;
const labels = [
game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`),
game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`)
];
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 = [value.dice];
if (value.bonus) str.push(value.bonus.signedString());
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
const icons = Array.from(type)
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
.filter(Boolean);
const labelValue = str.join('');
if (icons.length === 0) {
labels.push(labelValue);
labels.push(str);
} else {
labels.push({ value: labelValue, icons });
labels.push({ value: str, icons });
}
}

View file

@ -404,7 +404,27 @@ export const defaultCompanionTier = {
start: 2,
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,
domainCardByLevel: 0,
options: {

View file

@ -26,6 +26,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
return acc;
}, {})
: {};
const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => {
const id = foundry.utils.randomID();
acc[id] = { uuid: null, itemUuid: null, level: i };
@ -42,6 +43,20 @@ export class DhLevelup extends foundry.abstract.DataModel {
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] = {
name: tier.name,
belongingLevels: belongingLevels,

View file

@ -55,6 +55,22 @@ export default class DhAppearance extends foundry.abstract.DataModel {
showGenericStatusEffects: new fields.BooleanField({
initial: true,
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,
initial: false,
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) {
const tmpRoll = Roll.fromTerms(part.roll.terms)._evaluateSync({ maximize: true }),
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
part.roll.terms.push(...this.formatModifier(criticalBonus));
const total = part.roll.dice.reduce((acc, term) => acc + term._faces*term._number, 0);
if (total > 0) {
part.roll.terms.push(...this.formatModifier(total));
}
}
/* To Remove When Reaction System */

View file

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

View file

@ -2,6 +2,7 @@ import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs';
import { damageKeyToNumber } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
export default class DhpActor extends Actor {
/**
@ -142,9 +143,6 @@ export default class DhpActor extends Actor {
}, {})
});
this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
}
}
if (subclassFeatureState.class) {
@ -195,10 +193,6 @@ export default class DhpActor extends Actor {
}
});
this.sheet.render();
if (this.system.companion) {
this.system.companion.updateLevel(newLevel);
}
}
}
@ -219,16 +213,6 @@ export default class DhpActor extends Actor {
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({
system: {
levelData: {
@ -417,8 +402,21 @@ export default class DhpActor extends Actor {
});
this.sheet.render();
if (this.system.companion) {
this.system.companion.updateLevel(this.system.levelData.level.changed);
if (this.system.companion && !this.system.companion.system.levelData.canLevelUp) {
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
});
}
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 {
targetHook = null;
targetSelection = null;
async renderHTML() {
const actor = game.actors.get(this.speaker.actor);
@ -24,7 +23,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
/** @inheritDoc */
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();
}
@ -70,9 +69,13 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
}
}
if(!game.user.isGM && !this.isAuthor && !this.speakerActor?.isOwner) {
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
buttons.forEach(b => b.remove());
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");
buttons.forEach(b => b.remove());
}
}
}

View file

@ -72,4 +72,8 @@ export default class DHToken extends TokenDocument {
}
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#');
if (shortRest || longRest) {
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);
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,

View file

@ -251,13 +251,15 @@ export const adjustRange = (rangeVal, decrease) => {
};
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 */
for (let token of actor.getDependentTokens()) {
const tokenActor = token.baseActor ?? token.actor;
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;
try {
return Roll.replaceFormulaData(slicedValue, model?.getRollData?.() ?? model);
return Roll.replaceFormulaData(slicedValue, isItemTarget || !model?.getRollData ? model : model.getRollData());
} catch (_) {
return '';
}
@ -370,7 +372,7 @@ export function getScrollTextData(resources, resource, key) {
export function createScrollText(actor, optionsData) {
if (actor && optionsData?.length) {
actor.getDependentTokens().forEach(token => {
actor.getActiveTokens().forEach(token => {
optionsData.forEach(data => {
const { text, ...options } = data;
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/ui/combatTracker/combatTrackerSection.hbs',
'systems/daggerheart/templates/actionTypes/damage.hbs',
'systems/daggerheart/templates/actionTypes/healing.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/roll.hbs',
'systems/daggerheart/templates/actionTypes/save.hbs',

View file

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

View file

@ -122,7 +122,7 @@
},
"_id": "TCKVaVweyJzhEArX",
"systemPath": "actions",
"type": "",
"type": "attack",
"description": "",
"img": "icons/creatures/claws/claw-curved-jagged-yellow.webp",
"chatDisplay": true,
@ -708,4 +708,4 @@
"_id": "89yAh30vaNQOALlz",
"sort": 500000,
"_key": "!actors!89yAh30vaNQOALlz"
}
}

View file

@ -103,7 +103,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -768,4 +769,4 @@
],
"effects": [],
"_key": "!actors!G7jiltRjgvVhZewm"
}
}

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": {},
@ -319,4 +320,4 @@
],
"effects": [],
"_key": "!actors!vNIbYQ4YSzNf0WPE"
}
}

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": {},
@ -817,4 +818,4 @@
],
"effects": [],
"_key": "!actors!WPEOIGfclNJxWb87"
}
}

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": {},
@ -393,4 +394,4 @@
],
"effects": [],
"_key": "!actors!JRhrrEg5UroURiAD"
}
}

View file

@ -103,7 +103,8 @@
"roll": {
"bonus": 0,
"type": "attack"
}
},
"type": "attack"
}
},
"flags": {},
@ -514,4 +515,4 @@
}
],
"_key": "!actors!0ts6CGd93lLqGZI5"
}
}

View file

@ -109,7 +109,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -528,4 +529,4 @@
],
"effects": [],
"_key": "!actors!h5RuhzGL17dW5FBT"
}
}

View file

@ -109,7 +109,8 @@
}
]
},
"range": ""
"range": "",
"type": "attack"
}
},
"flags": {},
@ -1284,4 +1285,4 @@
],
"effects": [],
"_key": "!actors!dgH3fW9FTYLaIDvS"
}
}

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": {},
@ -467,4 +468,4 @@
],
"effects": [],
"_key": "!actors!71qKDLKO3CsrNkdy"
}
}

View file

@ -107,7 +107,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -441,4 +442,4 @@
],
"effects": [],
"_key": "!actors!B4LZcGuBAHzyVdzy"
}
}

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": {},
@ -526,4 +527,4 @@
],
"effects": [],
"_key": "!actors!2UeZ0tEe7AzgSJNd"
}
}

View file

@ -108,7 +108,8 @@
]
},
"name": "Club",
"img": "icons/weapons/clubs/club-banded-barbed-black.webp"
"img": "icons/weapons/clubs/club-banded-barbed-black.webp",
"type": "attack"
}
},
"flags": {},
@ -598,4 +599,4 @@
],
"effects": [],
"_key": "!actors!8Zkqk1jU09nKL2fy"
}
}

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": {},
@ -561,4 +562,4 @@
],
"effects": [],
"_key": "!actors!jDmHqGvzg5wjgmxE"
}
}

View file

@ -96,7 +96,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -313,4 +314,4 @@
],
"effects": [],
"_key": "!actors!99TqczuQipBmaB8i"
}
}

View file

@ -102,7 +102,8 @@
]
},
"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": {},
@ -631,4 +632,4 @@
],
"effects": [],
"_key": "!actors!uOP5oT9QzXPlnf3p"
}
}

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": {},
@ -379,4 +380,4 @@
],
"effects": [],
"_key": "!actors!ZxWaWPdzFIUPNC62"
}
}

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": {},
@ -459,4 +460,4 @@
],
"effects": [],
"_key": "!actors!CBBuEXAlLKFMJdjg"
}
}

View file

@ -114,7 +114,8 @@
]
},
"range": "far",
"img": "icons/weapons/staves/staff-ornate-purple.webp"
"img": "icons/weapons/staves/staff-ornate-purple.webp",
"type": "attack"
}
},
"flags": {},
@ -723,4 +724,4 @@
],
"effects": [],
"_key": "!actors!0NxCSugvKQ4W8OYZ"
}
}

View file

@ -103,7 +103,8 @@
}
]
},
"range": ""
"range": "",
"type": "attack"
}
},
"flags": {},
@ -453,4 +454,4 @@
],
"effects": [],
"_key": "!actors!tyBOpLfigAhI9bU3"
}
}

View file

@ -95,7 +95,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -312,4 +313,4 @@
],
"effects": [],
"_key": "!actors!zx99sOGTXicP4SSD"
}
}

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": {},
@ -473,4 +474,4 @@
],
"effects": [],
"_key": "!actors!9x2xY9zwc3xzbXo5"
}
}

View file

@ -108,7 +108,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -384,4 +385,4 @@
],
"effects": [],
"_key": "!actors!pnyjIGxxvurcWmTv"
}
}

View file

@ -108,7 +108,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -502,4 +503,4 @@
],
"effects": [],
"_key": "!actors!kE4dfhqmIQpNd44e"
}
}

View file

@ -109,7 +109,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -654,4 +655,4 @@
],
"effects": [],
"_key": "!actors!2VN3BftageoTTIzu"
}
}

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": {},
@ -439,4 +440,4 @@
],
"effects": [],
"_key": "!actors!SxSOkM4bcVOFyjbo"
}
}

View file

@ -109,7 +109,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -474,4 +475,4 @@
],
"effects": [],
"_key": "!actors!5lphJAgzoqZI3VoG"
}
}

View file

@ -109,7 +109,8 @@
"bonus": 0,
"type": "attack"
},
"range": ""
"range": "",
"type": "attack"
}
},
"flags": {},
@ -421,4 +422,4 @@
}
],
"_key": "!actors!NoRZ1PqB8N5wcIw0"
}
}

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": {},
@ -512,4 +513,4 @@
],
"effects": [],
"_key": "!actors!tBWHW00epmMnkawe"
}
}

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": {},
@ -499,4 +500,4 @@
],
"effects": [],
"_key": "!actors!wNzeuQLfLUMvgHlQ"
}
}

View file

@ -109,7 +109,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -568,4 +569,4 @@
],
"effects": [],
"_key": "!actors!wR7cFKrHvRzbzhBT"
}
}

View file

@ -103,7 +103,8 @@
"bonus": 0,
"type": "attack"
},
"range": ""
"range": "",
"type": "attack"
}
},
"flags": {},
@ -411,4 +412,4 @@
}
],
"_key": "!actors!TLzY1nDw0Bu9Ud40"
}
}

View file

@ -96,7 +96,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -313,4 +314,4 @@
],
"effects": [],
"_key": "!actors!P7h54ZePFPHpYwvB"
}
}

View file

@ -87,7 +87,7 @@
"img": "icons/weapons/polearms/spear-flared-steel.webp",
"_id": "jmrgFi8AUL6LTbtU",
"systemPath": "actions",
"type": "",
"type": "attack",
"description": "",
"chatDisplay": true,
"actionType": "action",
@ -435,4 +435,4 @@
"_id": "bfhVWMBUh61b9J6n",
"sort": 0,
"_key": "!actors!bfhVWMBUh61b9J6n"
}
}

View file

@ -109,7 +109,8 @@
]
},
"img": "icons/creatures/claws/claw-hooked-barbed.webp",
"range": ""
"range": "",
"type": "attack"
}
},
"flags": {},
@ -411,4 +412,4 @@
],
"effects": [],
"_key": "!actors!ChwwVqowFw8hJQwT"
}
}

View file

@ -96,7 +96,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -399,4 +400,4 @@
],
"effects": [],
"_key": "!actors!OsLG2BjaEdTZUJU9"
}
}

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": {},
@ -646,4 +647,4 @@
],
"effects": [],
"_key": "!actors!PELRry1vqjBzSAlr"
}
}

View file

@ -109,7 +109,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -452,4 +453,4 @@
],
"effects": [],
"_key": "!actors!8VZIgU12cB3cvlyH"
}
}

View file

@ -109,7 +109,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -484,4 +485,4 @@
],
"effects": [],
"_key": "!actors!YnObCleGjPT7yqEc"
}
}

View file

@ -87,7 +87,7 @@
"img": "icons/creatures/claws/claw-talons-glowing-orange.webp",
"_id": "W2KpXQNCg6Nnorbz",
"systemPath": "actions",
"type": "",
"type": "attack",
"description": "",
"chatDisplay": true,
"actionType": "action",
@ -745,4 +745,4 @@
"_id": "OMQ0v6PE8s1mSU0K",
"sort": 900000,
"_key": "!actors!OMQ0v6PE8s1mSU0K"
}
}

View file

@ -108,7 +108,8 @@
"bonus": -2,
"type": "attack"
},
"range": ""
"range": "",
"type": "attack"
}
},
"flags": {},
@ -453,4 +454,4 @@
}
],
"_key": "!actors!IIWV4ysJPFPnTP7W"
}
}

View file

@ -100,7 +100,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -343,4 +344,4 @@
],
"effects": [],
"_key": "!actors!4PfLnaCrOcMdb4dK"
}
}

View file

@ -96,7 +96,8 @@
"roll": {
"bonus": 1,
"type": "attack"
}
},
"type": "attack"
}
},
"flags": {},
@ -313,4 +314,4 @@
],
"effects": [],
"_key": "!actors!5s8wSvpyC5rxY5aD"
}
}

View file

@ -107,7 +107,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -535,4 +536,4 @@
],
"effects": [],
"_key": "!actors!fmfntuJ8mHRCAktP"
}
}

View file

@ -103,7 +103,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -658,4 +659,4 @@
],
"effects": [],
"_key": "!actors!8KWVLWXFhlY2kYx0"
}
}

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": {},
@ -621,4 +622,4 @@
],
"effects": [],
"_key": "!actors!8mJYMpbLTb8qIOrr"
}
}

View file

@ -103,7 +103,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -632,4 +633,4 @@
],
"effects": [],
"_key": "!actors!dsfB3YhoL5SudvS2"
}
}

View file

@ -103,7 +103,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -600,4 +601,4 @@
],
"effects": [],
"_key": "!actors!xIICT6tEdnA7dKDV"
}
}

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": {},
@ -601,4 +602,4 @@
],
"effects": [],
"_key": "!actors!SHXedd9zZPVfUgUa"
}
}

View file

@ -103,7 +103,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -371,4 +372,4 @@
],
"effects": [],
"_key": "!actors!kabueAo6BALApWqp"
}
}

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": {},
@ -376,4 +377,4 @@
],
"effects": [],
"_key": "!actors!VENwg7xEFcYObjmT"
}
}

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": {},
@ -378,4 +379,4 @@
],
"effects": [],
"_key": "!actors!uRtghKE9mHlII4rs"
}
}

View file

@ -111,7 +111,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -388,4 +389,4 @@
],
"effects": [],
"_key": "!actors!mK3A5FTx6k8iPU3F"
}
}

View file

@ -108,7 +108,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -665,4 +666,4 @@
],
"effects": [],
"_key": "!actors!i2UNbRvgyoSs07M6"
}
}

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": {},
@ -611,4 +612,4 @@
],
"effects": [],
"_key": "!actors!r1mbfSSwKWdcFdAU"
}
}

View file

@ -108,7 +108,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -572,4 +573,4 @@
],
"effects": [],
"_key": "!actors!6hbqmxDXFOzZJDk4"
}
}

View file

@ -103,7 +103,8 @@
"base": false
}
]
}
},
"type": "attack"
}
},
"flags": {},
@ -602,4 +603,4 @@
],
"effects": [],
"_key": "!actors!MI126iMOOobQ1Obn"
}
}

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